> ## 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.

# Troubleshooting 

> Common issues and how to fix them.

## Enabling debug logs

The SDK provides two mechanisms for debugging.

### On-screen log overlay (SwiftUI)

Add the debug config viewer to any SwiftUI view:

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

struct DebugView: View {
    var body: some View {
        Payrails.Debug.configViewer()
    }
}
```

This renders the parsed SDK configuration and recent log entries on screen. Requires an active session.

### Console logging

The SDK writes to `LogStore.shared` and also calls `Swift.print`. To see logs in the Xcode console, ensure the scheme is not suppressing standard output.

***

## Installation issues

**Package resolution fails**

* Confirm the package URL is exactly `https://github.com/payrails/ios-sdk.git`.
* Click **File → Packages → Reset Package Caches** in Xcode, then resolve again.

**"the checksum of the downloaded artifact does not match"**

* Swift Package Manager verifies the downloaded `Payrails.xcframework.zip` against the checksum in the package manifest, and the two disagree.
* Almost always a stale cache. Click **File → Packages → Reset Package Caches**, clear the Swift Package Manager cache at `~/Library/Caches/org.swift.swiftpm`, then resolve again.
* Payrails never re-uploads a published zip, so a mismatch that survives a cleared cache is worth a support ticket with the SDK version.

**`Package.resolved` still pins a source revision after upgrading to 3.0.0**

* 3.0.0 replaces the source target with a binary target, and `Package.resolved` pins the revision that was resolved before the upgrade.
* Click **File → Packages → Reset Package Caches**, then **File → Packages → Update to Latest Package Versions**. Resolving again rewrites the pin.

**Linker error: PayrailsCSE not found**

* `PayrailsCSE` is linked statically into the framework binary, so this is not a missing-code problem. The generated `.swiftinterface` carries `import PayrailsCSE`, so the Swift compiler needs that module present to build against the binary. That holds even though no public Payrails API exposes a CSE type.
* Confirm the `ios-cse` package appears in the project's resolved packages. If it is missing, resolve the `Payrails` package again. Its manifest declares both `ios-cse` and `paypalcheckout-ios`.
* The CSE code is therefore present twice: once absorbed into `Payrails.framework`, once as the separately resolved `ios-cse` package. Inside the framework that is 176 exported `PayrailsCSE` symbols, plus 626 from JOSESwift, which arrives transitively through `ios-cse`. That matters when you measure binary size or integrate `PayrailsCSE` directly. `PayPalCheckout` is the opposite case: it ships as a prebuilt binary XCFramework, so it stays a genuine external dynamic dependency.

**Compiler cannot load the framework's module interface**

* Each release is built with Xcode 16.4. Swift's `.swiftinterface` format is not backward compatible across toolchains, so an older Xcode cannot read the module. Xcode 16.4 is the minimum, not a recommendation.
* Upgrade Xcode, or open a support ticket with the Xcode version.

***

## SDK initialization issues

**"Provided configuration data is invalid and cannot be parsed"**

* The init payload `data` field is not valid base64-encoded JSON
* Ensure your backend passes the exact string returned by the Payrails `POST /checkout/initialize` endpoint without modification

**"SDK has not been properly initialized"**

* `Payrails.createCardForm()` or another factory was called before `Payrails.createSession()` completed
* These calls trigger a `precondition` failure if called without an active session — always `await` session creation first

**Session init works in debug but fails in release**

* Check that your backend call succeeds in the production environment
* Verify that the `env` option matches your backend environment (`.production` vs `.test`)

***

## Card form issues

**Card form appears but cannot submit — payment button stays unresponsive**

* Ensure `Payrails.createCardForm()` is called before `Payrails.createCardPaymentButton()`
* The button holds a strong reference to the form at creation time; order matters

**Validation errors not shown**

* Check that the card form has enough vertical space — error labels require height to render
* Use `CardFormConfig(showRequiredAsterisk: true)` to make required fields explicit

**Card icon not appearing**

* Pass `showCardIcon: true` in `CardFormConfig`.
* Icons load from the asset catalog inside the framework bundle, which the package resolves automatically. If icons are missing after a clean resolve, open a support ticket with the SDK version.

**Layout falls back to default unexpectedly (console logs "falling back to default layout")**

* Your custom `CardLayoutConfig` is missing required fields (`.CARD_NUMBER`, `.CVV`, and at least one expiry field)
* All three are required for a valid submission

***

## Apple Pay issues

**Apple Pay button is not visible**

* `session.isApplePayAvailable` is a **device-capability check only** (returns `false` when `PKPaymentAuthorizationController.canMakePayments()` is false). It does not inspect the merchant config.
* For a combined "configured and device capable" check, compose:
  ```swift theme={null}
  let canShow = session.isApplePayAvailable
      && !session.getPaymentMethodConfig(.specific("apple_pay")).isEmpty
  ```
* The `PKPaymentButton` hides itself when `PKPaymentAuthorizationViewController.canMakePayments()` returns false.

**Apple Pay sheet dismisses immediately**

* The merchant identifier in your app's entitlements does not match the one in the Payrails merchant configuration
* Verify the capability is enabled under **Signing & Capabilities** in Xcode

**"incorrectPaymentSetup" error for Apple Pay**

* The init payload does not include an Apple Pay payment option configuration
* Contact your Payrails integration engineer to enable Apple Pay in your merchant account

***

## PayPal issues

**PayPal button tap does nothing**

* Ensure the `PayPalCheckout` SDK is properly linked (check the build phases)
* The PayPal SDK requires a client ID in the init payload config; verify the payload includes it

**PayPal checkout WebView dismissed with no result**

* The user cancelled — `onAuthorizeFailed(_:failure:)` fires with `failure.code == .userCancelled`. In parallel, the SDK invokes the `onSessionExpired` closure supplied at `createSession` time to refresh the underlying execution; the merchant's cached `Session` reference keeps working.

***

## Payment issues

**Payment failed — how do I know why?**

* `onAuthorizeFailed(_ button:, failure:)` carries an `AuthorizationFailure` struct. Switch on `failure.code` to give each case the right UX, and read `failure.message` / `failure.rawError` for detail:
  * `.userCancelled` — user dismissed the 3DS sheet. Show neutral copy, no error banner.
  * `.authorizationError` — authorization rejected (issuer decline, 3DS rejected, fraud blocked, etc.). `failure.message` is the backend's `errors[0].reason.result`, falling back to "Authorization failed" when no detail is provided.
  * `.authenticationError` — session token rejected (HTTP 401 / 403). The SDK also fires its `onSessionExpired` refresh in the background.
  * `.unknownError` — network, SDK, or other unexpected failure. Inspect `failure.rawError` for the underlying error.

**My retry on the same card doesn't work after a failure**

* Payrails executions are single-use. Once a payment terminates (success or failure), the same execution cannot be retried. Supply the `onSessionExpired` closure to `Payrails.createSession(with:onSessionExpired:)` so the SDK can mint a fresh execution in-place when needed; the merchant's `Session` reference and cached buttons keep working unchanged.

**3DS challenge never appears / `presentPayment(_:)` not called**

* Confirm that `payButton.presenter = self` is set on the `CardPaymentButton`
* Confirm your view controller conforms to both `PaymentPresenter` and `PayrailsCardPaymentFormDelegate` if needed

**Long-polling timeout errors**

* `PayrailsError.finalStatusNotFoundAfterLongPoll` — the payment status was not confirmed within the polling window
* This is typically a transient network or backend issue; prompt the user to check their payment status through your order history

***

## Pre-authorization gate issues

### Payments are blocked and no request reaches the backend

The `onRequestStart` handler supplied at `createSession` answered `false`, or did not answer at all.
Check `failure.code == .validationFailed` in `onAuthorizeFailed(_:failure:)` to confirm — a block is
reported with that code and never as `.authorizationError`.

The most common cause is a handler that only answers on the branch it cares about. The gate fires
for **every** payment method on the session, so any branch that does not call `completion(.proceed)`
blocks that method:

```swift theme={null}
onRequestStart: { context, completion in
    guard context.paymentMethodCode == "payPal" else {
        completion(.proceed)   // ← omitting this blocks card, wallets, everything else
        return
    }
    myBackend.validate { completion($0) }
}
```

### A payment is blocked roughly ten seconds after tapping

The handler never called its completion. The SDK stops the attempt rather than leaving the element
spinning, and logs:

```
⚠️ onRequestStart did not answer within 10s for <method>; the payment was blocked.
```

Enable debug logs (see above) to see it. Check every path through the handler — including error and
early-return branches — calls `completion` exactly once.

### Only PayPal or Apple Pay report a block without a reason

Those two delegates still expose a deprecated `onAuthorizeFailed(_ button:)` with no payload, and a
default implementation forwards to it for backwards compatibility. Implement
`onAuthorizeFailed(_ button:, failure:)` instead — it is the only variant carrying the code that
distinguishes a gate block from an issuer decline.

***

## Stored instruments issues

**`Payrails.getStoredInstruments()` returns an empty array**

* The init payload does not include any stored instruments for this holder reference
* Instruments with status other than `"enabled"` or `"created"` are filtered out

**StoredInstruments view renders nothing**

* Same as above — the list silently renders nothing when there are no eligible instruments
* Optionally check the count before adding the view: `Payrails.getStoredInstruments().isEmpty`

***

## Crash report issues

**Payrails frames arrive as raw addresses**

* The SDK ships as a binary, so a crash reporter needs the SDK's debug symbols to name those frames.
* Payrails builds `Payrails.dSYMs.zip` for every release. Request the file for the version you shipped through a support ticket, then upload it to your crash reporter alongside your app's own dSYMs.
* Symbols only resolve for the SDK version in the build. dSYMs from another version do not match.

***

## Getting help

1. Enable debug logs and capture the output
2. Reproduce the issue with `env: .test` to rule out production-only configuration issues
3. Check the `PayrailsError.errorDescription` for the specific failure reason
4. Open a support ticket with your `executionId` (from `Payrails.query(.executionId)`) and the full error description


## Related topics

- [Troubleshooting](/docs/orchestration/checkout-sdks/android/troubleshooting.md)
- [How to Accept PayPal Payments](/docs/orchestration/checkout-sdks/android/how-to-accept-paypal-payments.md)
- [Single Sign-On](/docs/account-setup/user-management/single-sign-on.md)
