跳轉到主要內容

如何自動取消未付款訂單,又不丟掉遲到的付款?

如何自動取消未付款訂單:給訂單設置支付截止時間、先關閉收銀台再取消訂單,以及買家在訂單取消後才付款時該怎麼處理。

發佈於 約 4 分鐘閲讀

手繪插畫:一張帶沙漏的待支付訂單、一個 30 分鐘計時器,以及一把鎖住收銀台頁面的鎖,旁邊一個雙手插兜的人
本文目錄
  1. 為什麼會出現「取消後又付款」?
  2. 給每筆訂單設置支付截止時間
  3. 提前關閉訂單
  4. 自動取消任務,一步一步來
  5. 如果買家還是在訂單關閉後付了款呢?
  6. 收款鏈接和易支付的未付款訂單
  7. 總結

大多數網店都會在超時後自動取消未付款訂單,比如 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。

在沙箱裏把它跑通

註冊商戶後即可申請測試密鑰,不需要等待資質審核。