mirror of
https://gitee.com/liuxioabin/fengketrade.git
synced 2026-04-19 13:27:31 +08:00
架构修复
This commit is contained in:
parent
4b729e6e21
commit
639732e6d1
@ -27,7 +27,7 @@ class Ccbpayment extends Common
|
|||||||
* 不需要登录的方法 (支付回调不需要登录)
|
* 不需要登录的方法 (支付回调不需要登录)
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $noNeedLogin = ['callback', 'notify'];
|
protected $noNeedLogin = ['notify'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不需要权限的方法
|
* 不需要权限的方法
|
||||||
@ -98,7 +98,23 @@ class Ccbpayment extends Common
|
|||||||
$this->error('支付串生成失败: ' . $result['message']);
|
$this->error('支付串生成失败: ' . $result['message']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 直接返回支付串(不再重复保存数据库!)
|
// 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. 返回支付串
|
||||||
$this->success('支付串生成成功', $result['data']);
|
$this->success('支付串生成成功', $result['data']);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@ -163,22 +179,6 @@ class Ccbpayment extends Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ⚠️ 已废弃: 支付回调 (前端调用)
|
|
||||||
*
|
|
||||||
* @deprecated 2025-01-20 该方法存在严重安全漏洞,已被queryPaymentStatus()替代
|
|
||||||
* @see queryPaymentStatus()
|
|
||||||
*/
|
|
||||||
public function callback()
|
|
||||||
{
|
|
||||||
// 向后兼容:直接调用查询接口
|
|
||||||
Log::warning('[建行支付] callback()已废弃,请前端改用queryPaymentStatus()接口');
|
|
||||||
|
|
||||||
// 将POST的order_id转为GET参数
|
|
||||||
$_GET['order_id'] = $this->request->post('order_id', 0);
|
|
||||||
|
|
||||||
return $this->queryPaymentStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 建行支付通知 (建行服务器回调)
|
* 建行支付通知 (建行服务器回调)
|
||||||
@ -224,19 +224,26 @@ class Ccbpayment extends Common
|
|||||||
// 6. 调用支付服务处理通知(返回订单ID)
|
// 6. 调用支付服务处理通知(返回订单ID)
|
||||||
$result = $this->paymentService->handleNotify($params);
|
$result = $this->paymentService->handleNotify($params);
|
||||||
|
|
||||||
// 7. 处理成功后推送订单到建行外联系统
|
// 7. ✅ 处理成功后更新订单状态到建行(步骤13:调用订单更新接口更新订单状态)
|
||||||
if ($result['status'] === 'success' && !empty($result['order_id'])) {
|
if ($result['status'] === 'success' && !empty($result['order_id'])) {
|
||||||
// ⚠️ 只有新支付才推送,已支付的订单跳过推送
|
// ⚠️ 只有新支付才更新,已支付的订单跳过更新
|
||||||
if ($result['already_paid'] === false) {
|
if ($result['already_paid'] === false) {
|
||||||
try {
|
try {
|
||||||
$this->pushOrderToCcb($result['order_id']);
|
// 调用订单更新接口,将订单状态从未支付更新为已支付
|
||||||
Log::info('[建行通知] 订单推送成功 order_id:' . $result['order_id']);
|
$updateResult = $this->orderService->updateOrderStatus($result['order_id']);
|
||||||
|
|
||||||
|
if ($updateResult['status']) {
|
||||||
|
Log::info('[建行通知] 订单状态更新成功 order_id:' . $result['order_id']);
|
||||||
|
} else {
|
||||||
|
// ⚠️ 更新失败不影响本地支付状态,记录日志后续补推
|
||||||
|
Log::warning('[建行通知] 订单状态更新失败(本地已支付) order_id:' . $result['order_id'] . ' error:' . $updateResult['message']);
|
||||||
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// ⚠️ 推送失败不影响支付成功,记录日志后续补推
|
// ⚠️ 更新异常不影响支付成功,记录日志后续补推
|
||||||
Log::error('[建行通知] 订单推送失败 order_id:' . $result['order_id'] . ' error:' . $e->getMessage());
|
Log::error('[建行通知] 订单状态更新异常(本地已支付) order_id:' . $result['order_id'] . ' error:' . $e->getMessage());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log::info('[建行通知] 订单已支付且已推送,跳过推送 order_id:' . $result['order_id']);
|
Log::info('[建行通知] 订单已支付且已更新,跳过更新 order_id:' . $result['order_id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,104 +265,15 @@ class Ccbpayment extends Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推送订单到建行外联系统
|
* ⚠️ 已废弃: 推送订单到建行外联系统
|
||||||
*
|
*
|
||||||
* ⚠️ 重要: 只在notify()支付成功后调用!
|
* @deprecated 2025-01-20 根据建行流程图,订单推送应在createPayment()时完成,此方法已废弃
|
||||||
* ✅ 幂等性: 支持重复调用,已推送的订单会跳过
|
* @see createPayment() 步骤5: 推送未支付订单
|
||||||
*
|
* @see notify() 步骤7: 更新订单为已支付
|
||||||
* @param int $orderId 订单ID
|
|
||||||
* @return void
|
|
||||||
* @throws Exception 推送失败时抛出异常
|
|
||||||
*/
|
*/
|
||||||
private function pushOrderToCcb($orderId)
|
private function pushOrderToCcb($orderId)
|
||||||
{
|
{
|
||||||
try {
|
Log::warning('[建行支付] pushOrderToCcb()已废弃,请使用orderService->pushOrder()或updateOrderStatus()');
|
||||||
// ✅ 重新查询订单,确保状态已更新
|
|
||||||
$order = OrderModel::find($orderId);
|
|
||||||
if (!$order) {
|
|
||||||
throw new Exception('订单不存在');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 验证订单状态
|
|
||||||
if ($order->status !== 'paid') {
|
|
||||||
throw new Exception('订单状态不正确(status=' . $order->status . '),无法推送');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 幂等性检查: 如果已推送成功,跳过
|
|
||||||
if ($order->ccb_sync_status == 1) {
|
|
||||||
Log::info('[建行推送] 订单已推送,跳过 order_id:' . $orderId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取订单商品列表
|
|
||||||
$orderItems = Db::name('shopro_order_item')
|
|
||||||
->where('order_id', $order->id)
|
|
||||||
->field('goods_id, goods_sku_text, goods_title, goods_price, goods_num, discount_fee')
|
|
||||||
->select();
|
|
||||||
|
|
||||||
$goodsList = [];
|
|
||||||
foreach ($orderItems as $item) {
|
|
||||||
$goodsList[] = [
|
|
||||||
'goods_id' => $item['goods_id'],
|
|
||||||
'goods_name' => $item['goods_title'],
|
|
||||||
'goods_sku' => $item['goods_sku_text'],
|
|
||||||
'goods_price' => $item['goods_price'],
|
|
||||||
'goods_num' => $item['goods_num'],
|
|
||||||
'discount_amount' => $item['discount_fee'] ?? 0
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户的建行用户ID
|
|
||||||
$user = Db::name('user')->where('id', $order->user_id)->field('ccb_user_id')->find();
|
|
||||||
|
|
||||||
// 构造订单数据 (使用Shopro实际字段名)
|
|
||||||
$orderData = [
|
|
||||||
'id' => $order->id,
|
|
||||||
'order_sn' => $order->order_sn,
|
|
||||||
'ccb_user_id' => $user['ccb_user_id'] ?? '',
|
|
||||||
'total_amount' => $order->total_amount, // 订单总金额
|
|
||||||
'pay_amount' => $order->total_fee, // 实际支付金额
|
|
||||||
'discount_amount' => $order->discount_fee, // 优惠金额
|
|
||||||
'status' => $order->status, // Shopro使用status枚举
|
|
||||||
'refund_status' => $order->aftersale_status ?? 0, // 售后状态
|
|
||||||
'create_time' => $order->createtime, // Shopro使用秒级时间戳
|
|
||||||
'paid_time' => $order->paid_time, // 支付时间
|
|
||||||
'ccb_pay_flow_id' => $order->ccb_pay_flow_id,
|
|
||||||
'goods_list' => $goodsList,
|
|
||||||
];
|
|
||||||
|
|
||||||
// 推送到建行
|
|
||||||
$result = $this->orderService->pushOrder($order->id);
|
|
||||||
|
|
||||||
if (!$result['status']) {
|
|
||||||
// ✅ 推送失败: 更新状态为失败,记录错误原因
|
|
||||||
OrderModel::where('id', $orderId)->update([
|
|
||||||
'ccb_sync_status' => 2, // 2=失败
|
|
||||||
'ccb_sync_error' => $result['message'] ?? '未知错误',
|
|
||||||
'ccb_sync_time' => time(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw new Exception($result['message'] ?? '推送失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 推送成功: 更新同步状态
|
|
||||||
OrderModel::where('id', $orderId)->update([
|
|
||||||
'ccb_sync_status' => 1, // 1=成功
|
|
||||||
'ccb_sync_time' => time(),
|
|
||||||
'ccb_sync_error' => '', // 清空错误信息
|
|
||||||
]);
|
|
||||||
|
|
||||||
Log::info('[建行推送] 订单推送成功 order_id:' . $order->id . ' order_sn:' . $order->order_sn);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// ✅ 记录失败原因,供后续补推
|
|
||||||
OrderModel::where('id', $orderId)->update([
|
|
||||||
'ccb_sync_status' => 2, // 失败状态
|
|
||||||
'ccb_sync_error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw $e; // 向上抛出异常
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
**项目**: Shopro商城建行支付集成
|
**项目**: Shopro商城建行支付集成
|
||||||
**修复时间**: 2025-01-20
|
**修复时间**: 2025-01-20
|
||||||
**文档版本**: v1.0
|
**文档版本**: v2.0 (根据官方流程图修正)
|
||||||
**修复类型**: 🔴 严重安全漏洞 + 架构偏离
|
**修复类型**: 🔴 严重安全漏洞 + 架构偏离 + 订单同步时机错误
|
||||||
**建行接口版本**: v2.20 (2025-07-25)
|
**建行接口版本**: v2.20 (2025-07-25)
|
||||||
|
**参考文档**: 建行生活服务方接入流程图
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -17,14 +18,15 @@
|
|||||||
1. **前端callback机制违反建行标准流程** 🔴 致命
|
1. **前端callback机制违反建行标准流程** 🔴 致命
|
||||||
2. **前端可伪造支付成功请求** 🔴 安全漏洞
|
2. **前端可伪造支付成功请求** 🔴 安全漏洞
|
||||||
3. **双通道更新订单状态导致竞态条件** 🔴 致命
|
3. **双通道更新订单状态导致竞态条件** 🔴 致命
|
||||||
4. **订单推送时机错误** 🟡 严重
|
4. **订单推送时机错误** 🔴 致命 - 应该在创建订单时推送,而不是支付成功后
|
||||||
5. **缺少轮询查询机制** 🟡 严重
|
5. **订单更新时机错误** 🔴 致命 - 支付成功后应调用更新接口,而不是推送接口
|
||||||
|
6. **缺少轮询查询机制** 🟡 严重
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔴 建行标准支付流程 vs 错误实现
|
## 🔴 建行标准支付流程 vs 错误实现
|
||||||
|
|
||||||
### 建行标准流程(官方文档)
|
### 建行标准流程(官方文档 - 根据流程图修正)
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
@ -32,25 +34,39 @@ sequenceDiagram
|
|||||||
participant Backend as 服务方后台
|
participant Backend as 服务方后台
|
||||||
participant CCBApp as 建行APP
|
participant CCBApp as 建行APP
|
||||||
participant CCBBackend as 建行后端
|
participant CCBBackend as 建行后端
|
||||||
|
participant Merchant as 商户管理(外联)
|
||||||
|
|
||||||
Note over H5,Backend: 步骤1: 生成支付串
|
Note over H5,Backend: 步骤1: 请求下单
|
||||||
H5->>Backend: POST /createPayment
|
H5->>Backend: POST /createPayment(生成支付串)
|
||||||
|
Backend->>Backend: 保存订单(未支付状态)
|
||||||
|
|
||||||
|
Note over Backend,CCBBackend: 步骤2: 推送订单 ✅ 推送未支付订单!
|
||||||
|
Backend->>Merchant: 调用订单推送接口(A3341TP01)
|
||||||
|
Merchant-->>Backend: 返回推送结果
|
||||||
Backend-->>H5: {payment_string}
|
Backend-->>H5: {payment_string}
|
||||||
|
|
||||||
Note over H5,CCBApp: 步骤2: 调起建行收银台
|
Note over H5,CCBApp: 步骤3: 调起建行收银台
|
||||||
H5->>CCBApp: JSBridge.ccbpay(支付串)
|
H5->>CCBApp: JSBridge.ccbpay(支付串)
|
||||||
activate CCBBackend
|
activate CCBBackend
|
||||||
|
CCBApp->>CCBBackend: 校验登录
|
||||||
|
CCBApp->>CCBApp: 调用支付组件
|
||||||
|
H5->>CCBApp: 确认支付、输入密码
|
||||||
|
CCBApp->>CCBBackend: 发送支付请求
|
||||||
Note right of CCBBackend: 用户在建行APP中完成支付
|
Note right of CCBBackend: 用户在建行APP中完成支付
|
||||||
|
|
||||||
Note over CCBBackend,Backend: 步骤3: 建行异步通知 ✅ 唯一可信的支付确认!
|
Note over CCBBackend,Backend: 步骤10-12: 建行异步通知
|
||||||
CCBBackend->>Backend: POST notify(ORDERID, SIGN等)
|
CCBBackend->>Merchant: 返回支付成功通知
|
||||||
|
Merchant->>Backend: 推送服务器通知(notify)
|
||||||
Backend->>Backend: 验证SIGN签名
|
Backend->>Backend: 验证SIGN签名
|
||||||
Backend->>Backend: 原子更新订单状态为paid
|
Backend->>Backend: 原子更新本地订单状态为paid
|
||||||
Backend->>Backend: 推送订单到外联系统
|
|
||||||
Backend-->>CCBBackend: 返回SUCCESS
|
|
||||||
deactivate CCBBackend
|
deactivate CCBBackend
|
||||||
|
|
||||||
Note over H5,Backend: 步骤4: 前端轮询查询状态 ✅ 只查询,不更新!
|
Note over Backend,Merchant: 步骤13: 更新订单状态 ✅ 更新为已支付!
|
||||||
|
Backend->>Merchant: 调用订单更新接口(A3341TP02)
|
||||||
|
Merchant-->>Backend: 返回更新结果
|
||||||
|
Backend-->>Merchant: 返回SUCCESS
|
||||||
|
|
||||||
|
Note over H5,Backend: 步骤15-16: 前端轮询查询状态 (未收到通知时)
|
||||||
loop 每2秒轮询(最多60秒)
|
loop 每2秒轮询(最多60秒)
|
||||||
H5->>Backend: GET /queryPaymentStatus
|
H5->>Backend: GET /queryPaymentStatus
|
||||||
Backend-->>H5: {status: 'paid'或'unpaid'}
|
Backend-->>H5: {status: 'paid'或'unpaid'}
|
||||||
@ -60,6 +76,11 @@ sequenceDiagram
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**关键流程说明**:
|
||||||
|
1. **步骤2**: 生成支付串后**立即推送未支付订单**到建行外联系统(A3341TP01)
|
||||||
|
2. **步骤13**: 收到支付成功通知后**更新订单状态为已支付**(A3341TP02)
|
||||||
|
3. **步骤15**: 前端轮询查询订单状态(用于未收到通知的降级方案)
|
||||||
|
|
||||||
### 修复前的错误实现
|
### 修复前的错误实现
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@ -327,7 +348,76 @@ public function notify()
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 修复3: handleNotify()返回订单ID
|
### 修复3: createPayment()推送未支付订单
|
||||||
|
|
||||||
|
**修复后** (`addons/shopro/controller/Ccbpayment.php:101-118`):
|
||||||
|
|
||||||
|
```php
|
||||||
|
// 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. 返回支付串
|
||||||
|
$this->success('支付串生成成功', $result['data']);
|
||||||
|
```
|
||||||
|
|
||||||
|
**修复要点**:
|
||||||
|
- ✅ 生成支付串后立即调用`pushOrder()`推送未支付订单(A3341TP01)
|
||||||
|
- ✅ 推送失败不阻塞支付流程,用户仍可继续支付
|
||||||
|
- ✅ 记录推送结果日志,失败的可后续补推
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 修复4: notify()更新订单状态为已支付
|
||||||
|
|
||||||
|
**修复后** (`addons/shopro/controller/Ccbpayment.php:227-248`):
|
||||||
|
|
||||||
|
```php
|
||||||
|
// 7. ✅ 处理成功后更新订单状态到建行(步骤13:调用订单更新接口更新订单状态)
|
||||||
|
if ($result['status'] === 'success' && !empty($result['order_id'])) {
|
||||||
|
// ⚠️ 只有新支付才更新,已支付的订单跳过更新
|
||||||
|
if ($result['already_paid'] === false) {
|
||||||
|
try {
|
||||||
|
// 调用订单更新接口,将订单状态从未支付更新为已支付
|
||||||
|
$updateResult = $this->orderService->updateOrderStatus($result['order_id']);
|
||||||
|
|
||||||
|
if ($updateResult['status']) {
|
||||||
|
Log::info('[建行通知] 订单状态更新成功 order_id:' . $result['order_id']);
|
||||||
|
} else {
|
||||||
|
// ⚠️ 更新失败不影响本地支付状态,记录日志后续补推
|
||||||
|
Log::warning('[建行通知] 订单状态更新失败(本地已支付) order_id:' . $result['order_id'] . ' error:' . $updateResult['message']);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// ⚠️ 更新异常不影响支付成功,记录日志后续补推
|
||||||
|
Log::error('[建行通知] 订单状态更新异常(本地已支付) order_id:' . $result['order_id'] . ' error:' . $e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log::info('[建行通知] 订单已支付且已更新,跳过更新 order_id:' . $result['order_id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修复要点**:
|
||||||
|
- ✅ 收到支付成功通知后调用`updateOrderStatus()`(A3341TP02)
|
||||||
|
- ✅ 将订单状态从"未支付"更新为"已支付"
|
||||||
|
- ✅ 更新失败不影响本地支付状态(本地订单已标记为paid)
|
||||||
|
- ✅ 幂等性保护(已支付的订单跳过更新)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 修复5: handleNotify()返回订单ID
|
||||||
|
|
||||||
**修复后** (`addons/shopro/library/ccblife/CcbPaymentService.php:349-403`):
|
**修复后** (`addons/shopro/library/ccblife/CcbPaymentService.php:349-403`):
|
||||||
|
|
||||||
@ -404,84 +494,21 @@ public function handleNotify($params)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 修复4: pushOrderToCcb()增加幂等性检查
|
### ~~修复6: pushOrderToCcb()增加幂等性检查~~
|
||||||
|
|
||||||
**修复后** (`addons/shopro/controller/Ccbpayment.php:270-359`):
|
**⚠️ 已废弃**: 根据建行流程图,`pushOrderToCcb()`方法已废弃。
|
||||||
|
|
||||||
```php
|
**正确流程**:
|
||||||
/**
|
1. `createPayment()` → 调用`orderService->pushOrder()` → 推送未支付订单(A3341TP01)
|
||||||
* 推送订单到建行外联系统
|
2. `notify()` → 调用`orderService->updateOrderStatus()` → 更新为已支付(A3341TP02)
|
||||||
*
|
|
||||||
* ⚠️ 重要: 只在notify()支付成功后调用!
|
|
||||||
* ✅ 幂等性: 支持重复调用,已推送的订单会跳过
|
|
||||||
*
|
|
||||||
* @param int $orderId 订单ID
|
|
||||||
* @throws Exception 推送失败时抛出异常
|
|
||||||
*/
|
|
||||||
private function pushOrderToCcb($orderId)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// ✅ 重新查询订单,确保状态已更新
|
|
||||||
$order = OrderModel::find($orderId);
|
|
||||||
if (!$order) {
|
|
||||||
throw new Exception('订单不存在');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 验证订单状态
|
|
||||||
if ($order->status !== 'paid') {
|
|
||||||
throw new Exception('订单状态不正确,无法推送');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 幂等性检查: 如果已推送成功,跳过
|
|
||||||
if ($order->ccb_sync_status == 1) {
|
|
||||||
Log::info('[建行推送] 订单已推送,跳过 order_id:' . $orderId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 推送到建行
|
|
||||||
$result = $this->orderService->pushOrder($order->id);
|
|
||||||
|
|
||||||
if (!$result['status']) {
|
|
||||||
// ✅ 推送失败: 更新状态为失败,记录错误原因
|
|
||||||
OrderModel::where('id', $orderId)->update([
|
|
||||||
'ccb_sync_status' => 2, // 2=失败
|
|
||||||
'ccb_sync_error' => $result['message'] ?? '未知错误',
|
|
||||||
'ccb_sync_time' => time(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw new Exception($result['message'] ?? '推送失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 推送成功: 更新同步状态
|
|
||||||
OrderModel::where('id', $orderId)->update([
|
|
||||||
'ccb_sync_status' => 1, // 1=成功
|
|
||||||
'ccb_sync_time' => time(),
|
|
||||||
'ccb_sync_error' => '',
|
|
||||||
]);
|
|
||||||
|
|
||||||
Log::info('[建行推送] 订单推送成功 order_id:' . $orderId);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// ✅ 记录失败原因,供后续补推
|
|
||||||
OrderModel::where('id', $orderId)->update([
|
|
||||||
'ccb_sync_status' => 2,
|
|
||||||
'ccb_sync_error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**修复要点**:
|
|
||||||
- ✅ 幂等性检查(ccb_sync_status == 1时跳过)
|
|
||||||
- ✅ 状态验证(只推送已支付的订单)
|
|
||||||
- ✅ 失败原因记录(ccb_sync_error字段)
|
|
||||||
- ✅ 异常向上抛出(但不影响notify返回SUCCESS)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 修复5: 前端改为轮询查询
|
**⚠️ 已废弃**: 根据建行流程图,订单推送和更新的幂等性由`CcbOrderService`内部保证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 修复6: 前端改为轮询查询
|
||||||
|
|
||||||
**修复后** (`frontend/sheep/platform/pay.js:325-386`):
|
**修复后** (`frontend/sheep/platform/pay.js:325-386`):
|
||||||
|
|
||||||
@ -576,11 +603,13 @@ async ccbPay() {
|
|||||||
| **支付确认来源** | 前端callback + 建行notify (双通道) ❌ | 只依赖建行notify (单通道) ✅ |
|
| **支付确认来源** | 前端callback + 建行notify (双通道) ❌ | 只依赖建行notify (单通道) ✅ |
|
||||||
| **前端职责** | 调用callback通知后端支付成功 ❌ | 轮询查询订单状态 ✅ |
|
| **前端职责** | 调用callback通知后端支付成功 ❌ | 轮询查询订单状态 ✅ |
|
||||||
| **安全性** | 可伪造前端请求触发支付成功 🔴 | 只信任建行签名验证 ✅ |
|
| **安全性** | 可伪造前端请求触发支付成功 🔴 | 只信任建行签名验证 ✅ |
|
||||||
| **订单推送时机** | callback()中推送 ❌ | notify()成功后推送 ✅ |
|
| **订单推送时机** | 支付成功后推送 ❌ | **创建订单时推送未支付状态** ✅ |
|
||||||
|
| **订单更新时机** | 未更新到建行 ❌ | **支付成功后更新为已支付** ✅ |
|
||||||
| **竞态风险** | callback和notify可能同时执行 🔴 | 只有notify会更新订单 ✅ |
|
| **竞态风险** | callback和notify可能同时执行 🔴 | 只有notify会更新订单 ✅ |
|
||||||
| **幂等性** | 无幂等保护 ❌ | 支持重复调用,已推送的跳过 ✅ |
|
| **幂等性** | 无幂等保护 ❌ | 支持重复调用,已推送/已更新的跳过 ✅ |
|
||||||
| **符合建行规范** | 否 ❌ | 是 ✅ |
|
| **符合建行规范** | 否 ❌ | **完全符合流程图** ✅ |
|
||||||
| **订单状态一致性** | 可能重复更新或状态异常 🔴 | 原子更新,状态一致 ✅ |
|
| **订单状态一致性** | 可能重复更新或状态异常 🔴 | 原子更新,状态一致 ✅ |
|
||||||
|
| **建行订单同步** | 不同步或错误时机同步 ❌ | **按流程图正确同步** ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -652,25 +681,24 @@ curl -X POST "http://your-domain/addons/shopro/ccbpayment/notify" \
|
|||||||
### 4. 日志验证
|
### 4. 日志验证
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 查看创建支付串日志
|
||||||
|
tail -f runtime/log/$(date +%Y%m)/*.log | grep '建行支付'
|
||||||
|
|
||||||
|
# ✅ 正常流程应该看到:
|
||||||
|
# [建行支付] 订单推送成功 order_id:123 ← 步骤2: 推送未支付订单
|
||||||
|
|
||||||
# 查看notify日志
|
# 查看notify日志
|
||||||
tail -f runtime/log/$(date +%Y%m)/*.log | grep '建行通知'
|
tail -f runtime/log/$(date +%Y%m)/*.log | grep '建行通知'
|
||||||
|
|
||||||
# ✅ 正常流程应该看到:
|
# ✅ 正常流程应该看到:
|
||||||
# [建行通知] 收到异步通知: ORDERID=...
|
# [建行通知] 收到异步通知: ORDERID=...
|
||||||
# [建行通知] 解析参数: {...}
|
# [建行通知] 解析参数: {...}
|
||||||
# [建行通知] 订单状态更新成功 order_id:123
|
# [建行通知] 订单状态更新成功 order_id:123 ← 步骤13: 更新为已支付
|
||||||
# [建行通知] 订单推送成功 order_id:123
|
|
||||||
# [建行通知] 处理完成,返回: SUCCESS
|
# [建行通知] 处理完成,返回: SUCCESS
|
||||||
|
|
||||||
# 查看推送日志
|
|
||||||
tail -f runtime/log/$(date +%Y%m)/*.log | grep '建行推送'
|
|
||||||
|
|
||||||
# ✅ 正常流程应该看到:
|
|
||||||
# [建行推送] 订单推送成功 order_id:123 order_sn:202501200001
|
|
||||||
|
|
||||||
# 查看幂等性日志(重复通知时)
|
# 查看幂等性日志(重复通知时)
|
||||||
# [建行通知] 订单已支付,跳过处理 order_id:123
|
# [建行通知] 订单已支付,跳过处理 order_id:123
|
||||||
# [建行推送] 订单已推送,跳过 order_id:123
|
# [建行通知] 订单已支付且已更新,跳过更新 order_id:123
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -790,6 +818,7 @@ php think clear
|
|||||||
| 版本 | 日期 | 修改内容 |
|
| 版本 | 日期 | 修改内容 |
|
||||||
|-----|------|---------|
|
|-----|------|---------|
|
||||||
| v1.0 | 2025-01-20 | 初始版本,完成严重安全漏洞和架构偏离修复 |
|
| v1.0 | 2025-01-20 | 初始版本,完成严重安全漏洞和架构偏离修复 |
|
||||||
|
| v2.0 | 2025-01-20 | **重大修正**: 根据建行流程图修正订单同步时机 - 推送未支付订单在createPayment,更新已支付订单在notify |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user