123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- <?php
- namespace App\Helper;
- use App\Helper\Bytedance\ByteDanceAPI;
- use App\Models\Pay;
- use Carbon\Carbon;
- use GuzzleHttp\Client;
- use GuzzleHttp\Exception\GuzzleException;
- /**
- * Class ByteDance
- *
- * @package App\Helper
- * @property-read string $appId
- * @property-read string $slat
- * @property-read string $secret
- * @property-read string $token
- * @property-read string $accessTokenFile
- * @property-read string $accessToken
- * @property-read string $noticeUrl
- * @property-read string $validTimestamp
- */
- class ByteDance
- {
- private $appId = null;
- private $secret = null;
- private $slat = null;
- private $token = null;
- private $accessTokenFile = null;
- private $accessToken = null;
- private $noticeUrl = null;
- //订单过期时间(秒);
- private $validTimestamp = 24 * 60 * 60;
- /**
- * @param array $config
- * @return $this
- */
- public function factory($config = [])
- {
- $this->appId = $config['app_id'];
- $this->secret = $config['app_secret'];
- $this->slat = $config['slat'];
- $this->token = $config['token'];
- $this->accessTokenFile = storage_path('app/bytedance/bytedance_access_token.json');
- $this->accessToken = $this->checkAccessToken();
- $this->noticeUrl = env('APP_URL').'/api/pay/bytedance/notify';
- return $this;
- }
- /**
- * @param string $code
- * @return array|mixed
- * @throws \Exception
- */
- public function login($code = '')
- {
- return $this->post(ByteDanceAPI::LOGIN, [
- 'appid' => $this->appId,
- 'secret' => $this->secret,
- 'code' => $code,
- ]);
- }
- /**
- * @param string $sessionKey
- * @param string $iv
- * @param string $encrypted
- * @return array
- * @throws \Exception
- */
- public function decryptData(string $sessionKey, string $iv, string $encrypted): array
- {
- $decrypted = openssl_decrypt(
- base64_decode($encrypted,true),
- 'AES-128-CBC',
- base64_decode($sessionKey),
- OPENSSL_RAW_DATA,
- base64_decode($iv)
- );
- $decrypted = json_decode($decrypted,true);
- if(empty($decrypted)){
- throw new \Exception('解密数据错误');
- }
- return $decrypted;
- }
- /**
- * 校验access token 是否过期
- * @return mixed
- */
- private function checkAccessToken() : string
- {
- try {
- $dir = storage_path('app/bytedance');
- if (!is_dir($dir)) mkdir($dir, 0755);
- if (!is_file($this->accessTokenFile)) touch($this->accessTokenFile);
- $accessToken = file_get_contents($this->accessTokenFile);
- $accessToken = $accessToken ? json_decode($accessToken,true) : null;
- if (empty($accessToken) || $accessToken['expires_at'] < time()) {
- $accessToken = $this->getAccessToken();
- }else{
- $accessToken = $accessToken['access_token'];
- }
- return $accessToken;
- } catch (\Exception $e) {
- }
- }
- /**
- * 获取 access token
- * @throws \Exception
- */
- private function getAccessToken() : string
- {
- $res = $this->post(ByteDanceAPI::ACCESS_TOKEN, [
- 'grant_type' => 'client_credential',
- 'appid' => $this->appId,
- 'secret' => $this->secret,
- ]);
- if (!empty($res['err_no'])) {
- throw new \Exception('获取access token 错误');
- }
- file_put_contents($this->accessTokenFile, json_encode([
- 'access_token' => $res['access_token'],
- 'expires_at' => $res['expiresAt']
- ]));
- return $res['access_token'];
- }
- /**
- * 接口请求
- *
- * @param string $uri
- * @param array $data
- * @return array|mixed
- * @throws \Exception
- */
- private function post($uri = '', $data = []) : array
- {
- try {
- $client = new Client();
- $res = $client->post($uri, [
- 'verify' => false,
- 'headers' => ['Content-Type' => 'application/json'],
- 'body' => json_encode($data)
- ]);
- $stringBody = (string)$res->getBody();
- $res = json_decode($stringBody, true);
- if(!empty($res['err_no'])){
- throw new \Exception("请求字节跳动API接口错误,错误码:{$res['err_no']},错误信息:{$res['err_tips']}");
- }
- return $res['data'];
- } catch (GuzzleException $e) {
- \Log::error($e->getMessage());
- throw new \Exception($e->getMessage());
- }
- }
- /**
- * @param $outOrderNo
- * @param $totalAmount
- * @return array|mixed
- * @throws \Exception
- */
- public function createOrder($outOrderNo, $totalAmount)
- {
- $data = [
- 'app_id' => $this->appId,
- 'out_order_no' => $outOrderNo,
- 'total_amount' => intval($totalAmount * 100),
- 'subject' => "订单号:".$outOrderNo,
- 'body' => '抖音担保支付',
- 'valid_time' => $this->validTimestamp,
- 'sign' => $this->secret
- //'notify_url' => $notifyUrl, // 可以不设置 使用小程序后台设置的回调
- ];
- $data = array_filter($data);
- $data['sign'] = $this->getSign($data);
- return $this->post(
- ByteDanceAPI::CREATE_ORDER,
- $data
- );
- }
- /**
- * @param array $data
- * @return string
- */
- public function getSign(array $data)
- {
- $rList = array();
- foreach ($data as $k => $v) {
- if ($k == "other_settle_params" || $k == "app_id" || $k == "sign" || $k == "thirdparty_id")
- continue;
- $value = trim(strval($v));
- $len = strlen($value);
- if ($len > 1 && substr($value, 0, 1) == "\"" && substr($value, $len, $len - 1) == "\"")
- $value = substr($value, 1, $len - 1);
- $value = trim($value);
- if ($value == "" || $value == "null")
- continue;
- array_push($rList, $value);
- }
- array_push($rList, $this->slat);
- sort($rList, SORT_STRING);
- return md5(implode('&', $rList));
- }
- /**
- * @param array $data
- * @return string
- */
- public function getNotifySign(array $data)
- {
- $filtered = [];
- foreach ($data as $key => $value) {
- if (in_array($key, ['msg_signature', 'type'])) {
- continue;
- }
- $value = trim(strval($value));
- $len = strlen($value);
- if ($len > 1 && substr($value, 0, 1) == "\"" && substr($value, $len, $len - 1) == "\"")
- $value = substr($value, 1, $len - 1);
- $filtered[] =
- is_string($value)
- ? trim($value)
- : $value;
- }
- $filtered[] = trim($this->token);
- sort($filtered, SORT_STRING);
- return sha1(trim(implode('', $filtered)));
- }
- /**
- * @param \Closure $closure
- * @return \Illuminate\Http\JsonResponse
- * @throws \Exception
- */
- public function payNotify(\Closure $closure)
- {
- call_user_func($closure, $this->getNoticeData());
- return response()->json($data);
- if ($notify['msg_signature'] !== $this->getNotifySign($notify)) {
- \Log::error('回调验证错误');
- $data = [
- 'err_no' => 1,
- 'err_tips' => 'fail'
- ];
- } else {
- $order = json_decode($notify['msg'], true);
- //处理订单
- $payId = $order['cp_orderno'];
- $pay = Pay::find($payId);
- $pay->pay_type = $order['way'];
- $pay->serial_number = $order['channel_no'];
- $pay->status = $order['status'] == 'SUCCESS';
- $pay->pay_dt = Carbon::now()->toDateString();
- $pay->save();
- $data = [
- 'err_no' => 0,
- 'err_tips' => 'success'
- ];
- }
- return response()->json($data);
- }
- private function getNoticeData()
- {
- $notify = \request()->all();
- $notify = '{"msg":"{\"appid\":\"tt5b312d8cc40f46b701\",\"cp_orderno\":\"10022082800824490007\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"2022082822001477591433078541\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"PCP2022082822540531221069106962\",\"out_channel_order_no\":\"2022082822001477591433078541\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"71227862181355706950\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1661698458,\"message\":\"\",\"order_id\":\"7136939355201063205\",\"trade_item_list\":null,\"ec_pay_trade_no\":\"NEP2022082822540409483061846962\"}","msg_signature":"804a60e48936b14a739230cef21fe6204427732e","nonce":"78","timestamp":"1661698459","type":"payment"}';
- //获取订单信息
- $notify = json_decode($notify,true);
- \Log::info('抖音支付回调==>'.json_encode($notify,JSON_UNESCAPED_SLASHES));
- if($notify['msg_signature'] !== $this->getNotifySign($notify)){
- throw new \Exception('签名验证错误');
- }
- return json_decode($notify['msg'], true);
- }
- }
|