Recur
API 參考

Customers API

客戶資料查詢與管理 API

Customers API

客戶(Customer)是 Recur 中代表付費用戶的核心實體。每個客戶都與一個 email 綁定,並可選擇性地關聯您系統中的外部 ID。

所有 Customers API 端點都需要 Secret Key 認證,僅限後端使用。這是為了保護客戶資料安全,避免透過公開的 Publishable Key 被惡意存取或修改。

客戶物件

{
  "object": "customer",
  "id": "cus_xxxxx",
  "email": "user@example.com",
  "name": "王小明",
  "external_id": "user_123",
  "email_verified": false,
  "status": "ACTIVE",
  "created_at": "2025-01-01T00:00:00.000Z",
  "updated_at": "2025-01-15T10:00:00.000Z",
  "livemode": false
}

欄位說明

欄位類型說明
objectstring固定為 "customer"
idstringRecur 內部客戶 ID
emailstring客戶 Email(建立後不可變更)
namestring | null客戶名稱
external_idstring | null您系統中的客戶 ID(建立後不可變更)
email_verifiedbooleanEmail 是否已驗證
statusstring客戶狀態:ACTIVEBANNED
created_atstring建立時間(ISO 8601)
updated_atstring最後更新時間(ISO 8601)
livemodeboolean是否為正式環境

列出客戶

取得客戶列表,支援分頁和篩選。

端點

GET /customers

請求參數

參數類型說明
limitnumber每頁數量(預設 10,最大 100)
starting_afterstring分頁游標(客戶 ID)
emailstring依 Email 篩選(精確比對)
statusstring依狀態篩選(ACTIVEBANNED

回應範例

{
  "object": "list",
  "data": [
    {
      "object": "customer",
      "id": "cus_xxxxx",
      "email": "user@example.com",
      "name": "王小明",
      "external_id": "user_123",
      "email_verified": false,
      "status": "ACTIVE",
      "created_at": "2025-01-01T00:00:00.000Z",
      "updated_at": "2025-01-15T10:00:00.000Z",
      "subscriptions_count": 2,
      "orders_count": 5
    }
  ],
  "has_more": true,
  "next_cursor": "cus_yyyyy",
  "livemode": false
}

程式碼範例

// 列出所有客戶
const response = await fetch(
  'https://api.recur.tw/v1/customers?limit=20',
  {
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
    },
  }
);

const { data, has_more, next_cursor } = await response.json();

console.log(`共 ${data.length} 位客戶`);

// 分頁取得更多
if (has_more) {
  const nextPage = await fetch(
    `https://api.recur.tw/v1/customers?starting_after=${next_cursor}`,
    {
      headers: {
        'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
      },
    }
  );
}
# 列出客戶
curl -X GET "https://api.recur.tw/v1/customers?limit=20" \
  -H "Authorization: Bearer sk_test_xxx"

# 依 Email 篩選
curl -X GET "https://api.recur.tw/v1/customers?email=user@example.com" \
  -H "Authorization: Bearer sk_test_xxx"

取得客戶

透過 ID 取得單一客戶的詳細資訊。

端點

GET /customers/{id}

路徑參數

參數類型說明
idstring客戶 ID 或外部 ID

查詢參數

參數類型說明
by_external_idboolean設為 true 時,將 id 視為外部 ID 查詢

回應範例

{
  "object": "customer",
  "id": "cus_xxxxx",
  "email": "user@example.com",
  "name": "王小明",
  "external_id": "user_123",
  "email_verified": false,
  "status": "ACTIVE",
  "created_at": "2025-01-01T00:00:00.000Z",
  "updated_at": "2025-01-15T10:00:00.000Z",
  "subscriptions": [
    {
      "id": "sub_xxxxx",
      "status": "ACTIVE",
      "current_period_start": "2025-01-01T00:00:00.000Z",
      "current_period_end": "2025-02-01T00:00:00.000Z",
      "product": {
        "id": "prod_xxxxx",
        "name": "Pro Plan",
        "slug": "pro-monthly"
      }
    }
  ],
  "livemode": false
}

程式碼範例

// 透過內部 ID 查詢
const response = await fetch(
  'https://api.recur.tw/v1/customers/cus_xxxxx',
  {
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
    },
  }
);

const customer = await response.json();
console.log(`客戶: ${customer.name} (${customer.email})`);

// 透過外部 ID 查詢
const response2 = await fetch(
  'https://api.recur.tw/v1/customers/user_123?by_external_id=true',
  {
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
    },
  }
);
# 透過內部 ID 查詢
curl -X GET "https://api.recur.tw/v1/customers/cus_xxxxx" \
  -H "Authorization: Bearer sk_test_xxx"

# 透過外部 ID 查詢
curl -X GET "https://api.recur.tw/v1/customers/user_123?by_external_id=true" \
  -H "Authorization: Bearer sk_test_xxx"

更新客戶

更新客戶資訊。

安全性說明:此端點僅允許 Secret Key 認證。這是因為 Checkout Session 可透過 Publishable Key 建立,如果允許 Publishable Key 更新客戶資料,惡意使用者可能會竄改其他客戶的資訊。

端點

PATCH /customers/{id}

路徑參數

參數類型說明
idstring客戶 ID 或外部 ID

查詢參數

參數類型說明
by_external_idboolean設為 true 時,將 id 視為外部 ID 查詢

請求參數

參數類型必填說明
namestring客戶名稱(1-255 字元)
metadataobject自訂鍵值資料

emailexternal_id 在建立後不可變更。如需變更 Email,請建立新客戶。

回應範例

{
  "object": "customer",
  "id": "cus_xxxxx",
  "email": "user@example.com",
  "name": "王大明",
  "external_id": "user_123",
  "email_verified": false,
  "status": "ACTIVE",
  "created_at": "2025-01-01T00:00:00.000Z",
  "updated_at": "2025-01-15T10:30:00.000Z",
  "livemode": false
}

程式碼範例

// 更新客戶名稱
const response = await fetch(
  'https://api.recur.tw/v1/customers/cus_xxxxx',
  {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: '王大明',
    }),
  }
);

const customer = await response.json();
console.log(`已更新客戶名稱: ${customer.name}`);

// 透過外部 ID 更新
const response2 = await fetch(
  'https://api.recur.tw/v1/customers/user_123?by_external_id=true',
  {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: '新名字',
    }),
  }
);
import requests
import os

# 更新客戶名稱
response = requests.patch(
    'https://api.recur.tw/v1/customers/cus_xxxxx',
    headers={
        'Authorization': f'Bearer {os.environ["RECUR_SECRET_KEY"]}',
        'Content-Type': 'application/json',
    },
    json={
        'name': '王大明',
    }
)

customer = response.json()
print(f'已更新客戶名稱: {customer["name"]}')
# 更新客戶名稱
curl -X PATCH "https://api.recur.tw/v1/customers/cus_xxxxx" \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "王大明"}'

# 透過外部 ID 更新
curl -X PATCH "https://api.recur.tw/v1/customers/user_123?by_external_id=true" \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "新名字"}'

透過外部 ID 操作

除了使用查詢參數 ?by_external_id=true,Recur 也提供更簡潔的 URL 格式讓您透過外部 ID 操作客戶資料。

何時使用這個端點? 如果您的系統已經有自己的使用者 ID(external_id),使用 /customers/external/{external_id} 端點可以讓您的程式碼更簡潔,不需要先查詢 Recur 內部的客戶 ID。

透過外部 ID 取得客戶

端點

GET /customers/external/{external_id}

路徑參數

參數類型說明
external_idstring您系統中的客戶 ID

回應範例

{
  "object": "customer",
  "id": "cus_xxxxx",
  "email": "user@example.com",
  "name": "王小明",
  "external_id": "user_123",
  "email_verified": false,
  "status": "ACTIVE",
  "created_at": "2025-01-01T00:00:00.000Z",
  "updated_at": "2025-01-15T10:00:00.000Z",
  "subscriptions": [
    {
      "id": "sub_xxxxx",
      "status": "ACTIVE",
      "current_period_start": "2025-01-01T00:00:00.000Z",
      "current_period_end": "2025-02-01T00:00:00.000Z",
      "product": {
        "id": "prod_xxxxx",
        "name": "Pro Plan",
        "slug": "pro-monthly"
      }
    }
  ],
  "livemode": false
}

程式碼範例

// 透過外部 ID 取得客戶
const externalId = 'user_123';
const response = await fetch(
  `https://api.recur.tw/v1/customers/external/${externalId}`,
  {
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
    },
  }
);

const customer = await response.json();
console.log(`客戶: ${customer.name} (${customer.email})`);
console.log(`訂閱數: ${customer.subscriptions.length}`);
curl -X GET "https://api.recur.tw/v1/customers/external/user_123" \
  -H "Authorization: Bearer sk_test_xxx"

透過外部 ID 更新客戶

端點

PATCH /customers/external/{external_id}

路徑參數

參數類型說明
external_idstring您系統中的客戶 ID

請求參數

參數類型必填說明
namestring客戶名稱(1-255 字元)
metadataobject自訂鍵值資料

回應範例

{
  "object": "customer",
  "id": "cus_xxxxx",
  "email": "user@example.com",
  "name": "王大明",
  "external_id": "user_123",
  "email_verified": false,
  "status": "ACTIVE",
  "created_at": "2025-01-01T00:00:00.000Z",
  "updated_at": "2025-01-15T10:30:00.000Z",
  "livemode": false
}

程式碼範例

// 透過外部 ID 更新客戶名稱
const externalId = 'user_123';
const response = await fetch(
  `https://api.recur.tw/v1/customers/external/${externalId}`,
  {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: '王大明',
    }),
  }
);

const customer = await response.json();
console.log(`已更新客戶名稱: ${customer.name}`);
import requests
import os

# 透過外部 ID 更新客戶名稱
external_id = 'user_123'
response = requests.patch(
    f'https://api.recur.tw/v1/customers/external/{external_id}',
    headers={
        'Authorization': f'Bearer {os.environ["RECUR_SECRET_KEY"]}',
        'Content-Type': 'application/json',
    },
    json={
        'name': '王大明',
    }
)

customer = response.json()
print(f'已更新客戶名稱: {customer["name"]}')
curl -X PATCH "https://api.recur.tw/v1/customers/external/user_123" \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "王大明"}'

使用案例

1. 同步客戶資料

當您的系統中客戶資料更新時,同步到 Recur:

// app/api/webhooks/user-updated/route.ts
export async function POST(req: NextRequest) {
  const { userId, name, email } = await req.json();

  // 透過外部 ID 更新 Recur 客戶(使用更簡潔的 URL 格式)
  const response = await fetch(
    `${process.env.RECUR_API_URL}/v1/customers/external/${userId}`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ name }),
    }
  );

  if (response.ok) {
    console.log('Customer synced to Recur');
  }

  return NextResponse.json({ success: true });
}

2. 客戶管理後台

在您的管理後台顯示客戶列表:

// app/admin/customers/page.tsx
async function getCustomers(cursor?: string) {
  const url = new URL('https://api.recur.tw/v1/customers');
  url.searchParams.set('limit', '50');
  if (cursor) {
    url.searchParams.set('starting_after', cursor);
  }

  const response = await fetch(url.toString(), {
    headers: {
      'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
    },
    next: { revalidate: 60 }, // Cache for 1 minute
  });

  return response.json();
}

export default async function CustomersPage() {
  const { data: customers, has_more, next_cursor } = await getCustomers();

  return (
    <div>
      <h1>客戶列表</h1>
      <table>
        <thead>
          <tr>
            <th>Email</th>
            <th>名稱</th>
            <th>訂閱數</th>
            <th>建立時間</th>
          </tr>
        </thead>
        <tbody>
          {customers.map((customer) => (
            <tr key={customer.id}>
              <td>{customer.email}</td>
              <td>{customer.name || '-'}</td>
              <td>{customer.subscriptions_count}</td>
              <td>{new Date(customer.created_at).toLocaleDateString()}</td>
            </tr>
          ))}
        </tbody>
      </table>
      {has_more && (
        <button onClick={() => loadMore(next_cursor)}>
          載入更多
        </button>
      )}
    </div>
  );
}

3. 修正錯誤的客戶名稱

當客戶反映名稱錯誤時,客服可以協助修正:

async function fixCustomerName(email: string, correctName: string) {
  // 先用 email 找到客戶
  const listResponse = await fetch(
    `https://api.recur.tw/v1/customers?email=${encodeURIComponent(email)}`,
    {
      headers: {
        'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
      },
    }
  );

  const { data } = await listResponse.json();

  if (data.length === 0) {
    throw new Error('Customer not found');
  }

  const customerId = data[0].id;

  // 更新名稱
  const updateResponse = await fetch(
    `https://api.recur.tw/v1/customers/${customerId}`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ name: correctName }),
    }
  );

  return updateResponse.json();
}

錯誤處理

HTTP 狀態錯誤碼說明
400invalid_request請求參數無效
401unauthorized需要 Secret Key 認證
404resource_not_found客戶不存在
500internal_error伺服器錯誤

關於 Checkout Session 中的客戶資料

為什麼 Checkout Session 不會更新現有客戶資料?

Checkout Session 可透過 Publishable Key 建立,而 Publishable Key 是公開的(會暴露在前端)。

如果允許透過 Checkout Session 更新客戶資料,惡意使用者只需要:

  1. 取得您的 Publishable Key(本來就公開)
  2. 知道某客戶的 Email
  3. 建立 Checkout Session 並傳入惡意的 customer_name

這樣就能竄改其他客戶的資料,造成安全風險。

因此,客戶資料的更新僅能透過 Secret Key 認證的 Customer Update API 進行。


下一步

On this page