Skip to content

Fix: inject moto=true for user-key integrations with saved payment method ID 🪿✨ - #2564

Draft
joyceqin-stripe wants to merge 9 commits into
masterfrom
fix/moto-flag-not-set-with-saved-payment-method
Draft

Fix: inject moto=true for user-key integrations with saved payment method ID 🪿✨#2564
joyceqin-stripe wants to merge 9 commits into
masterfrom
fix/moto-flag-not-set-with-saved-payment-method

Conversation

@joyceqin-stripe

@joyceqin-stripe joyceqin-stripe commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Minion run

Summary

When the SDK is initialized with a user key (uk_), moto=true was only injected into card confirmation params when creating a new payment method inline. Confirming with a pre-existing paymentMethodId (saved card) bypassed the injection entirely on both Android and iOS, causing 3DS challenges to surface unexpectedly for MOTO payments.

Motivation

Android: PaymentMethodCreateParamsFactory.createCardStripeIntentParams branches on paymentMethodId != null. In that branch it built PaymentMethodOptionsParams.Card with only a CVC (if present), never setting moto. The Android SDK's maybeForDashboard auto-injection only fires when paymentMethodCreateParams != null, so it is never reached when a pre-existing ID is used. Additionally, the confirmSetupIntent path called ConfirmSetupIntentParams.create(paymentMethodId, clientSecret) — a public overload with no paymentMethodOptions parameter — silently discarding the options that were computed.

iOS: PaymentMethodFactory.createCardPaymentMethodOptions() only set CVC. After createOptions() returns, StripeSdkImpl.confirmSetupIntent applied options only in the inline-create branch; the paymentMethodId branch skipped createOptions entirely.

Changes:

  • PaymentMethodCreateParamsFactory.kt: Accept publishableKey in the constructor. When paymentMethodId != null and the key starts with uk_, set moto = true on PaymentMethodOptionsParams.Card. For the confirmSetupIntent path, use the @RestrictTo primary constructor of ConfirmSetupIntentParams directly so paymentMethodOptions is passed through.
  • StripeSdkModule.kt: Pass publishableKey to all three PaymentMethodCreateParamsFactory instantiations.
  • PaymentMethodFactory.swift: Accept publishableKey in init. In createCardPaymentMethodOptions(), set cardOptions.additionalAPIParameters["moto"] = true when the key starts with uk_.
  • StripeSdkImpl.swift: Pass STPAPIClient.shared.publishableKey to all three PaymentMethodFactory.init calls. In confirmSetupIntent, call factory.createOptions in the paymentMethodId branch, which previously skipped it.

Testing

  • I tested this manually
  • I added automated tests

Documentation

Select one:

  • I have added relevant documentation for my changes.
  • This PR does not result in any developer-facing changes.

…saved payment method ID

When the Stripe SDK is initialized with a user key (uk_), the MOTO flag
was only injected when creating a new card inline. Confirming with a
pre-existing payment method ID skipped this logic, causing unexpected
3DS challenges for MOTO payments made with saved cards.

Android: In PaymentMethodCreateParamsFactory.createCardStripeIntentParams,
detect the uk_ key prefix and set moto=true on PaymentMethodOptionsParams.Card
when a paymentMethodId is provided.

iOS: In PaymentMethodFactory.createCardPaymentMethodOptions, set
moto=true via STPConfirmCardOptions.additionalAPIParameters when the key
starts with uk_. Also apply createOptions in the confirmSetupIntent
paymentMethodId branch, which previously skipped it entirely.

Committed-By-Agent: goose
Covers all four affected paths:
- Android confirmPayment (PaymentIntent) + saved paymentMethodId
- Android confirmSetupIntent (SetupIntent) + saved paymentMethodId
- iOS createCardPaymentMethodOptions with uk_ key (used by both confirmPayment
  and confirmSetupIntent)

Each path is tested for both uk_ (moto=true expected) and pk_ (no moto)
keys, with and without a CVC.

Committed-By-Agent: goose
…remove internal property access in test

STPSetupIntentConfirmParams does not expose a paymentMethodOptions
property, so the moto injection for iOS confirmSetupIntent with a saved
payment method ID is not possible via this API. Revert that part of the
fix and update the changelog to reflect accurate platform/intent
coverage.

Also remove access to ConfirmSetupIntentParams.paymentMethodId in the
Android unit test, which is an internal property not accessible from
outside the stripe-android module.

Committed-By-Agent: goose
ConfirmSetupIntentParams.paymentMethodId is declared as
`@get:JvmSynthetic internal val` in the primary constructor.
Using it as a named argument from outside the module causes a
compile error even though the constructor itself is accessible
(via @SuppressLint("RestrictedApi") for the @RestrictTo lint check).

Switch to create().copy(paymentMethodOptions = ...) instead:
- create(paymentMethodId, clientSecret) is the public factory method
- copy() is @RestrictTo but already covered by the function-level
  @SuppressLint("RestrictedApi")
- paymentMethodOptions in copy() is a regular function parameter,
  not an internal property, so named argument use is unrestricted

Committed-By-Agent: goose
STPSetupIntentConfirmParams does not have a paymentMethodOptions
property. Instead, it conforms to STPFormEncodable and exposes
additionalAPIParameters, which are merged into the API request body.
Use this to inject payment_method_options[card][moto]=true for user-key
integrations.

Committed-By-Agent: goose
…ntent

The moto flag only applies to card payments. Add a paymentMethodType == .card
guard so non-card types (iDEAL, SEPADebit, USBankAccount, etc.) do not
receive spurious card payment method options in the API request.

Committed-By-Agent: goose
…set paymentMethodOptions

The copy() function on ConfirmSetupIntentParams does not include
paymentMethodOptions as a named parameter, causing a compile error.
Use the @RestrictTo primary constructor directly instead.

Committed-By-Agent: goose
…ParamMap()

PaymentMethodOptionsParams.toParamMap() returns a nested map keyed
by payment method type code, e.g. {"card": {"moto": true, "cvc": "123"}}.
The assertions were incorrectly reading top-level keys; fix them to
read from the nested card params map.

Committed-By-Agent: goose
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.

1 participant