Troubleshooting
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:
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.zipagainst 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.resolvedpins 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
PayrailsCSEis linked statically into the framework binary, so this is not a missing-code problem. The generated.swiftinterfacecarriesimport 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-csepackage appears in the project's resolved packages. If it is missing, resolve thePayrailspackage again. Its manifest declares bothios-cseandpaypalcheckout-ios. - The CSE code is therefore present twice: once absorbed into
Payrails.framework, once as the separately resolvedios-csepackage. Inside the framework that is 176 exportedPayrailsCSEsymbols, plus 626 from JOSESwift, which arrives transitively throughios-cse. That matters when you measure binary size or integratePayrailsCSEdirectly.PayPalCheckoutis 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
.swiftinterfaceformat 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
datafield is not valid base64-encoded JSON - Ensure your backend passes the exact string returned by the Payrails
POST /checkout/initializeendpoint without modification
"SDK has not been properly initialized"
Payrails.createCardForm()or another factory was called beforePayrails.createSession()completed- These calls trigger a
preconditionfailure if called without an active session — alwaysawaitsession creation first
Session init works in debug but fails in release
- Check that your backend call succeeds in the production environment
- Verify that the
envoption matches your backend environment (.productionvs.test)
Card form issues
Card form appears but cannot submit — payment button stays unresponsive
- Ensure
Payrails.createCardForm()is called beforePayrails.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: trueinCardFormConfig. - 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
CardLayoutConfigis 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.isApplePayAvailableis a device-capability check only (returnsfalsewhenPKPaymentAuthorizationController.canMakePayments()is false). It does not inspect the merchant config.- For a combined "configured and device capable" check, compose:
let canShow = session.isApplePayAvailable && !session.getPaymentMethodConfig(.specific("apple_pay")).isEmpty - The
PKPaymentButtonhides itself whenPKPaymentAuthorizationViewController.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
PayPalCheckoutSDK 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 withfailure.code == .userCancelled. In parallel, the SDK invokes theonSessionExpiredclosure supplied atcreateSessiontime to refresh the underlying execution; the merchant's cachedSessionreference keeps working.
Payment issues
Payment failed — how do I know why?
onAuthorizeFailed(_ button:, failure:)carries anAuthorizationFailurestruct. Switch onfailure.codeto give each case the right UX, and readfailure.message/failure.rawErrorfor detail:.userCancelled— user dismissed the 3DS sheet. Show neutral copy, no error banner..authorizationError— authorization rejected (issuer decline, 3DS rejected, fraud blocked, etc.).failure.messageis the backend'serrors[0].reason.result, falling back to "Authorization failed" when no detail is provided..authenticationError— session token rejected (HTTP 401 / 403). The SDK also fires itsonSessionExpiredrefresh in the background..unknownError— network, SDK, or other unexpected failure. Inspectfailure.rawErrorfor 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
onSessionExpiredclosure toPayrails.createSession(with:onSessionExpired:)so the SDK can mint a fresh execution in-place when needed; the merchant'sSessionreference and cached buttons keep working unchanged.
3DS challenge never appears / presentPayment(_:) not called
- Confirm that
payButton.presenter = selfis set on theCardPaymentButton - Confirm your view controller conforms to both
PaymentPresenterandPayrailsCardPaymentFormDelegateif 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:
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.zipfor 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: .testto rule out production-only configuration issues - Check the
PayrailsError.errorDescriptionfor the specific failure reason - Open a support ticket with your
executionId(fromPayrails.query(.executionId)) and the full error description
Updated 11 days ago