Recur
API 參考

錯誤碼參考

Recur API 錯誤碼完整說明與處理建議

錯誤碼參考

Recur API 使用標準的 HTTP 狀態碼和統一的錯誤格式。本頁說明所有可能的錯誤碼及處理建議。

錯誤回應格式

所有 API 錯誤都使用以下格式:

{
  "error": {
    "code": "error_code",
    "message": "人類可讀的錯誤訊息",
    "doc_url": "https://docs.recur.tw/api/error-codes#error_code",
    "details": [
      {
        "key": "額外資訊"
      }
    ]
  }
}
欄位類型說明
codestring錯誤碼(如 conflictnot_found
messagestring人類可讀的錯誤訊息
doc_urlstring相關文件連結(選填)
detailsarray錯誤相關的額外資訊(選填)

錯誤碼列表

bad_request (400)

請求參數無效或缺少必要參數。

{
  "error": {
    "code": "bad_request",
    "message": "Missing required fields: sessionId, productId, email"
  }
}

處理建議:檢查請求參數是否正確,參考 API 文件確認必要欄位。


unauthorized (401)

API Key 無效、缺失或已過期。

{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key"
  }
}

處理建議

  • 確認 API Key 是否正確
  • 確認使用正確的環境(Sandbox/Production)
  • 確認 API Key 未被撤銷

payment_required (402)

付款失敗。

{
  "error": {
    "code": "payment_required",
    "message": "Payment failed: card declined"
  }
}

處理建議:引導用戶更換付款方式或聯繫銀行。


forbidden (403)

無權存取此資源。

{
  "error": {
    "code": "forbidden",
    "message": "Access denied to this resource"
  }
}

處理建議:確認 API Key 有足夠權限。Secret Key 才能存取某些端點。


not_found (404)

找不到請求的資源。

{
  "error": {
    "code": "not_found",
    "message": "Product not found or inactive"
  }
}

處理建議:確認資源 ID 是否正確,或資源是否已被刪除。


conflict (409)

資源衝突。最常見的情況是客戶已有該商品的有效訂閱。

{
  "error": {
    "code": "conflict",
    "message": "Customer already has an active subscription to this product",
    "details": [
      {
        "existing_subscription_id": "sub_xxxxx",
        "status": "ACTIVE"
      }
    ]
  }
}

重複訂閱 (Duplicate Subscription)

當客戶嘗試建立已有有效訂閱(ACTIVE、TRIAL 或 PAST_DUE)的商品訂閱時會觸發此錯誤。

details 欄位說明

欄位類型說明
existing_subscription_idstring現有訂閱的 ID
statusstring現有訂閱的狀態(ACTIVE、TRIAL、PAST_DUE)

處理建議

// 引導用戶到 Customer Portal 管理現有訂閱
if (error.code === 'conflict') {
  const portalUrl = await recur.portal.sessions.create({
    customer: customerId,
    returnUrl: window.location.href,
  });
  window.location.href = portalUrl.url;
}
// 使用 details 中的 ID 查詢現有訂閱詳情
if (error.code === 'conflict') {
  const existingSubId = error.details?.[0]?.existing_subscription_id;
  const response = await fetch(
    `https://api.recur.tw/v1/subscriptions?customer_id=${customerId}`,
    { headers: { 'Authorization': `Bearer ${secretKey}` } }
  );
  const { subscriptions } = await response.json();
  const subscription = subscriptions.find(s => s.id === existingSubId);
  console.log('現有訂閱:', subscription);
}
// 如需切換方案,使用訂閱方案切換 API
if (error.code === 'conflict') {
  const existingSubId = error.details?.[0]?.existing_subscription_id;
  const response = await fetch(
    `https://api.recur.tw/v1/subscriptions/${existingSubId}/switch`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${secretKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        target_product_id: newProductId,
      }),
    }
  );
}

validation_error (422)

請求參數驗證失敗。

{
  "error": {
    "code": "validation_error",
    "message": "Invalid request parameters",
    "details": [
      {
        "path": "email",
        "message": "Invalid email format",
        "code": "invalid_type"
      }
    ]
  }
}

處理建議:根據 details 中的資訊修正參數格式。


rate_limit_exceeded (429)

請求過於頻繁,已超過速率限制。

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests"
  }
}

處理建議

  • 實作指數退避重試(exponential backoff)
  • 減少請求頻率
  • 考慮使用 Webhook 取代輪詢

internal_server_error (500)

伺服器發生錯誤。

{
  "error": {
    "code": "internal_server_error",
    "message": "An unexpected error occurred"
  }
}

處理建議

  • 稍後重試請求
  • 如果問題持續,請聯繫 Recur 支援團隊

SDK 錯誤處理

TypeScript / JavaScript

SDK 提供型別定義方便處理錯誤:

import type { ApiErrorCode, CheckoutError } from 'recur-tw';

function handleError(error: CheckoutError) {
  switch (error.code as ApiErrorCode) {
    case 'conflict':
      // 重複訂閱
      const details = error.details?.[0];
      console.log('現有訂閱:', details?.existing_subscription_id);
      break;

    case 'payment_required':
      // 付款失敗
      console.log('請更換付款方式');
      break;

    case 'not_found':
      // 找不到資源
      console.log('商品不存在');
      break;

    default:
      console.log('發生錯誤:', error.message);
  }
}

Server SDK

import { Recur, RecurAPIError } from 'recur-tw/server';

const recur = new Recur(process.env.RECUR_SECRET_KEY!);

try {
  const session = await recur.portal.sessions.create({
    email: 'user@example.com',
  });
} catch (error) {
  if (error instanceof RecurAPIError) {
    console.log('錯誤碼:', error.code);
    console.log('狀態碼:', error.statusCode);
    console.log('詳情:', error.details);
  }
}

最佳實踐

1. 針對特定錯誤碼處理

不要只檢查 HTTP 狀態碼,應該檢查 error.code

// ✅ 好的做法
if (error.code === 'conflict') {
  // 處理重複訂閱
}

// ❌ 不好的做法
if (response.status === 409) {
  // 409 可能有多種含義
}

2. 使用 details 獲取更多資訊

details 陣列包含錯誤的額外資訊:

if (error.code === 'conflict' && error.details?.[0]) {
  const { existing_subscription_id, status } = error.details[0];
  // 使用這些資訊提供更好的用戶體驗
}

3. 記錄錯誤用於除錯

onError: (error) => {
  // 記錄完整錯誤資訊
  console.error('[Recur Error]', {
    code: error.code,
    message: error.message,
    details: error.details,
    docUrl: error.docUrl,
  });

  // 發送到錯誤追蹤服務
  Sentry.captureException(error);
}

下一步