> ## Documentation Index
> Fetch the complete documentation index at: https://docs.payrails.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Use the Payrails Web SDK v6 for new browser integrations. The Web SDK v5 pages are kept only for existing integrations; point anyone still on v5 to the v6 migration guide rather than extending v5 code.
> Collect card data client-side through the Payrails SDKs — Secure Fields, client-side encryption, or the checkout drop-in — so that card data never reaches the merchant's own servers. Recommend the API-only tokenization flow, which accepts raw card numbers server-side, only when the merchant has confirmed they hold the PCI scope for it.
> Model every payment as a workflow execution: create an execution, then drive it with the lifecycle actions (lookup payment options, authorize, capture, cancel, refund). Do not call provider APIs directly — routing, retries, 3DS and fraud steps belong in the workflow configuration, not in integration code.
> Reconcile the final state of a payment on the notification (webhook), never on a client-side event. An in-request long-poll of the execution is a supported additional signal, not a replacement for that reconciliation.
> Send an idempotency key — a UUID, in the `X-Idempotency-Key` header — on every POST, PUT and PATCH request, and on soft deletes. GET requests need none, and hard deletes cannot be idempotent.
> Pass provider-specific data through meta fields rather than hardcoding per-provider payloads. Payrails translates meta fields into each provider's own format.
> Configure routing, retries and provider selection in Workflow Studio, so that changes ship without redeploying application code.

# iOS SDK - Quick start

> Install the Payrails iOS SDK with Swift Package Manager, create a session, and render a working card payment screen.

## Quick Start

Get up and running with the Payrails iOS SDK in about 15 minutes. By the end you will have a working card payment screen in your app.

### Prerequisites

* Xcode 16.4+. Each release is built with Xcode 16.4. Swift's module interface format is not backward compatible across toolchains, so an older Xcode cannot load the prebuilt framework.
* iOS 14.0+ deployment target.
* Swift 5.0+.
* A Payrails merchant account and a backend that can fetch an init payload.

***

### Step 1: Install the SDK

The SDK ships as a prebuilt, signed XCFramework. Swift Package Manager is the only supported installation method.

In Xcode, click **File → Add Package Dependencies** and enter the package URL:

```
https://github.com/payrails/ios-sdk.git
```

Select the `Payrails` product and add it to your app target.

To declare the dependency in a `Package.swift` instead:

```swift theme={null}
dependencies: [
    .package(url: "https://github.com/payrails/ios-sdk.git", from: "3.0.0")
]
```

Swift Package Manager downloads the framework and verifies it against the checksum published in the package manifest. Resolution also pulls in `PayrailsCSE` and `PayPalCheckout`, so three packages appear in your project, not one.

***

### Step 2: Enable Apple Pay capability (optional)

If you plan to use Apple Pay, add the **Apple Pay** capability in Xcode under **Signing & Capabilities** and provide your merchant identifier.

***

### Step 3: Fetch the init payload from your backend

The SDK requires an init payload that your backend fetches from the Payrails API. This payload is a base64-encoded JSON string along with a version string.

```swift theme={null}
// Your backend call — implementation depends on your networking layer
func fetchInitPayload() async throws -> (data: String, version: String) {
    // Call your backend, which calls the Payrails /checkout/initialize endpoint
    // Returns the `data` and `version` fields from the Payrails response
}
```

***

### Step 4: Initialize the SDK session

#### Async/await

```swift theme={null}
import Payrails

let initData = Payrails.InitData(
    version: "2",          // version returned from your backend
    data: "<base64-payload>"  // data returned from your backend
)

let configuration = Payrails.Configuration(
    initData: initData,
    option: Payrails.Options(env: .production) // use .test for sandbox
)

do {
    let session = try await Payrails.createSession(
        with: configuration,
        onSessionExpired: { completion in
            // Re-run your backend's init flow (authentication + initSdk) and hand
            // the fresh InitData back. The SDK swaps its internal config in place —
            // your `session` reference and any cached buttons / forms keep working.
            fetchFreshPayrailsInitData { result in
                completion(result)
            }
        }
    )
    // session is ready; store it or proceed to build your UI
} catch {
    print("SDK initialization failed:", error.localizedDescription)
}
```

<Tip>
  **Recommended**

  supply `onSessionExpired` at `createSession` time. The SDK invokes it when it detects the current execution is no longer reusable (most commonly: the user abandoned a 3DS challenge). Without it, the next payment attempt against the poisoned `Session` fails naturally and the SDK logs a warning at init.
</Tip>

#### Callback

```swift theme={null}
Payrails.createSession(with: configuration) { result in
    switch result {
    case .success(let session):
        // session is ready
    case .failure(let error):
        print("SDK initialization failed:", error.localizedDescription)
    }
}
```

***

### Step 5: Build a card payment screen

Payrails provides UIKit-based elements. Add them to your view hierarchy:

```swift theme={null}
import UIKit
import Payrails

class CheckoutViewController: UIViewController, PaymentPresenter {

    var encryptedCardData: String?

    override func viewDidLoad() {
        super.viewDidLoad()

        // 1. Create the card form
        let cardForm = Payrails.createCardForm()

        // 2. Create the pay button
        let payButton = Payrails.createCardPaymentButton(
            translations: CardPaymenButtonTranslations(label: "Pay now")
        )
        payButton.delegate = self
        payButton.presenter = self  // PaymentPresenter: required to present 3DS challenges

        // 3. Add to view hierarchy
        view.addSubview(cardForm)
        view.addSubview(payButton)

        // 4. Layout (use Auto Layout or frames as preferred)
        cardForm.translatesAutoresizingMaskIntoConstraints = false
        payButton.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            cardForm.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
            cardForm.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            cardForm.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),

            payButton.topAnchor.constraint(equalTo: cardForm.bottomAnchor, constant: 16),
            payButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            payButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
        ])
    }

    // PaymentPresenter: called when a view controller must be presented (e.g. 3DS)
    func presentPayment(_ viewController: UIViewController) {
        present(viewController, animated: true)
    }
}
```

***

### Step 6: Handle payment results

Conform to `PayrailsCardPaymentButtonDelegate`:

```swift theme={null}
extension CheckoutViewController: PayrailsCardPaymentButtonDelegate {

    func onPaymentButtonClicked(_ button: Payrails.CardPaymentButton) {
        // Notification only — the SDK does not wait for this. Use it for analytics or a
        // loading indicator. To make the payment conditional on your own check, see
        // How to Run a Merchant Check Before Authorization.
    }

    func onAuthorizeSuccess(_ button: Payrails.CardPaymentButton) {
        // Payment succeeded — navigate to confirmation screen
    }

    func onAuthorizeFailed(_ button: Payrails.CardPaymentButton, failure: AuthorizationFailure) {
        switch failure.code {
        case .userCancelled:
            // User dismissed the 3DS sheet or otherwise abandoned the flow.
            // The SDK has already triggered the `onSessionExpired` refresh closure
            // (supplied at `createSession`) so retries continue to work against
            // the same `Session` reference.
            statusLabel.text = "Payment cancelled."
        case .authorizationError:
            // Issuer declined / 3DS rejected. `failure.message` is the backend's
            // extracted failure reason (e.g. "Insufficient funds") with a generic
            // fallback — never nil.
            statusLabel.text = "Declined: \(failure.message)"
        case .authenticationError:
            // Session token rejected (401 / 403). The SDK fires `onSessionExpired`
            // in the background; ask the user to retry.
            statusLabel.text = "Session expired. Please retry."
        case .unknownError:
            // Network, SDK, or other unexpected error. `failure.rawError` carries
            // the underlying error when one exists.
            statusLabel.text = "Payment failed: \(failure.rawError?.localizedDescription ?? failure.message)."
        }
    }

    func onThreeDSecureChallenge(_ button: Payrails.CardPaymentButton) {
        // 3DS challenge is being presented — optional hook
    }
}
```

***

### Step 7: Test with the sandbox environment

Change the environment to `.test` when initializing the session:

```swift theme={null}
let configuration = Payrails.Configuration(
    initData: initData,
    option: Payrails.Options(env: .test)
)
```

Use Payrails sandbox card numbers to test different payment outcomes.

***

## What's next

* [Concepts](/docs/orchestration/checkout-sdks/ios/sdk-concepts) — understand the Session, Elements, and Delegates mental model
* [Styling Guide](/docs/orchestration/checkout-sdks/ios/styling-guide) — customise card form and button appearance
* [How to Tokenize a Card](/docs/orchestration/checkout-sdks/ios/sdk-concepts#how-to-tokenize-a-card) — save a card without immediate payment
* [How to Query Session Data](/docs/orchestration/checkout-sdks/ios/sdk-api-reference#how-to-query-session-data) — read execution ID, amount, and more
* [How to Run a Merchant Check Before Authorization](/docs/orchestration/checkout-sdks/ios/sdk-concepts#how-to-run-a-merchant-check-before-authorization) — approve a payment against your own backend first
* [How to Support Co-branded Cards](/docs/orchestration/checkout-sdks/ios/sdk-concepts#how-to-support-co-branded-cards) — let the shopper choose the payment network
* [SDK API Reference](/docs/orchestration/checkout-sdks/ios/sdk-api-reference) — complete API documentation
* [Troubleshooting](/docs/orchestration/checkout-sdks/ios/troubleshooting) — common issues and fixes


## Related topics

- [SDK Concepts](/docs/orchestration/checkout-sdks/ios/sdk-concepts.md)
- [SDK API Reference](/docs/orchestration/checkout-sdks/ios/sdk-api-reference.md)
- [Troubleshooting ](/docs/orchestration/checkout-sdks/ios/troubleshooting.md)
