Skip to Content

錯誤

所有 4xx/5xx 回應都帶有相同的 JSON 信封:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" } ] }
  • code數值型的 HTTP status(400401404 …)。用於通用的 HTTP 層處理很方便,但需要分支邏輯時請改用 messagecode 無法區分如 invalid_inputpayment_method_not_supported(兩者都是 400)這類情境。
  • messagelower-snake-case 的 sentinel 名稱,源自內部 errors.go 常數(例如 INVALID_INPUT → "invalid_input")。跨版本穩定 — 以此分支。
  • details — 僅在驗證錯誤時出現。{ field, message } 物件陣列,讓 client 能將錯誤對應到輸入欄位。其他情境下會省略。

目前我們不會在 body 中回傳 trace_idtimestamp。本頁先前的草稿曾承諾這兩個欄位 — 那只是期望,並未實作。如果你需要將伺服器 log 與請求對應,請擷取回應的 Date 標頭以及 gateway 端的 rate-limit 標頭(X-RateLimit-*),並在 support ticket 中引用。

gateway 邊緣的拒絕使用不同的形態。 上面的信封是後端服務發出的。在抵達服務 之前 就被 gateway 拒絕的請求 —— /b2b/v1/* 呼叫上缺失 / 非法的 X-Signature、未知的 X-Client-ID,或過期的 X-Timestamp —— 會以 { "error": "...", "message": "..." } 的形態回傳,其中 error 是粗粒度 slug(unauthorized / bad_request / service_unavailable),message 攜帶具體資訊。沒有數值 code,也沒有 details。因此驗證方應先以 HTTP 狀態分支,再讀 message,只有當請求越過 gateway 之後才把 code / details 當作存在。gateway body 範例(401):

{ "error": "unauthorized", "message": "invalid signature" }

HTTP 狀態 → 常見 code

HTTP常見 code意義
400INVALID_INPUTMISSING_REQUIREDINVALID_FORMATINVALID_LENGTHINVALID_VALUEINVALID_USER_STATUSINVALID_USER_ROLEPAYMENT_METHOD_NOT_SUPPORTEDAMOUNT_BELOW_MINIMUM請求有誤 — 請看 details
401INVALID_CREDENTIALSINVALID_TOKENTOKEN_EXPIREDINVALID_SIGNATURE(B2B);INVALID_OTPOTP_EXPIREDSESSION_EXPIRED(僅儀表板 / JWT)驗證失敗 — 金鑰錯誤、timestamp 過期、簽章不符。OTP / SESSION_EXPIRED 這幾個 code 只出現在儀表板 JWT 路由(/payment/v1/*);純 B2B 整合不會看到它們。
402INSUFFICIENT_CREDIT商家預付餘額用盡 — 請先儲值再重試
403FORBIDDENIP_BLOCKED金鑰有效但缺少呼叫此 API 所需的 scope / IP 許可
404NOT_FOUNDRECORD_NOT_FOUNDUSER_NOT_FOUNDSESSION_NOT_FOUND資源不存在(或對此商家不存在)
409ALREADY_EXISTSUSER_ALREADY_EXISTSSESSION_ALREADY_EXISTS不同 body 的冪等 replay,或狀態機拒絕該轉移
429TOO_MANY_REQUESTSTOO_MANY_ATTEMPTS觸發速率限制;請退避後重試
500INTERNAL_SERVER_ERRORDATABASE_CONNECTION_ERRORDATABASE_QUERY_ERRORDATABASE_TRANSACTION_ERRORREDIS_CONNECTION_ERRORREDIS_OPERATION_ERROREXTERNAL_SERVICE_ERROR我們的問題;可帶退避安全重試。(provider 專屬的 code 例如 TWILIO_SERVICE_ERROR / SENDGRID_SERVICE_ERROR 內部存在,但只在儀表板側的通知流程中出現,B2B endpoint 不會看到。)
503SERVICE_UNAVAILABLE下游依賴出問題。請退避後重試

完整 code 參考

你可能看到的完整 code 值集合(對應 payment-service/pkg/errors/errors.go):

身分驗證與 session(401)

  • INVALID_CREDENTIALS — 帳號密碼或 API 金鑰組合被拒絕
  • INVALID_TOKEN — JWT / session token 無法解析或被竄改
  • TOKEN_EXPIRED — JWT 已超過 exp
  • INVALID_OTP — OTP 不符
  • OTP_EXPIRED — OTP 已超過容差視窗
  • SESSION_EXPIRED — 儀表板 session 已過期
  • INVALID_SIGNATURE — B2B / webhook 呼叫的 HMAC 簽章不符

授權(403)

  • FORBIDDEN — 已驗證但 role / scope / 商家邊界封鎖了該動作
  • IP_BLOCKED — IP 在濫用清單上

找不到(404)

  • NOT_FOUND — 通用
  • RECORD_NOT_FOUND — 找不到該 ID 對應的列
  • USER_NOT_FOUND — 使用者查詢失敗
  • SESSION_NOT_FOUND — 儀表板 session id 無法辨識

衝突(409)

  • ALREADY_EXISTS — 通用
  • USER_ALREADY_EXISTS — 註冊撞到 unique 限制
  • SESSION_ALREADY_EXISTS — 重複 session 插入

驗證(400)

  • INVALID_INPUT — 通用;請查 details
  • MISSING_REQUIRED — 缺少必要欄位
  • INVALID_FORMAT — 值不符合預期格式(例如 UUID、URL、email)
  • INVALID_LENGTH — 值過短或過長
  • INVALID_VALUE — 值超出允許的 enum / 範圍
  • INVALID_USER_STATUS — 使用者處於不允許該動作的狀態
  • INVALID_USER_ROLE — Role 缺少執行該動作的權限
  • PAYMENT_METHOD_NOT_SUPPORTED — 該 provider / 資產組合未對此商家啟用
  • AMOUNT_BELOW_MINIMUM — 訂單金額低於該網路或環境的下限。回應的 details.floor_usd 攜帶目前設定的下限(USD),可直接取用顯示;message 文字也會說明它。
  • INSUFFICIENT_BALANCE — 買家的錢包持有的支付資產不足以支付此次轉帳。
  • INSUFFICIENT_GAS — 買家的錢包缺少原生 gas 以廣播此次轉帳。

付款 / 計費(402)

  • INSUFFICIENT_CREDIT — 商家預付餘額不足以支付 gas / 平台費用。請從儀表板儲值後再重試

速率限制(429)

  • TOO_MANY_REQUESTS — gateway IP 速率限制
  • TOO_MANY_ATTEMPTS — 對同一資源(例如 OTP)反覆失敗導致觸發 throttle

儲存 / 基礎設施(500)

  • DATABASE_CONNECTION_ERROR — 無法連線資料庫
  • DATABASE_QUERY_ERROR — Query plan 在 runtime 失敗
  • DATABASE_TRANSACTION_ERROR — Commit / rollback 失敗
  • REDIS_CONNECTION_ERROR — 無法連線 Redis
  • REDIS_OPERATION_ERROR — Redis 命令失敗
  • EXTERNAL_SERVICE_ERROR — 通用第三方失敗(下方未分類的 provider)
  • TWILIO_SERVICE_ERROR — Twilio SMS / Verify 呼叫失敗
  • SENDGRID_SERVICE_ERROR — SendGrid 郵件寄送失敗
  • INTERNAL_SERVER_ERROR — 非預期 fall-through;請擷取回應的 Date 標頭 + X-RateLimit-* 並回報我們

可用性(503)

  • SERVICE_UNAVAILABLE — 一個關鍵下游回報不健康;請退避後重試

驗證錯誤(400)

當問題是請求 body 不正確時,details 是陣列,讓你可以把錯誤對應 回欄位:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" }, { "field": "success_url", "message": "must be a valid https URL" } ] }

身分驗證錯誤(401)

INVALID_SIGNATURE 涵蓋三種不同的失敗模式 — 區分它們的唯一方法 就是逐一排查:

  • Secret key 錯誤(再次確認環境變數)
  • Timestamp 漂移 > 5 分鐘(同步 NTP)
  • Canonical string 錯誤(最常見:忘記 \n 分隔符,或簽了一份 經過 parse-then-re-stringify 與實際送出不同的 body)

確切的簽章演算法請見身分驗證

速率限制(429)

範圍限制
所有 gateway 路由(含 /b2b/v1/*)Gateway 以 IP 計算 — 500 req/min,共享 bucket。並非以商家為單位。
公開結帳(/checkout/*)更嚴格的子 bucket — 每 IP 20 req/min

X-RateLimit-LimitX-RateLimit-Remaining每一次經過 gateway 速率限制檢查的回應上都會發出(不只是成功時)。請把它們視為 你 IP 的即時預算。

被速率限制的回應會包含 Retry-After 標頭(距離視窗重置的秒數)— 請遵循它。作為備援,可以 jitter 退避 — 1s 基底 + 指數成長到 30s。

速率限制可能會調整。如果你是合法地撞到上限(例如對帳大量歷史 資料),請與我們聯繫 — 批次 endpoint 在路線圖上。

信用額度不足(402)

INSUFFICIENT_CREDIT(HTTP 402 Payment Required)代表商家的預付 餘額無法支付你嘗試的操作下一步的 gas 與平台費用 — 典型情境是結算 加密支付,或執行 gas 由平台代付的鏈上動作。請從商家儀表板 (Billing → Add credit)儲值後再重試該操作;處理中的工作會等待, 餘額恢復後會自動接續執行。

伺服器錯誤(5xx)

500 代表我們無法處理該請求。請以退避方式重試 — 你的 idempotency_key 確保即使原請求部分成功,也不會被重複收費。

若 1 分鐘內重試仍未恢復,請對買家顯示通用的「付款暫時無法使用」 訊息,並向 support 回報失敗的 endpoint、你的 merchant ID、回應的 Date 標頭與 X-RateLimit-* 的值,以及大約的請求時間 — 這些資訊 足以讓我們在 log 中定位到對應的請求。

Webhook 投遞錯誤

Webhook 投遞是獨立的失敗通道 — 它們不會以 API 錯誤的形式出現, 因為呼叫的不是你的伺服器。當投遞回非 2xx(或逾時)時,會以指數 退避排程重試:0s、1min、5min、15min、1h、6h(共 6 次嘗試 — 與 Webhooks → 總覽相同排程)。完整的每次 投遞歷史在儀表板的 Developers → Webhooks → [endpoint] → Delivery log 中。第 6 次嘗試失敗後,該投遞會進入死信 — 儀表板會顯示 「Failed」徽章,在你的伺服器恢復後可手動 replay。