diff --git a/.gitignore b/.gitignore index 0c9b7c6..477e549 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /nbproject/ /runtime/* -/doc/* +#/doc/* .DS_Store .idea composer.lock diff --git a/addons/shopro/config/ccblife.php b/addons/shopro/config/ccblife.php index 34e095b..8d98b0b 100644 --- a/addons/shopro/config/ccblife.php +++ b/addons/shopro/config/ccblife.php @@ -43,9 +43,40 @@ return [ 'pos_id' => Env::get('ccb.pos_id', '068295530'), 'branch_id' => Env::get('ccb.branch_id', '340650000'), - // 密钥配置 (从.env读取,BASE64格式,不含PEM头尾) + // ========== 密钥配置 (从.env读取) ========== + + /** + * 服务方私钥 (自己生成的RSA私钥) + * 用途: + * - 解密建行返回的加密数据(ccbParamSJ等) + * - 注意: 不直接参与签名,仅用于解密 + * 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾) + */ 'private_key' => $envVars['private_key'] ?? '', + + /** + * 服务方公钥 (自己生成的RSA公钥,对应上面的私钥) + * 用途: + * - 参与支付下单的MD5签名计算(PLATFORMPUB字段) + * - 加密商户公钥(ENCPUB字段) + * 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾) + */ 'public_key' => $envVars['public_key'] ?? '', + + /** + * 建行生活支付验签公钥 (建行生活平台分配的) + * 用途: + * - 验证异步通知中的SIGN字段(NT_TYPE=YS时) + * - ⚠️ 重要: 这不是你自己的公钥!需要联系建行生活技术支持获取 + * 获取方式: 联系建行生活平台运营人员或技术支持 + * 格式: PEM格式RSA公钥(2048位) + * + * 📌 如果未配置此字段: + * - 异步通知验签会降级为POSID验证 + * - 安全性降低,建议尽快获取并配置 + */ + 'ccb_payment_verify_public_key' => $envVars['ccb_payment_verify_public_key'] ?? '', + // HTTP请求配置 'http' => [ 'timeout' => 30, // 超时时间(秒) diff --git a/addons/shopro/controller/Ccbpayment.php b/addons/shopro/controller/Ccbpayment.php index 306fad4..6fc7bf0 100644 --- a/addons/shopro/controller/Ccbpayment.php +++ b/addons/shopro/controller/Ccbpayment.php @@ -164,12 +164,37 @@ class Ccbpayment extends Common $this->error('支付验证失败,请稍后再试'); } - // 5. 更新订单状态 + // 5. 更新订单状态(防止重复支付) Db::startTrans(); try { - $order->status = 'paid'; - $order->paid_time = time() * 1000; // Shopro使用毫秒时间戳 - $order->save(); + // ⚠️ 使用原子性更新,防止并发重复支付 + $affectedRows = Db::name('shopro_order') + ->where('id', $order->id) + ->where('status', 'unpaid') // 只更新未支付的订单 + ->update([ + 'status' => 'paid', + 'paid_time' => time() * 1000, // Shopro使用毫秒时间戳 + 'updatetime' => time() + ]); + + if ($affectedRows === 0) { + // 订单状态不正确或已支付,回滚事务 + Db::rollback(); + + // 检查订单当前状态 + $order->refresh(); + if ($order->status === 'paid') { + // 订单已支付,直接返回成功 + $this->success('订单已支付', [ + 'order_id' => $order->id, + 'order_sn' => $order->order_sn, + 'status' => 'paid', + ]); + return; + } else { + throw new Exception('订单状态异常,无法更新为已支付'); + } + } // 6. 推送订单状态到建行 $this->pushOrderToCcb($order); @@ -243,13 +268,18 @@ class Ccbpayment extends Common $result = $this->paymentService->handleNotify($params); // 7. 返回处理结果 - echo $result; // 'SUCCESS' 或 'FAIL' + // ⚠️ 重要: 必须使用exit直接退出,防止ThinkPHP框架追加额外内容 + // 建行要求返回纯文本 'SUCCESS' 或 'FAIL',任何额外字符都会导致建行认为通知失败 + Log::info('[建行通知] 处理完成,返回: ' . strtoupper($result)); - Log::info('[建行通知] 处理完成: ' . $result); + // 直接退出,确保只输出SUCCESS/FAIL + exit(strtoupper($result)); } catch (Exception $e) { Log::error('[建行通知] 处理失败 error:' . $e->getMessage()); - echo 'FAIL'; + + // 异常情况也要直接退出 + exit('FAIL'); } } diff --git a/addons/shopro/library/ccblife/CcbEncryption.php b/addons/shopro/library/ccblife/CcbEncryption.php index df63cec..d970510 100644 --- a/addons/shopro/library/ccblife/CcbEncryption.php +++ b/addons/shopro/library/ccblife/CcbEncryption.php @@ -96,25 +96,38 @@ class CcbEncryption // 获取公钥资源 $pubKeyId = openssl_pkey_get_public($publicKey); if (!$pubKeyId) { - throw new Exception('建行平台公钥格式错误'); + throw new Exception('建行平台公钥格式错误: ' . openssl_error_string()); } - // RSA加密 (分段加密,每段117字节) + // ⚠️ 动态获取RSA密钥大小,而非写死117字节 + $keyDetails = openssl_pkey_get_details($pubKeyId); + if (!$keyDetails || !isset($keyDetails['bits'])) { + throw new Exception('无法获取RSA密钥详情'); + } + + $keySize = $keyDetails['bits'] / 8; // 密钥字节数: 1024位=128字节, 2048位=256字节 + $chunkSize = $keySize - 11; // PKCS1填充需要预留11字节 + + // RSA加密 (分段加密) $encrypted = ''; $dataLen = strlen($data); - $chunkSize = 117; // 1024位RSA密钥,每次最多加密117字节 for ($i = 0; $i < $dataLen; $i += $chunkSize) { $chunk = substr($data, $i, $chunkSize); $encryptedChunk = ''; - if (!openssl_public_encrypt($chunk, $encryptedChunk, $pubKeyId)) { + if (!openssl_public_encrypt($chunk, $encryptedChunk, $pubKeyId, OPENSSL_PKCS1_PADDING)) { throw new Exception('RSA加密失败: ' . openssl_error_string()); } $encrypted .= $encryptedChunk; } + // PHP 8+ 资源自动释放 + if (PHP_VERSION_ID < 80000) { + openssl_free_key($pubKeyId); + } + // BASE64编码并去除换行符 return str_replace(["\r", "\n"], '', base64_encode($encrypted)); } @@ -134,28 +147,40 @@ class CcbEncryption // 获取私钥资源 $privKeyId = openssl_pkey_get_private($privateKey); if (!$privKeyId) { - throw new Exception('服务方私钥格式错误'); + throw new Exception('服务方私钥格式错误: ' . openssl_error_string()); } + // ⚠️ 动态获取RSA密钥大小 + $keyDetails = openssl_pkey_get_details($privKeyId); + if (!$keyDetails || !isset($keyDetails['bits'])) { + throw new Exception('无法获取RSA密钥详情'); + } + + $keySize = $keyDetails['bits'] / 8; // 密钥字节数: 1024位=128字节, 2048位=256字节 + // BASE64解码 $encrypted = base64_decode($data); - // RSA解密 (分段解密,每段128字节) + // RSA解密 (分段解密,每段密文长度等于密钥字节数) $decrypted = ''; $encryptedLen = strlen($encrypted); - $chunkSize = 128; // 1024位RSA密钥,密文长度为128字节 - for ($i = 0; $i < $encryptedLen; $i += $chunkSize) { - $chunk = substr($encrypted, $i, $chunkSize); + for ($i = 0; $i < $encryptedLen; $i += $keySize) { + $chunk = substr($encrypted, $i, $keySize); $decryptedChunk = ''; - if (!openssl_private_decrypt($chunk, $decryptedChunk, $privKeyId)) { + if (!openssl_private_decrypt($chunk, $decryptedChunk, $privKeyId, OPENSSL_PKCS1_PADDING)) { throw new Exception('RSA解密失败: ' . openssl_error_string()); } $decrypted .= $decryptedChunk; } + // PHP 8+ 资源自动释放 + if (PHP_VERSION_ID < 80000) { + openssl_free_key($privKeyId); + } + return $decrypted; } @@ -367,8 +392,11 @@ class CcbEncryption /** * 加密商户公钥 (用于支付串的ENCPUB字段) * + * ⚠️ 已废弃:建行要求只加密公钥后30位,请使用 encryptMerchantPublicKeyLast30() + * * @return string BASE64编码的加密公钥 * @throws Exception + * @deprecated 使用 encryptMerchantPublicKeyLast30() 替代 */ public function encryptMerchantPublicKey() { @@ -379,4 +407,40 @@ class CcbEncryption // 使用建行平台公钥加密商户公钥 return $this->rsaEncrypt($this->publicKey); } + + /** + * 加密商户公钥后30位 (用于支付串的ENCPUB字段) + * + * 根据建行文档v2.2规范: + * "使用服务方公钥对商户公钥后30位进行RSA加密并base64后的密文" + * + * @return string BASE64编码的加密密文 + * @throws Exception + */ + public function encryptMerchantPublicKeyLast30() + { + if (empty($this->publicKey)) { + throw new Exception('服务方公钥未配置'); + } + + // 取商户公钥的后30位 + $publicKeyContent = $this->publicKey; + + // 如果是PEM格式,去除头尾和换行符 + $publicKeyContent = str_replace([ + '-----BEGIN PUBLIC KEY-----', + '-----END PUBLIC KEY-----', + "\r", "\n", " " + ], '', $publicKeyContent); + + // 取后30位 + $last30Chars = substr($publicKeyContent, -30); + + if (strlen($last30Chars) < 30) { + throw new Exception('商户公钥长度不足30位'); + } + + // 使用建行平台公钥加密这30位字符 + return $this->rsaEncrypt($last30Chars); + } } diff --git a/addons/shopro/library/ccblife/CcbPaymentService.php b/addons/shopro/library/ccblife/CcbPaymentService.php index aff9156..a8b1b15 100644 --- a/addons/shopro/library/ccblife/CcbPaymentService.php +++ b/addons/shopro/library/ccblife/CcbPaymentService.php @@ -145,14 +145,16 @@ class CcbPaymentService // 生成签名字符串 $signString = http_build_query($paymentParams); - // ⚠️ 注意:建行支付串签名规则 - // 签名 = MD5(参数字符串 + 服务方私钥) - // 不需要PLATFORMPUB字段,直接使用私钥签名 - $mac = md5($signString . $this->config['private_key']); + // ⚠️ 建行支付串签名规则(v2.2版本): + // 1. PLATFORMPUB字段仅参与MD5计算,不作为HTTP参数传递 + // 2. 签名 = MD5(参数字符串 + &PLATFORMPUB= + 服务方公钥内容) + // 3. 生成32位大写MD5字符串(对照MD5Util.java第30行) + $platformPubKey = $this->config['public_key']; // 服务方公钥 + $mac = strtoupper(md5($signString . '&PLATFORMPUB=' . $platformPubKey)); - // 使用RSA加密商户公钥(用于ENCPUB字段) + // 使用RSA加密商户公钥后30位(用于ENCPUB字段) $encryption = new CcbEncryption($this->config); - $encpub = $encryption->encryptMerchantPublicKey(); + $encpub = $encryption->encryptMerchantPublicKeyLast30(); // 组装最终支付串 $finalPaymentString = $signString . '&MAC=' . $mac . '&PLATFORMID=' . $this->config['service_id'] . '&ENCPUB=' . urlencode($encpub); @@ -450,34 +452,121 @@ class CcbPaymentService /** * 验证异步通知签名 * + * ⚠️ 建行异步通知签名规则: + * 1. SIGN字段为256字符十六进制字符串(2048位RSA签名) + * 2. NT_TYPE=YS时,使用"建行生活分配的服务商支付验签公钥" + * 3. 签名算法: RSA-SHA256或SHA1(需建行技术支持确认) + * + * 📌 配置说明: + * - 如果配置了ccb_payment_verify_public_key: 使用RSA验签 + * - 如果未配置: 降级为POSID验证(临时方案) + * * @param array $params 通知参数 * @return bool */ private function verifyNotifySignature($params) { - // 获取签名 - $signature = $params['SIGN'] ?? ''; - if (empty($signature)) { + try { + // 1. 提取SIGN字段 + $sign = $params['SIGN'] ?? ''; + if (empty($sign)) { + Log::error('[建行验签] SIGN字段为空'); + return false; + } + + // 验证SIGN长度(256个十六进制字符 = 2048位RSA签名) + if (strlen($sign) !== 256) { + Log::error('[建行验签] SIGN长度错误: ' . strlen($sign) . ', 应为256'); + return false; + } + + // 2. 检查是否配置了建行支付验签公钥 + $ccbVerifyPublicKey = $this->config['ccb_payment_verify_public_key'] ?? ''; + + if (empty($ccbVerifyPublicKey)) { + // 降级方案: 未配置验签公钥时,使用POSID验证 + Log::warning('[建行验签] 未配置ccb_payment_verify_public_key,使用降级验证方案'); + + // 验证POSID是否匹配 + if (($params['POSID'] ?? '') !== $this->config['pos_id']) { + Log::error('[建行验签] POSID不匹配,预期: ' . $this->config['pos_id'] . ', 实际: ' . ($params['POSID'] ?? '')); + return false; + } + + // 验证订单号是否存在 + $orderSn = $params['USER_ORDERID'] ?? $params['ORDERID'] ?? ''; + if (empty($orderSn)) { + Log::error('[建行验签] 订单号为空'); + return false; + } + + Log::warning('[建行验签] 降级验证通过,建议联系建行技术支持获取验签公钥'); + return true; + } + + // 3. 移除SIGN字段,构建签名原串 + $verifyParams = $params; + unset($verifyParams['SIGN']); + + // 4. 按key排序 + ksort($verifyParams); + + // 5. 构建签名原串(非空参数) + $signStr = ''; + foreach ($verifyParams as $key => $value) { + // 跳过空值参数 + if ($value !== '' && $value !== null) { + $signStr .= $key . '=' . $value . '&'; + } + } + $signStr = rtrim($signStr, '&'); + + Log::info('[建行验签] 签名原串: ' . $signStr); + Log::info('[建行验签] 收到SIGN前32位: ' . substr($sign, 0, 32) . '...'); + + // 6. 将十六进制SIGN转为二进制 + $signBinary = hex2bin($sign); + if ($signBinary === false) { + Log::error('[建行验签] SIGN十六进制转换失败'); + return false; + } + + // 7. 加载建行支付验签公钥 + $pubKey = openssl_pkey_get_public($ccbVerifyPublicKey); + if (!$pubKey) { + Log::error('[建行验签] 验签公钥加载失败: ' . openssl_error_string()); + return false; + } + + // 8. 先尝试SHA256验签 + $verifyResult = openssl_verify($signStr, $signBinary, $pubKey, OPENSSL_ALGO_SHA256); + + if ($verifyResult !== 1) { + // SHA256失败,尝试SHA1 + Log::info('[建行验签] SHA256验签失败,尝试SHA1'); + $verifyResult = openssl_verify($signStr, $signBinary, $pubKey, OPENSSL_ALGO_SHA1); + } + + // PHP 8+ 资源自动释放 + if (PHP_VERSION_ID < 80000) { + openssl_free_key($pubKey); + } + + if ($verifyResult === 1) { + Log::info('[建行验签] RSA验签成功'); + return true; + } elseif ($verifyResult === 0) { + Log::error('[建行验签] RSA验签失败,签名不匹配'); + return false; + } else { + Log::error('[建行验签] RSA验签过程发生错误: ' . openssl_error_string()); + return false; + } + + } catch (\Exception $e) { + Log::error('[建行验签] 验签异常: ' . $e->getMessage()); return false; } - - // 移除签名字段 - unset($params['SIGN']); - - // 按照建行要求的方式构建签名字符串 - ksort($params); - $signStr = ''; - foreach ($params as $key => $value) { - if ($value !== '') { - $signStr .= $key . '=' . $value . '&'; - } - } - $signStr = rtrim($signStr, '&'); - - // 使用私钥计算签名 - $expectedSign = md5($signStr . $this->config['private_key']); - - return strtolower($signature) === strtolower($expectedSign); } /** diff --git a/doc/CCBLife小程序API使用说明_v1.1_20230511.html b/doc/CCBLife小程序API使用说明_v1.1_20230511.html new file mode 100644 index 0000000..b1f76f2 --- /dev/null +++ b/doc/CCBLife小程序API使用说明_v1.1_20230511.html @@ -0,0 +1,599 @@ + + +
+ + +文档修订记录
| 版本 | 日期 | 修订说明 |
|---|---|---|
| 1.0 | 2023.02.14 | 同步在线文档接口说明 |
| 1.1 | 2023.05.11 | 新增实名认证api |
文档目录
CCBLife小程序API使用说明_v1.1_202305111. 文档说明2. 接口说明2.1 login2.2 ccblife_login2.3 checkSession2.4 getUserInfo2.5 authorize2.6 requestPayment2.7 ccblife_requestPayment2.8 navigateTo2.9 scanCode2.10 openPayCode2.11 callMap2.12 startFaceScan2.13 userStatus2.14 share2.15 checkUser2.15 RealNameAuthorization
本文档所描述API适用于建行生活App端内运行的JUMP小程序。
回调函数统一格式:
回调结果参数(Object res)
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| data | object | 返回内容 | - |
| state | string | 状态码 | - |
| msg | string | 状态信息|报错信息 | - |
响应内容封装在data的Json对象里
用途说明
登录|获取用户信息。提供客户端认证模式与服务端认证模式两种模式。若建行生活处于未登录状态会跳转建行生活APP的登录页进行登录(行内单点登录使用)。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| type | number | - | 是 | 登录类型 | - |
| PLATFORM_ID | string | - | - | 服务方ID | - |
| Opn_Chnl_ID | string | - | 是 | 合作方渠道编号 | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
type 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 客户端认证模式 | - |
| 1 | 服务端认证模式 | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| encryptedData | string | "userid=xxx&mobile=xxx&PreAhr_ID=xxx"的加密字符串。(userid:建行生活用户编号,mobile:手机号,PreAhr_ID:用户中心预授权编码) | - |
注意
服务方IDPLATFORM_ID于客户端认证模式(type=0)时必填。
用途说明
登录|获取用户信息。提供客户端认证模式与服务端认证模式两种模式。若建行生活处于未登录状态会跳转建行生活APP的登录页进行登录。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| type | number | - | 是 | 登录类型 | - |
| PLATFORM_ID | string | - | - | 服务方ID | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
type 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 客户端认证模式 | - |
| 1 | 服务端认证模式 | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| encryptedData | string | "unionid=xxx&phone=xxx&locationCityCode=xxx"的加密字符串。(unionid:建行生活用户编号,phone:手机号,locationCityCode:用户选择城市码) | - |
注意
服务方IDPLATFORM_ID于客户端认证模式(type=0)时必填。
用途说明
检查登录态是否过期。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| PLATFORM_ID | string | - | - | 服务方ID | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| isVaild | boolean | 登录态是否有效 | - |
用途说明
获取用户信息。目前能返回的信息均为登录态敏感信息,加密。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| withCredentials | boolean | true | 否 | 是否带上登录态信息。 | - |
| loginType | number | - | 是 | 当前登录类型 | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
loginType的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 客户端认证模式 | - |
| 1 | 服务端认证模式 | - |
响应内容
state的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 获取成功 | - |
| 1 | 获取失败 | - |
| 2 | 获取失败:未授权 | - |
data:
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| userInfo | Object | 用户信息,不包含敏感数据 | - |
| encryptedData | string | 包括敏感数据在内的完整用户信息的加密数据 | - |
| signature | string | 用户数据签名 | - |
| salt | string | 签名使用的字符串 | - |
| iv | string | 加密算法的初始向量 | - |
encryptedData 解密:
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| unionid | string | 建行生活平台帐号的唯一标识 | - |
| openid | string | 用户在当前小程序的唯一标识 | - |
| cityCode | String | 用户选择城市编码 | |
| locationCityCode | string | 用户当前定位城市编码 | - |
| registerCityCode | string | 用户归属城市编码,即用户注册地 | - |
| phone | string | 用户手机号 | - |
注意:
当采用客户端认证模式时,数据使用服务方公钥加密,需使用服务方私钥解密。
当采用服务端认证模式时,数据使用session_key加密。
res.data的signature 、salt、iv仅当采用服务端认证模式时有效
signature = sha1( salt + session_key )
城市编码使用6位全国地区行政编码,仅市级编码有效
res.data.encryptedData 解密后得到的accessToken仅客户端认证模式有效
用途说明
提前向用户发起授权请求。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| scope | string | - | 是 | 需要获取权限的 scope | - |
| success | function | - | 否 | 接口调用成功的回调函数(授权成功) | - |
| fail | function | - | 否 | 接口调用失败的回调函数(授权失败) | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
scope说明:
| scope | 对应接口 | 说明 | 最低版本 |
|---|---|---|---|
| scope.userInfo | getUserInfo、login | 用户信息 | - |
| scope.camera | scanCode | 摄像头 | - |
用途说明
调用建行生活收银台。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| payInfo | string | - | 是 | 支付参数 | - |
payInfo参数内容:
| 属性 | 类型 | 可为空 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| MERCHANTID | char(15) | Y | F | 商户代码;由建行统一分配 | - |
| POSID | char(9) | Y | F | 柜台代码;由建行统一分配 | - |
| BRANCHID | char(9) | Y | F | 分行代码;由建行统一分配 | - |
| POSID19 | char(19) | N | F | 商户19位终端号;由建行统一分配,使用微信支付时上送。仅作为参数传递,不参与MAC校验 | - |
| PLATMCTID | char(19) | Y | F | 外部平台商户号;当使用外部商户号时,建行商户号、柜台号、分行号及终端号无需上送。当该字段有值时参与MAC校验,否则不参与MAC校验 | - |
| ORDERID | char(30) | Y | T | 订单号;由商户提供,最长30位 | - |
| PAYMENT | number(16,2) | Y | T | 付款金额;由商户提供,最长30位 | - |
| CURCODE | char(2) | Y | T | 币种;缺省为01-人民币(只支持人民币支付) | - |
| TXCODE | char(6) | Y | T | 交易码;由建行统一分配为520100 | - |
| REMARK1 | char(30) | N | T | 备注1;网银不处理,直接传到城综网,该字段只支持送数字和英文 | - |
| REMARK2 | char(30) | N | T | 备注2;上送YS开头的服务方编号,与PLATFORMID保持一致 | - |
| TYPE | char(1) | Y | T | 接口类型;1- 防钓鱼接口 | - |
| GATEWAY | char(100) | Y | T | 网关类型;默认送0 | - |
| CLIENTIP | char(40) | N | T | 客户端IP;客户在商户系统中的IP | - |
| REGINFO | char(256) | N | T | 客户注册信息;客户在商户系统中注册的信息,中文需使用escape编码 | - |
| PROINFO | char(256) | N | T | 商品信息;客户购买的商品,中文需使用escape编码 | - |
| REFERER | char(100) | N | T | 商户URL;商户送空值即可 | - |
| INSTALLNUM | char(2) | N | F | 分期期数;信用卡支付分期期数,一般为 3、6、12 等,必须为大于 1 的整数。 仅当分期支付时上送该字段,无此字段上送时,则视为普通支付。 | - |
| THIRDAPPINFO | char(40) | Y | T | 客户端标识;通过建行生活APP下单场景,订单中客户端标识固定设为comccbpay1234567890cloudmerchant | - |
| TIMEOUT | char(14) | N | F | 订单超时时间;格式:YYYYMMDDHHMMSS(如:20120214143005) 银行系统时间> TIMEOUT时拒绝交易,若送空值则不判断超时。 当该字段有值时参与MAC校验,否则不参与MAC校验。 | - |
| USERID | char(100) | N | F | 在中国建设银行App环境需提供。 当该字段有值时参与MAC校验,否则不参与MAC校验 | - |
| TOKEN | char(100) | N | F | 在中国建设银行App环境需提供。 当该字段有值时参与MAC校验,否则不参与MAC校验 | - |
| PAYSUCCESSURL | char(100) | N | F | 在中国建设银行App环境考虑,如需指定支付成功页面需提供,需对URL编码,生产环境必须为HTTPS。未提供则默认跳转到建行生活的支付成功页面 当该字段有值时参与MAC校验,否则不参与MAC校验 | - |
| PAYBITMAP | char(10) | N | F | 支付位图;默认为空,只需要展示龙支付时请送0100000000 当该字段有值时参与MAC校验,否则不参与MAC校验。 | - |
| POINTAVYID | varchar(6) | N | F | 积分二级活动编号;默认为空,特定场景使用。龙支付积分二级活动上送 010051 | - |
| DCEPDEPACCNO | varchar(32) | N | F | 数字人民币收款钱包编号;默认为空,特定场景使用。数字人民币商户绑定的收款钱包编号 | - |
| COUPONAVYID | varchar(32) | N | F | 有价券活动编号;默认为空,特定场景使用。 | - |
| ONLY_CREDIT_PAY_FLAG | varchar(1) | N | F | 限制信用卡支付标志;默认为空,特定场景使用。当有价券活动编号不为空时生效,送Y限制仅信用卡能支付,送N或空不作限制 | - |
| FIXEDPOINTVAL | varchar(16) | N | F | 固定抵扣积分值;默认为空,特定场景使用。上送该值时,若用户不满足积分使用条件将拒绝支付 | - |
| EXTENDPARAMS | varchar(256) | N | F | 积分二级活动编号;默认为空,特定场景使用。上送约定JSON格式字符串 | - |
| PLATFORMPUB | varchar(256) | Y | F | 服务方公钥;仅作为源串参加MD5摘要,不作为参数传递 | - |
| MAC | char(32) | T | T | MD5加密串;采用标准MD5算法,对以上字段进行MAC加密(32位小写),由商户实现。 | - |
| PLATFORMID | char(16) | Y | T | 服务方编号;仅作为参数传递,不参与MAC校验 | - |
| ENCPUB | varchar(512) | Y | F | 商户公钥密文;使用服务方公钥对商户公钥后30位进行RSA加密并base64后的密文。 若商户已经上架建行生活并同步公钥,可以不再上送商户公钥。仅作为参数传递,不参与MAC校验 | - |
| SCNID | char(32) | N | F | 场景编号;默认为空,埋点使用。特色场景的唯一标识。仅作为参数传递,不参与MAC校验 | - |
| SCN_PLTFRM_ID | char(32) | N | F | 场景平台编号;默认为空,埋点使用。场景平台唯一标识。仅作为参数传递,不参与MAC校验 |
注意:
字符串中变量名必须是大写字母。
中文信息需要escape编码:
使用js的escape()方法对payInfo.REGINFO(客户注册信息)和payInfo.PROINFO(商品信息)进行转码,数字字母信息不需转码。
payInfo.MAC采用标准MD5摘要算法对字符串数据签名(32位小写)
参与签名的字符串及其顺序如下(为上表中MAC字段之上的所有字段按顺序拼接):
MERCHANTID=123456789&POSID=000000000&BRANCHID=110000000&ORDERID=19991101234&PAYMENT=0.01&CURCODE=01&TXCODE=520100&REMARK1=&REMARK2=&TYP>E=1&GATEWAY=&CLIENTIP=172.0.0.1®INFO=%u5C0F%u98DE%u4FA0&PROINFO=%u5145%u503C%u5361&REFERER=&THIRDAPPINFO=comccbpay1234567890cloudmerchant&TIMEOUT=20161028101226&PAYBITMAP=0100000000&MAC=4f9033be946e7dcd78886fdb4b2b0ec3&PLATFORMID=YS0000000000000001&ENCPUB=ZGdXNUo3MXA5allmY0dUQm1mRW...
黑色字体对应的字段必须参与MAC,橙色的字段请根据需要上送,且有值时才参与MAC,否则无需参与MAC
如有和网银约定好的必传参数,请拼接在MAC和PLATFORMID字段之间,如PAYMAP
payInfo.ENCPUB:各服务方使用自己的服务方公钥对商户公钥后30位进行RSA加密,再进行base64后,生成的密文串。若商户已经上架建行生活并同步公钥,可以不再上送商户公钥。
公钥加密方法见《建行生活输入通讯报文接口规范》的报文加密章节。
支付通知等接口见《建行生活APP服务方接入文档》。
用途说明
调用建行生活收银台。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| miniId | string | - | 是 | 本小程序id | - |
| successPage | string | - | 否 | 成功页面的路径,不设置则跳转建行生活APP的支付成功页面 | - |
| payInfo | object | - | 是 | 支付参数 | - |
payInfo参数内容
| 属性 | 类型 | 可为空 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| MERCHANTID | char(15) | 是 | 是 | 商户代码;由建行统一分配 | - |
| POSID | char(9) | 是 | 是 | 柜台代码;由建行统一分配 | - |
| BRANCHID | char(9) | 是 | 是 | 分行代码;由建行统一分配 | - |
| POSID19 | char(19) | 否 | 否 | 商户19位终端号;由建行统一分配,仅作为参数传递,不参与MAC校验 | - |
| ORDERID | char(30) | 是 | 是 | 订单号;由商户提供,最长30位 | - |
| PAYMENT | number(16,2) | 是 | 是 | 付款金额;由商户提供,最长30位 | - |
| CURCODE | char(2) | 是 | 是 | 币种;缺省为01-人民币(只支持人民币支付) | - |
| TXCODE | char(6) | 是 | 是 | 交易码;由建行统一分配为520100 | - |
| REMARK1 | char(30) | 是 | 是 | 备注1;网银不处理,直接传到城综网,该字段只支持送数字和英文 | - |
| REMARK2 | char(30) | 是 | 是 | 备注2;上送YS开头的服务方编号 | - |
| TYPE | char(1) | 是 | 是 | 接口类型;1- 防钓鱼接口 | - |
| GATEWAY | char(100) | 是 | 是 | 网关类型;默认送0 | - |
| CLIENTIP | char(40) | 否 | 是 | 客户端IP;客户在商户系统中的IP | - |
| REGINFO | char(256) | 否 | 是 | 客户注册信息;客户在商户系统中注册的信息,中文需使用escape编码 | - |
| PROINFO | char(256) | 否 | 是 | 商品信息;客户购买的商品,中文需使用escape编码 | - |
| REFERER | char(100) | 否 | 是 | 商户URL;商户送空值即可 | - |
| THIRDAPPINFO | char(40) | 是 | 是 | 客户端标识;通过建行生活APP下单场景,订单中客户端标识固定设为comccbpay1234567890cloudmerchant | - |
| TIMEOUT | char(14) | 否 | 否 | 订单超时时间;格式:YYYYMMDDHHMMSS(如:20120214143005) 银行系统时间> TIMEOUT时拒绝交易,若送空值则不判断超时。 当该字段有值时参与MAC校验,否则不参与MAC校验。 | - |
| PAYBITMAP | char(10) | 否 | 否 | 支付位图;默认为空,只需要展示龙支付时请送0100000000 当该字段有值时参与MAC校验,否则不参与MAC校验。 | - |
| PLATFORMPUB | varchar(256) | 是 | 否 | 服务方公钥;仅作为源串参加MD5摘要,不作为参数传递 | - |
| MAC | char(32) | 是 | 是 | MD5加密串;采用标准MD5算法,对以上字段进行MAC加密(32位小写),由商户实现。 | - |
| PLATFORMID | char(16) | 是 | 是 | 服务方编号;仅作为参数传递,不参与MAC校验 | - |
| ENCPUB | varchar(512) | 是 | 否 | 商户公钥密文;使用服务方公钥对商户公钥后30位进行RSA加密并base64后的密文。 若商户已经上架建行生活并同步公钥,可以不再上送商户公钥。仅作为参数传递,不参与MAC校验 | - |
注意:
字符串中变量名必须是大写字母。
中文信息需要escape编码:
使用js的escape()方法对payInfo.REGINFO(客户注册信息)和payInfo.PROINFO(商品信息)进行转码,数字字母信息不需转码。
payInfo.MAC采用标准MD5摘要算法对字符串数据签名(32位小写)
参与签名的字符串及其顺序如下(为上表中MAC字段之上的所有字段按顺序拼接):
MERCHANTID=123456789&POSID=000000000&BRANCHID=110000000&ORDERID=19991101234&PAYMENT=0.01&CURCODE=01&TXCODE=520100&REMARK1=&REMARK2=&TYP>E=1&GATEWAY=&CLIENTIP=172.0.0.1®INFO=%u5C0F%u98DE%u4FA0&PROINFO=%u5145%u503C%u5361&REFERER=&THIRDAPPINFO=comccbpay1234567890cloudmerchant&TIMEOUT=20161028101226&PAYBITMAP=0100000000&MAC=4f9033be946e7dcd78886fdb4b2b0ec3&PLATFORMID=YS0000000000000001&ENCPUB=ZGdXNUo3MXA5allmY0dUQm1mRW...
黑色字体对应的字段必须参与MAC,橙色的字段请根据需要上送,且有值时才参与MAC,否则无需参与MAC
如有和网银约定好的必传参数,请拼接在MAC和PLATFORMID字段之间,如PAYMAP
payInfo.ENCPUB:各服务方使用自己的服务方公钥对商户公钥后30位进行RSA加密,再进行base64后,生成的密文串。若商户已经上架建行生活并同步公钥,可以不再上送商户公钥。
公钥加密方法见《建行生活输入通讯报文接口规范》的报文加密章节。
支付通知等接口见《建行生活APP服务方接入文档》。
用途说明
跳转建行生活页面 | 外部H5页|建信小程序 | 微信小程序。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| type | number | - | 是 | 跳转页面类型 | - |
| toPage | string | - | 是 | 跳转路径,类型为小程序本值为空时调起首页。 | - |
| isNewView | boolean | false | 否 | 是否打开新WebView,当type为1或2时有效。值为false时用进入小程序前入口所在页面的webview打开,若不存在则用新webview打开。 | - |
| isShowHeader | boolean | false | 否 | 是否展示通用标题栏,当type为1或2时有效 | - |
| headerName | string | - | 否 | 标题栏名称,isShowHeader为true时有效 | - |
| headerRightType | number | 0 | 否 | 展示标题栏时右边按钮的类型 | - |
| param | string | - | 否 | 跳转携带的参数,type非0时拼接到最终URL后 | - |
| miniId | string | - | 否 | 跳转小程序id,type为3或4时有效 | - |
| miniVersion | number | 0 | 否 | 微信小程序版本,type为4时有效 | |
| success | function | - | 否 | 接口调用的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
type 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 建行生活原生页面 | - |
| 1 | 建行生活H5页面 | - |
| 2 | 外部H5页面 | - |
| 3 | Jump小程序 | - |
| 4 | 微信小程序 | - |
headerRightType 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 关闭按钮 | - |
| 1 | 分享按钮 | - |
miniVersion 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 发布版 | - |
| 1 | 预览版 | - |
| 2 | 测试版 | - |
响应内容
无
state 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 跳转成功 | - |
| 1 | 跳转失败 | - |
用途说明
建行生活App的扫码功能,扫描建行生活提供的业务二维码(扫码支付等)需要用本接口而非jump.scanCode。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| content | string | 扫码结果 | - |
state 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 扫码成功 | - |
| 1 | 非有效业务二维码,无法解析 | - |
用途说明
打开支付码。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
无
state 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 打开成功 | - |
| 1 | 打开失败 | - |
用途说明
调起手机内的地图App。支持苹果地图|高德地图|百度地图。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| params | object | - | 否 | 参数 | - |
| needNavigation | boolean | false | 否 | 是否需要导航 | - |
| address | string | - | 是 | 商户地址 | - |
| lgt | number | - | 否 | 商户纬度 | - |
| ltt | number | - | 否 | 商户经度 | - |
| cityName | string | - | 否 | 城市名称 | - |
| business_name | string | - | 是 | 商户名称 | - |
| self_lgt | number | - | 否 | 客户维度 | - |
| self_ltt | number | - | 否 | 客户经度 | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
注意:
needNavigation为true时lgt 和ltt必填。
响应内容
无
state 的合法值:
| 值 | 说明 | 最低版本 |
|---|---|---|
| 0 | 调起地图成功 | - |
| 1 | 调起地图失败 | - |
用途说明
刷脸认证|人脸校验
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| PLATFORM_ID | string | - | 是 | 服务方ID | - |
| name | string | - | - | 姓名 | - |
| cardType | string | - | 是 | 卡片类型(身份证) | - |
| cardNum | string | - | 是 | 身份证号码 | - |
| phoneNum | string | - | 是 | 手机号 | - |
| showError | string | - | 否 | 报错弹窗:1-显示 0-不显示 | - |
| scanOnly | string | - | 否 | 仅刷脸:1-只刷脸,不发校验刷脸流水的交易 | - |
| Stm_Chnl_ID | string | - | 否 | 渠道号,默认为建行生活渠道 | - |
| Stm_Chnl_Txn_CD | string | - | 否 | 渠道交易码,默认为建行生活渠道交易码 | - |
| txCode | string | - | 否 | 安全交易码,默认为建行生活安全交易码 | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| success | string | 刷脸认证是否成功:0-失败,1-成功 | - |
| Comm_Auth_Fields | string | UUID | - |
| Apl_Aply_TrcNo | string | 全局流水号 | - |
用途说明
获取用户状态信息
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| PLATFORM_ID | string | - | - | 服务方ID | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| userType | string | 用户类型:00游客/未登录 01钱包用户 02已注册未开钱包 | - |
| isLogin | string | 登录状态:0:未登录 1:已登录 | - |
用途说明
分享。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| share_id | string | - | 是 | 分享id,存在分享ID时先调接口获取分享内容 | - |
| text | string | - | - | 分享的描述 | - |
| title | string | - | - | 标题 | - |
| url | string | - | - | 链接 | - |
| image | string | - | - | 图片链接 | - |
| type | string | - | - | 0--分享链接,1--分享微信朋友,2--分享朋友圈 | - |
| base64Pic | string | - | - | 图片base64格式化 | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| type | string | 0: 取消分享 1:分享到微信 2:分享到朋友圈 | - |
用途说明
校验用户身份。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| platformId | string | - | 是 | 服务方编号,非空 | - |
| sceneId | string | - | - | 场景ID | - |
| checkType | string | - | - | 校验类型 1-校验平台支付密码 2-校验平台登录密码 | - |
| checkScope | string | - | - | 验密有效范围,0-App内有效(默认值) 1-同类场景有效 2-场景内有效 3-场景内同功能有效 4-一次性有效 | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| token | string | 唯一验密流水号,每次重新生成 | - |
| code | string | 校验结果状态码 0:校验完成 1:已在其他功能场景校验通过,且在有效期内 -1:用户取消校验 -2:校验失败,当前场景未配置校验类型、用户状态异常等原因 | - |
注意:
当code为-1、-2时,token为空;为0、1时,token有值;
code为1时,若为弱金融场景,由业务和场景方决定可直接默认验密通过,若为强金融场景,由业务和场景方决定是否需要再次校验,如需再次校验,重新调起 ‘checkUser’ action并修改 ‘checkScope’ 参数为4即可;
收到 ‘checkUser’ action返回的token后,应由场景方服务端按服务方协议调用校验验密流水号交易,该交易返回验证通过后方能做后续涉密业务,否则应终止流程。
用途说明
实名认证。
请求参数
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| platformId | string | - | 是 | 服务方编号,非空 | - |
| success | function | - | 否 | 接口调用成功的回调函数 | - |
| fail | function | - | 否 | 接口调用失败的回调函数 | - |
| complete | function | - | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | - |
响应内容
| 属性 | 类型 | 说明 | 最低版本 |
|---|---|---|---|
| success | string | 实名结果 0-失败,1-成功 | - |