API 參考
Subscriptions API
訂閱狀態查詢與管理 API
Subscriptions API
訂閱相關的 API 端點,用於查詢和管理訂閱狀態。
查詢訂閱狀態
查詢訂閱狀態,支援列出所有訂閱或按客戶篩選。此端點需要 Secret Key 認證,適合後端使用。
端點
GET /subscriptions認證
此端點需要 Secret Key 認證,僅限後端使用。
支援以下認證方式:
# 方式 1: Authorization Header(推薦)
Authorization: Bearer sk_test_xxx
# 方式 2: 專用 Header
X-Recur-Secret-Key: sk_test_xxx請求參數
客戶篩選(選填)
| 參數 | 類型 | 說明 |
|---|---|---|
email | string | 客戶 Email |
external_id | string | 您系統中的客戶 ID(外部 ID) |
customer_id | string | Recur 內部客戶 ID |
若不提供任何客戶識別參數,將返回該組織下所有訂閱。每筆訂閱會包含對應的客戶資訊。
商品篩選(選填)
| 參數 | 類型 | 說明 |
|---|---|---|
product_id | string | 商品 ID |
product_slug | string | 商品 Slug |
product_id 和 product_slug 為選填。若不提供,將返回該客戶的所有訂閱。
狀態篩選(選填)
| 參數 | 類型 | 說明 |
|---|---|---|
active | boolean | 設為 true 僅返回有效訂閱(ACTIVE、TRIAL、PAST_DUE) |
status | string | 篩選特定狀態(如 ACTIVE、CANCELED) |
分頁參數(選填)
| 參數 | 類型 | 說明 |
|---|---|---|
limit | number | 每頁返回數量(預設 10,最大 100) |
starting_after | string | 分頁游標,傳入上一頁最後一筆訂閱的 ID |
回應格式
按客戶篩選(有訂閱)
{
"object": "list",
"has_active_subscription": true,
"subscriptions": [
{
"id": "sub_xxxxx",
"status": "ACTIVE",
"product_id": "prod_xxxxx",
"product_slug": "pro-monthly",
"product_name": "Pro Plan",
"amount": 299,
"interval": "month",
"interval_count": 1,
"current_period_start": "2025-01-01T00:00:00.000Z",
"current_period_end": "2025-02-01T00:00:00.000Z",
"canceled_at": null,
"started_at": "2024-12-01T00:00:00.000Z",
"coupon": null,
"coupon_remaining_cycles": null,
"discount_amount": 0,
"promotion_code": null
}
],
"customer": {
"id": "cus_xxxxx",
"email": "user@example.com",
"name": "王小明",
"external_id": "user_123"
},
"has_more": false,
"next_cursor": null,
"livemode": false
}列出所有訂閱(無客戶篩選)
當不提供任何客戶識別參數時,每筆訂閱會包含對應的客戶資訊:
{
"object": "list",
"has_active_subscription": true,
"subscriptions": [
{
"id": "sub_xxxxx",
"status": "ACTIVE",
"product_id": "prod_xxxxx",
"product_slug": "pro-monthly",
"product_name": "Pro Plan",
"amount": 299,
"interval": "month",
"interval_count": 1,
"current_period_start": "2025-01-01T00:00:00.000Z",
"current_period_end": "2025-02-01T00:00:00.000Z",
"canceled_at": null,
"started_at": "2024-12-01T00:00:00.000Z",
"customer": {
"id": "cus_xxxxx",
"email": "user@example.com",
"name": "王小明",
"external_id": "user_123"
}
},
{
"id": "sub_yyyyy",
"status": "ACTIVE",
"product_id": "prod_xxxxx",
"product_slug": "pro-monthly",
"product_name": "Pro Plan",
"amount": 299,
"interval": "month",
"interval_count": 1,
"current_period_start": "2025-01-05T00:00:00.000Z",
"current_period_end": "2025-02-05T00:00:00.000Z",
"canceled_at": null,
"started_at": "2024-12-05T00:00:00.000Z",
"customer": {
"id": "cus_yyyyy",
"email": "another@example.com",
"name": "李小華",
"external_id": "user_456"
}
}
],
"customer": null,
"has_more": true,
"next_cursor": "sub_yyyyy",
"livemode": false
}含優惠的訂閱範例:
{
"object": "list",
"has_active_subscription": true,
"subscriptions": [
{
"id": "sub_xxxxx",
"status": "ACTIVE",
"product_id": "prod_xxxxx",
"product_slug": "pro-monthly",
"product_name": "Pro Plan",
"amount": 299,
"interval": "month",
"interval_count": 1,
"current_period_start": "2025-01-01T00:00:00.000Z",
"current_period_end": "2025-02-01T00:00:00.000Z",
"canceled_at": null,
"started_at": "2024-12-01T00:00:00.000Z",
"coupon": {
"id": "cpn_xxxxx",
"name": "新年優惠 8 折",
"discount_type": "PERCENTAGE",
"discount_amount": 2000,
"duration": "REPEATING"
},
"coupon_remaining_cycles": 2,
"discount_amount": 60,
"promotion_code": "NEWYEAR2025"
}
],
"customer": {
"id": "cus_xxxxx",
"email": "user@example.com",
"name": "王小明",
"external_id": "user_123"
},
"has_more": false,
"next_cursor": null,
"livemode": false
}無有效訂閱的客戶
{
"object": "list",
"has_active_subscription": false,
"subscriptions": [
{
"id": "sub_xxxxx",
"status": "CANCELED",
"product_id": "prod_xxxxx",
"product_slug": "pro-monthly",
"product_name": "Pro Plan",
"amount": 299,
"interval": "month",
"interval_count": 1,
"current_period_start": "2024-12-01T00:00:00.000Z",
"current_period_end": "2025-01-01T00:00:00.000Z",
"canceled_at": "2025-01-01T00:00:00.000Z",
"started_at": "2024-12-01T00:00:00.000Z",
"coupon": null,
"coupon_remaining_cycles": null,
"discount_amount": 0,
"promotion_code": null
}
],
"customer": {
"id": "cus_xxxxx",
"email": "user@example.com",
"name": "王小明",
"external_id": null
},
"has_more": false,
"next_cursor": null,
"livemode": false
}找不到客戶
當提供客戶篩選參數但找不到對應客戶時:
{
"object": "list",
"has_active_subscription": false,
"subscriptions": [],
"customer": null,
"has_more": false,
"next_cursor": null,
"livemode": false
}回應欄位說明
| 欄位 | 類型 | 說明 |
|---|---|---|
object | string | 固定為 "list" |
has_active_subscription | boolean | 是否有有效訂閱(ACTIVE、TRIAL、PAST_DUE) |
subscriptions | array | 所有符合條件的訂閱(按建立時間降冪排序) |
customer | object | null | 客戶資訊(僅在按客戶篩選時有值) |
has_more | boolean | 是否有更多結果(用於分頁) |
next_cursor | string | null | 下一頁的游標(傳入 starting_after 參數) |
livemode | boolean | 是否為正式環境 |
訂閱物件欄位
| 欄位 | 類型 | 說明 |
|---|---|---|
id | string | 訂閱 ID |
status | string | 訂閱狀態 |
product_id | string | 商品 ID |
product_slug | string | 商品 Slug |
product_name | string | 商品名稱 |
amount | number | 金額 |
interval | string | 週期單位(month、year) |
interval_count | number | 週期數量 |
current_period_start | string | 當期開始時間 |
current_period_end | string | 當期結束時間 |
canceled_at | string | null | 取消時間 |
started_at | string | 開始時間 |
customer | object | 客戶資訊(僅在無客戶篩選時出現) |
coupon | object | null | 套用的優惠資訊(見下方說明) |
coupon_remaining_cycles | number | null | 週期性折扣剩餘期數(僅 REPEATING 類型) |
discount_amount | number | 本期折扣金額 |
promotion_code | string | null | 使用的推廣代碼 |
優惠物件結構(coupon)
| 欄位 | 類型 | 說明 |
|---|---|---|
id | string | 優惠 ID |
name | string | 優惠名稱 |
discount_type | string | 折扣類型:FIXED_AMOUNT、PERCENTAGE、FIRST_PERIOD_PRICE |
discount_amount | number | 折扣數值(固定金額為分、百分比為 basis points) |
duration | string | 折扣期間:ONCE、REPEATING、FOREVER |
訂閱狀態說明
以下狀態視為「有效訂閱」(hasActiveSubscription: true):
| 狀態 | 說明 |
|---|---|
ACTIVE | 正常訂閱中 |
TRIAL | 試用期中 |
PAST_DUE | 逾期但尚未取消(仍有效) |
以下狀態視為「無效訂閱」:
| 狀態 | 說明 |
|---|---|
CANCELED | 已取消 |
EXPIRED | 已過期 |
PAUSED | 已暫停 |
程式碼範例
// 使用 email 查詢所有訂閱
const response = await fetch(
'https://api.recur.tw/v1/subscriptions?' +
new URLSearchParams({
email: 'user@example.com',
}),
{
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
},
}
);
const data = await response.json();
if (data.has_active_subscription) {
console.log('用戶有有效訂閱');
console.log('訂閱數:', data.subscriptions.length);
console.log('第一筆訂閱狀態:', data.subscriptions[0].status);
} else {
console.log('用戶沒有有效訂閱');
}
// 使用 external_id 查詢特定方案
const response2 = await fetch(
'https://api.recur.tw/v1/subscriptions?' +
new URLSearchParams({
external_id: 'user_123',
product_slug: 'pro-monthly',
}),
{
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
},
}
);import requests
import os
# 使用 email 查詢所有訂閱
response = requests.get(
'https://api.recur.tw/v1/subscriptions',
headers={
'Authorization': f'Bearer {os.environ["RECUR_SECRET_KEY"]}',
},
params={
'email': 'user@example.com',
}
)
data = response.json()
if data['has_active_subscription']:
print('用戶有有效訂閱')
print(f'訂閱數: {len(data["subscriptions"])}')
print(f'第一筆訂閱狀態: {data["subscriptions"][0]["status"]}')
else:
print('用戶沒有有效訂閱')
# 使用 external_id 查詢
response2 = requests.get(
'https://api.recur.tw/v1/subscriptions',
headers={
'Authorization': f'Bearer {os.environ["RECUR_SECRET_KEY"]}',
},
params={
'external_id': 'user_123',
'active': 'true',
}
)# 列出所有有效訂閱(不需指定客戶)
curl -X GET "https://api.recur.tw/v1/subscriptions?active=true&limit=50" \
-H "Authorization: Bearer sk_test_xxx"
# 分頁:取得下一頁
curl -X GET "https://api.recur.tw/v1/subscriptions?active=true&limit=50&starting_after=sub_xxxxx" \
-H "Authorization: Bearer sk_test_xxx"
# 使用 email 查詢特定客戶的訂閱
curl -X GET "https://api.recur.tw/v1/subscriptions?email=user@example.com" \
-H "Authorization: Bearer sk_test_xxx"
# 使用 external_id 查詢特定方案
curl -X GET "https://api.recur.tw/v1/subscriptions?external_id=user_123&product_slug=pro-monthly" \
-H "X-Recur-Secret-Key: sk_test_xxx"
# 使用 customer_id 查詢僅有效訂閱
curl -X GET "https://api.recur.tw/v1/subscriptions?customer_id=cus_xxxxx&active=true" \
-H "Authorization: Bearer sk_test_xxx"
# 篩選特定狀態
curl -X GET "https://api.recur.tw/v1/subscriptions?email=user@example.com&status=CANCELED" \
-H "Authorization: Bearer sk_test_xxx"使用案例
1. 權限控制(使用 external_id)
在您的後端驗證用戶是否有權限存取付費功能:
// middleware/checkSubscription.ts
import { NextRequest, NextResponse } from 'next/server';
export async function checkSubscription(req: NextRequest, userId: string) {
// 使用您系統的 userId 作為 external_id 查詢
const response = await fetch(
`${process.env.RECUR_API_URL}/v1/subscriptions?` +
new URLSearchParams({
external_id: userId,
active: 'true', // 只查詢有效訂閱
}),
{
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
},
}
);
const { has_active_subscription } = await response.json();
if (!has_active_subscription) {
return NextResponse.json(
{ error: '此功能需要付費訂閱' },
{ status: 403 }
);
}
return null; // 允許通過
}2. 查詢用戶所有訂閱
不指定方案,取得用戶的所有訂閱歷史:
// app/api/user/subscriptions/route.ts
export async function GET(req: NextRequest) {
const session = await getSession();
const response = await fetch(
`${process.env.RECUR_API_URL}/v1/subscriptions?` +
new URLSearchParams({
email: session.user.email,
// 不指定 product_slug,返回所有訂閱
}),
{
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
},
}
);
const data = await response.json();
// Find active subscription
const activeSubscription = data.subscriptions.find(
(sub: { status: string }) => ['ACTIVE', 'TRIAL', 'PAST_DUE'].includes(sub.status)
);
return NextResponse.json({
hasActiveSubscription: data.has_active_subscription,
currentProduct: activeSubscription?.product_name,
allSubscriptions: data.subscriptions.map((sub: { product_name: string; status: string; started_at: string; current_period_end: string }) => ({
product: sub.product_name,
status: sub.status,
startedAt: sub.started_at,
expiresAt: sub.current_period_end,
})),
});
}3. 顯示訂閱資訊
在用戶設定頁面顯示當前訂閱狀態:
// app/api/user/subscription/route.ts
export async function GET(req: NextRequest) {
const session = await getSession();
const response = await fetch(
`${process.env.RECUR_API_URL}/v1/subscriptions?` +
new URLSearchParams({
email: session.user.email,
product_slug: 'pro',
}),
{
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
},
}
);
const data = await response.json();
// Get the first subscription (sorted by createdAt desc)
const subscription = data.subscriptions[0];
return NextResponse.json({
isSubscribed: data.has_active_subscription,
product: subscription?.product_name,
renewsAt: subscription?.current_period_end,
status: subscription?.status,
});
}錯誤處理
| HTTP 狀態 | 錯誤訊息 | 說明 |
|---|---|---|
| 401 | This endpoint requires a Secret Key | 需要 Secret Key 認證 |
| 401 | Invalid API key | API Key 無效 |
| 404 | Product not found: xxx | 找不到指定的商品(僅在提供 product_id/product_slug 時) |
注意事項
安全提醒:此端點使用 Secret Key 認證,請勿在前端調用。所有查詢應透過您的後端進行。
快取建議:為了效能考量,建議在您的後端實作適當的快取機制,避免頻繁查詢相同用戶的訂閱狀態。
訂閱方案切換
允許您代替客戶執行訂閱方案的升級、降級或週期變更。
所有訂閱切換 API 都需要 Secret Key 認證,僅限後端使用。
切換類型說明
| 類型 | 說明 | 執行方式 |
|---|---|---|
UPGRADE | 升級到更高價方案 | 立即執行,按比例計算差額 |
DOWNGRADE | 降級到較低價方案 | 排程至當期結束時執行 |
PERIOD_CHANGE | 變更計費週期(月→年或年→月) | 月→年立即執行;年→月排程執行 |
CROSSGRADE | 切換到同價方案 | 立即執行,無需補差額 |
預覽切換
在執行切換前,預覽切換結果和費用計算。
端點
GET /subscriptions/{subscription_id}/switch-preview請求參數
| 參數 | 類型 | 必填 | 說明 |
|---|---|---|---|
target_product_id | string | 是 | 目標方案 ID |
回應範例
{
"object": "switch_preview",
"subscription_id": "sub_xxxxx",
"switch_type": "UPGRADE",
"execution_mode": "immediate",
"current_plan": {
"product_id": "prod_basic",
"product_name": "Basic Plan",
"amount": 299,
"currency": "TWD",
"interval": "month",
"interval_count": 1,
"monthly_equivalent": 299
},
"new_plan": {
"product_id": "prod_pro",
"product_name": "Pro Plan",
"amount": 599,
"currency": "TWD",
"interval": "month",
"interval_count": 1,
"monthly_equivalent": 599
},
"proration": {
"credit_amount": 150,
"charge_amount": 599,
"net_amount": 449,
"unused_days": 15,
"total_days_in_period": 30,
"credit_description": "15 天未使用的 Basic Plan"
},
"effective_date": "2025-01-15T10:00:00.000Z",
"next_billing_date": "2025-02-15T00:00:00.000Z",
"requires_payment": true,
"can_proceed": true,
"is_in_trial": false
}回應欄位說明
| 欄位 | 類型 | 說明 |
|---|---|---|
switch_type | string | 切換類型(UPGRADE、DOWNGRADE、PERIOD_CHANGE、CROSSGRADE) |
execution_mode | string | 執行方式(immediate 立即執行、scheduled 排程執行) |
proration | object | null | 按比例計算結果(降級時為 null) |
proration.net_amount | number | 客戶需支付的淨額(charge - credit) |
requires_payment | boolean | 是否需要付款 |
can_proceed | boolean | 是否可執行切換 |
blocking_reason | string | null | 無法切換的原因(如有) |
程式碼範例
const response = await fetch(
`https://api.recur.tw/v1/subscriptions/${subscriptionId}/switch-preview?` +
new URLSearchParams({
target_product_id: 'prod_pro',
}),
{
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
},
}
);
const preview = await response.json();
if (preview.can_proceed) {
console.log(`切換類型: ${preview.switch_type}`);
console.log(`執行方式: ${preview.execution_mode}`);
if (preview.proration) {
console.log(`需付金額: ${preview.proration.net_amount}`);
}
}curl -X GET "https://api.recur.tw/v1/subscriptions/sub_xxxxx/switch-preview?target_product_id=prod_pro" \
-H "Authorization: Bearer sk_test_xxx"執行切換
執行訂閱方案切換。根據切換類型,會立即執行或排程至當期結束。
端點
POST /subscriptions/{subscription_id}/switch請求參數
| 參數 | 類型 | 必填 | 說明 |
|---|---|---|---|
target_product_id | string | 是 | 目標方案 ID |
proration_behavior | string | 否 | 按比例計算行為:create_prorations(預設)、none |
回應範例(立即執行)
升級或月轉年會立即執行:
{
"object": "switch_result",
"execution_mode": "immediate",
"subscription": {
"id": "sub_xxxxx",
"product_id": "prod_pro",
"status": "ACTIVE",
"amount": 599,
"currency": "TWD",
"interval": "month",
"interval_count": 1,
"current_period_start": "2025-01-15T10:00:00.000Z",
"current_period_end": "2025-02-15T00:00:00.000Z",
"next_billing_date": "2025-02-15T00:00:00.000Z",
"previous_product_id": "prod_basic",
"switched_at": "2025-01-15T10:00:00.000Z",
"switch_type": "UPGRADE"
},
"invoice": {
"id": "inv_xxxxx",
"invoice_number": "INV-20250115-ABC123",
"amount": 449,
"currency": "TWD",
"status": "PAID",
"billing_reason": "SUBSCRIPTION_UPDATE",
"billing_entries": [
{
"id": "entry_1",
"type": "PRORATION_CREDIT",
"direction": "CREDIT",
"amount": 150,
"description": "15 天未使用的 Basic Plan"
},
{
"id": "entry_2",
"type": "SUBSCRIPTION",
"direction": "CHARGE",
"amount": 599,
"description": "Pro Plan (月繳)"
}
]
},
"schedule": null,
"switch_type": "UPGRADE",
"proration": {
"credit_amount": 150,
"charge_amount": 599,
"net_amount": 449,
"unused_days": 15,
"total_days_in_period": 30,
"credit_description": "15 天未使用的 Basic Plan"
},
"effective_date": "2025-01-15T10:00:00.000Z"
}回應範例(排程執行)
降級或年轉月會排程至當期結束:
{
"object": "switch_result",
"execution_mode": "scheduled",
"subscription": {
"id": "sub_xxxxx"
},
"invoice": null,
"schedule": {
"id": "sched_xxxxx",
"target_product_id": "prod_basic",
"switch_type": "DOWNGRADE",
"effective_at": "2025-02-15T00:00:00.000Z",
"status": "PENDING",
"created_at": "2025-01-15T10:00:00.000Z"
},
"switch_type": "DOWNGRADE",
"proration": null,
"effective_date": "2025-02-15T00:00:00.000Z"
}程式碼範例
// 執行升級
const response = await fetch(
`https://api.recur.tw/v1/subscriptions/${subscriptionId}/switch`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
target_product_id: 'prod_pro',
}),
}
);
const result = await response.json();
if (result.execution_mode === 'immediate') {
console.log('切換已立即生效');
console.log(`新方案: ${result.subscription.product_id}`);
if (result.invoice) {
console.log(`帳單金額: ${result.invoice.amount}`);
}
} else {
console.log('切換已排程');
console.log(`生效日期: ${result.schedule.effective_at}`);
}curl -X POST "https://api.recur.tw/v1/subscriptions/sub_xxxxx/switch" \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{"target_product_id": "prod_pro"}'查詢排程切換
查詢訂閱是否有待執行的排程切換。
端點
GET /subscriptions/{subscription_id}/schedule回應範例(有排程)
{
"object": "schedule",
"has_pending_schedule": true,
"schedule": {
"id": "sched_xxxxx",
"subscription_id": "sub_xxxxx",
"target_product_id": "prod_basic",
"target_product_name": "Basic Plan",
"switch_type": "DOWNGRADE",
"effective_at": "2025-02-15T00:00:00.000Z",
"status": "PENDING",
"created_at": "2025-01-15T10:00:00.000Z"
}
}回應範例(無排程)
{
"object": "schedule",
"has_pending_schedule": false,
"schedule": null
}取消排程切換
取消待執行的排程切換,訂閱將維持當前方案。
端點
DELETE /subscriptions/{subscription_id}/schedule回應範例
{
"object": "schedule_cancellation",
"cancelled": true,
"subscription_id": "sub_xxxxx"
}程式碼範例
// 取消排程的降級
const response = await fetch(
`https://api.recur.tw/v1/subscriptions/${subscriptionId}/schedule`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
},
}
);
const result = await response.json();
if (result.cancelled) {
console.log('排程切換已取消,訂閱將維持當前方案');
}curl -X DELETE "https://api.recur.tw/v1/subscriptions/sub_xxxxx/schedule" \
-H "Authorization: Bearer sk_test_xxx"使用案例
1. 客服協助升級
當客服需要幫客戶升級方案時:
async function upgradeSubscription(subscriptionId: string, newProductId: string) {
// 1. 先預覽切換
const previewRes = await fetch(
`${RECUR_API_URL}/v1/subscriptions/${subscriptionId}/switch-preview?` +
new URLSearchParams({ target_product_id: newProductId }),
{
headers: { 'Authorization': `Bearer ${RECUR_SECRET_KEY}` },
}
);
const preview = await previewRes.json();
if (!preview.can_proceed) {
throw new Error(`無法切換: ${preview.blocking_reason}`);
}
// 2. 確認後執行切換
const switchRes = await fetch(
`${RECUR_API_URL}/v1/subscriptions/${subscriptionId}/switch`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${RECUR_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ target_product_id: newProductId }),
}
);
return switchRes.json();
}2. 顯示待執行的降級
在用戶設定頁面顯示排程中的降級:
async function getPendingDowngrade(subscriptionId: string) {
const res = await fetch(
`${RECUR_API_URL}/v1/subscriptions/${subscriptionId}/schedule`,
{
headers: { 'Authorization': `Bearer ${RECUR_SECRET_KEY}` },
}
);
const data = await res.json();
if (data.has_pending_schedule) {
return {
hasPendingChange: true,
newPlan: data.schedule.target_product_name,
effectiveDate: data.schedule.effective_at,
canCancel: true,
};
}
return { hasPendingChange: false };
}錯誤處理
| HTTP 狀態 | 錯誤碼 | 說明 |
|---|---|---|
| 401 | unauthorized | 需要 Secret Key 認證 |
| 404 | subscription_not_found | 找不到訂閱 |
| 404 | product_not_found | 找不到目標方案 |
| 400 | same_product | 目標方案與當前相同 |
| 400 | subscription_not_active | 訂閱非有效狀態 |
| 400 | past_due_blocks_switch | 訂閱逾期中,無法切換 |
| 402 | payment_required | 需要付款但付款失敗 |
下一步
- Webhook 整合 - 接收訂閱狀態變更通知
- API 認證 - 了解 API Key 使用方式
- Customer Portal - 讓客戶自助管理訂閱