mirror of
https://gitee.com/liuxioabin/fengketrade.git
synced 2026-04-17 21:03:17 +08:00
支付串生成
This commit is contained in:
parent
b4e76a58ab
commit
70b1d415de
@ -92,6 +92,142 @@ return [
|
||||
'timeout_minutes' => 30, // 支付超时时间(分钟)
|
||||
],
|
||||
|
||||
// ========== 可选支付参数配置 ==========
|
||||
// 以下参数按需配置,不配置则不会添加到支付串中
|
||||
|
||||
/**
|
||||
* 外部平台商户号 (与建行商户号二选一)
|
||||
* 说明: 使用外部平台商户号时,会自动移除MERCHANTID、POSID、BRANCHID
|
||||
*/
|
||||
'plat_mct_id' => Env::get('ccb.plat_mct_id', ''),
|
||||
|
||||
/**
|
||||
* 微信支付19位终端号
|
||||
* 说明: 微信支付场景下使用
|
||||
*/
|
||||
'pos_id_19' => Env::get('ccb.pos_id_19', ''),
|
||||
|
||||
/**
|
||||
* 支付位图 - 控制支付方式
|
||||
* 格式: 6位字符串,每位对应一个支付方式(1=开启,0=关闭)
|
||||
* 位置: [生活钱包][龙支付][微信][数字人民币][信用付][快贷]
|
||||
* 示例: '111111' = 全部开启, '110000' = 仅生活钱包和龙支付
|
||||
*/
|
||||
'pay_bitmap' => Env::get('ccb.pay_bitmap', '110000'),
|
||||
|
||||
/**
|
||||
* 账户位图 - 控制支付账户类型
|
||||
* 格式: 5位字符串,每位对应一个账户类型(1=开启,0=关闭)
|
||||
* 位置: [建行借记卡][建行贷记卡][他行借记卡][他行贷记卡][建行钱包]
|
||||
* 示例: '11111' = 全部开启, '11000' = 仅建行卡
|
||||
*/
|
||||
'account_bitmap' => Env::get('ccb.account_bitmap', '11000'),
|
||||
|
||||
/**
|
||||
* 分期付款期数
|
||||
* 说明: 分期支付场景下使用
|
||||
*/
|
||||
'install_num' => Env::get('ccb.install_num', ''),
|
||||
|
||||
/**
|
||||
* 积分二级活动编号
|
||||
* 说明: 积分抵扣场景下使用
|
||||
*/
|
||||
'point_avy_id' => Env::get('ccb.point_avy_id', ''),
|
||||
|
||||
/**
|
||||
* 固定抵扣积分值
|
||||
* 说明: 固定积分抵扣场景下使用
|
||||
*/
|
||||
'fixed_point_val' => Env::get('ccb.fixed_point_val', ''),
|
||||
|
||||
/**
|
||||
* 最小使用积分抵扣限制
|
||||
* 说明: 积分抵扣最小值限制
|
||||
*/
|
||||
'min_point_limit' => Env::get('ccb.min_point_limit', ''),
|
||||
|
||||
/**
|
||||
* 有价券活动编号
|
||||
* 说明: 优惠券活动场景下使用
|
||||
*/
|
||||
'coupon_avy_id' => Env::get('ccb.coupon_avy_id', ''),
|
||||
|
||||
/**
|
||||
* 限制信用卡支付标志
|
||||
* 说明: 1=仅限信用卡支付
|
||||
*/
|
||||
'only_credit_pay_flag' => Env::get('ccb.only_credit_pay_flag', ''),
|
||||
|
||||
/**
|
||||
* 扩展域参数
|
||||
* 格式: JSON字符串(配置时无需urlencode,代码会自动处理)
|
||||
* 示例: '{"key1":"value1","key2":"value2"}'
|
||||
*/
|
||||
'extend_params' => Env::get('ccb.extend_params', ''),
|
||||
|
||||
// ========== 数字人民币(DCEP)配置 ==========
|
||||
|
||||
/**
|
||||
* 数字人民币商户类型
|
||||
* 1=融合商户(使用普通商户号)
|
||||
* 2=非融合商户(需单独配置数币商户号)
|
||||
*/
|
||||
'dcep_mct_type' => Env::get('ccb.dcep_mct_type', ''),
|
||||
|
||||
/**
|
||||
* 数字人民币商户号 (dcep_mct_type=2时必填)
|
||||
*/
|
||||
'dcep_merchant_id' => Env::get('ccb.dcep_merchant_id', ''),
|
||||
|
||||
/**
|
||||
* 数字人民币柜台号 (dcep_mct_type=2时必填)
|
||||
*/
|
||||
'dcep_pos_id' => Env::get('ccb.dcep_pos_id', ''),
|
||||
|
||||
/**
|
||||
* 数字人民币分行号 (dcep_mct_type=2时必填)
|
||||
*/
|
||||
'dcep_branch_id' => Env::get('ccb.dcep_branch_id', ''),
|
||||
|
||||
/**
|
||||
* 数字人民币存款账号
|
||||
*/
|
||||
'dcep_dep_acc_no' => Env::get('ccb.dcep_dep_acc_no', ''),
|
||||
|
||||
// ========== 二级商户配置(平台类服务方使用) ==========
|
||||
|
||||
/**
|
||||
* 二级商户编号
|
||||
* 说明: 平台型服务方为下级商户收款时使用
|
||||
*/
|
||||
'sub_mct_id' => Env::get('ccb.sub_mct_id', ''),
|
||||
|
||||
/**
|
||||
* 二级商户名称
|
||||
*/
|
||||
'sub_mct_name' => Env::get('ccb.sub_mct_name', ''),
|
||||
|
||||
/**
|
||||
* 二级商户MCC码
|
||||
* 说明: 商户类别码,标识商户行业类型
|
||||
*/
|
||||
'sub_mct_mcc' => Env::get('ccb.sub_mct_mcc', ''),
|
||||
|
||||
// ========== 场景编号配置(埋点使用,不参与MAC签名) ==========
|
||||
|
||||
/**
|
||||
* 场景编号
|
||||
* 说明: 用于数据埋点分析,不参与MAC校验
|
||||
*/
|
||||
'scn_id' => Env::get('ccb.scn_id', ''),
|
||||
|
||||
/**
|
||||
* 场景平台编号
|
||||
* 说明: 用于数据埋点分析,不参与MAC校验
|
||||
*/
|
||||
'scn_pltfrm_id' => Env::get('ccb.scn_pltfrm_id', ''),
|
||||
|
||||
// 日志配置
|
||||
'log' => [
|
||||
'enabled' => true,
|
||||
|
||||
@ -87,34 +87,43 @@ class Ccbpayment extends Common
|
||||
$this->error('订单已支付或已关闭');
|
||||
}
|
||||
|
||||
// 4. 生成支付串
|
||||
// 4. ✅ 生成支付流水号(统一标识,用于订单推送和支付串生成)
|
||||
// 格式: PAY + 年月日时分秒(14位) + 随机数(6位) = 23位
|
||||
$payFlowId = 'PAY' . date('YmdHis') . mt_rand(100000, 999999);
|
||||
Log::info('[建行支付] 生成支付流水号 pay_flow_id:' . $payFlowId . ' order_id:' . $orderId);
|
||||
|
||||
// 5. ✅ 先推送订单到建行生活(步骤2:调用A3341TP01订单推送接口)
|
||||
// ⚠️ 重要:必须先推送订单,收银台才能校验订单信息
|
||||
// 根据《5.6.2 业务流程说明》步骤2:由服务方调用订单推送接口向建行生活推送订单信息
|
||||
try {
|
||||
$pushResult = $this->orderService->pushOrder($orderId, $payFlowId);
|
||||
|
||||
if (!$pushResult['status']) {
|
||||
// ⚠️ 推送失败必须阻塞支付流程!收银台会找不到订单
|
||||
Log::error('[建行支付] 订单推送失败(阻塞支付) order_id:' . $orderId . ' error:' . $pushResult['message']);
|
||||
$this->error('订单推送失败,无法生成支付串: ' . $pushResult['message']);
|
||||
}
|
||||
|
||||
Log::info('[建行支付] 订单推送成功 order_id:' . $orderId);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// ⚠️ 推送异常必须阻塞支付流程
|
||||
Log::error('[建行支付] 订单推送异常(阻塞支付) order_id:' . $orderId . ' error:' . $e->getMessage());
|
||||
$this->error('订单推送异常,无法生成支付串: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 6. 生成支付串(步骤3:调用收银台)
|
||||
// ⚠️ 注意: generatePaymentString()内部已经完成了以下操作:
|
||||
// - 更新订单的ccb_pay_flow_id字段
|
||||
// - 记录支付日志到ccb_payment_log表
|
||||
// 控制器不应该重复操作,否则会导致数据重复写入!
|
||||
$result = $this->paymentService->generatePaymentString($orderId);
|
||||
$result = $this->paymentService->generatePaymentString($orderId, $payFlowId);
|
||||
|
||||
if (!$result['status']) {
|
||||
$this->error('支付串生成失败: ' . $result['message']);
|
||||
}
|
||||
|
||||
// 5. ✅ 推送订单到建行(步骤2:调用订单推送接口将已提交订单)
|
||||
// ⚠️ 注意:此时推送的是未支付状态的订单
|
||||
try {
|
||||
$pushResult = $this->orderService->pushOrder($orderId);
|
||||
|
||||
if ($pushResult['status']) {
|
||||
Log::info('[建行支付] 订单推送成功 order_id:' . $orderId);
|
||||
} else {
|
||||
// ⚠️ 推送失败不阻塞支付流程,只记录日志
|
||||
Log::warning('[建行支付] 订单推送失败(不阻塞支付) order_id:' . $orderId . ' error:' . $pushResult['message']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// ⚠️ 推送异常不阻塞支付流程
|
||||
Log::error('[建行支付] 订单推送异常(不阻塞支付) order_id:' . $orderId . ' error:' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 6. 返回支付串
|
||||
// 7. 返回支付串给前端调用收银台
|
||||
$this->success('支付串生成成功', $result['data']);
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
@ -46,14 +46,20 @@ class CcbOrderService
|
||||
* 当用户下单后调用此方法同步订单信息
|
||||
*
|
||||
* @param int $orderId Shopro订单ID
|
||||
* @param string $payFlowId 支付流水号(由控制器统一生成)
|
||||
* @return array ['status' => bool, 'message' => string, 'data' => array]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function pushOrder($orderId)
|
||||
public function pushOrder($orderId, $payFlowId)
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
// ✅ 验证支付流水号
|
||||
if (empty($payFlowId)) {
|
||||
throw new \Exception('支付流水号不能为空');
|
||||
}
|
||||
|
||||
// 获取订单信息
|
||||
$order = Db::name('shopro_order')
|
||||
->alias('o')
|
||||
@ -78,7 +84,8 @@ class CcbOrderService
|
||||
->select();
|
||||
|
||||
// 构建订单数据(符合A3341TP01接口规范)
|
||||
$orderData = $this->buildOrderData($order, $orderItems, $ccbUserId);
|
||||
// ✅ 传入统一的支付流水号
|
||||
$orderData = $this->buildOrderData($order, $orderItems, $ccbUserId, $payFlowId);
|
||||
|
||||
// 记录请求数据(同步日志)
|
||||
$txSeq = CcbMD5::generateTransactionSeq();
|
||||
@ -310,12 +317,11 @@ class CcbOrderService
|
||||
* @param string $ccbUserId 建行用户ID
|
||||
* @return array
|
||||
*/
|
||||
private function buildOrderData($order, $orderItems, $ccbUserId)
|
||||
private function buildOrderData($order, $orderItems, $ccbUserId, $payFlowId)
|
||||
{
|
||||
// ⚠️ 验证必填字段:PAY_FLOW_ID(支付流水号)
|
||||
// 这个字段在 createPayment 时设置,推送订单前必须存在
|
||||
if (empty($order['ccb_pay_flow_id'])) {
|
||||
throw new \Exception('订单支付流水号(ccb_pay_flow_id)不存在,请先调用createPayment生成支付串');
|
||||
// ✅ 使用控制器传入的统一支付流水号(确保与支付串生成使用同一流水号)
|
||||
if (empty($payFlowId)) {
|
||||
throw new \Exception('支付流水号不能为空');
|
||||
}
|
||||
|
||||
// 构建SKU商品列表(JSON字符串格式)
|
||||
@ -378,7 +384,7 @@ class CcbOrderService
|
||||
'ORDER_STATUS' => $this->mapOrderStatus($order['status']), // 订单状态
|
||||
'REFUND_STATUS' => $this->mapRefundStatus($order['refund_status'] ?? 0), // 退款状态
|
||||
'MCT_NM' => $this->config['merchant']['name'] ?? '商户名称', // 商户名称
|
||||
'PAY_FLOW_ID' => $order['ccb_pay_flow_id'], // 支付流水号(必填!已在上方验证)
|
||||
'PAY_FLOW_ID' => $payFlowId, // ✅ 支付流水号(使用控制器传入的统一流水号)
|
||||
'PAY_MRCH_ID' => $this->config['merchant_id'], // 支付商户号(必填!)
|
||||
'SKU_LIST' => $skuList, // 商品信息JSON字符串(必填!)
|
||||
|
||||
|
||||
@ -49,9 +49,10 @@ class CcbPaymentService
|
||||
* ⚠️ 注意:必须包含所有必需参数,签名前按ASCII排序
|
||||
*
|
||||
* @param int $orderId Shopro订单ID
|
||||
* @param string $payFlowId 支付流水号(由控制器统一生成)
|
||||
* @return array ['status' => bool, 'message' => string, 'data' => array]
|
||||
*/
|
||||
public function generatePaymentString($orderId)
|
||||
public function generatePaymentString($orderId, $payFlowId)
|
||||
{
|
||||
// ⚠️ 开启事务保护,确保数据一致性
|
||||
Db::startTrans();
|
||||
@ -68,37 +69,133 @@ class CcbPaymentService
|
||||
throw new \Exception('订单状态不正确');
|
||||
}
|
||||
|
||||
// 获取用户建行ID
|
||||
$user = Db::name('user')->where('id', $order['user_id'])->field('ccb_user_id')->find();
|
||||
// 获取用户建行生活ID(用于订单推送)
|
||||
$user = Db::name('user')->where('id', $order['user_id'])
|
||||
->field('ccb_user_id')
|
||||
->find();
|
||||
if (empty($user['ccb_user_id'])) {
|
||||
throw new \Exception('用户未绑定建行账号');
|
||||
throw new \Exception('用户未绑定建行生活账号');
|
||||
}
|
||||
|
||||
// 生成支付流水号(使用订单号作为唯一标识)
|
||||
$payFlowId = 'PAY' . date('YmdHis') . mt_rand(100000, 999999);
|
||||
// ✅ 使用控制器传入的统一支付流水号(确保与订单推送使用同一流水号)
|
||||
if (empty($payFlowId)) {
|
||||
throw new \Exception('支付流水号不能为空');
|
||||
}
|
||||
|
||||
// 构建完整的支付参数(34个参数)
|
||||
// ✅ 构建完整的48个支付参数(按照建行文档5.4完整参数定义)
|
||||
// 基础商户参数(必须二选一:建行商户号组合 或 外部平台商户号)
|
||||
$paymentParams = [
|
||||
'MERCHANTID' => $this->config['merchant_id'], // 商户代码
|
||||
'POSID' => $this->config['pos_id'], // 柜台代码
|
||||
'BRANCHID' => $this->config['branch_id'], // 分行代码
|
||||
'ORDERID' => $payFlowId, // 支付流水号(必须唯一!)
|
||||
'PAYMENT' => number_format($order['pay_fee'], 2, '.', ''), // 支付金额(Shopro使用pay_fee)
|
||||
'CURCODE' => '01', // 币种(01=人民币)
|
||||
'TXCODE' => '520100', // 交易码(520100=即时支付)
|
||||
'REMARK1' => '', // 备注1
|
||||
'REMARK2' => $this->config['service_id'], // 备注2(服务方编号)
|
||||
'TYPE' => '1', // 支付类型(1=个人)
|
||||
'GATEWAY' => '0', // 网关标志
|
||||
'CLIENTIP' => $this->getClientIp(), // 客户端IP
|
||||
'REGINFO' => '', // 注册信息
|
||||
'PROINFO' => $this->buildProductInfo($order), // 商品信息
|
||||
'REFERER' => '', // 来源页面
|
||||
'THIRDAPPINFO' => 'comccbpay1234567890cloudmerchant', // 第三方应用信息(固定值)
|
||||
'USER_ORDERID' => $order['order_sn'], // 商户订单号
|
||||
'TIMEOUT' => date('YmdHis', strtotime('+30 minutes')) // 超时时间
|
||||
'MERCHANTID' => $this->config['merchant_id'], // 商户代码(F=可选,但不用外部商户号时必填)
|
||||
'POSID' => $this->config['pos_id'], // 柜台代码(F)
|
||||
'BRANCHID' => $this->config['branch_id'], // 分行代码(F)
|
||||
'ORDERID' => $payFlowId, // 支付流水号(T=必送)
|
||||
'USER_ORDERID' => $order['order_sn'], // 用户订单号(T)
|
||||
'PAYMENT' => number_format($order['pay_fee'], 2, '.', ''), // 支付金额(T)
|
||||
'CURCODE' => '01', // 币种(T,01=人民币)
|
||||
'TXCODE' => '520100', // 交易码(T,520100=即时支付)
|
||||
'REMARK1' => '', // 备注1(T)
|
||||
'REMARK2' => $this->config['service_id'], // 备注2(T,服务方编号)
|
||||
'TYPE' => '1', // 接口类型(T,1=防钓鱼)
|
||||
'GATEWAY' => '0', // 网关类型(T)
|
||||
'CLIENTIP' => $this->getClientIp(), // 客户端IP(T)
|
||||
'REGINFO' => '', // 客户注册信息(T,中文需escape编码)
|
||||
'PROINFO' => $this->buildProductInfo($order), // 商品信息(T,中文已escape编码)
|
||||
'REFERER' => '', // 商户URL(T)
|
||||
'THIRDAPPINFO' => 'comccbpay1234567890cloudmerchant', // 客户端标识(T,固定值)
|
||||
'TIMEOUT' => date('YmdHis', strtotime('+30 minutes')), // 超时时间(F,格式YYYYMMDDHHmmss)
|
||||
];
|
||||
|
||||
// ✅ 可选参数(根据实际场景添加)
|
||||
// 外部平台商户号(与建行商户号二选一)
|
||||
if (!empty($this->config['plat_mct_id'])) {
|
||||
$paymentParams['PLATMCTID'] = $this->config['plat_mct_id'];
|
||||
// 使用外部商户号时,删除建行商户号
|
||||
unset($paymentParams['MERCHANTID'], $paymentParams['POSID'], $paymentParams['BRANCHID']);
|
||||
}
|
||||
|
||||
// 微信支付19位终端号
|
||||
if (!empty($this->config['pos_id_19'])) {
|
||||
$paymentParams['POSID19'] = $this->config['pos_id_19'];
|
||||
}
|
||||
|
||||
// 支付位图(控制支付方式:生活钱包/龙支付/微信/数币/信用付/快贷)
|
||||
if (!empty($this->config['pay_bitmap'])) {
|
||||
$paymentParams['PAYBITMAP'] = $this->config['pay_bitmap'];
|
||||
}
|
||||
|
||||
// 账户位图(控制支付账户:建行借记卡/贷记卡/他行借记卡/贷记卡/建行钱包)
|
||||
if (!empty($this->config['account_bitmap'])) {
|
||||
$paymentParams['ACCOUNTBITMAP'] = $this->config['account_bitmap'];
|
||||
}
|
||||
|
||||
// 分期期数
|
||||
if (!empty($this->config['install_num'])) {
|
||||
$paymentParams['INSTALLNUM'] = $this->config['install_num'];
|
||||
}
|
||||
|
||||
// 积分二级活动编号
|
||||
if (!empty($this->config['point_avy_id'])) {
|
||||
$paymentParams['POINTAVYID'] = $this->config['point_avy_id'];
|
||||
}
|
||||
|
||||
// 数字人民币参数
|
||||
if (!empty($this->config['dcep_mct_type'])) {
|
||||
$paymentParams['DCEP_MCT_TYPE'] = $this->config['dcep_mct_type'];
|
||||
if ($this->config['dcep_mct_type'] == '2') {
|
||||
// 非融合商户需要填写数币商户号
|
||||
$paymentParams['DCEP_MERCHANTID'] = $this->config['dcep_merchant_id'] ?? '';
|
||||
$paymentParams['DCEP_POSID'] = $this->config['dcep_pos_id'] ?? '';
|
||||
$paymentParams['DCEP_BRANCHID'] = $this->config['dcep_branch_id'] ?? '';
|
||||
}
|
||||
if (!empty($this->config['dcep_dep_acc_no'])) {
|
||||
$paymentParams['DCEPDEPACCNO'] = $this->config['dcep_dep_acc_no'];
|
||||
}
|
||||
}
|
||||
|
||||
// 有价券活动编号
|
||||
if (!empty($this->config['coupon_avy_id'])) {
|
||||
$paymentParams['COUPONAVYID'] = $this->config['coupon_avy_id'];
|
||||
}
|
||||
|
||||
// 限制信用卡支付标志
|
||||
if (!empty($this->config['only_credit_pay_flag'])) {
|
||||
$paymentParams['ONLY_CREDIT_PAY_FLAG'] = $this->config['only_credit_pay_flag'];
|
||||
}
|
||||
|
||||
// 固定抵扣积分值
|
||||
if (!empty($this->config['fixed_point_val'])) {
|
||||
$paymentParams['FIXEDPOINTVAL'] = $this->config['fixed_point_val'];
|
||||
}
|
||||
|
||||
// 最小使用积分抵扣限制
|
||||
if (!empty($this->config['min_point_limit'])) {
|
||||
$paymentParams['MINPOINTLIMIT'] = $this->config['min_point_limit'];
|
||||
}
|
||||
|
||||
// 扩展域(JSON格式,需encodeURI)
|
||||
if (!empty($this->config['extend_params'])) {
|
||||
$paymentParams['EXTENDPARAMS'] = urlencode($this->config['extend_params']);
|
||||
}
|
||||
|
||||
// 二级商户参数(平台类服务方使用)
|
||||
if (!empty($this->config['sub_mct_id'])) {
|
||||
$paymentParams['SUB_MCT_ID'] = $this->config['sub_mct_id'];
|
||||
}
|
||||
if (!empty($this->config['sub_mct_name'])) {
|
||||
$paymentParams['SUB_MCT_NAME'] = $this->config['sub_mct_name'];
|
||||
}
|
||||
if (!empty($this->config['sub_mct_mcc'])) {
|
||||
$paymentParams['SUB_MCT_MCC'] = $this->config['sub_mct_mcc'];
|
||||
}
|
||||
|
||||
// 场景编号(埋点使用,不参与MAC校验)
|
||||
if (!empty($this->config['scn_id'])) {
|
||||
$paymentParams['SCNID'] = $this->config['scn_id'];
|
||||
}
|
||||
if (!empty($this->config['scn_pltfrm_id'])) {
|
||||
$paymentParams['SCN_PLTFRM_ID'] = $this->config['scn_pltfrm_id'];
|
||||
}
|
||||
|
||||
// 按ASCII排序
|
||||
ksort($paymentParams);
|
||||
|
||||
@ -108,9 +205,9 @@ class CcbPaymentService
|
||||
// ⚠️ 建行支付串签名规则(v2.2版本):
|
||||
// 1. PLATFORMPUB字段仅参与MD5计算,不作为HTTP参数传递
|
||||
// 2. 签名 = MD5(参数字符串 + &PLATFORMPUB= + 服务方公钥内容)
|
||||
// 3. 生成32位大写MD5字符串(对照MD5Util.java第30行)
|
||||
// 3. 生成32位小写MD5字符串(根据建行文档5.4.1要求)
|
||||
$platformPubKey = $this->config['public_key']; // 服务方公钥
|
||||
$mac = strtoupper(md5($signString . '&PLATFORMPUB=' . $platformPubKey));
|
||||
$mac = strtolower(md5($signString . '&PLATFORMPUB=' . $platformPubKey));
|
||||
|
||||
// ✅ 修复:使用 CcbRSA 加密商户公钥后30位(用于ENCPUB字段)
|
||||
// 删除 CcbEncryption 类,统一使用 CcbRSA 处理密钥格式化
|
||||
@ -182,11 +279,47 @@ class CcbPaymentService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现JavaScript的escape()编码
|
||||
* 用于REGINFO和PROINFO字段的中文编码
|
||||
*
|
||||
* 根据建行文档4.3:
|
||||
* "使用js的escape()方法对REGINFO(客户注册信息)和PROINFO(商品信息)进行编码"
|
||||
*
|
||||
* @param string $str 要编码的字符串
|
||||
* @return string 编码后的字符串
|
||||
*/
|
||||
private function jsEscape($str)
|
||||
{
|
||||
if (empty($str)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = '';
|
||||
$length = mb_strlen($str, 'UTF-8');
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$char = mb_substr($str, $i, 1, 'UTF-8');
|
||||
|
||||
// ASCII字符(数字、字母、部分符号)不编码
|
||||
if (preg_match('/^[A-Za-z0-9@*_+\-.\\/]$/', $char)) {
|
||||
$result .= $char;
|
||||
} else {
|
||||
// 非ASCII字符转为 %uXXXX 格式(如:小 -> %u5C0F)
|
||||
$unicode = mb_ord($char, 'UTF-8');
|
||||
$result .= '%u' . strtoupper(str_pad(dechex($unicode), 4, '0', STR_PAD_LEFT));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建商品信息字符串
|
||||
* 根据建行文档4.3:商品信息中文需使用escape()编码
|
||||
*
|
||||
* @param object $order 订单对象
|
||||
* @return string
|
||||
* @return string escape编码后的商品信息
|
||||
*/
|
||||
private function buildProductInfo($order)
|
||||
{
|
||||
@ -197,11 +330,14 @@ class CcbPaymentService
|
||||
->column('goods_title');
|
||||
|
||||
if (empty($orderItems)) {
|
||||
return '商城订单';
|
||||
$productInfo = '商城订单';
|
||||
} else {
|
||||
// 拼接商品名称,建议不超过50字符(编码前)
|
||||
$productInfo = mb_substr(implode(',', $orderItems), 0, 50, 'UTF-8');
|
||||
}
|
||||
|
||||
// 拼接商品名称
|
||||
return implode(',', $orderItems);
|
||||
// ✅ 使用JavaScript的escape()编码中文
|
||||
return $this->jsEscape($productInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -292,8 +428,8 @@ class CcbPaymentService
|
||||
// 支付成功,更新订单状态
|
||||
$this->updateOrderPaymentStatus($order, $params);
|
||||
|
||||
// 同步订单到建行
|
||||
$this->orderService->pushOrder($order['id']);
|
||||
// ✅ 更新订单状态到建行(订单已在createPayment时推送,这里只需更新状态)
|
||||
$this->orderService->updateOrderStatus($order['id']);
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user