<!-- Source: https://docs.infraio.xyz/zh-TW/sdks/javascript -->
<!-- Last updated: 2026-10-04 -->

# JavaScript / 瀏覽器 SDK

`@lartech/infraio-checkout-js` 是我們目前唯一發佈的 SDK。它在瀏覽器中
執行並開啟我們託管的結帳頁。目前沒有
後端 SDK。請直接呼叫 API;[Quickstart](https://docs.infraio.xyz/zh-TW/get-started/quickstart)
包含 HMAC 簽章輔助函式。

- 目前版本:`0.1.1-beta.17`(pre-1.0;可能有 minor break)
- 格式:**ESM**(`index.js`)、**CJS**(`index.cjs`)、**IIFE**(`index.global.js`)
- 內建型別(`index.d.ts`)
- 無 runtime peer 相依 — 不需要 React、jQuery 或其他套件

> **Note:**
>
> **沒有伺服端入口**。Webhook 簽章驗證輔助函式並未打包進來 — 請以
> `crypto` 自行實作([簽章驗證頁面](https://docs.infraio.xyz/zh-TW/webhooks/signature-verification)
> 有 4 種語言的可直接複製貼上的程式碼)。

## 安裝

**npm**

```bash
npm install @lartech/infraio-checkout-js
```

**pnpm**

```bash
pnpm add @lartech/infraio-checkout-js
```

**yarn**

```bash
yarn add @lartech/infraio-checkout-js
```

**bun**

```bash
bun add @lartech/infraio-checkout-js
```

**CDN**

```html
<script src="https://unpkg.com/@lartech/infraio-checkout-js/dist/index.global.js"></script>
<script>
  const sdk = await InfraIo.loadInfraIo("pk_live_yourkeyhere");
  sdk.checkout({ sessionId, checkoutUrl });
</script>
```

## `loadInfraIo(publicKey, options?)`

回傳 `Promise<InfraIoInstance>`。

```ts
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo("pk_live_yourkeyhere", {
  // 選填。僅在指向非 production 環境時覆寫。
  checkoutUrl: "https://checkout-dev.infraio.xyz",
});
```

| 參數 | 型別 | 必填 | 說明 |
| --- | --- | --- | --- |
| `publicKey` | `string` | ✓ | 必須符合 `pk_(live\|test)_…` |
| `options.checkoutUrl` | `string` | — | 覆寫結帳基礎 URL。預設:`https://checkout.infraio.xyz`。結帳頁面會自動連線到對應的 API。 |

## `sdk.checkout({ … })`

開啟託管結帳頁。回傳 `void`(以 callback 處理狀態)。

| 欄位 | 型別 | 必填 | 說明 |
| --- | --- | --- | --- |
| `sessionId` | `string` | ✓ | `POST /b2b/v1/checkout-sessions/quick` 回傳的 `session_key` |
| `checkoutUrl` | `string` | — | 同一 endpoint 回傳的完整 URL。若省略，SDK 會以 `loadInfraIo()` 的 `checkoutUrl`(或預設值)+ `sessionId` 組合 |
| `mode` | `"popup" \| "redirect" \| "embed"` | — | 預設 `"popup"` |
| `container` | `string \| HTMLElement` | 僅 embed | iframe 要掛載到的 CSS 選擇器或 DOM 元素 |
| `width` | `number` | — | 僅 popup。預設 `560`。範圍夾在 `[320, 1280]` |
| `height` | `number` | — | 僅 popup。預設 `780`。範圍夾在 `[400, 1000]` |
| `timeoutMs` | `number` | — | 僅 popup。Iframe 載入逾時。預設 `30000`。傳 `0` 可停用 |
| `locale` | `string` | — | BCP-47 標籤，以 `?locale=` 傳給結帳頁(`en`、`vi`、`ja`、`ko`、`es`、`pt-BR`、`ru`、`tr`、`zh-CN`、`zh-TW`) |
| `hideSummary` | `boolean` | — | 隱藏訂單摘要欄。預設 `false` |
| `hideHeader` | `boolean` | — | 隱藏 InfraIO Pay header 與內建的錢包連線按鈕。預設 `false`。搭配 `walletAddress` 可達成完整白牌 |
| `walletAddress` | `string` | — | 預先連接買家錢包。需要搭配 `onSignRequest` |
| `walletChainId` | `number` | — | 預連接錢包的 EVM chain ID |
| `onReady` | `() => void` | — | 當 iframe 變成可互動時觸發。僅 popup/embed |
| `onSignRequest` | `(req: { method: string; params: unknown[] }) => Promise<string>` | 設定 `walletAddress` 時必填 | SDK 把錢包 RPC 代理給你的 handler;回傳已簽章的 hex |
| `onSuccess` | `({ sessionId }) => void` | — | 付款成功時觸發。**並非權威 — webhook 才是** |
| `onCancel` | `() => void` | — | 買家在未付款的情況下關閉 popup/embed 時觸發 |
| `onError` | `(err: InfraIoError) => void` | — | 在 iframe 載入失敗時觸發(popup + embed 模式)。參數無效會同步 throw,**不會**透過此處傳遞。Redirect 模式沒有 runtime error 介面 — 失敗會在重導後的頁面中觀察到。 |

> **Warning:**
>
> `onSuccess` **不是**權威訊號。即使 webhook 之後判定付款失敗
> (testnet 重組、買家端時序問題),它仍可能觸發。請僅用於 UX
> (顯示「謝謝!」、轉址)。履約前一定要透過 webhook 確認。

## `sdk.close()`

以程式方式關閉已開啟的 popup 或 embed。redirect 模式下為 no-op。

```ts
sdk.checkout({ sessionId, mode: "popup", /* … */ });
// 之後,例如當使用者離開頁面時
sdk.close();
```

## `sdk.openRefundRequest({ … })`

開啟託管的**退款申請表單**,使用你的後端透過
`POST /b2b/v1/merchants/{merchant_id}/refund-requests` 鑄造的一次性
token。買家在我們的頁面填入退款目的地位址 + 原因 +(選填)metadata;
你的頁面只負責處理開啟/關閉生命週期。回傳一個 **`close()` 函式** —
呼叫它可以程式化關閉 popup 或卸載 embed iframe。在 redirect 模式下
回傳的函式為 no-op。

> **Note:**
>
> 表單位於 `https://checkout.infraio.xyz/refund-request/:token`。此方法
> 只是把該 URL 包裝成 popup / redirect / embed,讓買家不必離開你的網域
> (popup / embed),或讓他自動返回(redirect)。後端用來鑄造 token 的
> endpoint 是 `POST /b2b/v1/merchants/{merchant_id}/refund-requests` —
> 以你的 secret key 做 HMAC 簽章，跟其他 B2B 接口使用相同的驗證機制。
> 請參考[概念 → 退款](https://docs.infraio.xyz/zh-TW/concepts/refunds#mint-via-b2b-api)。

```ts
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo("pk_live_yourkeyhere");

// 在伺服端鑄造 token,再交給瀏覽器端的 SDK。
const { token } = await fetch("/api/mint-refund-token", { method: "POST" }).then(r => r.json());

const close = sdk.openRefundRequest({
  token,
  mode: "popup",
  onSuccess: ({ linkToken, refundId }) => {
    // 買家送出表單。
    // linkToken → /r/:linkToken 狀態頁面(分享給買家)。
    // refundId  → 搭配 B2B API 來核准 / 駁回。
    window.location.href = `/r/${linkToken}`;
  },
  onCancel: () => { /* 買家未送出就關閉 */ },
});

// 之後如有需要可程式化關閉 popup:
// close();
```

| 欄位 | 型別 | 必填 | 說明 |
| --- | --- | --- | --- |
| `token` | `string` | ✓ | `POST /b2b/v1/merchants/{merchant_id}/refund-requests` 回傳的 `rfqt_…` token |
| `mode` | `"popup" \| "redirect" \| "embed"` | — | 預設 `"popup"`。介面語意與 `sdk.checkout()` 相同 — 參見[模式說明](#mode-notes) |
| `container` | `string \| HTMLElement` | 僅 embed | iframe 要掛載到的 CSS 選擇器或 DOM 元素 |
| `locale` | `string` | — | BCP-47 標籤，以 `?locale=` 傳遞(`en`、`vi`、`ja`、`ko`、`es`、`pt-BR`、`ru`、`tr`、`zh-CN`、`zh-TW`) |
| `hideHeader` | `boolean` | — | 隱藏 iframe 內的 InfraIO Pay header。popup/embed 中 SDK 會自繪 modal 外觀，頁面 header 通常不需要。預設 `false` |
| `hideSummary` | `boolean` | — | 隱藏「訂單摘要」欄，僅顯示退款表單。預設 `false` |
| `walletAddress` | `string` | — | 預填目的地錢包欄位(`?wallet_address=`)。讓已知道買家錢包的商家可跳過手動輸入 |
| `onSuccess` | `(data: { linkToken: string; refundId: string }) => void` | — | 買家送出表單後觸發。`linkToken` → `/r/:linkToken` 狀態頁，可分享給買家。`refundId` → 用於 B2B API 核准 / 駁回 |
| `onCancel` | `() => void` | — | 買家未送出就關閉 popup/embed 時觸發 |
| `onError` | `(err: InfraIoError) => void` | — | iframe 載入失敗或參數無效時觸發。Token 過期 / 取消由託管頁面處理，不會透過 `onError` 通知 |

**透過 `onError` 暴露的 Token 狀態**

若買家開啟一個已過期的 token,頁面本身會處理顯示(渲染「已過期 —
申請新連結」提示等),SDK **不會**為這些情境觸發 `onError` — 買家
正在表單流程中，你的程式碼不需要反應。`onError` 只在你的程式碼
可以採取行動時觸發(錯誤參數、載入 iframe 的網路失敗)。

> **Warning:**
>
> `openRefundRequest` 記錄退款意圖 — 它**不會**移動資金。`onSuccess`
> 之後，退款列為 `PENDING`(或若你的商家設定為自動核准客戶退款，則為
> `APPROVED`)。你仍需用你的資金庫錢包簽章並廣播鏈上轉帳，然後將
> tx hash 送到 `POST /b2b/v1/refunds/:id/submit-tx`。完整生命週期請見
> [概念 → 退款](https://docs.infraio.xyz/zh-TW/concepts/refunds)。

## Error class

```ts
import { InfraIoError } from "@lartech/infraio-checkout-js";

sdk.checkout({
  sessionId,
  onError: (err: InfraIoError) => {
    switch (err.code) {
      case "invalid_request_error":   /* sessionId / 參數錯誤 */ break;
      case "iframe_load_error":       /* iframe 載入失敗 */ break;
      case "iframe_timeout_error":    /* 超過 timeoutMs */ break;
      case "already_open_error":      /* 已有另一個結帳處於開啟狀態 */ break;
      case "network_error":           /* 與結帳 origin 通訊時的短暫網路問題 */ break;
      case "api_error":               /* SDK 發起的呼叫,後端回了非 2xx */ break;
    }
  },
});
```

`sdk.openRefundRequest()` 只會同步丟出 `invalid_request_error`(token /
參數遺失或無效)。`iframe_load_error`(退款申請 iframe 載入失敗)是
**透過 `onError` 非同步傳遞**,不會被 throw。它**不會**發出
`iframe_timeout_error` 或 `already_open_error` — 退款申請 popup 沒有
載入逾時，並允許多個並行 popup。

## Mode notes

### Popup
- 置中疊加層，搭配深色半透明背景
- `z-index: 2147483647`(int32 最大值)— 位於所有元素之上
- 開啟時鎖定 body 捲動;關閉時還原
- 關閉按鈕取得初始焦點;Tab 鍵被限制在 popup 內
- 可由以下方式關閉:關閉按鈕、Escape、點擊外部、`sdk.close()`。
  全部都會呼叫 `onCancel`
- 結帳頁可透過 `postMessage` 要求 resize — SDK 會將其夾在
  `width`/`height` 限制範圍內

### Redirect
- 透過 `window.location.href` 做硬轉址
- 自動附加 `?return_url=<current-page>`,讓買家回到他原本的位置。
  若 session 上的 `success_url` / `cancel_url` 已涵蓋此情境，
  往返流程會忽略 `return_url`

### Embed
- iframe 帶 `allow="payment; clipboard-write"`(HTML5 Feature Policy
  指令 — 不是 `sandbox` 屬性)。Iframe 由結帳 origin 提供，因此買家
  端的錢包 popup 與剪貼簿寫入無需額外 opt-in 即可運作。
- 容器寬度為 100%;高度透過 `INFRAIO_RESIZE` postMessage 自動調整，
  夾在 `[200, 2000]px` 範圍
- 除 iframe 邊界以外不做 CSS 隔離 — 你的父頁面樣式不會滲透進來
- 請務必接上 `onReady`,以便在結帳變成可互動時隱藏你自己的載入狀態

## TypeScript

所有型別都已打包。最常用的匯出:

```ts
import type {
  CheckoutOptions,
  RefundRequestOptions,
  LoadOptions,
  InfraIoInstance,
  InfraIoErrorCode,
} from "@lartech/infraio-checkout-js";
import { loadInfraIo, InfraIoError, VERSION } from "@lartech/infraio-checkout-js";
```

`VERSION` 是 SDK 自身的版本字串 — 在 bug 回報時很有用。
