# Web SDK 接入

> 服务端创建支付，使用 paymentId 初始化 Web SDK，并处理自定义按钮、钱包、外跳与最终结果确认。

Web SDK 接入分为两部分：服务端创建支付并保存订单与 `paymentId` 的映射，浏览器加载 SDK 并使用该 `paymentId` 创建 Checkout。`secret`、请求签名、客户身份映射和最终支付状态确认始终保留在服务端。

开始前，先按[接入准备](/zh/payments/get-started/setup)获取 API 凭证，并将服务端的公网出口 IP 加入对应环境的白名单。每个请求都需按[请求签名](/zh/payments/get-started/request-signing)生成 `sign`。上线前，参考[沙盒测试](/zh/payments/get-started/testing)在沙盒环境验证集成中使用的全部场景。

Web SDK 使用 Onerway 的 Google Pay 能力，无需报备网站；网站报备仅适用于 [API 直连接入](/zh/payments/online-payments/payment-methods/google-pay#%E4%B8%8A%E7%BA%BF%E5%89%8D%E7%BD%91%E7%AB%99%E6%8A%A5%E5%A4%87)。

## 接入流程

1. 服务端调用[创建 SDK 交易](/zh/payments/api-reference/endpoints/sdk-create-transaction)，保存返回的 `paymentId`。
2. 浏览器加载与交易环境匹配的 CDN 脚本。
3. 使用 `paymentId` 创建 Checkout，先订阅事件，再挂载 Payment Element。
4. Card、本地支付和其他自定义支付按钮调用 `confirmPayment()`；Apple Pay、Google Pay 等 SDK 自有按钮只监听 `payment_result`。
5. 客户端根据事件更新页面；服务端通过[查询支付记录](/zh/payments/api-reference/endpoints/query-payments)或支付 Webhook 确认最终状态。

## 在服务端创建支付

普通 Web SDK v4 支付应通过[创建 SDK 交易](/zh/payments/api-reference/endpoints/sdk-create-transaction)传入 `productType=ALL`、`subProductType=DIRECT` 和 `txnType=SALE`。标准的一次性扣款示例省略了 `billingInformation`、`shippingInformation`、`paymentMode` 和 `osType`。

创建交易时，`billingInformation` 与 `shippingInformation` 均为可选。信息已具备时即可传入；初始下单省略且业务流程需要时，可以在确认支付前通过[更新 SDK 订单](/zh/payments/api-reference/endpoints/sdk-update-order)补充。传入对象后，其内部字段原有的必填条件仍然生效。更新订单时，服务端应复用原下单的 `merchantTxnId`，并在客户确认支付前等待更新成功响应。

当前 Web SDK 的[字段 `txnOrderMsg`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-txnOrderMsg)只包含 `returnUrl`、`products`、`appId`、`customerPlatform`、`periodValue` 和 `notifyUrl`；每个字段是否必填及其适用条件以 API Reference 为准。商户服务端不要采集或传入浏览器、设备和持卡人 IP 字段，这些信息由 Web SDK 采集。

Web SDK 支付通常省略 `paymentMode`，无需区分桌面和移动浏览器。传入非 `WEB` 值时，必须同时传入 `osType`。

服务端只把创建支付响应中的 `paymentId` 传给浏览器。`transactionId` 可用于服务端订单关联，但不能替代 `paymentId` 初始化 SDK。响应仍会返回 `redirectUrl`，当前 Web SDK 初始化时不消费该字段。

## 加载 SDK

CDN 与 `environment` 必须匹配：

| 环境 | CDN | `environment` |
| --- | --- | --- |
| Sandbox | `https://sandbox-checkout-sdk.onerway.com/v4/latest/onerway.js` | `sandbox` |
| Production | `https://checkout-sdk.onerway.com/v4/latest/onerway.js` | `production`，默认值 |

`v4/latest` 是 Production 正式推荐的长期地址。即使 `environment` 默认为 `production`，也建议显式传入，避免复制配置时混用环境。

```html
<script src="https://checkout-sdk.onerway.com/v4/latest/onerway.js"></script>

<div id="onerway_checkout"></div>
<button id="pay_button" type="button">Pay now</button>
```

## 初始化、订阅事件并挂载

```js
const checkout = await Onerway.createCheckout(paymentId, {
  environment: 'production',
  locale: 'en'
})

const paymentElement = checkout.createPaymentElement()

paymentElement.on('ready', (event) => {
  console.log(event.availablePaymentMethods)
})

paymentElement.on('loaderror', (event) => {
  console.error(event.error.code, event.error.message)
})

checkout.on('payment_result', handlePaymentResult)

paymentElement.mount('#onerway_checkout')
```

必须先订阅事件，再调用 `mount()`：

- `ready.availablePaymentMethods` 是当前订单、服务端配置和浏览器钱包能力共同决定的可用支付方式列表。不要把示例值当成固定枚举；钱包不可用时 SDK 会隐藏相应按钮。
- `loaderror` 只表示 Checkout 初始化失败。程序逻辑应判断 `event.error.code`，`event.error.message` 仅用于展示。公开的 code 只有两个：`checkout_load_failed`（首次加载支付方式失败，可让客户重新初始化）和 `no_available_payment_methods`（过滤后没有可展示的支付方式，检查服务端配置与 `paymentMethod`）。
- 表单校验、支付接口失败、钱包取消和 3DS/外跳异常不属于 `loaderror`，应从 `confirmPayment()` 返回值或 `payment_result` 处理。

## 配置 Payment Element

`config` 的子字段均为可选，但商户应根据自己的支付方式、表单和品牌要求决定是否传入，不能假定所有业务都使用默认展示。配置在 `paymentElement.mount()` 时读取；修改后需要重新创建并挂载 Payment Element 才能生效。

| 字段 | 类型 | 默认值 | 作用 |
| --- | --- | --- | --- |
| `paymentMethod` | `String[]` | 不传 | 支付方式显示白名单，只保留服务端实际返回且存在于数组中的方式 |
| `showBillingAddress` | `Boolean` | `true` | 是否展示并校验账单地址表单 |
| `displayCardholdername` | `Boolean` | `true` | 是否展示并校验持卡人姓名输入框 |
| `walletButtons` | `Object` | 见下文 | 配置 Apple Pay 和 Google Pay 官方按钮支持的外观 |
| `checkoutTheme` | `String` | `light` | SDK 主题预设；当前公开主题只有 `light` |
| `variables` | `Object` | `{}` | 覆盖主题变量，建议优先使用 |
| `styles` | `Object` | `{}` | 按 CSS selector 覆盖 SDK 样式，优先级最高 |
| `customCssURL` | `String` | SDK 默认 CSS | 替换 SDK 基础样式表来源；`variables` 和 `styles` 仍然生效 |

例如，商户可以只展示当前业务接受的方式、隐藏 SDK 账单地址表单，并对 SDK 可控区域应用品牌样式：

```js
const checkout = await Onerway.createCheckout(paymentId, {
  environment: 'production',
  locale: 'en',
  config: {
    paymentMethod: ['CARD', 'DOKU_VA', 'GooglePay'],
    showBillingAddress: false,
    displayCardholdername: true,
    checkoutTheme: 'light',
    variables: {
      colorPrimary: '#2563eb',
      colorText: '#1a202c',
      borderRadius: '8px'
    },
    walletButtons: {
      googlePay: { type: 'pay', color: 'black', height: '44px' },
      applePay: { type: 'pay', color: 'black', height: '44px' }
    }
  }
})
```

### 限制支付方式

`paymentMethod` 只负责前端显示过滤，不会为商户开通支付方式：

| 传值 | 行为 |
| --- | --- |
| 不传 | 展示服务端返回且当前设备可用的全部方式 |
| `['CARD', 'FPX', 'GooglePay']` | 只展示服务端也返回的 `CARD`、`FPX` 和 `GooglePay` |
| `[]` | 不展示任何方式，并触发 `loaderror`；`event.error.code` 为 `no_available_payment_methods` |

数组值必须与服务端返回的支付方式标识完全一致。数组顺序不控制展示顺序；实际顺序仍由服务端支付方式配置和 SDK 钱包区域决定。是否成功加载某种方式，最终以 `ready.availablePaymentMethods` 为准。

### 配置表单显示

- `showBillingAddress: false` 会隐藏账单地址并停止相应的前端校验，但不会修改 Create transaction 或 Update order 的服务端字段约束。需要账单信息的业务应在确认支付前自行采集并更新订单。
- `displayCardholdername: false` 会隐藏持卡人姓名并停止相应的前端校验。

### 配置钱包按钮

| 字段 | 默认值 | 支持值或规则 |
| --- | --- | --- |
| `googlePay.type` | `pay` | `book`, `buy`, `checkout`, `donate`, `order`, `pay`, `plain`, `subscribe` |
| `googlePay.color` | `black` | `black`, `white` |
| `applePay.type` | `pay` | `add-money`, `book`, `buy`, `check-out`, `continue`, `contribute`, `donate`, `order`, `plain`, `reload`, `rent`, `subscribe`, `support`, `tip`, `top-up`, `pay` |
| `applePay.color` | `black` | `black`, `white`, `white-outline` |
| `googlePay.width` / `applePay.width` | `100%` | CSS 尺寸字符串 |
| `googlePay.height` / `applePay.height` | `44px` | CSS 尺寸字符串 |
| `googlePay.radius` / `applePay.radius` | `8px` | CSS 尺寸字符串；Apple / Google 平台可能限制最终外观 |

这些选项只调整 SDK 官方钱包按钮支持的外观，不会使当前设备不支持的钱包变为可用，也不能用 `variables`、`styles` 或自定义 CSS 强制覆盖平台控制的按钮外观。Apple Pay 的域名验证等接入准备见[支付方式](/zh/payments/online-payments/payment-methods)。

### 配置主题与样式

- `checkoutTheme` 当前只支持 `light`。
- `variables` 是推荐的品牌定制方式。支持的常用变量包括 `containerBackground`、`cardBackground`、`inputBackground`、`inputBrandBackground`、`aggregateHeaderBackground`、`dialogBackground`、`colorText`、`colorPrimary`、`colorDanger`、`fontFamily`、`fontSizeBase` 和 `borderRadius`。`fontSizeBase` 支持 `12px`–`24px`，`borderRadius` 支持 `0px`–`24px`。
- `styles` 接收 CSS selector 到 CSS 属性对象的映射，例如 `{ '.onerway-checkout__input': { color: '#1a202c' } }`，用于变量无法覆盖的局部样式。
- `customCssURL` 用于整体替换 SDK 基础样式表；之后仍可通过 `variables` 和 `styles` 覆盖。

主题和样式仅作用于 SDK 可控区域。历史配置 `showPayButton` 和 `payButtonText` 已不再支持并会被 SDK 忽略。

## 语言（Locale）

不传 `locale` 时，SDK 使用浏览器语言。显式传入的值或浏览器语言不受当前支付方式支持时，SDK 默认回退到英语 `en`。

普通支付方式支持以下 `locale` 取值（按字母序）：

| 代码 | 语言 | 代码 | 语言 |
| --- | --- | --- | --- |
| `ar` | 阿拉伯语 | `de` | 德语 |
| `en` | 英语 | `es` | 西班牙语 |
| `fi` | 芬兰语 | `fr` | 法语 |
| `it` | 意大利语 | `ja` | 日语 |
| `ko` | 韩语 | `nl` | 荷兰语 |
| `no` | 挪威语 | `pl` | 波兰语 |
| `pt` | 葡萄牙语 | `ru` | 俄语 |
| `sv` | 瑞典语 | `th` | 泰语 |
| `zh-cn` | 简体中文 | `zh-tw` | 繁体中文 |

钱包使用自己的 locale 枚举和大小写，两个钱包的支持范围只有以下差异：

| 支持范围 | Locale |
| --- | --- |
| 两个钱包都支持 | `ar`, `ca`, `cs`, `da`, `de`, `el`, `en`, `es`, `fi`, `fr`, `hr`, `id`, `it`, `ja`, `ko`, `ms`, `nl`, `no`, `pl`, `pt`, `ru`, `sk`, `sv`, `th`, `tr`, `uk`, `zh` |
| 仅 Google Pay | `bg`, `et`, `sl`, `sr` |
| 仅 Apple Pay | `he`, `hi`, `hu`, `ro`, `vi`, `zh-TW` |

钱包遇到不支持的 locale 时同样回退到英语 `en`。例如，普通支付方式的简体中文为 `zh-cn`，钱包为 `zh`；普通支付方式的繁体中文为 `zh-tw`，钱包为 `zh-TW`。不要自行转换其他未列出的值。

## 确认支付

### Card、本地支付与自定义按钮

这些支付方式必须由商户按钮调用 `confirmPayment()`：

```js
document.querySelector('#pay_button').addEventListener('click', async () => {
  const result = await checkout.confirmPayment()
  handleConfirmResult(result)
})
```

不要重复创建 Checkout 或在重试时重新下单。可重试状态下，保留同一个 `paymentId`、Checkout 和 Payment Element。

### Apple Pay、Google Pay 等 SDK 自有按钮

SDK 自有钱包按钮不能调用 `confirmPayment()`。用户点击 SDK 渲染的官方按钮后，只通过 `payment_result` 接收客户端结果：

```js
checkout.on('payment_result', (result) => {
  if (result.reason?.type === 'canceled') {
    // 用户关闭钱包；恢复页面交互，不要把它映射为支付失败。
    return
  }

  renderClientResult(result)
})
```

钱包是否展示还取决于服务端配置、浏览器、设备和钱包能力。

### 在 App 内嵌 WebView 中使用 Google Pay

Google Pay 是否可用由能力检测决定：条件不满足时 `ready.availablePaymentMethods` 不会包含 `GooglePay`，SDK 不渲染其按钮，属预期行为而非故障。

- Android WebView 官方支持 Google Pay，但需宿主 App 配合：Android WebView 137+、Google Play services 25.18.30+，集成 `androidx.webkit:webkit:1.14.0`、声明 Chromium payment intent actions、启用 Payment Request API，并向 Google 发布 App integration；使用自定义 User-Agent 时需附加 `GOOGLE_PAY_SUPPORTED`。详见 [Google 官方 WebView 指南](https://developers.google.com/pay/api/android/guides/recipes/using-android-webview)。
- iOS 内嵌 WebView 不支持 Google Pay。
- 宿主 App 不具备上述条件时，可将支付流程外跳系统浏览器完成，并确保支付后引导用户返回原 App。

## `paymentStatus` 与 `nextAction`

只有 `paymentStatus === 'R'` 时才会返回 `nextAction`：

| `nextAction.type` | SDK 行为 | 商户处理 |
| --- | --- | --- |
| `PresentToShopper` | SDK 在当前页展示二维码、本地支付页等承接界面；此时还没有最终支付结果。 | 保留当前 Checkout，等待 `payment_result`，不要再次调用 `confirmPayment()`。 |
| `RedirectShopper` | SDK 即将跳转到外部支付页面；此时还没有最终支付结果。3DS 使用此流程。 | 等待 Onerway 回跳到下单时的 `returnUrl`，再由服务端使用已保存的 `paymentId` 调用[查询支付记录](/zh/payments/api-reference/endpoints/query-payments)。 |

`R` 只表示支付流程需要继续，不代表成功或失败。`nextAction` 不会在 `paymentStatus !== 'R'` 时返回。

`paymentStatus` 的完整取值与定义见[响应字段 `paymentStatus`](/zh/payments/api-reference/endpoints/sdk-create-transaction#response-data-paymentStatus)。客户端状态只用于更新页面，不得据此发货、充值或记账：

| `paymentStatus` | 商户处理 |
| --- | --- |
| `I`、`U`、`P`、`A` | 非最终状态。订单保持待处理，不履约。 |
| `R` | 进入承接或外跳流程，读取 `nextAction.type` 并按上表处理。 |
| `O` | 允许继续支付或重试。保留同一个 `paymentId`、Checkout 和 Payment Element，不重新下单。 |
| `S`、`N` | 客户端收到的结果，仍需服务端确认后再完成订单或按订单规则展示。 |

没有形成支付状态的客户端异常通过 `reason` 描述：

| `reason.type` | 含义 |
| --- | --- |
| `validation_error` | 本地表单校验失败，未形成支付状态 |
| `sdk_error` | SDK 本地状态、配置或调用问题 |
| `api_error` | 支付接口或后端业务失败；`reason.code` 是后端原始 `respCode`，参见[响应码](/zh/payments/api-reference/response-codes) |
| `canceled` | 客户主动取消当前交互；`reason.code` 为 `presenter_closed`（关闭 SDK 承接的二维码或本地支付弹窗）、`cvv_closed`（关闭 Google Pay 授权后的二次 CVV 验证弹窗）或 `wallet_canceled`（取消 Apple Pay 或 Google Pay 官方授权窗） |

客户取消时可能没有 `paymentStatus`，只有 `reason.type === 'canceled'`；取消前端流程不等于最终支付失败，恢复页面交互即可。

## 确认最终支付结果

`confirmPayment()` 返回值、`payment_result` 和 `returnUrl` 都只能驱动客户端页面流转，最终结果以 Webhook 为准：普通支付见[支付结果通知](/zh/payments/api-reference/webhooks/payment-result)，客户自选保存卡见[保存支付方式结果通知](/zh/payments/api-reference/webhooks/payment-method-result)，订阅见[订阅扣款通知](/zh/payments/api-reference/webhooks/subscription-payment)，预授权见[预授权、请款与撤销通知](/zh/payments/api-reference/webhooks/authorization-capture)。验签、应答与重试、幂等去重、状态判断与查询补偿的通用规则见 [Webhook 通知](/zh/payments/get-started/webhooks)。

Web SDK 接入需额外注意：服务端应保存商户订单与 `paymentId` 的映射；外跳回到 `returnUrl` 后不要相信 URL 中携带的支付状态，先把页面交互恢复为“正在确认”，未收到 Webhook 时由服务端使用 `paymentId` 调用[查询支付记录](/zh/payments/api-reference/endpoints/query-payments)补偿并返回业务结果。不要在浏览器日志、持久化数据或埋点中保存未脱敏的 `rawResult`、请求响应或支付数据。

## 保存卡与订阅

### 让客户主动选择是否保存卡

保持[字段 `subProductType`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-subProductType) `=DIRECT` 并传入稳定的[字段 `merchantCustId`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-merchantCustId)。SDK 会向客户展示保存卡选项，该选项不会默认选中；后续支付复用同一个 `merchantCustId`，SDK 会回显该客户的已保存卡，并在内部完成选卡和支付，客户端无需为该流程获取 `tokenId`。

Web SDK 接入需额外注意：`subProductType=TOKEN` 属于旧版 Web SDK 保存卡流程，不用于当前 Web SDK 的客户自选保存卡流程。`merchantCustId` 的取值要求、保存支付方式结果通知与已保存 token 的查询、删除见[保存支付方式](/zh/payments/online-payments/scenarios/saved-payment-methods)。

### 创建固定计划的初始订阅

初始订阅支付使用 `subProductType=SUBSCRIBE`。服务端应维护允许购买的计划映射，并传入稳定、可读的[字段 `subscription.productName`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-subscription-productName)；计费方式由[字段 `subscription.selfExecute`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-subscription-selfExecute) 决定。托管卡订阅可同时传入[字段 `subscription.bindCard`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-subscription-bindCard) `=true`，在订阅成功时保存客户卡，此时会收到两条独立通知。

托管订阅与自主管理订阅的选择、合约凭证、生命周期通知、续费与升降级、订阅并绑卡的双通知处理见[订阅支付](/zh/payments/online-payments/scenarios/subscriptions)。

## 预授权

传入[字段 `txnType`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-txnType) `=AUTH`（`subProductType` 保持 `DIRECT`）时，本次支付是预授权：冻结客户卡上的订单金额，不立即扣款；SDK 集成流程与普通支付一致。预授权成功后保存响应中的 `transactionId` 与 `paymentId`，后续请款或撤销由服务端调用[预授权请款或撤销](/zh/payments/api-reference/endpoints/capture-or-void-authorization)完成。

适用范围、请款与撤销的生命周期、通知、边界与状态判断见[预授权与请款](/zh/payments/online-payments/scenarios/pre-authorization)。

## 分账

分账是平台模式下的能力：平台商户以收款子商户的 `merchantNo` 创建支付，并传入[字段 `paymentMethodOptions`](/zh/payments/api-reference/endpoints/sdk-create-transaction#request-paymentMethodOptions) 在其中的 `share` 设置 `profitShare=true`，该笔支付才可参与分账；SDK 集成流程与普通支付一致。同时设置 `profitShareRate` 时，`SALE` 或 `CAPTURE` 成功后由 Onerway 自动分账；不设置时由服务端通过 API 发起分账。需要接收自动分账及自动分账回退通知时，同时设置 `profitShareNotifyUrl`。`paymentMethodOptions` 按接口要求以 JSON 字符串提交。

自动分账与通过 API 发起分账的选择、结果通知、查询与分账回退见[分账](/zh/payments/online-payments/scenarios/profit-sharing)。
