跳轉到主要內容

支付接口錯誤處理:錯誤碼、錯誤類型,以及哪些錯誤可以安全重試

如何處理支付接口錯誤:讀懂錯誤響應結構、按錯誤碼而不是文案做分支、哪些錯誤可以安全重試,以及如何用 request_id 快速排查。

發佈於 約 5 分鐘閲讀

手繪插畫:一張顯示 code 400、error 和 param 字段的錯誤響應卡片,被分到「重試」和「先修正」兩個托盤裏,旁邊一個指着它的人
本文目錄
  1. 支付接口的錯誤長什麼樣?
  2. 按錯誤碼分支,而不是按文案
  3. 錯誤類型,以及能不能重試
  4. 最常遇到的支付接口錯誤碼
  5. 永遠不要把超時當成「訂單不存在」
  6. 接口錯誤不等於付款結果
  7. 用 request_id 更快地排查
  8. 支付接口錯誤處理清單

支付接口錯誤處理,就是決定請求失敗時你的接入該怎麼做的那段代碼:修正請求、重試,還是停下來通知人。這對支付接口比對大多數接口都更重要,因為兩個經典錯誤的代價都很高:一是重試一個永遠不會成功的錯誤;二是對結果未知的錯誤不重試,或者用不安全的方式重試,訂單就是這樣丟失或被創建兩次的。

這篇文章説明如何讀懂一個支付接口錯誤、為什麼要按錯誤碼而不是文案做分支、哪些錯誤可以安全重試,以及如何快速排查一次失敗的調用。示例使用 KukoPay 的接口,原則適用於任何支付服務商。

支付接口的錯誤長什麼樣?

KukoPay 的所有接口都用同一套錯誤結構返回錯誤:

{
  "code": 400,
  "error": "invalid_request",
  "type": "invalid_request_error",
  "message": "…",
  "param": "amount",
  "doc_url": "https://kukopay.com/docs/api/errors",
  "request_id": "req_9f2c1a7b4e8d0356c1ab77de90f4b215"
}
字段用途
codeHTTP 狀態碼,在響應體裏再給一次
error穩定的、機器可讀的錯誤碼,請基於它做分支
type錯誤大類。新增錯誤碼時不變,適合做「要不要重試」這類粗粒度判斷
message給人看的描述。文案可能調整,可以展示、可以記日誌,但不要用來做分支
param出錯的請求字段,沒有時為 null
doc_url該錯誤碼的文檔地址
request_id本次請求的唯一標識,同時在 X-Request-Id 響應頭中返回

按錯誤碼分支,而不是按文案

文案是寫給人看的,會被修改;而且在 KukoPay,文案會按請求的語言渲染,同一個錯誤可能以英文或中文返回。像 if (message.includes("amount")) 這樣的代碼,文案一改就會失效。具體處理用 error,通用規則用 type:

switch (body.error) {
  case "payment_limit_exceeded":
    return showBuyer("這筆金額暫時超出了可收款的範圍。")
  case "conflict":
    return reloadOrder(outTradeNo) // 訂單狀態變了,重新讀取最新狀態
  default:
    return handleByType(body.type)
}

錯誤類型,以及能不能重試

type含義能否重試
authentication_error密鑰缺失或無效,請求沒有被識別為任何商戶修正密鑰後
permission_error身份成立,但無權執行該操作否
invalid_request_error請求本身有問題:字段錯誤、對象不存在、狀態衝突修正請求後
idempotency_error同一個 Idempotency-Key 被用於不同的請求修正請求後
rate_limit_error超出調用頻率限制可以,等待 Retry-After 後
api_connection_error上游通道異常,你的餘額沒有被改動可以
api_error平台內部錯誤可以

實用的規則:

  • 網絡超時、429、502 和 500:保留相同的 Idempotency-Key,指數退避後重試。結果未知或只是暫時的,冪等鍵讓重試是安全的。
  • 400、401、403:不要循環重試。先修正參數、憑據或權限。
  • 404:核對標識,並確認密鑰的環境和應用與對象一致。
  • 409:重新讀取訂單、退款或拒付的最新狀態,再決定下一步。

冪等鍵如何保護重試,見 冪等鍵是什麼;429 的處理見 API 調用頻率限制與 429 Too Many Requests。

最常遇到的支付接口錯誤碼

HTTPerror含義怎麼做
401missing_api_key / invalid_api_key沒有密鑰,或密鑰錯誤、已吊銷確認 X-Api-Key 請求頭沒有被代理層去掉;更新密鑰,見 API 密鑰輪換
403merchant_not_approved賬戶審核通過前使用了正式密鑰審核完成前繼續在沙箱裏開發
403ip_not_allowed請求來源不在應用的 IP 白名單內核對服務器的出口 IP
403app_disabled密鑰所屬的應用已停用,不能創建訂單重新啓用該應用,或改用其他應用的密鑰
400invalid_request某個字段不合法按 param 指出的字段修正
400payment_limit_exceeded超出賬戶的收款限額給買家一個清楚的提示;限額見商戶入駐文檔
400refund_window_expired支付方式的退款期限已過與買家線下協商處理,見 部分退款怎麼操作
404order_not_found 等 *_not_found在這把密鑰的環境和應用下沒有這個對象核對單號、環境和應用
409conflict狀態已變化、存在未結案拒付,或同一個 out_trade_no 配了不同的內容重新讀取對象;新訂單請使用新的訂單號
429rate_limited超出每分鐘配額等待 Retry-After 秒
502processor_error通道異常,你的餘額沒有任何變化用相同的冪等鍵重試
500internal_error平台出現故障用相同的冪等鍵重試;持續出現請附上 request_id 聯繫我們

完整的錯誤碼列表見 錯誤碼。它和接口使用的是同一份清單生成的,所以文檔裏的錯誤碼就是你實際會收到的。

永遠不要把超時當成「訂單不存在」

超時不是錯誤響應,你根本沒有收到響應,訂單很可能已經創建了。不要生成一個新的訂單號再試一次,買家的兩筆訂單就是這麼來的。要麼用同一個 out_trade_no(以及同一個 Idempotency-Key)重試,如果訂單已經存在,返回的就是那筆訂單;要麼先用 out_trade_no 查詢訂單。

接口錯誤不等於付款結果

POST /v1/orders 調用成功,不代表買家已經付款;你的服務器報錯,也不代表買家沒有付款。付款結果(已支付、被拒、過期)是通過訂單狀態和 Webhook 告訴你的,而不是通過接口錯誤。比如銀行卡被拒,根本不會出現在你的接口調用裏:買家在收銀台上看到提示,並可以重試(見 銀行卡支付被拒怎麼辦)。請以驗簽後的 payment.succeeded Webhook 決定是否履約,見 return_url 和 notify_url 的區別。

用 request_id 更快地排查

每一個響應,無論成功還是失敗,都會在響應體和 X-Request-Id 響應頭裏帶上 request_id。它總是由平台簽發,並與平台日誌一一對應。

  • 每次調用都記下它,和你自己的訂單號放在一起。
  • 自己查詢這次調用:調用日誌保存每次調用的請求和響應,敏感字段已脱敏,保留 30 天。可以按 request_id、status=4xx、路徑或錯誤碼篩選。
curl -G "https://api.kukopay.com/v1/logs" \
  -H "X-Api-Key: $KUKOPAY_API_KEY" \
  -d status=4xx \
  -d limit=20
  • 需要幫助時發給客服:有了 request_id,可以立刻找到那一次調用;沒有它,只能按訂單號和時間範圍人工查找。

見 API 調用日誌。

支付接口錯誤處理清單

  • 按 error 分支,以 type 兜底,永遠不要按 message;
  • 超時、429 和 5xx 用退避加相同的 Idempotency-Key 重試;
  • 400/401/403 要修正,而不是重試;
  • 遇到 409,重新讀取對象;
  • 永遠不要用一個新訂單號來應對超時;
  • 每次調用都記錄 request_id;
  • 用 Webhook 和訂單狀態判斷付款結果,而不是接口錯誤。

在沙箱裏把它跑通

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