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

# Validation

> Field validation results and helpers for the card form

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

The card form validates each field and surfaces the results through `PrimerCardFormState`. Read validation state from the `PrimerCardFormSession` inside your custom slots to display targeted error messages and gate submission.

## FieldError

A `FieldError` represents a validation error for a single form field. It is `Equatable` and `Identifiable` (its `id` is the `fieldType`), so it works directly with SwiftUI's `ForEach`.

```swift theme={"dark"}
@available(iOS 15.0, *)
public struct FieldError: Equatable, Identifiable {
    public var id: PrimerInputElementType { fieldType }
    public let fieldType: PrimerInputElementType
    public let message: String
    public let errorCode: String?

    public init(
        fieldType: PrimerInputElementType,
        message: String,
        errorCode: String? = nil
    )
}
```

| Property    | Type                     | Description                                                               |
| ----------- | ------------------------ | ------------------------------------------------------------------------- |
| `id`        | `PrimerInputElementType` | Deterministic identifier based on `fieldType` for stable SwiftUI diffing. |
| `fieldType` | `PrimerInputElementType` | The type of field that has the error.                                     |
| `message`   | `String`                 | Human-readable error message to display to the user.                      |
| `errorCode` | `String?`                | Machine-readable error code for programmatic handling.                    |

## Validation surfaces on PrimerCardFormState

Validation is exposed through the form state held by `PrimerCardFormSession`. Observe the session with `@ObservedObject` (or `@StateObject`) and read `session.state`.

| Member               | Type                                  | Description                                                                              |
| -------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
| `fieldErrors`        | `[FieldError]`                        | Field-specific validation errors currently present in the form.                          |
| `isValid`            | `Bool`                                | Whether the form is valid overall. Use this to enable or disable your submit button.     |
| `hasError(for:)`     | `(PrimerInputElementType) -> Bool`    | Returns `true` when the given field currently has an error.                              |
| `errorMessage(for:)` | `(PrimerInputElementType) -> String?` | Returns the first error message for the given field, or `nil` if the field has no error. |

```swift theme={"dark"}
@available(iOS 15.0, *)
public struct PrimerCardFormState: Equatable {
    public internal(set) var fieldErrors: [FieldError]
    public internal(set) var isValid: Bool

    public func hasError(for fieldType: PrimerInputElementType) -> Bool
    public func errorMessage(for fieldType: PrimerInputElementType) -> String?
}
```

<Info>
  `isValid` reflects the form's overall validity and updates as the user edits fields. Use it to drive the enabled state of your submit button, and use `fieldErrors` / `errorMessage(for:)` to render per-field messages.
</Info>

## Reading a field error inside a slot

Use the helpers on `session.state` to show an inline error for a specific field. The example below renders a CVV field that highlights its border and shows a message when validation fails.

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

@available(iOS 15.0, *)
struct CvvField: View {
    @ObservedObject var session: PrimerCardFormSession
    @State private var cvv = ""

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            TextField("CVV", text: $cvv)
                .keyboardType(.numberPad)
                .onChange(of: cvv) { newValue in
                    session.updateCvv(newValue)
                }
                .overlay(
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(session.state.hasError(for: .cvv) ? .red : .secondary)
                )

            if let message = session.state.errorMessage(for: .cvv) {
                Text(message)
                    .font(.footnote)
                    .foregroundColor(.red)
            }
        }
    }
}
```

To enumerate every error currently present in the form, iterate `fieldErrors` directly — `FieldError` is `Identifiable`, so it slots into `ForEach`.

```swift theme={"dark"}
ForEach(session.state.fieldErrors) { error in
    Text("\(error.fieldType.stringValue): \(error.message)")
        .font(.footnote)
        .foregroundColor(.red)
}
```

## Gating submission with isValid

Bind your submit button's enabled state to `session.state.isValid` so it activates only once the form passes validation.

```swift theme={"dark"}
Button("Pay") {
    session.submit()
}
.disabled(!session.state.isValid)
```

## PrimerInputElementType

`PrimerInputElementType` identifies the type of input field referenced by a `FieldError` (and used throughout the card form). Each case has a `stringValue` returning the canonical string identifier.

```swift theme={"dark"}
@objc
public enum PrimerInputElementType: Int {
    case cardNumber
    case expiryDate
    case cvv
    case cardholderName
    case otp
    case postalCode
    case phoneNumber
    case retailer
    case unknown
    case countryCode
    case firstName
    case lastName
    case addressLine1
    case addressLine2
    case city
    case state
    case email
    case all

    public var stringValue: String { get }
}
```

| Case             | `stringValue`     | Description                                                    |
| ---------------- | ----------------- | -------------------------------------------------------------- |
| `cardNumber`     | `CARD_NUMBER`     | Credit/debit card number (e.g., "4242 4242 4242 4242").        |
| `expiryDate`     | `EXPIRY_DATE`     | Card expiration date in MM/YY format.                          |
| `cvv`            | `CVV`             | Card verification value (3-4 digit security code).             |
| `cardholderName` | `CARDHOLDER_NAME` | Name as printed on the card.                                   |
| `otp`            | `OTP`             | One-time password for verification flows.                      |
| `postalCode`     | `POSTAL_CODE`     | Postal or ZIP code for billing address.                        |
| `phoneNumber`    | `PHONE_NUMBER`    | Phone number for contact or verification.                      |
| `retailer`       | `RETAILER`        | Retail outlet selection for cash payment methods.              |
| `unknown`        | `UNKNOWN`         | Unknown or unrecognized field type.                            |
| `countryCode`    | `COUNTRY_CODE`    | ISO country code (e.g., "US", "GB").                           |
| `firstName`      | `FIRST_NAME`      | First name for billing address.                                |
| `lastName`       | `LAST_NAME`       | Last name for billing address.                                 |
| `addressLine1`   | `ADDRESS_LINE_1`  | Primary street address line.                                   |
| `addressLine2`   | `ADDRESS_LINE_2`  | Secondary address line (apartment, suite, etc.).               |
| `city`           | `CITY`            | City name for billing address.                                 |
| `state`          | `STATE`           | State or province for billing address.                         |
| `email`          | `EMAIL`           | Email address for receipts and notifications.                  |
| `all`            | `ALL`             | Represents all fields collectively (used for bulk operations). |

<Tip>
  You typically don't construct `FieldError` yourself — the SDK populates `fieldErrors` for you. Read it through `session.state` and use `hasError(for:)` / `errorMessage(for:)` to drive your UI.
</Tip>

## See also

<CardGroup cols={2}>
  <Card title="Card form session" icon="rectangle-list" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session">
    The observable session that exposes form state, mutation methods, and submission.
  </Card>

  <Card title="Card field components" icon="input-text" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-field-components">
    Built-in field views you can compose inside the card form slots.
  </Card>
</CardGroup>
