diff --git a/addons/shopro/config/ccblife.php b/addons/shopro/config/ccblife.php index 22dacb5..1d2fa81 100644 --- a/addons/shopro/config/ccblife.php +++ b/addons/shopro/config/ccblife.php @@ -44,8 +44,8 @@ return [ /** * 服务方私钥 (自己生成的RSA私钥) * 用途: + * - API请求签名(MD5(明文 + 私钥)) * - 解密建行返回的加密数据(ccbParamSJ等) - * - 注意: 不直接参与签名,仅用于解密 * 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾) */ 'private_key' => $envVars['private_key'] ?? '', @@ -53,7 +53,8 @@ return [ /** * 服务方公钥 (自己生成的RSA公钥,对应上面的私钥) * 用途: - * - 参与支付下单的MD5签名计算(PLATFORMPUB字段) + * - 加密API请求报文(建行用相同的公钥解密) + * - 支付下单的MD5签名计算(PLATFORMPUB字段) * - 加密商户公钥(ENCPUB字段) * 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾) */ @@ -63,7 +64,6 @@ return [ * 建行生活支付验签公钥 (建行生活平台分配的) * 用途: * - 验证异步通知中的SIGN字段(NT_TYPE=YS时) - * - ⚠️ 重要: 这不是你自己的公钥!需要联系建行生活技术支持获取 * 获取方式: 联系建行生活平台运营人员或技术支持 * 格式: PEM格式RSA公钥(2048位) * diff --git a/addons/shopro/library/ccblife/CcbHttpClient.php b/addons/shopro/library/ccblife/CcbHttpClient.php index d78d17b..37969ba 100644 --- a/addons/shopro/library/ccblife/CcbHttpClient.php +++ b/addons/shopro/library/ccblife/CcbHttpClient.php @@ -54,13 +54,13 @@ class CcbHttpClient Log::info('建行生活API原始请求报文 [txCode=' . $txCode . '] [txSeq=' . $txSeq . ']'); Log::info('原始报文内容: ' . $message); - // 加密报文 + // 使用服务方公钥加密报文 $encryptedMessage = CcbRSA::encryptForCcb($message, $this->config['public_key']); // 移除BASE64中的换行符 $encryptedMessage = str_replace(["\r", "\n", "\r\n"], '', $encryptedMessage); - // 生成签名 + // 使用服务方私钥签名 $mac = CcbMD5::signApiMessage($message, $this->config['private_key']); // 发送HTTP请求 @@ -118,26 +118,27 @@ class CcbHttpClient 'svcid' => $this->config['service_id'] ]; + // ✅ 将请求参数转为JSON格式(按照建行报文示例要求) + $jsonParams = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + // 📝 记录请求参数(加密后的完整内容) - Log::info('建行生活API加密请求参数 [txCode=' . $txCode . '] [url=' . $apiUrl . '] [timeout=' . self::DEFAULT_TIMEOUT . 's]'); - Log::info('加密参数 cnt: ' . $cnt); - Log::info('加密参数 mac: ' . $mac); - Log::info('加密参数 svcid: ' . $this->config['service_id']); + Log::info('建行生活API加密请求 [txCode=' . $txCode . '] [url=' . $apiUrl . '] [timeout=' . self::DEFAULT_TIMEOUT . 's]'); + Log::info('JSON请求体: ' . $jsonParams); // 初始化CURL $ch = curl_init(); - // 设置CURL选项 + // 设置CURL选项(按照建行要求发送JSON格式) curl_setopt_array($ch, [ CURLOPT_URL => $apiUrl, CURLOPT_POST => true, - CURLOPT_POSTFIELDS => http_build_query($params), + CURLOPT_POSTFIELDS => $jsonParams, // ✅ 发送JSON格式,不是URL编码 CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => self::DEFAULT_TIMEOUT, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_HTTPHEADER => [ - 'Content-Type: application/x-www-form-urlencoded', + 'Content-Type: application/json; charset=utf-8', // ✅ JSON格式 'Accept: application/json' ] ]); diff --git a/addons/shopro/library/ccblife/CcbOrderService.php b/addons/shopro/library/ccblife/CcbOrderService.php index 83ee6c1..2f6e0fc 100644 --- a/addons/shopro/library/ccblife/CcbOrderService.php +++ b/addons/shopro/library/ccblife/CcbOrderService.php @@ -333,12 +333,22 @@ class CcbOrderService $discountAmount = number_format($order['total_discount_fee'] ?? 0, 2, '.', ''); $totalRefundAmount = number_format($order['refund_fee'] ?? 0, 2, '.', ''); - // 处理订单时间(Shopro的createtime是毫秒时间戳,需要除以1000) + // 处理订单时间(自动判断秒级或毫秒级时间戳) $createTimeValue = $order['createtime'] ?? null; if (empty($createTimeValue) || !is_numeric($createTimeValue)) { - $createTimeValue = time() * 1000; + $createTimeValue = time(); // 使用当前秒级时间戳 } - $orderDt = date('YmdHis', intval($createTimeValue / 1000)); + + // 判断时间戳类型:大于9999999999说明是毫秒级(13位数),否则是秒级(10位数) + if ($createTimeValue > 9999999999) { + // 毫秒级时间戳,除以1000转为秒 + $timestamp = intval($createTimeValue / 1000); + } else { + // 秒级时间戳,直接使用 + $timestamp = intval($createTimeValue); + } + + $orderDt = date('YmdHis', $timestamp); // 处理订单过期时间 $invDt = ''; diff --git a/doc/建行生活API接口文档.md b/doc/建行生活API接口文档.md index ae5cd49..6466c81 100644 --- a/doc/建行生活API接口文档.md +++ b/doc/建行生活API接口文档.md @@ -255,26 +255,31 @@ https://yunbusiness.ccb.com/tp_service/txCtrl/server?txcode=XX | 字段 | 说明 | |------|------| | cnt | 使用服务方公钥对明文报文进行RSA加密后的BASE64编码内容 | -| mac | MD5(明文报文 + 服务方私钥) 的32位小写签名 | +| mac | MD5(明文报文 + 服务方私钥) 的32位**大写**签名(API接口使用大写) | | svcid | 服务方编号(建行分配) | **请求方式**: -``` +```http POST https://yunbusiness.ccb.com/tp_service/txCtrl/server?txcode=A3341TP01 -Content-Type: application/x-www-form-urlencoded +Content-Type: application/json; charset=utf-8 -cnt=BASE64加密内容&mac=MD5签名&svcid=服务方编号 +{"cnt":"BASE64加密内容","mac":"大写MD5签名","svcid":"服务方编号"} ``` -**完整示例**(参考): +**完整示例**(参考 - 来自建行提供的示例文件): ```json { "cnt": "Y2tFMDFJd2RGMGg5aFdXUGtjVVdaSmo4NHBKQzNNZE1wQTRRSXZVRlhBSWhqVEdXNE1LcE9MOXdxY0hhNUlIZndUU0RLK3NrZ1hpTytJUitpREEwSUp0bktRcWMxRG5hN1R0OEtjcUkxTUFDVE5FY2Z0b3lCeTVTaEo3cmNjSnBOUVFsSjRBR2htSzRheEhNb0p6N215eFViK1ZjeGd5WjVTTjJQcHUxQlBnZXJsQXE2Q1lrQ2VuSmZEYUxVSks5RGx2Yk9YWDlDczJiVVllYjlHSHQrUkFuYTljc2hucGhqVWNwNDgrcThNcGhQOElBL20xNVk5NG9lZEV4SXpmc0pDcDExZjFvQ0E5YkwwOWJOZjM4VHR3TkJkTmhqM3lKSVpWeWVpT0FucGhjS3JpOEs5RnlZbXlNVHF1UER3UjhmQ0p5dk5vYkNMS1BPRmQ3WFdXTVczZ29kSWpLaG5OUnhnaFA3N2txdDU3K2Rkd3hGbDgxUEdYbXJWN1ZKWDFOeXRVUFg2dWp3ZzdsUU1OSTlubU1kVE9nbHZJUHRoS205aEludFc2ZFBVTG1DUlNLNzZDc05qTUIyb1hTR2M2cHBNazMxNDJSa05KR0hvY1ZBNFUzcmc4SVk4ZFlYaTUzZmF3cHRES3pHY2JYVFI0SldRVzRNU2ZmSUxvNFpxTkY=", - "mac": "947cab4dfebe59265dd28246e4465157", - "svcid": "YS44000009001853" + "mac": "947CAB4DFEBE59265DD28246E4465157", + "svcid": "YSTEST" } ``` +**⚠️ 重要说明**: +- 请求体必须是**JSON格式**,不是URL编码格式 +- MAC签名必须是**32位大写MD5**(API接口) +- Content-Type必须设置为`application/json; charset=utf-8` + --- ### 3.2 A3341TP02 - 服务方订单更新