支付bug修改

This commit is contained in:
Billy 2025-10-18 01:16:25 +08:00
parent 24faf35e55
commit b65e4d520c
6 changed files with 653 additions and 54 deletions

View File

@ -95,11 +95,11 @@ class Ccbpayment extends Common
}
// 5. 保存支付流水号到订单
$order->ccb_pay_flow_id = $result['data']['order_sn']; // 使用订单号作为流水号
$order->ccb_pay_flow_id = $result['data']['pay_flow_id']; // ✅ 使用真实的支付流水号
$order->save();
// 6. 记录支付日志
$this->savePaymentLog($order, $result['data']['payment_string'], $result['data']['order_sn']);
$this->savePaymentLog($order, $result['data']['payment_string'], $result['data']['pay_flow_id']);
// 7. 返回支付串
$this->success('支付串生成成功', [
@ -108,7 +108,7 @@ class Ccbpayment extends Common
'mac' => $result['data']['mac'],
'order_id' => $order->id,
'order_sn' => $order->order_sn,
'pay_flow_id' => $result['data']['order_sn'],
'pay_flow_id' => $result['data']['pay_flow_id'], // ✅ 返回真实的支付流水号
'amount' => $result['data']['amount'],
]);
@ -210,23 +210,46 @@ class Ccbpayment extends Common
* 建行支付成功后,会向notify_url发送支付通知
* 这是服务器到服务器的回调,需要验签
*
* ⚠️ 重要:此接口为建行服务器异步回调,必须返回纯文本 'SUCCESS' 'FAIL'
*
* @return void
*/
public function notify()
{
try {
// TODO: 实现建行支付通知处理逻辑
// 1. 接收建行推送的支付结果
// 2. 验签
// 3. 更新订单状态
// 4. 返回success给建行
// 1. 获取原始请求数据
$rawData = file_get_contents('php://input');
Log::info('[建行通知] 收到异步通知: ' . $rawData);
$this->success('SUCCESS');
// 2. 解析POST参数
$params = $this->request->post();
// 3. 如果POST为空尝试解析原始数据
if (empty($params) && $rawData) {
parse_str($rawData, $params);
}
// 4. 记录参数
Log::info('[建行通知] 解析参数: ' . json_encode($params, JSON_UNESCAPED_UNICODE));
// 5. 验证必需参数
if (empty($params['ORDERID'])) {
Log::error('[建行通知] 缺少ORDERID参数');
echo 'FAIL';
return;
}
// 6. 调用支付服务处理通知
$result = $this->paymentService->handleNotify($params);
// 7. 返回处理结果
echo $result; // 'SUCCESS' 或 'FAIL'
Log::info('[建行通知] 处理完成: ' . $result);
} catch (Exception $e) {
Log::error('[建行通知] 处理失败 error:' . $e->getMessage());
$this->error('FAIL');
echo 'FAIL';
}
}

View File

@ -10,10 +10,17 @@ class CcbMD5
{
/**
* 生成API消息签名
* 使用大写MD5(源报文 + 私钥)
*
* ⚠️ 注意建行API接口签名使用大写MD5
* 签名规则MD5(JSON报文 + 服务方私钥) 转大写
*
* 示例:
* message = '{"CLD_HEADER":{...},"CLD_BODY":{...}}'
* privateKey = 'MIICeAIBA...'
* sign = strtoupper(md5(message + privateKey))
*
* @param string $message JSON格式的源报文
* @param string $privateKey 商户私钥BASE64格式
* @param string $privateKey 服务方私钥BASE64格式
* @return string 大写的32位MD5签名
*/
public static function signApiMessage($message, $privateKey)
@ -21,7 +28,7 @@ class CcbMD5
// 移除私钥中的空格和换行
$privateKey = preg_replace('/\s+/', '', $privateKey);
// 计算MD5并转大写
// 计算MD5并转大写(建行要求)
return strtoupper(md5($message . $privateKey));
}
@ -41,8 +48,13 @@ class CcbMD5
/**
* 生成支付字符串签名
* 使用小写MD5(支付字符串)
* 支付字符串格式: MERCHANTID=xxx&POSID=xxx&BRANCHID=xxx&ORDERID=xxx&PAYMENT=xxx&CURCODE=01&TXCODE=530550&REMARK1=&REMARK2=&TIMEOUT=商户私钥
*
* ⚠️ 注意建行收银台支付串签名使用小写MD5
* 签名规则MD5(支付参数按顺序拼接 + 商户私钥) 保持小写
*
* 示例:
* paymentString = 'MERCHANTID=xxx&POSID=xxx&...&TIMEOUT=xxx' + privateKey
* mac = md5(paymentString) // 小写
*
* @param array $params 支付参数
* @param string $privateKey 商户私钥
@ -73,7 +85,7 @@ class CcbMD5
// 拼接支付字符串并添加私钥
$paymentString = implode('&', $parts) . $privateKey;
// 计算MD5保持小写
// 计算MD5保持小写,建行要求
return md5($paymentString);
}

View File

@ -261,6 +261,13 @@ class CcbOrderService
/**
* 构建符合建行要求的订单数据
*
* ⚠️ 注意Shopro字段说明
* - total_fee: 实际支付金额
* - total_amount: 订单原价
* - discount_fee: 优惠金额
* - paid_time: 支付时间(毫秒时间戳!)
* - createtime: 创建时间(秒级时间戳)
*
* @param array $order 订单数组
* @param array $orderItems 订单商品列表
* @param string $ccbUserId 建行用户ID
@ -271,26 +278,35 @@ class CcbOrderService
// 构建商品列表
$goodsList = $this->buildGoodsList($orderItems);
// 计算各项金额
$totalAmount = number_format($order['total_amount'], 2, '.', '');
$payAmount = number_format($order['pay_amount'] ?? $order['total_amount'], 2, '.', '');
$discountAmount = number_format($order['discount_amount'] ?? 0, 2, '.', '');
// 计算各项金额Shopro使用total_fee作为实付金额
$totalAmount = number_format($order['total_amount'] ?? 0, 2, '.', '');
$payAmount = number_format($order['total_fee'] ?? 0, 2, '.', '');
$discountAmount = number_format($order['discount_fee'] ?? 0, 2, '.', '');
// 处理支付时间Shopro的paid_time是毫秒时间戳需要除以1000
$payTime = '';
if (!empty($order['paid_time'])) {
$payTime = date('YmdHis', intval($order['paid_time'] / 1000));
}
// 处理创建时间Shopro的createtime是秒级时间戳
$createTime = date('YmdHis', $order['createtime'] ?? time());
// 构建订单数据34个必填字段
return [
'USER_ID' => $ccbUserId, // 建行用户ID
'ORDER_ID' => $order['order_sn'], // 订单号
'ORDER_DT' => date('YmdHis', strtotime($order['createtime'])), // 订单时间
'TOTAL_AMT' => $totalAmount, // 订单总金额
'PAY_AMT' => $payAmount, // 实付金额
'DISCOUNT_AMT' => $discountAmount, // 优惠金额
'ORDER_DT' => $createTime, // 订单时间
'TOTAL_AMT' => $totalAmount, // 订单金额
'PAY_AMT' => $payAmount, // 实付金额total_fee
'DISCOUNT_AMT' => $discountAmount, // 优惠金额discount_fee
'ORDER_STATUS' => $this->mapOrderStatus($order['status']), // 订单状态
'REFUND_STATUS' => $this->mapRefundStatus($order['refund_status'] ?? 0), // 退款状态
'REFUND_STATUS' => $this->mapRefundStatus($order['aftersale_status'] ?? 0), // 退款状态aftersale_status
'MCT_NM' => $this->config['merchant']['name'] ?? '商户名称', // 商户名称
'MCT_ORDER_ID' => $order['id'], // 商户订单ID
'GOODS_LIST' => json_encode($goodsList, JSON_UNESCAPED_UNICODE), // 商品列表
'PAY_TYPE' => $this->mapPayType($order['pay_type'] ?? ''), // 支付方式
'PAY_TIME' => $order['paytime'] ? date('YmdHis', $order['paytime']) : '', // 支付时间
'PAY_TIME' => $payTime, // 支付时间(毫秒转秒)
'DELIVERY_TYPE' => '01', // 配送方式01快递
'DELIVERY_STATUS' => $this->mapDeliveryStatus($order['status']), // 配送状态
'DELIVERY_TIME' => $order['delivery_time'] ? date('YmdHis', $order['delivery_time']) : '', // 发货时间
@ -303,7 +319,7 @@ class CcbOrderService
'ORDER_TYPE' => '01', // 订单类型01普通订单
'IS_VIRTUAL' => '0', // 是否虚拟商品
'ORDER_URL' => $this->config['merchant']['order_detail_url'] . $order['id'], // 订单详情链接
'CREATE_TIME' => date('YmdHis'), // 创建时间
'CREATE_TIME' => $createTime, // 创建时间
'UPDATE_TIME' => date('YmdHis'), // 更新时间
'SHOP_ID' => '1', // 店铺ID
'SHOP_NAME' => $this->config['merchant']['name'] ?? '', // 店铺名称

View File

@ -35,6 +35,8 @@ class CcbPaymentService
* 生成建行支付串
* 用于前端JSBridge调用建行收银台
*
* ⚠️ 注意必须包含所有必需参数签名前按ASCII排序
*
* @param int $orderId Shopro订单ID
* @return array ['status' => bool, 'message' => string, 'data' => array]
*/
@ -58,32 +60,75 @@ class CcbPaymentService
throw new \Exception('用户未绑定建行账号');
}
// 生成支付串签名Shopro 使用 total_fee 作为支付金额字段)
$result = CcbMD5::generatePaymentSignature(
$this->config['merchant_id'],
$this->config['pos_id'],
$this->config['branch_id'],
$order['order_sn'],
number_format($order['total_fee'], 2, '.', ''), // 使用 total_fee
$this->config['private_key'],
'01', // 币种:人民币
'530550' // 交易码
);
// 生成支付流水号(使用订单号作为唯一标识)
$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->buildPaymentUrl($result['params'], $result['mac']);
$paymentUrl = $this->config['cashier_url'] . '?' . $finalPaymentString;
// 记录支付请求
$this->recordPaymentRequest($orderId, $result);
$this->recordPaymentRequest($orderId, [
'payment_string' => $finalPaymentString,
'params' => $paymentParams,
'mac' => $mac,
'pay_flow_id' => $payFlowId
]);
return [
'status' => true,
'message' => '支付串生成成功',
'data' => [
'payment_string' => $result['payment_string'],
'mac' => $result['mac'],
'payment_string' => $finalPaymentString,
'mac' => $mac,
'payment_url' => $paymentUrl,
'order_sn' => $order['order_sn'],
'pay_flow_id' => $payFlowId,
'amount' => number_format($order['total_fee'], 2, '.', '')
]
];
@ -98,6 +143,28 @@ class CcbPaymentService
}
}
/**
* 构建商品信息字符串
*
* @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);
}
/**
* 处理支付回调
* 建行支付完成后的同步回调
@ -117,13 +184,14 @@ class CcbPaymentService
}
// 获取关键参数
$orderSn = $params['ORDERID'] ?? '';
$payFlowId = $params['ORDERID'] ?? ''; // 支付流水号
$userOrderId = $params['USER_ORDERID'] ?? ''; // 商户订单号
$posId = $params['POSID'] ?? '';
$success = $params['SUCCESS'] ?? 'N';
// 验证参数
if (empty($orderSn)) {
throw new \Exception('订单号不能为空');
if (empty($payFlowId)) {
throw new \Exception('支付流水号不能为空');
}
// 验证POS号
@ -131,8 +199,14 @@ class CcbPaymentService
throw new \Exception('POS号验证失败');
}
// 查询订单
$order = Order::where('order_sn', $orderSn)->find();
// ⚠️ 重要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('订单不存在');
}
@ -150,7 +224,8 @@ class CcbPaymentService
'message' => '支付成功',
'data' => [
'order_id' => $order['id'],
'order_sn' => $orderSn,
'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号
'pay_flow_id' => $payFlowId, // 支付流水号
'amount' => $params['PAYMENT'] ?? ''
]
];
@ -161,7 +236,8 @@ class CcbPaymentService
'message' => '支付失败',
'data' => [
'order_id' => $order['id'],
'order_sn' => $orderSn,
'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号
'pay_flow_id' => $payFlowId, // 支付流水号
'error_code' => $params['ERRCODE'] ?? '',
'error_msg' => $params['ERRMSG'] ?? ''
]
@ -193,9 +269,16 @@ class CcbPaymentService
throw new \Exception('签名验证失败');
}
// 获取订单信息
$orderSn = $params['ORDERID'] ?? '';
$order = Order::where('order_sn', $orderSn)->find();
// ⚠️ 重要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('订单不存在');
@ -339,7 +422,7 @@ class CcbPaymentService
Db::name('ccb_payment_log')->insert([
'order_id' => $orderId,
'order_sn' => $order['order_sn'],
'pay_flow_id' => $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'] ?? '',

View File

@ -0,0 +1,206 @@
<?php
namespace addons\shopro\library\ccblife\model;
use think\Model;
/**
* 建行支付日志模型
*
* 用于记录建行支付相关的日志信息
* 表名: fa_ccb_payment_log
*/
class CcbPaymentLog extends Model
{
/**
* 数据表名称(不含前缀)
* @var string
*/
protected $name = 'ccb_payment_log';
/**
* 自动写入时间戳字段
* @var bool
*/
protected $autoWriteTimestamp = false;
/**
* 字段类型转换
* @var array
*/
protected $type = [
'order_id' => 'integer',
'user_id' => 'integer',
'amount' => 'float',
'status' => 'integer',
'pay_time' => 'integer',
'create_time' => 'integer',
];
/**
* 支付状态常量
*/
const STATUS_PENDING = 0; // 待支付
const STATUS_SUCCESS = 1; // 支付成功
const STATUS_FAILED = 2; // 支付失败
const STATUS_CANCELLED = 3; // 已取消
/**
* 支付状态映射
* @var array
*/
public static $statusMap = [
self::STATUS_PENDING => '待支付',
self::STATUS_SUCCESS => '支付成功',
self::STATUS_FAILED => '支付失败',
self::STATUS_CANCELLED => '已取消',
];
/**
* 获取状态文本
*
* @param int $status 状态值
* @return string
*/
public static function getStatusText($status)
{
return self::$statusMap[$status] ?? '未知状态';
}
/**
* 根据订单ID查找支付日志
*
* @param int $orderId 订单ID
* @return CcbPaymentLog|null
*/
public static function findByOrderId($orderId)
{
return self::where('order_id', $orderId)
->order('create_time', 'desc')
->find();
}
/**
* 根据支付流水号查找
*
* @param string $payFlowId 支付流水号
* @return CcbPaymentLog|null
*/
public static function findByPayFlowId($payFlowId)
{
return self::where('pay_flow_id', $payFlowId)->find();
}
/**
* 根据订单号查找
*
* @param string $orderSn 订单号
* @return CcbPaymentLog|null
*/
public static function findByOrderSn($orderSn)
{
return self::where('order_sn', $orderSn)
->order('create_time', 'desc')
->find();
}
/**
* 创建支付日志
*
* @param array $data 日志数据
* @return CcbPaymentLog|false
*/
public static function createLog($data)
{
$defaultData = [
'status' => self::STATUS_PENDING,
'create_time' => time(),
];
$logData = array_merge($defaultData, $data);
return self::create($logData);
}
/**
* 更新支付状态
*
* @param string $payFlowId 支付流水号
* @param int $status 状态
* @param array $extraData 额外数据
* @return bool
*/
public static function updateStatus($payFlowId, $status, $extraData = [])
{
$updateData = array_merge([
'status' => $status,
], $extraData);
return self::where('pay_flow_id', $payFlowId)->update($updateData) !== false;
}
/**
* 记录支付成功
*
* @param string $payFlowId 支付流水号
* @param string $transId 交易ID
* @param array $callbackData 回调数据
* @return bool
*/
public static function recordSuccess($payFlowId, $transId, $callbackData = [])
{
return self::updateStatus($payFlowId, self::STATUS_SUCCESS, [
'trans_id' => $transId,
'pay_time' => time(),
'callback_data' => is_array($callbackData) ? json_encode($callbackData, JSON_UNESCAPED_UNICODE) : $callbackData,
]);
}
/**
* 记录支付失败
*
* @param string $payFlowId 支付流水号
* @param string $errorMsg 错误信息
* @return bool
*/
public static function recordFailure($payFlowId, $errorMsg)
{
return self::updateStatus($payFlowId, self::STATUS_FAILED, [
'error_msg' => $errorMsg,
]);
}
/**
* 获取待支付的日志列表
*
* @param int $limit 限制数量
* @return array
*/
public static function getPendingList($limit = 100)
{
return self::where('status', self::STATUS_PENDING)
->where('create_time', '>', time() - 3600 * 24) // 24小时内
->limit($limit)
->select();
}
/**
* 关联订单模型(如果需要)
*
* @return \think\model\relation\BelongsTo
*/
public function order()
{
return $this->belongsTo('app\admin\model\shopro\order\Order', 'order_id', 'id');
}
/**
* 关联用户模型(如果需要)
*
* @return \think\model\relation\BelongsTo
*/
public function user()
{
return $this->belongsTo('app\common\model\User', 'user_id', 'id');
}
}

View File

@ -0,0 +1,259 @@
<?php
namespace addons\shopro\library\ccblife\model;
use think\Model;
/**
* 建行订单同步日志模型
*
* 用于记录建行订单同步相关的日志信息
* 表名: fa_ccb_sync_log
*/
class CcbSyncLog extends Model
{
/**
* 数据表名称(不含前缀)
* @var string
*/
protected $name = 'ccb_sync_log';
/**
* 自动写入时间戳字段
* @var bool
*/
protected $autoWriteTimestamp = false;
/**
* 字段类型转换
* @var array
*/
protected $type = [
'order_id' => 'integer',
'sync_status' => 'integer',
'sync_time' => 'integer',
'cost_time' => 'integer',
'retry_times' => 'integer',
];
/**
* 同步状态常量
*/
const STATUS_FAILED = 0; // 同步失败
const STATUS_SUCCESS = 1; // 同步成功
/**
* 交易代码常量
*/
const TX_CODE_PUSH = 'A3341TP01'; // 订单推送
const TX_CODE_UPDATE = 'A3341TP02'; // 订单状态更新
const TX_CODE_QUERY = 'A3341TP03'; // 订单查询
const TX_CODE_REFUND = 'A3341TP04'; // 订单退款
/**
* 交易代码映射
* @var array
*/
public static $txCodeMap = [
self::TX_CODE_PUSH => '订单推送',
self::TX_CODE_UPDATE => '状态更新',
self::TX_CODE_QUERY => '订单查询',
self::TX_CODE_REFUND => '订单退款',
];
/**
* 同步状态映射
* @var array
*/
public static $statusMap = [
self::STATUS_FAILED => '失败',
self::STATUS_SUCCESS => '成功',
];
/**
* 获取交易代码文本
*
* @param string $txCode 交易代码
* @return string
*/
public static function getTxCodeText($txCode)
{
return self::$txCodeMap[$txCode] ?? '未知交易';
}
/**
* 获取状态文本
*
* @param int $status 状态值
* @return string
*/
public static function getStatusText($status)
{
return self::$statusMap[$status] ?? '未知状态';
}
/**
* 根据订单ID查找同步日志
*
* @param int $orderId 订单ID
* @param string $txCode 交易代码(可选)
* @return array
*/
public static function findByOrderId($orderId, $txCode = '')
{
$query = self::where('order_id', $orderId);
if ($txCode) {
$query->where('tx_code', $txCode);
}
return $query->order('sync_time', 'desc')->select();
}
/**
* 根据交易流水号查找
*
* @param string $txSeq 交易流水号
* @return CcbSyncLog|null
*/
public static function findByTxSeq($txSeq)
{
return self::where('tx_seq', $txSeq)->find();
}
/**
* 创建同步日志
*
* @param array $data 日志数据
* @return CcbSyncLog|false
*/
public static function createLog($data)
{
$defaultData = [
'sync_status' => self::STATUS_FAILED,
'sync_time' => time(),
'retry_times' => 0,
'cost_time' => 0,
];
$logData = array_merge($defaultData, $data);
return self::create($logData);
}
/**
* 更新同步状态
*
* @param string $txSeq 交易流水号
* @param int $status 状态
* @param array $extraData 额外数据
* @return bool
*/
public static function updateStatus($txSeq, $status, $extraData = [])
{
$updateData = array_merge([
'sync_status' => $status,
], $extraData);
return self::where('tx_seq', $txSeq)->update($updateData) !== false;
}
/**
* 记录同步成功
*
* @param string $txSeq 交易流水号
* @param mixed $responseData 响应数据
* @param int $costTime 耗时(毫秒)
* @return bool
*/
public static function recordSuccess($txSeq, $responseData, $costTime = 0)
{
return self::updateStatus($txSeq, self::STATUS_SUCCESS, [
'response_data' => is_array($responseData) ? json_encode($responseData, JSON_UNESCAPED_UNICODE) : $responseData,
'cost_time' => $costTime,
]);
}
/**
* 记录同步失败
*
* @param string $txSeq 交易流水号
* @param string $errorMsg 错误信息
* @param int $costTime 耗时(毫秒)
* @return bool
*/
public static function recordFailure($txSeq, $errorMsg, $costTime = 0)
{
return self::updateStatus($txSeq, self::STATUS_FAILED, [
'error_msg' => $errorMsg,
'cost_time' => $costTime,
]);
}
/**
* 增加重试次数
*
* @param string $txSeq 交易流水号
* @return bool
*/
public static function incrementRetry($txSeq)
{
return self::where('tx_seq', $txSeq)->setInc('retry_times') !== false;
}
/**
* 获取失败的同步日志
*
* @param int $limit 限制数量
* @param int $maxRetryTimes 最大重试次数
* @return array
*/
public static function getFailedList($limit = 100, $maxRetryTimes = 3)
{
return self::where('sync_status', self::STATUS_FAILED)
->where('retry_times', '<', $maxRetryTimes)
->where('sync_time', '>', time() - 3600 * 24) // 24小时内
->order('sync_time', 'asc')
->limit($limit)
->select();
}
/**
* 获取订单最后一次同步记录
*
* @param int $orderId 订单ID
* @param string $txCode 交易代码
* @return CcbSyncLog|null
*/
public static function getLastSync($orderId, $txCode)
{
return self::where('order_id', $orderId)
->where('tx_code', $txCode)
->order('sync_time', 'desc')
->find();
}
/**
* 检查订单是否同步成功
*
* @param int $orderId 订单ID
* @param string $txCode 交易代码
* @return bool
*/
public static function isSyncSuccess($orderId, $txCode)
{
$lastSync = self::getLastSync($orderId, $txCode);
return $lastSync && $lastSync['sync_status'] === self::STATUS_SUCCESS;
}
/**
* 关联订单模型(如果需要)
*
* @return \think\model\relation\BelongsTo
*/
public function order()
{
return $this->belongsTo('app\admin\model\shopro\order\Order', 'order_id', 'id');
}
}