Collect and validate the data entered by your customers.
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.
| Mechanism | Description |
|---|---|
validationOnBlur / validationOnChange | On FieldComponentConfig / SecuredFieldComponentConfig. Controls whether validation runs when the field loses focus and/or when the value changes. |
onValidationPassed / onValidationFailed | Receive [ValidationResult] after a validation pass for that field. |
NewCardComponentConfig.onValidation | Aggregates validation across the new-card form. |
CardSubmitComponentConfig.onValidation | Fires with combined results from linked inputs (and AVS fields when enabled) during the submit pipeline. |
CardSubmitComponentConfig.onCustomValidation | async -> 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.
Only a few types expose public "read value" or "validate now" methods:
| Component | Public API | Notes |
|---|---|---|
| Card consent / Pre-fill billing address checkbox | getValue() -> Bool | CheckboxComponent — 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.
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.
| Code | When it fires |
|---|---|
CN01 | Empty value (required) |
CN02 | Sanitised value isn't all digits. |
CN03 | Entered length is still below the detected brand's minimum length. |
CN04 | Brand is unknown after pattern matching. |
CN05 | Brand is known but not in acceptedCardBrands. |
CN06 | Luhn check fails (validateCardNumber in the SDK) |
CN07 | Custom validation message with format argument (for example "%@ not accepted") |
CN08 | BIN 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.
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.
| Code | Typical meaning |
|---|---|
ED05 | Empty (required) |
ED01 | Disallowed characters (must be digits / allowed separators) |
ED03 | Split / pattern / full-format mismatch (depends on CardExpiryDateFormatOption) |
ED02 | Month not 1-12 (or non-numeric month part) |
ED04 | Expiry 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.
| Code | Typical meaning |
|---|---|
CVC01 | Required but empty |
CVC02 | Non-digit characters |
CVC03 | Shorter than min length for the current brand (often 3) |
CVC04 | Longer 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.
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: enable
validationOnChange(and/orvalidationOnBlur) on fields where you want early feedback; useonValidationPassed/onValidationFailedto drive inline UI. - Submit-time:
disableUntilValidatedonCardSubmitComponentConfigkeeps the default button disabled until linked fields (and AVS, if enabled) validate;submitAsync()re-validates before proceeding.