Skip to content

fix(android): forward stripeAccountId and catch errors in retrievePaymentIntent / retrieveSetupIntent#2403

Open
adityapsbisht wants to merge 2 commits into
stripe:masterfrom
adityapsbisht:fix/android-retrieve-intent-connected-account
Open

fix(android): forward stripeAccountId and catch errors in retrievePaymentIntent / retrieveSetupIntent#2403
adityapsbisht wants to merge 2 commits into
stripe:masterfrom
adityapsbisht:fix/android-retrieve-intent-connected-account

Conversation

@adityapsbisht
Copy link
Copy Markdown

Summary

On Android, retrievePaymentIntent and retrieveSetupIntent have two defects that together cause a hard, un-catchable app crash whenever the underlying PaymentIntent/SetupIntent belongs to a connected Stripe account:

  1. stripeAccountId is not forwarded to the native call. Every other public API in StripeSdkModule.kt that touches the Stripe Android SDK passes stripeAccountId (createCardTokenSynchronous, createPiiTokenSynchronous, createBankAccountTokenSynchronous, every PaymentLauncherFragment.forXxx, and the module's own internal Google Pay path). But retrievePaymentIntent / retrieveSetupIntent were calling the 1-arg overloads:

    stripe.retrievePaymentIntentSynchronous(clientSecret)   // drops stripeAccountId
    stripe.retrieveSetupIntentSynchronous(clientSecret)     // drops stripeAccountId

    So the request hits the platform account rather than the connected account, and Stripe responds with InvalidRequestException: No such payment_intent: 'pi_…'.

  2. The coroutine body is unguarded. The network call runs inside CoroutineScope(Dispatchers.IO).launch { … } with no try/catch / runCatching, so InvalidRequestException escapes the coroutine and becomes an uncaught exception. The app crashes before the Promise is ever rejected, so a JS-side try/catch around retrievePaymentIntent cannot help.

The matching iOS implementation already forwards stripeAccountId and resolves errors via the Promise — this PR brings Android behavior in line.

Reproduction

  1. Stripe account configured with connected/custom accounts.
  2. Create a PaymentIntent on a connected account, hand the client its client_secret.
  3. On Android, await retrievePaymentIntent(clientSecret) (even with a JS try/catch).
  4. App crashes:
    com.stripe.android.core.exception.InvalidRequestException: No such payment_intent: 'pi_…'
      at com.stripe.android.networking.StripeApiRepository.handleApiError(StripeApiRepository.kt:1705)
      at com.stripe.android.Stripe.retrievePaymentIntentSynchronous(Stripe.kt:441)
      at com.reactnativestripesdk.StripeSdkModule$retrievePaymentIntent$1.invokeSuspend(StripeSdkModule.kt:645)
    

iOS with the same client secret works correctly.

Fix

Two targeted changes to android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt:

  • Pass stripeAccountId to retrievePaymentIntentSynchronous / retrieveSetupIntentSynchronous (the 2-arg overloads already exist on Stripe).
  • Wrap the coroutine body in runCatching { … }.onFailure { … } and resolve the promise with a structured error via createError(RetrievePaymentIntentErrorType.Unknown, …) / createError(RetrieveSetupIntentErrorType.Unknown, …) — both enums already exist in utils/Errors.kt.

Net diff: 20 insertions, 4 deletions, one file.

Impact

  • Resolves an app-terminating crash for any app using connected accounts that calls retrievePaymentIntent/retrieveSetupIntent on Android (PaymentSheet flows that need to re-read intent status post-confirmation, amount display after deep-linking back from SCA, etc.).
  • JS consumers now receive a structured rejection that can be handled identically to iOS.

Verified

  • Bug is present from at least v0.50.3 through v0.64.0 (current master) — the 1-arg overload has been used since the original implementation and was never updated alongside other native calls that adopted stripeAccountId.
  • Change is Kotlin-only, no TS/JS surface changes, no behavioral difference for non-connected-account use (since stripeAccountId is nullable and the 2-arg overload accepts null).

Related

Happy to add an e2e test if there's a reasonable way to set one up against example-stripe-connect/ — pointers welcome.

Made with Cursor

…mentIntent / retrieveSetupIntent

Both @ReactMethod entry points were calling the 1-arg overload of
Stripe#retrievePaymentIntentSynchronous / retrieveSetupIntentSynchronous,
dropping the configured stripeAccountId. For connected-account intents
this hits the platform account and the Stripe Android SDK throws
InvalidRequestException ("No such payment_intent: 'pi_…'").

The call also ran inside CoroutineScope(Dispatchers.IO).launch { … }
with no try/catch, so the exception escaped the coroutine as an
uncaught error and crashed the app before the JS Promise could reject.
The matching iOS implementation already forwards stripeAccountId and
resolves errors via the Promise.

Wrap both coroutine bodies in runCatching, pass stripeAccountId to the
synchronous SDK calls, and resolve a structured error via
createError(RetrievePaymentIntentErrorType.Unknown, …) /
createError(RetrieveSetupIntentErrorType.Unknown, …) so the JS caller
can handle it the same way as on iOS.

Related: stripe#322 (closed without fix), likely stripe#1800.
Made-with: Cursor
@adityapsbisht adityapsbisht requested review from a team as code owners April 20, 2026 10:11
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented Apr 20, 2026

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants