Learn how to use card components in your iOS project.
Every card flow follows the same pattern:
- Build
CheckoutConfigand callPxpCheckout.initialize(config:)(once per checkout session). - Create components with
try pxpCheckout.create(_:componentConfig:), passing the matching*ComponentConfig, and wire weak references where required (see About configuration). - Render in SwiftUI with
component.buildContent().
Add PXPCheckoutSDK to your app. See Install.
You'll need a session from your server (session ID, HMAC key, and encryption key) and a merchant shopper ID that matches your backend model.
Call PxpCheckout.initialize(config:) with a CheckoutConfig. This can throw if mandatory fields are missing or inconsistent.
import PXPCheckoutSDK
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key"
)
let transactionData = TransactionData(
amount: 10,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: .authorisation),
merchantTransactionId: "unique-transaction-id",
merchantTransactionDate: { Date() }
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "shopper-id",
ownerType: "MerchantGroup",
ownerId: "your-owner-id",
onGetShopper: { async in TransactionShopper(id: "shopper-id") },
analyticsEvent: { event in
_ = event.eventName
}
)
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)The following table describes the main properties in CheckoutConfig:
| Property | Description |
|---|---|
environmentEnvironment required | The environment type. Possible values:
|
sessionSessionData required | Details about the checkout session returned from the Unity Sessions API. Includes sessionId, hmacKey, encryptionKey, and optional allowedFundingTypes. |
transactionDataTransactionData required | Details about the transaction. See the properties below for required fields. |
transactionData.amountDecimal required | The transaction amount. Must match the currency's expected decimal places. |
transactionData.currencyString required | The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies. |
transactionData.entryTypeEntryType required | The entry type. Possible values:
|
transactionData.intentTransactionIntentData required | The transaction intents for each payment method. See Supported transaction intents for details. |
transactionData.merchantTransactionIdString required | A unique identifier for this transaction. Use a UUID or order ID. |
transactionData.merchantTransactionDate() -> Date required | A function that returns the current date and time. |
transactionData.recurringRecurringType? | Optional recurring payment data (frequencyInDays, frequencyExpiration). |
transactionData.linkIdString? | Optional link identifier for related transactions. |
transactionData.cardAcceptorNameString? | Optional card acceptor name for the transaction. |
merchantShopperIdString required | A unique identifier for the shopper in your system. Used for card tokenisation and Card-on-File functionality. |
ownerTypeString? | Always set to "MerchantGroup" for most integrations. Required for token and some API flows. |
ownerIdString required | Your unique merchant or merchant group identifier, as assigned by PXP. You can find it in the Unity Portal. |
localisationLocalisation? | Optional UI string overrides for customising displayed text. |
localeString? | Optional locale code in BCP 47 format (for example en-GB). |
paypalConfigPayPalConfig? | PayPal-specific settings when you use PayPal components. |
restrictionsRestrictions? | Optional card restrictions for owner types and funding sources. When both session and config restrictions are provided, they are merged as a union (session values first, then config values not already present). See Card restrictions for details. |
kountDisabledBool | Whether to disable Kount fraud detection. Defaults to false. |
onGetShippingAddress() async -> ShippingAddress? | async closure returning shipping address information. Used for pre-filling billing address from shipping address. |
onGetShopper() async -> TransactionShopper? | async closure returning shopper information. Required for card tokenisation and Card-on-File functionality. Returns shopper object containing id, email, firstName, and lastName. |
analyticsEvent(BaseAnalyticsEvent) -> Void | Optional analytics callback. See Analytics for details. |
Example for transactionData.recurring:
transactionData: TransactionData(
// ...
recurring: RecurringType(frequencyInDays: 30, frequencyExpiration: "2025-01-01")
)The transactionData.intent.card property accepts the following intent types:
| Intent | Description |
|---|---|
.authorisation | Reserve funds on the customer's payment method (capture later). The default for most payment flows. Use when you need to hold funds temporarily, for example pending order shipment. |
.purchase | Capture funds immediately after authorisation. Use for instant-fulfilment transactions like digital goods or instant services. |
.verification | Verify that a card is legitimate and active, without reserving any funds or completing a purchase. Use for validating payment methods during customer onboarding. |
.estimatedAuthorisation | Reserve funds on the customer's payment method, based on an estimated amount. The final amount is confirmed at capture time. Use in environments like hotels, car rental agencies, or fuel stations where the final charge may vary. |
.payout | Send funds to the cardholder's card (also called disbursement or withdrawal). Use for marketplace seller payments, insurance settlements, refunds, or prize distributions. Funds move from your merchant account to the recipient's card. |
You can restrict which cards are accepted based on owner type (corporate or consumer) and funding source (credit, debit, or prepaid). Restrictions can be set in two places:
- Session-level: returned from the backend in
SessionData.restrictions. - Config-level: passed directly to
CheckoutConfig.restrictions.
When both are provided, they're merged as a union (session values first, then config values not already present).
let restrictions = Restrictions(
card: Restrictions.Card(
ownerTypes: [.corporate, .consumer], // Optional
fundingSources: [.credit, .debit] // Optional
)
)
let checkoutConfig = CheckoutConfig(
// ... other parameters
restrictions: restrictions
)| Value | Description |
|---|---|
.corporate | Corporate/business cards |
.consumer | Consumer/personal cards |
| Value | Description |
|---|---|
.credit | Credit cards |
.debit | Debit cards |
.prepaid | Prepaid cards |
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "shopper-id",
ownerId: "your-owner-id",
restrictions: Restrictions(
card: Restrictions.Card(
ownerTypes: [.consumer],
fundingSources: [.credit, .debit]
)
)
)If ownerTypes or fundingSources is nil, no restriction is applied for that dimension. Setting both to nil means all cards are accepted.
Use try pxpCheckout.create(componentType, componentConfig:). Cast the returned BaseComponent to the concrete type when you need buildContent() or submit APIs.
let config = NewCardComponentConfig(/* … */)
guard let newCard = try pxpCheckout.create(.newCard, componentConfig: config) as? NewCardComponent else {
throw /* or handle configuration error */
}Embed buildContent() in your SwiftUI hierarchy. For CardSubmitComponent, submit collects data through weak references on CardSubmitComponentConfig:
- Pre-built
.newCard* field refs are wired internally; merchants only passsubmitinsideNewCardComponentConfig. - Standalone
.cardSubmit: assign weak refs before render/submit, e.g.,newCardComponentwhen reusing aNewCardComponentwith a separate submit button, orcardNumberComponent,cardExpiryDateComponent,cardCvcComponent,cardHolderNameComponent,cardConsentComponentfor fully standalone fields.
See About configuration for the full weak-reference matrix.
import SwiftUI
struct PaymentScreen: View {
let newCard: NewCardComponent
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("Payment")
.font(.title2)
newCard.buildContent()
}
.padding()
}
}
}- Hold
PxpCheckoutfor as long as the checkout session is valid (typically the lifetime of your payment screen or a coordinator you inject). - Recreate
PxpCheckoutwhen you start a new server session (newSessionData), not on every SwiftUI body refresh. - Components are reference types; avoid recreating them unnecessarily. If you use
@State, store the component instance you created afterinitialize, not a new config on every render. - There's no public SDK
cleanup(); dropping references when the customer leaves checkout is sufficient for typical apps.
Use FieldStyle, FieldInputStateStyles, ContainerStyle, ButtonStateStyles, and per-component configs. Each component guide lists the relevant structs — start from New card or Card number.
var numberConfig = CardNumberComponentConfig(
label: "Card number",
acceptedCardBrands: [.visa, .mastercard, .amex]
)iOS field configs use () -> Void for onChange / focus, and ([ValidationResult]) -> Void for validation callbacks (ValidationResult.valid, errors). Submit lifecycle lives on CardSubmitComponentConfig. onPreAuthorisation is required for card payment — if it's nil, submit stops silently (loading resets, no onPostAuthorisation, no onSubmitError).
var fields = NewCardComponentFields()
fields.cardNumber = CardNumberComponentConfig(
label: "Card number",
onCardBrandDetected: { event in
// CardBrandDetectionEvent: cardBrand, isCardBrandAccepted
},
onValidationFailed: { results in
for r in results where !r.valid {
r.errors?.values.forEach { print($0.code, $0.message) }
}
}
)
var submit = CardSubmitComponentConfig()
submit.onPreAuthorisation = { (preAuthData: PreAuthorizationData?) async -> TransactionInitiationData? in
// preAuthData?.gatewayTokenId, preAuthData?.schemeTokenId available after tokenisation
TransactionInitiationData(
psd2Data: nil,
riskScreeningData: nil
)
}
submit.onPostAuthorisation = { result in
if result is MerchantSubmitResult { /* success */ }
else if let refused = result as? RefusedSubmitResult { /* decline */ }
else if let failed = result as? FailedSubmitResult { /* API / transport failure */ }
}
submit.onSubmitError = { error in
print(error.errorCode, error.errorMessage) // BaseSdkException
}
let newCardConfig = NewCardComponentConfig(
fields: fields,
submit: submit,
onValidation: { results in
let ok = results.allSatisfy(\.valid)
// drive your own CTA state
}
)Details: Events.
Handle CheckoutConfig.analyticsEvent — pattern matching on BaseAnalyticsEvent subclasses (for example ComponentErrorAnalyticsEvent). See Analytics.
CheckoutConfig(
// …
analyticsEvent: { event in
if let err = event as? ComponentErrorAnalyticsEvent {
// log err.errorMessage, err.errorCode, etc.
}
}
)Use onSubmitError, do/catch around initialize / create, and downcast onPostAuthorisation results.
do {
let checkout = try PxpCheckout.initialize(config: checkoutConfig)
_ = checkout
} catch {
// Show configuration / session error
}Minimal SwiftUI screen: bootstrap checkout, create new card, render it. Replace placeholders with real session values and tighten error handling for production.
import SwiftUI
import PXPCheckoutSDK
@MainActor
final class NewCardPaymentModel: ObservableObject {
/// Keep checkout alive for the lifetime of the payment screen.
private var pxpCheckout: PxpCheckout?
@Published private(set) var newCard: NewCardComponent?
@Published var bootstrapError: String?
func load() {
bootstrapError = nil
do {
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key"
)
let transactionData = TransactionData(
amount: 10,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: .authorisation),
merchantTransactionId: UUID().uuidString,
merchantTransactionDate: { Date() }
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "shopper-id",
ownerType: "MerchantGroup",
ownerId: "your-owner-id",
onGetShopper: { async in TransactionShopper(id: "shopper-id") }
)
let checkout = try PxpCheckout.initialize(config: checkoutConfig)
self.pxpCheckout = checkout
var submitConfig = CardSubmitComponentConfig()
submitConfig.onPreAuthorisation = { (preAuthData: PreAuthorizationData?) async -> TransactionInitiationData? in
TransactionInitiationData(
psd2Data: nil,
riskScreeningData: nil
)
}
submitConfig.onPostAuthorisation = { result in
if result is MerchantSubmitResult {
// Go to success screen
} else if let refused = result as? RefusedSubmitResult {
_ = refused.provider?.message
}
}
submitConfig.onSubmitError = { error in
_ = error.errorCode
}
var fields = NewCardComponentFields()
fields.cardNumber = CardNumberComponentConfig(label: "Card number")
fields.expiryDate = CardExpiryDateComponentConfig(label: "Expiry")
fields.cvc = NewCardOptionalField(
config: CardCvcComponentConfig(label: "CVC", isRequired: true),
isShown: true
)
let newCardConfig = NewCardComponentConfig(
fields: fields,
submit: submitConfig
)
newCard = try checkout.create(.newCard, componentConfig: newCardConfig) as? NewCardComponent
} catch {
bootstrapError = error.localizedDescription
}
}
}
struct NewCardPaymentView: View {
@StateObject private var model = NewCardPaymentModel()
var body: some View {
Group {
if let card = model.newCard {
card.buildContent()
} else if let message = model.bootstrapError {
Text(message).foregroundStyle(.red)
} else {
ProgressView()
}
}
.padding()
.onAppear { model.load() }
}
}