appid = isset($toutiao['toutiao_mini_appid']) ? trim($toutiao['toutiao_mini_appid']) : ''; $this->appSecret = isset($toutiao['toutiao_mini_secret']) ? trim($toutiao['toutiao_mini_secret']) : ''; $this->paySalt = isset($toutiao['toutiao_pay_salt']) ? trim($toutiao['toutiao_pay_salt']) : ''; $this->token = isset($toutiao['toutiao_token']) ? trim($toutiao['toutiao_token']) : ''; } /** * 获得用户信息 根据code 获取session_key * @param array|string $openid * @return $userInfo */ public function getUserInfo($code) { try { $dat = array( 'appid' => $this->appid, 'secret' => $this->appSecret, 'code' => $code, "anonymous_code" => '' ); if (isset($code)) { $res = self::jsonPost(self::CODE2_SESSION_URL, json_encode($dat)); $data = json_decode($res, true); if ($data['err_no'] == 0) { return $data['data']; } else { throw new ValidateException("获取失败"); } } else { throw new ValidateException("获取失败"); } } catch (\Throwable $e) { throw new ValidateException($e->getMessage()); } } /** * 加密数据解密 * @param $sessionKey * @param $iv * @param $encryptData * @return $userInfo */ public static function encryptor($sessionKey, $iv, $encrypted) { try { $decrypted = openssl_decrypt( base64_decode($encrypted, true), 'aes-128-cbc', base64_decode($sessionKey, true), OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, base64_decode($iv, true) ); } catch (Exception $e) { throw new EncryptionException($e->getMessage(), EncryptionException::ERROR_DECRYPT_AES); } if (is_null($result = json_decode(self::decode($decrypted), true))) { throw new EncryptionException('ILLEGAL_BUFFER', EncryptionException::ILLEGAL_BUFFER); } return $result; } public static function decode($decrypted) { $pad = ord(substr($decrypted, -1)); if ($pad < 1 || $pad > 32) { $pad = 0; } return substr($decrypted, 0, (strlen($decrypted) - $pad)); } /** * 生成支付订单对象 * @param $openid * @param $out_trade_no * @param $total_fee * @param $attach * @param $body * @param string $detail * @param string $trade_type * @param array $options * @return Order */ protected static function paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = []) { $total_fee = bcmul($total_fee, 100, 0); $order = array_merge(compact('openid', 'out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type'), $options); if ($order['detail'] == '') unset($order['detail']); return new Order($order); } /** * 订单推送到抖音 * @param $data array 订单数据 * @note order_status 与 status须保持一致,但类型不同 * @return array */ public function pushOrder($order_id) { $orderInfo = StoreOrder::where(['order_id' => $order_id])->find(); $cart = StoreOrderCartInfo::where('oid', $orderInfo['id'])->find(); $cartInfo = (array)(json_decode($cart['cart_info'])); $userInfo = ToutiaoUser::where('uid', $cartInfo['uid'])->find(); $productInfo = (array)$cartInfo['productInfo']; $openid = $userInfo['openid']; //获取下单用户openid //组装商品 $item_list = [[ 'item_code' => (string)$productInfo['id'], 'img' => $productInfo['image'], 'title' => $productInfo['store_name'], 'amount' => $cartInfo['cart_num'], 'price' => (int)($orderInfo['pay_price']*100) ]]; //参数对应请查看官方文档,注意字段类型 // 组装订单 $detail = [ 'order_id' => $orderInfo['order_id'], 'create_time' => $orderInfo['add_time'], 'status' => "已支付", 'amount' => $orderInfo['total_num'], 'total_price' => (int)($orderInfo['total_price']*100), 'detail_url' => "pages/store/detail?id=" . $productInfo['id'], 'item_list' => $item_list ]; $param = [ 'access_token' => $this->get_token(), 'app_name' => "douyin", 'open_id' => $openid, 'update_time' => $this->getMillisecond(), 'order_detail' => json_encode($detail), 'order_type' => 0, 'order_status' => 1 ]; $this->jsonPost(self::PUSH_ORDER_URL, json_encode($param)); //请求 } public function getMillisecond() { list($t1, $t2) = explode(' ', microtime()); return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); } /**头条小程序二维码生成接口不限量永久 * @param $scene * @param null $page * @param null $width * @param null $autoColor * @param array $lineColor * @return \Psr\Http\Message\StreamInterface */ public function appCodeUnlimitService($scene, $page = null ,$width = 430, $autoColor = false, $lineColor = ['r' => 0, 'g' => 0, 'b' => 0]) { $data = [ 'app_name' => 'douyin', 'appid' => $this->appid, //订单号 'path' =>$page.'?'.$scene , //金额 单位:分 ]; $json = json_encode($data, 320); $res = json_decode(self::clientjsonPost(self::QRCODE_URL, $json),true); if($res['err_no']!=0)throw new ValidateException($res['err_msg']/* .'可点击右上角分享' */); return json_decode($res, true)['data']; } /** * 获得jsSdk支付参数 * @param $openid * @param $out_trade_no * @param $total_fee * @param $attach * @param $body * @param string $detail * @param string $trade_type * @param array $options * @return array|string */ public function jsPay( $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = []) { $price = $total_fee * 100; $data = [ 'app_id' => $this->appid, 'out_order_no' => $out_trade_no, //订单号 'total_amount' => $price, //金额 单位:分 'body' => $body, //支付的内容 'subject' => $body, //支付的标题 'valid_time' => 3600, 'cp_extra' => $attach, 'notify_url' => SystemConfigService::get('site_url') . Url::build('wap/Toutiao/notify'), ]; $data['sign'] = self::generate_sign($data); $json = json_encode($data, 320); $res = self::jsonPost(self::CREATE_ORDER_URL, $json); return json_decode($res, true)['data']; } /** * 签名 */ public function generate_sign($map) { $rList = []; foreach ($map as $k => $v) { if ($k == "other_settle_params" || $k == "app_id" || $k == "sign" || $k == "thirdparty_id") continue; $value = trim(strval($v)); if (is_array($v)) { $value = self::arrayToStr($v); } $len = strlen($value); if ($len > 1 && substr($value, 0, 1) == "\"" && substr($value, $len - 1) == "\"") $value = substr($value, 1, $len - 1); $value = trim($value); if ($value == "" || $value == "null") continue; $rList[] = $value; } $rList[] = $this->paySalt; sort($rList, SORT_STRING); return md5(implode('&', $rList)); } public static function arrayToStr($map) { $isMap = self::isArrMap($map); $result = ""; if ($isMap) { $result = "map["; } $keyArr = array_keys($map); if ($isMap) { sort($keyArr); } $paramsArr = array(); foreach ($keyArr as $k) { $v = $map[$k]; if ($isMap) { if (is_array($v)) { $paramsArr[] = sprintf("%s:%s", $k, self::arrayToStr($v)); } else { $paramsArr[] = sprintf("%s:%s", $k, trim(strval($v))); } } else { if (is_array($v)) { $paramsArr[] = self::arrayToStr($v); } else { $paramsArr[] = trim(strval($v)); } } } $result = sprintf("%s%s", $result, join(" ", $paramsArr)); if (!$isMap) { $result = sprintf("[%s]", $result); } else { $result = sprintf("%s]", $result); } return $result; } public static function isArrMap($map) { foreach ($map as $k => $v) { if (is_string($k)) { return true; } } return false; } /** * 获得验签 */ public function callbackSign($data) { if (!isset($data['msg_signature']) || !isset($data['timestamp']) || !isset($data['nonce']) || !isset($data['msg'])) { return false; } $arr = [ $this->token, (string)$data['timestamp'], (string)$data['nonce'], (string)$data['msg'] ]; Log::write($arr); sort($arr, SORT_STRING); $join_str = implode('', $arr); return sha1($join_str); } /** * 获得token */ public function get_token() { try { $param = ['appid' => $this->appid, 'secret' => $this->appSecret, 'grant_type' => "client_credential"]; $access_token = Cache::get('dy_accessToken'); if (empty($access_token)) { $data = $this->jsonPost(self::ACCESS_TOKEN_URL,json_encode($param)); $data=(array)json_decode($data); if ($data['err_no'] == 0) { $access_token = ((array)$data['data'])['access_token']; Cache::set('dy_accessToken', $access_token, ((array)$data['data'])['expires_in']); } } return $access_token; } catch (\Throwable $e) { throw new ValidateException($e->getMessage()); } } /** * [createRefund 订单退款] * @param [type] $order [订单相关信息] * @return [type] [description] * $order = array( * order_sn' => '', // 订单编号 * 'refund_sn' => '', // 退款编号 * 'total_amount' => '', // 订单金额(分) * 'body' => '', // 退款原因 */ public function createRefund($order) { $params = [ 'app_id' => $this->appid, // 是 小程序 id 'out_order_no' => (string)$order['order_id'], // 是 商户分配订单号,标识进行退款的订单 'out_refund_no' => (string)'refund_'.$order['order_id'], // 是 商户分配退款号 'refund_amount' => (int)($order['refund_price']*100), // 是 退款金额,单位[分] 'reason' => $order['refund_reason_wap_explain'] ?? '用户申请退款', // 是 退款理由,长度上限 100 'cp_extra' => '', // 否 开发者自定义字段,回调原样回传 'notify_url' => '', // 否 商户自定义回调地址 // 'sign' => '', // 是 开发者对核心字段签名, 签名方式见文档, 防止传输过程中出现意外 'thirdparty_id' => '', // 否,服务商模式接入必传 第三方平台服务商 id,非服务商模式留空 'disable_msg' => 1, // 否 是否屏蔽担保支付消息,1-屏蔽 'msg_page' => '', // 否 担保支付消息跳转页 ]; $params['sign']=self::generate_sign( $params); !empty($order['cp_extra']) && $params['cp_extra'] = $order['cp_extra']; !empty($order['all_settle']) && $params['all_settle'] = $order['all_settle']; !empty($config['thirdparty_id']) && $params['thirdparty_id'] = $config['thirdparty_id']; if (!empty($config['msg_page'])) { $params['disable_msg'] = 0; $params['msg_page'] = $config['msg_page']; } $params['sign'] = self::generate_sign($params); $url = self::REFUND_ORDER_URL; $response = $this->jsonPost($url, json_encode($params)); $result = json_decode($response, true); return $result; } /** * [queryRefund 退款查询] * @param [type] $refundSn [开发者侧的订单号, 不可重复] * @return [type] [description] */ public function queryRefund($refundSn) { $params = [ 'app_id' => $this->appid, // 小程序 AppID 'out_refund_no' => $refundSn, // 开发者侧的退款号 // 'sign' => '', // 开发者对核心字段签名, 签名方式见文档, 防止传输过程中出现意外 // 'thirdparty_id' => '', // 服务商模式接入必传 第三方平台服务商 id,非服务商模式留空 ]; !empty($config['thirdparty_id']) && $params['thirdparty_id'] = $config['thirdparty_id']; $params['sign'] = self::generate_sign($params); $url = self::$queryRefundUrl; $response = Http::post($url, json_encode($params)); $result = json_decode($response, true); return $result; } /** * [notifyRefund 退款回调验证] * @return [array] [返回数组格式的notify数据] */ public static function notifyRefund() { $data = $_POST; // 获取回调数据 $config = self::$config; if (!$data || empty($data['status'])) die('暂无回调信息'); $result = json_decode($data['msg'], true); // 进行签名验证 // 判断签名是否正确 判断支付状态 if ($result && $data['status']!='FAIL') { return $data; } else { return false; } } public function get_client_token(){ try { $param = ['client_key' => $this->appid, 'client_secret' => $this->appSecret, 'grant_type' => "client_credential"]; $access_token = Cache::get('dy_clientToken'); if (empty($access_token)) { $data = $this->jsonPost(self::CLIENT_TOKEN_URL,$param,0,'multipart/form-data'); $data=(array)json_decode($data); $data=(array)($data['data']); if ($data['error_code'] == 0) { $access_token = $data['access_token']; Cache::set('dy_clientToken', $access_token, ($data['expires_in'])); } } return $access_token; } catch (\Throwable $e) { throw new ValidateException($e->getMessage()); } } public function jsonPost($url, $data = NULL, $times = 0,$type="application/json; charset=utf-8") { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_TIMEOUT, 2); //超时时间2秒 curl_setopt($curl, CURLOPT_POSTFIELDS, $data); curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Content-Type: '.$type, 'Cache-Control: no-cache', 'Pragma: no-cache' )); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $res = curl_exec($curl); curl_close($curl); return $res; } public function clientjsonPost($url, $data = NULL, $times = 0) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_TIMEOUT, 2); //超时时间2秒 curl_setopt($curl, CURLOPT_POSTFIELDS, $data); curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json; charset=utf-8', 'Cache-Control: no-cache', 'Pragma: no-cache', 'access-token:'.$this->get_client_token() )); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $res = curl_exec($curl); curl_close($curl); return $res; } public static function curlGet($url = '', $options = array()) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 30); if (!empty($options)) { curl_setopt_array($ch, $options); } //https请求 不验证证书和host curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($ch); curl_close($ch); return $data; } public function curlPost($url = '', $postData = '', $options = array()) { if (is_array($postData)) { $postData = http_build_query($postData); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数 if (!empty($options)) { curl_setopt_array($ch, $options); } //https请求 不验证证书和host curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($ch); curl_close($ch); return $data; } }