PHP项目订单支付状态如何同步更新

wen PHP项目 28

本文目录导读:

PHP项目订单支付状态如何同步更新

  1. 核心实现方案
  2. 关键注意事项
  3. 推荐方案组合
  4. 常见问题解决

在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();

推荐方案组合

  1. 主方案:支付回调 + 幂等性处理
  2. 补充方案:主动查询(定时任务)
  3. 增强方案:WebSocket推送实时状态

这样的组合可以确保:

  • 回调正常时:几乎实时更新
  • 回调丢失时:主动查询补充
  • 用户端:实时看到状态变化

常见问题解决

  1. 回调延迟:使用队列处理回调,避免阻塞
  2. 订单状态不一致:记录日志,定期对账
  3. 并发问题:使用数据库锁或乐观锁
  4. 测试困难:模拟支付回调,提供测试工具

这样设计基本能覆盖各种场景,确保订单支付状态准确同步。

抱歉,评论功能暂时关闭!