flutter-stripe / flutter_stripe

Flutter SDK for Stripe.
https://pub.dev/packages/flutter_stripe
955 stars 526 forks source link

You cannot confirm this Paymentlntent because it's missing a payment method #1980

Open itsamit108 opened 2 weeks ago

itsamit108 commented 2 weeks ago

Describe the Bug
I am implementing Stripe ACH with instant verification in a Flutter app but encounter an error during payment confirmation. I followed the official Flutter Stripe documentation: Flutter Stripe Financial Connections.

Issue Details
After collecting and verifying the bank account through Stripe ACH with instant verification (as per the documentation), I receive this error when attempting to confirm the payment:

"You cannot confirm this PaymentIntent because it's missing a payment method. You can either update the PaymentIntent with a payment method and then confirm it again, or confirm it again directly with a payment method or ConfirmationToken."

image

Key Question

How should the payment method be provided or linked in the following code?

final paymentIntent = await Stripe.instance.confirmPayment(
  paymentIntentClientSecret: paymentIntentData['paymentIntent'],
);

This is a business-critical requirement.

For reference, I also reviewed the related issue: GitHub Issue #1348.

Critical Code Snippets

Payment Handling Code

Future<void> handlePayment() async {
  if (_amountController.text.isEmpty) {
    setState(() { _errorMessage = 'Please enter an amount'; });
    return;
  }

  setState(() => _errorMessage = null);
  ref.read(paymentLoadingProvider.notifier).state = true;

  // Step 1: Create Financial Connection Session
  final sessionData = await PaymentService.createFinancialConnectionSession();

  // Step 2: Collect Financial Connection Account
  final result = await Stripe.instance.collectFinancialConnectionsAccounts(
    clientSecret: sessionData['clientSecret'],
  );

  _selectedAccountId = result.session.accounts.first.id;

  // Step 3: Create Payment Intent
  final paymentIntentData = await PaymentService.createPaymentIntent(
    amount: double.parse(_amountController.text),
  );

  // Step 4: Confirm Payment
  final paymentIntent = await Stripe.instance.confirmPayment(
    paymentIntentClientSecret: paymentIntentData['paymentIntent'],
  );

  if (mounted) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Payment processed successfully!'),
        backgroundColor: Colors.green,
      ),
    );
    setState(() {
      _amountController.clear();
      _selectedAccountId = null;
    });
  }

  ref.read(paymentLoadingProvider.notifier).state = false;
}

Payment Service Class

class PaymentService {
  static Future<Map<String, dynamic>> createPaymentIntent({
    required double amount,
    String staffingFirmId = '',
  }) async {
    final amountInCents = (amount * 100).round();
    final response = await http.post(
      Uri.parse("${dotenv.get("baseurl")}/api/payment/create-payment-intent"),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': "Bearer ${IdSharedPreferences.getJWTHead()}",
      },
      body: json.encode({
        'amount': amountInCents,
        'currency': 'usd',
        'description': 'Payment of \$$amount',
        'staffingFirmId': staffingFirmId,
      }),
    ).timeout(const Duration(seconds: 10), onTimeout: () {
      throw 'Connection timeout. Please check if the server is running.';
    });

    if (response.statusCode == 200) {
      final responseData = json.decode(response.body);
      return { 'paymentIntent': responseData['client_secret'] };
    } else {
      throw 'Server returned status code: ${response.statusCode}\nResponse: ${response.body}';
    }
  }

  static Future<Map<String, dynamic>> createFinancialConnectionSession() async {
    final response = await http.post(
      Uri.parse("${dotenv.get("baseurl")}/api/payment/create-financial-connection-session"),
    ).timeout(const Duration(seconds: 10), onTimeout: () {
      throw 'Connection timeout. Please check if the server is running.';
    });

    if (response.statusCode == 200) {
      final responseData = json.decode(response.body);
      return { 'clientSecret': responseData['clientSecret'] };
    } else {
      throw 'Server returned status code: ${response.statusCode}\nResponse: ${response.body}';
    }
  }
}

Stripe Integration - Payment Intent Creation

async createPaymentIntent(amount, currency = 'usd', description = '') {
  const paymentIntent = await paymentIntents.create({
    amount,
    currency,
    description,
    automatic_payment_methods: { enabled: true },
    payment_method_options: {
      us_bank_account: {
        verification_method: 'automatic',
        financial_connections: { permissions: ['payment_method', 'balances'] },
      }
    },
  });

  Log_INFO(`PaymentIntent created with ID: ${paymentIntent.id}`);
  return paymentIntent;
}

Financial Connections Session Creation

export async function createFinancialConnectionsSession(req, res, next) {
  const account = await stripe.accounts.create({
    country: 'US',
    type: 'custom',
    capabilities: {
      card_payments: { requested: true },
      transfers: { requested: true },
    },
  });

  const session = await stripe.financialConnections.sessions.create({
    account_holder: { type: 'account', account: account.id },
    filters: { countries: ['US'] },
    permissions: ['ownership', 'payment_method'],
  });

  return res.send({ clientSecret: session.client_secret });
}

To Reproduce
Steps to reproduce the issue:

  1. Enter 123-456-789 in the card field.
  2. Tap the confirm button.
  3. Observe the error message.

Expected Behavior
A clear and successful confirmation of the payment intent.

Smartphone / Tablet Details

Additional Context
Any additional insights or workarounds are welcome.

remonh87 commented 1 week ago

you need to use confirmPayment with the us bankaccount payment like done in this example: https://github.com/flutter-stripe/flutter_stripe/blob/fe5498b6ed605c8e7fbc9180843cf07f9ab9200f/example/lib/screens/regional_payment_methods/us_bank_account.dart#L94

itsamit108 commented 1 week ago

you need to use confirmPayment with the us bankaccount payment like done in this example:

https://github.com/flutter-stripe/flutter_stripe/blob/fe5498b6ed605c8e7fbc9180843cf07f9ab9200f/example/lib/screens/regional_payment_methods/us_bank_account.dart#L94

Thanks for the suggestion! However, this example applies to Stripe ACH with microdeposit payments, while I'm implementing Stripe ACH with instant verification using financial connections.

The error I'm facing occurs after collecting and verifying the bank account through instant verification, as per the documentation. When I attempt to confirm the payment, I get this error:

"You cannot confirm this PaymentIntent because it's missing a payment method. You can either update the PaymentIntent with a payment method and then confirm it again, or confirm it again directly with a payment method or ConfirmationToken."

Please refer to the code snippets provided above for reference, or feel free to ask any follow-up questions for further clarity.

remonh87 commented 3 days ago

I will try to look at it next week at the moment I am abroad for work

ogoldberg commented 1 day ago

I always get this error when I accidentally use a test payment method in a prod environment or vice versa. You might want to double check that you are using the right key for the environment you are in.