以為用戶付款後**必須跳回商戶頁面(`return_url`)** 才能更新訂單狀態。若用戶關閉頁面、網絡斷開、跳轉失敗,訂單就會卡在「未處理」狀態。
| 通道 | 參數 | 觸發時機 | 用途 | 可靠性 |
|---|---|---|---|---|
| 同步跳轉 | return_url | 用戶付款成功後,支付寶頁面自動跳轉 | 頁面提示「支付成功」、引導下載、跳轉會員中心 | 低(用戶可關頁、攔截、網絡失敗) |
| 異步通知 | notify_url | 支付寶服務器主動 POST 到商戶後台 | **核心業務邏輯**:驗簽、更新訂單狀態、發貨、發券、記賬 | 高(支付寶重試機制、服務器對服務器) |
WAIT_BUYER_PAY,保存 out_trade_no、notify_url、return_url。notify_url,參數含 trade_status=TRADE_SUCCESS、out_trade_no、trade_no、sign 等。trade_status**、冪等處理**(防重複通知)、**更新訂單狀態**、**執行下游業務**。success 純文本,支付寶收到才停止重試。return_url,前端讀取參數展示成功頁,**不做狀態更新**,僅引導用戶。alipay.trade.query 查詢訂單狀態並同步。// notify_url 處理器
public void notify(HttpServletRequest request, HttpServletResponse response) {
Map params = buildParams(request); // 獲取所有參數
String sign = params.remove("sign");
String signType = params.get("sign_type");
// 1. 驗簽
if (!AlipaySignature.rsaCheckV1(params, alipayPublicKey, charset, signType)) {
log.warn("簽名驗證失敗");
return;
}
// 2. 業務校驗
String outTradeNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus)) {
return; // 僅處理成功狀態
}
// 3. 冪等:檢查訂單當前狀態
Order order = orderService.getByOutTradeNo(outTradeNo);
if (order == null || order.getStatus() == OrderStatus.PAID) {
printSuccess(response); // 已處理,直接成功
return;
}
// 4. 更新狀態 & 業務
orderService.paySuccess(order, params.get("trade_no"), params.get("gmt_payment"));
// 5. 回應支付寶
printSuccess(response);
}
private void printSuccess(HttpServletResponse response) {
response.setContentType("text/plain");
response.getWriter().write("success");
}
notify_url **必須是公網可訪問的 HTTP/HTTPS URL**,不能是 localhost、內網 IP、帶參數的動態頁(建議用靜態路徑 /api/alipay/notify)。out_trade_no 可能收到多次通知(重試、並發),必須用訂單狀態或分布式鎖防重。trade_status 判斷。記住:**`notify_url` 才是訂單狀態的唯一真相來源**,`return_url` 只是給用戶看的體驗優化。