Enabling debug logs
The SDK provides two mechanisms for debugging.
On-screen log overlay (SwiftUI)
Add the debug config viewer to any SwiftUI view:
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 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:
- 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:
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:
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
- Enable debug logs and capture the output
- Reproduce the issue with
env: .test to rule out production-only configuration issues
- Check the
PayrailsError.errorDescription for the specific failure reason
- Open a support ticket with your
executionId (from Payrails.query(.executionId)) and the full error description
Last modified on September 24, 2026