API = $api; } /** * 获取 access token * @throws \Exception */ protected function getAccessToken() : string { $res = $this->post($this->API::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 $outOrderNo * @param $totalAmount * @param $openId * @return array|mixed * @throws \Exception */ public function createOrder($outOrderNo, $totalAmount, $openId): array { $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( $this->API::CREATE_ORDER, $data ); } /** * @param array $data * @return string */ public function getSign(array $data) { 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 string $code * @return array|mixed * @throws \Exception */ public function login($code = ''): array { return $this->post($this->API::LOGIN, [ 'appid' => $this->appId, 'secret' => $this->secret, 'code' => $code, ]); } /** * 接口请求 * * @param string $uri * @param array $data * @return array|mixed * @throws \Exception */ protected 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()); } } protected function setAccessFileDir(): void { $this->accessTokenDir = storage_path('app/bytedance'); } protected function setAccessFilePath(): void { $this->accessTokenFile = storage_path('app/bytedance/bytedance_access_token.json'); } protected function setNoticeUrl(): void { $this->noticeUrl = env('APP_URL').'/api/pay/bytedance/notify'; } }