Learn about how to configure the card submit component.
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! CardSubmitComponentRender the components in your SwiftUI view:
VStack {
cardNumber.buildContent()
HStack {
cardExpiry.buildContent()
cardCvc.buildContent()
}
cardSubmit.buildContent()
}| Property | Description |
|---|---|
cardNumberComponentCardNumberComponent? | The card number input component. Required when using standalone card fields (not using newCardComponent). See Card number. Defaults to nil. |
cardExpiryDateComponentCardExpiryDateComponent? | The expiry date component. Required when using standalone card fields (not using newCardComponent). See Card expiry date. Defaults to nil. |
cardCvcComponentCardCvcComponent? | The CVC component. Required when using standalone card fields (not using newCardComponent). See Card CVC. Defaults to nil. |
newCardComponentNewCardComponent? | 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.
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| Property | Description |
|---|---|
submitTextString? | The button text. Supports {amount} and {currency} placeholders which are automatically replaced with the transaction values. Defaults to SDK localisation. |
disableUntilValidatedBool? | 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. |
hideSubmitButtonBool? | Whether to hide the button so you can call submitAsync() from your own control. When true, you must manually trigger submission. Defaults to false. |
stylesButtonStateStyles? | 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). |
cardHolderNameComponentCardHolderNameComponent? | The optional cardholder name field. When provided, the cardholder's name is included in the tokenisation request. See Cardholder name. Defaults to nil. |
cardConsentComponentCardConsentComponent? | 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. |
cardOnFileComponentCardOnFileComponent? | The saved cards component. Required when useCardOnFile is true. Displays previously saved card tokens. See Card-on-file. Defaults to nil. |
clickOnceStandaloneComponentClickOnceStandaloneComponent? | The Click Once component for one-click payments. Displays the most recently used card with a submit button. See Click-once. Defaults to nil. |
useCardOnFileBool? | Whether to use a tokenised card instead of new card entry. When true, cardOnFileComponent must be provided. Defaults to false. |
avsRequestBool? | Whether to send billing address for address verification (AVS). When true, billingAddressComponents must be provided. Defaults to false. |
billingAddressComponentsBillingAddressComponents? | The billing address components for AVS. Required when avsRequest is true. Defaults to nil. |
useUnityAuthenticationStrategyBool? | Whether to use Unity's integrated 3DS strategy. When true, the SDK handles 3DS authentication automatically using Unity's service. Defaults to false. |
authenticationConfigAuthenticationConfig? | The optional authentication configuration object. Provides additional control over 3DS authentication behaviour. Defaults to nil. |
billingAddressComponents.billingAddressComponentBillingAddressComponent? | The pre-built billing address form including country, postcode, and street address. See Billing address. |
billingAddressComponents.countrySelectionComponentCountrySelectionComponent? | The standalone country field. Alternative to using the pre-built billing address form. See Country selection. |
billingAddressComponents.postcodeComponentPostcodeComponent? | The standalone postcode field. Alternative to using the pre-built billing address form. See Postcode. |
billingAddressComponents.addressComponentAddressComponent? | 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.
The card submit button uses built-in styling with colors, padding, and loading states.
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)
)
)| Property | Description |
|---|---|
styles.baseFieldStyle? | 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.disabledFieldStyle? | The button appearance when disabled due to validation failure or processing. Uses the same properties as base. |
styles.loadingFieldStyle? | The button appearance during payment processing. Typically shows a loading indicator. Uses the same properties as base. |
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
}| Callback | Description |
|---|---|
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.
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:
- Validates linked components and AVS data
- Tokenises the card (for new card flows)
- Runs 3DS authentication when required
- Calls
onPreAuthorisationand processes authorisation - Invokes configured callbacks in sequence
Returns SwiftUI content for the submit button:
cardSubmit.buildContent()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
}
}
}Add your own validation logic before submission:
submitConfig.onCustomValidation = {
// Can use await here for async validation
return myCustomValidationCheck()
}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()
}
}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
)
}
)
)
)
}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.