Skip to content

Commit 073d14f

Browse files
committed
fix: return sealed CollectBankAccountResult for bank account collection
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.
1 parent 3d908f0 commit 073d14f

8 files changed

Lines changed: 387 additions & 18 deletions

File tree

example/lib/screens/regional_payment_methods/us_bank_account_direct_debit_screen.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,15 @@ class _UsBankAccountDirectDebitScreenState
134134
),
135135
);
136136

137-
if (result.status == PaymentIntentsStatus.RequiresConfirmation) {
137+
final paymentIntent = switch (result) {
138+
CollectBankAccountPaymentIntentResult(:final paymentIntent) =>
139+
paymentIntent,
140+
CollectBankAccountSetupIntentResult() => null,
141+
};
142+
if (paymentIntent?.status == PaymentIntentsStatus.RequiresConfirmation) {
138143
setState(() {
139144
canConfirm = true;
140-
clientSecretForConfirm = result.clientSecret;
145+
clientSecretForConfirm = paymentIntent!.clientSecret;
141146
});
142147
if (context.mounted) {
143148
scaffoldMessenger.showSnackBar(

packages/stripe/lib/src/stripe.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,10 @@ class Stripe {
632632

633633
/// Collect the bankaccount details for the payment intent.
634634
///
635-
/// Only US bank accounts are supported.
636-
Future<PaymentIntent> collectBankAccount({
635+
/// Only US bank accounts are supported. The returned
636+
/// [CollectBankAccountResult] is a sealed union containing either a
637+
/// [PaymentIntent] or a [SetupIntent] depending on [isPaymentIntent].
638+
Future<CollectBankAccountResult> collectBankAccount({
637639
/// Whether the clientsecret is associated with setup or paymentintent
638640
required bool isPaymentIntent,
639641

@@ -655,8 +657,10 @@ class Stripe {
655657
/// Verify the bank account with microtransactions
656658
///
657659
/// Only US bank accounts are supported.This method is only implemented for
658-
/// iOS at the moment.
659-
Future<PaymentIntent> verifyPaymentIntentWithMicrodeposits({
660+
/// iOS at the moment. The returned [CollectBankAccountResult] is a sealed
661+
/// union containing either a [PaymentIntent] or a [SetupIntent] depending on
662+
/// [isPaymentIntent].
663+
Future<CollectBankAccountResult> verifyPaymentIntentWithMicrodeposits({
660664
/// Whether the clientsecret is associated with setup or paymentintent
661665
required bool isPaymentIntent,
662666

packages/stripe_platform_interface/lib/src/method_channel_stripe.dart

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'package:stripe_platform_interface/src/result_parser.dart';
1515

1616
import 'models/app_info.dart';
1717
import 'models/card_details.dart';
18+
import 'models/collect_bank_account_result.dart';
1819
import 'models/errors.dart';
1920
import 'models/payment_intents.dart';
2021
import 'models/payment_methods.dart';
@@ -569,7 +570,7 @@ class MethodChannelStripe extends StripePlatform {
569570
}
570571

571572
@override
572-
Future<PaymentIntent> collectBankAccount({
573+
Future<CollectBankAccountResult> collectBankAccount({
573574
required bool isPaymentIntent,
574575
required String clientSecret,
575576
required CollectBankAccountParams params,
@@ -583,13 +584,11 @@ class MethodChannelStripe extends StripePlatform {
583584

584585
_financialConnectionsEventHandler = params.onEvent;
585586

586-
return ResultParser<PaymentIntent>(
587-
parseJson: (json) => PaymentIntent.fromJson(json),
588-
).parse(result: result!, successResultKey: 'paymentIntent');
587+
return CollectBankAccountResult.fromJson(result!);
589588
}
590589

591590
@override
592-
Future<PaymentIntent> verifyPaymentIntentWithMicrodeposits({
591+
Future<CollectBankAccountResult> verifyPaymentIntentWithMicrodeposits({
593592
required bool isPaymentIntent,
594593
required String clientSecret,
595594
required VerifyMicroDepositsParams params,
@@ -601,9 +600,7 @@ class MethodChannelStripe extends StripePlatform {
601600
'clientSecret': clientSecret,
602601
});
603602

604-
return ResultParser<PaymentIntent>(
605-
parseJson: (json) => PaymentIntent.fromJson(json),
606-
).parse(result: result!, successResultKey: 'paymentIntent');
603+
return CollectBankAccountResult.fromJson(result!);
607604
}
608605

609606
@override
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
3+
import 'errors.dart';
4+
import 'payment_intents.dart';
5+
import 'setup_intent.dart';
6+
7+
part 'collect_bank_account_result.freezed.dart';
8+
9+
/// Result of `collectBankAccount` / `verifyPaymentIntentWithMicrodeposits`.
10+
///
11+
/// Native SDKs return either a payment intent or a setup intent depending on
12+
/// the client secret that was supplied, so the result is modelled as a sealed
13+
/// union to keep both flows strongly typed.
14+
@freezed
15+
sealed class CollectBankAccountResult with _$CollectBankAccountResult {
16+
const factory CollectBankAccountResult.paymentIntent(
17+
PaymentIntent paymentIntent,
18+
) = CollectBankAccountPaymentIntentResult;
19+
20+
const factory CollectBankAccountResult.setupIntent(SetupIntent setupIntent) =
21+
CollectBankAccountSetupIntentResult;
22+
23+
/// Parses a native method-channel response into the matching variant, or
24+
/// throws [StripeException] when neither key is present.
25+
factory CollectBankAccountResult.fromJson(Map<String, dynamic> json) {
26+
final payment = json['paymentIntent'];
27+
if (payment is Map<String, dynamic>) {
28+
return CollectBankAccountResult.paymentIntent(
29+
PaymentIntent.fromJson(payment),
30+
);
31+
}
32+
final setup = json['setupIntent'];
33+
if (setup is Map<String, dynamic>) {
34+
return CollectBankAccountResult.setupIntent(SetupIntent.fromJson(setup));
35+
}
36+
throw StripeException.fromJson(json);
37+
}
38+
}

0 commit comments

Comments
 (0)