2025-10-17 16:32:16 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace addons\shopro\library\ccblife;
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
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 CcbOrderService
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* HTTP客户端实例
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
|
|
|
|
|
private $httpClient;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 配置信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构造函数
|
|
|
|
|
|
*/
|
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('建行生活配置文件不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 10:17:40 +08:00
|
|
|
|
// ✅ 修复: 删除processPemKeys()调用
|
|
|
|
|
|
// 密钥格式化统一由CcbRSA类处理,避免重复格式化导致OpenSSL ASN1解析错误
|
|
|
|
|
|
// CcbRSA::formatPublicKey/formatPrivateKey 会在加密/解密时自动处理密钥格式
|
2025-10-18 15:47:25 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$this->httpClient = new CcbHttpClient($this->config);
|
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 int $orderId Shopro订单ID
|
2025-10-21 14:33:20 +08:00
|
|
|
|
* @param string $payFlowId 支付流水号(由控制器统一生成)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @return array ['status' => bool, 'message' => string, 'data' => array]
|
|
|
|
|
|
* @throws \Exception
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-21 14:33:20 +08:00
|
|
|
|
public function pushOrder($orderId, $payFlowId)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$startTime = microtime(true);
|
|
|
|
|
|
|
2025-10-17 16:32:16 +08:00
|
|
|
|
try {
|
2025-10-21 14:33:20 +08:00
|
|
|
|
// ✅ 验证支付流水号
|
|
|
|
|
|
if (empty($payFlowId)) {
|
|
|
|
|
|
throw new \Exception('支付流水号不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 获取订单信息
|
|
|
|
|
|
$order = Db::name('shopro_order')
|
|
|
|
|
|
->alias('o')
|
|
|
|
|
|
->join('user u', 'o.user_id = u.id', 'LEFT')
|
|
|
|
|
|
->where('o.id', $orderId)
|
|
|
|
|
|
->field('o.*, u.ccb_user_id')
|
|
|
|
|
|
->find();
|
|
|
|
|
|
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取建行用户ID
|
|
|
|
|
|
$ccbUserId = $order['ccb_user_id'];
|
|
|
|
|
|
if (!$ccbUserId) {
|
|
|
|
|
|
throw new \Exception('用户未绑定建行生活账号');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取订单商品列表
|
|
|
|
|
|
$orderItems = Db::name('shopro_order_item')
|
|
|
|
|
|
->where('order_id', $orderId)
|
2025-10-21 10:17:40 +08:00
|
|
|
|
->select();
|
2025-10-17 17:18:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 构建订单数据(符合A3341TP01接口规范)
|
2025-10-21 14:33:20 +08:00
|
|
|
|
// ✅ 传入统一的支付流水号
|
|
|
|
|
|
$orderData = $this->buildOrderData($order, $orderItems, $ccbUserId, $payFlowId);
|
2025-10-17 17:18:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 记录请求数据(同步日志)
|
|
|
|
|
|
$txSeq = CcbMD5::generateTransactionSeq();
|
|
|
|
|
|
$this->recordSyncLog($orderId, 'A3341TP01', $txSeq, $orderData, 'request');
|
|
|
|
|
|
|
|
|
|
|
|
// 调用建行API推送订单
|
|
|
|
|
|
$response = $this->httpClient->pushOrder($orderData);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录响应数据和耗时
|
|
|
|
|
|
$costTime = round((microtime(true) - $startTime) * 1000, 2);
|
|
|
|
|
|
$this->recordSyncLog($orderId, 'A3341TP01', $txSeq, $response, 'response', true, $costTime);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-22 21:06:35 +08:00
|
|
|
|
// 更新订单同步状态为成功(清空错误信息)
|
|
|
|
|
|
$this->updateOrderSyncStatus($orderId, 1, '');
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
return [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'status' => true,
|
|
|
|
|
|
'message' => '订单推送成功',
|
|
|
|
|
|
'data' => $response
|
2025-10-17 16:32:16 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
// 记录错误
|
|
|
|
|
|
$costTime = round((microtime(true) - $startTime) * 1000, 2);
|
|
|
|
|
|
$this->recordSyncLog($orderId, 'A3341TP01', '', null, 'error', false, $costTime, $e->getMessage());
|
|
|
|
|
|
|
2025-10-22 21:06:35 +08:00
|
|
|
|
// 更新同步状态为失败,并保存错误信息
|
|
|
|
|
|
$this->updateOrderSyncStatus($orderId, 2, $e->getMessage());
|
2025-10-17 17:18:15 +08:00
|
|
|
|
|
|
|
|
|
|
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-17 17:18:15 +08:00
|
|
|
|
* 更新订单状态到建行生活
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param int $orderId 订单ID
|
2025-10-22 20:32:53 +08:00
|
|
|
|
* @param string|null $status 支付状态(0-待支付 1-支付成功 2-已过期 3-支付失败 4-取消)
|
|
|
|
|
|
* @param string|null $refundStatus 退款状态(0-无退款 1-退款申请 2-已退款 3-部分退款)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @return array
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-17 17:18:15 +08:00
|
|
|
|
public function updateOrderStatus($orderId, $status = null, $refundStatus = null)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$startTime = microtime(true);
|
|
|
|
|
|
$txSeq = CcbMD5::generateTransactionSeq();
|
|
|
|
|
|
|
2025-10-17 16:32:16 +08:00
|
|
|
|
try {
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 获取订单信息(包含支付流水号)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$order = Db::name('shopro_order')
|
|
|
|
|
|
->alias('o')
|
|
|
|
|
|
->join('user u', 'o.user_id = u.id', 'LEFT')
|
|
|
|
|
|
->where('o.id', $orderId)
|
|
|
|
|
|
->field('o.*, u.ccb_user_id')
|
|
|
|
|
|
->find();
|
|
|
|
|
|
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 获取支付流水号
|
|
|
|
|
|
$payFlowId = $order['pay_flow_id'] ?? null;
|
|
|
|
|
|
if (empty($payFlowId)) {
|
|
|
|
|
|
throw new \Exception('订单缺少支付流水号,无法更新状态到建行');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取支付商户号
|
|
|
|
|
|
$payMrchId = $this->config['merchant_id'] ?? null;
|
|
|
|
|
|
if (empty($payMrchId)) {
|
|
|
|
|
|
throw new \Exception('配置中缺少支付商户号(merchant_id)');
|
2025-10-17 17:18:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 映射订单状态
|
2025-10-22 20:32:53 +08:00
|
|
|
|
$payStatus = $status ?: $this->mapOrderStatus($order['status']);
|
|
|
|
|
|
$mappedRefundStatus = $refundStatus ?: $this->mapRefundStatus($order['refund_status'] ?? 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 确定通知类型(0-支付状态修改 1-退款状态修改)
|
|
|
|
|
|
$informId = !empty($refundStatus) && $refundStatus != '0' ? '1' : '0';
|
|
|
|
|
|
|
|
|
|
|
|
// 构建额外参数
|
|
|
|
|
|
$additionalParams = [];
|
|
|
|
|
|
if ($informId == '0') {
|
|
|
|
|
|
// 支付状态修改
|
|
|
|
|
|
$additionalParams['PAY_STATUS'] = $payStatus;
|
|
|
|
|
|
$additionalParams['PAY_AMT'] = number_format($order['pay_fee'] ?? 0, 2, '.', '');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 退款状态修改
|
|
|
|
|
|
$additionalParams['REFUND_STATUS'] = $mappedRefundStatus;
|
|
|
|
|
|
$additionalParams['TOTAL_REFUND_AMT'] = number_format($order['refund_fee'] ?? 0, 2, '.', '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加其他可选参数
|
|
|
|
|
|
$additionalParams['DISCOUNT_AMT'] = number_format($order['total_discount_fee'] ?? 0, 2, '.', '');
|
|
|
|
|
|
if (!empty($order['goods_name'])) {
|
|
|
|
|
|
$additionalParams['GOODS_NM'] = mb_substr($order['goods_name'], 0, 200);
|
|
|
|
|
|
}
|
2025-10-17 17:18:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 记录请求
|
|
|
|
|
|
$requestData = [
|
2025-10-22 20:32:53 +08:00
|
|
|
|
'order_id' => $order['order_sn'],
|
|
|
|
|
|
'inform_id' => $informId,
|
|
|
|
|
|
'pay_flow_id' => $payFlowId,
|
|
|
|
|
|
'pay_mrch_id' => $payMrchId,
|
|
|
|
|
|
'additional_params' => $additionalParams
|
2025-10-17 17:18:15 +08:00
|
|
|
|
];
|
|
|
|
|
|
$this->recordSyncLog($orderId, 'A3341TP02', $txSeq, $requestData, 'request');
|
|
|
|
|
|
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 调用建行API更新状态(使用新接口)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$response = $this->httpClient->updateOrderStatus(
|
2025-10-22 20:32:53 +08:00
|
|
|
|
$order['order_sn'], // 订单编号
|
|
|
|
|
|
$informId, // 通知类型
|
|
|
|
|
|
$payFlowId, // 支付流水号
|
|
|
|
|
|
$payMrchId, // 支付商户号
|
|
|
|
|
|
$additionalParams // 额外参数
|
2025-10-17 17:18:15 +08:00
|
|
|
|
);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 记录响应
|
|
|
|
|
|
$costTime = round((microtime(true) - $startTime) * 1000, 2);
|
|
|
|
|
|
$this->recordSyncLog($orderId, 'A3341TP02', $txSeq, $response, 'response', true, $costTime);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
return [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'status' => true,
|
|
|
|
|
|
'message' => '订单状态更新成功',
|
|
|
|
|
|
'data' => $response
|
2025-10-17 16:32:16 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
$costTime = round((microtime(true) - $startTime) * 1000, 2);
|
|
|
|
|
|
$this->recordSyncLog($orderId, 'A3341TP02', $txSeq, null, 'error', false, $costTime, $e->getMessage());
|
|
|
|
|
|
|
|
|
|
|
|
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-17 17:18:15 +08:00
|
|
|
|
* 查询建行订单信息
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-22 20:32:53 +08:00
|
|
|
|
* @param string $orderSn 订单号(支付流水号,对应收银台ORDERID字段)
|
|
|
|
|
|
* @param string|null $startTime 开始时间(格式yyyyMMddHHmmss,默认7天前)
|
|
|
|
|
|
* @param string|null $endTime 结束时间(格式yyyyMMddHHmmss,默认当前时间)
|
|
|
|
|
|
* @param int $page 页码(默认1)
|
|
|
|
|
|
* @param string $txType 交易类型(0-支付交易 1-退款交易 a-查询可退款订单)
|
|
|
|
|
|
* @param string $txnStatus 交易状态(00-成功 01-失败 02-不确定)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @return array
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-22 20:32:53 +08:00
|
|
|
|
public function queryOrder($orderSn, $startTime = null, $endTime = null, $page = 1, $txType = '0', $txnStatus = '00')
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
try {
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 调用建行API查询订单(使用新接口)
|
|
|
|
|
|
$response = $this->httpClient->queryOrder(
|
|
|
|
|
|
$orderSn,
|
|
|
|
|
|
$startTime,
|
|
|
|
|
|
$endTime,
|
|
|
|
|
|
$page,
|
|
|
|
|
|
$txType,
|
|
|
|
|
|
$txnStatus
|
|
|
|
|
|
);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
return [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'status' => true,
|
|
|
|
|
|
'message' => '订单查询成功',
|
|
|
|
|
|
'data' => $response
|
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-17 17:18:15 +08:00
|
|
|
|
* 处理订单退款
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param int $orderId 订单ID
|
2025-10-22 20:32:53 +08:00
|
|
|
|
* @param float $refundAmount 退款金额(单位:元)
|
|
|
|
|
|
* @param string|null $refundCode 退款流水号(可选,建议填写用于查询退款结果)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @return array
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-22 20:32:53 +08:00
|
|
|
|
public function refundOrder($orderId, $refundAmount, $refundCode = null)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
try {
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 获取订单信息(需要支付流水号和支付时间)
|
2025-10-21 09:51:19 +08:00
|
|
|
|
$order = Db::name('shopro_order')->where('id', $orderId)->find();
|
2025-10-17 17:18:15 +08:00
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证退款金额
|
2025-10-21 09:51:19 +08:00
|
|
|
|
if ($refundAmount > $order['order_amount']) {
|
2025-10-17 17:18:15 +08:00
|
|
|
|
throw new \Exception('退款金额不能超过订单总额');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 获取支付流水号(必须)
|
|
|
|
|
|
$payFlowId = $order['pay_flow_id'] ?? null;
|
|
|
|
|
|
if (empty($payFlowId)) {
|
|
|
|
|
|
throw new \Exception('订单缺少支付流水号,无法执行退款');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取支付时间(用于计算查询时间范围)
|
|
|
|
|
|
$payTime = $order['pay_time'] ?? $order['createtime'];
|
|
|
|
|
|
if (empty($payTime)) {
|
|
|
|
|
|
throw new \Exception('订单缺少支付时间,无法执行退款');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用建行API发起退款(使用新接口)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$response = $this->httpClient->refund(
|
2025-10-22 20:32:53 +08:00
|
|
|
|
$payFlowId, // 支付流水号(对应收银台ORDERID)
|
|
|
|
|
|
$refundAmount, // 退款金额
|
|
|
|
|
|
$payTime, // 支付时间(用于计算查询时间范围)
|
|
|
|
|
|
$refundCode // 退款流水号(可选)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 更新订单退款状态
|
|
|
|
|
|
$this->updateOrderStatus($orderId, null, '2');
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
return [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'status' => true,
|
|
|
|
|
|
'message' => '退款申请成功',
|
|
|
|
|
|
'data' => $response
|
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-21 09:51:19 +08:00
|
|
|
|
* 构建符合建行 A3341TP01 接口规范的订单数据
|
|
|
|
|
|
*
|
2025-10-21 11:11:59 +08:00
|
|
|
|
* 📋 建行生活订单推送接口规范说明(v1.1.6):
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* 必填字段(11个):
|
|
|
|
|
|
* - USER_ID: 客户编号(建行用户ID)
|
|
|
|
|
|
* - ORDER_ID: 订单号
|
|
|
|
|
|
* - ORDER_DT: 订单日期(yyyyMMddHHmmss格式)
|
|
|
|
|
|
* - TOTAL_AMT: 订单原金额
|
|
|
|
|
|
* - ORDER_STATUS: 订单状态
|
2025-10-21 11:11:59 +08:00
|
|
|
|
* - REFUND_STATUS: 退款状态
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* - MCT_NM: 商户名称
|
|
|
|
|
|
* - CUS_ORDER_URL: 订单详情链接
|
2025-10-21 11:11:59 +08:00
|
|
|
|
* - PAY_FLOW_ID: 支付流水号
|
|
|
|
|
|
* - PAY_MRCH_ID: 支付商户号
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* - SKU_LIST: 商品信息JSON字符串
|
|
|
|
|
|
*
|
2025-10-21 11:11:59 +08:00
|
|
|
|
* 重要可选字段(建议必填):
|
|
|
|
|
|
* - PAY_AMT: 订单实际支付金额(文档要求:如为空必须在状态变更时推送)
|
|
|
|
|
|
* - DISCOUNT_AMT: 第三方平台优惠金额(文档要求:如为空必须在状态变更时推送)
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* - DISCOUNT_AMT_DESC: 第三方平台优惠说明
|
2025-10-21 11:11:59 +08:00
|
|
|
|
* - INV_DT: 订单过期日期
|
|
|
|
|
|
* - GOODS_NM: 商品名称
|
|
|
|
|
|
* - PREFTL_MRCH_ID: 门店商户号
|
|
|
|
|
|
* - PLAT_MCT_ID: 服务商门店编号
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* - PLAT_ORDER_TYPE: 服务方订单类型
|
2025-10-21 11:11:59 +08:00
|
|
|
|
* - PLATFORM: 下单场景
|
2025-10-21 09:51:19 +08:00
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 注意:Shopro字段映射
|
|
|
|
|
|
* - pay_fee → PAY_AMT(实际支付金额)
|
|
|
|
|
|
* - order_amount → TOTAL_AMT(订单总金额)
|
|
|
|
|
|
* - total_discount_fee → DISCOUNT_AMT(优惠总金额)
|
|
|
|
|
|
* - createtime → ORDER_DT(毫秒时间戳需除以1000)
|
2025-10-21 11:11:59 +08:00
|
|
|
|
* - expiry_time → INV_DT(过期时间)
|
2025-10-18 01:16:25 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param array $order 订单数组
|
|
|
|
|
|
* @param array $orderItems 订单商品列表
|
|
|
|
|
|
* @param string $ccbUserId 建行用户ID
|
|
|
|
|
|
* @return array
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*/
|
2025-10-21 14:33:20 +08:00
|
|
|
|
private function buildOrderData($order, $orderItems, $ccbUserId, $payFlowId)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
2025-10-21 14:33:20 +08:00
|
|
|
|
// ✅ 使用控制器传入的统一支付流水号(确保与支付串生成使用同一流水号)
|
|
|
|
|
|
if (empty($payFlowId)) {
|
|
|
|
|
|
throw new \Exception('支付流水号不能为空');
|
2025-10-21 11:11:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 09:51:19 +08:00
|
|
|
|
// 构建SKU商品列表(JSON字符串格式)
|
|
|
|
|
|
$skuList = $this->buildSkuList($orderItems);
|
2025-10-21 11:11:59 +08:00
|
|
|
|
|
2025-10-21 09:51:19 +08:00
|
|
|
|
// 计算各项金额(保留2位小数)
|
2025-10-18 15:47:25 +08:00
|
|
|
|
$totalAmount = number_format($order['order_amount'] ?? 0, 2, '.', '');
|
2025-10-21 11:11:59 +08:00
|
|
|
|
$payAmount = number_format($order['pay_fee'] ?? $order['order_amount'] ?? 0, 2, '.', '');
|
|
|
|
|
|
$discountAmount = number_format($order['total_discount_fee'] ?? 0, 2, '.', '');
|
|
|
|
|
|
$totalRefundAmount = number_format($order['refund_fee'] ?? 0, 2, '.', '');
|
|
|
|
|
|
|
2025-10-21 20:48:28 +08:00
|
|
|
|
// 处理订单时间(自动判断秒级或毫秒级时间戳)
|
2025-10-18 15:47:25 +08:00
|
|
|
|
$createTimeValue = $order['createtime'] ?? null;
|
|
|
|
|
|
if (empty($createTimeValue) || !is_numeric($createTimeValue)) {
|
2025-10-21 20:48:28 +08:00
|
|
|
|
$createTimeValue = time(); // 使用当前秒级时间戳
|
2025-10-18 15:47:25 +08:00
|
|
|
|
}
|
2025-10-21 20:48:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 判断时间戳类型:大于9999999999说明是毫秒级(13位数),否则是秒级(10位数)
|
|
|
|
|
|
if ($createTimeValue > 9999999999) {
|
|
|
|
|
|
// 毫秒级时间戳,除以1000转为秒
|
|
|
|
|
|
$timestamp = intval($createTimeValue / 1000);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 秒级时间戳,直接使用
|
|
|
|
|
|
$timestamp = intval($createTimeValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$orderDt = date('YmdHis', $timestamp);
|
2025-10-18 15:47:25 +08:00
|
|
|
|
|
2025-10-21 11:11:59 +08:00
|
|
|
|
// 处理订单过期时间
|
|
|
|
|
|
$invDt = '';
|
|
|
|
|
|
if (!empty($order['expiry_time'])) {
|
|
|
|
|
|
// Shopro 的 expiry_time 可能是时间戳或日期字符串
|
|
|
|
|
|
if (is_numeric($order['expiry_time'])) {
|
|
|
|
|
|
// 如果是毫秒时间戳,需要除以1000
|
|
|
|
|
|
$timestamp = intval($order['expiry_time']);
|
|
|
|
|
|
if ($timestamp > 9999999999) {
|
|
|
|
|
|
$timestamp = intval($timestamp / 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
$invDt = date('YmdHis', $timestamp);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$invDt = date('YmdHis', strtotime($order['expiry_time']));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取商品名称(取第一个商品)
|
|
|
|
|
|
$goodsName = '';
|
|
|
|
|
|
if (!empty($orderItems)) {
|
|
|
|
|
|
$goodsName = $orderItems[0]['goods_title'] ?? '';
|
|
|
|
|
|
// 如果有多个商品,可以拼接
|
|
|
|
|
|
if (count($orderItems) > 1) {
|
|
|
|
|
|
$goodsName .= ' 等' . count($orderItems) . '件商品';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建优惠说明(如果有优惠金额)
|
|
|
|
|
|
$discountAmtDesc = '';
|
|
|
|
|
|
if ($discountAmount > 0) {
|
|
|
|
|
|
// 格式:名称=金额|@|名称=金额
|
|
|
|
|
|
// 这里简化处理,实际应该根据具体优惠券信息构建
|
|
|
|
|
|
$discountAmtDesc = '平台优惠=' . $discountAmount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 09:51:19 +08:00
|
|
|
|
// 构建符合A3341TP01接口规范的订单数据
|
2025-10-21 11:11:59 +08:00
|
|
|
|
$orderData = [
|
2025-10-21 09:51:19 +08:00
|
|
|
|
// ========== 必填字段 ==========
|
|
|
|
|
|
'USER_ID' => $ccbUserId, // 客户编号
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'ORDER_ID' => $order['order_sn'], // 订单号
|
2025-10-21 09:51:19 +08:00
|
|
|
|
'ORDER_DT' => $orderDt, // 订单日期(yyyyMMddHHmmss)
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'TOTAL_AMT' => $totalAmount, // 订单原金额
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'ORDER_STATUS' => $this->mapOrderStatus($order['status']), // 订单状态
|
2025-10-21 09:51:19 +08:00
|
|
|
|
'REFUND_STATUS' => $this->mapRefundStatus($order['refund_status'] ?? 0), // 退款状态
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'MCT_NM' => $this->config['merchant']['name'] ?? '商户名称', // 商户名称
|
2025-10-21 14:33:20 +08:00
|
|
|
|
'PAY_FLOW_ID' => $payFlowId, // ✅ 支付流水号(使用控制器传入的统一流水号)
|
2025-10-21 11:11:59 +08:00
|
|
|
|
'PAY_MRCH_ID' => $this->config['merchant_id'], // 支付商户号(必填!)
|
2025-10-21 09:51:19 +08:00
|
|
|
|
'SKU_LIST' => $skuList, // 商品信息JSON字符串(必填!)
|
2025-10-21 11:11:59 +08:00
|
|
|
|
|
|
|
|
|
|
// ========== 重要可选字段(强烈建议填写) ==========
|
|
|
|
|
|
'PAY_AMT' => $payAmount, // 订单实际支付金额
|
|
|
|
|
|
'DISCOUNT_AMT' => $discountAmount, // 第三方平台优惠金额
|
|
|
|
|
|
'PLAT_ORDER_TYPE' => 'T0000', // 服务方订单类型(T0000-普通类型)
|
|
|
|
|
|
'PLATFORM' => '99', // 下单场景(99-建行生活APP)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
];
|
2025-10-21 11:11:59 +08:00
|
|
|
|
|
|
|
|
|
|
// ========== 条件可选字段(有值才添加) ==========
|
|
|
|
|
|
|
|
|
|
|
|
// 优惠说明
|
|
|
|
|
|
if (!empty($discountAmtDesc)) {
|
|
|
|
|
|
$orderData['DISCOUNT_AMT_DESC'] = $discountAmtDesc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 订单过期时间
|
|
|
|
|
|
if (!empty($invDt)) {
|
|
|
|
|
|
$orderData['INV_DT'] = $invDt;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 商品名称
|
|
|
|
|
|
if (!empty($goodsName)) {
|
|
|
|
|
|
$orderData['GOODS_NM'] = mb_substr($goodsName, 0, 200); // 限制长度200字符
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 累计退款金额(如果有退款)
|
|
|
|
|
|
if ($totalRefundAmount > 0) {
|
|
|
|
|
|
$orderData['TOTAL_REFUND_AMT'] = $totalRefundAmount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $orderData;
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* 构建符合建行规范的SKU商品列表(JSON字符串格式)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
*
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* 📋 建行 SKU_LIST 字段规范:
|
2025-10-17 17:18:15 +08:00
|
|
|
|
*
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* 必填字段(4个):
|
|
|
|
|
|
* - SKU_NAME: 商品名称(必填)
|
|
|
|
|
|
* - SKU_REF_PRICE: 商品参考价(必填,支持小数最多2位)
|
|
|
|
|
|
* - SKU_NUM: 商品数量(必填,支持小数最多1位)
|
|
|
|
|
|
* - SKU_SELL_PRICE: 商品售价(必填,支持小数最多2位)
|
2025-10-18 15:47:25 +08:00
|
|
|
|
*
|
2025-10-21 09:51:19 +08:00
|
|
|
|
* ⚠️ 注意:Shopro字段映射
|
|
|
|
|
|
* - goods_title → SKU_NAME(商品名称)
|
|
|
|
|
|
* - goods_original_price → SKU_REF_PRICE(商品原价作为参考价)
|
|
|
|
|
|
* - goods_num → SKU_NUM(购买数量)
|
|
|
|
|
|
* - goods_price → SKU_SELL_PRICE(商品实际售价)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array $items 订单商品项数组
|
|
|
|
|
|
* @return string JSON字符串格式的SKU列表
|
2025-10-17 17:18:15 +08:00
|
|
|
|
*/
|
2025-10-21 09:51:19 +08:00
|
|
|
|
private function buildSkuList($items)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
{
|
2025-10-21 09:51:19 +08:00
|
|
|
|
$skuList = [];
|
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
|
$skuList[] = [
|
|
|
|
|
|
'SKU_NAME' => $item['goods_title'], // 商品名称(必填)
|
|
|
|
|
|
'SKU_REF_PRICE' => number_format($item['goods_original_price'] ?? $item['goods_price'], 2, '.', ''), // 商品参考价(必填)
|
|
|
|
|
|
'SKU_NUM' => $item['goods_num'], // 商品数量(必填)
|
|
|
|
|
|
'SKU_SELL_PRICE' => number_format($item['goods_price'], 2, '.', ''), // 商品售价(必填)
|
|
|
|
|
|
];
|
2025-10-18 15:47:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 09:51:19 +08:00
|
|
|
|
// 返回JSON字符串(不转义Unicode,保持中文可读)
|
|
|
|
|
|
return json_encode($skuList, JSON_UNESCAPED_UNICODE);
|
2025-10-17 17:18:15 +08:00
|
|
|
|
}
|
2025-10-21 10:17:40 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 记录同步日志
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $orderId 订单ID
|
|
|
|
|
|
* @param string $txCode 交易代码
|
|
|
|
|
|
* @param string $txSeq 交易流水号
|
|
|
|
|
|
* @param mixed $data 数据
|
|
|
|
|
|
* @param string $type 类型:request/response/error
|
|
|
|
|
|
* @param bool $success 是否成功
|
|
|
|
|
|
* @param float $costTime 耗时(毫秒)
|
|
|
|
|
|
* @param string $errorMsg 错误信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function recordSyncLog($orderId, $txCode, $txSeq, $data, $type = 'request', $success = true, $costTime = 0, $errorMsg = '')
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取订单号
|
|
|
|
|
|
$orderSn = Db::name('shopro_order')->where('id', $orderId)->value('order_sn');
|
|
|
|
|
|
|
|
|
|
|
|
$logData = [
|
|
|
|
|
|
'order_id' => $orderId,
|
|
|
|
|
|
'order_sn' => $orderSn ?: '',
|
|
|
|
|
|
'tx_code' => $txCode,
|
|
|
|
|
|
'tx_seq' => $txSeq,
|
|
|
|
|
|
'sync_status' => $success ? 1 : 0,
|
|
|
|
|
|
'sync_time' => time(),
|
|
|
|
|
|
'cost_time' => intval($costTime),
|
|
|
|
|
|
'retry_times' => 0
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if ($type == 'request') {
|
|
|
|
|
|
$logData['request_data'] = is_array($data) ? json_encode($data, JSON_UNESCAPED_UNICODE) : $data;
|
|
|
|
|
|
} elseif ($type == 'response') {
|
|
|
|
|
|
$logData['response_data'] = is_array($data) ? json_encode($data, JSON_UNESCAPED_UNICODE) : $data;
|
|
|
|
|
|
} elseif ($type == 'error') {
|
|
|
|
|
|
$logData['error_msg'] = $errorMsg;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Db::name('ccb_sync_log')->insert($logData);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('记录同步日志失败: ' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新订单同步状态
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param int $orderId 订单ID
|
|
|
|
|
|
* @param int $status 同步状态:0-未同步 1-已同步 2-同步失败
|
2025-10-22 21:06:35 +08:00
|
|
|
|
* @param string $errorMsg 错误信息(失败时填写,成功时传空字符串清空)
|
2025-10-17 17:18:15 +08:00
|
|
|
|
*/
|
2025-10-22 21:06:35 +08:00
|
|
|
|
private function updateOrderSyncStatus($orderId, $status, $errorMsg = '')
|
2025-10-17 17:18:15 +08:00
|
|
|
|
{
|
2025-10-22 21:06:35 +08:00
|
|
|
|
$updateData = [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'ccb_sync_status' => $status,
|
|
|
|
|
|
'ccb_sync_time' => time(),
|
|
|
|
|
|
'updatetime' => time()
|
2025-10-22 21:06:35 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 根据状态处理错误信息
|
|
|
|
|
|
if ($status == 1) {
|
|
|
|
|
|
// 同步成功,清空错误信息
|
|
|
|
|
|
$updateData['ccb_sync_error'] = '';
|
|
|
|
|
|
} elseif ($status == 2 && !empty($errorMsg)) {
|
|
|
|
|
|
// 同步失败,保存错误信息(限制长度255字符)
|
|
|
|
|
|
$updateData['ccb_sync_error'] = mb_substr($errorMsg, 0, 255, 'UTF-8');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Db::name('shopro_order')->where('id', $orderId)->update($updateData);
|
2025-10-17 17:18:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 映射订单状态
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
|
|
|
|
|
* @param string $status Shopro订单状态
|
|
|
|
|
|
* @return string 建行订单状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function mapOrderStatus($status)
|
|
|
|
|
|
{
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$statusMap = [
|
|
|
|
|
|
'unpaid' => '0', // 待支付
|
|
|
|
|
|
'paid' => '1', // 已支付
|
|
|
|
|
|
'shipped' => '2', // 已发货
|
|
|
|
|
|
'received' => '3', // 已收货
|
|
|
|
|
|
'completed' => '4', // 已完成
|
|
|
|
|
|
'cancelled' => '5', // 已取消
|
|
|
|
|
|
'refunded' => '6' // 已退款
|
2025-10-17 16:32:16 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
return $statusMap[$status] ?? '0';
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 映射退款状态
|
|
|
|
|
|
*
|
2025-10-17 17:18:15 +08:00
|
|
|
|
* @param int $refundStatus 退款状态
|
|
|
|
|
|
* @return string
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function mapRefundStatus($refundStatus)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ($refundStatus == 0) return '0'; // 无退款
|
|
|
|
|
|
if ($refundStatus == 1) return '1'; // 退款中
|
|
|
|
|
|
if ($refundStatus == 2) return '2'; // 已退款
|
|
|
|
|
|
return '0';
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|