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

# Customize Checkout Behavior

> Configure payment methods, validation rules, and callbacks to optimize checkout form completion rates.

## When and Why to Configure Behavior

Configure checkout form behavior to optimize for your specific business requirements and user demographics. Strategic configuration helps reduce payment failures while improving user experience.

<CardGroup cols={2}>
  <Card title="Card Network Filtering" icon="credit-card">
    Optimize payment acceptance by controlling which card brands to support. Reduce processing fees and improve conversion rates by accepting only the most relevant cards for your target markets and business agreements.
  </Card>

  <Card title="Payment Method Priority" icon="list-ordered">
    Payment method ordering and priority are automatically managed through the PayNext Dashboard. The SDK automatically optimizes for user context and device capabilities.
  </Card>

  <Card title="Callback Integration" icon="bell">
    Build robust payment workflows with comprehensive event handling. Implement custom business logic, analytics tracking, and user experience flows that respond to payment success, failures, and different payment methods.
  </Card>

  <Card title="Environment Management" icon="server">
    Ensure reliable deployments across all environments with proper configuration management. Set up environment-specific validation, error handling, and testing strategies that prevent production issues and maintain security.
  </Card>
</CardGroup>

***

## Configure via `PayNextConfig`

Configure checkout form behavior through the `PayNextConfig` interface:

<Note>
  Create the instance with `new PayNextCheckout()` and pass the element ID as the first parameter and your assembled `PayNextConfig` as the second parameter into `checkout.mount('element-id', { ...config })` as demonstrated in [Mount the Checkout](/sdk-reference/introduction/getting-started#mount-the-checkout). Apply the configuration fragments below before calling `mount`.
</Note>

<Expandable title="Core Configuration Properties">
  <ParamField path="clientToken" type="string" required>
    Client session token from your server endpoint. See [Create a Client Session](/sdk-reference/introduction/getting-started#create-a-client-session) for server-side setup guidance.
  </ParamField>

  <ParamField path="environment" type="'sandbox' | 'production'" required>
    Processing environment. Use `sandbox` for testing, `production` for live payments. Review [Environment Types](/sdk-reference/web-sdk/constants/environment-types) for recommended usage.
  </ParamField>

  <ParamField path="apiVersion" type="string" required>
    PayNext SDK/API version (e.g., `"1.0.0"`).
  </ParamField>

  <ParamField path="variant" type="'default' | 'compact'">
    Layout variant: `default` shows all methods, `compact` prioritizes digital wallets. Compare outcomes in [Layout Variants](/sdk-reference/web-sdk/customization/behavior#layout-variants).
  </ParamField>

  <ParamField path="locale" type="string">
    Language code (e.g., `en`, `es`). Defaults to browser language detection. Explore localization strategies in [Customize Text & Translations](/sdk-reference/web-sdk/customization/text-and-translations). Updatable at runtime — without re-mounting — via `checkout.update({ locale })` in SDK `1.1.0` and later. See [Update Configuration at Runtime](/sdk-reference/web-sdk/customization/behavior#update-configuration-at-runtime).
  </ParamField>

  <ParamField path="errorMessageText" type="string">
    Override the localized global error banner that appears when checkout initialization fails. Provide custom copy if you need to match brand tone or include support instructions. See [Override the Global Error Banner](/sdk-reference/web-sdk/customization/text-and-translations#override-the-global-error-banner) for implementation details.
  </ParamField>

  <ParamField path="returnUrl" type="string">
    URL to return users after off-site steps. Use a hash to auto-scroll back to an embedded checkout (e.g., `https://your-site.com/checkout/return#paynext-checkout`).
  </ParamField>

  <ParamField path="paymentsEnabled" type="boolean" default="true">
    Available in SDK `1.0.19` and later. Gates every payment button. When `false`, tapping any button (Google Pay, PayPal, Apple Pay, card, etc.) does **not** start a payment and does **not** open any provider sheet or popup; the card and Pix forms may still expand, but submitting them is blocked. Each blocked interaction fires [`onCheckoutBlocked`](/sdk-reference/web-sdk/customization/behavior#configure-via-paynextconfig). Button appearance is unchanged — there is no visual disabled state. Defaults to `true` (payments work) so existing integrations are unaffected. Toggle at runtime — without re-mounting — with `checkout.setPaymentsEnabled(enabled)`, or through the unified `checkout.update({ paymentsEnabled })` channel (SDK `1.1.0`+); both are equivalent. See [Gate Payments Behind a Consent Checkbox](/sdk-reference/web-sdk/customization/behavior#gate-payments-behind-a-consent-checkbox).
  </ParamField>

  <ParamField path="acceptedCardNetworks" type="Partial<Record<PaymentMethod, CardType[]>>">
    Available in SDK `1.2.0` and later. Restricts the accepted card networks **per payment method** — a map keyed by [`PaymentMethod`](/sdk-reference/web-sdk/constants/payment-method-types) (only `CARD`, `APPLEPAY`, and `GPAY` are read), each value a list of [`CardType`](/sdk-reference/web-sdk/constants/card-networks). A non-empty list **overrides** the networks configured for that method in your dashboard — it can narrow **or** widen the set — bounded to what the method technically supports: the card form supports Visa, Mastercard, Discover, and JCB, while Apple Pay and Google Pay additionally support American Express (`CardType.AmericanExpress`). An empty array `[]`, a missing key, or a list containing only networks the method doesn't support falls back to the configured default — a method is never left with zero networks and its button is never hidden. Read once at mount; not updatable via `checkout.update(...)`. See [Restrict Card Networks per Payment Method](/sdk-reference/web-sdk/customization/behavior#restrict-card-networks-per-payment-method).
  </ParamField>

  <ParamField path="onCheckoutLoaded" type="(result: LoadedResult) => void">
    Fires once after the checkout UI finishes loading. Use it to hide skeletons, log the `LoadedResult.error` when `success` is `false`, or trigger follow-up requests once the form is ready. See callback orchestration patterns in [Integration Patterns](/sdk-reference/web-sdk/customization/advanced-usage).
  </ParamField>

  <ParamField path="onCheckoutAttempt" type="(result: AttemptResult) => void">
    Fires whenever a user starts a payment attempt (e.g., selecting Apple Pay or Cash App Pay). The callback receives `{ paymentMethod, cardType }`, where `paymentMethod` can be `'CARD'`, `'APPLEPAY'`, `'GPAY'`, `'PAYPAL'`, `'VENMO'`, or `'CASHAPP'` (for Stripe only). For card payments, `cardType` contains the brand string such as `\"visa\"`, `\"mastercard\"`, etc. For digital wallet payments (including Cash App Pay), `cardType` is an empty string. Useful for analytics or disabling UI while processing. See callback orchestration patterns in [Integration Patterns](/sdk-reference/web-sdk/customization/advanced-usage).
  </ParamField>

  <ParamField path="beforeCheckoutAttempt" type="(result: AttemptResult) => boolean | Promise<boolean>">
    Available in SDK `1.0.17` and later. Runs immediately before each payment attempt and gates the request. Return `true` (or a promise that resolves to `true`) to let the SDK proceed with the payment. Return `false` (or a promise that resolves to `false`) to block the attempt — the SDK fires `onCheckoutFail` with `status: 'blocked'` and sends no payment request. The callback receives the same `{ paymentMethod, cardType }` payload as `onCheckoutAttempt`. If the callback throws or its promise rejects, the SDK fires `onCheckoutFail` with `status: 'sdk_failed'`. Omit this field to keep the default behavior (no gate, payment proceeds normally). Use it for last-mile checks such as duplicate-subscription detection or product-side card validation. See the gating pattern in [Integration Patterns](/sdk-reference/web-sdk/customization/advanced-usage).
  </ParamField>

  <ParamField path="onCheckoutComplete" type="(result: PaymentResult) => void">
    Callback fired when the checkout flow finishes successfully on the client — use it for UX only (redirecting to a confirmation page, clearing the cart, analytics). It is **not** a settlement confirmation, and it is **not guaranteed to run**: it fires in the customer's browser, so a closed or backgrounded tab or a lost connection can prevent it — this affects every payment method (seen on PayPal and Apple Pay). For asynchronous methods such as [Pix](/integrations/payment-methods/pix-automatico) the payment can also still be settling when it fires. Drive order fulfillment from [payment webhooks](/webhooks/introduction/getting-started), the source of truth for the final payment status. The `PaymentResult` includes payment details, customer information, and payment method data. Use the server-side webhook payload (or your own payment storage) if you need processor-specific metadata such as Stripe customer/payment method IDs for Cash App Pay. See callback orchestration patterns in [Integration Patterns](/sdk-reference/web-sdk/customization/advanced-usage).
  </ParamField>

  <ParamField path="onCheckoutFail" type="(error: CheckoutError) => void">
    Callback fired when payment fails or encounters an error. See callback orchestration patterns in [Integration Patterns](/sdk-reference/web-sdk/customization/advanced-usage). The `CheckoutError` provides a `status` (`'declined'` or `'failed'`) and a structured `status_reason` with `advice_code`, an optional `decline_code`, and a localized `message`.
  </ParamField>

  <ParamField path="onCheckoutBlocked" type="(result: AttemptResult) => void">
    Available in SDK `1.0.19` and later. Fires when a user interacts with a payment button — or submits the card/Pix form — while [`paymentsEnabled`](/sdk-reference/web-sdk/customization/behavior#configure-via-paynextconfig) is `false`. The callback receives the same `{ paymentMethod, cardType }` payload as `onCheckoutAttempt`, so you can tell which button was tapped and react (for example, highlight your own consent checkbox). No payment is started and no provider sheet opens. See [Gate Payments Behind a Consent Checkbox](/sdk-reference/web-sdk/customization/behavior#gate-payments-behind-a-consent-checkbox).
  </ParamField>
</Expandable>

<Tip>
  Pair `onCheckoutLoaded` with a loading spinner or skeleton to ensure the checkout only appears once assets and payment methods are ready. If the callback reports `success: false`, show a retry CTA and log the included `CheckoutError`.
</Tip>

### Supported Card Networks

<CardGroup cols={2}>
  <Card title="Visa" icon="credit-card">
    CardType.Visa
  </Card>

  <Card title="Mastercard" icon="credit-card">
    CardType.Mastercard
  </Card>

  <Card title="JCB" icon="credit-card">
    CardType.JCB
  </Card>

  <Card title="Discover" icon="credit-card">
    CardType.Discover
  </Card>

  <Card title="American Express" icon="credit-card">
    CardType.AmericanExpress — Apple Pay / Google Pay only, not accepted on the card form
  </Card>
</CardGroup>

### Restrict Card Networks per Payment Method

Available in SDK `1.2.0` and later. Pass [`acceptedCardNetworks`](/sdk-reference/web-sdk/customization/behavior#configure-via-paynextconfig) at mount to control which card networks each payment method accepts. This is useful when your business agreements or per-customer rules differ from your global dashboard configuration.

```typescript theme={"system"}
import { PayNextCheckout, PaymentMethod, CardType } from "@paynext/sdk";

const checkout = new PayNextCheckout();

checkout.mount("paynext-checkout", {
  clientToken,
  environment: "production",
  apiVersion: "1.0.0",
  acceptedCardNetworks: {
    [PaymentMethod.CARD]:       [CardType.Visa, CardType.Mastercard],
    [PaymentMethod.APPLE_PAY]:  [CardType.Visa, CardType.AmericanExpress],
    [PaymentMethod.GOOGLE_PAY]: [], // empty -> falls back to the dashboard default
  },
});
```

How each method resolves:

* **A non-empty list overrides** the dashboard configuration for that method — it can both narrow and widen the accepted set, bounded to what the method technically supports. Networks the method doesn't support are dropped (for example, `CardType.AmericanExpress` on the `CARD` method).
* **An empty array `[]`, a missing key, or a list of only unsupported networks** falls back to the dashboard default. A method is never left with zero networks, and its button is never hidden.
* Keys other than `CARD`, `APPLEPAY`, and `GPAY` are ignored.

<Note>
  `acceptedCardNetworks` is read once when the checkout mounts. It is **not** part of the runtime [`checkout.update(...)`](/sdk-reference/web-sdk/customization/behavior#update-configuration-at-runtime) surface — change it by re-mounting with a new configuration.
</Note>

***

## Advanced Patterns

### Gate Payments Behind a Consent Checkbox

Use `paymentsEnabled` when payment must be blocked until an external condition is met — for example, the customer ticking a terms or consent checkbox that lives in **your** UI, outside the SDK.

While `paymentsEnabled` is `false`:

* Tapping any payment button (Google Pay, PayPal, Apple Pay, etc.) does **not** start a payment and does **not** open the provider's sheet or popup.
* The card and Pix forms can still **expand**, but submitting them is blocked.
* Every blocked interaction fires `onCheckoutBlocked` with `{ paymentMethod, cardType }`, so you know which button was tapped and can react (e.g. highlight your checkbox).
* Buttons render **identically** — there is no visual disabled state.

The flag defaults to `true`, so omitting it keeps the normal payment flow. Toggle it at runtime with `checkout.setPaymentsEnabled(enabled)` — no re-mount required.

```tsx theme={"system"}
const checkout = new PayNextCheckout()

await checkout.mount('paynext-checkout', {
  ...config,
  // Start blocked until the customer accepts your terms.
  paymentsEnabled: termsAccepted,
  onCheckoutBlocked: ({ paymentMethod }: AttemptResult) => {
    // The user tried to pay before accepting — draw attention to your checkbox.
    highlightTermsCheckbox()
    console.info('Payment blocked, consent not given:', paymentMethod)
  },
})

// When the customer toggles your checkbox, push the new state to the SDK.
// This takes effect immediately, without re-mounting.
termsCheckbox.addEventListener('change', (event) => {
  checkout.setPaymentsEnabled((event.target as HTMLInputElement).checked)
})
```

<Note>
  `paymentsEnabled` controls **whether** a payment can be initiated; [`beforeCheckoutAttempt`](/sdk-reference/web-sdk/customization/behavior#configure-via-paynextconfig) runs a last-mile async check **at the moment** of an attempt and can reject it. Use `paymentsEnabled` for a standing on/off gate tied to your own UI state, and `beforeCheckoutAttempt` for per-attempt validation.
</Note>

### Update Configuration at Runtime

<Info>
  `checkout.update(config)` is available in SDK `1.1.0` and later. Upgrade `@paynext/sdk` if your project pins an earlier version.
</Info>

After mount, call `checkout.update(config)` to change selected config fields **without re-mounting**. Only what changed re-renders, and form state (entered card data, open forms) is preserved — so switching language or theme no longer needs an `unmount()` + `mount()` cycle.

Fields you can update at runtime:

| Field             | Effect                                                      |
| :---------------- | :---------------------------------------------------------- |
| `locale`          | Re-translates the UI in place.                              |
| `translate`       | Applies translation overrides in place.                     |
| `theme`           | Switches `light` / `dark` / `system` without re-mount.      |
| `paymentsEnabled` | Same gate as `setPaymentsEnabled` — both share one channel. |

```tsx theme={"system"}
const checkout = new PayNextCheckout()

await checkout.mount('paynext-checkout', { ...config, locale: 'en', theme: 'light' })

// Later — e.g. the customer switches language or toggles dark mode in your UI.
// Each call applies immediately, without re-mounting, and keeps form state.
checkout.update({ locale: 'es' })
checkout.update({ theme: 'dark' })
checkout.update({ paymentsEnabled: false })
```

<Note>
  `clientToken`, `environment`, and `apiVersion` **cannot** be updated at runtime — they initialize the API client, so changing them requires a fresh `mount`. TypeScript rejects them in `update()`; passing them from plain JavaScript logs a console warning and is ignored (it never throws). `variant`, `styles`, and the callback handlers are likewise fixed at mount.
</Note>

### Digital Wallet Configuration

Digital wallet availability (Apple Pay, Google Pay, PayPal, Venmo, Cash App Pay) depends on **both** your dashboard configuration **and** the user's device/browser capabilities.

<Info>
  All digital wallet configurations (merchant names, supported networks, authentication settings) are managed in your PayNext dashboard. No code changes are required when enabling or disabling payment methods.
</Info>

### Payment Method Priority

<Info>
  **Set priority directly in the PayNext Dashboard (Checkout → Payment methods).** Drag to reorder wallets, toggle them on/off, and add conditions for card visibility. The SDK reads this configuration on every load—no code changes or redeploys required.
</Info>

**How Ordering Works:**

1. The dashboard order becomes the exact order shown in the SDK (top to bottom).
2. If you never customized the list, PayNext falls back to its default order: digital wallets → PayPal → cards.
3. When a method is disabled or hidden by conditions, the SDK closes the gap and keeps the remaining order intact.

**Automatic Optimization Features (handled by the SDK):**

* Shows payment methods available to the customer's device/browser (e.g., hides Apple Pay on Windows)
* Applies your dashboard ordering after filtering out unsupported methods
* Respects your conditions per processor/country/currency
* Automatically hides methods when a processor disconnects or the customer's device can't use it.

<Tip>
  Reordering the list in the dashboard is the quickest way to run A/B tests on wallet prominence—no SDK updates are necessary.
</Tip>

#### Understanding Payment Method Availability

The SDK determines payment method availability using multiple factors:

<AccordionGroup>
  <Accordion title="Device Capabilities">
    * **Apple Pay**: Safari on iOS/macOS with Apple Pay configured
    * **Google Pay**: Chrome/Chromium browsers with Google Pay enabled
    * **PayPal**: Shown when enabled in your dashboard
    * **Venmo**: Shown for US users on desktop browsers (via QR code) and mobile browsers (via deep link or web fallback)
    * **Cash App Pay**: Available when Stripe + Cash App Pay are enabled and the customer is in a supported region/device context
    * **Amazon Pay**: Available when Stripe + Amazon Pay are enabled and the customer is in a supported region/device context
  </Accordion>

  <Accordion title="Geographic Optimization">
    * **North America**: Prioritizes Apple Pay, Google Pay, PayPal, Venmo
    * **Europe**: Emphasizes Apple Pay, Google Pay, PayPal, SEPA
    * **Asia**: Focuses on local payment methods and digital wallets
    * **Global**: Falls back to major card networks (Visa, Mastercard)
  </Accordion>

  <Accordion title="User Behavior Patterns">
    * **Returning users**: May see previously used payment methods first
    * **Mobile users**: Digital wallets prioritized over card forms
    * **Desktop users**: Traditional card forms are more prominent
    * **High-value transactions**: May show premium payment options first
  </Accordion>
</AccordionGroup>

### Layout Variants

Control how payment methods are initially presented:

<Tabs>
  <Tab title="Default Layout">
    ```tsx theme={"system"}
    // When mounting (see Getting Started "Mount the Checkout" section):
    const checkout = new PayNextCheckout()
    await checkout.mount('checkout-container', {
      ...config,
      variant: 'default',
    })
    ```

    <Note>
      Refer to [Mount the Checkout](/sdk-reference/introduction/getting-started#mount-the-checkout) for the complete mounting flow.
    </Note>

    **Use cases:**

    * Desktop-focused experiences
    * Users are comfortable with traditional forms
    * When screen space is abundant
    * Clear overview of all payment options needed
  </Tab>

  <Tab title="Compact Layout">
    ```tsx theme={"system"}
    // When mounting (see Getting Started "Mount the Checkout" section):
    const checkout = new PayNextCheckout()
    await checkout.mount('checkout-container', {
      ...config,
      variant: 'compact',
    })
    ```

    <Note>
      Refer to [Mount the Checkout](/sdk-reference/introduction/getting-started#mount-the-checkout) for the complete mounting flow.
    </Note>

    **Use cases:**

    * Mobile-first experiences
    * Digital wallet adoption priority
    * Cleaner, less cluttered interface
    * Users prefer quick payment methods

    <Tip>
      Compact mode can increase digital wallet usage by 15-25% by featuring them prominently.
    </Tip>
  </Tab>
</Tabs>

### Success States and Decline Messages

The SDK automatically handles success states and localized decline fallback messaging across all devices and browsers.

**Success States**

The SDK automatically displays success confirmations when payments are completed successfully. No additional configuration is required.

<Note>
  The on-screen success confirmation reflects that the checkout flow finished — it is a UX signal, not a guarantee that funds have settled. It also renders in the customer's browser, so it isn't guaranteed to be seen: a closed or backgrounded tab or a lost connection can prevent it, on any payment method. Fulfill orders from [payment webhooks](/webhooks/introduction/getting-started), the source of truth for the final payment status. For asynchronous methods such as [Pix](/integrations/payment-methods/pix-automatico) the payment can also still be settling when the confirmation appears.
</Note>

**Decline Messages**

When a payment is declined, the SDK displays a single localized fallback message regardless of the specific decline reason:

<Warning>
  "We are unable to authenticate your payment method. Please choose a different payment method and try again."
</Warning>

This consistent messaging approach simplifies the user experience while maintaining security by not exposing specific decline details

***

## Best Practices

### Environment Configuration

Configure different behavior per environment:

```tsx environment-detection.ts theme={"system"}
import { PayNextCheckout, type PayNextConfig } from '@paynext/sdk'

export async function mountEnvironmentAwareCheckout(containerId: string) {
  const isProduction = process.env.NODE_ENV === 'production'
  const isDevelopment = process.env.NODE_ENV === 'development'
  
  // Environment-specific configuration
  const config: PayNextConfig = {
    clientToken: isProduction 
      ? process.env.NEXT_PUBLIC_PAYNEXT_PROD_TOKEN!
      : process.env.NEXT_PUBLIC_PAYNEXT_SANDBOX_TOKEN!,
    environment: isProduction ? 'production' : 'sandbox', // 'sandbox' | 'production'  
    variant: isProduction ? 'compact' : 'default',
    /* other options */
  }
  
  const checkout = new PayNextCheckout()
  await checkout.mount(containerId, config)
  return checkout
}
```

### Form Validation and Error Handling

The SDK provides real-time validation for all payment fields:

<AccordionGroup>
  <Accordion title="Card Number Validation">
    * **Format validation**: Checks card number format in real-time
    * **Card type detection**: Automatically detects Visa, Mastercard, etc.
    * **Luhn algorithm**: Validates card number using industry standard
    * **Length verification**: Ensures correct number of digits
  </Accordion>

  <Accordion title="Expiry Date Validation">
    * **Format checking**: MM/YY format validation
    * **Date logic**: Validates month (01-12) and future dates
    * **Expiry detection**: Prevents expired card acceptance
    * **Input formatting**: Auto-formats as user types
  </Accordion>

  <Accordion title="Security Code (CVC) Validation">
    * **Length validation**: 3 digits for most cards, 4 for Amex
    * **Numeric verification**: Ensures only numbers are entered
    * **Real-time feedback**: Immediate validation as user types
    * **Card-specific rules**: Adapts based on detected card type
  </Accordion>
</AccordionGroup>

### Common Pitfalls

Avoid these behavior configuration mistakes:

* **Overly restrictive card filtering** that excludes common payment methods
* **Missing error handling** for network or payment failures
* **Exposing sensitive data** in client-side error messages
* **Not testing across environments** before production deployment
* **Ignoring mobile-specific considerations** for payment method availability

***

<Warning>
  Test all behavior configurations thoroughly in a sandbox environment before deploying to production
</Warning>
