Learn how to configure card components for iOS.
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.
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
}
)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 }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")
}
)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
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
)All components provide event handlers for user interactions and lifecycle events.
Common event patterns:
- Field lifecycle:
onChange,onFocus,onBlurfor field interactions - Validation:
onValidationPassed,onValidationFailedfor validation events - Payment flow:
onPreAuthorisation,onPostAuthorisation,onSubmitErrorfor payments - 3DS authentication:
onPreAuthentication,onPostAuthenticationfor 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()
}Components can be linked together for coordinated behaviour.
// 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// 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// 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! NewCardComponentChoose 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.
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
)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.
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 = trueTest 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
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)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
)