Skip to content

Card submit

Learn about how to configure the card submit component.

Basic usage

Minimal configuration

The card submit component orchestrates validation, tokenisation, 3DS authentication, and authorisation. At minimum, you need to link your card input components and implement the required callbacks:

import SwiftUI
import PXPCheckoutSDK

let pxpCheckout = try PxpCheckout.initialize(config: CheckoutConfig(
    environment: .test,
    session: SessionData(
        sessionId: "your-session-id",
        hmacKey: "your-hmac-key",
        encryptionKey: "your-encryption-key"
    ),
    transactionData: TransactionData(
        amount: Decimal(10),
        currency: "USD",
        entryType: .ecom,
        intent: TransactionIntentData(card: .authorisation),
        merchantTransactionId: "unique-transaction-id",
        merchantTransactionDate: { Date() }
    ),
    merchantShopperId: "shopper-id",
    ownerType: "MerchantGroup",
    ownerId: "your-owner-id"
))

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

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

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

var submitConfig = CardSubmitComponentConfig()
submitConfig.cardNumberComponent = cardNumber
submitConfig.cardExpiryDateComponent = cardExpiry
submitConfig.cardCvcComponent = cardCvc

submitConfig.onPreAuthorisation = { preAuth in
    TransactionInitiationData()
}

submitConfig.onPostAuthorisation = { result in
    // Handle result (AuthorisedSubmitResult, RefusedSubmitResult, FailedSubmitResult)
}

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

Render the components in your SwiftUI view:

VStack {
    cardNumber.buildContent()
    HStack {
        cardExpiry.buildContent()
        cardCvc.buildContent()
    }
    cardSubmit.buildContent()
}
PropertyDescription
cardNumberComponent
CardNumberComponent?
The card number input component. Required when using standalone card fields (not using newCardComponent). See Card number. Defaults to nil.
cardExpiryDateComponent
CardExpiryDateComponent?
The expiry date component. Required when using standalone card fields (not using newCardComponent). See Card expiry date. Defaults to nil.
cardCvcComponent
CardCvcComponent?
The CVC component. Required when using standalone card fields (not using newCardComponent). See Card CVC. Defaults to nil.
newCardComponent
NewCardComponent?
The pre-built new card component. Use this instead of standalone card fields for a simplified implementation. See New card. Defaults to nil.
onPreAuthorisation
(PreAuthorizationData?) async -> TransactionInitiationData?
Required callback to provide transaction data before authorisation. Called after validation and tokenisation but before the authorisation request. Return nil to abort the transaction.
onPostAuthorisation
(BaseSubmitResult) -> Void
Callback for handling the final transaction result. Receives one of: AuthorisedSubmitResult, RefusedSubmitResult, or FailedSubmitResult.

Use either newCardComponent or the standalone card fields, not both.

Advanced configuration

You can customise the submit button, enable AVS, configure 3DS, and add optional components:

let cardHolderName = try pxpCheckout.create(
    .cardHolderName,
    componentConfig: CardHolderNameComponentConfig(label: "Cardholder name")
) as! CardHolderNameComponent

let cardConsent = try pxpCheckout.create(
    .cardConsent,
    componentConfig: CardConsentComponentConfig(label: "Save this card for future purchases")
) as! CardConsentComponent

let billingAddress = try pxpCheckout.create(
    .billingAddress,
    componentConfig: BillingAddressComponentConfig()
) as! BillingAddressComponent

var submitConfig = CardSubmitComponentConfig()
submitConfig.cardNumberComponent = cardNumber
submitConfig.cardExpiryDateComponent = cardExpiry
submitConfig.cardCvcComponent = cardCvc
submitConfig.cardHolderNameComponent = cardHolderName
submitConfig.cardConsentComponent = cardConsent

submitConfig.submitText = "Complete payment"
submitConfig.disableUntilValidated = true
submitConfig.hideSubmitButton = false

submitConfig.useCardOnFile = false
submitConfig.avsRequest = true
submitConfig.billingAddressComponents = BillingAddressComponents(
    billingAddressComponent: billingAddress
)

submitConfig.useUnityAuthenticationStrategy = true

submitConfig.onPreAuthorisation = { preAuth in
    // Can use await here for async operations
    return TransactionInitiationData()
}
submitConfig.onPostAuthorisation = { result in }

let cardSubmit = try pxpCheckout.create(.cardSubmit, componentConfig: submitConfig) as! CardSubmitComponent
PropertyDescription
submitText
String?
The button text. Supports {amount} and {currency} placeholders which are automatically replaced with the transaction values. Defaults to SDK localisation.
disableUntilValidated
Bool?
Whether to disable the button until all fields are valid. When true, the button remains disabled until all required card fields pass validation. Defaults to false.
hideSubmitButton
Bool?
Whether to hide the button so you can call submitAsync() from your own control. When true, you must manually trigger submission. Defaults to false.
styles
ButtonStateStyles?
The button styling for base, disabled, and loading states. Each state can have different appearance. Defaults to nil. States: base (normal clickable), disabled (validation failed or processing), loading (payment in progress).
cardHolderNameComponent
CardHolderNameComponent?
The optional cardholder name field. When provided, the cardholder's name is included in the tokenisation request. See Cardholder name. Defaults to nil.
cardConsentComponent
CardConsentComponent?
The optional consent checkbox for saving cards. When checked and transaction succeeds, the card is tokenised for future use. See Card consent. Defaults to nil.
cardOnFileComponent
CardOnFileComponent?
The saved cards component. Required when useCardOnFile is true. Displays previously saved card tokens. See Card-on-file. Defaults to nil.
clickOnceStandaloneComponent
ClickOnceStandaloneComponent?
The Click Once component for one-click payments. Displays the most recently used card with a submit button. See Click-once. Defaults to nil.
useCardOnFile
Bool?
Whether to use a tokenised card instead of new card entry. When true, cardOnFileComponent must be provided. Defaults to false.
avsRequest
Bool?
Whether to send billing address for address verification (AVS). When true, billingAddressComponents must be provided. Defaults to false.
billingAddressComponents
BillingAddressComponents?
The billing address components for AVS. Required when avsRequest is true. Defaults to nil.
useUnityAuthenticationStrategy
Bool?
Whether to use Unity's integrated 3DS strategy. When true, the SDK handles 3DS authentication automatically using Unity's service. Defaults to false.
authenticationConfig
AuthenticationConfig?
The optional authentication configuration object. Provides additional control over 3DS authentication behaviour. Defaults to nil.
billingAddressComponents.billingAddressComponent
BillingAddressComponent?
The pre-built billing address form including country, postcode, and street address. See Billing address.
billingAddressComponents.countrySelectionComponent
CountrySelectionComponent?
The standalone country field. Alternative to using the pre-built billing address form. See Country selection.
billingAddressComponents.postcodeComponent
PostcodeComponent?
The standalone postcode field. Alternative to using the pre-built billing address form. See Postcode.
billingAddressComponents.addressComponent
AddressComponent?
The standalone address field. Alternative to using the pre-built billing address form. See Address.

If you provide both a pre-built billing address component and standalone billing fields, the pre-built component takes precedence.

Styling

Default styling

The card submit button uses built-in styling with colors, padding, and loading states.

Custom styling

You can customise the button appearance for different states:

submitConfig.styles = ButtonStateStyles(
    base: FieldStyle(
        color: .white,
        font: .headline,
        backgroundColor: .blue,
        cornerRadius: 10,
        padding: EdgeInsets(top: 14, leading: 20, bottom: 14, trailing: 20)
    ),
    disabled: FieldStyle(
        color: .white,
        backgroundColor: .gray.opacity(0.5),
        cornerRadius: 10,
        padding: EdgeInsets(top: 14, leading: 20, bottom: 14, trailing: 20)
    ),
    loading: FieldStyle(
        color: .white,
        backgroundColor: .blue.opacity(0.7),
        cornerRadius: 10,
        padding: EdgeInsets(top: 14, leading: 20, bottom: 14, trailing: 20)
    )
)
PropertyDescription
styles.base
FieldStyle?
The button appearance in normal state when clickable and ready. Properties: color, font, fontWeight, fontSize, backgroundColor, borderColor, borderWidth, cornerRadius, padding, margin, textAlignment, opacity, shadow (ShadowStyle), icon (IconStyle).
styles.disabled
FieldStyle?
The button appearance when disabled due to validation failure or processing. Uses the same properties as base.
styles.loading
FieldStyle?
The button appearance during payment processing. Typically shows a loading indicator. Uses the same properties as base.

Event handling

The card submit component provides event handlers for the complete payment lifecycle:

submitConfig.onClick = {
    // Button tapped, before validation
}

submitConfig.onStartSubmit = {
    // Validation passed, submission started
}

submitConfig.onChange = { state in
    // Component state changed (idle, loading, success, error)
}

submitConfig.onValidation = { results in
    // Field validation results
}

submitConfig.onCustomValidation = {
    // Can use await here for async validation
    return true
}

submitConfig.onPreTokenisation = {
    // Before tokenisation (new card)
    return true
}

submitConfig.onPostTokenisation = { result in
    // Tokenisation completed
}

submitConfig.onPreInitiateAuthentication = {
    // Can use await here
    return PreInitiateIntegratedAuthenticationData(providerId: "pxpfinancial", timeout: 120)
}

submitConfig.onPostInitiateAuthentication = { result in
    // 3DS pre-initiate completed
}

submitConfig.onPreAuthentication = {
    // Can use await here
    return InitiateIntegratedAuthenticationData(
        merchantCountryNumericCode: "826",
        merchantLegalName: "Example Ltd",
        challengeWindowSize: .size1,
        requestorChallengeIndicator: .challengeRequestedMandate,
        timeout: 180
    )
}

submitConfig.onPostAuthentication = { result in
    // 3DS authentication completed
}

submitConfig.onPreAuthorisation = { preAuth in
    // Can use await here for async operations
    return TransactionInitiationData()
}

submitConfig.onPostAuthorisation = { submitResult in
    // Final transaction result
}

submitConfig.onPreRetrySoftDecline = { result in
    .simple(true)
}

submitConfig.onGetFingerprintResult = {
    // Can use await here to fetch fingerprint
    return ""
}

submitConfig.onCollectStart = { _ in }
submitConfig.onCollectEnd = { _ in }

submitConfig.onSubmitError = { error in
    // Handle errors
}
CallbackDescription
onClick: (() -> Void)?Called when the button is tapped, before validation runs.
onStartSubmit: (() -> Void)?Called after validation passes and before submission begins.
onChange: ((ComponentState) -> Void)?Called when the component state changes. Receives the new state: idle, loading, success, or error.
onValidation: (([ValidationResult]) -> Void)?Called with aggregated validation results from all linked components.
onCustomValidation: (() async -> Bool)?Custom validation logic hook. Return false to abort submission.
onPreTokenisation: (() -> Bool)?Called before tokenisation. Return false to skip tokenisation.
onPostTokenisation: ((CardTokenizationResult) -> Void)?Called after tokenisation completes. Receives the tokenisation result.
onPreInitiateAuthentication: (() async -> PreInitiateIntegratedAuthenticationData?)?Called to provide 3DS pre-initiate configuration. Return nil to skip this step.
onPostInitiateAuthentication: ((AuthenticationResult) -> Void)?Called after 3DS pre-initiate completes. Receives the authentication result.
onPreAuthentication: (() async -> InitiateIntegratedAuthenticationData?)?Called to provide 3DS authentication configuration. Return nil to skip 3DS challenge.
onPostAuthentication: ((AuthenticationResult) -> Void)?Called after 3DS challenge completes. Receives the authentication result.
onPreAuthorisation: ((PreAuthorizationData?) async -> TransactionInitiationData?)?Required callback to provide transaction data before authorisation. Return nil to abort authorisation.
onPostAuthorisation: ((BaseSubmitResult) -> Void)?Called with the final transaction result. Receives one of: AuthorisedSubmitResult, RefusedSubmitResult, or FailedSubmitResult.
onPreRetrySoftDecline: ((BaseSubmitResult) -> SoftDeclineRetryResult)?Called when a soft decline occurs. Return retry decision (.simple(true) to retry, .simple(false) to stop).
onGetFingerprintResult: (() async -> String)?Called to provide device fingerprint for risk screening. Return the fingerprint string.
onCollectStart: ((Any) -> Void)?Called when data collection starts. Receives collection metadata.
onCollectEnd: ((Any) -> Void)?Called when data collection ends. Receives collection metadata.
onSubmitError: ((BaseSdkException) -> Void)?Called when SDK or network errors occur. Receives the exception details.

For more information about event data structures and usage patterns, see Events, 3DS transactions, and Data validation.

Methods

submitAsync()

Programmatically triggers the payment flow. Use when hideSubmitButton is true or from your own custom button:

await cardSubmit.submitAsync()

The method runs the complete payment pipeline:

  1. Validates linked components and AVS data
  2. Tokenises the card (for new card flows)
  3. Runs 3DS authentication when required
  4. Calls onPreAuthorisation and processes authorisation
  5. Invokes configured callbacks in sequence

buildContent()

Returns SwiftUI content for the submit button:

cardSubmit.buildContent()

Examples

Basic payment form

A complete payment form with card fields and submit button:

struct BasicCardPaymentView: View {
    @State private var checkout: PxpCheckout?
    @State private var cardNumber: CardNumberComponent?
    @State private var cardExpiry: CardExpiryDateComponent?
    @State private var cardCvc: CardCvcComponent?
    @State private var cardSubmit: CardSubmitComponent?

    var body: some View {
        Group {
            if let cardNumber, let cardExpiry, let cardCvc, let cardSubmit {
                VStack(spacing: 16) {
                    cardNumber.buildContent()
                    HStack {
                        cardExpiry.buildContent()
                        cardCvc.buildContent()
                    }
                    cardSubmit.buildContent()
                }
                .padding()
            } else {
                ProgressView()
            }
        }
        .task { await setup() }
    }

    private func setup() async {
        do {
            let session = SessionData(
                sessionId: "your-session-id",
                hmacKey: "your-hmac-key",
                encryptionKey: "your-encryption-key"
            )
            let tx = TransactionData(
                amount: Decimal(10),
                currency: "USD",
                entryType: .ecom,
                intent: TransactionIntentData(card: .authorisation),
                merchantTransactionId: UUID().uuidString,
                merchantTransactionDate: { Date() }
            )
            let config = CheckoutConfig(
                environment: .test,
                session: session,
                transactionData: tx,
                merchantShopperId: "shopper-1",
                ownerType: "MerchantGroup",
                ownerId: "your-owner-id"
            )
            let pxp = try PxpCheckout.initialize(config: config)

            let cn = try pxp.create(.cardNumber, componentConfig: CardNumberComponentConfig(label: "Card number")) as! CardNumberComponent
            let ex = try pxp.create(.cardExpiryDate, componentConfig: CardExpiryDateComponentConfig(label: "Expiry")) as! CardExpiryDateComponent
            let cv = try pxp.create(.cardCvc, componentConfig: CardCvcComponentConfig(label: "CVC")) as! CardCvcComponent

            var sc = CardSubmitComponentConfig()
            sc.cardNumberComponent = cn
            sc.cardExpiryDateComponent = ex
            sc.cardCvcComponent = cv
            sc.submitText = "Pay now"
            sc.disableUntilValidated = true
            sc.onPreAuthorisation = { _ in
                // Can use await here for async operations
                return TransactionInitiationData()
            }
            sc.onPostAuthorisation = { result in
                if result is AuthorisedSubmitResult {
                    // Navigate to success
                }
            }
            sc.onSubmitError = { _ in }
            let submit = try pxp.create(.cardSubmit, componentConfig: sc) as! CardSubmitComponent

            await MainActor.run {
                checkout = pxp
                cardNumber = cn
                cardExpiry = ex
                cardCvc = cv
                cardSubmit = submit
            }
        } catch {
            // Handle setup error
        }
    }
}

Custom validation

Add your own validation logic before submission:

submitConfig.onCustomValidation = {
    // Can use await here for async validation
    return myCustomValidationCheck()
}

Programmatic submission

Hide the SDK button and use your own:

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

// In your SwiftUI view:
Button("Pay with custom button") {
    Task {
        await cardSubmit.submitAsync()
    }
}

Soft decline retry

Handle soft declines with retry logic:

submitConfig.onPreRetrySoftDecline = { result in
    .withConfig(
        SoftDeclineRetryConfig(
            retry: true,
            updatedConfigs: UpdatedAuthConfigs(
                onPreAuthentication: {
                    // Can use await here
                    return InitiateIntegratedAuthenticationData(
                        merchantCountryNumericCode: "826",
                        merchantLegalName: "Example Ltd",
                        challengeWindowSize: .size1,
                        requestorChallengeIndicator: .challengeRequestedMandate,
                        timeout: 180
                    )
                }
            )
        )
    )
}

With 3DS authentication

Enable Unity's 3DS strategy and implement authentication callbacks:

submitConfig.useUnityAuthenticationStrategy = true
submitConfig.onPreInitiateAuthentication = {
    // Can use await here
    return PreInitiateIntegratedAuthenticationData(providerId: "pxpfinancial", timeout: 120)
}
submitConfig.onPostInitiateAuthentication = { result in
    // Handle pre-initiate result
}
submitConfig.onPreAuthentication = {
    // Can use await here
    return InitiateIntegratedAuthenticationData(
        merchantCountryNumericCode: "826",
        merchantLegalName: "Example Ltd",
        challengeWindowSize: .size1,
        requestorChallengeIndicator: .challengeRequestedMandate,
        timeout: 180
    )
}
submitConfig.onPostAuthentication = { result in
    // Handle authentication result
}

For integration context, see Implementation. For 3DS details, see 3DS transactions. For validation behaviour, see Data validation.