如何自動取消未付款訂單,又不丟掉遲到的付款?
如何自動取消未付款訂單:給訂單設置支付截止時間、先關閉收銀台再取消訂單,以及買家在訂單取消後才付款時該怎麼處理。
大多數網店都會在超時後自動取消未付款訂單,比如 30 分鐘、2 小時或一天,用來釋放庫存、保持訂單列表乾淨。陷阱在於:計時器觸發時,買家還停留在收銀台頁面上。他一分鐘後付了款,錢扣了,而你的系統已經把訂單取消了。接下來你只能手動退款,再向買家道歉。
解決辦法很簡單:收銀台必須比你的訂單先關閉。 這篇文章説明如何在避開這個競態的前提下自動取消未付款訂單:給每筆訂單設置支付截止時間、在需要時提前關閉訂單,以及知道一筆遲到的付款最終會怎樣處理。
為什麼會出現「取消後又付款」?
你的網店和支付服務商各有一個時鐘:
- 你的訂單超時時間,到期後你的系統取消訂單、釋放庫存;
- 收銀台的有效期,在此期間買家仍然可以付款。
如果收銀台比你的訂單活得更久,就會出現一個窗口期:買家可以為一筆在你看來已經作廢的訂單付款。在 KukoPay,收銀台鏈接默認有效 24 小時,比大多數網店的未付款超時時間長得多。
給每筆訂單設置支付截止時間
創建訂單時傳入 expires_at,並且不要晚於你自己的取消時間:
curl -X POST "https://api.kukopay.com/v1/orders" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $KUKOPAY_API_KEY" \
-d '{
"out_trade_no": "ORD_20261105_0001",
"amount": 2990,
"currency": "USD",
"notify_url": "https://shop.example.com/webhooks/kukopay",
"expires_at": "2026-11-05T10:30:00Z"
}'expires_at接受 ISO-8601 時間或 Unix 秒。它必須至少在 5 分鐘之後,並且只能縮短默認的 24 小時,不能延長;超出這個範圍會返回400 invalid_request,不會被自動調整。- 響應裏的
expires_at是這筆訂單實際的截止時間。 - 留出餘量。 把它設在你自己的取消時間之前幾分鐘,讓最後一刻開始的 3D Secure 驗證或錢包授權能在你的計時器觸發前完成。
- 重試是安全的。 用同一個
out_trade_no重複下單,返回的是原訂單及其原有的截止時間,expires_at不參與內容比較,所以每次重試都重新計算「當前時間 + 30 分鐘」也不會產生衝突。
截止時間到了仍未付款,訂單會變為 expired,你會收到 payment.expired。這個事件很適合用來觸發庫存釋放,所以除了 payment.succeeded,也要訂閲它。
提前關閉訂單
庫存售罄、買家在你這邊取消了購物車,或者你用一筆新訂單替換了它?立即關閉,而不是等它過期:
curl -X POST "https://api.kukopay.com/v1/orders/ORD_20261105_0001/expire" \
-H "X-Api-Key: $KUKOPAY_API_KEY" \
-H "Idempotency-Key: expire-ORD_20261105_0001"路徑參數接受你的 out_trade_no 或 KukoPay 的 trade_no。控制台的訂單詳情頁也有同樣的 關閉訂單 操作,需要所有者或管理員角色。
平台不會只憑自己的記錄就關閉訂單,而是先向支付通道確認:
| 情況 | 結果 |
|---|---|
| 買家尚未付款 | 訂單關閉:返回 200,狀態為 expired,並發送 payment.expired |
| 買家其實已經付款 | 訂單照常入賬,而不會被關閉:返回 409 conflict,你會收到 payment.succeeded |
| 買家正在完成錢包或 3D Secure 步驟,暫時無法取消 | 訂單保持 pending:返回 409 conflict,稍後重試 |
| 支付通道暫時不可達 | 訂單保持 pending:返回 502 processor_error,用相同的冪等鍵重試 |
訂單已經是 expired | 原樣返回該訂單,200 |
| 訂單已支付、已退款或已失敗 | 409 conflict |
收到任何 409 時,都請重新查詢訂單,以它的最新狀態為準,尤其是買家恰好在你關單的同一時刻完成付款的情況。
自動取消任務,一步一步來
每筆訂單都帶上 expires_at
設在你自己的取消時間之前幾分鐘。
由 payment.expired 驅動取消
收到它時再取消訂單、釋放庫存。這時收銀台已經關閉,不存在競態。
因為其他原因取消?先關閉訂單
調用關閉訂單接口,等它返回 200,或者收到 payment.expired 之後,再在你這一側取消。
讓任務保持冪等
只取消仍在等待付款的訂單。已支付或已取消的訂單保持不動,任務運行兩次也不會有任何變化。
永遠不要因為買家離開了頁面、或者沒有從回跳返回就取消訂單,這些都不能説明付款情況,原因見 return_url 和 notify_url 的區別。
如果買家還是在訂單關閉後付了款呢?
極少數情況下,付款仍會在訂單關閉之後才到達,比如訂單已經被另一筆付款支付,或者已經過期。在 KukoPay,這筆付款會自動全額退回給買家,你會收到 payment.duplicate_refunded。它從不影響你的餘額,不收手續費,也不需要你做任何操作。如果付款到達時訂單仍是 pending,則按正常付款入賬,你會收到 payment.succeeded。
所以你永遠不用為一筆遲到的付款去追退款;不過如果客服團隊需要知道這件事,請訂閲 payment.duplicate_refunded。
收款鏈接和易支付的未付款訂單
- 收款鏈接每次被訪問都會生成一筆訂單,所以沒人付款的訪問也是訂單。它們會隨收銀台鏈接一起過期;控制台的訂單列表默認不顯示它們,勾選 含收款鏈接未付款的訪問 即可看到。見 收款鏈接是什麼。
- 易支付訂單總是帶有截止時間,取自該商戶 ID 設置的訂單有效期,默認 30 分鐘,因為易支付協議裏沒有這個字段。請讓它短於對接系統自己的取消時間,原因見 易支付接入指南。
總結
- 只有收銀台比你的訂單先關閉,自動取消未付款訂單才是安全的;
- 每筆訂單都傳
expires_at:至少 5 分鐘之後、最長不超過默認的 24 小時,並在你自己的超時時間之前留出幾分鐘餘量; - 由
payment.expired觸發取消,或者用POST /v1/orders/{trade_no}/expire提前關閉,它會先向通道確認,已經發生的付款會照常入賬; - 收到
409時,先重新查詢訂單再做決定; - 訂單關閉後才到達的付款會自動退回,並發送
payment.duplicate_refunded。
在沙箱裏把它跑通
註冊商戶後即可申請測試密鑰,不需要等待資質審核。