# 基本結帳範例 (/guides/examples/basic-checkout)





基本結帳範例 [#基本結帳範例]

這個範例展示如何快速整合 Recur SDK（0.4.4+）來建立基本的訂閱結帳流程。

<Callout type="info">
  **提示**：`customerEmail` 和 `customerName` 都是選填。如果未預填，用戶會在結帳頁面輸入（Email 在結帳時為必填，用於寄送收據）。
</Callout>

完整範例程式碼 [#完整範例程式碼]

<Tabs items="['Vanilla JS', 'React']">
  <Tab value="Vanilla JS">
    ```javascript
    // 1. 初始化 SDK
    const recur = RecurCheckout.init({
      publishableKey: 'pk_test_xxx', // 您的 Publishable Key
    });

    // 2. 建立結帳按鈕並綁定事件
    const checkoutButton = document.getElementById('checkout-button');

    checkoutButton.addEventListener('click', async () => {
      try {
        // 3. 開啟結帳視窗
        await recur.checkout({
          productId: 'prod_xxx',             // 產品 ID（或使用 productSlug）
          customerEmail: 'user@example.com', // 選填：預填 Email
          customerName: '使用者名稱',         // 選填：預填姓名
          externalId: 'user_123',            // 選填：連結您系統的用戶
          mode: 'modal',
          onSuccess: (result) => {
            console.log('Checkout session 建立成功:', result);
          },
          onPaymentComplete: (result) => {
            console.log('付款完成:', result);
            window.location.href = '/subscription/success';
          },
          onPaymentFailed: (error) => {
            // 可選：自訂付款失敗處理
            // 回傳 undefined 使用 SDK 預設行為（顯示錯誤訊息並允許重試）
            console.log('付款失敗:', error.details?.failure_code);

            // 或自訂處理邏輯
            if (error.details?.failure_code === 'INSUFFICIENT_FUNDS') {
              return {
                action: 'custom',
                customTitle: '餘額不足',
                customMessage: '請使用其他付款方式或聯繫客服',
              };
            }
          },
          onError: (error) => {
            console.error('結帳錯誤:', error);
            alert('結帳時發生錯誤，請稍後再試');
          },
          onClose: () => {
            console.log('使用者關閉結帳視窗');
          },
        });
      } catch (error) {
        console.error('開啟結帳視窗時發生錯誤:', error);
      }
    });
    ```
  </Tab>

  <Tab value="React">
    ```tsx
    import { RecurCheckout } from 'recur-tw';
    import { useState, useCallback } from 'react';

    export function SubscriptionButton({ productId }: { productId: string }) {
      const [isLoading, setIsLoading] = useState(false);

      const handleSubscribe = useCallback(async () => {
        setIsLoading(true);

        const recur = RecurCheckout.init({
          publishableKey: process.env.NEXT_PUBLIC_RECUR_PUBLISHABLE_KEY!,
        });

        try {
          await recur.checkout({
            productId,                         // 產品 ID（或使用 productSlug）
            customerEmail: 'user@example.com', // 選填：預填 Email
            customerName: '使用者名稱',         // 選填：預填姓名
            mode: 'modal',
            onPaymentComplete: (result) => {
              console.log('付款完成:', result);
              window.location.href = '/subscription/success';
            },
            onPaymentFailed: (error) => {
              // 可選：自訂付款失敗處理
              console.log('付款失敗:', error.details?.failure_code);
              // 回傳 undefined 使用預設行為
            },
            onError: (error) => {
              console.error('結帳錯誤:', error);
              alert('結帳時發生錯誤，請稍後再試');
              setIsLoading(false);
            },
            onClose: () => {
              setIsLoading(false);
            },
          });
        } catch (error) {
          console.error('開啟結帳視窗時發生錯誤:', error);
          setIsLoading(false);
        }
      }, [productId]);

      return (
        <button
          onClick={handleSubscribe}
          disabled={isLoading}
          className="subscribe-button"
        >
          {isLoading ? '處理中...' : '立即訂閱'}
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

HTML 結構 [#html-結構]

```html
<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Recur SDK - 基本結帳範例</title>
</head>
<body>
  <div class="container">
    <h1>訂閱我們的服務</h1>
    <p>選擇您想要的訂閱方案</p>

    <div class="plan-card">
      <h2>專業方案</h2>
      <p class="price">NT$ 999 / 月</p>
      <ul>
        <li>功能 A</li>
        <li>功能 B</li>
        <li>功能 C</li>
      </ul>
      <button id="checkout-button">立即訂閱</button>
    </div>
  </div>

  <!-- 載入 Recur SDK -->
  <script src="https://unpkg.com/recur-tw@latest/dist/recur.umd.js"></script>
  <!-- 你的應用程式腳本 -->
  <script src="app.js"></script>
</body>
</html>
```

步驟說明 [#步驟說明]

1\. 安裝 SDK [#1-安裝-sdk]

<Tabs items="['NPM', 'CDN']">
  <Tab value="NPM">
    ```bash
    npm install recur-tw
    # 或
    yarn add recur-tw
    # 或
    pnpm add recur-tw
    ```
  </Tab>

  <Tab value="CDN">
    ```html
    <script src="https://unpkg.com/recur-tw@latest/dist/recur.umd.js"></script>
    ```
  </Tab>
</Tabs>

2\. 取得必要的資訊 [#2-取得必要的資訊]

你需要以下資訊：

* **Publishable Key**：在 [Recur 儀表板](https://app.recur.tw) → 設定 → 開發者 → API 金鑰
* **Product ID**：在儀表板的「商品管理」中建立並取得

3\. 初始化 SDK [#3-初始化-sdk]

使用你的 Publishable Key 初始化 SDK：

```javascript
const recur = RecurCheckout.init({
  publishableKey: 'pk_test_xxx',
});
```

4\. 開啟結帳視窗 [#4-開啟結帳視窗]

呼叫 `recur.checkout()` 並傳入參數：

| 參數              | 必填  | 說明                       |
| --------------- | --- | ------------------------ |
| `productId`     | ✅\* | 產品 ID（與 productSlug 二擇一） |
| `productSlug`   | ✅\* | 產品 Slug（與 productId 二擇一） |
| `customerEmail` | ❌   | 預填客戶 Email（結帳時必填）        |
| `customerName`  | ❌   | 預填客戶姓名                   |
| `externalId`    | ❌   | 外部客戶 ID                  |
| `mode`          | ❌   | `'modal'` 或 `'iframe'`   |

回調函數 [#回調函數]

| 回調                  | 說明                       |
| ------------------- | ------------------------ |
| `onSuccess`         | Checkout session 建立成功時呼叫 |
| `onPaymentComplete` | 付款成功完成時呼叫                |
| `onPaymentFailed`   | 付款失敗時呼叫（可自訂處理邏輯）         |
| `onError`           | 結帳流程發生錯誤時呼叫              |
| `onClose`           | 使用者關閉結帳視窗時呼叫             |

<Callout type="info">
  **`onPaymentFailed` vs `onError`**：`onPaymentFailed` 專門處理「用戶送出付款後」的失敗（如卡片被拒絕），而 `onError` 處理其他錯誤（如網路問題、API 錯誤）。詳見[錯誤處理指南](/guides/checkout/error-handling)。
</Callout>

重點提醒 [#重點提醒]

<Callout type="info">
  **測試模式**：開發時請使用測試環境的 API Key（`pk_test_xxx`），避免產生實際交易。
</Callout>

<Callout type="warning">
  **安全性**：Publishable Key 可以在前端使用，但 Secret Key 必須保持在後端。
</Callout>

常見問題 [#常見問題]

Q: customerEmail 和 customerName 是必填嗎？ [#q-customeremail-和-customername-是必填嗎]

A: 兩者在建立 checkout session 時都是**選填**的。如果未預填，用戶會在結帳頁面輸入。但 Email 在結帳時是必填的（用於寄送收據）。

Q: 如何處理使用者取消訂閱？ [#q-如何處理使用者取消訂閱]

A: 使用 `onClose` 回調函數來處理使用者關閉結帳視窗的情況。

Q: 可以自訂結帳視窗的樣式嗎？ [#q-可以自訂結帳視窗的樣式嗎]

A: 可以！請參考 [自訂樣式範例](/guides/examples/custom-styling)。

Q: 如何驗證訂閱是否成功？ [#q-如何驗證訂閱是否成功]

A: 建議在後端透過 Webhook 來驗證訂閱狀態，詳見 [Webhook 處理範例](/guides/examples/webhook-handling)。

Q: 如何自訂付款失敗的處理方式？ [#q-如何自訂付款失敗的處理方式]

A: 使用 `onPaymentFailed` 回調函數可以根據不同的失敗原因自訂處理邏輯。詳見[錯誤處理指南](/guides/checkout/error-handling)。

下一步 [#下一步]

* [錯誤處理](/guides/checkout/error-handling) - 深入了解付款錯誤處理
* [自訂樣式](/guides/examples/custom-styling) - 學習如何自訂樣式
* [Webhook 通知](/guides/webhooks) - 設定即時付款通知
* [API 參考](/api) - 查看完整的 API 文件
