fengketrade/addons/shopro/library/ccblife/CcbPaymentService.php
2025-10-18 01:16:25 +08:00

491 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace addons\shopro\library\ccblife;
use app\admin\model\shopro\order\Order;
use think\Db;
use think\Log;
/**
* 建行生活支付服务类
* 处理支付串生成、支付回调、支付验证等业务
*/
class CcbPaymentService
{
/**
* 配置信息
*/
private $config;
/**
* 订单服务实例
*/
private $orderService;
/**
* 构造函数
*/
public function __construct()
{
$this->config = config('ccblife');
$this->orderService = new CcbOrderService();
}
/**
* 生成建行支付串
* 用于前端JSBridge调用建行收银台
*
* ⚠️ 注意必须包含所有必需参数签名前按ASCII排序
*
* @param int $orderId Shopro订单ID
* @return array ['status' => bool, 'message' => string, 'data' => array]
*/
public function generatePaymentString($orderId)
{
try {
// 获取订单信息
$order = Order::find($orderId);
if (!$order) {
throw new \Exception('订单不存在');
}
// 检查订单状态
if ($order['status'] != 'unpaid') {
throw new \Exception('订单状态不正确');
}
// 获取用户建行ID
$user = Db::name('user')->where('id', $order['user_id'])->field('ccb_user_id')->find();
if (empty($user['ccb_user_id'])) {
throw new \Exception('用户未绑定建行账号');
}
// 生成支付流水号(使用订单号作为唯一标识)
$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, // 支付流水号(必须唯一!)
'PAYMENT' => number_format($order['total_fee'], 2, '.', ''), // 支付金额
'CURCODE' => '01', // 币种01=人民币)
'TXCODE' => '520100', // 交易码520100=即时支付)
'REMARK1' => '', // 备注1
'REMARK2' => $this->config['service_id'], // 备注2服务方编号
'TYPE' => '1', // 支付类型1=个人)
'GATEWAY' => '0', // 网关标志
'CLIENTIP' => request()->ip(), // 客户端IP
'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()
]);
// 构建完整的支付URL
$paymentUrl = $this->config['cashier_url'] . '?' . $finalPaymentString;
// 记录支付请求
$this->recordPaymentRequest($orderId, [
'payment_string' => $finalPaymentString,
'params' => $paymentParams,
'mac' => $mac,
'pay_flow_id' => $payFlowId
]);
return [
'status' => true,
'message' => '支付串生成成功',
'data' => [
'payment_string' => $finalPaymentString,
'mac' => $mac,
'payment_url' => $paymentUrl,
'order_sn' => $order['order_sn'],
'pay_flow_id' => $payFlowId,
'amount' => number_format($order['total_fee'], 2, '.', '')
]
];
} catch (\Exception $e) {
Log::error('建行支付串生成失败: ' . $e->getMessage());
return [
'status' => false,
'message' => $e->getMessage(),
'data' => null
];
}
}
/**
* 构建商品信息字符串
*
* @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);
}
/**
* 处理支付回调
* 建行支付完成后的同步回调
*
* @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);
}
}
// 获取关键参数
$payFlowId = $params['ORDERID'] ?? ''; // 支付流水号
$userOrderId = $params['USER_ORDERID'] ?? ''; // 商户订单号
$posId = $params['POSID'] ?? '';
$success = $params['SUCCESS'] ?? 'N';
// 验证参数
if (empty($payFlowId)) {
throw new \Exception('支付流水号不能为空');
}
// 验证POS号
if ($posId != $this->config['pos_id']) {
throw new \Exception('POS号验证失败');
}
// ⚠️ 重要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();
}
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'],
'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号
'pay_flow_id' => $payFlowId, // 支付流水号
'amount' => $params['PAYMENT'] ?? ''
]
];
} else {
// 支付失败
return [
'status' => false,
'message' => '支付失败',
'data' => [
'order_id' => $order['id'],
'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号
'pay_flow_id' => $payFlowId, // 支付流水号
'error_code' => $params['ERRCODE'] ?? '',
'error_msg' => $params['ERRMSG'] ?? ''
]
];
}
} catch (\Exception $e) {
Log::error('建行支付回调处理失败: ' . $e->getMessage());
return [
'status' => false,
'message' => $e->getMessage(),
'data' => null
];
}
}
/**
* 处理异步通知
* 建行支付异步通知处理
*
* @param array $params 通知参数
* @return string 'success' 或 'fail'
*/
public function handleNotify($params)
{
try {
// 验证签名
if (!$this->verifyNotifySignature($params)) {
throw new \Exception('签名验证失败');
}
// ⚠️ 重要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();
}
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';
}
}
/**
* 验证支付结果
* 主动查询订单支付状态
*
* @param string $orderSn 订单号
* @return bool
*/
public function verifyPayment($orderSn)
{
try {
// 查询建行订单状态
$result = $this->orderService->queryOrder($orderSn);
if ($result['status']) {
$data = $result['data']['CLD_BODY'] ?? [];
$txnStatus = $data['TXN_STATUS'] ?? '';
// 00=交易成功
return $txnStatus == '00';
}
return false;
} catch (\Exception $e) {
Log::error('建行支付验证失败: ' . $e->getMessage());
return false;
}
}
/**
* 构建支付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;
}
/**
* 更新订单支付状态
*
* @param object $order 订单对象
* @param array $params 支付参数
*/
private function updateOrderPaymentStatus($order, $params)
{
// 更新订单状态为已支付
Order::where('id', $order['id'])->update([
'status' => 'paid',
'pay_type' => 'ccb',
'paytime' => time(),
'transaction_id' => $params['ORDERID'] ?? '',
'updatetime' => time()
]);
// 记录支付日志
$this->recordPaymentLog($order['id'], 'payment_success', $params);
}
/**
* 验证异步通知签名
*
* @param array $params 通知参数
* @return bool
*/
private function verifyNotifySignature($params)
{
// 获取签名
$signature = $params['SIGN'] ?? '';
if (empty($signature)) {
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);
}
/**
* 记录支付请求
*
* @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'],
'pay_flow_id' => $paymentData['pay_flow_id'] ?? '', // ✅ 使用真实的支付流水号
'payment_string' => $paymentData['payment_string'] ?? '',
'user_id' => $order['user_id'],
'ccb_user_id' => $user['ccb_user_id'] ?? '',
'amount' => $order['total_fee'],
'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
];
}
}
}