> ## 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.8).
> The API is subject to change before the stable release.

`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`](https://primer.io/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`](https://primer.io/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.

> [!NOTE]
>
> `PrimerPaymentMethods` is available on iOS 15.0 and later (`@available(iOS 15.0, *)`).

## 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`](https://primer.io/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`](https://primer.io/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.

`PrimerPaymentMethods` applies the spacing between its slots itself, so the spacing stays the same when you override a slot. `primerSpaceMedium` (12pt by default) separates the header from the list of methods, or from the empty state when there are no methods. `primerSpaceSmall` (8pt by default) separates the method rows. You can change both through [`SpacingOverrides`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/theming/spacing-tokens) in `PrimerCheckoutTheme`.

`PrimerPaymentMethods` renders nothing while no `PrimerSelectionSession` is in the environment. This happens while the checkout session is still initializing, and when the view is used outside `.primerCheckoutSession(_:)`. The list appears once `session.phase` reaches `.ready`.

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

struct CheckoutScreen: View {
  @StateObject private var session: PrimerCheckoutSession

  init(clientToken: String) {
    _session = StateObject(wrappedValue: 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.

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

  public init(
    id: String,
    type: String,
    name: String,
    icon: UIImage? = nil,
    surcharge: Int? = nil,
    hasUnknownSurcharge: Bool = false,
    formattedSurcharge: String? = nil,
    backgroundColor: UIColor? = nil,
    buttonText: String? = nil,
    textColor: UIColor? = nil,
    borderColor: UIColor? = nil,
    borderWidth: CGFloat? = nil,
    cornerRadius: CGFloat? = nil
  )
}
```

### 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. The SDK's default row ignores it; only a custom `method` slot can use it. |
| `borderWidth` | `CGFloat?` | Custom border width for the payment method button. The SDK's default row ignores it; only a custom `method` slot can use it. |
| `cornerRadius` | `CGFloat?` | Custom corner radius for the payment method button. The SDK's default row ignores it; only a custom `method` slot can use it. |

> [!TIP]
>
> `CheckoutPaymentMethod` conforms to `Identifiable` via `id`, so you can iterate methods directly with `ForEach` when building a custom list.

> [!NOTE]
>
> `CheckoutPaymentMethod` equality compares only `id`, `type`, `name`, `surcharge`, `hasUnknownSurcharge`, `formattedSurcharge` and `backgroundColor`. Background colors are compared in both light and dark mode. The display-only properties `icon`, `buttonText`, `textColor`, `borderColor`, `borderWidth` and `cornerRadius` are ignored, so a change to them alone does not make two methods unequal.

## 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`](https://primer.io/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.

## See also

> [card] [PrimerSelectionSession](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session.md)
>
> The observable session that drives the method list and selection.

> [card] [PaymentMethodsDefaults](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-defaults.md)
>
> Default slot bodies used by PrimerPaymentMethods.

> [card] [PrimerVaultedPaymentMethods](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/vaulted-payment-methods.md)
>
> Render and select saved (vaulted) payment methods.

> [card] [PrimerCardForm](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form.md)
>
> The slot-based card entry composable.


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