recur-tw
v0.14.0
Published
React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)
Maintainers
Readme
Recur SDK
台灣訂閱制金流 SDK - 透過 PAYUNi 金流處理訂閱式與一次性付款。
A React & Vanilla JS SDK for embedding subscription checkout flows with PAYUNi payment integration.
Features
- Zero-config 嵌入 - 一行程式碼即可嵌入付款按鈕
- React SDK - 完整 React 整合 (
useProducts,useRecur) - Vanilla JS - 純 JavaScript,無框架依賴
- Web Components - 現代化、封裝良好的 UI 元件
- TypeScript - 完整型別定義
- SSR Safe - 支援 Next.js、Remix 等 SSR 框架
快速開始
方式一:Checkout Button(最簡單)
只需一個 script 和一個連結,點擊即開啟結帳視窗:
<script src="https://unpkg.com/recur-tw/dist/checkout.js"></script>
<a class="recur-button"
href="https://recur.tw/buy/prod_xxx"
data-key="pk_live_xxx">
訂閱方案
</a>點擊連結會開啟 modal 結帳視窗,完成後觸發事件:
window.addEventListener('recur:success', (e) => {
console.log('付款成功!', e.detail);
});
window.addEventListener('recur:error', (e) => {
console.error('付款失敗', e.detail);
});
window.addEventListener('recur:close', () => {
console.log('使用者關閉視窗');
});JavaScript API:
// 程式控制開啟結帳
Recur.popup({
productId: 'prod_xxx',
key: 'pk_live_xxx',
email: '[email protected]',
name: '王小明'
});
// 關閉結帳視窗
Recur.close();Data 屬性:
| 屬性 | 說明 |
|------|------|
| data-key | Publishable Key(必填) |
| data-email | 預填 Email |
| data-name | 預填姓名 |
方式二:Floating Widget(浮動按鈕)
在頁面右下角顯示浮動按鈕,適合部落格或內容網站:
<script
src="https://unpkg.com/recur-tw/dist/widget.js"
data-key="pk_live_xxx"
data-product="prod_xxx"
data-text="訂閱支持"
data-color="#667eea"
data-position="right"
></script>Data 屬性:
| 屬性 | 說明 | 預設值 |
|------|------|--------|
| data-key | Publishable Key(必填) | - |
| data-product | 商品 ID(必填) | - |
| data-text | 按鈕文字 | 訂閱支持 |
| data-color | 按鈕顏色 | #667eea |
| data-text-color | 文字顏色 | #ffffff |
| data-position | 位置 (left/right) | right |
| data-x-margin | 水平邊距 (px) | 18 |
| data-y-margin | 垂直邊距 (px) | 18 |
JavaScript API:
RecurWidget.open(); // 開啟結帳視窗
RecurWidget.close(); // 關閉結帳視窗
RecurWidget.show(); // 顯示浮動按鈕
RecurWidget.hide(); // 隱藏浮動按鈕
RecurWidget.destroy(); // 移除 Widget方式三:Vanilla JavaScript
適合需要更多控制的場景:
<script src="https://unpkg.com/recur-tw/dist/recur.umd.js"></script>
<script>
const recur = RecurCheckout.init({
publishableKey: 'pk_live_xxx'
});
// 取得商品列表
const { products } = await recur.fetchProducts();
// 開啟結帳(Modal 模式)
await recur.checkout({
productId: 'prod_xxx',
customerEmail: '[email protected]',
customerName: '王小明',
mode: 'modal',
onPaymentComplete: (result) => {
console.log('付款成功!', result);
},
onError: (error) => {
console.error('付款失敗', error);
}
});
// 或重導到 Hosted Checkout
await recur.redirectToCheckout({
productId: 'prod_xxx',
successUrl: 'https://yoursite.com/success',
cancelUrl: 'https://yoursite.com/cancel'
});
</script>Checkout 模式:
| 模式 | 說明 |
|------|------|
| modal | 在 Modal 中開啟付款表單(預設) |
| iframe | 嵌入到指定容器 |
| redirect | 重導到 Hosted Checkout 頁面 |
方式四:React / Next.js
npm install recur-tw'use client';
import { RecurProvider, useProducts, useRecur } from 'recur-tw';
// 1. 包裝 Provider
export default function App() {
return (
<RecurProvider config={{ publishableKey: 'pk_live_xxx' }}>
<ProductsPage />
</RecurProvider>
);
}
// 2. 使用 Hooks
function ProductsPage() {
const { data: products, isLoading } = useProducts();
const { checkout, isCheckingOut } = useRecur();
if (isLoading) return <div>載入中...</div>;
return (
<div>
{products?.map((product) => (
<button
key={product.id}
onClick={() => checkout({
productId: product.id,
customerEmail: '[email protected]',
mode: 'modal'
})}
disabled={isCheckingOut}
>
{product.name} - NT${product.price}
</button>
))}
</div>
);
}Hooks:
useProducts(options?)- 取得商品列表useRecur()- 取得 checkout 函式與狀態useCustomer()- 檢查客戶權限與訂閱狀態
// 篩選商品類型
const { data: subscriptions } = useProducts({ type: 'SUBSCRIPTION' });
const { data: oneTimeProducts } = useProducts({ type: 'ONE_TIME' });權限檢查(useCustomer)
檢查客戶是否有特定產品的訂閱權限,無需自建資料庫或處理 Webhook:
import { RecurProvider, useCustomer } from 'recur-tw';
// 1. Provider 需傳入 customer 識別資訊
function App() {
const user = useAuth(); // 你的認證系統
return (
<RecurProvider
config={{ publishableKey: 'pk_live_xxx' }}
customer={{ email: user?.email }}
>
<PremiumFeature />
</RecurProvider>
);
}
// 2. 使用 check() 檢查權限
function PremiumFeature() {
const { check, isLoading } = useCustomer();
if (isLoading) return <Spinner />;
const { allowed } = check({ product: 'pro-plan' });
if (!allowed) {
return <UpgradePrompt />;
}
return <PremiumContent />;
}
// 3. 顯示訂閱狀態
function AccountPage() {
const { customer, subscription, entitlements } = useCustomer();
return (
<div>
<p>方案:{subscription?.product.name}</p>
<p>到期日:{subscription?.currentPeriodEnd}</p>
<p>擁有權限:{entitlements.map(e => e.product).join(', ')}</p>
</div>
);
}安全提醒:
check()從本地快取讀取,可被繞過。僅用於 UI 控制,敏感操作請在後端驗證。
方式五:<recur-checkout> Web Component
零 JavaScript 的結帳按鈕:
<script src="https://unpkg.com/recur-tw/dist/recur.umd.js"></script>
<recur-checkout
publishable-key="pk_live_xxx"
product-id="prod_xxx"
success-url="/success"
cancel-url="/cancel"
button-text="立即訂閱"
button-style="gradient">
</recur-checkout>Server SDK
後端驗證與管理:
import { Recur } from 'recur-tw/server';
const recur = new Recur(process.env.RECUR_SECRET_KEY!);
// 建立 Portal Session(讓客戶管理訂閱)
const session = await recur.portal.sessions.create({
customer: 'cus_xxx',
returnUrl: 'https://yoursite.com/account'
});
// 重導客戶到 Portal
res.redirect(session.url);權限檢查(Server-side)
在 API 路由中驗證客戶權限,確保安全:
import { Recur } from 'recur-tw/server';
const recur = new Recur(process.env.RECUR_SECRET_KEY!);
// API 路由範例
export async function GET(request: Request) {
const user = await getUser(request);
// 檢查單一產品權限
const { allowed } = await recur.entitlements.check({
product: 'pro-plan',
customer: { email: user.email },
});
if (!allowed) {
return Response.json(
{ error: '請升級到 Pro 方案' },
{ status: 403 }
);
}
return Response.json(protectedData);
}
// 列出所有權限
const { entitlements } = await recur.entitlements.list({
customer: { email: '[email protected]' },
});
console.log(entitlements);
// [
// { product: 'pro-plan', status: 'active', subscriptionId: 'sub_xxx' },
// { product: 'addon-ai', status: 'active', subscriptionId: 'sub_yyy' },
// ]客戶識別方式(擇一):
// 方式一:Email(推薦)
customer: { email: '[email protected]' }
// 方式二:External ID(你的系統 User ID)
customer: { externalId: 'usr_12345' }
// 方式三:Recur Customer ID
customer: { id: 'cus_xxx' }API Key
- 前往 Recur Dashboard
- Settings → API Keys
- 建立 Publishable Key (
pk_*) 用於前端 - 建立 Secret Key (
sk_*) 用於後端
注意: 永遠不要在前端使用 Secret Key!
商品類型
| 類型 | 說明 |
|------|------|
| SUBSCRIPTION | 訂閱制商品(週期性扣款) |
| ONE_TIME | 一次性購買 |
| CREDITS | 點數/代幣包 |
| DONATION | 贊助/捐款 |
事件
所有嵌入方式都支援相同的事件:
// 付款成功
window.addEventListener('recur:success', (e) => {
console.log(e.detail); // { subscriptionId, orderId, ... }
});
// 付款失敗
window.addEventListener('recur:error', (e) => {
console.log(e.detail); // { message, code }
});
// 使用者關閉視窗
window.addEventListener('recur:close', () => {
console.log('Closed');
});TypeScript
import type {
RecurConfig,
Product,
ProductsResult,
CheckoutOptions,
CheckoutResult,
CheckoutError,
Subscription
} from 'recur-tw';CDN 連結
| 檔案 | 用途 | CDN |
|------|------|-----|
| checkout.js | Gumroad 風格按鈕 | https://unpkg.com/recur-tw/dist/checkout.js |
| widget.js | 浮動按鈕 Widget | https://unpkg.com/recur-tw/dist/widget.js |
| recur.umd.js | Vanilla JS 完整版 | https://unpkg.com/recur-tw/dist/recur.umd.js |
或使用 jsDelivr:
https://cdn.jsdelivr.net/npm/recur-tw/dist/checkout.js瀏覽器支援
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- 支援 Web Components 的行動瀏覽器
範例
查看 /examples 目錄:
checkout-button-test.html- Checkout Button 測試widget-test.html- Widget 測試
本地測試:
cd packages/recur-sdk
pnpm examplesLicense
可以:
- 在你的應用程式中免費使用此 SDK
- 修改程式碼供自己使用
- 分發修改後的版本(需附帶授權條款並標註修改)
不可以:
- 將此 SDK 作為託管/管理服務提供給第三方
- 移除或規避授權機制
- 移除版權聲明或授權資訊
Support
- Issues: https://github.com/kaikhq/recur.tw/issues
- Email: [email protected]
