2025-10-17 16:32:16 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace addons\shopro\library\ccblife;
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
use app\admin\model\shopro\order\Order;
|
|
|
|
|
|
use think\Db;
|
|
|
|
|
|
use think\Log;
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* 建行生活支付服务类
|
|
|
|
|
|
* 处理支付串生成、支付回调、支付验证等业务
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
|
|
|
|
|
class CcbPaymentService
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 配置信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* 订单服务实例
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
private $orderService;
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构造函数
|
|
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
public function __construct()
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-18 15:47:25 +08:00
|
|
|
|
// 加载插件配置文件
|
|
|
|
|
|
$configFile = __DIR__ . '/../../config/ccblife.php';
|
|
|
|
|
|
if (file_exists($configFile)) {
|
|
|
|
|
|
$this->config = include $configFile;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new \Exception('建行生活配置文件不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理BASE64格式的密钥,添加PEM包装
|
|
|
|
|
|
$this->config = $this->processPemKeys($this->config);
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$this->orderService = new CcbOrderService();
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-18 15:47:25 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理PEM格式密钥
|
|
|
|
|
|
* 如果密钥是BASE64格式(不含-----BEGIN-----),则添加PEM包装
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array $config 配置数组
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function processPemKeys($config)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理私钥
|
|
|
|
|
|
if (!empty($config['private_key']) && strpos($config['private_key'], '-----BEGIN') === false) {
|
|
|
|
|
|
$config['private_key'] = "-----BEGIN PRIVATE KEY-----\n"
|
|
|
|
|
|
. chunk_split($config['private_key'], 64, "\n")
|
|
|
|
|
|
. "-----END PRIVATE KEY-----";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理公钥
|
|
|
|
|
|
if (!empty($config['public_key']) && strpos($config['public_key'], '-----BEGIN') === false) {
|
|
|
|
|
|
$config['public_key'] = "-----BEGIN PUBLIC KEY-----\n"
|
|
|
|
|
|
. chunk_split($config['public_key'], 64, "\n")
|
|
|
|
|
|
. "-----END PUBLIC KEY-----";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容merchant_public_key字段
|
|
|
|
|
|
if (empty($config['merchant_public_key'])) {
|
|
|
|
|
|
$config['merchant_public_key'] = $config['public_key'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理平台公钥
|
|
|
|
|
|
if (!empty($config['platform_public_key'])) {
|
|
|
|
|
|
// 如果有配置平台公钥且是BASE64格式,添加PEM包装
|
|
|
|
|
|
if (strpos($config['platform_public_key'], '-----BEGIN') === false) {
|
|
|
|
|
|
$config['platform_public_key'] = "-----BEGIN PUBLIC KEY-----\n"
|
|
|
|
|
|
. chunk_split($config['platform_public_key'], 64, "\n")
|
|
|
|
|
|
. "-----END PUBLIC KEY-----";
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有配置平台公钥,使用商户公钥作为默认值
|
|
|
|
|
|
$config['platform_public_key'] = $config['public_key'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $config;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 16:32:16 +08:00
|
|
|
|
/**
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* 生成建行支付串
|
|
|
|
|
|
* 用于前端JSBridge调用建行收银台
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-18 01:16:25 +08:00
|
|
|
|
* ⚠️ 注意:必须包含所有必需参数,签名前按ASCII排序
|
|
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param int $orderId Shopro订单ID
|
|
|
|
|
|
* @return array ['status' => bool, 'message' => string, 'data' => array]
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
public function generatePaymentString($orderId)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
try {
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 获取订单信息
|
|
|
|
|
|
$order = Order::find($orderId);
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('订单不存在');
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 检查订单状态
|
|
|
|
|
|
if ($order['status'] != 'unpaid') {
|
|
|
|
|
|
throw new \Exception('订单状态不正确');
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 获取用户建行ID
|
|
|
|
|
|
$user = Db::name('user')->where('id', $order['user_id'])->field('ccb_user_id')->find();
|
|
|
|
|
|
if (empty($user['ccb_user_id'])) {
|
|
|
|
|
|
throw new \Exception('用户未绑定建行账号');
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-18 01:16:25 +08:00
|
|
|
|
// 生成支付流水号(使用订单号作为唯一标识)
|
|
|
|
|
|
$payFlowId = 'PAY' . date('YmdHis') . mt_rand(100000, 999999);
|
|
|
|
|
|
|
|
|
|
|
|
// 构建完整的支付参数(34个参数)
|
|
|
|
|
|
$paymentParams = [
|
|
|
|
|
|
'MERCHANTID' => $this->config['merchant_id'], // 商户代码
|
|
|
|
|
|
'POSID' => $this->config['pos_id'], // 柜台代码
|
|
|
|
|
|
'BRANCHID' => $this->config['branch_id'], // 分行代码
|
|
|
|
|
|
'ORDERID' => $payFlowId, // 支付流水号(必须唯一!)
|
2025-10-18 15:47:25 +08:00
|
|
|
|
'PAYMENT' => number_format($order['pay_fee'], 2, '.', ''), // 支付金额(Shopro使用pay_fee)
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'CURCODE' => '01', // 币种(01=人民币)
|
|
|
|
|
|
'TXCODE' => '520100', // 交易码(520100=即时支付)
|
|
|
|
|
|
'REMARK1' => '', // 备注1
|
|
|
|
|
|
'REMARK2' => $this->config['service_id'], // 备注2(服务方编号)
|
|
|
|
|
|
'TYPE' => '1', // 支付类型(1=个人)
|
|
|
|
|
|
'GATEWAY' => '0', // 网关标志
|
2025-10-18 15:47:25 +08:00
|
|
|
|
'CLIENTIP' => $this->getClientIp(), // 客户端IP
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'REGINFO' => '', // 注册信息
|
|
|
|
|
|
'PROINFO' => $this->buildProductInfo($order), // 商品信息
|
|
|
|
|
|
'REFERER' => '', // 来源页面
|
|
|
|
|
|
'THIRDAPPINFO' => 'comccbpay1234567890cloudmerchant', // 第三方应用信息(固定值)
|
|
|
|
|
|
'USER_ORDERID' => $order['order_sn'], // 商户订单号
|
|
|
|
|
|
'TIMEOUT' => date('YmdHis', strtotime('+30 minutes')) // 超时时间
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 按ASCII排序
|
|
|
|
|
|
ksort($paymentParams);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成签名字符串
|
|
|
|
|
|
$signString = http_build_query($paymentParams);
|
|
|
|
|
|
|
|
|
|
|
|
// ⚠️ 注意:建行支付串签名规则
|
|
|
|
|
|
// 签名 = MD5(参数字符串 + 服务方私钥)
|
|
|
|
|
|
// 不需要PLATFORMPUB字段,直接使用私钥签名
|
|
|
|
|
|
$mac = md5($signString . $this->config['private_key']);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用RSA加密商户公钥(用于ENCPUB字段)
|
|
|
|
|
|
$encryption = new CcbEncryption($this->config);
|
|
|
|
|
|
$encpub = $encryption->encryptMerchantPublicKey();
|
|
|
|
|
|
|
|
|
|
|
|
// 组装最终支付串
|
|
|
|
|
|
$finalPaymentString = $signString . '&MAC=' . $mac . '&PLATFORMID=' . $this->config['service_id'] . '&ENCPUB=' . urlencode($encpub);
|
|
|
|
|
|
|
|
|
|
|
|
// 保存支付流水号到订单
|
|
|
|
|
|
Order::where('id', $orderId)->update([
|
|
|
|
|
|
'ccb_pay_flow_id' => $payFlowId,
|
|
|
|
|
|
'updatetime' => time()
|
|
|
|
|
|
]);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 构建完整的支付URL
|
2025-10-18 01:16:25 +08:00
|
|
|
|
$paymentUrl = $this->config['cashier_url'] . '?' . $finalPaymentString;
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 记录支付请求
|
2025-10-18 01:16:25 +08:00
|
|
|
|
$this->recordPaymentRequest($orderId, [
|
|
|
|
|
|
'payment_string' => $finalPaymentString,
|
|
|
|
|
|
'params' => $paymentParams,
|
|
|
|
|
|
'mac' => $mac,
|
|
|
|
|
|
'pay_flow_id' => $payFlowId
|
|
|
|
|
|
]);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
return [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'status' => true,
|
|
|
|
|
|
'message' => '支付串生成成功',
|
|
|
|
|
|
'data' => [
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'payment_string' => $finalPaymentString,
|
|
|
|
|
|
'mac' => $mac,
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'payment_url' => $paymentUrl,
|
|
|
|
|
|
'order_sn' => $order['order_sn'],
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'pay_flow_id' => $payFlowId,
|
2025-10-18 15:47:25 +08:00
|
|
|
|
'amount' => number_format($order['pay_fee'], 2, '.', '')
|
2025-10-17 17:18:15 +08:00
|
|
|
|
]
|
2025-10-17 16:32:16 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('建行支付串生成失败: ' . $e->getMessage());
|
2025-10-17 16:32:16 +08:00
|
|
|
|
return [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'status' => false,
|
|
|
|
|
|
'message' => $e->getMessage(),
|
|
|
|
|
|
'data' => null
|
2025-10-17 16:32:16 +08:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-18 15:47:25 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取客户端IP
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return string
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function getClientIp()
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
$ip = request()->ip();
|
|
|
|
|
|
return $ip ?: '127.0.0.1';
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
// CLI模式或其他异常情况,使用默认值
|
|
|
|
|
|
return '127.0.0.1';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-18 01:16:25 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 构建商品信息字符串
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param object $order 订单对象
|
|
|
|
|
|
* @return string
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function buildProductInfo($order)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取订单商品
|
|
|
|
|
|
$orderItems = Db::name('shopro_order_item')
|
|
|
|
|
|
->where('order_id', $order['id'])
|
|
|
|
|
|
->limit(3) // 最多取3个商品
|
|
|
|
|
|
->column('goods_title');
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($orderItems)) {
|
|
|
|
|
|
return '商城订单';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拼接商品名称
|
|
|
|
|
|
return implode(',', $orderItems);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 16:32:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理支付回调
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* 建行支付完成后的同步回调
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array $params URL参数
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function handleCallback($params)
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 解密ccbParamSJ参数
|
|
|
|
|
|
if (isset($params['ccbParamSJ'])) {
|
|
|
|
|
|
$decryptedParams = CcbUrlDecrypt::decrypt($params['ccbParamSJ'], $this->config['service_id']);
|
|
|
|
|
|
if ($decryptedParams) {
|
|
|
|
|
|
$params = array_merge($params, $decryptedParams);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取关键参数
|
2025-10-18 01:16:25 +08:00
|
|
|
|
$payFlowId = $params['ORDERID'] ?? ''; // 支付流水号
|
|
|
|
|
|
$userOrderId = $params['USER_ORDERID'] ?? ''; // 商户订单号
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$posId = $params['POSID'] ?? '';
|
|
|
|
|
|
$success = $params['SUCCESS'] ?? 'N';
|
|
|
|
|
|
|
|
|
|
|
|
// 验证参数
|
2025-10-18 01:16:25 +08:00
|
|
|
|
if (empty($payFlowId)) {
|
|
|
|
|
|
throw new \Exception('支付流水号不能为空');
|
2025-10-17 17:18:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证POS号
|
|
|
|
|
|
if ($posId != $this->config['pos_id']) {
|
|
|
|
|
|
throw new \Exception('POS号验证失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-18 01:16:25 +08:00
|
|
|
|
// ⚠️ 重要:ORDERID是支付流水号,不是订单号!
|
|
|
|
|
|
// 优先使用USER_ORDERID查询,如果没有则用ccb_pay_flow_id查询
|
|
|
|
|
|
if (!empty($userOrderId)) {
|
|
|
|
|
|
$order = Order::where('order_sn', $userOrderId)->find();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$order = Order::where('ccb_pay_flow_id', $payFlowId)->find();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理支付结果
|
|
|
|
|
|
if ($success == 'Y') {
|
|
|
|
|
|
// 支付成功,更新订单状态
|
|
|
|
|
|
$this->updateOrderPaymentStatus($order, $params);
|
|
|
|
|
|
|
|
|
|
|
|
// 同步订单到建行
|
|
|
|
|
|
$this->orderService->pushOrder($order['id']);
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'status' => true,
|
|
|
|
|
|
'message' => '支付成功',
|
|
|
|
|
|
'data' => [
|
|
|
|
|
|
'order_id' => $order['id'],
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号
|
|
|
|
|
|
'pay_flow_id' => $payFlowId, // 支付流水号
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'amount' => $params['PAYMENT'] ?? ''
|
|
|
|
|
|
]
|
|
|
|
|
|
];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 支付失败
|
|
|
|
|
|
return [
|
|
|
|
|
|
'status' => false,
|
|
|
|
|
|
'message' => '支付失败',
|
|
|
|
|
|
'data' => [
|
|
|
|
|
|
'order_id' => $order['id'],
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号
|
|
|
|
|
|
'pay_flow_id' => $payFlowId, // 支付流水号
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'error_code' => $params['ERRCODE'] ?? '',
|
|
|
|
|
|
'error_msg' => $params['ERRMSG'] ?? ''
|
|
|
|
|
|
]
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('建行支付回调处理失败: ' . $e->getMessage());
|
|
|
|
|
|
return [
|
|
|
|
|
|
'status' => false,
|
|
|
|
|
|
'message' => $e->getMessage(),
|
|
|
|
|
|
'data' => null
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理异步通知
|
|
|
|
|
|
* 建行支付异步通知处理
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param array $params 通知参数
|
|
|
|
|
|
* @return string 'success' 或 'fail'
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
public function handleNotify($params)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-17 17:18:15 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 验证签名
|
|
|
|
|
|
if (!$this->verifyNotifySignature($params)) {
|
|
|
|
|
|
throw new \Exception('签名验证失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-18 01:16:25 +08:00
|
|
|
|
// ⚠️ 重要:ORDERID是支付流水号,不是订单号!
|
|
|
|
|
|
// 优先使用USER_ORDERID查询,如果没有则用ccb_pay_flow_id查询
|
|
|
|
|
|
$payFlowId = $params['ORDERID'] ?? ''; // 支付流水号
|
|
|
|
|
|
$userOrderId = $params['USER_ORDERID'] ?? ''; // 商户订单号
|
|
|
|
|
|
|
|
|
|
|
|
if (!empty($userOrderId)) {
|
|
|
|
|
|
$order = Order::where('order_sn', $userOrderId)->find();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$order = Order::where('ccb_pay_flow_id', $payFlowId)->find();
|
|
|
|
|
|
}
|
2025-10-17 17:18:15 +08:00
|
|
|
|
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果订单已支付,直接返回成功
|
|
|
|
|
|
if ($order['status'] == 'paid') {
|
|
|
|
|
|
return 'success';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新订单状态
|
|
|
|
|
|
$this->updateOrderPaymentStatus($order, $params);
|
|
|
|
|
|
|
|
|
|
|
|
// 同步到建行
|
|
|
|
|
|
$this->orderService->pushOrder($order['id']);
|
|
|
|
|
|
|
|
|
|
|
|
return 'success';
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('建行支付异步通知处理失败: ' . $e->getMessage());
|
|
|
|
|
|
return 'fail';
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 验证支付结果
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* 主动查询订单支付状态
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param string $orderSn 订单号
|
|
|
|
|
|
* @return bool
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
public function verifyPayment($orderSn)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-17 17:18:15 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 查询建行订单状态
|
|
|
|
|
|
$result = $this->orderService->queryOrder($orderSn);
|
|
|
|
|
|
|
|
|
|
|
|
if ($result['status']) {
|
|
|
|
|
|
$data = $result['data']['CLD_BODY'] ?? [];
|
|
|
|
|
|
$txnStatus = $data['TXN_STATUS'] ?? '';
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 00=交易成功
|
|
|
|
|
|
return $txnStatus == '00';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('建行支付验证失败: ' . $e->getMessage());
|
|
|
|
|
|
return false;
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
2025-10-17 17:18:15 +08:00
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 构建支付URL
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array $params 支付参数
|
|
|
|
|
|
* @param string $mac 签名
|
|
|
|
|
|
* @return string
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function buildPaymentUrl($params, $mac)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 添加必要参数
|
|
|
|
|
|
$params['MAC'] = $mac;
|
|
|
|
|
|
$params['REMARK2'] = $this->config['service_id']; // 服务方编号
|
|
|
|
|
|
|
|
|
|
|
|
// 生成查询字符串
|
|
|
|
|
|
$queryString = http_build_query($params);
|
|
|
|
|
|
|
|
|
|
|
|
// 返回完整URL(实际使用时通过JSBridge调用,不直接访问)
|
|
|
|
|
|
return $this->config['cashier_url'] . '?' . $queryString;
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* 更新订单支付状态
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param object $order 订单对象
|
|
|
|
|
|
* @param array $params 支付参数
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
private function updateOrderPaymentStatus($order, $params)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-18 15:47:25 +08:00
|
|
|
|
// ⚠️ 重要字段说明:
|
|
|
|
|
|
// 1. paid_time: Shopro使用毫秒时间戳(time() * 1000)
|
|
|
|
|
|
// 2. pay_type: 建行支付暂用'offline'(建行线下银行支付),后续可扩展枚举
|
|
|
|
|
|
// 3. transaction_id: 存储建行支付流水号(ORDERID)
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
Order::where('id', $order['id'])->update([
|
|
|
|
|
|
'status' => 'paid',
|
2025-10-18 15:47:25 +08:00
|
|
|
|
'pay_type' => 'offline', // 建行支付归类为线下银行支付
|
|
|
|
|
|
'paid_time' => time() * 1000, // 毫秒时间戳
|
|
|
|
|
|
'transaction_id' => $params['ORDERID'] ?? '', // 建行支付流水号
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'updatetime' => time()
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录支付日志
|
|
|
|
|
|
$this->recordPaymentLog($order['id'], 'payment_success', $params);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* 验证异步通知签名
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param array $params 通知参数
|
|
|
|
|
|
* @return bool
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
private function verifyNotifySignature($params)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 获取签名
|
|
|
|
|
|
$signature = $params['SIGN'] ?? '';
|
|
|
|
|
|
if (empty($signature)) {
|
|
|
|
|
|
return false;
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 移除签名字段
|
|
|
|
|
|
unset($params['SIGN']);
|
|
|
|
|
|
|
|
|
|
|
|
// 按照建行要求的方式构建签名字符串
|
|
|
|
|
|
ksort($params);
|
|
|
|
|
|
$signStr = '';
|
|
|
|
|
|
foreach ($params as $key => $value) {
|
|
|
|
|
|
if ($value !== '') {
|
|
|
|
|
|
$signStr .= $key . '=' . $value . '&';
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$signStr = rtrim($signStr, '&');
|
|
|
|
|
|
|
|
|
|
|
|
// 使用私钥计算签名
|
|
|
|
|
|
$expectedSign = md5($signStr . $this->config['private_key']);
|
|
|
|
|
|
|
|
|
|
|
|
return strtolower($signature) === strtolower($expectedSign);
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 记录支付请求
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $orderId 订单ID
|
|
|
|
|
|
* @param array $paymentData 支付数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function recordPaymentRequest($orderId, $paymentData)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取订单信息
|
|
|
|
|
|
$order = Order::find($orderId);
|
|
|
|
|
|
$user = Db::name('user')->where('id', $order['user_id'])->field('ccb_user_id')->find();
|
|
|
|
|
|
|
|
|
|
|
|
// 记录到建行支付日志表
|
|
|
|
|
|
Db::name('ccb_payment_log')->insert([
|
|
|
|
|
|
'order_id' => $orderId,
|
|
|
|
|
|
'order_sn' => $order['order_sn'],
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'pay_flow_id' => $paymentData['pay_flow_id'] ?? '', // ✅ 使用真实的支付流水号
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'payment_string' => $paymentData['payment_string'] ?? '',
|
|
|
|
|
|
'user_id' => $order['user_id'],
|
|
|
|
|
|
'ccb_user_id' => $user['ccb_user_id'] ?? '',
|
2025-10-18 15:47:25 +08:00
|
|
|
|
'amount' => $order['pay_fee'], // 使用Shopro的pay_fee字段
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'status' => 0, // 待支付
|
|
|
|
|
|
'create_time' => time()
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 记录支付日志
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $orderId 订单ID
|
|
|
|
|
|
* @param string $type 日志类型
|
|
|
|
|
|
* @param array $data 数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function recordPaymentLog($orderId, $type, $data)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 更新建行支付日志
|
|
|
|
|
|
if ($type == 'payment_success') {
|
|
|
|
|
|
Db::name('ccb_payment_log')
|
|
|
|
|
|
->where('order_id', $orderId)
|
|
|
|
|
|
->update([
|
|
|
|
|
|
'status' => 1, // 支付成功
|
|
|
|
|
|
'pay_time' => time(),
|
|
|
|
|
|
'trans_id' => $data['ORDERID'] ?? '',
|
|
|
|
|
|
'callback_data' => json_encode($data, JSON_UNESCAPED_UNICODE)
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成退款申请
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $orderId 订单ID
|
|
|
|
|
|
* @param float $refundAmount 退款金额
|
|
|
|
|
|
* @param string $refundReason 退款原因
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function refund($orderId, $refundAmount, $refundReason = '')
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 调用订单服务处理退款
|
|
|
|
|
|
$result = $this->orderService->refundOrder($orderId, $refundAmount, $refundReason);
|
|
|
|
|
|
|
|
|
|
|
|
if ($result['status']) {
|
|
|
|
|
|
// 记录退款日志
|
|
|
|
|
|
$this->recordPaymentLog($orderId, 'refund_request', [
|
|
|
|
|
|
'amount' => $refundAmount,
|
|
|
|
|
|
'reason' => $refundReason
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('建行退款申请失败: ' . $e->getMessage());
|
|
|
|
|
|
return [
|
|
|
|
|
|
'status' => false,
|
|
|
|
|
|
'message' => $e->getMessage(),
|
|
|
|
|
|
'data' => null
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|