Recur
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_idstring客戶的內部 ID(優先順序最高)
external_idstring客戶的外部 ID(來自您的系統)
emailstring客戶的 email 地址(優先順序最低)

客戶識別優先順序customer_id > external_id > email

您必須提供至少一個識別參數。如果同時提供多個,系統會按照優先順序選擇。

其他參數:

參數必填類型說明
return_urlstring客戶離開 Portal 後的返回 URL
configuration_idstring指定 Portal 設定 ID
localestring語言偏好(zh-TWen

如果未提供 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"
}

回應欄位說明

欄位類型說明
idstringPortal Session ID
objectstring固定為 portal.session
urlstring導向客戶的 Portal URL
return_urlstring離開 Portal 後的返回 URL
customer_idstring客戶 ID
statusstringSession 狀態(activeexpiredrevoked
expires_atstringSession 過期時間(ISO 8601)
accessed_atstring | null最後存取時間
created_atstring建立時間

程式碼範例

// 推薦:使用 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

狀態說明

狀態說明
activeSession 有效,可以存取 Portal
expiredSession 已過期
revokedSession 已被撤銷

錯誤處理

HTTP 狀態錯誤代碼說明
400invalid_param請求參數無效(需要至少一個客戶識別參數)
400missing_return_url未提供 return_url 且無預設值
401invalid_api_key需要 Secret Key 認證
404customer_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 確保傳輸安全

下一步

On this page