appId = '6587209115185920913';// 3659064244254722812 -- 6587209115185920913 $this->appSecret = '3f935d5f-bf65-467e-a61c-72cfb1d53960'; // dce31e3b-32da-45ed-8353-f2f041b5288f -- 3f935d5f-bf65-467e-a61c-72cfb1d53960 $this->merchantId = ''; // 需要从配置中获取或外部设置 // 根据环境设置沙箱标志 $this->isSandbox = !$isProduction; } /** * 生成授权URL * 用户需要通过浏览器访问此URL进行授权 * * @param string $redirectUri 重定向地址(回调地址) * @return string 授权URL */ public function generateAuthUrl($redirectUri) { $params = [ 'appId' => $this->appId, 'devId' => 133146508, // 蜂鸟开放平台 【开发者中心】->【应用管理】->【发起商户授权】获得的 'authCallbackUrl' => $redirectUri, ]; // 构建URL $queryString = http_build_query($params); return 'https://open.ele.me/app-auth?' . $queryString; } /** * 设置商户ID * * @param string $merchantId 商户ID */ public function setMerchantId($merchantId) { $this->merchantId = $merchantId; return $this; } /** * 生成签名 * 根据峰鸟开放平台API文档,签名算法采用SHA-256 * * @param array $params 参数数组 * @return string 签名结果 */ protected function generateSignature(array $params) { // Step 2: 按字典序排序 ksort($params); // Step 3: 拼接成字符串 $paramStr = ''; $first = true; foreach ($params as $key => $value) { if ($first) { $paramStr = "{$key}={$value}"; $first = false; } else { $paramStr .= "&{$key}={$value}"; } } // Step 4: 拼接 appSecret $signBefore = $this->appSecret . $paramStr; // Step 5: 使用SHA-256加密 $signature = hash('sha256', $signBefore); Yii::info("[FengniaAuth] Sign Before: {$signBefore}, Signature: {$signature}"); return $signature; } /** * 获取 Token URL * * @return string */ protected function getTokenUrl() { return $this->isSandbox ? self::TOKEN_URL_SANDBOX : self::TOKEN_URL_PRODUCTION; } /** * 获取刷新 Token URL * * @return string */ protected function getRefreshTokenUrl() { return $this->isSandbox ? self::REFRESH_TOKEN_URL_SANDBOX : self::REFRESH_TOKEN_URL_PRODUCTION; } /** * 获取AccessToken * 使用授权码换取令牌 * * 文档:获取token接口 * 入参:grant_type, code, app_id, merchant_id, timestamp, signature * 出参:access_token, refresh_token, expire_in * * @param string $code 授权码(来自授权回调) * @param string $merchantId 商户ID(可选,如果已设置则使用已设置的) * @return array 返回格式:['success' => true/false, 'data' => [...], 'error' => ''] */ public function getAccessToken($code, $merchantId = null) { if ($merchantId) { $this->setMerchantId($merchantId); } if (empty($this->merchantId)) { return [ 'success' => false, 'error' => '商户ID未设置', ]; } $timestamp = (int)(microtime(true) * 1000); // 毫秒级时间戳 $params = [ 'grant_type' => self::GRANT_TYPE_AUTH_CODE, 'code' => $code, 'app_id' => $this->appId, 'merchant_id' => $this->merchantId, 'timestamp' => $timestamp, ]; // 沙箱环境不传code if (getenv('YII_ENV') !== 'production') { //unset($params['code']); //$params['code'] = ""; } // 生成签名 $signature = $this->generateSignature($params); $params['signature'] = $signature; $url = $this->getTokenUrl(); // 发送POST请求 $response = HttpClient::post($url, $params, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaAuth] GetAccessToken Response: " . json_encode($response)); return $this->parseResponse($response); } /** * 刷新AccessToken * 使用刷新令牌获取新的AccessToken * * 文档:刷新token接口 * 入参:grant_type, app_id, merchant_id, timestamp, refresh_token, signature * 出参:access_token, refresh_token, expire_in * * @param string $refreshToken 刷新令牌 * @param string $merchantId 商户ID(可选,如果已设置则使用已设置的) * @return array 返回格式:['success' => true/false, 'data' => [...], 'error' => ''] */ public function refreshAccessToken($refreshToken, $merchantId = null) { if ($merchantId) { $this->setMerchantId($merchantId); } if (empty($this->merchantId)) { return [ 'success' => false, 'error' => '商户ID未设置', ]; } $timestamp = (string)(time() * 1000); // 毫秒级时间戳 $params = [ 'grant_type' => self::GRANT_TYPE_REFRESH, 'app_id' => $this->appId, 'merchant_id' => $this->merchantId, 'timestamp' => $timestamp, 'refresh_token' => $refreshToken, ]; // 生成签名 $signature = $this->generateSignature($params); $params['signature'] = $signature; $url = $this->getRefreshTokenUrl(); // 发送POST请求 $response = HttpClient::post($url, $params, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaAuth] RefreshAccessToken Response: " . json_encode($response)); return $this->parseResponse($response); } /** * 解析API响应 * * 峰鸟开放平台 API 返回格式: * { * "sign": "返回值签名", * "code": "200|错误码", * "msg": "错误信息", * "business_data": { * "app_id": "应用id", * "merchant_id": "商户id", * "access_token": "凭证token", * "refresh_token": "刷新token", * "expire_in": "access_token剩余有效时间,单位:秒", * "re_expire_in": "refresh_token剩余有效时间" * } * } * * @param array $response HTTP响应 * @return array 标准化的响应格式 */ protected function parseResponse($response) { // 检查 code 字段 if (!isset($response['code'])) { return [ 'success' => false, 'error' => $response['msg'] ?? '响应格式错误,缺少 code 字段', ]; } // 如果 code 不等于 '200' 或 200,表示请求失败 if ((string)$response['code'] !== '200') { return [ 'success' => false, 'error' => $response['msg'] ?? 'API返回异常', 'code' => $response['code'], ]; } // 提取 business_data 字段中的数据 $businessData = $response['business_data'] ?? []; if (is_string($businessData)) { $businessData = json_decode($businessData, true); } // 检查是否包含必需的token信息 if (empty($businessData['access_token'])) { return [ 'success' => false, 'error' => $response['msg'] ?? '未获取到 access_token', ]; } // 成功响应 return [ 'success' => true, 'data' => [ 'access_token' => $businessData['access_token'] ?? '', 'refresh_token' => $businessData['refresh_token'] ?? '', 'app_id' => $businessData['app_id'] ?? '', 'merchant_id' => $businessData['merchant_id'] ?? '', 'expire_in' => $businessData['expire_in'] ?? 0, 're_expire_in' => $businessData['re_expire_in'] ?? 0, ], ]; } /** * 获取AppId * * @return string */ public function getAppId() { return $this->appId; } /** * 获取AppSecret * * @return string */ public function getAppSecret() { return $this->appSecret; } /** * 获取商户ID * * @return string */ public function getMerchantId() { return $this->merchantId; } /** * 获取是否为沙箱环境 * * @return bool */ public function isSandbox() { return $this->isSandbox; } }