API 參考
Portal Sessions API
Customer Portal Session 建立與管理 API
Portal Sessions API
Customer Portal 讓您的客戶可以自助管理訂閱,包括查看訂閱詳情、更新付款方式、取消訂閱等。
建立 Portal Session
建立一個短期有效的 Portal Session,用於將客戶導向 Customer Portal。
端點
POST /portal/sessions認證
此端點需要 Secret Key 認證,僅限後端使用。Portal Session 包含敏感的客戶資訊,不應在前端建立。
# Authorization Header(推薦)
Authorization: Bearer sk_test_xxx
# 或專用 Header
X-Recur-Secret-Key: sk_test_xxx請求參數
客戶識別(至少需要其中一個):
| 參數 | 必填 | 類型 | 說明 |
|---|---|---|---|
customer_id | ❌ | string | 客戶的內部 ID(優先順序最高) |
external_id | ❌ | string | 客戶的外部 ID(來自您的系統) |
email | ❌ | string | 客戶的 email 地址(優先順序最低) |
客戶識別優先順序:customer_id > external_id > email
您必須提供至少一個識別參數。如果同時提供多個,系統會按照優先順序選擇。
其他參數:
| 參數 | 必填 | 類型 | 說明 |
|---|---|---|---|
return_url | ❌ | string | 客戶離開 Portal 後的返回 URL |
configuration_id | ❌ | string | 指定 Portal 設定 ID |
locale | ❌ | string | 語言偏好(zh-TW 或 en) |
如果未提供 return_url,將使用 Portal 設定中的預設返回 URL。若都未設定,會返回錯誤。
回應格式
{
"id": "portal_session_xxxxx",
"object": "portal.session",
"url": "https://portal.recur.tw/s/ps_xxxxx",
"return_url": "https://your-site.com/account",
"customer_id": "cus_xxxxx",
"status": "active",
"expires_at": "2025-01-15T12:00:00.000Z",
"accessed_at": null,
"created_at": "2025-01-15T11:00:00.000Z"
}回應欄位說明
| 欄位 | 類型 | 說明 |
|---|---|---|
id | string | Portal Session ID |
object | string | 固定為 portal.session |
url | string | 導向客戶的 Portal URL |
return_url | string | 離開 Portal 後的返回 URL |
customer_id | string | 客戶 ID |
status | string | Session 狀態(active、expired、revoked) |
expires_at | string | Session 過期時間(ISO 8601) |
accessed_at | string | null | 最後存取時間 |
created_at | string | 建立時間 |
程式碼範例
// 推薦:使用 Recur Server SDK
import Recur from 'recur-tw/server';
const recur = new Recur({
secretKey: process.env.RECUR_SECRET_KEY!,
});
// 方式一:使用客戶 ID
const session = await recur.portal.sessions.create({
customer: 'cus_xxxxx',
return_url: 'https://your-site.com/account',
});
// 方式二:使用 email
const session = await recur.portal.sessions.create({
email: 'customer@example.com',
return_url: 'https://your-site.com/account',
});
// 方式三:使用外部 ID(您系統中的用戶 ID)
const session = await recur.portal.sessions.create({
external_id: 'user_123',
return_url: 'https://your-site.com/account',
});
// 導向客戶
redirect(session.url);// app/api/portal/create/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
// 可以接收 customer_id、email 或 external_id
const { customer_id, email, external_id } = await request.json();
const response = await fetch('https://api.recur.tw/v1/portal/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.RECUR_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// 提供至少其中一個識別參數
...(customer_id && { customer_id }),
...(email && { email }),
...(external_id && { external_id }),
return_url: `${process.env.NEXT_PUBLIC_URL}/account`,
}),
});
const data = await response.json();
if (!response.ok) {
return NextResponse.json({ error: data.error }, { status: response.status });
}
return NextResponse.json({ url: data.url });
}import requests
import os
def create_portal_session(
customer_id: str = None,
email: str = None,
external_id: str = None
) -> dict:
"""
建立 Portal Session
至少需要提供 customer_id、email 或 external_id 其中之一
"""
body = {
'return_url': f'{os.environ["APP_URL"]}/account',
}
# 根據提供的參數選擇識別方式
if customer_id:
body['customer_id'] = customer_id
elif external_id:
body['external_id'] = external_id
elif email:
body['email'] = email
else:
raise ValueError('至少需要提供 customer_id、email 或 external_id')
response = requests.post(
'https://api.recur.tw/v1/portal/sessions',
headers={
'Authorization': f'Bearer {os.environ["RECUR_SECRET_KEY"]}',
'Content-Type': 'application/json',
},
json=body
)
response.raise_for_status()
return response.json()
# 使用方式
session = create_portal_session(customer_id='cus_xxxxx')
session = create_portal_session(email='customer@example.com')
session = create_portal_session(external_id='user_123')
print(f'Portal URL: {session["url"]}')# 使用客戶 ID
curl -X POST "https://api.recur.tw/v1/portal/sessions" \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_xxxxx",
"return_url": "https://your-site.com/account"
}'
# 使用 email
curl -X POST "https://api.recur.tw/v1/portal/sessions" \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"email": "customer@example.com",
"return_url": "https://your-site.com/account"
}'
# 使用外部 ID
curl -X POST "https://api.recur.tw/v1/portal/sessions" \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"external_id": "user_123",
"return_url": "https://your-site.com/account"
}'Session 生命週期
有效期限
- Portal Session 有效期為 1 小時
- 過期後 Session 狀態變為
expired - 每個客戶最多同時擁有 5 個 有效 Session
狀態說明
| 狀態 | 說明 |
|---|---|
active | Session 有效,可以存取 Portal |
expired | Session 已過期 |
revoked | Session 已被撤銷 |
錯誤處理
| HTTP 狀態 | 錯誤代碼 | 說明 |
|---|---|---|
| 400 | invalid_param | 請求參數無效(需要至少一個客戶識別參數) |
| 400 | missing_return_url | 未提供 return_url 且無預設值 |
| 401 | invalid_api_key | 需要 Secret Key 認證 |
| 404 | customer_not_found | 找不到指定的客戶 |
錯誤回應範例
// 找不到客戶(使用 email)
{
"error": {
"type": "invalid_request_error",
"code": "customer_not_found",
"message": "Customer not found (email: customer@example.com)"
}
}
// 找不到客戶(使用 external_id)
{
"error": {
"type": "invalid_request_error",
"code": "customer_not_found",
"message": "Customer not found (external_id: user_123)"
}
}
// 缺少客戶識別參數
{
"error": {
"type": "invalid_request_error",
"code": "invalid_param",
"message": "At least one of customer_id, email, or external_id is required"
}
}使用案例
1. 帳戶設定頁面的「管理訂閱」按鈕
// app/account/page.tsx
'use client';
import { useState } from 'react';
export function ManageSubscriptionButton({ customerId }: { customerId: string }) {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
try {
const response = await fetch('/api/portal/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ customer_id: customerId }),
});
const { url, error } = await response.json();
if (error) {
alert('發生錯誤:' + error.message);
return;
}
window.location.href = url;
} finally {
setLoading(false);
}
};
return (
<button onClick={handleClick} disabled={loading}>
{loading ? '載入中...' : '管理訂閱'}
</button>
);
}2. 使用 Recur SDK Web Component
<!-- 方式一:直接提供 Portal URL(server-rendered) -->
<recur-portal portal-url="https://portal.recur.tw/s/ps_xxxxx">
管理訂閱
</recur-portal>
<!-- 方式二:透過 API 動態建立 Session -->
<recur-portal
api-endpoint="/api/portal/create"
customer-id="cus_xxxxx">
管理訂閱
</recur-portal>安全性考量
重要安全提醒
- Portal Session URL 包含敏感存取權杖,請勿記錄或暴露
- 務必驗證用戶身份後再建立對應客戶的 Portal Session
- 使用 HTTPS 確保傳輸安全
下一步
- Customer Portal 整合指南 - 完整整合教學
- Webhook 事件 - 監聽訂閱變更事件
- Subscriptions API - 查詢訂閱狀態