PayPal 支付状态查询(2026-06-08)
变更背景
PayPal 支付存在一个问题:即使银行 decline(拒绝扣款),PayPal 页面仍会显示"支付成功"。买家看到成功后离开,但商户实际未收到钱。
解决方案:在 PayPal 返回页面增加轮询逻辑,查询实际 capture 状态,根据结果显示不同 UI。
前端需要对接的内容
1. PayPal 回调 URL 现在携带paypal_order_id
用户从 PayPal 支付完成后,后端会重定向到前端的 callback URL。
变更前:
https://your-frontend.com/paypal/return
变更后:
https://your-frontend.com/paypal/return?paypal_order_id=2L146185MU803905Y
前端可以从 URL query params 中获取 paypal_order_id,用于调用支付状态查询接口。
2. 支付状态查询接口
POST /api/user/orders/payment_status
用途:
- 查询 PayPal 远程订单和 capture 的实际状态。
- 前端在 PayPal 返回页面轮询此接口,判断支付是否真正成功。
请求参数:
| 字段 | 类型 | 必填 | 说明 |
|---|
paypal_order_id | string | 是 | PayPal 订单 ID,从回调 URL 的 query params 中获取 |
请求示例:
{
"paypal_order_id": "2L146185MU803905Y"
}
响应示例(capture 成功):
{
"data": {
"paypal_order_id": "2L146185MU803905Y",
"order_status": "completed",
"capture_status": "completed",
"capture_id": "9TU30802256203744"
}
}
响应示例(capture 被拒):
{
"data": {
"paypal_order_id": "2L146185MU803905Y",
"order_status": "approved",
"capture_status": "declined",
"capture_id": "9TU30802256203744"
}
}
响应示例(capture 处理中):
{
"data": {
"paypal_order_id": "2L146185MU803905Y",
"order_status": "approved",
"capture_status": "pending",
"capture_id": "3A384378MG7692232"
}
}
响应示例(查询失败,返回空状态):
{
"data": {
"paypal_order_id": "2L146185MU803905Y",
"order_status": null,
"capture_status": null,
"capture_id": null
}
}
字段说明
| 字段 | 说明 |
|---|
order_status | PayPal 远程订单状态:created / approved / completed / voided 等 |
capture_status | capture 状态:completed(成功)/ declined(被拒)/ pending(处理中)/ null(无 capture 记录) |
capture_id | PayPal capture ID,可用于后续退款等操作 |
前端对接方案
PayPal 返回页面轮询逻辑
1. 页面加载,从 URL 获取 paypal_order_id
2. 显示 loading + "正在查询支付结果"
3. 调用 POST /api/user/orders/payment_status
4. 根据 capture_status 判断:
- completed → 显示 "支付完成"
- declined → 显示 "Paypal 扣款失败,请检查支付方式"
- pending → 显示 "配货正在处理中,请等待配货处理结果",5 秒后重试
- null → 显示 loading,5 秒后重试
5. 最多轮询 N 次(建议 12 次,即 1 分钟),超时提示用户稍后查看订单状态
建议的前端实现
async function pollPaymentStatus(paypalOrderId: string) {
const MAX_RETRIES = 12
const INTERVAL = 5000 // 5 秒
for (let i = 0; i < MAX_RETRIES; i++) {
const { data } = await api.post('/api/user/orders/payment_status', {
paypal_order_id: paypalOrderId,
})
if (data.capture_status === 'completed') {
return 'success'
}
if (data.capture_status === 'declined') {
return 'failed'
}
// pending 或 null,继续轮询
await sleep(INTERVAL)
}
return 'timeout'
}
注意事项
- 不要依赖 PayPal 页面的"成功"提示:PayPal 页面显示成功不代表真正扣款成功,必须通过此接口确认。
- 轮询间隔建议 5 秒:正常情况 capture 很快完成(1-2 秒),异常情况可能 pending 数天。
- capture_status 为 declined 时:引导用户检查支付方式或更换支付方式重新支付。
- capture_status 为 pending 时:这是 PayPal 正在处理中的正常状态,不要标记为失败。