Search code examples
flutterstripe-payments

Flutter Stripe Web with PaymentElement instead of PaymentSheet


After exploring other options I learned that the only way to solve problem for my use case of accepting card payments in my Flutter web app is via Flutter_stripe and flutter_stripe_web packages.

I implemented both to find out that Stripe.PaymentSheet is NOT supported for web. :( From Stripe support, I learned that instead of PaymentSheet, I should use Stripe.PaymentElement while rest of the code remain same i.e. PaymentIntent and backend function etc

I have tried following document (though a bit unclear in context with Flutter/dart) but I am just unable to fill the missing blocks.

Could you guys take a look and let me know what am I doing wrong or what could be the right way of implementing PaymentElement instead of Sheets

Thanks in advance.

My PaymentFile

import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:http/http.dart' as http;

    class StripePayments {
      Future<void> initPaymentSheet(context,
          {required String email, required int amount}) async {
        try {
          // 1. create payment intent on the server
          final response = await http.post(
              Uri.parse(
                  'https://mycloudFunctionUrl/stripePaymentIntentRequest'),
              body: {
                'email': email,
                'amount': amount.toString(),
              });
    
          if (response.statusCode != 200) {
            throw Exception('Failed to create payment intent: ${response.body}');
          }
    
          final jsonResponse = jsonDecode(response.body);
          log('Payment intent response: $jsonResponse');
    
          // 2. Verify the keys and data
          if (jsonResponse['paymentIntent'] == null ||
              jsonResponse['customer'] == null ||
              jsonResponse['ephemeralKey'] == null) {
            throw Exception('Invalid response from server: $jsonResponse');
          }
    
          // 3. initialize the payment sheet
    
          await Stripe.instance.initPaymentSheet(
            paymentSheetParameters: SetupPaymentSheetParameters(
              paymentIntentClientSecret: jsonResponse['paymentIntent'],
              merchantDisplayName: 'Cruise-App',
              customerId: jsonResponse['customer'],
              customerEphemeralKeySecret: jsonResponse['ephemeralKey'],
              style: ThemeMode.light,
            ),
          );
    
          // 4. Present the payment sheet
          await Stripe.instance.presentPaymentSheet();
    
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Payment completed!')),
          );
        } catch (e) {
          if (e is StripeException) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('Error from Stripe: ${e.error.localizedMessage}'),
              ),
            );
          } else if (e is StripeConfigException) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('Stripe configuration error: ${e.message}'),
              ),
            );
          } else {
            print(e.toString());
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Error: $e')),
            );
          }
        }
      }
    }

This is how my backend / Cloud Function looks like

const functions = require("firebase-functions");
const cors = require("cors")({ origin: true });
const stripe = require("stripe")("sk_test_my_secret_key");

exports.stripePaymentIntentRequest = functions.region("europe-west3").https.onRequest((req, res) => {
  cors(req, res, async () => {
    try {
      let customerId;

      // Gets the customer whose email id matches the one sent by the client
      const customerList = await stripe.customers.list({
        email: req.body.email,
        limit: 1
      });

      // Checks if the customer exists, if not creates a new customer
      if (customerList.data.length !== 0) {
        customerId = customerList.data[0].id;
      } else {
        const customer = await stripe.customers.create({
          email: req.body.email
        });
        customerId = customer.id; // Changed customer.data.id to customer.id
      }

      // Creates a temporary secret key linked with the customer
      const ephemeralKey = await stripe.ephemeralKeys.create(
        { customer: customerId },
        { apiVersion: '2020-08-27' }
      );

      // Creates a new payment intent with amount passed in from the client
      const paymentIntent = await stripe.paymentIntents.create({
        amount: parseInt(req.body.amount),
        currency: 'nok',
        customer: customerId,
      });

      res.status(200).send({
        paymentIntent: paymentIntent.client_secret,
        ephemeralKey: ephemeralKey.secret,
        customer: customerId,
        success: true,
      });

    } catch (error) {
      res.status(404).send({ success: false, error: error.message });
    }
  });
});

This is the documentation I was referred to by Stripe Team but their support says on Discord, "We don't know dart or flutter. Unsure how people do that who have already done it"

Please suggest ! Thank you again


Solution

  • you can directly use PaymentElement widget. I don't have much knowledge on this but this may help you to get started.

    var paymentIntentResult = await createPaymentIntent('100','JYP');
    var paymentIntentSecret = paymentIntentResult['client_secret'];
    
    PaymentElement(
            autofocus: true,
            enablePostalCode: true,
            onCardChanged: (_) {},
            clientSecret: paymentIntentSecret ?? '',
    )
    
    createPaymentIntent(String amount, String currency) async {
    try {
      //Request body
      Map<String, dynamic> body = {
        'amount': calculateAmount(amount),
        'currency': currency,
      };
    
      //Make post request to Stripe
      var response = await http.post(
        Uri.parse('https://api.stripe.com/v1/payment_intents'),
        headers: {
          'Authorization':
              'Bearer [your_key]',
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: body,
      );
      return json.decode(response.body);
    } catch (err) {
      throw Exception(err.toString());
    }
    

    and use this to make the call

     OutlinedButton(
              onPressed: () async {
                try {
                  await WebStripe.instance
                      .confirmPaymentElement(ConfirmPaymentElementOptions(
                    redirect: PaymentConfirmationRedirect.ifRequired,
                    confirmParams: ConfirmPaymentParams(return_url: ''),
                  ));
                } on Exception catch (e) {
                  print(e.toString());
                }
              },
              child: Text('Go'));