本文目录导读:

在PHP项目中同步更新订单支付状态,通常涉及以下几个关键环节,我会从最佳实践和常见方案的角度给你详细讲解。
核心实现方案
支付回调处理(最常用方案)
支付平台(如支付宝、微信支付)会异步通知你的服务器支付结果:
// 支付回调处理接口
public function handlePaymentCallback() {
// 1. 验证签名(防止伪造回调)
$signVerified = $this->verifySign($_POST);
if (!$signVerified) {
return 'fail'; // 返回失败,支付平台会重试
}
// 2. 获取支付结果
$orderNo = $_POST['out_trade_no']; // 商户订单号
$tradeStatus = $_POST['trade_status'];
$transactionId = $_POST['trade_no']; // 支付平台交易号
try {
// 3. 使用数据库事务确保数据一致性
DB::beginTransaction();
// 4. 查询订单当前状态(避免重复处理)
$order = OrderModel::findByOrderNo($orderNo);
if ($order->status == OrderStatus::PAID) {
// 已支付成功,防止重复处理
DB::rollback();
return 'success';
}
if ($tradeStatus == 'TRADE_SUCCESS') {
// 5. 更新订单状态
$order->status = OrderStatus::PAID;
$order->transaction_id = $transactionId;
$order->paid_at = date('Y-m-d H:i:s');
$order->save();
// 6. 触发后续业务逻辑(如发送通知、更新库存等)
Event::dispatch(new OrderPaid($order));
DB::commit();
return 'success';
} else {
// 支付失败
$order->status = OrderStatus::PAY_FAILED;
$order->save();
DB::commit();
return 'success';
}
} catch (\Exception $e) {
DB::rollback();
// 记录日志
Log::error('Payment callback error: ' . $e->getMessage());
return 'fail'; // 返回失败让支付平台重试
}
}
主动查询方案(补充方案)
当回调不可靠时,可以定期主动查询支付状态:
// 定时任务:每分钟处理超时未支付的订单
public function syncPaymentStatus() {
// 查询超过30分钟未支付且未处理回调的订单
$orders = OrderModel::where('status', OrderStatus::PENDING)
->where('created_at', '<', now()->subMinutes(30))
->get();
foreach ($orders as $order) {
// 调用支付平台查询接口
$paymentResult = PaymentService::queryOrderStatus($order->order_no);
if ($paymentResult['trade_state'] == 'SUCCESS') {
// 更新订单为已支付
$order->status = OrderStatus::PAID;
$order->transaction_id = $paymentResult['transaction_id'];
$order->paid_at = $paymentResult['time_end'];
$order->save();
// 触发后续业务逻辑
Event::dispatch(new OrderPaid($order));
} elseif ($paymentResult['trade_state'] == 'CLOSED') {
// 订单已关闭
$order->status = OrderStatus::CLOSED;
$order->save();
}
}
}
轮询方案(前端配合)
适用于长连接或即时性要求不高的场景:
// 前端代码
function pollPaymentStatus(orderNo) {
const checkInterval = setInterval(async () => {
try {
const response = await fetch(`/api/order/status?order_no=${orderNo}`);
const data = await response.json();
if (data.status === 'paid') {
clearInterval(checkInterval);
// 支付成功,跳转或更新UI
window.location.href = '/order/success/' + orderNo;
} else if (data.status === 'failed' || data.status === 'closed') {
clearInterval(checkInterval);
// 支付失败处理
alert('支付失败,请重新下单');
}
} catch (error) {
console.error('查询支付状态失败:', error);
}
}, 3000); // 每3秒查询一次
// 超时停止轮询(比如5分钟)
setTimeout(() => {
clearInterval(checkInterval);
}, 300000);
}
// 后端接口
public function getOrderStatus(Request $request) {
$order = OrderModel::where('order_no', $request->order_no)->first();
if (!$order) {
return response()->json(['error' => '订单不存在'], 404);
}
return response()->json([
'status' => $order->status,
'paid_at' => $order->paid_at
]);
}
WebSocket方案(实时性最好)
使用 Laravel WebSocket 或 Swoole 实现:
// 使用 Laravel WebSocket
class OrderPaymentHandler {
public function onPaymentSuccess($orderNo) {
// 获取订单信息
$order = OrderModel::findByOrderNo($orderNo);
// 发送WebSocket事件
event(new OrderStatusUpdated([
'order_no' => $orderNo,
'status' => 'paid',
'paid_at' => $order->paid_at
]));
}
}
关键注意事项
幂等性处理(最重要)
- 同一个支付回调可能被发送多次
- 必须在处理前检查订单状态
- 使用唯一约束或乐观锁防止重复更新
// 使用乐观锁
$affected = DB::table('orders')
->where('order_no', $orderNo)
->where('status', OrderStatus::PENDING) // 只有待支付状态才更新
->update(['status' => OrderStatus::PAID]);
if ($affected === 0) {
// 可能已被处理,记录日志
Log::warning('Order already processed: ' . $orderNo);
}
顺序保证
- 先更新订单状态,再执行后续业务逻辑
- 失败时回滚所有操作
- 使用数据库事务确保一致性
异常处理
public function safeProcessPayment($orderNo, $transactionId) {
try {
// 处理支付
$this->processPayment($orderNo, $transactionId);
} catch (\Exception $e) {
// 记录详细日志
Log::error('Payment processing failed', [
'order_no' => $orderNo,
'transaction_id' => $transactionId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// 发送告警
AlertService::send('支付处理异常: ' . $orderNo);
}
}
超时与重试机制
// 配置队列重试
'retry_after' => 90, // 90秒后重试
'max_attempts' => 5, // 最多重试5次
// 处理超时订单
Schedule::command('orders:handle-timeout')->everyMinute();
推荐方案组合
- 主方案:支付回调 + 幂等性处理
- 补充方案:主动查询(定时任务)
- 增强方案:WebSocket推送实时状态
这样的组合可以确保:
- 回调正常时:几乎实时更新
- 回调丢失时:主动查询补充
- 用户端:实时看到状态变化
常见问题解决
- 回调延迟:使用队列处理回调,避免阻塞
- 订单状态不一致:记录日志,定期对账
- 并发问题:使用数据库锁或乐观锁
- 测试困难:模拟支付回调,提供测试工具
这样设计基本能覆盖各种场景,确保订单支付状态准确同步。