開發者指南Customer Portal
前端整合
使用 Recur SDK Web Component 整合 Customer Portal 按鈕
前端整合
Recur SDK 提供 <recur-portal> Web Component,讓您可以快速在前端加入 Customer Portal 按鈕。
安裝 SDK
npm install recur-tw// 在應用程式入口點引入
import 'recur-tw';<script src="https://cdn.recur.tw/sdk/recur.umd.js"></script><recur-portal> 組件
使用方式一:直接提供 Portal URL
適合 Server-Side Rendering (SSR) 的應用,在後端取得 Portal URL 後直接傳入:
<recur-portal portal-url="https://portal.recur.tw/s/ps_xxxxx">
管理訂閱
</recur-portal>// Next.js Server Component 範例
import Recur from 'recur-tw/server';
const recur = new Recur({ secretKey: process.env.RECUR_SECRET_KEY! });
export default async function AccountPage() {
const session = await recur.portal.sessions.create({
customer: 'cus_xxxxx',
returnUrl: 'https://your-site.com/account',
});
return (
<recur-portal portal-url={session.url}>
管理訂閱
</recur-portal>
);
}使用方式二:透過 API 動態建立
適合 Client-Side Rendering (CSR) 的應用,點擊時呼叫您的後端 API 建立 Session:
<recur-portal
api-endpoint="/api/portal/create"
customer-id="cus_xxxxx">
管理訂閱
</recur-portal>使用 api-endpoint 模式時,組件會在點擊後 POST 到指定端點,期望回應 { url: "..." } 格式。
屬性說明
| 屬性 | 必填 | 說明 |
|---|---|---|
portal-url | ❌* | 直接提供 Portal URL |
api-endpoint | ❌* | 後端 API 端點(用於動態建立 Session) |
customer-id | ❌ | 客戶 ID(傳送給 API) |
return-url | ❌ | 返回 URL(傳送給 API) |
button-text | ❌ | 按鈕文字(預設:插槽內容或「管理訂閱」) |
button-style | ❌ | 按鈕樣式:primary、outline、gradient、link |
target | ❌ | 設為 _blank 在新視窗開啟 |
disabled | ❌ | 禁用按鈕 |
portal-url 和 api-endpoint 至少需要提供一個。
按鈕樣式
<!-- Primary(預設) -->
<recur-portal portal-url="..." button-style="primary">
管理訂閱
</recur-portal>
<!-- Outline -->
<recur-portal portal-url="..." button-style="outline">
管理訂閱
</recur-portal>
<!-- Gradient -->
<recur-portal portal-url="..." button-style="gradient">
管理訂閱
</recur-portal>
<!-- Link -->
<recur-portal portal-url="..." button-style="link">
管理訂閱
</recur-portal>事件處理
portal-redirect
在導向 Portal 前觸發:
document.querySelector('recur-portal').addEventListener('portal-redirect', (event) => {
console.log('Redirecting to:', event.detail.url);
// 可用於追蹤分析
analytics.track('portal_opened');
});portal-error
發生錯誤時觸發:
document.querySelector('recur-portal').addEventListener('portal-error', (event) => {
console.error('Portal error:', event.detail.message);
// 顯示錯誤訊息給用戶
alert('無法開啟訂閱管理頁面,請稍後再試');
});React 整合
使用 Web Component
// components/PortalButton.tsx
'use client';
import { useEffect, useRef } from 'react';
import 'recur-tw';
interface PortalButtonProps {
portalUrl?: string;
apiEndpoint?: string;
customerId?: string;
}
export function PortalButton({ portalUrl, apiEndpoint, customerId }: PortalButtonProps) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const handleError = (e: Event) => {
const { message } = (e as CustomEvent).detail;
console.error('Portal error:', message);
};
element.addEventListener('portal-error', handleError);
return () => element.removeEventListener('portal-error', handleError);
}, []);
return (
<recur-portal
ref={ref}
portal-url={portalUrl}
api-endpoint={apiEndpoint}
customer-id={customerId}
>
管理訂閱
</recur-portal>
);
}使用自訂按鈕
如果您想使用自己的 UI 元件,可以直接呼叫 SDK 方法:
'use client';
import { useState } from 'react';
import Recur from 'recur-tw';
export function CustomPortalButton({ customerId }: { customerId: string }) {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
try {
const recur = Recur.init({ publishableKey: 'pk_xxx' });
const session = await recur.createPortalSession('/api/portal/create', {
customerId,
});
window.location.href = session.url;
} catch (error) {
console.error('Failed to open portal:', error);
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleClick}
disabled={loading}
className="btn btn-primary"
>
{loading ? '載入中...' : '管理訂閱'}
</button>
);
}Vue 整合
<template>
<recur-portal
:portal-url="portalUrl"
@portal-redirect="onRedirect"
@portal-error="onError"
>
管理訂閱
</recur-portal>
</template>
<script setup lang="ts">
import 'recur-tw';
defineProps<{
portalUrl: string;
}>();
const onRedirect = (event: CustomEvent) => {
console.log('Redirecting to:', event.detail.url);
};
const onError = (event: CustomEvent) => {
console.error('Portal error:', event.detail.message);
};
</script>後端 API 範例
當使用 api-endpoint 模式時,您需要建立對應的後端 API:
// app/api/portal/create/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Recur from 'recur-tw/server';
import { auth } from '@/lib/auth';
const recur = new Recur({
secretKey: process.env.RECUR_SECRET_KEY!,
});
export async function POST(request: NextRequest) {
// 驗證用戶
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: { message: 'Unauthorized' } }, { status: 401 });
}
// 從請求中取得參數(可選)
const body = await request.json().catch(() => ({}));
// 建立 Portal Session
const portalSession = await recur.portal.sessions.create({
customer: session.user.recurCustomerId,
returnUrl: body.returnUrl || `${process.env.NEXT_PUBLIC_URL}/account`,
});
// 返回 URL(組件期望的格式)
return NextResponse.json({
url: portalSession.url,
});
}TypeScript 支援
// 宣告 Web Component 類型
declare global {
namespace JSX {
interface IntrinsicElements {
'recur-portal': React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement> & {
'portal-url'?: string;
'api-endpoint'?: string;
'customer-id'?: string;
'return-url'?: string;
'button-text'?: string;
'button-style'?: 'primary' | 'outline' | 'gradient' | 'link';
'target'?: string;
'disabled'?: boolean;
},
HTMLElement
>;
}
}
}下一步
- 後端整合 - Server SDK 詳細說明
- Portal API 參考 - 完整 API 文件
- Webhook 整合 - 接收訂閱變更通知