Skip to main content
Primer Checkout iOS SDK is currently in beta (v3.0.0-beta.1). The API is subject to change before the stable release.
CardFormDefaults exposes a set of per-field building blocks alongside its full section renderers. Each building block is a static function with the signature (PrimerCardFormSession) -> CardFieldContent. Drop any of them into a custom PrimerCardForm section slot to recompose the form one field at a time, while keeping the SDK’s built-in formatting, validation, keyboard configuration, and inline error display.
Every building block is self-hiding: it renders nothing unless its field is part of the active CardFormConfiguration for the current session. You can drop every block into a layout without worrying about which fields the current session actually requires — only the configured ones appear.

How the building blocks work

The full-section renderers — CardFormDefaults.cardDetails(_:) and CardFormDefaults.billingAddress(_:) — produce the complete, config-aware form. The field-level building blocks let you interleave your own content (banners, headings, custom layout) between individual fields instead. Each field block forwards to the same shared, configuration-aware renderer the section helpers use, so a hand-composed layout behaves identically to the default form. The block checks whether its field appears in the session’s configuration.cardFields or configuration.billingFields; if not, it resolves to an EmptyView.
@available(iOS 15.0, *)
public enum CardFormDefaults {
    public static func cardNumber(_ session: PrimerCardFormSession) -> CardFieldContent
    public static func expiryDate(_ session: PrimerCardFormSession) -> CardFieldContent
    // ...one function per field
}
All field blocks take a single unlabeled PrimerCardFormSession parameter and return a concrete View. Read session.state for rendering and call into the session for mutations and submission.

Card detail fields

These map to the cardFields in the active CardFormConfiguration (the default configuration is .cardNumber, .expiryDate, .cvv, .cardholderName).
MethodReturn typeRenders whenDescription
cardNumber(_:)CardFieldContent.cardNumber is in configuration.cardFieldsCard number field with network-aware grouping, Luhn validation, and detected-network icon.
expiryDate(_:)CardFieldContent.expiryDate is configuredExpiry field; auto-inserts the / separator (MM/YY) and validates for a valid, future month.
cvv(_:)CardFieldContent.cvv is configuredCVV field; masked input, max length adapts to the detected network (4 for Amex, 3 otherwise).
cardholderName(_:)CardFieldContent.cardholderName is configuredCardholder name field with word capitalization.
@available(iOS 15.0, *)
public static func cardNumber(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func expiryDate(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func cvv(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func cardholderName(_ session: PrimerCardFormSession) -> CardFieldContent

Co-badged card network selector

cardNetwork(_:) is a standalone selector for co-badged cards. Unlike the other blocks it does not read CardFormConfiguration; it renders only when more than one network is detected (session.state.availableNetworks.count > 1), reusing the SDK’s built-in dropdown. Tapping a network commits the choice through session.selectCardNetwork(_:).
MethodReturn typeRenders whenDescription
cardNetwork(_:)CardNetworkFieldContentavailableNetworks.count > 1Dropdown selector for co-badged cards (e.g., Cartes Bancaires + Visa).
@available(iOS 15.0, *)
public static func cardNetwork(_ session: PrimerCardFormSession) -> CardNetworkFieldContent
Use cardNetwork(_:) only when you recompose the individual card fields. The full cardDetails(_:) section already renders the selector when multiple networks are detected — combining the two would show two selectors.

Billing address fields

These map to the billingFields in the active CardFormConfiguration. They appear only when billing address collection is enabled for the session (configuration.requiresBillingAddress and the corresponding field present in billingFields).
MethodReturn typeRenders whenDescription
firstName(_:)CardFieldContent.firstName is in configuration.billingFieldsFirst name field with word capitalization.
lastName(_:)CardFieldContent.lastName is configuredLast name field with word capitalization.
email(_:)CardFieldContent.email is configuredEmail field for receipts and notifications.
phoneNumber(_:)CardFieldContent.phoneNumber is configuredPhone number field.
addressLine1(_:)CardFieldContent.addressLine1 is configuredPrimary street address line.
addressLine2(_:)CardFieldContent.addressLine2 is configuredSecondary address line (apartment, suite, etc.).
city(_:)CardFieldContent.city is configuredCity field.
state(_:)CardFieldContent.state is configuredState or province field; label adapts to the selected country.
postalCode(_:)CardFieldContent.postalCode is configuredPostal/ZIP code field; format and label vary by country.
countryCode(_:)CardFieldContent.countryCode is configuredCountry selector field.
@available(iOS 15.0, *)
public static func firstName(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func lastName(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func email(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func phoneNumber(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func addressLine1(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func addressLine2(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func city(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func state(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func postalCode(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func countryCode(_ session: PrimerCardFormSession) -> CardFieldContent

Composing a custom card detail layout

Pass a @ViewBuilder closure to the cardDetails slot on PrimerCardForm. Lay out the field blocks in any order, and add your own content between them. Because each block is self-hiding, the extra fields you reference (such as cardholderName) appear only if the session’s configuration includes them.
import SwiftUI
import PrimerSDK

@available(iOS 15.0, *)
struct CustomCardDetails: View {
    var body: some View {
        PrimerCardForm(cardDetails: { session in
            VStack(alignment: .leading, spacing: 16) {
                Text("Card details")
                    .font(.headline)

                CardFormDefaults.cardNumber(session)

                HStack(spacing: 12) {
                    CardFormDefaults.expiryDate(session)
                    CardFormDefaults.cvv(session)
                }

                CardFormDefaults.cardholderName(session)

                // Co-badged selector: shown only when multiple networks are detected.
                CardFormDefaults.cardNetwork(session)
            }
        })
    }
}
Because you replaced the default cardDetails section, include CardFormDefaults.cardNetwork(session) so co-badged cards still get a selector — the built-in one is suppressed once you recompose the individual fields.

Recomposing billing fields too

The billingAddress slot works the same way. Group billing blocks into your own layout; only the configured fields render.
@available(iOS 15.0, *)
struct CustomBilling: View {
    var body: some View {
        PrimerCardForm(billingAddress: { session in
            VStack(alignment: .leading, spacing: 16) {
                Text("Billing address")
                    .font(.headline)

                HStack(spacing: 12) {
                    CardFormDefaults.firstName(session)
                    CardFormDefaults.lastName(session)
                }

                CardFormDefaults.addressLine1(session)
                CardFormDefaults.addressLine2(session)

                HStack(spacing: 12) {
                    CardFormDefaults.city(session)
                    CardFormDefaults.state(session)
                }

                CardFormDefaults.postalCode(session)
                CardFormDefaults.countryCode(session)
            }
        })
    }
}

Reacting to field state

Each building block manages its own input and inline error display, so you don’t have to wire bindings manually. When you need to react to the form as a whole — for example to drive your own submit button — read the published session.state:
@available(iOS 15.0, *)
struct CustomSubmit: View {
    @ObservedObject var session: PrimerCardFormSession

    var body: some View {
        Button("Pay now") { session.submit() }
            .disabled(!session.state.isValid || session.state.isLoading)
    }
}
Per-field errors are available on session.state via fieldErrors, hasError(for:), and errorMessage(for:). See Card Form Session for the full state surface.

See also

Card Form Defaults

The full section renderers and submit button these blocks complement.

PrimerCardForm

The composable form view and its @ViewBuilder section slots.

Card Form Session

Observable state, field updates, and submission for the card form.