@clink-ai/clink-elements
v0.0.4
Published
[English](./README.md)
Maintainers
Readme
Clink Elements SDK
将安全的、预构建的支付 UI 组件嵌入任意网站。Clink Elements SDK 是一个轻量、框架无关的 JavaScript 库,集成多种支付方式。
前置条件
- 拥有 Clink 商户账号及 publish key(
pk_...) - 通过 Clink 服务端 API 创建的 checkout session ID
安装
npm / yarn / pnpm
npm install @clink-ai/clink-elements
# 或
yarn add @clink-ai/clink-elements
# 或
pnpm add @clink-ai/clink-elementsimport { loadClinkElements } from '@clink-ai/clink-elements';CDN(Script 标签)
<script src="https://unpkg.com/@clink-ai/clink-elements/dist/index.iife.js"></script>通过 <script> 标签加载时,SDK 挂载在全局变量 ClinkElements 下:
const { loadClinkElements } = ClinkElements;快速开始
使用打包工具(ESM)
<!-- 可选: 货币切换组件 -->
<div id="currency-select"></div>
<!-- 必须: 支付表单组件 -->
<div id="payment-method"></div>
<!-- 您自定义的 submit 按钮 -->
<button id="pay-button" disabled>支付</button>import { loadClinkElements } from '@clink-ai/clink-elements';
const clink = await loadClinkElements({
publishKey: 'pk_live_xxxxxxxx',
environment: 'production',
sessionId: 'xxxx',
});
const paymentMethod = clink.createElement('paymentMethod');
const currencySelect = clink.createElement('currencySelect');
paymentMethod.mount('#payment-method');
currencySelect.mount('#currency-select');
clink.on('submit-enabled', (enabled) => {
document.getElementById('pay-button').disabled = !enabled;
});
clink.on('session-success', () => {
alert('支付成功!');
});
document.getElementById('pay-button').addEventListener('click', () => {
clink.submit();
});使用 Script 标签(IIFE)
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@clink-ai/clink-elements/dist/index.iife.js"></script>
</head>
<body>
<div id="currency-select"></div>
<div id="payment-method"></div>
<button id="pay-button" disabled>支付</button>
<script>
(async function () {
var clink = await ClinkElements.loadClinkElements({
publishKey: 'pk_live_xxxxxxxx',
environment: 'production',
sessionId: 'cs_xxxxxxxx',
});
var paymentMethod = clink.createElement('paymentMethod');
var currencySelect = clink.createElement('currencySelect');
paymentMethod.mount('#payment-method');
currencySelect.mount('#currency-select');
clink.on('submit-enabled', function (enabled) {
document.getElementById('pay-button').disabled = !enabled;
});
clink.on('session-success', function () {
alert('支付成功!');
});
document.getElementById('pay-button').addEventListener('click', function () {
clink.submit();
});
})();
</script>
</body>
</html>API 参考
loadClinkElements(options)
异步工厂函数,验证商户凭证后返回 ClinkElements 实例。
async function loadClinkElements(
options: LoadClinkElementsOptions
): Promise<ClinkElements>;若 publish key 或 session ID 无效,抛出 ClinkApiError。
LoadClinkElementsOptions
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
| publishKey | string | 是 | 商户 publish key(以 pk_ 开头) |
| environment | 'sandbox' \| 'production' | 是 | 目标环境 |
| sessionId | string | 是 | 服务端 API 创建的 checkout session ID |
| presetOptions | PresetOptions | 否 | UI 自定义配置 |
PresetOptions
interface PresetOptions {
locale?: 'de-DE' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'ko-KR' | 'pt-PT' | 'zh-CN' | 'zh-Hant';
theme?: 'light' | 'dark';
primaryColor?: string;
radius?: {
components?: number;
card?: number;
};
currencySelect?:
| { hideIfOneCurrency?: boolean }
| { oneCurrencyStyle?: Record<string, string | number> };
section?: {
hideExpire?: boolean;
hideError?: boolean;
hideSkeleton?: boolean;
hideSuccess?: boolean;
hidePending?: boolean;
};
sdkButtons?: SdkButtonOptions;
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| locale | LocaleKey | 'en-US' | 显示语言,支持:'de-DE'、'en-US'、'es-ES'、'fr-FR'、'ja-JP'、'ko-KR'、'pt-PT'、'zh-CN'、'zh-Hant' |
| theme | 'light' \| 'dark' | 'light' | 颜色主题 |
| primaryColor | string | — | 主色调(任意 CSS 颜色值,如 '#1677FF') |
| radius.components | number | 6 | 输入框和按钮的圆角半径(px) |
| radius.card | number | 6 | 卡片容器的圆角半径(px) |
| currencySelect | object | — | 货币选择器行为(详情) |
| section | object | — | 控制 UI 状态的可见性(详情) |
| sdkButtons | SdkButtonOptions | — | Apple Pay、Google Pay、Link 和 PayPal 按钮样式 |
注意:
currencySelect.hideIfOneCurrency与currencySelect.oneCurrencyStyle互斥 — 只能设置其中一个。
第三方 SDK 按钮
按钮配置按用户可识别的支付方式组织,不区分 Stripe Connect、Stripe 或原生实现。SDK 会根据 session 自动选择底层实现并映射同一份配置。
presetOptions.sdkButtons及其公共类型从 SDK0.0.3开始提供。
interface SdkButtonOptions {
applePay?: ApplePayButtonOptions;
googlePay?: GooglePayButtonOptions;
link?: LinkButtonOptions;
paypal?: PayPalButtonOptions;
}
interface ApplePayButtonOptions {
buttonHeight?: number;
buttonRadius?: number;
buttonTheme?: 'black' | 'white' | 'white-outline';
buttonType?: 'buy' | 'check-out' | 'donate' | 'order' | 'plain';
}
interface GooglePayButtonOptions {
buttonHeight?: number;
buttonRadius?: number;
buttonTheme?: 'black' | 'white';
buttonType?:
| 'book'
| 'buy'
| 'checkout'
| 'donate'
| 'order'
| 'pay'
| 'plain'
| 'subscribe';
}
interface LinkButtonOptions {
buttonHeight?: number;
buttonRadius?: number;
}
interface PayPalButtonOptions {
buttonHeight?: number;
buttonRadius?: number;
buttonType?: 'buynow' | 'checkout' | 'donate' | 'pay' | 'subscribe';
}| 按钮 | 默认值 | 说明 |
|---|---|---|
| Apple Pay | 高度 45、圆角 6、类型 buy | 未指定主题时,浅色主题使用 black,深色主题使用 white |
| Google Pay | 高度 45、圆角 6、主题 black、类型 pay | 原生 Google Pay 与 Stripe Connect 使用相同公共配置 |
| Link | 高度 45、圆角 6 | 支持 Stripe Express Checkout 高度和圆角配置 |
| PayPal | 高度 45、圆角 6、PayPal SDK 默认类型 | Clink 通过外层样式应用高度和圆角,可通过 buttonType 设置按钮文案 |
const clink = await loadClinkElements({
publishKey: 'pk_live_xxxxxxxx',
environment: 'production',
sessionId: 'cs_xxxxxxxx',
presetOptions: {
locale: 'zh-CN',
theme: 'light',
sdkButtons: {
applePay: {
buttonHeight: 48,
buttonRadius: 8,
buttonTheme: 'black',
buttonType: 'buy',
},
googlePay: {
buttonHeight: 48,
buttonRadius: 8,
buttonTheme: 'black',
buttonType: 'pay',
},
link: { buttonHeight: 48, buttonRadius: 8 },
paypal: { buttonHeight: 48, buttonRadius: 8, buttonType: 'pay' },
},
},
});- 所有按钮固定占满容器宽度,不提供任意 CSS、边框或尺寸模式透传。
buttonHeight必须为有限正数;buttonRadius会限制在0到buttonHeight / 2,非法值回退到默认值。- PayPal 的高度和圆角由 Clink 通过外层样式统一应用到按钮、加载态和错误态。
presetOptions.locale和setLocale()统一控制全部按钮语言。Apple Pay 未显式设置buttonTheme时继续跟随presetOptions.theme和setTheme()。- 金额、币种、商户号、网关密钥、支付网络、tokenization 和 PayPal 拉起模式始终由 session 与 SDK 内部控制。
ClinkElements
loadClinkElements() 返回的主实例。
createElement(type)
createElement(type: 'paymentMethod' | 'currencySelect'): ClinkElement创建支付元素,返回可挂载到 DOM 的 ClinkElement 实例。
'paymentMethod'— 主支付表单(银行卡输入、钱包按钮等)'currencySelect'— 货币选择下拉框
约束:
'paymentMethod'必须在'currencySelect'之前创建- 每种类型在同一实例中只能创建一次
on(event, callback) / off(event, callback)
on<K extends EventType>(event: K, callback: EventCallback<K>): void
off<K extends EventType>(event: K, callback: EventCallback<K>): void订阅或取消订阅事件。在 TypeScript 中回调参数会自动推断类型。
submit()
submit(): void触发支付提交。SDK 内部自动处理所有支付流程(3DS 认证、二维码支付、第三方跳转)。
监听 session-success 或 session-pending 获取支付结果。
setLocale(locale)
setLocale(locale: 'de-DE' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'ko-KR' | 'pt-PT' | 'zh-CN' | 'zh-Hant'): void运行时切换显示语言,作用于所有已挂载的元素。
setTheme(theme)
setTheme(theme: 'light' | 'dark'): void运行时切换颜色主题,作用于所有已挂载的元素。
promoCodeChange(data)
promoCodeChange(
data: { type: 'apply'; code: string } | { type: 'clear' }
): void应用或清除优惠码。完整流程参见优惠码章节。
destroy()
destroy(): void卸载所有元素、移除事件监听、清理资源。可安全多次调用(幂等)。页面卸载时务必调用此方法。
ClinkElement
单个 UI 元素。
mount(target)
mount(target: HTMLElement | string): void将元素挂载到 DOM。接受 CSS 选择器字符串(如 '#payment-method')或 HTMLElement 引用。
若目标元素不存在或元素已挂载,将抛出异常。
unmount()
unmount(): void从 DOM 中移除元素并恢复容器的原始样式。未挂载时调用无副作用。
事件
使用 clink.on(event, callback) 监听事件:
| 事件 | 回调数据 | 说明 |
|---|---|---|
| submit-enabled | boolean | 支付按钮是否应启用 |
| submit-visible | boolean | 支付按钮是否应显示(部分钱包使用内置按钮) |
| sdk-button-initialized | SdkButtonInitializedData | 当前 session 的第三方支付按钮完成一轮初始化 |
| session-init-success | undefined | 会话初始化完成,元素已就绪 |
| session-success | undefined | 支付成功 |
| session-pending | undefined | 支付待确认(异步支付方式) |
| amount-change | { amount: DueTodayAmountInfo } | 订单金额或价格明细发生变化 |
| promo-code-error | { message: string } | 优惠码验证失败 |
| error | { error: Error } | 发生错误(参见错误处理) |
sdk-button-initialized从 SDK0.0.3开始提供。
示例
clink.on('submit-enabled', (enabled) => {
payButton.disabled = !enabled;
});
clink.on('submit-visible', (visible) => {
payButton.style.display = visible ? 'block' : 'none';
});
clink.on('sdk-button-initialized', (status) => {
// 例如:{ applePay: true, googlePay: false, link: true }
console.log('第三方按钮初始化状态:', status);
});
clink.on('amount-change', ({ amount }) => {
priceDisplay.textContent = `${amount.currency} ${amount.dueTodayAmount}`;
});
clink.on('session-success', () => {
window.location.href = '/thank-you';
});
clink.on('error', ({ error }) => {
console.error('支付错误:', error);
});SdkButtonInitializedData
type SdkButtonType = 'applePay' | 'googlePay' | 'link' | 'paypal';
type SdkButtonInitializedData = Partial<Record<SdkButtonType, boolean>>;- Payload 字段与
presetOptions.sdkButtons配置键保持一致,统一使用驼峰命名。 - Payload 只包含当前 session 提供的第三方按钮;没有目标按钮时为
{}。 true表示 SDK 完成初始化且按钮在当前设备真实可用,false表示设备不支持、配置错误或 SDK 加载失败。- 事件会等待本轮所有目标按钮进入成功或失败终态后触发。支付方式集合变化或按钮重新加载完成后会再次触发。
- 支付弹窗取消、下单失败等初始化完成后的交互错误不会改变该状态。
DueTodayAmountInfo
amount-change 事件提供详细的价格信息:
interface DueTodayAmountInfo {
currency: string;
subtotalAmount: number;
dueTodayAmount: number;
product: Product;
multiProducts?: MultiProduct[];
subscription: Subscription;
enablePromotionCode?: boolean;
promotionCodeInfo?: PromotionCodeInfo;
requiresTaxCalculation?: boolean;
taxInfo?: TaxInfo;
}
interface Product {
name?: string;
/** 多语言产品名称,key 为 locale(如 en-US、zh-CN) */
localizedNames: Record<string, string> | null;
type: 'ONETIME' | 'SUBSCRIPTION';
imageUrl?: string;
}
interface Subscription {
units?: number;
recurring?: 'DAY' | 'WEEK' | 'MONTH' | 'QUARTER' | 'HALF_YEAR' | 'YEAR' | 'CUSTOM';
customDays?: number;
isFreeTrial?: boolean;
freeTrialDays?: number;
freeTrialEndDate?: string;
}
interface MultiProduct {
name: string;
quantity: number;
unitAmount: number;
currency: string;
imageUrl?: string;
}
interface PromotionCodeInfo {
name: string;
terms: string;
discountAmount: number;
currency: string;
durationType: 'ONCE' | 'REPEATING' | 'FOREVER' | null;
durationPeriods?: number;
}
interface TaxInfo {
name?: string;
rate?: string;
amount: number | null;
currency: string;
}自定义配置
语言
通过 presetOptions.locale 设置初始语言,或运行时切换:
clink.setLocale('zh-CN');主题
通过 presetOptions.theme 设置初始主题,或运行时切换:
clink.setTheme('dark');主色调
覆盖按钮和交互元素的主色调:
const clink = await loadClinkElements({
// ...
presetOptions: {
primaryColor: '#7C3AED',
},
});圆角半径
自定义 UI 组件和卡片容器的圆角半径:
presetOptions: {
radius: {
components: 12,
card: 16,
},
}货币选择器行为
控制仅有一种货币时的货币选择器行为:
// 方式 A:直接隐藏
presetOptions: {
currencySelect: { hideIfOneCurrency: true },
}
// 方式 B:自定义样式
presetOptions: {
currencySelect: {
oneCurrencyStyle: { opacity: 0.5, pointerEvents: 'none' },
},
}两个选项互斥,不可同时设置。
区块可见性
控制支付后各 UI 状态的显示:
presetOptions: {
section: {
hideExpire: false,
hideError: false,
hideSkeleton: false,
hideSuccess: true,
hidePending: true,
},
}| 属性 | 说明 |
|---|---|
| hideExpire | 隐藏会话过期状态 |
| hideError | 隐藏错误状态 |
| hideSkeleton | 隐藏加载骨架屏 |
| hideSuccess | 隐藏支付成功状态 |
| hidePending | 隐藏支付待确认状态 |
优惠码
在结账流程中使用优惠码的完整步骤:
1. 检查是否启用优惠码
clink.on('amount-change', ({ amount }) => {
if (amount.enablePromotionCode) {
showPromoCodeInput();
}
});2. 应用优惠码
clink.promoCodeChange({ type: 'apply', code: 'SAVE20' });3. 处理错误
clink.on('promo-code-error', ({ message }) => {
showError(message);
});4. 读取折扣信息
下一次 amount-change 事件的数据中将包含 promotionCodeInfo,携带折扣详情。
5. 清除优惠码
clink.promoCodeChange({ type: 'clear' });错误处理
初始化错误
使用 try-catch 包裹 loadClinkElements() 处理初始化失败:
import {
loadClinkElements,
ClinkApiError,
SessionExpiredError,
SessionCompleteError,
} from '@clink-ai/clink-elements';
try {
const clink = await loadClinkElements({ /* ... */ });
} catch (error) {
if (error instanceof ClinkApiError) {
console.error('凭证无效:', error.message);
}
}运行时错误
监听 error 事件处理初始化之后发生的错误:
clink.on('error', ({ error }) => {
if (error instanceof SessionExpiredError) {
showMessage('会话已过期,请刷新页面。');
} else if (error instanceof SessionCompleteError) {
showMessage('该笔支付已完成。');
} else {
showMessage('出了点问题,请重试。');
}
});异常类
| 类名 | 说明 |
|---|---|
| ClinkApiError | API 请求失败(publish key 无效、网络错误等) |
| SessionExpiredError | checkout 会话已过期 |
| SessionCompleteError | 该笔支付已完成 |
| SessionLoadError | 加载会话数据失败 |
| SessionNotSupportedError | 会话的 UI 模式与 Elements 不兼容 |
| PromoCodeError | 优惠码操作失败 |
所有异常类均继承自原生 Error 类。
TypeScript 支持
SDK 附带完整的 TypeScript 类型声明,所有导出均有完整类型:
import { loadClinkElements } from '@clink-ai/clink-elements';
import type {
ClinkElements,
ClinkElement,
LoadClinkElementsOptions,
PresetOptions,
EventType,
EventCallback,
EventDataMap,
SdkButtonOptions,
ApplePayButtonOptions,
GooglePayButtonOptions,
LinkButtonOptions,
PayPalButtonOptions,
SdkButtonType,
SdkButtonInitializedData,
Environment,
ElementType,
DueTodayAmountInfo,
Product,
Subscription,
MultiProduct,
PromotionCodeInfo,
TaxInfo,
} from '@clink-ai/clink-elements';事件回调的参数类型会根据事件名自动推断:
clink.on('submit-enabled', (enabled) => {
// `enabled` 被推断为 `boolean`
});
clink.on('amount-change', ({ amount }) => {
// `amount` 被推断为 `DueTodayAmountInfo`
});注意事项
- 仅限浏览器 — SDK 依赖
window和document,不兼容服务端渲染。请在客户端加载(如onMounted、useEffect或DOMContentLoaded中)。 - 元素创建顺序 —
'paymentMethod'必须在'currencySelect'之前创建,否则会抛出异常。 - 单次创建 — 每种元素类型在同一
ClinkElements实例中只能创建一次。 - 资源清理 — 页面卸载时务必调用
destroy()以避免内存泄漏。 - 挂载目标 — 调用
mount()时目标 DOM 元素必须已存在。 - 沙箱测试 — 开发和测试阶段使用
environment: 'sandbox'配合测试 publish key,上线时切换为'production'。
