Skip to content

Data validation

Collect and validate the data entered by your customers.

Overview

Validation in PXPCheckoutSDK helps you:

  • Cut down on avoidable declines and retries by catching bad input early.
  • Give shoppers clear, localisable messages tied to stable error codes (for example CN01, ED04).
  • Keep card data inside the SDK's secured fields rather than re-implementing PAN/CVC rules in your app.

Validation is driven by component configuration (validationOnBlur, validationOnChange) and callbacks that receive [ValidationResult]. CardSubmitComponent then runs a submit-time validation pass before tokenisation and payment.

How validation runs

MechanismDescription
validationOnBlur / validationOnChangeOn FieldComponentConfig / SecuredFieldComponentConfig. Controls whether validation runs when the field loses focus and/or when the value changes.
onValidationPassed / onValidationFailedReceive [ValidationResult] after a validation pass for that field.
NewCardComponentConfig.onValidationAggregates validation across the new-card form.
CardSubmitComponentConfig.onValidationFires with combined results from linked inputs (and AVS fields when enabled) during the submit pipeline.
CardSubmitComponentConfig.onCustomValidationasync -> Bool. Runs after standard field validation succeeds; return false to block submitAsync().
submitAsync()Performs collection and validation internally; you don't call a separate validateWhenSubmit() per field.

SeeEvents for the full callback list.

Public value and validation helpers (limited)

Only a few types expose public "read value" or "validate now" methods:

ComponentPublic APINotes
Card consent / Pre-fill billing address checkboxgetValue() -> BoolCheckboxComponent — checked state.
Click Once (standalone)validate() async -> [ValidationResult]Primarily CVC when required.
Click Once (standalone)getValue() async -> ClickOnceStandaloneComponentData?Selected token + optional CVC; nil if no token.

Field components (card number, expiry, CVC, holder name, country, postcode, address) don't expose a public getValue() method. These components use internal value APIs that the SDK reads automatically. When you call submitAsync() on CardSubmitComponent, it collects the secured values from all linked field components, so you don't need to retrieve values manually.

Card brand selector and dynamic card image are display components: no editable value and no field-validation resource in the same sense as PAN/CVC.

Pre-built containers (new card, billing address, card-on-file, click-once pre-built, card submit) compose their behaviour from children and submit functionality.

ValidationResult (Swift)

Each result bundles a boolean and an optional map of errors (keys are field identifiers used inside the SDK).

public struct ValidationResult: Codable {
    public let valid: Bool
    public let errors: [String: ValidationError]?
}

public struct ValidationError: Codable {
    public let message: String
    public let code: String
}

Typical handling:

func process(_ results: [ValidationResult]) {
    for r in results {
        if r.valid { continue }
        r.errors?.forEach { key, err in
            print("\(key): \(err.code)\(err.message)")
        }
    }
}

There is no isValid property — use valid.

Card number

Rule order (matches CardNumberComponent.validateField)

CodeWhen it fires
CN01Empty value (required)
CN02Sanitised value isn't all digits.
CN03Entered length is still below the detected brand's minimum length.
CN04Brand is unknown after pattern matching.
CN05Brand is known but not in acceptedCardBrands.
CN06Luhn check fails (validateCardNumber in the SDK)
CN07Custom validation message with format argument (for example "%@ not accepted")
CN08BIN lookup didn't return card type.

Override copy with CardNumberValidationResource (all of CN01-CN08 are required in the initialiser).

let validations = CardNumberValidationResource(
    CN01: "Enter your card number",
    CN02: "Use digits only",
    CN03: "Card number looks too short",
    CN04: "We could not recognise this card type",
    CN05: "This card type is not accepted",
    CN06: "Check the number and try again",
    CN07: "%@ not accepted",
    CN08: "Could not determine card type"
)

var config = CardNumberComponentConfig(
    label: "Card number",
    acceptedCardBrands: [.visa, .mastercard, .amex],
    validations: validations,
    validationOnBlur: true,
    validationOnChange: false,
    onValidationFailed: { results in
        _ = results.first { !$0.valid }?.errors?.values.first?.message
    }
)

Brand ranges and lengths come from the SDK's internal brand tables (patterns and CVC lengths also drive CardCvcComponent min/max). See Card number.

Luhn

The SDK implements the Luhn check in ValidateCardNumber.swift (used for CN06). You don't need to duplicate it unless you are validating non-SDK input.

Expiry date

Codes (CardExpiryDateComponent)

CodeTypical meaning
ED05Empty (required)
ED01Disallowed characters (must be digits / allowed separators)
ED03Split / pattern / full-format mismatch (depends on CardExpiryDateFormatOption)
ED02Month not 1-12 (or non-numeric month part)
ED04Expiry is before the minimum valid month (respects numberOfMonthExpired when set)

Messages are set via CardExpiryDateValidationResource (ED01-ED05).

let validations = CardExpiryDateValidationResource(
    ED01: "Use digits only (you can use / or . as separators)",
    ED02: "Enter a month from 01 to 12",
    ED03: "Use the correct expiry format",
    ED04: "This card appears to be expired",
    ED05: "Enter the expiry date"
)

let expiryConfig = CardExpiryDateComponentConfig(
    label: "Expiry",
    formatOptions: .mmYY,
    validations: validations
)

See Card expiry date.

CVC

Codes (CardCvcComponent)

CodeTypical meaning
CVC01Required but empty
CVC02Non-digit characters
CVC03Shorter than min length for the current brand (often 3)
CVC04Longer than max (typically 4 for Amex-style rules)

Min/max length is updated when the card number component detects a brand (setMinMaxLength). Configure the copy with CardCvcValidationResource.

See Card CVC.

Custom validation (onCustomValidation)

Use CardSubmitComponentConfig.onCustomValidation for business checks (limits, fraud rules, cart state) after SDK field validation has passed.

submitConfig.onCustomValidation = {
    guard await orderStillValid() else { return false }
    return true
}

If any standard validation fails, the submit button path doesn't reach onCustomValidation. Surface field issues via onValidation, onValidationFailed, or UI bound to ValidationResult.

Real-time vs submit-time

  • Real-time: enable validationOnChange (and/or validationOnBlur) on fields where you want early feedback; use onValidationPassed / onValidationFailed to drive inline UI.
  • Submit-time: disableUntilValidated on CardSubmitComponentConfig keeps the default button disabled until linked fields (and AVS, if enabled) validate; submitAsync() re-validates before proceeding.