支付接口錯誤處理:錯誤碼、錯誤類型,以及哪些錯誤可以安全重試
如何處理支付接口錯誤:讀懂錯誤響應結構、按錯誤碼而不是文案做分支、哪些錯誤可以安全重試,以及如何用 request_id 快速排查。
本文目錄
支付接口錯誤處理,就是決定請求失敗時你的接入該怎麼做的那段代碼:修正請求、重試,還是停下來通知人。這對支付接口比對大多數接口都更重要,因為兩個經典錯誤的代價都很高:一是重試一個永遠不會成功的錯誤;二是對結果未知的錯誤不重試,或者用不安全的方式重試,訂單就是這樣丟失或被創建兩次的。
這篇文章説明如何讀懂一個支付接口錯誤、為什麼要按錯誤碼而不是文案做分支、哪些錯誤可以安全重試,以及如何快速排查一次失敗的調用。示例使用 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"
}| 字段 | 用途 |
|---|---|
code | HTTP 狀態碼,在響應體裏再給一次 |
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。
最常遇到的支付接口錯誤碼
| HTTP | error | 含義 | 怎麼做 |
|---|---|---|---|
| 401 | missing_api_key / invalid_api_key | 沒有密鑰,或密鑰錯誤、已吊銷 | 確認 X-Api-Key 請求頭沒有被代理層去掉;更新密鑰,見 API 密鑰輪換 |
| 403 | merchant_not_approved | 賬戶審核通過前使用了正式密鑰 | 審核完成前繼續在沙箱裏開發 |
| 403 | ip_not_allowed | 請求來源不在應用的 IP 白名單內 | 核對服務器的出口 IP |
| 403 | app_disabled | 密鑰所屬的應用已停用,不能創建訂單 | 重新啓用該應用,或改用其他應用的密鑰 |
| 400 | invalid_request | 某個字段不合法 | 按 param 指出的字段修正 |
| 400 | payment_limit_exceeded | 超出賬戶的收款限額 | 給買家一個清楚的提示;限額見商戶入駐文檔 |
| 400 | refund_window_expired | 支付方式的退款期限已過 | 與買家線下協商處理,見 部分退款怎麼操作 |
| 404 | order_not_found 等 *_not_found | 在這把密鑰的環境和應用下沒有這個對象 | 核對單號、環境和應用 |
| 409 | conflict | 狀態已變化、存在未結案拒付,或同一個 out_trade_no 配了不同的內容 | 重新讀取對象;新訂單請使用新的訂單號 |
| 429 | rate_limited | 超出每分鐘配額 | 等待 Retry-After 秒 |
| 502 | processor_error | 通道異常,你的餘額沒有任何變化 | 用相同的冪等鍵重試 |
| 500 | internal_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 和訂單狀態判斷付款結果,而不是接口錯誤。
在沙箱裏把它跑通
註冊商戶後即可申請測試密鑰,不需要等待資質審核。