npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

recur-tw

v0.14.0

Published

React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)

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

  1. 前往 Recur Dashboard
  2. SettingsAPI Keys
  3. 建立 Publishable Key (pk_*) 用於前端
  4. 建立 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 examples

License

Elastic License 2.0

可以:

  • 在你的應用程式中免費使用此 SDK
  • 修改程式碼供自己使用
  • 分發修改後的版本(需附帶授權條款並標註修改)

不可以:

  • 將此 SDK 作為託管/管理服務提供給第三方
  • 移除或規避授權機制
  • 移除版權聲明或授權資訊

Support