SDK API Reference
SDK API Reference
Current version: 3.0.0
Minimum deployment target: iOS 14.0
Swift version: 5.0+
Distribution: Swift Package Manager (signed XCFramework)
Installation
Swift Package Manager, using the package URL:
https://github.com/payrails/ios-sdk.git
dependencies: [
.package(url: "https://github.com/payrails/ios-sdk.git", from: "3.0.0")
]The package resolves a prebuilt, signed Payrails.xcframework and verifies it against the checksum in the package manifest. PayrailsCSE and PayPalCheckout resolve as separate package dependencies.
Getting started
Payrails.InitData
Payrails.InitDataHolds the init payload returned by your backend after calling the Payrails initialization endpoint.
public struct Payrails.InitData: Codable {
public init(version: String, data: String)
public let version: String // Version string from backend response
public let data: String // Base64-encoded JSON payload from backend response
}Payrails.Options
Payrails.OptionsRuntime options passed to Configuration.
public struct Payrails.Options {
public init(env: Payrails.Env = .production)
public let env: Payrails.Env
}
public enum Payrails.Env: String {
case production
case test
}Payrails.Configuration
Payrails.ConfigurationWraps InitData and Options as the input to createSession.
public struct Payrails.Configuration {
public init(initData: Payrails.InitData, option: Payrails.Options)
public let initData: Payrails.InitData
public let option: Payrails.Options
}Payrails.createSession(with:onSessionExpired:onRequestStart:)
Payrails.createSession(with:onSessionExpired:onRequestStart:)Creates and stores a session. All factory methods use the most recently created session.
// Async/await
public static func createSession(
with configuration: Payrails.Configuration,
onSessionExpired: SessionExpiredHandler? = nil,
onRequestStart: RequestStartHandler? = nil
) async throws -> Payrails.Session
// Callback
public static func createSession(
with configuration: Payrails.Configuration,
onSessionExpired: SessionExpiredHandler? = nil,
onRequestStart: RequestStartHandler? = nil,
onInit: OnInitCallback
)
public typealias OnInitCallback = (Result<Payrails.Session, PayrailsError>) -> Void
public typealias SessionExpiredHandler = (
@escaping (Result<Payrails.InitData, Error>) -> Void
) -> Void
public typealias RequestStartHandler = (
Payrails.RequestStartContext,
@escaping (Payrails.RequestStartDecision) -> Void
) -> VoidThe onSessionExpired closure lets the SDK self-heal when the underlying Payrails execution becomes unreusable (typically: user abandoned a 3DS challenge and the backend execution stayed in authorizePending). The merchant supplies a closure that fetches fresh InitData from their backend; the SDK swaps its internal config in place — the merchant's Session reference and cached buttons / forms keep working unchanged.
let session = try await Payrails.createSession(
with: Payrails.Configuration(initData: initData, option: .init(env: .production)),
onSessionExpired: { completion in
myBackend.fetchPayrailsInit { result in
completion(result) // .success(Payrails.InitData) or .failure(Error)
}
}
)If the closure is omitted, the SDK logs a warning at init time and cannot recover from a poisoned execution — the next payment attempt against that Session will fail naturally.
onRequestStart
onRequestStartOptional gate invoked once per payment attempt, before the authorization request is sent and before any provider UI (wallet sheet, PayPal sheet, redirect) is presented. Calling completion(.proceed) lets the attempt continue; completion(.refuse()) stops it.
The gate fires for every payment method configured on the session. A handler that only gates one method must call completion(.proceed) on the other branches.
public extension Payrails {
enum RequestStartDecision {
case proceed
case refuse(message: String?)
/// Refuse without a reason.
public static func refuse() -> RequestStartDecision
}
struct RequestStartContext {
/// The Payrails execution this attempt runs against, when one is known.
public let executionId: String?
/// `"card"`, `"payPal"`, `"applePay"`, or any other configured code.
public let paymentMethodCode: String
/// Whether the SDK is about to authorize or tokenize.
public let action: Action
public enum Action: String {
case authorize = "AUTHORIZE"
case tokenize = "TOKENIZE"
}
}
}Action.tokenize is reserved. The tokenization flow is not gated in this version, so action is always .authorize today.
refuse(message:)'s message, when supplied, becomes AuthorizationFailure.message on the delivered .validationFailed. It is passed through verbatim and is not displayed by the SDK.
| Handler behaviour | Result |
|---|---|
completion(.proceed) | Authorization proceeds |
completion(.refuse(message:)) | Attempt stopped; delegate receives .validationFailed carrying message |
completion(.refuse()) | Attempt stopped; delegate receives .validationFailed with a generic description |
completion not called within 10 seconds | Attempt stopped with a generic description; a warning is logged |
completion called more than once | First answer decides; later calls ignored |
The timeout case does not carry the SDK's diagnostic into failure.message: it describes an integration fault rather than something phrased for a customer. Only a deliberate .refuse(message:) travels outward.
When the attempt is stopped, no authorization request is sent, isPaymentInProgress returns to false, and the initiating element's delegate receives onAuthorizeFailed(_:failure:) with failure.code == .validationFailed.
Omitting the handler leaves the payment path fully synchronous — the SDK skips the gate rather than taking an asynchronous detour.
Session
Payrails.Session is returned from createSession and is the single typed API surface for headless integrations.
When to use query(_:) vs session methods directly:
query(_:)— stateless reads of session metadata (holder reference, amount, execution ID, API links, payment method config, stored instruments). Single unified accessor returning aPayrailsQueryResultenum.- Session methods — actions and mutations (
executePayment,tokenize,deleteInstrument,updateInstrument,update), device-capability checks (isApplePayAvailable), or typed reads where merchants prefer concrete return types over an enum (getPaymentMethodConfig(_:)).
Rule of thumb: query(_:) reads data; session methods do things, check the device, or return typed values.
public class Payrails.Session {
// Availability — device capability only
// Compose with getPaymentMethodConfig(.specific("apple_pay")) for
// the combined "configured and device capable" signal.
public var isApplePayAvailable: Bool { get }
// Payment method configuration
public func getPaymentMethodConfig(_ filter: PaymentMethodFilter = .all) -> [PayrailsPaymentOption]
// Payment execution — callback variants
public func executePayment(
with type: PaymentType,
paymentMethodCode: String?,
saveInstrument: Bool,
presenter: PaymentPresenter?,
onResult: @escaping OnPayCallback
)
public func executePayment(
withStoredInstrument instrument: StoredInstrument,
presenter: PaymentPresenter?,
onResult: @escaping OnPayCallback
)
// Payment execution — async variants
@MainActor public func executePayment(
with type: Payrails.PaymentType,
paymentMethodCode: String?,
saveInstrument: Bool,
presenter: PaymentPresenter?
) async -> OnPayResult
@MainActor public func executePayment(
withStoredInstrument instrument: StoredInstrument,
presenter: PaymentPresenter?
) async -> OnPayResult
// Tokenization — save an instrument without paying (async + callback)
public func tokenize(
_ request: Payrails.TokenizationRequest,
options: TokenizeOptions = TokenizeOptions()
) async throws -> SaveInstrumentResponse
public func tokenize(
_ request: Payrails.TokenizationRequest,
options: TokenizeOptions = TokenizeOptions(),
onSuccess: @escaping (SaveInstrumentResponse) -> Void,
onFailed: @escaping (PayrailsError) -> Void,
onCancelled: @escaping () -> Void
)
// Instrument management
public func deleteInstrument(instrumentId: String) async throws -> DeleteInstrumentResponse
public func updateInstrument(instrumentId: String, body: UpdateInstrumentBody) async throws -> UpdateInstrumentResponse
// Session state
public func query(_ key: PayrailsQueryKey) -> PayrailsQueryResult?
public func update(_ options: UpdateOptions)
}Payment types
public enum Payrails.PaymentType: String {
case card
case applePay
case payPal
case genericRedirect
}Factory methods
All factory methods are static methods on Payrails and require an active session.
Card form
public static func createCardForm(
config: CardFormConfig? = nil,
showSaveInstrument: Bool = false
) -> Payrails.CardFormPayrails.CardForm is a UIStackView subclass. Add it to your view hierarchy and constrain with Auto Layout.
Card payment button
// Card form mode — requires prior createCardForm()
public static func createCardPaymentButton(
buttonStyle: CardButtonStyle? = nil,
translations: CardPaymenButtonTranslations
) -> Payrails.CardPaymentButton
// Stored instrument mode
public static func createCardPaymentButton(
storedInstrument: StoredInstrument,
buttonStyle: StoredInstrumentButtonStyle? = nil,
translations: CardPaymenButtonTranslations,
storedInstrumentTranslations: StoredInstrumentButtonTranslations? = nil
) -> Payrails.CardPaymentButtonApple Pay button
public static func createApplePayButton(
type: PKPaymentButtonType,
style: PKPaymentButtonStyle,
showSaveInstrument: Bool = false
) -> ApplePayElementPayPal button
public static func createPayPalButton(showSaveInstrument: Bool = false) -> PaypalElementGeneric redirect button
public static func createGenericRedirectButton(
buttonStyle: CardButtonStyle? = nil,
translations: CardPaymenButtonTranslations,
paymentMethodCode: String
) -> Payrails.GenericRedirectButtonStored instruments
public static func createStoredInstruments(
style: StoredInstrumentsStyle? = nil,
translations: StoredInstrumentsTranslations? = nil,
showDeleteButton: Bool = false,
showUpdateButton: Bool = false,
showPayButton: Bool = false
) -> Payrails.StoredInstrumentsStatic helpers
// Returns all stored instruments (card + PayPal)
public static func getStoredInstruments() -> [StoredInstrument]
// Returns stored instruments for a specific payment type
public static func getStoredInstruments(for type: Payrails.PaymentType) -> [StoredInstrument]
// Runtime session state update
public static func update(_ options: UpdateOptions)
// Query session state
public static func query(_ key: PayrailsQueryKey) -> PayrailsQueryResult?Instrument management (delete/update) moved to typed Session methods in 1.28.0.
Usesession.deleteInstrument(instrumentId:)andsession.updateInstrument(instrumentId:body:)directly — see the Session block above.
Query API
PayrailsQueryKey
PayrailsQueryKeypublic enum PayrailsQueryKey {
case executionId
case holderReference
case amount
case binLookup
case instrumentDelete
case instrumentUpdate
case paymentMethodConfig(PaymentMethodFilter)
case paymentMethodInstruments(type: Payrails.PaymentType)
}PaymentMethodFilter
PaymentMethodFilterpublic enum PaymentMethodFilter {
case all
case redirect
case specific(String) // paymentMethodCode
}PayrailsQueryResult
PayrailsQueryResultpublic enum PayrailsQueryResult {
case string(String)
case amount(PayrailsAmount)
case link(PayrailsLink)
case paymentOptions([PayrailsPaymentOption])
case storedInstruments([StoredInstrument])
}Supporting types
public struct PayrailsAmount {
public let value: String
public let currency: String
}
public struct PayrailsLink {
public let method: String?
public let href: String?
}
public struct PayrailsPaymentOption {
public let paymentMethodCode: String
public let description: String?
public let integrationType: String
public let clientConfig: ClientConfig?
public struct ClientConfig {
public let displayName: String?
public let flow: String?
public let supportsSaveInstrument: Bool?
public let supportsBillingInfo: Bool?
}
}How to Query Session Data
Payrails.query(_:) provides read-only access to the current session's configuration and state. Use it to retrieve the execution ID, payment amount, stored instruments, API links, and more — without reaching into internal session state.
When to use query(_:) vs Session methods
query(_:) vs Session methods- Use
query(_:)for stateless reads of session metadata:.holderReference,.amount,.executionId,.binLookup,.paymentMethodConfig(...),.paymentMethodInstruments(...). - Use Session methods directly for actions (
executePayment,deleteInstrument,updateInstrument,update), device-capability checks (isApplePayAvailable), or typed reads where merchants prefer concrete return types over an enum (getPaymentMethodConfig(_:)).
Rule of thumb: query(_:) reads data. Session methods do things, check the device, or return typed values where an enum would add friction.
Prerequisites
An active session must exist (created via Payrails.createSession(with:)). All queries return nil when no session is active.
Calling Payrails.query
Payrails.querylet result: PayrailsQueryResult? = Payrails.query(.amount)The return type is PayrailsQueryResult?, a Swift enum. Switch on it to extract the typed value:
switch Payrails.query(.amount) {
case .amount(let amount):
print("Amount:", amount.value, amount.currency)
case .none:
print("No active session")
default:
break
}Available query keys
.executionId
.executionIdThe execution ID for the current checkout. Pass this to your backend for order correlation.
if case .string(let executionId) = Payrails.query(.executionId) {
print("Execution ID:", executionId)
// myBackend.attachExecutionId(executionId)
}.holderReference
.holderReferenceThe holder reference (customer identifier) associated with this session.
if case .string(let ref) = Payrails.query(.holderReference) {
print("Holder reference:", ref)
}.amount
.amountThe payment amount and currency for the current execution.
if case .amount(let payrailsAmount) = Payrails.query(.amount) {
let display = "\(payrailsAmount.currency) \(payrailsAmount.value)"
amountLabel.text = display
}.binLookup
.binLookupThe API link for BIN lookup. Use this to call the lookup endpoint and determine card network, country, and 3DS requirements before payment.
if case .link(let link) = Payrails.query(.binLookup) {
print("BIN lookup URL:", link.href ?? "")
print("Method:", link.method ?? "")
}.instrumentDelete
.instrumentDeleteThe API link for deleting a stored instrument.
if case .link(let link) = Payrails.query(.instrumentDelete) {
// Use link.href and link.method to build the request in your networking layer
}.instrumentUpdate
.instrumentUpdateThe API link for updating a stored instrument (e.g. setting as default).
if case .link(let link) = Payrails.query(.instrumentUpdate) {
// Use link.href and link.method to build the request
}.paymentMethodConfig(filter:)
.paymentMethodConfig(filter:)Configuration for available payment methods, filtered by a PaymentMethodFilter.
// All payment methods
if case .paymentOptions(let options) = Payrails.query(.paymentMethodConfig(.all)) {
for option in options {
print(option.paymentMethodCode, option.clientConfig?.displayName ?? "")
}
}
// Redirect-based methods only
if case .paymentOptions(let options) = Payrails.query(.paymentMethodConfig(.redirect)) {
// Build redirect payment buttons dynamically
}
// A specific method by code
if case .paymentOptions(let options) = Payrails.query(.paymentMethodConfig(.specific("ideal"))) {
let ideal = options.first
print("iDEAL display name:", ideal?.clientConfig?.displayName ?? "")
}.paymentMethodInstruments(type:)
.paymentMethodInstruments(type:)The stored instruments for a given payment type.
// Card instruments
if case .storedInstruments(let cards) = Payrails.query(.paymentMethodInstruments(type: .card)) {
print("Saved cards:", cards.count)
for card in cards {
print(" -", card.id, card.type.rawValue)
}
}
// PayPal instruments
if case .storedInstruments(let paypals) = Payrails.query(.paymentMethodInstruments(type: .payPal)) {
print("Saved PayPal accounts:", paypals.count)
}Summary table
| Key | Returns | Description |
|---|---|---|
.executionId | .string | Current execution ID |
.holderReference | .string | Customer holder reference |
.amount | .amount(PayrailsAmount) | Payment amount and currency |
.binLookup | .link(PayrailsLink) | BIN lookup API link |
.instrumentDelete | .link(PayrailsLink) | Instrument delete API link |
.instrumentUpdate | .link(PayrailsLink) | Instrument update API link |
.paymentMethodConfig(.all) | .paymentOptions([PayrailsPaymentOption]) | All payment method configs |
.paymentMethodConfig(.redirect) | .paymentOptions([PayrailsPaymentOption]) | Redirect-only methods |
.paymentMethodConfig(.specific(code)) | .paymentOptions([PayrailsPaymentOption]) | Single method config |
.paymentMethodInstruments(type:) | .storedInstruments([StoredInstrument]) | Saved instruments by type |
Updating session state
public struct UpdateOptions {
public var amount: PayrailsAmount?
public init(amount: PayrailsAmount? = nil)
}
// Usage
Payrails.update(UpdateOptions(amount: PayrailsAmount(value: "57.49", currency: "USD")))How to Update the Checkout Amount
The checkout amount is set when the session is initialized from the init payload. If the amount changes after initialization — for example, the user adds a tip, chooses express shipping, or applies a discount code — you must update both your backend and the SDK in lockstep.
Important: The SDK amount and the amount recorded in the Payrails execution must always match. A mismatch causes the payment to be rejected with a 401 error.
How amount updates work
Updating the amount is a two-step process:
1. Recalculate amount in your UI (e.g. user selects a tip)
2. Call your backend to update the execution amount in Payrails
3. Call Payrails.update(options:) on the client to sync the SDK
4. User taps the pay button — amount is now consistent
Both steps must complete before the user initiates payment.
Step 1: Recalculate the amount
let subtotal = 49.99
let tipPercentage = 0.15
let total = subtotal + (subtotal * tipPercentage)
let formattedTotal = String(format: "%.2f", total) // "57.49"
let currency = "USD"Step 2: Update the amount on your backend
Call your backend, which calls the Payrails API to update the execution amount. The exact endpoint and request shape are defined by your backend implementation.
func updateExecutionAmount(value: String, currency: String) async throws {
// Your backend call — POST /executions/{id}/update or similar
// This MUST complete before calling Payrails.update()
try await myBackendClient.updateCheckoutAmount(value: value, currency: currency)
}Step 3: Update the SDK amount
After the backend confirms the update, sync the SDK:
let newAmount = PayrailsAmount(value: formattedTotal, currency: currency)
Payrails.update(UpdateOptions(amount: newAmount))Complete example: tip selection
class CheckoutViewController: UIViewController {
private var selectedTipRate: Double = 0.0
@IBAction func tipButtonTapped(_ sender: UIButton) {
let tipRate = tipRate(for: sender.tag)
selectTip(rate: tipRate)
}
private func selectTip(rate: Double) {
selectedTipRate = rate
updateAmountDisplay()
Task {
await applyTipToPayment(rate: rate)
}
}
private func updateAmountDisplay() {
let total = calculateTotal(tipRate: selectedTipRate)
amountLabel.text = formatAmount(total)
}
private func applyTipToPayment(rate: Double) async {
let total = calculateTotal(tipRate: rate)
let formatted = String(format: "%.2f", total)
do {
// Step 1: Update the backend execution
try await myBackend.updateExecutionAmount(value: formatted, currency: "USD")
// Step 2: Sync the SDK
let newAmount = PayrailsAmount(value: formatted, currency: "USD")
Payrails.update(UpdateOptions(amount: newAmount))
} catch {
showError("Failed to apply tip: \(error.localizedDescription)")
}
}
private func calculateTotal(tipRate: Double) -> Double {
let subtotal = 49.99
return subtotal + (subtotal * tipRate)
}
}After a redirect session recovery
If the user's payment involved a redirect (e.g. PayPal, generic redirect) and the app returned from the background, the session may be restored from the original init payload. In this case:
- Any in-memory amount updates made via
Payrails.update()are reset to the original init payload amount. - If you need to preserve the updated amount after a redirect, you must re-apply
Payrails.update()once the session is restored.
Troubleshooting
Payment rejected with 401 / authorization error
The SDK amount does not match the Payrails execution amount. Verify that your backend update completed successfully before calling Payrails.update().
Payrails.update() appears to have no effect
If there is no active session, the call is silently dropped. Ensure Payrails.createSession() has completed successfully before calling update(). Check for any No active Payrails session log messages.
Amount label does not update
Payrails.update() updates the internal SDK state; it does not automatically refresh any UI element. Update your amount label independently after recalculating.
Callbacks and results
Payment outcomes are delivered to your CardPaymentButton / CardPaymentForm / StoredInstrumentPaymentButton / GenericRedirectButton delegate. Failures arrive as an AuthorizationFailure struct — a flat { code, message, rawError } shape that mirrors the Web SDK's onFailed payload:
/// Payload passed to `onAuthorizeFailed(_:failure:)` on every delegate protocol.
public struct AuthorizationFailure {
/// Discriminating code — switch on this to give each outcome the right UX.
public let code: AuthorizationFailureReason
/// Human-readable detail. Backend's `errors[0].reason.result` for `.authorizationError`,
/// generic fallback for the other cases. Never nil.
public let message: String
/// Underlying error when one exists (typically populated for `.unknownError`).
public let rawError: Error?
}
/// String-raw-valued discriminator. Raw values match the Web SDK's
/// `AuthorizationFailureReasons` 1:1.
public enum AuthorizationFailureReason: String {
/// Authorization rejected — issuer declined, 3DS rejected, fraud blocked, etc.
case authorizationError = "AUTHORIZATION_ERROR"
/// Session token was rejected (HTTP 401 / 403). The merchant must
/// re-initialise the session; the SDK also fires its `onSessionExpired`
/// refresh in the background.
case authenticationError = "AUTHENTICATION_ERROR"
/// The user intentionally abandoned the flow (e.g. swiped the 3DS sheet away).
case userCancelled = "USER_CANCELLED"
/// Network failure, decode error, encryption failure, polling timeout, or
/// any other unexpected error. `rawError` carries the underlying error.
case unknownError = "UNKNOWN_ERROR"
/// The merchant's `onRequestStart` handler stopped the payment before it
/// started — it answered `false`, or did not answer within the timeout.
/// No authorization request was sent, so this is not a decline.
case validationFailed = "VALIDATION_FAILED"
}Client-side input validation never reaches this path: an element early-returns on an invalid form rather than emitting a failure.
.validationFailedis reserved for anonRequestStarthandler blocking the attempt.
Delegate callbacks fired
| Outcome | Delegate method fired |
|---|---|
| Authorization succeeded | onAuthorizeSuccess(self) |
| Backend declined / rejected authorization | onAuthorizeFailed(self, failure: .authorizationError(message:)) |
| Session token expired / rejected | onAuthorizeFailed(self, failure: .authenticationError) |
| User dismissed 3DS sheet (no backend terminal confirmed) | onAuthorizeFailed(self, failure: .userCancelled) |
| Network / SDK error | onAuthorizeFailed(self, failure: .unknownError(_)) |
onRequestStart refused the attempt | onAuthorizeFailed(self, failure: .validationFailed(message:)) |
| Backend execution pending with no action required | onAuthorizePending(self) |
When the user dismisses the 3DS sheet (or any other path that leaves the Payrails execution in authorizePending), the SDK additionally invokes the merchant's onSessionExpired closure (supplied at createSession) in the background to rebuild its internal config in place — the merchant's Session reference keeps working. If the closure was not supplied, the SDK logs a warning at createSession time and the next payment attempt against the Session will fail naturally against the dead execution.
Payment presenter protocol
Required to present view controllers during payment (e.g. 3DS challenges):
public protocol PaymentPresenter: AnyObject {
func presentPayment(_ viewController: UIViewController)
var encryptedCardData: String? { get set }
}Typically conformed to by a UIViewController:
extension MyCheckoutViewController: PaymentPresenter {
func presentPayment(_ viewController: UIViewController) {
present(viewController, animated: true)
}
}Delegate protocols
onPaymentButtonClickedis a notification, not a gate. It tells you the customer tapped, for
analytics, observability or showing a spinner. It returnsVoidand the SDK does not wait for it,
so it cannot stop or defer a payment. To make the payment conditional on your own check, use
onRequestStart— the SDK awaits that one and honours its answer.
PayrailsCardPaymentButtonDelegate
PayrailsCardPaymentButtonDelegatepublic protocol PayrailsCardPaymentButtonDelegate: AnyObject {
func onPaymentButtonClicked(_ button: Payrails.CardPaymentButton)
func onAuthorizeSuccess(_ button: Payrails.CardPaymentButton)
func onAuthorizePending(_ button: Payrails.CardPaymentButton)
func onThreeDSecureChallenge(_ button: Payrails.CardPaymentButton)
/// Fires for every terminal failure of an authorization attempt. The `failure`
/// payload's `.code` discriminates between issuer decline, authentication
/// failure, user cancellation, and other errors — see `AuthorizationFailure`.
func onAuthorizeFailed(_ button: Payrails.CardPaymentButton, failure: AuthorizationFailure)
/// Optional — default no-op.
func onStoredInstrumentChanged(_ button: Payrails.CardPaymentButton, instrument: StoredInstrument?)
}Breaking change in ONB-739. The pre-ONB-739 signature was
onAuthorizeFailed(_ button: Payrails.CardPaymentButton)with no payload.
Merchants migrating from earlier versions must update their delegate
conformance to takefailure: AuthorizationFailureand (if they relied on
session-expiry signaling) supply theonSessionExpiredclosure to
createSession. See the
card-payment-flow documentation
for a migration example.
PayrailsCardFormDelegate
PayrailsCardFormDelegatepublic protocol PayrailsCardFormDelegate: AnyObject {
func cardForm(_ view: Payrails.CardForm, didCollectCardData data: String)
func cardForm(_ view: Payrails.CardForm, didFailWithError error: Error)
func cardForm(_ view: Payrails.CardForm, didChangePreferredScheme change: PreferredSchemeChange)
}didChangePreferredScheme is optional — a protocol-extension default provides a no-op, so implementations that predate co-branded support continue to compile. See Co-branded cards.
Co-branded cards
Emitted by the card form when a card carries two payment networks. Requires featureConfig.coBrandedCardsRollout and a links.binLookup entry in the session init response; absent either, none of this is produced.
public struct PreferredSchemeChange: Equatable {
public let preferredScheme: String?
public let cardSchemes: [CardScheme]
}
public struct CardScheme: Equatable {
public let code: String
public let name: String
public let logoUrl: URL?
public let selected: Bool
}| Field | Type | Notes |
|---|---|---|
preferredScheme | String? | Scheme code sent with the authorization. nil when the card is not co-branded |
cardSchemes | [CardScheme] | Both available schemes. Empty when the card is not co-branded |
code | String | "visa", "cartesBancaires", "mada", … |
name | String | Display name |
logoUrl | URL? | Brand logo, when the scheme has one |
selected | Bool | Whether this scheme is the current choice |
cardForm(_:didChangePreferredScheme:) fires when a co-branded BIN resolves, when the shopper selects a different brand, and when the card number changes such that co-branded state clears. The clearing case delivers an empty cardSchemes and a nil preferredScheme.
The selected scheme is carried into the authorization request by the SDK. Callers do not pass it to any payment method.
Styling is CardFormStyle.cardBrandSelector: CardBrandSelectorStyle?; nil keeps the SDK default. The selector's heading and subheading are CardTranslations.Labels.cardBrandSelectorTitle and .cardBrandSelectorSubtitle; each falls back to an SDK default when nil.
PayrailsApplePayButtonDelegate
PayrailsApplePayButtonDelegatepublic protocol PayrailsApplePayButtonDelegate: AnyObject {
func onPaymentButtonClicked(_ button: Payrails.ApplePayButton)
func onAuthorizeSuccess(_ button: Payrails.ApplePayButton)
func onAuthorizeFailed(_ button: Payrails.ApplePayButton, failure: AuthorizationFailure)
func onPaymentSessionExpired(_ button: Payrails.ApplePayButton)
}Deprecated:
onAuthorizeFailed(_ button: Payrails.ApplePayButton)with no payload. A default
implementation forwards to it, so existing integrations keep receiving failures, but only the
failure:variant carries the discriminatingcode— the sole way to distinguish an
onRequestStartblock from an issuer decline.
PayrailsPayPalButtonDelegate
PayrailsPayPalButtonDelegatepublic protocol PayrailsPayPalButtonDelegate: AnyObject {
func onPaymentButtonClicked(_ button: Payrails.PayPalButton)
func onAuthorizeSuccess(_ button: Payrails.PayPalButton)
func onAuthorizeFailed(_ button: Payrails.PayPalButton, failure: AuthorizationFailure)
func onPaymentSessionExpired(_ button: Payrails.PayPalButton)
}Deprecated:
onAuthorizeFailed(_ button: Payrails.PayPalButton)with no payload. Same
forwarding default and same reasoning as Apple Pay above.
PayrailsStoredInstrumentsDelegate
PayrailsStoredInstrumentsDelegatepublic protocol PayrailsStoredInstrumentsDelegate: AnyObject {
func storedInstruments(_ view: Payrails.StoredInstruments, didSelectInstrument instrument: StoredInstrument)
func storedInstruments(_ view: Payrails.StoredInstruments, didCompletePaymentForInstrument instrument: StoredInstrument)
func storedInstruments(_ view: Payrails.StoredInstruments, didFailPaymentForInstrument instrument: StoredInstrument, error: PayrailsError)
func storedInstruments(_ view: Payrails.StoredInstruments, didRequestDeleteInstrument instrument: StoredInstrument)
func storedInstruments(_ view: Payrails.StoredInstruments, didRequestUpdateInstrument instrument: StoredInstrument)
}Error handling
PayrailsError
PayrailsErrorpublic enum PayrailsError: Error, LocalizedError {
case authenticationError
case sdkNotInitialized
case missingData(String?)
case invalidDataFormat
case invalidCardData
case unknown(error: Error?)
case unsupportedPayment(type: Payrails.PaymentType)
case incorrectPaymentSetup(type: Payrails.PaymentType)
case pollingFailed(String)
case failedToDerivePaymentStatus(String)
case finalStatusNotFoundAfterLongPoll(String)
case longPollingFailed(underlyingError: Error?)
}PayrailsError conforms to LocalizedError; use error.errorDescription for a human-readable message.
Tokenization
Saves a payment method as a reusable instrument without charging the customer. tokenize is unified across methods: the TokenizationRequest case selects which instrument to tokenize and carries what that method needs. Both overloads live on Payrails.Session (see Session), alongside executePayment.
public enum Payrails.TokenizationRequest {
case applePay(presenter: PaymentPresenter) // SDK presents the Apple Pay sheet
case card(CardForm) // SDK reads + encrypts the embedded card form
}
public struct TokenizeOptions {
public let storeInstrument: Bool // default false
public let futureUsage: FutureUsage // default .cardOnFile
}
public enum FutureUsage: String {
case cardOnFile
case subscription
case unscheduledCardOnFile
}
public struct SaveInstrumentResponse: Decodable {
public let id: String // stable Payrails instrument id
public let createdAt: String
public let holderId: String
public let paymentMethod: String // e.g. "applePay", "card"
public let status: String
public let data: InstrumentData
public let fingerprint: String?
public let futureUsage: String?
}// Apple Pay — async; `self` conforms to PaymentPresenter
let response = try await session.tokenize(
.applePay(presenter: self),
options: TokenizeOptions(storeInstrument: true)
)
let instrumentId = response.idInstrument management
public struct UpdateInstrumentBody: Codable {
// Fields depend on the update operation (e.g. isDefault)
}
public struct DeleteInstrumentResponse: Codable {
public let success: Bool
}
public struct UpdateInstrumentResponse: Codable {
// Returned data depends on the operation
}Call the typed Session methods to manage instruments:
let delete = try await session.deleteInstrument(instrumentId: id)
let update = try await session.updateInstrument(
instrumentId: id,
body: UpdateInstrumentBody(default: true)
)Card form configuration
See Styling Guide for full details.
public struct CardFormConfig {
public init(
showNameField: Bool = false,
showSaveInstrument: Bool = false,
showCardIcon: Bool = false,
showRequiredAsterisk: Bool = true,
cardIconAlignment: CardIconAlignment = .left,
fieldVariant: FieldVariant = .outlined,
layout: CardLayoutConfig? = nil,
styles: CardFormStylesConfig? = nil,
translations: CardTranslations? = nil
)
}
public enum FieldVariant {
case outlined
case filled
}
public enum CardIconAlignment {
case left
case right
}Debug
public extension Payrails {
struct Debug {
// Returns a SwiftUI view displaying the parsed SDK config and logs
public static func configViewer() -> some View
}
}
// Logs a message to both the Xcode console and the on-screen LogStore
public static func log(_ items: Any..., separator: String, terminator: String)Updated 12 days ago