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

# Configuring checkout

> Set component properties with `setAttribute` and SDK options with `.options`.

Primer Checkout uses platform-specific configuration to control checkout behavior, appearance, and payment handling.

> [tab] Web
>
> Primer Checkout Web uses two types of configuration: **component properties** (HTML attributes) and **SDK options** (JavaScript object).
>
> ```javascript theme={"dark"}
> const checkout = document.querySelector('primer-checkout');
>
> // Component properties - use setAttribute()
> checkout.setAttribute('client-token', 'your-token');
>
> // SDK options - assign directly
> checkout.options = {
>   locale: 'en-GB',
>   card: { cardholderName: { required: true } }
> };
> ```

> [tab] Android
>
> The Android SDK uses `PrimerSettings` to configure checkout behavior. Pass it when creating the checkout controller:
>
> ```kotlin theme={"dark"}
> val checkout = rememberPrimerCheckoutController(
>     clientToken = clientToken,
>     settings = PrimerSettings().apply {
>         locale = Locale("fr", "FR")
>         uiOptions.dismissalMechanism = listOf(DismissalMechanism.CLOSE_BUTTON)
>     },
> )
> ```

> [tab] iOS
>
> The iOS SDK uses `PrimerSettings` and `PrimerCheckoutTheme` to configure checkout behavior and appearance. Pass them when creating `PrimerCheckout`:
>
> ```swift theme={"dark"}
> PrimerCheckout(
>   clientToken: clientToken,
>   primerSettings: PrimerSettings(paymentHandling: .auto),
>   primerTheme: PrimerCheckoutTheme(
>     colors: ColorOverrides(primerColorBrand: .blue)
>   )
> )
> ```

***

## Configuration Overview

> [tab] Web
>
> | Type | How to Set | Examples |
> | - | - | - |
> | **Component Properties** | `setAttribute()` | `client-token`, `custom-styles`, `loader-disabled` |
> | **SDK Options** | Direct assignment to `.options` | `locale`, `card`, `vault`, `applePay` |
>
> > [!WARNING]
> >
> > Don't mix them up. Component properties must use `setAttribute()`. SDK options must be assigned directly to `.options`.

> [tab] Android
>
> | Type | How to Set | Examples |
> | - | - | - |
> | **Client token** | `rememberPrimerCheckoutController(clientToken)` | Authentication token |
> | **Settings** | `PrimerSettings` object | `locale`, `uiOptions`, `paymentMethodOptions`, `debugOptions` |
> | **Theme** | `PrimerTheme` parameter on composable | `PrimerCheckoutSheet(theme = ...)` |
>
> > [!NOTE]
> >
> > Theming is **not** part of `PrimerSettings`. Pass `PrimerTheme` directly to `PrimerCheckoutSheet` or `PrimerCheckoutHost`. See [Styling Guide](https://primer.io/docs/checkout/primer-checkout/build-your-ui/styling-customization.md).

> [tab] iOS
>
> | Type | How to Set | Examples |
> | - | - | - |
> | **Client token** | `PrimerCheckout(clientToken:)` | Authentication token |
> | **Settings** | `PrimerSettings` parameter | `paymentHandling`, `uiOptions`, `paymentMethodOptions` |
> | **Theme** | `PrimerCheckoutTheme` parameter | `colors`, `darkColors`, `radius`, `spacing`, `sizes`, `typography`, `width` |
>
> > [!NOTE]
> >
> > Theming is **not** part of `PrimerSettings`. Pass `PrimerCheckoutTheme` as a separate parameter to `PrimerCheckout` or `PrimerCheckoutPresenter`. See [Styling Guide](https://primer.io/docs/checkout/primer-checkout/build-your-ui/styling-customization.md).

***

## Component Properties / Client Token

The client token authenticates your checkout session. Obtain this by [creating a client session](https://primer.io/docs/checkout/client-session.md).

> [tab] Web
>
> ### client-token (required)
>
> ```javascript theme={"dark"}
> // ✅ Correct
> checkout.setAttribute('client-token', 'your-client-token');
>
> // ❌ Wrong - won't work
> checkout.clientToken = 'your-token';
> ```
>
> ```html theme={"dark"}
> <!-- Or set directly in HTML -->
> <primer-checkout client-token="your-client-token"></primer-checkout>
> ```
>
> #### Updating the client token
>
> To start a new session, set a new client token on the existing component. The checkout tears down the current session and initializes the new one in place, so you don't need to reload the page or recreate the component.
>
> ```javascript theme={"dark"}
> checkout.setAttribute('client-token', newClientToken);
> ```
>
> This is a full re-initialization: the loader shows again, entered card details are cleared, payment methods are reloaded, and `primer:ready` fires again with a new PrimerJS instance. Don't change the token while a payment is in progress.
>
> If you updated the existing client session on your server and the token hasn't changed, call [`primer.refreshSession()`](https://primer.io/docs/sdk/primer-checkout-web/events-reference#refreshsession) instead. It syncs the checkout with the session without re-initializing.
>
> ### custom-styles
>
> JSON string containing CSS variables for theming.
>
> ```javascript theme={"dark"}
> const styles = {
>   primerColorBrand: '#4a6cf7',
>   primerTypographyBrand: 'Inter, sans-serif'
> };
>
> checkout.setAttribute('custom-styles', JSON.stringify(styles));
> ```
>
> > See [Styling Guide](https://primer.io/docs/checkout/primer-checkout/build-your-ui/styling-customization.md) for available properties.
>
> ### loader-disabled
>
> Disables the default loading spinner.
>
> ```javascript theme={"dark"}
> checkout.setAttribute('loader-disabled', 'true');
> ```

> [tab] Android
>
> Pass the client token directly when creating the checkout controller:
>
> ```kotlin theme={"dark"}
> val checkout = rememberPrimerCheckoutController(
>     clientToken = clientToken,
> )
> ```

> [tab] iOS
>
> Pass the client token directly when creating `PrimerCheckout`:
>
> ```swift theme={"dark"}
> // SwiftUI
> PrimerCheckout(clientToken: clientToken)
>
> // UIKit
> PrimerCheckoutPresenter.presentCheckout(
>   clientToken: clientToken,
>   from: viewController,
>   primerSettings: PrimerSettings(),
>   primerTheme: PrimerCheckoutTheme()
> )
> ```

***

## SDK Options / Settings

> [tab] Web
>
> SDK options configure checkout behavior. Assign a JavaScript object directly to the `options` property.
>
> ```javascript theme={"dark"}
> // ✅ Correct - assign directly
> checkout.options = {
>   locale: 'en-GB',
>   card: { cardholderName: { required: true } }
> };
>
> // ❌ Wrong - don't use setAttribute for options
> checkout.setAttribute('options', JSON.stringify({...}));
> ```
>
> > [!NOTE]
> >
> > For the complete list of available options, see the [SDK Options Reference](https://primer.io/docs/sdk/primer-checkout-web/sdk-options-reference.md).

> [tab] Android
>
> `PrimerSettings` configures the SDK behavior for a checkout session. Pass it when creating the controller.
>
> ```kotlin theme={"dark"}
> val checkout = rememberPrimerCheckoutController(
>     clientToken = clientToken,
>     settings = PrimerSettings().apply {
>         locale = Locale("fr", "FR")
>         uiOptions.dismissalMechanism = listOf(DismissalMechanism.CLOSE_BUTTON)
>         paymentMethodOptions.redirectScheme = "myapp"
>     },
> )
> ```
>
> > [!NOTE]
> >
> > For the complete list of available options, see the [SDK Options Reference](https://primer.io/docs/sdk/android-checkout/v3.0.0-beta/api/overview.md).

> [tab] iOS
>
> `PrimerSettings` configures the SDK behavior for a checkout session. Pass it when creating `PrimerCheckout`.
>
> ```swift theme={"dark"}
> let settings = PrimerSettings(
>   paymentHandling: .auto,
>   paymentMethodOptions: PrimerPaymentMethodOptions(),
>   uiOptions: PrimerUIOptions()
> )
>
> PrimerCheckout(
>   clientToken: clientToken,
>   primerSettings: settings
> )
> ```
>
> > [!NOTE]
> >
> > For the complete list of available options, see the [SDK Options Reference](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/common/primer-settings.md).

***

## Common Patterns

> [tab] Web
>
> ### Set Locale
>
> ```javascript theme={"dark"}
> checkout.options = {
>   locale: 'en-GB'
> };
> ```
>
> > [Supported locales](https://primer.io/docs/checkout/primer-checkout/configuration/localization.md)
>
> ### Enable Specific Payment Methods
>
> ```javascript theme={"dark"}
> import { PaymentMethodType } from '@primer-io/primer-js';
>
> checkout.options = {
>   enabledPaymentMethods: [
>     PaymentMethodType.PAYMENT_CARD,
>     PaymentMethodType.PAYPAL,
>     PaymentMethodType.ADYEN_BLIK
>   ]
> };
> ```
>
> ### Pre-fill Cardholder Name
>
> ```javascript theme={"dark"}
> checkout.options = {
>   card: {
>     cardholderName: {
>       required: true,
>       visible: true,
>       defaultValue: 'John Doe'
>     }
>   }
> };
> ```
>
> For runtime updates after initialization, use `primer.setCardholderName()` instead.
>
> ### Enable Vault (Saved Payment Methods)
>
> ```javascript theme={"dark"}
> checkout.options = {
>   vault: {
>     enabled: true
>   }
> };
> ```
>
> ### Configure Apple Pay Button
>
> ```javascript theme={"dark"}
> checkout.options = {
>   applePay: {
>     buttonType: 'buy',
>     buttonStyle: 'black'
>   }
> };
> ```
>
> ### Configure Google Pay Button
>
> ```javascript theme={"dark"}
> checkout.options = {
>   googlePay: {
>     buttonType: 'buy',
>     buttonColor: 'black',
>     captureBillingAddress: true
>   }
> };
> ```
>
> ### Configure Klarna
>
> ```javascript theme={"dark"}
> checkout.options = {
>   klarna: {
>     paymentFlow: 'DEFAULT',
>     allowedPaymentCategories: ['pay_now', 'pay_later'],
>     buttonOptions: {
>       text: 'Pay with Klarna'
>     }
>   }
> };
> ```
>
> ### Configure Klarna via Adyen
>
> ```javascript theme={"dark"}
> checkout.options = {
>   adyenKlarna: {
>     buttonOptions: {
>       text: 'Pay with Klarna'
>     }
>   }
> };
> ```

> [tab] Android
>
> ### Set Locale
>
> ```kotlin theme={"dark"}
> settings = PrimerSettings().apply {
>     locale = Locale("fr", "FR")
> }
> ```
>
> Default is `Locale.getDefault()` (device locale). Controls SDK-internal strings and currency formatting.
>
> ### Custom Success/Error Screens
>
> Replace the default success and error screens with your own composables via slot parameters:
>
> ```kotlin theme={"dark"}
> PrimerCheckoutSheet(
>     checkout = checkout,
>     success = { checkoutData ->
>         MyCustomSuccessScreen(checkoutData)
>     },
>     error = { primerError, _ ->
>         MyCustomErrorScreen(primerError)
>     },
> )
> ```
>
> See [Layout Customization](https://primer.io/docs/checkout/primer-checkout/build-your-ui/layout-customization.md) for more slot examples.
>
> ### Configure Dismissal Behavior
>
> ```kotlin theme={"dark"}
> settings = PrimerSettings().apply {
>     uiOptions.dismissalMechanism = listOf(DismissalMechanism.CLOSE_BUTTON)
> }
> ```
>
> | Value | Behavior |
> | - | - |
> | `GESTURES` | Swipe-to-dismiss (default) |
> | `CLOSE_BUTTON` | Explicit close button |
>
> You can combine both: `listOf(DismissalMechanism.GESTURES, DismissalMechanism.CLOSE_BUTTON)`.
>
> ### Configure Google Pay
>
> ```kotlin theme={"dark"}
> settings = PrimerSettings().apply {
>     paymentMethodOptions.googlePayOptions = PrimerGooglePayOptions(
>         merchantName = "My Store",
>         captureBillingAddress = true,
>         emailAddressRequired = true,
>         buttonStyle = GooglePayButtonStyle.BLACK,
>     )
> }
> ```
>
> ### Set Redirect Scheme
>
> Required for redirect-based payment methods (PayPal, Klarna, etc.):
>
> ```kotlin theme={"dark"}
> settings = PrimerSettings().apply {
>     paymentMethodOptions.redirectScheme = "myapp"
> }
> ```

> [tab] iOS
>
> ### Set Payment Handling
>
> ```swift theme={"dark"}
> let settings = PrimerSettings(paymentHandling: .auto)
> ```
>
> | Value | Behavior |
> | - | - |
> | `.auto` | Primer handles the full payment flow (recommended) |
> | `.manual` | Not supported in Primer Checkout. The checkout fails at start with a `PrimerError` whose `errorId` is `invalid-value`. Keep `.auto`. See [PrimerPaymentHandling](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/common/primer-settings.md#primerpaymenthandling). |
>
> ### Toggle Managed Screens
>
> `PrimerCheckout` renders Primer's default init, success, and error screens. Toggle them through `uiOptions` to handle results yourself instead:
>
> ```swift theme={"dark"}
> let settings = PrimerSettings(
>   uiOptions: PrimerUIOptions(
>     isInitScreenEnabled: false,
>     isSuccessScreenEnabled: false,
>     isErrorScreenEnabled: false
>   )
> )
> ```
>
> For fully custom screens, compose your own views under [`PrimerCheckoutSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-session) instead of `PrimerCheckout` and drive them with the session's published state.
>
> ### Configure Theme Colors
>
> ```swift theme={"dark"}
> let theme = PrimerCheckoutTheme(
>   colors: ColorOverrides(
>     primerColorBrand: .blue,
>     primerColorBackgroundPrimary: .white
>   )
> )
> ```
>
> ### Handle Completion
>
> ```swift theme={"dark"}
> PrimerCheckout(
>   clientToken: clientToken,
>   onCompletion: { state in
>     switch state {
>     case .success(let result):
>       print("Payment: \(result.paymentId)")
>     case let .failure(error, _):
>       print("Failed: \(error.localizedDescription)")
>     case .dismissed:
>       print("Dismissed")
>     default:
>       break
>     }
>   }
> )
> ```

***

## Complete Example

> [tab] Web
>
> ```javascript theme={"dark"}
> import { PaymentMethodType } from '@primer-io/primer-js';
>
> const checkout = document.querySelector('primer-checkout');
>
> // 1. Component properties
> checkout.setAttribute('client-token', 'your-client-token');
> checkout.setAttribute('custom-styles', JSON.stringify({
>   primerColorBrand: '#4a6cf7'
> }));
>
> // 2. SDK options
> checkout.options = {
>   locale: 'en-GB',
>   enabledPaymentMethods: [
>     PaymentMethodType.PAYMENT_CARD,
>     PaymentMethodType.PAYPAL
>   ],
>   card: {
>     cardholderName: {
>       required: true,
>       visible: true
>     }
>   },
>   vault: {
>     enabled: true
>   }
> };
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> @Composable
> fun CheckoutScreen(clientToken: String) {
>     val checkout = rememberPrimerCheckoutController(
>         clientToken = clientToken,
>         settings = PrimerSettings().apply {
>             locale = Locale("fr", "FR")
>             uiOptions.dismissalMechanism = listOf(DismissalMechanism.CLOSE_BUTTON)
>             paymentMethodOptions.redirectScheme = "myapp"
>             paymentMethodOptions.googlePayOptions = PrimerGooglePayOptions(
>                 merchantName = "My Store",
>                 captureBillingAddress = true,
>             )
>         },
>     )
>
>     val state by checkout.state.collectAsStateWithLifecycle()
>
>     LaunchedEffect(state) {
>         when (val s = state) {
>             is PrimerCheckoutState.Success -> navigateToConfirmation(s.checkoutData)
>             is PrimerCheckoutState.Failure -> {
>                 Log.e("Checkout", "Failed: ${s.primerError.description}")
>             }
>             else -> Unit
>         }
>     }
>
>     PrimerCheckoutSheet(
>         checkout = checkout,
>         success = { checkoutData -> MySuccessScreen(checkoutData) },
>         error = { primerError, _ -> MyErrorScreen(primerError) },
>         onDismiss = {
>             navigateBack()
>         },
>     )
> }
> ```

> [tab] iOS
>
> ```swift theme={"dark"}
> private let primerSettings = PrimerSettings(
>   paymentHandling: .auto,
>   uiOptions: PrimerUIOptions(
>     dismissalMechanism: [.gestures, .closeButton]
>   )
> )
>
> private let primerTheme = PrimerCheckoutTheme(
>   colors: ColorOverrides(primerColorBrand: .blue),
>   radius: RadiusOverrides(primerRadiusMedium: 12)
> )
>
> struct CheckoutView: View {
>   let clientToken: String
>
>   var body: some View {
>     PrimerCheckout(
>       clientToken: clientToken,
>       primerSettings: primerSettings,
>       primerTheme: primerTheme,
>       onCompletion: { state in
>         switch state {
>         case .success(let result):
>           print("Payment: \(result.paymentId)")
>         case let .failure(error, _):
>           print("Failed: \(error.localizedDescription)")
>         case .dismissed:
>           print("Dismissed")
>         default:
>           break
>         }
>       }
>     )
>   }
> }
> ```

***

## Framework-Specific Patterns

> [tab] Web
>
> > [tab] Vanilla JS
> >
> > ```javascript theme={"dark"}
> > document.addEventListener('DOMContentLoaded', () => {
> >   const checkout = document.querySelector('primer-checkout');
> >   checkout.setAttribute('client-token', 'your-token');
> >   checkout.options = { locale: 'en-GB' };
> > });
> > ```
>
> > [tab] React
> >
> > ```tsx theme={"dark"}
> > const SDK_OPTIONS = { locale: 'en-GB' };
> >
> > function CheckoutPage() {
> >   return (
> >     <primer-checkout
> >       client-token="your-token"
> >       options={SDK_OPTIONS}
> >     />
> >   );
> > }
> > ```
> >
> > > See [React Integration Guide](https://primer.io/docs/checkout/primer-checkout/frameworks/react-integration-guide.md) for stable references and React 18/19 differences.
>
> > [tab] Next.js / SSR
> >
> > ```tsx theme={"dark"}
> > 'use client';
> >
> > import { useEffect } from 'react';
> > import { loadPrimer } from '@primer-io/primer-js';
> >
> > const SDK_OPTIONS = { locale: 'en-GB' };
> >
> > export default function CheckoutPage() {
> >   useEffect(() => {
> >     loadPrimer();
> >   }, []);
> >
> >   return (
> >     <primer-checkout
> >       client-token="your-token"
> >       options={SDK_OPTIONS}
> >     />
> >   );
> > }
> > ```
> >
> > > See [SSR Guide](https://primer.io/docs/checkout/primer-checkout/frameworks/ssr-guide.md) for framework-specific patterns.

> [tab] Android
>
> The Android SDK uses standard Jetpack Compose patterns. Configuration is passed when creating the checkout controller:
>
> ```kotlin theme={"dark"}
> @Composable
> fun CheckoutScreen(clientToken: String) {
>     val checkout = rememberPrimerCheckoutController(
>         clientToken = clientToken,
>         settings = PrimerSettings().apply {
>             paymentMethodOptions.redirectScheme = "myapp"
>         },
>     )
>
>     PrimerCheckoutSheet(checkout = checkout)
> }
> ```
>
> See the [Best Practices](https://primer.io/docs/checkout/primer-checkout/configuration/best-practices.md) guide for Compose-specific patterns.

> [tab] iOS
>
> > [tab] SwiftUI
> >
> > ```swift theme={"dark"}
> > struct CheckoutView: View {
> >   let clientToken: String
> >
> >   var body: some View {
> >     PrimerCheckout(
> >       clientToken: clientToken,
> >       primerSettings: PrimerSettings(paymentHandling: .auto),
> >       onCompletion: { state in
> >         // Handle result
> >       }
> >     )
> >   }
> > }
> > ```
> >
> > > See the [Best Practices](https://primer.io/docs/checkout/primer-checkout/configuration/best-practices.md) guide for SwiftUI-specific patterns.
>
> > [tab] UIKit
> >
> > ```swift theme={"dark"}
> > class CheckoutViewController: UIViewController, PrimerCheckoutPresenterDelegate {
> >   func showCheckout() {
> >     PrimerCheckoutPresenter.shared.delegate = self
> >
> >     PrimerCheckoutPresenter.presentCheckout(
> >       clientToken: clientToken,
> >       from: self,
> >       primerSettings: PrimerSettings(paymentHandling: .auto),
> >       primerTheme: PrimerCheckoutTheme()
> >     )
> >   }
> >
> >   func primerCheckoutPresenterDidCompleteWithSuccess(_ result: PaymentResult) {
> >     // Navigate to confirmation
> >   }
> >
> >   func primerCheckoutPresenterDidFailWithError(_ error: PrimerError, checkoutData: PrimerCheckoutData?) {
> >     print("Failed: \(error.localizedDescription)")
> >   }
> >
> >   func primerCheckoutPresenterDidDismiss() {
> >     print("Dismissed")
> >   }
> > }
> > ```
> >
> > > See [UIKit Integration Guide](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/guides/uikit-integration.md) for more details.

***

## Advanced Configuration

> [tab] Web
>
> > [accordion] Headless Vault
> >
> > Build custom vault UIs while retaining full vault functionality. Enable headless mode:
> >
> > ```javascript theme={"dark"}
> > checkout.options = {
> >   vault: {
> >     enabled: true,
> >     headless: true
> >   }
> > };
> > ```
> >
> > Then use `onVaultedMethodsUpdate`, `vault.createCvvInput()`, and `vault.startPayment()` to build your custom UI.
> >
> > > [Headless Vault Implementation Guide](https://primer.io/docs/sdk/primer-checkout-web/components/primer-vault-manager.md#headless-vault-implementation)
>
> > [accordion] External Submit Button
> >
> > Use your own submit button instead of the built-in one:
> >
> > ```javascript theme={"dark"}
> > checkout.options = {
> >   submitButton: {
> >     useBuiltInButton: false
> >   }
> > };
> >
> > // Trigger submission from your button
> > document.getElementById('my-button').addEventListener('click', () => {
> >   document.dispatchEvent(new CustomEvent('primer:card-submit', {
> >     bubbles: true,
> >     composed: true
> >   }));
> > });
> > ```
> >
> > > [External Submit Button Recipe](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/external-submit-button.md)
>
> > [accordion] Show Amount on Button
> >
> > Display the payment amount on the submit button:
> >
> > ```javascript theme={"dark"}
> > checkout.options = {
> >   submitButton: {
> >     amountVisible: true
> >   }
> > };
> > ```

> [tab] Android
>
> > [accordion] Custom Submit Button
> >
> > Override the submit button slot in `PrimerCardForm`:
> >
> > ```kotlin theme={"dark"}
> > PrimerCardForm(
> >     controller = cardFormController,
> >     submitButton = {
> >         Button(
> >             onClick = { cardFormController.submit() },
> >             enabled = formState.isFormValid && !formState.isLoading,
> >         ) {
> >             Text("Pay")
> >         }
> >     },
> > )
> > ```
> >
> > > [External Submit Button Recipe](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/external-submit-button.md)
>
> > [accordion] Google Pay with Shipping
> >
> > Configure Google Pay to collect billing address, email, and shipping:
> >
> > ```kotlin theme={"dark"}
> > settings = PrimerSettings().apply {
> >     paymentMethodOptions.googlePayOptions = PrimerGooglePayOptions(
> >         merchantName = "My Store",
> >         captureBillingAddress = true,
> >         emailAddressRequired = true,
> >         shippingAddressParameters = PrimerGoogleShippingAddressParameters(
> >             phoneNumberRequired = true,
> >         ),
> >         requireShippingMethod = true,
> >     )
> > }
> > ```
>
> > [accordion] Stripe ACH Mandate
> >
> > Configure mandate data for Stripe ACH payments:
> >
> > ```kotlin theme={"dark"}
> > settings = PrimerSettings().apply {
> >     paymentMethodOptions.stripeOptions = PrimerStripeOptions(
> >         mandateData = MandateData.TemplateMandateData(merchantName = "My Store"),
> >     )
> > }
> > ```
>
> > [accordion] 3DS Configuration
> >
> > Set the 3DS App Requestor URL:
> >
> > ```kotlin theme={"dark"}
> > settings = PrimerSettings().apply {
> >     paymentMethodOptions.threeDsOptions = PrimerThreeDsOptions(
> >         threeDsAppRequestorUrl = "https://mystore.com",
> >     )
> > }
> > ```

> [tab] iOS
>
> > [accordion] Inline Custom Layout
> >
> > `PrimerCheckout` renders the SDK's default screens. For a fully custom layout, hold a [`PrimerCheckoutSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-session) as a `@StateObject`, compose the SwiftUI views you need, and wire them up with the `.primerCheckoutSession(_:theme:onCompletion:)` modifier:
> >
> > ```swift theme={"dark"}
> > struct CustomCheckoutView: View {
> >   @StateObject private var session: PrimerCheckoutSession
> >
> >   init(clientToken: String) {
> >     _session = StateObject(wrappedValue: PrimerCheckoutSession(clientToken: clientToken))
> >   }
> >
> >   var body: some View {
> >     ScrollView {
> >       VStack(spacing: 24) {
> >         Text("Secure Checkout").font(.headline)
> >         PrimerPaymentMethods()
> >         PrimerCardForm()
> >       }
> >       .padding()
> >     }
> >     .primerCheckoutSession(session) { state in
> >       // The closure receives .failure for each failed payment attempt, so the shopper can retry.
> >       // It receives .success or .dismissed once. After either, or after a failed initialization, it receives nothing more.
> >     }
> >   }
> > }
> > ```
>
> > [accordion] Custom Card Form Submit Button
> >
> > Override the `submitButton` slot on [`PrimerCardForm`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form). Each slot closure receives the active [`PrimerCardFormSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session), so you read `session.state` and call `session.submit()`:
> >
> > ```swift theme={"dark"}
> > PrimerCardForm(submitButton: { session in
> >   Button("Pay Now") {
> >     session.submit()
> >   }
> >   .disabled(!session.state.isValid || session.state.isLoading)
> > })
> > ```
> >
> > The other slots (`cardDetails`, `billingAddress`) keep their `CardFormDefaults` styling. Place `PrimerCardForm` under the `.primerCheckoutSession` modifier so it resolves its session from the environment.
> >
> > > [Custom Submit Button Recipe](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/external-submit-button.md)

***

## See also

> [card] [Web SDK Options Reference](https://primer.io/docs/sdk/primer-checkout-web/sdk-options-reference.md)
>
> Complete list of all available Web options

> [card] [Android SDK Reference](https://primer.io/docs/sdk/android-checkout/v3.0.0-beta/api/overview.md)
>
> Android SDK API documentation

> [card] [Events Guide](https://primer.io/docs/checkout/primer-checkout/configuration/events.md)
>
> Handle payment success, failure, and state changes

> [card] [Styling](https://primer.io/docs/checkout/primer-checkout/build-your-ui/styling-customization.md)
>
> Customize colors, fonts, and spacing


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.