Recur
開發者指南結帳整合

Embedded Checkout

在您的網站上嵌入支付表單,完全掌控 UI 體驗

Embedded Checkout

Embedded Checkout 讓您在自己的網站上嵌入支付表單,完全掌控使用者介面和體驗。

適合場景

  • 需要完全控制結帳 UI/UX
  • 想讓結帳流程無縫融入您的網站
  • 有前端開發資源
  • 需要高度客製化

運作流程

1. 用戶點擊「訂閱」按鈕

2. 前端呼叫您的後端 API

3. 後端呼叫 POST /v1/checkouts 取得 SDK Token

4. 前端使用 SDK Token 渲染支付表單

5. 用戶填寫卡號並提交

6. SDK 處理付款並回呼 onSuccess/onError

安裝 SDK

npm install recur-tw
yarn add recur-tw
pnpm add recur-tw
<script src="https://unpkg.com/recur-tw/dist/recur.umd.js"></script>

React 整合

1. 設定 Provider

// app/providers.tsx
'use client';

import { RecurProvider } from 'recur-tw';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <RecurProvider publishableKey={process.env.NEXT_PUBLIC_RECUR_PUBLISHABLE_KEY!}>
      {children}
    </RecurProvider>
  );
}

2. 建立訂閱按鈕

// components/SubscribeButton.tsx
'use client';

import { useSubscribe } from 'recur-tw';

export function SubscribeButton({ productId }: { productId: string }) {
  const { subscribe, isLoading, error } = useSubscribe({
    onSuccess: (result) => {
      console.log('訂閱成功!', result);
      window.location.href = '/success';
    },
    onError: (error) => {
      console.error('訂閱失敗:', error.message);
    },
  });

  return (
    <button
      onClick={() => subscribe({
        productId,
        customerEmail: 'user@example.com',
        customerName: '王小明',
      })}
      disabled={isLoading}
    >
      {isLoading ? '處理中...' : '立即訂閱'}
    </button>
  );
}

Vanilla JavaScript 整合

<div id="checkout-container"></div>

<script src="https://unpkg.com/recur-tw/dist/recur.umd.js"></script>
<script>
  const recur = RecurCheckout.init({
    publishableKey: 'pk_test_xxx',
  });

  recur.createEmbeddedCheckout({
    productId: 'prod_xxx',
    container: '#checkout-container',
    customerEmail: 'user@example.com',
    onSuccess: (result) => {
      console.log('成功!', result);
    },
    onError: (error) => {
      alert('錯誤:' + error.message);
    },
  });
</script>

本地開發

重要:Localhost 限制

PAYUNi 金流不支援 localhost 作為嵌入式結帳的 domain。這是因為 PAYUNi SDK 需要驗證請求來源的安全性。

解決方案

有三種方式可以在本地開發時測試 Embedded Checkout:

方案一:使用 Hosted Checkout(推薦)

在本地開發時使用 Hosted Checkout 模式。建立 Checkout Session 後會取得一個 url 欄位,直接導向該 URL 即可完成測試。

// 建立 Checkout Session
const response = await fetch('/api/create-checkout', {
  method: 'POST',
  body: JSON.stringify({ productId: 'prod_xxx' }),
});

const { url } = await response.json();

// 導向 Recur 託管的結帳頁面(在 localhost 也能正常運作)
window.location.href = url;

Hosted Checkout 頁面位於 checkout.recur.tw,不受 localhost 限制。

方案二:使用 Tunneling 服務

使用 ngrok、Cloudflare Tunnel 或 localtunnel 等服務,將您的本地伺服器暴露到公開網路:

使用 ngrok:

# 安裝 ngrok
npm install -g ngrok

# 啟動 tunnel(假設您的開發伺服器在 port 5173)
ngrok http 5173

ngrok 會提供一個公開 URL(如 https://abc123.ngrok.io),使用這個 URL 存取您的應用程式即可正常使用 Embedded Checkout。

使用 Cloudflare Tunnel:

# 安裝 cloudflared
brew install cloudflared

# 啟動 tunnel
cloudflared tunnel --url http://localhost:5173

方案三:部署到 Staging 環境

將您的應用程式部署到有正式 domain 的 staging 環境進行測試:

  • Vercel: your-app.vercel.app
  • Netlify: your-app.netlify.app
  • Railway: your-app.up.railway.app

這些平台都提供免費的子網域,可以正常使用 Embedded Checkout。

錯誤訊息說明

當您在 localhost 使用 Embedded Checkout 時,可能會看到以下錯誤:

{
  "error": {
    "code": "localhost_not_supported",
    "message": "Embedded checkout is not supported on localhost"
  }
}

這表示 PAYUNi 拒絕了來自 localhost 的請求。請使用上述三種解決方案之一。

測試模式

使用 pk_test_* 開頭的 Publishable Key 時,SDK 會自動進入測試模式。在測試模式下:

  • 支付表單頂部會顯示「🧪 測試模式」提示
  • 可以展開查看測試卡號
  • 不會產生真實交易

測試卡號

卡號說明
4147-6310-0000-0001VISA 測試卡(授權成功)
3560-5110-0000-0001JCB 測試卡(授權成功)
4147-6310-0000-0002VISA 測試卡(3D 驗證失敗)
3560-5110-0000-0002JCB 測試卡(3D 驗證失敗)
  • 有效期:任意未過期日期
  • CVV(背面末三碼):任意 3 位數

API 參考

POST /v1/checkouts

建立 Checkout 並取得 SDK Token。

請求參數:

參數必填說明
productId商品 ID
customerEmail客戶 Email(結帳時必填)
customerName客戶姓名(結帳時必填)

回應:

{
  "checkout": {
    "id": "pi_xxx",
    "client_secret": "pi_xxx_secret_xxx",
    "status": "requires_payment_method",
    "amount": 990,
    "currency": "TWD"
  },
  "product": {
    "id": "prod_xxx",
    "name": "Pro 方案",
    "price": 990,
    "interval": "month"
  },
  "sdk_token": "xxx",
  "sdk_token_expires_at": "2024-01-01T12:30:00Z"
}

下一步

On this page