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
}欄位說明
| 欄位 | 類型 | 說明 |
|---|---|---|
object | string | 固定為 "customer" |
id | string | Recur 內部客戶 ID |
email | string | 客戶 Email(建立後不可變更) |
name | string | null | 客戶名稱 |
external_id | string | null | 您系統中的客戶 ID(建立後不可變更) |
email_verified | boolean | Email 是否已驗證 |
status | string | 客戶狀態:ACTIVE、BANNED |
created_at | string | 建立時間(ISO 8601) |
updated_at | string | 最後更新時間(ISO 8601) |
livemode | boolean | 是否為正式環境 |
列出客戶
取得客戶列表,支援分頁和篩選。
端點
GET /customers請求參數
| 參數 | 類型 | 說明 |
|---|---|---|
limit | number | 每頁數量(預設 10,最大 100) |
starting_after | string | 分頁游標(客戶 ID) |
email | string | 依 Email 篩選(精確比對) |
status | string | 依狀態篩選(ACTIVE、BANNED) |
回應範例
{
"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}路徑參數
| 參數 | 類型 | 說明 |
|---|---|---|
id | string | 客戶 ID 或外部 ID |
查詢參數
| 參數 | 類型 | 說明 |
|---|---|---|
by_external_id | boolean | 設為 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}路徑參數
| 參數 | 類型 | 說明 |
|---|---|---|
id | string | 客戶 ID 或外部 ID |
查詢參數
| 參數 | 類型 | 說明 |
|---|---|---|
by_external_id | boolean | 設為 true 時,將 id 視為外部 ID 查詢 |
請求參數
| 參數 | 類型 | 必填 | 說明 |
|---|---|---|---|
name | string | 否 | 客戶名稱(1-255 字元) |
metadata | object | 否 | 自訂鍵值資料 |
email 和 external_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_id | string | 您系統中的客戶 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_id | string | 您系統中的客戶 ID |
請求參數
| 參數 | 類型 | 必填 | 說明 |
|---|---|---|---|
name | string | 否 | 客戶名稱(1-255 字元) |
metadata | object | 否 | 自訂鍵值資料 |
回應範例
{
"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 狀態 | 錯誤碼 | 說明 |
|---|---|---|
| 400 | invalid_request | 請求參數無效 |
| 401 | unauthorized | 需要 Secret Key 認證 |
| 404 | resource_not_found | 客戶不存在 |
| 500 | internal_error | 伺服器錯誤 |
關於 Checkout Session 中的客戶資料
為什麼 Checkout Session 不會更新現有客戶資料?
Checkout Session 可透過 Publishable Key 建立,而 Publishable Key 是公開的(會暴露在前端)。
如果允許透過 Checkout Session 更新客戶資料,惡意使用者只需要:
- 取得您的 Publishable Key(本來就公開)
- 知道某客戶的 Email
- 建立 Checkout Session 並傳入惡意的
customer_name
這樣就能竄改其他客戶的資料,造成安全風險。
因此,客戶資料的更新僅能透過 Secret Key 認證的 Customer Update API 進行。
下一步
- Subscriptions API - 查詢和管理訂閱
- API 認證 - 了解 API Key 使用方式
- Webhook 整合 - 接收客戶相關事件通知