Skip to content

fix!: return sealed CollectBankAccountResult from collectBankAccount / verifyMicrodeposits#2395

Merged
remonh87 merged 1 commit into
flutter-stripe:mainfrom
BiAksoy:fix/setup-intent-response-key
Apr 24, 2026
Merged

fix!: return sealed CollectBankAccountResult from collectBankAccount / verifyMicrodeposits#2395
remonh87 merged 1 commit into
flutter-stripe:mainfrom
BiAksoy:fix/setup-intent-response-key

Conversation

@BiAksoy

@BiAksoy BiAksoy commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixes collectBankAccount and verifyPaymentIntentWithMicrodeposits when called with isPaymentIntent: false. The native SDKs return a setup-intent-shaped payload that cannot be parsed by PaymentIntent.fromJson (missing amount, currency, captureMethod, confirmationMethod).
  • Introduces a sealed CollectBankAccountResult union with .paymentIntent(PaymentIntent) / .setupIntent(SetupIntent) variants.
  • Both MethodChannelStripe methods now delegate to ResultParser<T> + the generated fromJson for both branches, removing all hand-written field extraction and status mapping.

Breaking change

Stripe.instance.collectBankAccount and Stripe.instance.verifyPaymentIntentWithMicrodeposits now return Future<CollectBankAccountResult> instead of Future<PaymentIntent>.

Callers must now destructure the union:

final result = await Stripe.instance.collectBankAccount(...);

switch (result) {
  case CollectBankAccountPaymentIntentResult(:final paymentIntent):
    // handle payment intent flow
    break;
  case CollectBankAccountSetupIntentResult(:final setupIntent):
    // handle setup intent flow
    break;
}

Note: The setup-intent branch was broken before this change, so no working code depends on the previous shape.


Affected packages

  • stripe_platform_interface
  • stripe
  • stripe_web

Summary by CodeRabbit

  • Breaking Changes
    • The return type of collectBankAccount() and verifyPaymentIntentWithMicrodeposits() has changed from a generic intent to a sealed union result type that explicitly indicates whether a payment or setup intent was obtained. Code calling these methods requires updates to properly handle the distinct result types and their associated data.

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa43801e-ea3c-4f75-87da-e7279a3f0e27

📥 Commits

Reviewing files that changed from the base of the PR and between a8d4c55 and 073d14f.

📒 Files selected for processing (8)
  • example/lib/screens/regional_payment_methods/us_bank_account_direct_debit_screen.dart
  • packages/stripe/lib/src/stripe.dart
  • packages/stripe_platform_interface/lib/src/method_channel_stripe.dart
  • packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.dart
  • packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.freezed.dart
  • packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart
  • packages/stripe_platform_interface/lib/stripe_platform_interface.dart
  • packages/stripe_web/lib/src/web_stripe.dart
✅ Files skipped from review due to trivial changes (2)
  • packages/stripe_platform_interface/lib/stripe_platform_interface.dart
  • packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.freezed.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • example/lib/screens/regional_payment_methods/us_bank_account_direct_debit_screen.dart
  • packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.dart
  • packages/stripe_platform_interface/lib/src/method_channel_stripe.dart

📝 Walkthrough

Walkthrough

The ACH-related methods collectBankAccount and verifyPaymentIntentWithMicrodeposits are refactored to return a new sealed union type CollectBankAccountResult instead of PaymentIntent. This union represents either a payment intent or setup intent outcome. Platform interfaces, method channels, web implementations, and example code are updated accordingly.

Changes

Cohort / File(s) Summary
Model Definition
packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.dart
Introduces CollectBankAccountResult sealed union with paymentIntent and setupIntent variants, including fromJson factory for deserialization.
Generated Union Implementation
packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.freezed.dart
Auto-generated Freezed implementation providing concrete union classes, pattern-matching extensions (map, when, maybeMap, etc.), and copyWith support.
Platform Interface Signatures
packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart, packages/stripe_platform_interface/lib/stripe_platform_interface.dart
Updates StripePlatform method return types from Future<PaymentIntent> to Future<CollectBankAccountResult> and exports new model.
Method Channel Implementation
packages/stripe_platform_interface/lib/src/method_channel_stripe.dart
Changes collectBankAccount and verifyPaymentIntentWithMicrodeposits to deserialize results into CollectBankAccountResult using fromJson instead of ResultParser.
Public API Signatures
packages/stripe/lib/src/stripe.dart
Updates main Stripe class method signatures from Future<PaymentIntent> to Future<CollectBankAccountResult> with updated DartDoc comments.
Web Implementation
packages/stripe_web/lib/src/web_stripe.dart
Aligns WebStripe override signatures from Future<PaymentIntent> to Future<CollectBankAccountResult>.
Example Code
example/lib/screens/regional_payment_methods/us_bank_account_direct_debit_screen.dart
Adds Dart pattern matching to handle both payment-intent and setup-intent variants, gating confirmation logic on the payment-intent path.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • jonasbark
  • remonh87

Poem

🐰 A union seal'd, two paths now clear—
🏦 Both intent and setup appear!
✨ Pattern matching hops through the code,
💳 Bank accounts on a type-safe road!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: introducing a sealed CollectBankAccountResult return type for two ACH-related methods, fixing an API design issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/stripe_platform_interface/lib/src/method_channel_stripe.dart (1)

587-603: Add regression coverage for the setupIntent branch.

Both public methods now rely on the same private key-selection logic, so this bug can come back quietly. Please add tests that exercise collectBankAccount(..., isPaymentIntent: false) and verifyPaymentIntentWithMicrodeposits(..., isPaymentIntent: false) with a native result shaped like { "setupIntent": ... }.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stripe_platform_interface/lib/src/method_channel_stripe.dart` around
lines 587 - 603, Add regression tests that exercise the setupIntent branch by
invoking collectBankAccount (or the public method that calls it) and
verifyPaymentIntentWithMicrodeposits with isPaymentIntent: false and mocking the
platform/method channel to return a map whose top-level key is "setupIntent"
(e.g., {"setupIntent": {...}}). Specifically, in your unit tests for
MethodChannelStripe (or the test file that covers _methodChannel interactions),
mock _methodChannel.invokeMapMethod to return a Map<String,dynamic> containing
"setupIntent" and assert that _parseIntentResult (via the public APIs
collectBankAccount and verifyPaymentIntentWithMicrodeposits) correctly handles
the setupIntent payload instead of expecting paymentIntent. Ensure both public
entry points are covered so the private key-selection logic that looks for
"paymentIntent" vs "setupIntent" is exercised when isPaymentIntent is false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/stripe_platform_interface/lib/src/method_channel_stripe.dart`:
- Around line 622-669: The current branch fabricates a PaymentIntent from
setupIntentJson by filling missing fields with defaults and forcing
CaptureMethod.Automatic; instead parse the native payload using
SetupIntent.fromJson (or validate required fields) and convert it to a
PaymentIntent only if parsing succeeds, otherwise fail fast; replace the manual
construction that uses setupIntentJson and CaptureMethod.Automatic with code
that calls SetupIntent.fromJson(setupIntentJson) (or validates
id/clientSecret/etc.) and map the captureMethod to CaptureMethod.Unknown for a
synthetic wrapper when appropriate, ensuring any missing required values cause
an error/early return rather than silently using ''/0/false.

---

Nitpick comments:
In `@packages/stripe_platform_interface/lib/src/method_channel_stripe.dart`:
- Around line 587-603: Add regression tests that exercise the setupIntent branch
by invoking collectBankAccount (or the public method that calls it) and
verifyPaymentIntentWithMicrodeposits with isPaymentIntent: false and mocking the
platform/method channel to return a map whose top-level key is "setupIntent"
(e.g., {"setupIntent": {...}}). Specifically, in your unit tests for
MethodChannelStripe (or the test file that covers _methodChannel interactions),
mock _methodChannel.invokeMapMethod to return a Map<String,dynamic> containing
"setupIntent" and assert that _parseIntentResult (via the public APIs
collectBankAccount and verifyPaymentIntentWithMicrodeposits) correctly handles
the setupIntent payload instead of expecting paymentIntent. Ensure both public
entry points are covered so the private key-selection logic that looks for
"paymentIntent" vs "setupIntent" is exercised when isPaymentIntent is false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9a0b525-2e61-4427-8e19-8f430fee8dad

📥 Commits

Reviewing files that changed from the base of the PR and between 3d908f0 and d0a96cf.

📒 Files selected for processing (1)
  • packages/stripe_platform_interface/lib/src/method_channel_stripe.dart

Comment thread packages/stripe_platform_interface/lib/src/method_channel_stripe.dart Outdated

@remonh87 remonh87 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I do not think this is correct if the key is different we should handle this but not create our own parsing ourselves


final setupIntentJson = result['setupIntent'] as Map<String, dynamic>?;
if (setupIntentJson != null) {
return PaymentIntent(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I do not like this this we should always rely on automatic parsing of the map through freezed + jsonserializable like we do above.

).parse(result: result, successResultKey: 'paymentIntent');
}

static PaymentIntentsStatus _parseStatus(String value) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this should not be needed when we parse it using freezed + json serializable

@BiAksoy

BiAksoy commented Apr 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, that makes sense.

I added the manual parsing because the native response is setup-intent-shaped and doesn’t fully match PaymentIntent.fromJson, but I agree this isn’t the right layer for it.

I’ll refactor this to rely on the existing freezed + json_serializable parsing and align with the current pattern.

For the setup intent case, I may introduce a sealed result type to keep both flows consistent. Let me know if you’d prefer a different approach.

I can also update the feat/web PR (#2396) in the same way if that makes sense.

@BiAksoy BiAksoy force-pushed the fix/setup-intent-response-key branch from d0a96cf to da389bf Compare April 23, 2026 10:41
@BiAksoy BiAksoy changed the title fix: handle setupIntent response key in collectBankAccount and verifyMicrodeposits fix!: return sealed CollectBankAccountResult from collectBankAccount / verifyMicrodeposits Apr 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/stripe/lib/src/stripe.dart (1)

635-663: ⚠️ Potential issue | 🟠 Major

This is a breaking API change that requires version bumping and CHANGELOG documentation.

The return type change from Future<PaymentIntent> to Future<CollectBankAccountResult> for both collectBankAccount() and verifyPaymentIntentWithMicrodeposits() breaks existing code that expects PaymentIntent. However, the version remains at 12.6.0 and no breaking change entry appears in the CHANGELOG.

Semantic versioning requires a major version bump to 13.0.0 and explicit documentation of this breaking change. Add an entry to both packages/stripe/CHANGELOG.md and packages/stripe_platform_interface/CHANGELOG.md noting the sealed union return type, then bump versions accordingly before release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stripe/lib/src/stripe.dart` around lines 635 - 663, This change
introduces a breaking API: the return type for collectBankAccount and
verifyPaymentIntentWithMicrodeposits changed from Future<PaymentIntent> to
Future<CollectBankAccountResult>, so update package versions and docs to reflect
that breaking change: add clear entries to both CHANGELOG.md files describing
the sealed-union return type and migration note for collectBankAccount and
verifyPaymentIntentWithMicrodeposits, and bump the package versions (pubspecs)
from 12.6.0 to 13.0.0 for both the stripe and stripe_platform_interface packages
before releasing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/stripe/lib/src/stripe.dart`:
- Around line 635-663: This change introduces a breaking API: the return type
for collectBankAccount and verifyPaymentIntentWithMicrodeposits changed from
Future<PaymentIntent> to Future<CollectBankAccountResult>, so update package
versions and docs to reflect that breaking change: add clear entries to both
CHANGELOG.md files describing the sealed-union return type and migration note
for collectBankAccount and verifyPaymentIntentWithMicrodeposits, and bump the
package versions (pubspecs) from 12.6.0 to 13.0.0 for both the stripe and
stripe_platform_interface packages before releasing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5099a4b9-ecaf-47e5-a7c4-cec96b437736

📥 Commits

Reviewing files that changed from the base of the PR and between d0a96cf and da389bf.

📒 Files selected for processing (8)
  • example/lib/screens/regional_payment_methods/us_bank_account_direct_debit_screen.dart
  • packages/stripe/lib/src/stripe.dart
  • packages/stripe_platform_interface/lib/src/method_channel_stripe.dart
  • packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.dart
  • packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.freezed.dart
  • packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart
  • packages/stripe_platform_interface/lib/stripe_platform_interface.dart
  • packages/stripe_web/lib/src/web_stripe.dart
✅ Files skipped from review due to trivial changes (2)
  • packages/stripe_platform_interface/lib/stripe_platform_interface.dart
  • packages/stripe_platform_interface/lib/src/models/collect_bank_account_result.freezed.dart

@BiAksoy BiAksoy force-pushed the fix/setup-intent-response-key branch from 4def7c8 to a8d4c55 Compare April 23, 2026 10:58
@BiAksoy

BiAksoy commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

Pushed the refactor.

@remonh87 remonh87 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Almost there! If you can revert the changelog and versioning we can ship this. You can think about the jsonserialzable instead of the resultparser but this is not required.

@@ -1,3 +1,9 @@
## 13.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you revert this change? We will update the changelogs when we actually release.

Comment thread packages/stripe_web/CHANGELOG.md Outdated
@@ -1,3 +1,10 @@
## 8.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you revert this change? We will update the changelogs when we actually release.

Comment thread packages/stripe/pubspec.yaml Outdated
name: flutter_stripe
description: Flutter library for Stripe. Supports PaymentSheets, Apple & Google Pay, SCA, PSD2 and much more.
version: 12.6.0
version: 13.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

also remove versioning

Comment thread packages/stripe_web/pubspec.yaml Outdated
name: flutter_stripe_web
description: Stripe sdk bindings for the Flutter web platform. This package contains the implementation of the platform interface for web.
version: 7.6.0
version: 8.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

also remove versioning

Native SDKs return either a payment intent or a setup intent depending on
the client secret passed to collectBankAccount /
verifyPaymentIntentWithMicrodeposits, but the platform interface only
typed the result as PaymentIntent. The setup intent branch therefore
failed to parse against PaymentIntent.fromJson, which requires fields
(amount, currency, captureMethod, confirmationMethod) that are not
present on a setup intent payload.

Introduce a sealed CollectBankAccountResult union so both variants can
be parsed by their own freezed + json_serializable generated factories
without any hand-written field extraction or status mapping.
@BiAksoy BiAksoy force-pushed the fix/setup-intent-response-key branch from a8d4c55 to 073d14f Compare April 23, 2026 18:56
@BiAksoy

BiAksoy commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

Pushed.

  • Reverted the changelog and version bump changes.
  • On the parser point: moved the dispatch out of MethodChannelStripe into a CollectBankAccountResult.fromJson factory on the union itself, so the call site is just CollectBankAccountResult.fromJson(result!) and the inner parsing still flows through the json_serializable-generated PaymentIntent.fromJson / SetupIntent.fromJson.

I didn't put @JsonSerializable directly on the union because our native payload uses the key name itself as the discriminator ({"paymentIntent": ...} vs {"setupIntent": ...}) rather than a separate type field, so freezed's auto generated union dispatch doesn't fit cleanly. The manual fromJson is ~10 lines and still delegates all the field work to json_serializable. Happy to restructure if you'd prefer the annotation.

@BiAksoy BiAksoy requested a review from remonh87 April 23, 2026 19:02

@remonh87 remonh87 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice thank you

@remonh87 remonh87 merged commit ad190d2 into flutter-stripe:main Apr 24, 2026
3 of 6 checks passed
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