> ## 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.

# Accept your first payment

> Create a client session, render the checkout, listen for results, and test with sandbox cards.

You've installed the SDK. You're ready to accept payments.

## The default payment flow

When a customer reaches your checkout, here's what happens:

```mermaid theme={"dark"}
sequenceDiagram
    participant User
    participant App as Your App
    participant SDK as Primer SDK
    participant Primer as Primer API
    participant PSP as Payment Processor

    App->>Primer: Create client session
    Primer-->>App: clientToken
    App->>SDK: Initialize checkout
    SDK->>Primer: Load payment methods
    Primer-->>SDK: Available methods

    User->>SDK: Selects payment method
    User->>SDK: Enters payment details
    SDK->>SDK: Validates input

    User->>SDK: Submits payment
    SDK->>Primer: Submit payment
    Primer->>PSP: Process payment
    PSP-->>Primer: Result
    Primer-->>SDK: Payment result
    SDK-->>App: Success / Failure
```

1. **Your server creates a client session** and returns a `clientToken`
2. **The SDK initializes** and loads available payment methods
3. **Customer selects a method** and enters their details
4. **Customer submits** and the SDK processes the payment
5. **Your app receives the result** via events

## Create a Client Session (Server-Side)

Create a client session on your server using the [Client Session API](https://primer.io/docs/checkout/client-session.md). This returns a `clientToken` that you pass to your client.

```bash theme={"dark"}
curl -X POST https://api.primer.io/client-session \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order-123",
    "currencyCode": "USD",
    "amount": 1000,
    "order": {
      "lineItems": [{
        "itemId": "item-1",
        "description": "Widget",
        "amount": 1000,
        "quantity": 1
      }]
    }
  }'
```

## Initialize and Show Checkout

> [tab] Web
>
> ```html theme={"dark"}
> <primer-checkout client-token="your-client-token"></primer-checkout>
> ```
>
> Listen for payment results:
>
> ```javascript theme={"dark"}
> const checkout = document.querySelector('primer-checkout');
>
> checkout.addEventListener('primer:ready', (event) => {
>   const primer = event.detail;
>
>   primer.onPaymentSuccess = ({ payment }) => {
>     window.location.href = `/confirmation?order=${payment.orderId}`;
>   };
>
>   primer.onPaymentFailure = ({ error }) => {
>     console.error('Payment failed:', error.diagnosticsId);
>   };
> });
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> PrimerCheckoutSheet(
>     checkout = rememberPrimerCheckoutController(
>         clientToken = clientToken,
>     )
> )
> ```

> [tab] iOS
>
> ```swift theme={"dark"}
> import SwiftUI
> import PrimerSDK
>
> struct CheckoutView: View {
>   let clientToken: String
>
>   var body: some View {
>     PrimerCheckout(
>       clientToken: clientToken,
>       onCompletion: { state in
>         switch state {
>         case .success(let result):
>           // Navigate to confirmation
>           print("Payment succeeded: \(result.paymentId)")
>         case let .failure(error, _):
>           print("Payment failed: \(error.localizedDescription)")
>         case .dismissed:
>           print("Checkout was dismissed")
>         default:
>           break
>         }
>       }
>     )
>   }
> }
> ```

## What Primer handles for you

Out of the box, Primer Checkout manages:

| Concern | What Primer Does |
| - | - |
| **Validation** | Real-time input validation with inline error messages |
| **Error display** | Payment failures shown automatically in the UI |
| **Redirects** | 3DS challenges, bank redirects, and wallet flows |
| **Loading states** | Visual feedback during processing |
| **Payment method logic** | Each method's specific requirements and flows |

You don't need to write code for any of these behaviors.

## Listening for results

To react to payment outcomes, listen for payment events:

> [tab] Web
>
> ```javascript theme={"dark"}
> const checkout = document.querySelector('primer-checkout');
>
> checkout.addEventListener('primer:payment-success', (event) => {
>   const { payment } = event.detail;
>   window.location.href = `/confirmation?order=${payment.orderId}`;
> });
>
> checkout.addEventListener('primer:payment-failure', (event) => {
>   const { error } = event.detail;
>   // Error is already displayed in the checkout UI
>   console.error('Payment failed:', error.diagnosticsId);
> });
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> val state by checkout.state.collectAsStateWithLifecycle()
>
> LaunchedEffect(state) {
>     when (val s = state) {
>         is PrimerCheckoutState.Success -> {
>             val payment = s.checkoutData.payment
>             navController.navigate("confirmation/${payment.id}")
>         }
>         is PrimerCheckoutState.Failure -> {
>             Log.e("Checkout", "Failed: ${s.primerError.diagnosticsId}")
>         }
>         else -> Unit
>     }
> }
>
> PrimerCheckoutSheet(checkout = checkout)
> ```

> [tab] iOS
>
> ```swift theme={"dark"}
> PrimerCheckout(
>   clientToken: clientToken,
>   onCompletion: { state in
>     switch state {
>     case .success(let result):
>       // Navigate to confirmation screen
>       print("Payment succeeded: \(result.paymentId)")
>     case let .failure(error, _):
>       // Error is already displayed in the checkout UI
>       print("Payment failed: \(error.localizedDescription)")
>     case .dismissed:
>       print("Checkout was dismissed")
>     default:
>       break
>     }
>   }
> )
> ```
>
> | State | When it fires |
> | - | - |
> | `.success(PaymentResult)` | Payment completed successfully |
> | `.failure(PrimerError, checkoutData)` | Fires once per failed attempt, including a failed initialization. By default, the SDK error screen stays open so the customer can retry. If you set `PrimerUIOptions(isErrorScreenEnabled: false)`, the SDK shows nothing and you decide what comes next. `checkoutData` carries the payment when one was created |
> | `.dismissed` | The customer closed the checkout with the SDK's close button, which appears when `PrimerUIOptions(dismissalMechanism:)` includes `.closeButton`. If you present `PrimerCheckout` in your own sheet, dismissing that sheet does not deliver `.dismissed`, so handle it in your sheet's `onDismiss` |
>
> `onCompletion` never receives `.initializing` or `.ready`. `.success` or `.dismissed` arrives exactly once and ends the checkout. Nothing is delivered after it.

## Testing in sandbox

Use these test cards to verify your integration:

| Card Number | Result |
| - | - |
| `4111 1111 1111 1111` | Success |
| `4000 0000 0000 0002` | Declined |

Use any future expiry date and any 3-digit CVV.

Try the Web SDK example in your browser:

[![Try it in Stackblitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/primer-io/examples/tree/main/examples/primer-checkout-basic)

## See also

> [card] [Styling](https://primer.io/docs/checkout/primer-checkout/build-your-ui/styling-customization.md)
>
> Match the checkout to your brand

> [card] [Layout](https://primer.io/docs/checkout/primer-checkout/build-your-ui/layout-customization.md)
>
> Control the structure of checkout components

> [card] [Core Concepts](https://primer.io/docs/checkout/primer-checkout/core-concepts.md)
>
> Understand the architecture

> [card] [Track in Analytics](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/track-payment-analytics.md)
>
> Send events to your analytics platform


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