Skip to content

About configuration

Learn how to configure card components for iOS.

Overview

The PXP Checkout iOS SDK provides a suite of configurable components for integrating card payments into your iOS application. Each component is built with SwiftUI and offers comprehensive configuration options for styling, behaviour, and event handling.

Configuration structure

All card components follow a consistent configuration pattern:

let componentConfig = ComponentConfig(
    // Required properties
    label: "Label text",
    
    // Optional properties
    placeholder: "Placeholder text",
    
    // Styling
    inputStyles: FieldInputStateStyles(
        base: FieldInputStyle(/* style properties */)
    ),
    
    // Event handlers
    onChange: {
        // Handle change
    }
)

Basic configuration

At minimum, each component requires a few essential properties:

// Card number field
let cardNumberConfig = CardNumberComponentConfig(
    label: "Card number"
)

// Card expiry field
let expiryConfig = CardExpiryDateComponentConfig(
    label: "Expiry date"
)

// CVC field
let cvcConfig = CardCvcComponentConfig(
    label: "CVC"
)

// Submit button
var submitConfig = CardSubmitComponentConfig()
submitConfig.onPreAuthorisation = { _ async in TransactionInitiationData() }
submitConfig.onPostAuthorisation = { _ in }

Advanced configuration

For more complex implementations, you can configure styling, validation, and callbacks:

var cardNumberConfig = CardNumberComponentConfig(
    // Required configuration
    label: "Card number",
    placeholder: "1234 5678 9012 3456",
    
    // Validation
    acceptedCardBrands: [.visa, .mastercard, .amex],
    validations: CardNumberValidationResource(
        CN01: "Enter a 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"
    ),
    
    // Styling
    inputStyles: FieldInputStateStyles(
        base: FieldInputStyle(
            fontSize: 16,
            borderWidth: 1,
            borderColor: Color(.separator),
            cornerRadius: 8
        ),
        active: FieldInputStyle(
            borderColor: .accentColor,
            borderWidth: 2
        )
    ),
    
    // Event handlers
    onChange: {
        print("Card number changed")
    },
    onValidationPassed: { _ in
        print("Card number is valid")
    }
)

Styling components

Default styling

All components come with SwiftUI default styling:

  • Card fields: Standard text fields with system colours and spacing
  • Submit button: Primary style with standard corner radius
  • Consent checkbox: System checkbox with standard dimensions
  • Billing address: Clean layout with consistent field styling

Custom styling

Each component offers comprehensive styling options:

  • Colours: Customise backgrounds, text, borders, and state colours.
  • Typography: Control font sizes, weights, and text styles.
  • Layout: Configure spacing, padding, dimensions, and positioning.
  • Shapes: Adjust corner radius and border styles.
let customFieldStyle = FieldInputStateStyles(
    base: FieldInputStyle(
        backgroundColor: .white,
        color: Color(.label),
        fontSize: 16,
        fontWeight: .regular,
        borderWidth: 1,
        borderColor: Color(.separator),
        cornerRadius: 8,
        padding: EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)
    ),
    active: FieldInputStyle(
        borderColor: .accentColor,
        borderWidth: 2
    ),
    invalid: FieldInputStyle(
        borderColor: .red,
        borderWidth: 2
    ),
    valid: FieldInputStyle(
        borderColor: .green,
        borderWidth: 1
    )
)

var cardNumberConfig = CardNumberComponentConfig(
    label: "Card number",
    inputStyles: customFieldStyle
)

Event handling

All components provide event handlers for user interactions and lifecycle events.

Common event patterns:

  • Field lifecycle: onChange, onFocus, onBlur for field interactions
  • Validation: onValidationPassed, onValidationFailed for validation events
  • Payment flow: onPreAuthorisation, onPostAuthorisation, onSubmitError for payments
  • 3DS authentication: onPreAuthentication, onPostAuthentication for 3DS flows
var submitConfig = CardSubmitComponentConfig()

// Payment success handling
submitConfig.onPostAuthorisation = { result in
    switch result {
    case is AuthorisedSubmitResult:
        navigateToSuccessScreen()
    case let refused as RefusedSubmitResult:
        showRefusalMessage(refused.stateData?.message)
    case let failed as FailedSubmitResult:
        showErrorDialog(failed.errorReason ?? "Unknown error")
    default:
        break
    }
}

// Error handling
submitConfig.onSubmitError = { error in
    print("Payment error: \(error.errorMessage)")
    showErrorDialog(error.errorMessage)
}

// Start handling
submitConfig.onStartSubmit = {
    showLoadingIndicator()
}

Component linking

Components can be linked together for coordinated behaviour.

Linking fields with submit

// Create card fields first
let cardNumber = try pxpCheckout.create(
    .cardNumber,
    componentConfig: CardNumberComponentConfig(label: "Card number")
) as! CardNumberComponent

let cardExpiry = try pxpCheckout.create(
    .cardExpiryDate,
    componentConfig: CardExpiryDateComponentConfig(label: "Expiry")
) as! CardExpiryDateComponent

let cardCvc = try pxpCheckout.create(
    .cardCvc,
    componentConfig: CardCvcComponentConfig(label: "CVC")
) as! CardCvcComponent

// Link to submit component
var submitConfig = CardSubmitComponentConfig()
submitConfig.cardNumberComponent = cardNumber
submitConfig.cardExpiryDateComponent = cardExpiry
submitConfig.cardCvcComponent = cardCvc
submitConfig.onPreAuthorisation = { _ async in TransactionInitiationData() }
submitConfig.onPostAuthorisation = { _ in }

let submit = try pxpCheckout.create(.cardSubmit, componentConfig: submitConfig) as! CardSubmitComponent

Linking billing address with submit

// Create billing address first
let billing = try pxpCheckout.create(
    .billingAddress,
    componentConfig: BillingAddressComponentConfig()
) as! BillingAddressComponent

// Link to submit for AVS
var submitConfig = CardSubmitComponentConfig()
submitConfig.avsRequest = true
submitConfig.billingAddressComponents = BillingAddressComponents(
    billingAddressComponent: billing
)
submitConfig.onPreAuthorisation = { _ async in TransactionInitiationData() }
submitConfig.onPostAuthorisation = { _ in }

let submit = try pxpCheckout.create(.cardSubmit, componentConfig: submitConfig) as! CardSubmitComponent

Linking dynamic card image with new card

// Create dynamic card image first
let dynamicCardImage = try pxpCheckout.create(
    .dynamicCardImage,
    componentConfig: DynamicCardImageComponentConfig()
) as! DynamicCardImageComponent

// Link to new card component
var submitConfig = CardSubmitComponentConfig()
submitConfig.onPreAuthorisation = { _ async in TransactionInitiationData() }
submitConfig.onPostAuthorisation = { _ in }

let newCardConfig = NewCardComponentConfig(
    dynamicCardImageComponent: dynamicCardImage,
    submit: submitConfig
)

let newCard = try pxpCheckout.create(.newCard, componentConfig: newCardConfig) as! NewCardComponent

Best practices

Use appropriate components

Choose the right component for your use case:

  • Simple new card payment: Use the new card component.
  • Custom field layout: Use standalone card fields with submit.
  • Saved card payment: Use the card-on-file component.
  • Quick checkout: Use click-once for returning customers.

Configure for your brand

Customise the styling to match your app's design:

// Define brand styling
struct BrandStyle {
    static let cornerRadius: CGFloat = 12
    static let borderWidth: CGFloat = 1
    static let spacing: CGFloat = 16
    
    static let fieldStyle = FieldInputStateStyles(
        base: FieldInputStyle(
            borderWidth: borderWidth,
            borderColor: Color(.separator),
            cornerRadius: 8
        ),
        active: FieldInputStyle(
            borderColor: .accentColor,
            borderWidth: 2
        )
    )
}

// Apply to components
var newCardConfig = NewCardComponentConfig(
    styles: ContainerStyle(
        cornerRadius: BrandStyle.cornerRadius,
        padding: EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
        spacing: BrandStyle.spacing
    ),
    inputStyles: BrandStyle.fieldStyle,
    submit: submitConfig
)

Handle all events

Implement comprehensive event handling:

var submitConfig = CardSubmitComponentConfig()

// Required callbacks
submitConfig.onPreAuthorisation = { _ async in
    TransactionInitiationData()
}

submitConfig.onPostAuthorisation = { result in
    handlePaymentResult(result)
}

// Error handling
submitConfig.onSubmitError = { error in
    handlePaymentError(error)
}

// Optional callbacks
submitConfig.onStartSubmit = {
    showLoading()
}

submitConfig.onPostTokenisation = { result in
    if let success = result as? CardTokenizationResultSuccess {
        saveTokenForFutureUse(success.gatewayTokenId)
    }
}

For more information about events, see Events.

Validate user input

Configure validation for a better user experience:

var cardNumberConfig = CardNumberComponentConfig(
    label: "Card number",
    validationOnChange: true,
    onValidationPassed: { _ in
        print("Card number is valid")
    },
    onValidationFailed: { _ in
        print("Card number is invalid")
    }
)

var submitConfig = CardSubmitComponentConfig()
submitConfig.disableUntilValidated = true

Test different scenarios

Test your components in various conditions:

  • Light and dark mode
  • Different screen sizes and orientations
  • Various card brands and types
  • 3DS and non-3DS flows
  • Error conditions and network failures
  • Accessibility features like VoiceOver and Dynamic Type

Configuration layers

Checkout-level configuration

Global settings like environment, session, and merchant identity are configured once at initialisation:

let checkoutConfig = CheckoutConfig(
    environment: .test,
    session: sessionData,
    transactionData: transactionData,
    merchantShopperId: "shopper-id",
    ownerId: "owner-id",
    onGetShopper: { async in
        TransactionShopper(id: "shopper-id")
    },
    onGetShippingAddress: { async in
        ShippingAddress(
            countryCode: "GB",
            postalCode: "SW1A 1AA",
            address: "10 Downing Street"
        )
    },
    analyticsEvent: { event in
        trackAnalytics(event)
    }
)

let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)

Component-level configuration

Each component has its own configuration type with specific properties:

// Field component configuration
let cardNumberConfig = CardNumberComponentConfig(
    label: "Card number",
    placeholder: "1234 5678 9012 3456",
    acceptedCardBrands: [.visa, .mastercard],
    inputStyles: fieldStyle
)

// Submit component configuration
var submitConfig = CardSubmitComponentConfig()
submitConfig.styles = buttonStyle
submitConfig.onPreAuthorisation = { _ async in TransactionInitiationData() }
submitConfig.onPostAuthorisation = { _ in }

// Composite component configuration
let newCardConfig = NewCardComponentConfig(
    styles: containerStyle,
    inputStyles: fieldStyle,
    fields: NewCardComponentFields(
        cardNumber: cardNumberConfig
    ),
    submit: submitConfig
)