# Web SDK integration

> Create a server-side payment, initialize the Web SDK with paymentId, and handle custom buttons, wallets, redirects, and final result verification.

A Web SDK integration has two parts: your server creates the payment and stores the order-to-`paymentId` mapping; the browser loads the SDK and creates Checkout with that `paymentId`. Keep the `secret`, request signing, customer mapping, and final payment verification on the server.

Before you begin, follow [Setup](/payments/get-started/setup) to obtain your API credentials and add your server's public outbound IP addresses to the allowlist for the target environment. Generate `sign` for each request according to [Request signing](/payments/get-started/request-signing). Before going live, follow [Sandbox testing](/payments/get-started/testing) to verify all scenarios used by your integration in the sandbox.

The Web SDK uses Onerway's Google Pay integration, so no Google Pay website registration is needed; that applies only to [Direct API integrations](/payments/online-payments/payment-methods/google-pay#website-registration-before-going-live).

## Integration flow

1. Call [Create SDK transaction](/payments/api-reference/endpoints/sdk-create-transaction) from your server and store the returned `paymentId`.
2. Load the CDN script that matches the transaction environment.
3. Create Checkout with `paymentId`, subscribe to events, and then mount the Payment Element.
4. Call `confirmPayment()` for cards, each local payment method, and other custom payment buttons. For SDK-owned Apple Pay and Google Pay buttons, listen only for `payment_result`.
5. Use client events to update the page. Confirm the final status on your server through [Query payments](/payments/api-reference/endpoints/query-payments) or a payment webhook.

## Create a payment on your server

For an ordinary Web SDK v4 payment, submit `productType=ALL`, `subProductType=DIRECT`, and `txnType=SALE` to [Create SDK transaction](/payments/api-reference/endpoints/sdk-create-transaction). The standard one-time payment example omits `billingInformation`, `shippingInformation`, `paymentMode`, and `osType`.

Both `billingInformation` and `shippingInformation` are optional when you create the transaction. You can provide either object when the information is available, or add it through [Update SDK order](/payments/api-reference/endpoints/sdk-update-order) before payment confirmation when your business flow requires it. If you submit an object, its nested required fields and conditions still apply. When updating the order, reuse the `merchantTxnId` of the original transaction creation on your server, and wait for a successful update response before the customer confirms payment.

For the current Web SDK, [`txnOrderMsg`](/payments/api-reference/endpoints/sdk-create-transaction#request-txnOrderMsg) contains only `returnUrl`, `products`, `appId`, `customerPlatform`, `periodValue`, and `notifyUrl`. See the API Reference for whether each field is required and its conditions. Do not collect or send browser, device, or cardholder IP fields from your server; the Web SDK collects that context.

Omit `paymentMode` for ordinary Web SDK payments; you do not need to distinguish desktop and mobile browsers. If you send a value other than `WEB`, you must also send `osType`.

Pass only the `paymentId` from the create-payment response to the browser. You may use `transactionId` for server-side order correlation, but it cannot replace `paymentId` when initializing the SDK. The response also still returns `redirectUrl`; the current Web SDK does not consume it during initialization.

## Load the SDK

The CDN and `environment` must match:

| 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`, the default |

`v4/latest` is the officially recommended long-term Production URL. Although `environment` defaults to `production`, pass it explicitly to prevent environment mix-ups when copying configuration.

```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>
```

## Initialize, subscribe, and mount

```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')
```

Subscribe before calling `mount()`:

- `ready.availablePaymentMethods` is the displayable set determined by the order, server configuration, and browser wallet capabilities. Do not treat example values as a fixed enum. The SDK hides wallet buttons when a wallet is unavailable.
- `loaderror` means that Checkout initialization failed. Branch on `event.error.code`; use `event.error.message` for display only. The only public codes are `checkout_load_failed` (the first load of payment methods failed; let the customer reinitialize) and `no_available_payment_methods` (no payment method remains after filtering; check the server-side configuration and `paymentMethod`).
- Validation failures, payment API errors, wallet cancellation, and 3DS or redirect errors are not `loaderror` events. Handle them through the `confirmPayment()` result or `payment_result`.

## Configure the Payment Element

Every child field of `config` is optional, but each merchant should decide which fields to provide based on its payment-method, form, and branding requirements. Do not assume that the default presentation fits every integration. The SDK reads the configuration when `paymentElement.mount()` runs. Recreate and remount the Payment Element after changing it.

| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `paymentMethod` | `String[]` | Omitted | Display allowlist that retains only methods also returned by the server |
| `showBillingAddress` | `Boolean` | `true` | Whether to display and validate the billing-address form |
| `displayCardholdername` | `Boolean` | `true` | Whether to display and validate the cardholder-name input |
| `walletButtons` | `Object` | See below | Supported appearance settings for the official Apple Pay and Google Pay buttons |
| `checkoutTheme` | `String` | `light` | SDK theme preset; `light` is currently the only public theme |
| `variables` | `Object` | `{}` | Theme-variable overrides and the recommended customization layer |
| `styles` | `Object` | `{}` | CSS-selector overrides with the highest priority |
| `customCssURL` | `String` | SDK default CSS | Replaces the SDK base stylesheet; `variables` and `styles` still apply |

For example, a merchant can display only the methods accepted by its checkout, hide the SDK billing-address form, and apply branding to SDK-controlled content:

```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' }
    }
  }
})
```

### Restrict payment methods

`paymentMethod` only filters the browser presentation. It does not enable a payment method for the merchant:

| Value | Behavior |
| --- | --- |
| Omitted | Display every server-returned method that is available on the current device |
| `['CARD', 'FPX', 'GooglePay']` | Display `CARD`, `FPX`, and `GooglePay` only when the server also returns them |
| `[]` | Display no methods and emit `loaderror` with `event.error.code` set to `no_available_payment_methods` |

Each value must exactly match the payment-method identifier returned by the server. Array order does not control display order; the server payment-method configuration and the SDK wallet region still determine the actual order. Use `ready.availablePaymentMethods` as the final indication that a method loaded successfully.

### Configure form visibility

- `showBillingAddress: false` hides the billing address and stops its client-side validation. It does not change server-side field requirements for Create transaction or Update order. If your business requires billing information, collect it and update the order before payment confirmation.
- `displayCardholdername: false` hides the cardholder name and stops its client-side validation.

### Configure wallet buttons

| Field | Default | Supported values or rule |
| --- | --- | --- |
| `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 size string |
| `googlePay.height` / `applePay.height` | `44px` | CSS size string |
| `googlePay.radius` / `applePay.radius` | `8px` | CSS size string; the Apple / Google platforms may constrain the final appearance |

These options customize only the supported appearance of official SDK wallet buttons. They cannot make a wallet available on an unsupported device, and `variables`, `styles`, or custom CSS cannot force an appearance that the wallet platform does not allow. Setup for wallets, such as Apple Pay domain verification, is covered in [Payment methods](/payments/online-payments/payment-methods).

### Configure themes and styles

- `checkoutTheme` currently supports only `light`.
- `variables` is the recommended branding layer. Common supported variables are `containerBackground`, `cardBackground`, `inputBackground`, `inputBrandBackground`, `aggregateHeaderBackground`, `dialogBackground`, `colorText`, `colorPrimary`, `colorDanger`, `fontFamily`, `fontSizeBase`, and `borderRadius`. `fontSizeBase` accepts `12px`–`24px`; `borderRadius` accepts `0px`–`24px`.
- `styles` maps CSS selectors to CSS property objects, such as `{ '.onerway-checkout__input': { color: '#1a202c' } }`, for local adjustments that variables cannot express.
- `customCssURL` replaces the SDK base stylesheet; `variables` and `styles` can still override it.

Themes and styles affect SDK-controlled content only. The legacy `showPayButton` and `payButtonText` options are no longer supported and are ignored by the SDK.

## Locale

When `locale` is omitted, the SDK uses the browser language. If the explicit value or browser language is not supported by the current payment method, the SDK falls back to English (`en`).

Non-wallet payment methods support the following `locale` values (alphabetical):

| Code | Language | Code | Language |
| --- | --- | --- | --- |
| `ar` | Arabic | `de` | German |
| `en` | English | `es` | Spanish |
| `fi` | Finnish | `fr` | French |
| `it` | Italian | `ja` | Japanese |
| `ko` | Korean | `nl` | Dutch |
| `no` | Norwegian | `pl` | Polish |
| `pt` | Portuguese | `ru` | Russian |
| `sv` | Swedish | `th` | Thai |
| `zh-cn` | Simplified Chinese | `zh-tw` | Traditional Chinese |

Wallets use their own locale enums and casing. The two wallets support the same locales except for these differences:

| Support | Locales |
| --- | --- |
| Both wallets | `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 only | `bg`, `et`, `sl`, `sr` |
| Apple Pay only | `he`, `hi`, `hu`, `ro`, `vi`, `zh-TW` |

Wallets also fall back to English (`en`) when a locale is unsupported. For example, Simplified Chinese is `zh-cn` for non-wallet methods and `zh` for wallets; Traditional Chinese is `zh-tw` for non-wallet methods and `zh-TW` for wallets. Do not invent conversions for values that are not listed.

## Confirm the payment

### Cards, local payment method flows, and custom buttons

These payment methods require your button to call `confirmPayment()`:

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

Do not create another Checkout or another order when retrying. Keep the same `paymentId`, Checkout, and Payment Element while the payment remains retryable.

### SDK-owned Apple Pay and Google Pay buttons

SDK-owned wallet buttons cannot call `confirmPayment()`. After the customer clicks an official button rendered by the SDK, receive the client result only through `payment_result`:

```js
checkout.on('payment_result', (result) => {
  if (result.reason?.type === 'canceled') {
    // The customer closed the wallet. Restore the UI; do not map this to a payment failure.
    return
  }

  renderClientResult(result)
})
```

Wallet visibility also depends on server configuration, browser, device, and wallet capabilities.

### Google Pay in embedded app WebViews

Google Pay availability is capability-detected: when the requirements are not met, `ready.availablePaymentMethods` does not include `GooglePay` and the SDK does not render its button. This is expected behavior, not a failure.

- Android WebView officially supports Google Pay, but the host app must cooperate: Android WebView 137+, Google Play services 25.18.30+, the `androidx.webkit:webkit:1.14.0` dependency, the Chromium payment intent actions declared in the manifest, the Payment Request API enabled, and the app integration published to Google. A custom User-Agent must append `GOOGLE_PAY_SUPPORTED`. See the [official Google WebView guide](https://developers.google.com/pay/api/android/guides/recipes/using-android-webview).
- Embedded WebViews on iOS do not support Google Pay.
- When the host app does not meet these requirements, break the payment flow out to the system browser and make sure the customer is guided back to the app after payment.

## `paymentStatus` and `nextAction`

The SDK returns `nextAction` only when `paymentStatus === 'R'`:

| `nextAction.type` | SDK behavior | Merchant action |
| --- | --- | --- |
| `PresentToShopper` | The SDK presents a QR code, local payment page, or another handoff UI on the current page. There is no final result yet. | Keep the current Checkout and wait for `payment_result`. Do not call `confirmPayment()` again. |
| `RedirectShopper` | The SDK is about to redirect to an external payment page. There is no final result yet. 3DS uses this flow. | Wait for Onerway to return the customer to the request's `returnUrl`, then call [Query payments](/payments/api-reference/endpoints/query-payments) on the server using the stored `paymentId`. |

`R` means that the payment flow must continue; it does not mean success or failure. `nextAction` is absent when `paymentStatus !== 'R'`.

See [the `paymentStatus` response field](/payments/api-reference/endpoints/sdk-create-transaction#response-data-paymentStatus) for the full set of values and their definitions. Use client statuses only to update the page; do not fulfill, credit, or account for an order from them alone:

| `paymentStatus` | Merchant action |
| --- | --- |
| `I`, `U`, `P`, `A` | Not a final state. Keep the order pending; do not fulfill. |
| `R` | The flow continues in an SDK handoff or redirect. Read `nextAction.type` and follow the table above. |
| `O` | The payment can continue or be retried. Keep the same `paymentId`, Checkout, and Payment Element; do not create another order. |
| `S`, `N` | A client-received result. Verify on the server before completing the order or updating its display. |

Client exceptions that do not produce a payment status are described by `reason`:

| `reason.type` | Meaning |
| --- | --- |
| `validation_error` | Local form validation failed; no payment status was produced |
| `sdk_error` | An SDK local state, configuration, or invocation problem |
| `api_error` | The payment API or a backend business call failed; `reason.code` is the backend's original `respCode` — see [Response codes](/payments/api-reference/response-codes) |
| `canceled` | The customer canceled the current interaction; `reason.code` is `presenter_closed` (closed the SDK-presented QR code or local payment dialog), `cvv_closed` (closed the second card verification code dialog after Google Pay authorization), or `wallet_canceled` (canceled the Apple Pay or Google Pay authorization sheet) |

A cancellation may carry no `paymentStatus`, only `reason.type === 'canceled'`. Canceling the client flow is not a final payment failure; restore the page so the customer can retry.

## Verify the final payment result

The `confirmPayment()` result, `payment_result`, and `returnUrl` can drive client navigation only; the final result is determined by webhooks: the [payment result webhook](/payments/api-reference/webhooks/payment-result) for ordinary payments, the [saved payment method result webhook](/payments/api-reference/webhooks/payment-method-result) when the customer opts in to saving a card, the [subscription payment webhook](/payments/api-reference/webhooks/subscription-payment) for subscriptions, and the [authorization, capture, and void webhook](/payments/api-reference/webhooks/authorization-capture) for pre-authorizations. Signature verification, acknowledgement and retries, deduplication, status interpretation, and query fallback follow the shared rules in [Webhooks](/payments/get-started/webhooks).

Additional considerations for the Web SDK integration: store the merchant-order-to-`paymentId` mapping on your server. After a redirect returns to `returnUrl`, do not trust a payment status in the URL — restore the page to a “confirming” state and, if no webhook has arrived, fall back to [Query payments](/payments/api-reference/endpoints/query-payments) with the `paymentId` from your server and return the business result to the client. Do not store unredacted `rawResult`, request or response payloads, or payment data in browser logs, persistent storage, or analytics.

## Saved cards and subscriptions

### Let the customer choose whether to save a card

Keep [`subProductType`](/payments/api-reference/endpoints/sdk-create-transaction#request-subProductType) at `DIRECT` and provide a stable [`merchantCustId`](/payments/api-reference/endpoints/sdk-create-transaction#request-merchantCustId). The SDK presents the save-card choice to the customer; it is not selected by default. Reuse the same `merchantCustId` for later payments, and the SDK displays the customer's saved cards and completes saved-card selection and payment internally; your client does not need a `tokenId` for that flow.

Additional considerations for the Web SDK integration: `subProductType=TOKEN` belongs to the legacy Web SDK save-card flow and is not used for the current Web SDK's customer-controlled save-card flow. The requirements for `merchantCustId`, the saved payment method result webhook, and listing and deleting saved tokens are covered in [Saved payment methods](/payments/online-payments/scenarios/saved-payment-methods).

### Create the initial payment for a fixed plan

Use `subProductType=SUBSCRIBE` for the initial subscription payment. Keep the allowed plan mapping on your server and send a stable, readable [`subscription.productName`](/payments/api-reference/endpoints/sdk-create-transaction#request-subscription-productName); the billing model is determined by [`subscription.selfExecute`](/payments/api-reference/endpoints/sdk-create-transaction#request-subscription-selfExecute). For a managed card subscription, you can also send [`subscription.bindCard`](/payments/api-reference/endpoints/sdk-create-transaction#request-subscription-bindCard) as `true` to save the customer card when the subscription succeeds, in which case you receive two separate notifications.

Choosing between managed and self-managed subscriptions, contract credentials, lifecycle notifications, renewals and plan changes, and handling the two notifications of a subscription that also saves the card are covered in [Subscription payments](/payments/online-payments/scenarios/subscriptions).

## Pre-authorization

Send [`txnType`](/payments/api-reference/endpoints/sdk-create-transaction#request-txnType) as `AUTH` and keep `subProductType` at `DIRECT` to perform a pre-authorization: the order amount is held on the customer's card without an immediate charge, and the SDK integration flow is the same as an ordinary payment. After the pre-authorization succeeds, store the `transactionId` and `paymentId` from the response; your server later captures or voids through [Capture or void authorization](/payments/api-reference/endpoints/capture-or-void-authorization).

Scope, the lifecycle from authorization to capture or void, notifications, boundaries, and status handling are covered in [Pre-authorization and capture](/payments/online-payments/scenarios/pre-authorization).

## Profit sharing

Profit sharing applies to the platform model: the platform merchant creates the payment with the receiving sub-merchant's `merchantNo`, and the payment is eligible for profit sharing only when you submit the [`paymentMethodOptions`](/payments/api-reference/endpoints/sdk-create-transaction#request-paymentMethodOptions) with `profitShare=true` in its `share` object; the SDK integration flow is the same as an ordinary payment. When you also set `profitShareRate`, Onerway allocates funds automatically after a successful `SALE` or `CAPTURE`; without it, your server initiates profit sharing through the API. To receive automatic profit share and reversal notifications, also set `profitShareNotifyUrl`. Submit `paymentMethodOptions` as a JSON string as required by the API.

Choosing between automatic and API-initiated profit sharing, result notifications, queries, and reversals are covered in [Profit sharing](/payments/online-payments/scenarios/profit-sharing).
