Troubleshooting

Common issues and their solutions when integrating the Payrails Android SDK.

Build Issues

"Payrails session must be initialized"

Error: IllegalStateException: Payrails session must be initialized

Cause: You called createCardForm(), createCardPaymentButton(), or createStoredInstruments() before Payrails.createSession().

Fix: Ensure createSession() completes successfully before creating any UI elements:

// Correct order
val session = Payrails.createSession(configuration)  // must complete first
val cardForm = Payrails.createCardForm()              // now safe
val payButton = Payrails.createCardPaymentButton(...)  // now safe

Missing CSE Dependency

Error: Runtime crash or NoClassDefFoundError related to card encryption

Fix: Add both dependencies:

dependencies {
    implementation("com.payrails.checkout:android-sdk:<version>")
    implementation("com.payrails.android:cse:<version>")  // required for card payments
}

Card Form Issues

Card Form Not Validating

Symptom: User can submit without validation, or errors don't show.

Checks:

  • The CardPaymentButton validates the form automatically when clicked. You don't need to call validate() manually.
  • Validation errors appear after the user taps "Pay Now" or when a field loses focus (blur validation).
  • If you're using onChange events, check event.isValid for real-time validation state.

Card Network Not Detected

Symptom: cardNetwork stays UNKNOWN, card icon doesn't update.

Checks:

  • Network detection requires at least 1–2 digits. Enter a full card number to see detection.
  • Supported networks: Visa, Mastercard, Amex, Discover. Other networks show as UNKNOWN.
  • Ensure showCardIcon = true in CardFormConfig if you expect to see icons.

Layout Crashes at Creation

Error: IllegalArgumentException: CardForm layout contains unsupported fields or similar.

Fix: Check your layout configuration:

  • Don't mix EXPIRATION_DATE with EXPIRATION_MONTH/EXPIRATION_YEAR
  • Don't duplicate fields across rows
  • Supported fields: CARDHOLDER_NAME, CARD_NUMBER, EXPIRATION_DATE, EXPIRATION_MONTH, EXPIRATION_YEAR, CVV

Payment Issues

3DS Browser Doesn't Open

Symptom: Payment hangs after authorization, no browser opens.

Checks:

  • Ensure a PaymentPresenter is set on the button: payButton.presenter = presenter
  • If you don't set one, the SDK creates a default browser presenter automatically, but only after Render() is composed
  • The device must have a browser installed (Chrome Custom Tabs preferred)
  • Check that the Activity reference is still valid (not destroyed)

3DS Succeeds But onAuthorizeSuccess Not Called

Symptom: User completes 3DS in browser, returns to app, but delegate isn't called.

Explanation: This is expected behavior. The SDK polls the execution status after the user returns. The delegate callback fires only when polling reaches a terminal state (success or failure).

Possible causes for delay:

  • Slow backend processing — the SDK continues polling
  • Network connectivity issues — polling retries with backoff
  • If polling times out, onAuthorizeFailed fires instead (the SDK triggers session recovery if onSessionExpired is configured)

Payment Always Returns authorizationFailed

Checks:

  1. Verify your init payload is correct (version and data from your backend)
  2. Check that your Payrails dashboard has the payment method configured
  3. Ensure your test card numbers are valid for your sandbox environment
  4. Look at PayrailsError in the onAuthorizationFailed case for specific details

Stored Instrument Payment Fails

Checks:

  • Ensure the stored instrument is still valid (not expired or deleted)
  • Set the presenter on the button: payButton.presenter = presenter
  • Verify the instrument was retrieved from the current session: Payrails.getStoredInstruments()

Lifecycle Issues

Session Lost After Configuration Change

Symptom: Payment fails after screen rotation or configuration change.

Explanation: The SDK ties the session to the Activity lifecycle. When the Activity is destroyed (including for configuration changes), the session is cleaned up if isFinishing is true.

Fix: For configuration changes (rotation), the standard Compose remember pattern preserves element references across recomposition. However, if the Activity is recreated, you'll need to re-initialize the session.

Consider using a ViewModel to hold the session across configuration changes:

class PaymentViewModel : ViewModel() {
    var session: Session? = null
        private set

    suspend fun initializeSession(configuration: Configuration) {
        session = Payrails.createSession(configuration)
    }
}

Memory Leak Warnings

Symptom: LeakCanary reports a leak from Payrails or BrowserPaymentPresenterImpl.

Checks:

  • The SDK holds a WeakReference to the Activity — this should not cause leaks
  • Ensure you're not holding strong references to CardForm or CardPaymentButton in long-lived scopes (e.g., a singleton)
  • The SDK registers ActivityLifecycleCallbacks once and cleans up when the bound Activity is destroyed

ProGuard / R8

If you're using code shrinking, the SDK's public API should work without additional rules. If you encounter issues with serialization or reflection:

-keep class com.payrails.sdk.** { *; }

This is a broad keep rule. In most cases, the SDK works without any ProGuard configuration. Only add rules if you see specific obfuscation-related crashes.

Getting Help

If your issue isn't covered here:

  1. Review the SDK Concepts to verify your integration pattern
  2. Check the API Reference for correct method signatures
  3. Contact Payrails support with:
    • SDK version
    • Android API level
    • The specific PayrailsError message (if applicable)
    • Steps to reproduce