Skip to content

Fix FlowController bypassing mandate display when setupFutureUsage is added#12973

Merged
cttsai-stripe merged 6 commits into
masterfrom
cttsai/fix-card-mandate-bypass-on-sfu-update
Apr 28, 2026
Merged

Fix FlowController bypassing mandate display when setupFutureUsage is added#12973
cttsai-stripe merged 6 commits into
masterfrom
cttsai/fix-card-mandate-bypass-on-sfu-update

Conversation

@cttsai-stripe

@cttsai-stripe cttsai-stripe commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a bug where PaymentSheet.FlowController allows confirm() to proceed without showing legally required mandate/terms text when setupFutureUsage is dynamically added via configureWithIntentConfiguration().

Jira: RUN_MOBILESDK-5344

Motivation

When a merchant calls FlowController.configureWithIntentConfiguration() to add setupFutureUsage = offSession after the user has already entered card details, the PaymentSelectionUpdater preserved the card selection — allowing confirm() to proceed without the user ever seeing the mandate text that the SDK itself would display in the card form.

iOS correctly clears paymentOption in this scenario, forcing the user back through the payment sheet. Android did not.

Changes

  • PaymentSelectionUpdater.kt: Added needsMandateDisplayForCard() to shouldAskForMandate(). Invalidates a New.Card selection when:

    1. The new metadata would show mandate text (hasIntentToSetup && mandateAllowed, or forceSetupFutureUseBehaviorAndNewMandate)
    2. The card was entered without setupFutureUsage (user never saw mandate)
    3. Respects termsDisplay = NEVER — merchant handles mandate externally
  • PaymentSelectionUpdaterTest.kt: Added 3 unit tests:

    • Card invalidated when SFU dynamically added (the bug)
    • Card preserved when SFU was already set at entry time
    • Card preserved when termsDisplay = NEVER
  • DefaultFlowControllerTest.kt: Added 1 integration test using configureWithIntentConfiguration to simulate the full merchant reconfiguration flow with DefaultPaymentSelectionUpdater.

  • FakePaymentElementLoader.kt: Added updateStripeIntent() for changing the intent between configure calls in tests.

Testing

  • Unit tests (PaymentSelectionUpdaterTest): 3 new tests for the core logic
  • Integration test (DefaultFlowControllerTest): 1 new test for the full FlowController flow
  • TDD: tests written first, confirmed RED against unfixed code, then GREEN after fix

Impact

Merchants using deferred intents who dynamically add setupFutureUsage will now have their card selection cleared, requiring the user to re-open the payment sheet and see the mandate text before confirming. Merchants using termsDisplay = .never (who display mandate externally) are unaffected.

Changelog

  • [FIXED]12973 Fixed an issue where FlowController would bypass mandate display when setupFutureUsage was added via configureWithIntentConfiguration() after the user had already entered card details.

cttsai-stripe and others added 4 commits April 27, 2026 15:29
… added

When a merchant calls configureWithIntentConfiguration() to add
setupFutureUsage after the user has already entered card details,
the PaymentSelectionUpdater preserved the card selection — allowing
confirm() to proceed without the user ever seeing the mandate text.

This adds a check in shouldAskForMandate() that invalidates the card
selection when the new metadata would show mandate text but the card
was entered without setupFutureUsage awareness. Respects
termsDisplay=NEVER for merchants who display mandate externally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
…rim KDoc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Matches the actual merchant API (deferred intent flow) instead of
configureWithPaymentIntent + loader mutation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
@cttsai-stripe cttsai-stripe marked this pull request as ready for review April 28, 2026 17:48
@cttsai-stripe cttsai-stripe requested review from a team as code owners April 28, 2026 17:48
cttsai-stripe and others added 2 commits April 28, 2026 10:50
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
…date-bypass-on-sfu-update

Committed-By-Agent: claude

# Conflicts:
#	CHANGELOG.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Embedded have a similar issue?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Embedded does not have this issue. DefaultEmbeddedSelectionChooser.hasCompatibleForm() compares form element counts between the previous and new metadata. When SFU is added to a card, CardDefinition.createFormElements() adds a MandateTextElement, making newFormElements.size > previousFormElements.size, so hasCompatibleForm returns false and the selection is invalidated.

It is a less explicit check than what this PR adds to PaymentSelectionUpdater (form element count comparison vs. direct mandate check), but it covers this case.

@cttsai-stripe cttsai-stripe merged commit 338b3b4 into master Apr 28, 2026
19 checks passed
@cttsai-stripe cttsai-stripe deleted the cttsai/fix-card-mandate-bypass-on-sfu-update branch April 28, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants