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

# PrimerPaymentMethods

> Payment method list composable with slot-based rows

<Warning>
  Primer Checkout iOS SDK is currently in **beta** (v3.0.0-beta.1).
  The API is subject to change before the stable release.
</Warning>

`PrimerPaymentMethods` is the SwiftUI view that renders the list of available payment methods. It is composed from three `@ViewBuilder` slots — a `header`, a per-method `method` row, and an `emptyState` — and resolves its [`PrimerSelectionSession`](/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session) from the environment supplied by the `.primerCheckoutSession(_:)` modifier.

Each slot defaults to the matching helper on [`PaymentMethodsDefaults`](/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-defaults), so calling `PrimerPaymentMethods()` with no arguments renders the SDK's built-in UI. Override any one slot to customize that part of the list while keeping the defaults for the rest.

<Info>
  `PrimerPaymentMethods` is available on iOS 15.0 and later (`@available(iOS 15.0, *)`).
</Info>

## Signature

```swift theme={"dark"}
@available(iOS 15.0, *)
public struct PrimerPaymentMethods<Header: View, Method: View, Empty: View>: View {
  public init(
    @ViewBuilder header: @escaping (PrimerSelectionSession) -> Header
      = { PaymentMethodsDefaults.header($0) },
    @ViewBuilder method: @escaping (CheckoutPaymentMethod, @escaping () -> Void) -> Method
      = { PaymentMethodsDefaults.method($0, onSelect: $1) },
    @ViewBuilder emptyState: @escaping (PrimerSelectionSession) -> Empty
      = { PaymentMethodsDefaults.emptyState($0) }
  )
}
```

## Parameters

| Parameter    | Type                                                      | Default                                           | Description                                                                                                                                                              |
| ------------ | --------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `header`     | `(PrimerSelectionSession) -> Header`                      | `PaymentMethodsDefaults.header($0)`               | Slot for the section header rendered above the list. Receives the [`PrimerSelectionSession`](/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session). |
| `method`     | `(CheckoutPaymentMethod, @escaping () -> Void) -> Method` | `PaymentMethodsDefaults.method($0, onSelect: $1)` | Slot rendered once per available method. Receives the `CheckoutPaymentMethod` and an `onSelect` closure to invoke when the row is tapped.                                |
| `emptyState` | `(PrimerSelectionSession) -> Empty`                       | `PaymentMethodsDefaults.emptyState($0)`           | Slot shown when no payment methods are available. Receives the [`PrimerSelectionSession`](/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session).    |

## Default behavior

By default `PrimerPaymentMethods` arranges its slots in a vertical stack:

* Renders the default header.
* When `session.state.paymentMethods` is non-empty, renders one default row per method. Each row's tap triggers `session.select(paymentMethod)`, starting that method's flow.
* When `session.state.paymentMethods` is empty, renders the default empty state.

If no `PrimerSelectionSession` is present in the environment (the view is used outside `.primerCheckoutSession(_:)`), nothing is rendered.

```swift theme={"dark"}
import SwiftUI
import PrimerSDK

struct CheckoutScreen: View {
  @StateObject private var session = PrimerCheckoutSession(clientToken: clientToken)

  var body: some View {
    ScrollView {
      PrimerPaymentMethods()
    }
    .primerCheckoutSession(session) { state in
      handle(state)
    }
  }
}
```

<Note>
  Wire the view hierarchy with `.primerCheckoutSession(_:)`. `PrimerPaymentMethods` reads its session from the environment — you do not pass the session into the view directly.
</Note>

## CheckoutPaymentMethod

`CheckoutPaymentMethod` describes a single payment method available for selection. It contains the method's display information and optional custom styling. The `method` slot receives one instance per available method, and selecting a method is also surfaced through `session.state.selectedPaymentMethod`.

```swift theme={"dark"}
@available(iOS 15.0, *)
public struct CheckoutPaymentMethod: Equatable, Identifiable {
  public let id: String
  public let type: String
  public let name: String
  public let icon: UIImage?
  public let surcharge: Int?
  public let hasUnknownSurcharge: Bool
  public let formattedSurcharge: String?
  public let backgroundColor: UIColor?
  public let buttonText: String?
  public let textColor: UIColor?
  public let borderColor: UIColor?
  public let borderWidth: CGFloat?
  public let cornerRadius: CGFloat?
}
```

### Properties

| Property              | Type       | Description                                                                             |
| --------------------- | ---------- | --------------------------------------------------------------------------------------- |
| `id`                  | `String`   | Unique identifier for this payment method instance.                                     |
| `type`                | `String`   | The payment method type identifier (e.g., `"PAYMENT_CARD"`, `"PAYPAL"`, `"APPLE_PAY"`). |
| `name`                | `String`   | Human-readable display name for the payment method.                                     |
| `icon`                | `UIImage?` | Icon image to display for this payment method.                                          |
| `surcharge`           | `Int?`     | Surcharge amount in minor currency units (e.g., cents), if applicable.                  |
| `hasUnknownSurcharge` | `Bool`     | Indicates whether the surcharge amount is unknown (e.g., varies by card network).       |
| `formattedSurcharge`  | `String?`  | Pre-formatted surcharge string for display (e.g., `"+ $0.50"`).                         |
| `backgroundColor`     | `UIColor?` | Custom background color for the payment method button.                                  |
| `buttonText`          | `String?`  | Custom button text from display metadata (e.g., `"Pay with Klarna"`).                   |
| `textColor`           | `UIColor?` | Custom text color for the payment method button.                                        |
| `borderColor`         | `UIColor?` | Custom border color for the payment method button.                                      |
| `borderWidth`         | `CGFloat?` | Custom border width for the payment method button.                                      |
| `cornerRadius`        | `CGFloat?` | Custom corner radius for the payment method button.                                     |

<Tip>
  `CheckoutPaymentMethod` conforms to `Identifiable` via `id`, so you can iterate methods directly with `ForEach` when building a custom list.
</Tip>

## Customization

### Custom method row

Override the `method` slot to render your own row. The slot receives the `CheckoutPaymentMethod` and an `onSelect` closure — call `onSelect` when the user taps your row to start that method's flow.

```swift theme={"dark"}
PrimerPaymentMethods(
  method: { paymentMethod, onSelect in
    Button(action: onSelect) {
      HStack {
        if let icon = paymentMethod.icon {
          Image(uiImage: icon)
            .resizable()
            .scaledToFit()
            .frame(width: 28, height: 28)
        }
        Text(paymentMethod.buttonText ?? paymentMethod.name)
        Spacer()
        if let surcharge = paymentMethod.formattedSurcharge {
          Text(surcharge)
            .foregroundColor(.secondary)
        }
      }
      .padding()
    }
    .buttonStyle(.plain)
  }
)
```

### Custom empty state

Override the `emptyState` slot to control what appears when no methods are available. The slot receives the [`PrimerSelectionSession`](/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session) so you can read selection state if needed.

```swift theme={"dark"}
PrimerPaymentMethods(
  emptyState: { _ in
    VStack(spacing: 12) {
      Image(systemName: "creditcard.and.123")
        .font(.largeTitle)
      Text("No payment methods are available right now.")
        .multilineTextAlignment(.center)
        .foregroundColor(.secondary)
    }
    .frame(maxWidth: .infinity)
    .padding()
  }
)
```

### Custom header

Override the `header` slot to replace the default section title.

```swift theme={"dark"}
PrimerPaymentMethods(
  header: { _ in
    Text("Choose how to pay")
      .font(.title2.weight(.semibold))
      .frame(maxWidth: .infinity, alignment: .leading)
  }
)
```

<Warning>
  The generic parameters `Header`, `Method`, and `Empty` are inferred from the slots you provide. When overriding only some slots, the remaining slots keep their `PaymentMethodsDefaults` bodies — you do not need to re-specify them.
</Warning>

## See also

<CardGroup cols={2}>
  <Card title="PrimerSelectionSession" icon="list" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session">
    The observable session that drives the method list and selection.
  </Card>

  <Card title="PaymentMethodsDefaults" icon="puzzle-piece" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-defaults">
    Default slot bodies used by PrimerPaymentMethods.
  </Card>

  <Card title="PrimerVaultedPaymentMethods" icon="vault" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/vaulted-payment-methods">
    Render and select saved (vaulted) payment methods.
  </Card>

  <Card title="PrimerCardForm" icon="credit-card" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form">
    The slot-based card entry composable.
  </Card>
</CardGroup>
