Skip to content

本章是 Payment Gateway 商戶 API 的權威錯誤參考。用它把 HTTP 狀態碼與業務錯誤碼對應到商戶該採取的動作:重試、修正請求,或上報給操作員。

回應信封

每一個商戶 API 回應,無論成功或失敗,都使用相同的 JSON 信封。讀 code 欄位,不要只看 HTTP 狀態碼來決定動作。

json
{
  "success": false,
  "code": "INVALID_SIGNATURE",
  "message": "X-Signature did not match expected value",
  "data": null
}
欄位類型說明
successboolean2xx 時為 true,其他情況為 false
codestring機器可讀。2xx 時為 SUCCESS,其他情況為下方列出的某個錯誤碼。
messagestring人類可讀。可安全寫入日誌。不要未翻譯就直接顯示給終端使用者。
dataobject成功時是該操作特定的 payload。錯誤時為 null

部分錯誤回應也會包含 details 物件,附帶欄位層級的資訊(例如 Pydantic 驗證錯誤)。把 details 視為選用且僅供參考。

永遠用 code 做分支。不要用 message 做分支:message 的文字不屬於 API 合約,可能會改。

HTTP 狀態碼

商戶 API 使用以下 HTTP 狀態碼。「可重試?」欄位說明商戶客戶端是否可以在不需操作員介入的情況下,直接重試同一個請求。

狀態碼意義常見原因可重試?
200OK查詢成功。codeSUCCESS不需要。
201Created訂單或 session 已建立。codeSUCCESS不需要。
400Bad Request業務規則拒絕了該請求,或 JSON 請求主體無法解析。否。修正請求或上報給操作員。
401UnauthorizedAPI key 缺失、無效、過期、簽章錯誤,或時間戳超出窗口範圍。否。修正憑證或簽章邏輯。
403Forbidden商戶停用,或缺少 can_deposit / can_withdraw 權限。否。聯絡平台操作員。
404Not Found訂單不存在,或訂單屬於不同商戶。否。確認識別碼是否正確。
409Conflict資源衝突,例如重複的 merchant_order_no否。先解決衝突再重試。
422Unprocessable Entity請求形狀是合法的 JSON,但欄位層級的驗證失敗。否。修正 payload。
429Too Many Requests超過速率限制。保留用途;目前並非所有端點都有強制執行。是,使用指數退避。
500Internal Server Error未處理的伺服器錯誤。信封 code 永遠是 INTERNAL_ERROR是,使用退避策略。若持續發生請開立工單。
502Bad Gateway上游渠道或出款供應商回傳了無效的回應。是,使用退避策略。
503Service Unavailable服務正在啟動、清空連線中,或暫時下線。是,使用退避策略。
504Gateway Timeout上游渠道逾時。是,使用退避策略並帶冪等鍵。

重試建議是針對「同一個原本送出的請求」。對 create 呼叫遇到 5xx 時,永遠使用冪等鍵(即 merchant_order_no),這樣重試才不會產生重複訂單。

業務錯誤碼

以下錯誤碼會出現在信封的 code 欄位。錯誤碼是穩定的。狀態碼配對反映目前後端的行為。

成功

codeHTTP 狀態碼意義商戶應採取的動作
SUCCESS200、201操作成功。繼續。讀取 data 取得該操作的 payload。

認證與授權

codeHTTP 狀態碼意義商戶應採取的動作
AUTHENTICATION_ERROR401一般性認證失敗。檢查 X-API-Key、簽章邏輯與時鐘。
INVALID_SIGNATURE401X-Signature 與伺服器計算出來的簽章不一致。重新計算簽章。檢查參數排序與 api_key 附加方式。
INVALID_TIMESTAMP401X-Timestamp 缺失、格式錯誤,或超出 5 分鐘窗口。用 NTP 同步你的時鐘。以當前的 Unix 時間戳重新送出。
AUTHORIZATION_ERROR403已認證,但不被允許:商戶停用,或缺少該操作的權限。聯絡平台操作員。不要重試。

認證依賴會在任何業務邏輯執行前,攔截錯誤的 key、錯誤的簽章與過期的時間戳。對當前請求而言,所有 401 回應都視為終局。

驗證

codeHTTP 狀態碼意義商戶應採取的動作
VALIDATION_ERROR400、422請求 payload 驗證失敗。422 用於 Pydantic 欄位錯誤,會帶 details.errors 陣列;400 用於 service 在業務邊界拒絕 payload 的情況。修正 payload。沒修改就不要重試。

狀態與衝突

codeHTTP 狀態碼意義商戶應採取的動作
INVALID_STATE400目標資源目前的狀態不允許此操作,例如取消一筆已完成的訂單。重新查詢訂單看當前的 status。不要盲目重試。
CONFLICT409違反了唯一性約束,通常是重複的 merchant_order_no視為重複。決定是否用新的 merchant_order_no 重試前,先查詢既有訂單。
OPTIMISTIC_LOCK_ERROR400在同一筆商戶餘額或訂單列上,與另一個同時進行的更新發生競爭。在小範圍內(3 次)以短抖動重試同一請求。

OPTIMISTIC_LOCK_ERROR 是唯一一個業務層錯誤碼,可以在不修改請求的情況下安全重試。請限制重試次數並加入抖動,避免 thundering-herd。

餘額

codeHTTP 狀態碼意義商戶應採取的動作
INSUFFICIENT_BALANCE400來源帳本的一般性餘額不足。充值或減少金額。不要原樣重試。
INSUFFICIENT_MERCHANT_BALANCE400商戶可用餘額不足以涵蓋一筆提款。等待充值完成入帳,然後用相同金額重試,或減少金額。

找不到

codeHTTP 狀態碼意義商戶應採取的動作
NOT_FOUND404訂單不存在,或訂單存在但屬於不同商戶。這兩種情況是刻意做成無法區分的。確認你送出的識別碼是否正確。

業務與渠道

codeHTTP 狀態碼意義商戶應採取的動作
BUSINESS_ERROR400業務規則拒絕了該請求,例如「訂單已過期」或「無可用充值帳戶」。讀 message 看上下文。修正請求或上報給操作員。不要重試同一個請求。
EXTERNAL_SERVICE_ERROR400、502上游渠道或出款供應商回傳錯誤。用退避策略重試,並使用相同的 merchant_order_no 維持冪等性。
MERCHANT_API_ERROR400商戶 API 相關失敗的父類錯誤碼。通常你會看到更具體的子類碼,例如 INVALID_SIGNATURE視為終局,除非有更具體的錯誤碼另行說明。

系統

codeHTTP 狀態碼意義商戶應採取的動作
INTERNAL_ERROR500未處理的伺服器錯誤。在正式環境中 message 是通用的。用退避策略重試。若持續發生,聯絡平台團隊。

重試策略

在你的客戶端決定如何處理一個回應時,使用以下這張矩陣。

HTTP 狀態碼可安全重試?策略
200、201N/A完成。把結果持久化。
400中止。修正請求後再送。
401中止。憑證或簽章錯誤。重試不會解決問題。
403中止。需要操作員介入。
404中止。識別碼錯誤或資源不存在。
409中止。先解決衝突,例如查詢既有訂單。
422中止。payload 格式錯誤。
429帶抖動的指數退避。例如限制 60 秒內最多 5 次嘗試。
500指數退避。永遠用相同的 merchant_order_no 維持冪等。
502指數退避。上游渠道可能會恢復。
503指數退避。服務可能正在重啟。
504指數退避。使用相同的 merchant_order_no,因為訂單可能已經建立。

特殊情況:遇到 OPTIMISTIC_LOCK_ERROR(HTTP 400)時,即使狀態碼是 400,也以短抖動重試最多 3 次。

合理的預設退避策略是 min(2^attempt * 250ms + random_jitter, 8s),上限 5 次嘗試。

日誌建議

紀錄足以除錯的內容,但不要多到外洩資料。

每次錯誤都紀錄:

  • HTTP 方法、路徑、狀態碼。
  • code,以及回應中若帶有 request_id 也一併紀錄。
  • 你自己的 merchant_order_no,以及任何內部追蹤 ID。
  • 送出的 X-Timestamp,以及你觀察到的時間戳偏差。

不要紀錄:

  • X-API-Key 的值。只紀錄短前綴或雜湊。
  • X-Signature 的值。只紀錄它驗證失敗的事實;值本身對除錯沒有用處。
  • 完整的銀行帳號、卡號,或終端使用者 PII。寫入遮罩過的值。
  • 含有以上內容的請求主體。

對於 5xx 錯誤,從回應標頭(若有)擷取 request_id 與時間戳。聯絡平台支援時把兩者都附上。

把信封的 message 當成參考資訊。不要對它做模式比對來決定邏輯分支。永遠用 code 分支。

常見陷阱

以下錯誤是商戶整合中常見的錯誤。每一個都對應到 API 中真實存在的失敗情境。

  1. 把 HTTP 200 當成成功,不讀信封內容。 商戶 API 對某些查詢回應會回 HTTP 200,但 success 仍然是 false。永遠同時讀 HTTP 狀態與信封的 code

  2. 重試 401。 401 意味著你的 key、簽章或時間戳錯了。重試同一個 payload 只會得到同一個 401。修正客戶端,再送出一個重新簽章後的請求。

  3. 遇到 429 不做退避就猛打。 當速率限制有強制執行時,沒有退避的反覆請求只會讓你持續被擋。使用帶抖動的指數退避。

  4. 訂單建立時忽略 5xx。 建立訂單時收到 5xx 並不代表訂單沒有建立。伺服器可能在失敗前已經 commit 了訂單。用相同的 merchant_order_no 重試,然後以 merchant_order_no 查詢看真實狀態,再決定下一步。

  5. message 文字做分支。 message 欄位不屬於 API 合約。新的措辭、翻譯或細節變更都可能默默地讓你的客戶端壞掉。只用 code 做分支。

  6. 混淆 400 與 422。 400 表示業務層拒絕了一個結構上沒問題的請求(例如「訂單已過期」)。422 表示請求形狀本身的欄位驗證失敗。兩者的修法不同:400 可能是操作面的問題,422 則一定是客戶端 bug。

  7. 跨不同訂單重複使用 merchant_order_no 這會產生 409 CONFLICT。每一筆新訂單意圖都要產生一個全新的 merchant_order_no。只有在重試「同一筆訂單」時才能重複使用同一個 merchant_order_no

  8. 忽略 merchant_order_no 的重試語意。 沒有穩定的冪等鍵,5xx 後的網路重試會建立重複訂單。伺服器無法替你去重。

  9. X-Timestamp 的時鐘飄移。 伺服器接受 5 分鐘的 X-Timestamp 窗口。客戶端時鐘偏差超過 5 分鐘會產生 INVALID_TIMESTAMP。使用 NTP。不要透過「把伺服器的時鐘回送過去」來「修」這個問題;用你自己當下的 Unix 時間簽章。

  10. 不處理 OPTIMISTIC_LOCK_ERROR 同一商戶餘額的併發更新可能會輸掉競爭。以短抖動重試最多 3 次。不要把第一次衝突直接上報給操作員。

  11. 把 404 解讀成「絕對不存在」。 404 也可能代表該訂單屬於不同商戶。如果你確定自己有建立這筆訂單,先回頭檢查 API key 與 tenant,再認定訂單消失。

  12. 把 API key 或簽章寫進日誌。 這些是憑證。一份外洩的日誌檔就是一份外洩的憑證。只紀錄前綴或雜湊。

交叉參考

  • 認證X-API-KeyX-TimestampX-Signature 是如何建立與驗證的。
  • 回調 — 伺服器對商戶的回調如何傳遞狀態變更;當同步 API 在 5xx 重試期間漏掉狀態時,回調可以補上。
  • 沙盒 — 如何在受控環境裡重現每一個錯誤碼,再上正式環境。