> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clinkbill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript SDK

> 在浏览器应用中通过跳转或嵌入方式拉起 Clink Checkout。

Clink JavaScript SDK 用于在浏览器应用中拉起 Clink Checkout，支持整页跳转和嵌入式 checkout 两种模式，并通过 publishable key 初始化。

<Warning>
  **安全提示：** 浏览器端请使用 **publishable key**。不要在客户端暴露 **Secret API keys**。
</Warning>

<Card title="NPM Package" icon="npm" href="https://www.npmjs.com/package/@clink-ai/clink-js">
  在 npmjs.com 查看该包
</Card>

<CardGroup cols={2}>
  <Card title="Create Checkout Session" icon="square-plus" href="/api-reference/endpoint/create-checkout-session">
    先在你的后端创建 checkout session，再拉起 checkout。
  </Card>

  <Card title="Checkout Session Guide" icon="banknote" href="/guides/payments/checkout_session">
    了解 hosted checkout 的完整工作流程。
  </Card>
</CardGroup>

## 安装

使用你偏好的包管理器从 npm 安装：

<CodeGroup>
  ```bash npm theme={null}
  npm install @clink-ai/clink-js
  ```

  ```bash yarn theme={null}
  yarn add @clink-ai/clink-js
  ```

  ```bash pnpm theme={null}
  pnpm add @clink-ai/clink-js
  ```
</CodeGroup>

如果你需要浏览器全局版本，该包也提供了 UMD bundle。你可以自托管 `dist/index.umd.js`，加载后通过 `window` 上的 `Clink.loadClink(...)` 使用。

## 初始化

使用 publishable key 初始化 SDK：

```ts theme={null}
import { loadClink } from '@clink-ai/clink-js';

const clink = await loadClink('pk_uat_xxxxxxxxx', {
  checkoutEnvironment: 'sandbox',
  locale: 'en-US',
});
```

支持的 publishable key 格式包括 `pk_test_*`、`pk_uat_*` 和 `pk_prod_*`。

如果你已经明确知道最终 checkout host，也可以直接传入 `checkoutBaseUrl` 跳过 bootstrap：

```ts theme={null}
import { loadClink } from '@clink-ai/clink-js';

const clink = await loadClink('pk_prod_xxxxxxxxx', {
  checkoutBaseUrl: 'https://checkout.clinkbill.com',
});
```

### 初始化参数

* `checkoutEnvironment`：`sandbox` 或 `production`。未传 `checkoutBaseUrl` 时会使用它
* `checkoutBaseUrl`：直接指定 checkout host。传入后会跳过 bootstrap
* `locale`：会透传给 bootstrap 请求
* `origin`：覆盖当前站点 origin
* `fetchImpl`：为非浏览器运行环境提供自定义 `fetch`

未传 `checkoutBaseUrl` 时，SDK 会按以下顺序确定 bootstrap 环境：

1. `checkoutEnvironment`
2. `CLINK_ENV`

其中 `CLINK_ENV=sandbox` 会映射到 `https://uat-api.clinkbill.com/api/sdk/bootstrap`，`CLINK_ENV=production` 会映射到 `https://api.clinkbill.com/api/sdk/bootstrap`。

## Redirect Checkout

如果你希望由 Clink 接管整页支付流程，可以使用 redirect 模式。

```ts theme={null}
import { loadClink } from '@clink-ai/clink-js';

const clink = await loadClink('pk_uat_xxxxxxxxx', {
  checkoutEnvironment: 'sandbox',
});

document
  .getElementById('checkout-button')
  ?.addEventListener('click', async () => {
    await clink.redirectToCheckout({
      // 当后端返回 opaque session token 时，优先使用 sessionParam
      sessionParam: 'sess_xxx#token_xxx',
      replace: false,
    });
  });
```

`redirectToCheckout` 支持以下参数：

* `sessionParam`：优先使用
* `sessionId`：只有 session ID 时可用
* `replace`：使用 `window.location.replace(...)` 而不是 `assign(...)`

如果同时传入 `sessionParam` 和 `sessionId`，会优先使用 `sessionParam`。

## Embedded Checkout

如果你希望支付流程停留在当前页面中，可以使用嵌入式 checkout。

```ts theme={null}
import { loadClink } from '@clink-ai/clink-js';

const clink = await loadClink('pk_uat_xxxxxxxxx', {
  checkoutEnvironment: 'sandbox',
});

const embedded = await clink.initEmbeddedCheckout({
  async fetchSession() {
    const response = await fetch('/api/clink/checkout-session', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        uiMode: 'elements',
        returnUrl: `${window.location.origin}/complete.html?session_id={ELEMENTS_SESSION_ID}`,
      }),
    });

    return await response.json();
    // {
    //   sessionId: 'sess_xxx',
    //   checkoutUrl: 'https://checkout.clinkbill.com/pay/sess_xxx%23token_xxx',
    //   orderId: 'ord_xxx'
    // }
  },
  onEvent(event) {
    console.log(event.type, event.payload);
  },
  async pollStatus({ sessionId, orderId, attempt }) {
    const response = await fetch(
      `/api/clink/checkout-status?sessionId=${sessionId}`,
    );
    const result = await response.json();

    if (result.state === 'pending' || result.state === 'payment') {
      return null;
    }

    return {
      state: result.state,
      payload: {
        orderId,
        attempt,
      },
    };
  },
});

embedded.mount('#clink-checkout');
```

`fetchSession` 必须在你的后端创建 checkout session，并返回最终的 checkout URL。对于嵌入式 checkout，创建 session 时需要传 `uiMode: 'elements'`。当 `uiMode` 为 `elements` 时，`returnUrl` 为必填，Clink 会在返回你的站点前将 `{ELEMENTS_SESSION_ID}` 替换为创建出的 session ID。SDK 会原样挂载返回的 URL，不会改写其中的 query 参数。

### 嵌入式参数

* `fetchSession`：必填。必须返回 `{ sessionId, checkoutUrl, orderId? }`
* `onEvent`：接收 checkout 生命周期事件
* `autoResize`：自动处理 iframe 高度变化。默认：`true`
* `autoDestroyOnComplete`：支付成功后自动销毁实例。默认：`true`
* `pollStatus`：可选的轮询函数，用于判断终态
* `pollIntervalMs`：轮询间隔，单位毫秒。默认：`2000`

### 嵌入式实例 API

* `mount(container)`：挂载到 CSS selector 或 `HTMLElement`
* `unmount()`：移除 iframe，但保留实例可复用
* `destroy()`：彻底销毁实例
* `on(type, handler)`：订阅指定事件
* `getState()`：返回 `{ mounted, destroyed }`

## 事件与状态

嵌入式 checkout 会触发以下事件：

| Event           | Description               |
| --------------- | ------------------------- |
| `ready`         | checkout iframe 已就绪或已完成加载 |
| `resize`        | iframe 请求更新高度             |
| `state_change`  | checkout 状态发生变化           |
| `complete`      | 到达终态                      |
| `hosted_return` | hosted checkout 将控制权返回父页面 |
| `error`         | SDK 或轮询过程出现错误             |

可用的嵌入式状态包括：

* `payment`
* `pending`
* `success`
* `cancelled`
* `error`
* `expired`

几个关键语义建议区分清楚：

* `complete`：支付已经进入终态，可能来自 checkout 页面本身，也可能来自 `pollStatus` 的兜底确认
* `hosted_return`：hosted success/cancel 页把控制权交还父页面，适合做 UI 收口或页面跳转
* `error`：SDK 或轮询过程报错，不一定表示支付进入失败终态

## Bootstrap 环境控制

SDK 支持通过 `CLINK_ENV` 固定远端 bootstrap 环境：

* `CLINK_ENV=sandbox` → `https://uat-api.clinkbill.com/api/sdk/bootstrap`
* `CLINK_ENV=production` → `https://api.clinkbill.com/api/sdk/bootstrap`

`checkoutEnvironment` 与 `CLINK_ENV` 保持一致，直接使用 `sandbox` / `production`。

优先级如下：

1. `loadClink(..., { checkoutBaseUrl })`
2. `loadClink(..., { checkoutEnvironment })`
3. `CLINK_ENV`

## 错误处理

当参数校验、bootstrap 或嵌入式 checkout 初始化失败时，SDK 会抛出 `ClinkError`。

```ts theme={null}
import {
  CLINK_ERROR_CODES,
  ClinkError,
  loadClink,
} from '@clink-ai/clink-js';

try {
  const clink = await loadClink('pk_uat_xxxxxxxxx', {
    checkoutEnvironment: 'sandbox',
  });

  await clink.redirectToCheckout({
    sessionId: 'sess_xxx',
  });
} catch (error) {
  if (error instanceof ClinkError) {
    if (error.code === CLINK_ERROR_CODES.INVALID_PUBLIC_KEY) {
      console.error('Invalid publishable key');
    }
  }
}
```

常见错误码包括：

* `INVALID_PUBLIC_KEY`
* `INVALID_CHECKOUT_ENV`
* `BOOTSTRAP_REQUEST_FAILED`
* `INVALID_BOOTSTRAP_RESPONSE`
* `INVALID_REDIRECT_PARAMS`
* `INVALID_EMBEDDED_OPTIONS`
* `INVALID_SESSION_ID`
* `SESSION_ID_FETCH_FAILED`
* `EMBEDDED_CHECKOUT_DISABLED`
* `CONTAINER_NOT_FOUND`
* `NOT_IN_BROWSER`

## 参考资料

* [Create Checkout Session](/api-reference/endpoint/create-checkout-session)
* [Checkout Session Guide](/guides/payments/checkout_session)
