@seaart/payment-checkout-embed
v0.1.5
Published
SeaArt embeddable payment checkout Web Component
Readme
@seaart/payment-checkout-embed
可嵌入的 SeaArt 收银台 Web Component,负责支付方式展示、支付 SDK 挂载、3DS、跳转支付和订单状态轮询。
它不负责计算商品价格、读取宿主的 Cookie/localStorage,也不会推断用户、商品或权益信息。建单支持两种明确模式:payment-hub 交易模式和主站商品驱动模式。
选择接入模式
| 模式 | 适用场景 | 是否传 transactionId | 建单时机 |
| --- | --- | --- | --- |
| openPaymentCheckout | 调用方已有 payment-hub transaction | 是 | 由 payment-hub 处理 |
| openBusinessPaymentCheckout | 宿主先创建 payment-hub transaction | 业务页不传,Adapter 返回 | 打开收银台前 |
| openMainSitePaymentCheckout | SeaArt 主站商品、订阅、算力、礼包等 | 否 | 用户选择支付方式并确认后 |
安装
npm install @seaart/payment-checkout-embed该包仅能在浏览器中运行。SSR 项目请在客户端动态导入,或放在 ClientOnly 中渲染。
主站商品驱动方式
主站当前 PaymentForm 不是先创建 payment-hub transaction:它会先加载支付策略,随后根据商品类型、支付渠道、试用、数量和来源创建订单。因此主站接入请使用 openMainSitePaymentCheckout,不要把 goodsId、主站订单号或任意业务 ID 填入 transactionId。
import { openMainSitePaymentCheckout } from '@seaart/payment-checkout-embed';
const checkout = openMainSitePaymentCheckout({
businessContext: {
productType: 'computing',
goodsId: 'calculate_1000',
quantity: 1,
productInfo,
purchParams: { discount_type: 'recommend_calculate' },
pageFrom: 'VipComputingDialogV3',
dotFrom: 'vip_computing_recommend',
},
// 每次打开都生成新的关联号,用于 success / unsuccess / close 生命周期关联。
checkoutReference: crypto.randomUUID(),
runtimeConfig: {
baseUrl: window.location.origin,
credentials: 'include',
},
adapter: {
// 复用主站已确认的 init-context / 支付策略聚合逻辑,返回收银台上下文。
loadCheckoutContext: (context, signal) => loadMainSiteCheckoutContext(context, signal),
// 复用主站 PaymentParamsBuilder + PaymentAPIManager 的商品类型分流逻辑。
createPayment: (context, paymentParams, signal) => createMainSiteOrder(context, paymentParams, signal),
getOrderStatus: (context, orderId, signal) => getMainSiteOrderStatus(context, orderId, signal),
checkThreeDs: (context, params, signal) => checkMainSiteThreeDs(context, params, signal),
},
language: 'zhCN',
onSuccess: (result) => {
refreshHostEntitlements(result);
checkout.remove();
},
onClose: () => checkout.remove(),
});checkoutReference 必须在每次打开时唯一。主站模式的 success / unsuccess 事件返回该字段和真实 orderId(成功或已建单后),不会伪造 transactionId。adapter.checkThreeDs 是必填项,即使当前商品未触发 3DS;它必须返回 { status: { code: number, msg?: string }, data?: { order_id?: string } } 完整响应壳,不能只返回 data。缺失或结构不符时公共包会报出明确配置错误,绝不回退调用 payment-hub 3DS 接口。
adapter 是业务边界,公共包不会硬编码主站内部的 /api/v1/payment/order/create、create-sub、create-gift-order 等接口。调用方必须复用主站现有的商品参数构建和接口分流,保证试用、礼包、升级、数量、优惠和幂等规则一致。
Adapter 详细契约
adapter 的四个方法均为必填。公共包只处理收银台状态、SDK 和轮询;商品定价、支付接口选择、订单幂等与权益刷新均由主站负责。
type MainSiteCheckoutAdapter<TContext> = {
loadCheckoutContext(context: TContext, signal?: AbortSignal): Promise<TransactionResponse>;
createPayment(
context: TContext,
params: CreatePaymentParams,
signal?: AbortSignal,
): Promise<CreatePaymentResponse>;
getOrderStatus(context: TContext, orderId: string, signal?: AbortSignal): Promise<OrderStatus>;
checkThreeDs(
context: TContext,
params: CheckThreeDsParams,
signal?: AbortSignal,
): Promise<CheckThreeDsResponse>;
};loadCheckoutContext
打开收银台时调用一次。它负责调用主站的支付初始化接口,并适配为收银台上下文;不能在公共包中硬编码主站接口。
最小返回结构如下,payment_strategy 中的支付方式决定展示内容和可用渠道:
return {
price: product.price,
product_name: product.name,
local_price: {
price: product.localPrice,
code: product.currency,
currency: product.currency,
price_old: product.originalPrice,
},
country_code,
payment_strategy,
adyen_payment_methods,
adyen_config,
adyen_card: cardList.find((item) => item.channel_type === 9) || null,
payermax_card: cardList.find((item) => item.channel_type === 1) || null,
payermax_params_by_purchase_type,
subscription: isSubscription ? { period: 'month', period_amount: renewalAmount } : null,
};payment_strategy.sub_payment_method_items 代表可订阅的支付方式,single_payment_method_items 代表一次性支付方式。支付条目应保留后端的 id、name、type、interaction_type、logo、channel_id、channel_type 和 prev_pay_type。
主站商品驱动模式会隐藏 Airwallex 支付方式;payment-hub 模式严格按后端 payment_strategy 展示,不做该渠道过滤。
createPayment
用户点击支付后调用。context 是调用方传入的商品业务上下文;params 是公共包根据当前渠道生成的支付参数:
{
transactionId, // 主站模式中是 checkoutReference,仅用于前端生命周期关联
from,
thirdPlatType, // 渠道类型,例如 1=PayerMax、9=Adyen
usePayType, // 例如 VISA、scheme、paypal
origin,
oneClick,
thirdExtra, // Adyen payment_method/browser_info;PayerMax payment_token/session_key
}主站必须根据 context.productType、试用、升级、礼包、数量和优惠,复用现有的 create、create-sub 等业务建单逻辑;并透传渠道所需的 thirdExtra。不要将 params.transactionId 当作主站订单号提交,也不要记录 payment_token、session_key 等敏感字段。
返回值必须统一为:
{
order_id: '主站真实订单号',
payment_url: '可选,跳转或 3DS 地址',
result: '可选,Adyen action 的 JSON 字符串',
check_three_ds2: false,
}order_id 是进入等待支付和轮询的必要字段。没有 order_id 时应视为建单失败,不能只返回 payment_url。
getOrderStatus
外部跳转、3DS 和关闭前确认都会调用。必须按传入的真实 orderId 查询,并返回:
{
id: orderId,
status: 1 | 0 | -1, // 1=成功,0=待支付,-1=失败
status_desc: '可选状态说明',
price: 5.99, // 可选,成功回调金额
}checkThreeDs
仅 Adyen 3DS 流程调用。必须将 three_ds_result、order_id 和可选的 adyen_payment_method 传给主站的 3DS 校验接口,并保留完整响应壳:
{
status: { code: 10000, msg: 'ok' },
data: { order_id: '主站真实订单号' },
}不能只返回 data,也不能在主站模式下回退请求 payment-hub 的 3DS 接口。
主站接口字段映射
以下映射对应主站当前已封装的接口,Adapter 应在宿主侧调用这些函数后再转换返回值。公共包不会直接请求这些 URL。
初始化上下文:loadCheckoutContext
| 主站接口 | 请求字段 | 响应字段 | 映射到公共包上下文 |
| --- | --- | --- | --- |
| POST /api/v1/payment/init-context | country_code | payment_strategy | payment_strategy |
| 同上 | - | payment_methods / adyen_payment_methods | adyen_payment_methods |
| 同上 | - | card_list 中 channel_type === 9 | adyen_card |
| 同上 | - | card_list 中 channel_type === 1 | payermax_card |
| 主站商品信息 | 商品名、价格、币种、续费金额 | - | product_name、price、local_price、subscription |
| POST /api/v1/payment/order/pre-pay | 主站现有预支付请求参数 | common_client_key、common_session_key | payermax_params_by_purchase_type.common.client_key/session_key |
| 同上 | - | sub_client_key、sub_session_key | payermax_params_by_purchase_type.sub.client_key/session_key |
init-context 的 payment_strategy 是支付方式的唯一来源。其 sub_payment_method_items 和 single_payment_method_items 中的原始字段应原样保留,不要在 Adapter 内改写渠道类型或支付方式类型。
创建订单:createPayment
普通商品使用主站 POST /api/v1/payment/order/create,订阅商品使用 POST /api/v1/payment/order/create-sub。其他商品类型由 businessContext.productType 复用主站既有建单分支。
| 主站建单字段 | 来源 |
| --- | --- |
| cp_product_id | context.goodsId |
| quantity | context.quantity,缺省时由主站业务规则决定 |
| third_plat_type | params.thirdPlatType |
| use_pay_type | params.usePayType |
| payment_method、browser_info | params.thirdExtra,供 Adyen 使用 |
| payment_token、session_key | params.thirdExtra,供 PayerMax 使用;不得写日志 |
| page_location | context.pageFrom 或调用方约定的页面来源 |
| sub_source | params.oneClick ? 'one_click' : 'manual' |
| os | 主站 Web 固定使用 '1' |
| 试用、升级、优惠、礼包等 | context.purchParams、context.apiParams 与主站已有参数构建器 |
主站接口返回的 ThirdOrderId / order_id 映射为 CreatePaymentResponse.order_id;PaymentUrl / payment_url 映射为 payment_url;Result / result 映射为 result。
订单状态:getOrderStatus
调用 POST /api/v1/payment/order/status,请求体固定为:
{ order_id: orderId }将主站响应映射为公共包状态:is_paid === true 为 status: 1,明确失败为 status: -1,其余为 status: 0;响应中的 order_id/order_no 映射为 id,usd_price 映射为可选 price。
3DS 校验:checkThreeDs
调用 POST /api/v1/payment/adyen/check-three-ds2,请求字段与公共包参数一一对应:
| 主站请求字段 | 公共包参数 |
| --- | --- |
| three_ds_result | params.three_ds_result |
| order_id | params.order_id |
| adyen_payment_method | params.adyen_payment_method,存在时透传 |
返回主站原始 { status, data },不能解包成 data 后返回。
主站 SDK 配置
loadCheckoutContext 除支付策略、绑卡和 Adyen 配置外,还必须返回主站已取得的渠道 SDK 配置。公共包不会自行请求主站的预支付接口,也不会推断测试或生产环境。
PayerMax 使用普通购买与订阅购买两套独立会话。主站应先调用现有的 /api/v1/payment/order/pre-pay,再将结果映射为:
return {
// ...payment_strategy、adyen_payment_methods、adyen_config、card_list 等上下文
payermax_params_by_purchase_type: {
common: {
client_key: prePay.common_client_key,
session_key: prePay.common_session_key,
sandbox: isDevelopment ? 1 : 0,
},
sub: {
client_key: prePay.sub_client_key,
session_key: prePay.sub_session_key,
sandbox: isDevelopment ? 1 : 0,
},
},
};公共包会根据支付策略条目来源选择会话:sub_payment_method_items 使用 sub,single_payment_method_items 使用 common。缺少某一类会话时仅隐藏该类 PayerMax 方式,不影响另一类方式或其他渠道。旧的 payermax_params 仍保留给 payment-hub 单会话模式兼容使用。
payment-hub 业务适配方式
主站及大多数业务调用方应使用 openBusinessPaymentCheckout。调用方只传递自己的商品上下文,例如 goodsId、商品类型、数量和业务来源;由宿主的 createTransaction Adapter 创建业务订单和 payment-hub transaction。
transactionId 仍是支付中心内部必需的交易标识,但不需要暴露给业务页面或用户输入。
import { openBusinessPaymentCheckout } from '@seaart/payment-checkout-embed';
const checkout = await openBusinessPaymentCheckout({
businessContext: {
productType: 'computing',
goodsId: 'calculate_1000',
quantity: 1,
pageFrom: 'mall',
dotFrom: 'mall_checkout',
},
// 该函数属于宿主业务层:先创建主站业务订单,再创建 payment-hub transaction。
// 不能把 goodsId 或旧渠道订单号直接作为 transactionId 使用。
createTransaction: async (context) => {
const transaction = await createMainSitePaymentTransaction(context);
return {
transactionId: transaction.transactionId,
userName: transaction.userName,
productName: transaction.productName,
};
},
language: 'zhCN',
baseUrl: 'https://payment-api.example.com',
runtimeConfig: {
baseUrl: 'https://payment-api.example.com',
credentials: 'include',
},
onSuccess: (result) => {
refreshHostEntitlements(result);
checkout.remove();
},
onUnsuccess: (result) => reportPaymentFailure(result),
onClose: () => checkout.remove(),
});createTransaction 必须返回:
type PaymentCheckoutTransaction = {
transactionId: string;
userName?: string;
productName?: string;
};主站 Adapter 负责将商品上下文转换为已确认的建单接口参数。价格、订单号、用户身份、国家/IP、订阅周期和幂等控制必须由宿主后端或主站既有业务接口确定,公共包不会构造这些字段。
底层方式:已有交易 ID 时调用
当调用方已通过自己的后端创建了 payment-hub transaction,可使用 openPaymentCheckout:
import { openPaymentCheckout } from '@seaart/payment-checkout-embed';
const checkout = openPaymentCheckout({
transactionId: transaction.transactionId,
language: 'en',
baseUrl: 'https://payment-api.example.com',
runtimeConfig: {
baseUrl: 'https://payment-api.example.com',
authToken: getHostIssuedToken(),
credentials: 'omit',
},
onSuccess: (result) => {
refreshHostEntitlements(result);
checkout.remove();
},
onClose: () => checkout.remove(),
});Web Component 方式
该方式适用于已有 transactionId 的低层接入。请在元素连接前同步调用 configure:
<div id="checkout-container"></div>
<script type="module">
import './node_modules/@seaart/payment-checkout-embed/dist/seaart-payment-checkout.mjs';
const checkout = document.createElement('seaart-payment');
checkout.setAttribute('transaction-id', transaction.transactionId);
checkout.setAttribute('language', 'en');
checkout.setAttribute('base-url', 'https://payment-api.example.com');
checkout.configure({
baseUrl: 'https://payment-api.example.com',
credentials: 'include',
});
checkout.addEventListener('success', (event) => {
refreshHostEntitlements(event.detail);
checkout.remove();
});
checkout.addEventListener('close', () => checkout.remove());
document.querySelector('#checkout-container').appendChild(checkout);
</script>参数说明
openBusinessPaymentCheckout
| 参数 | 必填 | 说明 |
| --- | --- | --- |
| businessContext | 是 | 宿主业务上下文。主站可传 productType、goodsId、quantity、purchParams、apiParams、productInfo、isSubscription、isFreeTry、upgrade、pageFrom、dotFrom。 |
| createTransaction | 是 | 宿主建单 Adapter,必须返回 { transactionId, userName?, productName? }。 |
| baseUrl | 是 | payment-hub API 地址;包内不内置默认 host。 |
| language | 否 | 内置语言,如 en、zhCN、zhTW、ja。 |
| runtimeConfig | 否 | 请求运行配置,见下表。 |
| container | 否 | 收银台挂载容器,默认 document.body。 |
| onReady/onStateChange/onSuccess/onUnsuccess/onClose | 否 | 收银台生命周期回调。 |
runtimeConfig
| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| baseUrl | 无 | payment-hub API 地址,必填。 |
| authToken | 无 | Bearer Token。Cookie 鉴权时不传。 |
| credentials | omit | Cookie 策略:include 或 omit。 |
| headers | 无 | 附加请求头。不要放入业务密钥或支付凭据。 |
| timeout | 20000 | 单请求超时,单位毫秒。 |
运行中的请求配置不可修改。需要刷新 token、切换 API 地址或重新建单时,请关闭并移除旧组件后重新打开。
事件
| 事件 | event.detail |
| --- | --- |
| ready | 无 |
| statechange | 支付状态,例如 processing、waiting_redirect、completed。 |
| success | { orderId, amount, currency, transactionId } |
| unsuccess | { reason, message, transactionId, orderId? } |
| close | 无 |
安全边界
- 包只使用调用方显式传入的
baseUrl、token、headers 和 Cookie 策略。 - 包不读取 Cookie、
localStorage或sessionStorage,也不创建可信用户身份和价格。 - 商品价格、主站订单、支付 transaction、用户身份、IP/国家、权益刷新和最终支付结果校验必须由宿主后端或主站业务层负责。
- payment API 必须允许宿主来源的 CORS,并为该来源返回可用的支付 SDK 配置。
构建
pnpm --filter @seaart/payment-checkout-embed build