Stripe Payments with Flutter

Stripe Payments with Flutter

Stripe Payment gateway integration in Flutter using Stripe SDK.

Lets setup stripe

Create an account on Stripe and complete basic account verification and other stuff.

Step - 1: Turn on the test mode and goto developer section

Step - 2: Get API Keys

Now from the left sidebar, select API Keys.

These keys will be required for making payments successful.

  • Publishable key (required)

  • Secret key (required)


Setting up Flutter Project:

Plugins used:

Add these plugins into project

Add Internet permission:

android > app > src > main > AndroidManifest.xml Provide internet permission (it may cause issues in the release build):

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.stripetest">
    <uses-permission android:name="android.permission.INTERNET" /> //add this
   <application
...

Set SDK values in android > app > build.gradle:

compileSdkVersion 33
minSdkVersion 21
targetSdkVersion 28

// Enable multidesk support (its optional)
dependencies {
    implementation 'com.android.support:multidex:1.0.3' // add this
}

Add these lines in MainActivity.kt :

android > app > src > main > kotlin > com > example > stripe > MainActivity.kt :

package com.example.stripe

========== Add these lines ==========
import io.flutter.embedding.android.FlutterActivity //Maybe its there already
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity: FlutterFragmentActivity () {
}
======================================

Add Theme values:

android > app > src > main > res > values-night > styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="LaunchTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
        <item name="android:windowBackground">@drawable/launch_background</item>
    </style>
    <style name="NormalTheme" parent="Theme.MaterialComponents">
        <item name="android:windowBackground">?android:colorBackground</item>
    </style>
</resources>

android > app > src > main > res > values > styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/launch_background</item>
    </style>
    <style name="NormalTheme" parent="Theme.MaterialComponents">
        <item name="android:windowBackground">?android:colorBackground</item>
    </style>
</resources>

Let's implement in the code

Initialise Stripe in main method:

main.dart:

void main() {
Stripe.publishableKey = // YOUR PUBLISHABLE KEY HERE as String(quoted)
  runApp(const MyApp());
}

PaymentScreen.dart:

Declare paymentIntent Object and secretKey in Stateful / Stateless Widget:

  Map<String, dynamic>? paymentIntent;
  var clientkey = // SECRET KEY HERE as String(quoted);

Some UI

Scaffold(
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("Buy Premium Membership at 10 INR"),
            Container(
              width: MediaQuery.of(context).size.width,
              color: Colors.teal,
              margin: const EdgeInsets.all(10),
              child: TextButton(
                onPressed: () => makePayment(), // callback fxn
                child: const Text(
                  'Pay',
                  style: TextStyle(color: Colors.white),
                ),
              ),
            )
          ],
        ),
      )

Create payment intent:


  createPaymentIntent(String amount, String currency) async {
    try {

      // TODO: Request body
      Map<String, dynamic> body = {
        'amount': calculateAmount(amount),
        'currency': currency,
        'payment_method_types[]': 'card'
      };

      // TODO: POST request to stripe
      var response = await http.post(
        Uri.parse('https://api.stripe.com/v1/payment_intents'),
        headers: {
          'Authorization': 'Bearer ' + clientkey, //SecretKey used here
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: body,
      );

      log('Payment Intent Body->>> ${response.body.toString()}');
      return jsonDecode(response.body);
    } catch (err) {
      // ignore: avoid_print
      log('err charging user: ${err.toString()}');
    }
  }

Calculate Price:

  calculateAmount(String amount) {
    final calculatedAmout = (int.parse(amount)) * 100;
    return calculatedAmout.toString();
  }

To make payment to stripe:


  Future<void> makePayment() async {
    try {

      // TODO: Create Payment intent
      paymentIntent = await createPaymentIntent('10', 'INR');

      // TODO: Initialte Payment Sheet
      await Stripe.instance
          .initPaymentSheet(
        paymentSheetParameters: SetupPaymentSheetParameters(
          paymentIntentClientSecret: paymentIntent!['client_secret'],
          applePay: null,
          googlePay: null,
          style: ThemeMode.light,
          merchantDisplayName: 'SomeMerchantName',
        ),
      )
          .then((value) {
        log("Success");
      });

      // TODO: now finally display payment sheeet
      displayPaymentSheet(); // Payment Sheet
    } catch (e, s) {
      String ss = "exception 1 :$e";
      String s2 = "reason :$s";
      log("exception 1:$e");
    }
  }

Displaying Payment sheet:


  displayPaymentSheet() async {
    try {
      await Stripe.instance.presentPaymentSheet().then((value) {
        showDialog(
          context: context, 
          builder: (_) => AlertDialog(
            content: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Row(
                  children: const [
                    Padding(
                      padding: EdgeInsets.symmetric(horizontal: 10),
                      child: Icon(
                        Icons.check_circle,
                        color: Colors.green,
                      ),
                    ),
                    Text("Payment Successfull"),
                  ],
                ),
              ],
            ),
          ),
        );

        // TODO: update payment intent to null
        paymentIntent = null;
      }).onError((error, stackTrace) {
        String ss = "exception 2 :$error";
        String s2 = "reason :$stackTrace";
      });
    } on StripeException catch (e) {
      print('Error is:---> $e');
      String ss = "exception 3 :$e";
    } catch (e) {
      log('$e');
    }
  }
}

Running the app:

Details used: (as mentioned in docs)

Card Number: 4242 4242 4242 4242

expiry: 12/34

cvv: 567

Complete Code:

void main() {
  Stripe.publishableKey = "PUBLISHABLE_KEY";
  runApp(const MyApp());
}

// ==================== MyApp ====================
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Stripe Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const PaymentScreen(),
    );
  }
}

// ================ PaymentScreen ================

class PaymentScreen extends StatefulWidget {
  const PaymentScreen({Key? key}) : super(key: key);

  @override
  State<PaymentScreen> createState() => _PaymentScreenState();
}

class _PaymentScreenState extends State<PaymentScreen> {

  Map<String, dynamic>? paymentIntent;
  var clientkey = "SECRET_KEY_HERE"; // Secret Key

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("Buy Premium Membership at 10 INR"),
            Container(
              width: MediaQuery.of(context).size.width,
              color: Colors.teal,
              margin: const EdgeInsets.all(10),
              child: TextButton(
                onPressed: () => makePayment(), 
                child: const Text(
                  'Pay',
                  style: TextStyle(color: Colors.white),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }

  createPaymentIntent(String amount, String currency) async {
    try {

      // TODO: Request body
      Map<String, dynamic> body = {
        'amount': calculateAmount(amount),
        'currency': currency,
        'payment_method_types[]': 'card'
      };

      // TODO: POST request to stripe
      var response = await http.post(
        Uri.parse('https://api.stripe.com/v1/payment_intents'),
        headers: {
          'Authorization': 'Bearer ' + clientkey,
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: body,
      );
      log('Payment Intent Body->>> ${response.body.toString()}');
      return jsonDecode(response.body);
    } catch (err) {
      log('err charging user: ${err.toString()}');
    }
  }

  calculateAmount(String amount) {
    final calculatedAmout = (int.parse(amount)) * 100;
    return calculatedAmout.toString();
  }

  Future<void> makePayment() async {
    try {

      // TODO: Create Payment intent
      paymentIntent = await createPaymentIntent('10', 'INR');

      // TODO: Initialte Payment Sheet
      await Stripe.instance
          .initPaymentSheet(
        paymentSheetParameters: SetupPaymentSheetParameters(
          paymentIntentClientSecret: paymentIntent!['client_secret'],
          applePay: null,
          googlePay: null,
          style: ThemeMode.light,
          merchantDisplayName: 'someMerchantName',
        ),
      )
          .then((value) {
        log("Success");
      });

      // TODO: now finally display payment sheeet
      displayPaymentSheet();
    } catch (e, s) {
      String ss = "exception 1 :$e";
      String s2 = "reason :$s";
      log("exception 1:$e");
    }
  }

  displayPaymentSheet() async {
    try {
      await Stripe.instance.presentPaymentSheet().then((value) {
        showDialog(
          context: context,
          builder: (_) => AlertDialog(
            content: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Row(
                  children: const [
                    Padding(
                      padding: EdgeInsets.symmetric(horizontal: 10),
                      child: Icon(
                        Icons.check_circle,
                        color: Colors.green,
                      ),
                    ),
                    Text("Payment Successfull"),
                  ],
                ),
              ],
            ),
          ),
        );

        // TODO: update payment intent to null
        paymentIntent = null;
      }).onError((error, stackTrace) {
        String ss = "exception 2 :$error";
        String s2 = "reason :$stackTrace";
      });
    } on StripeException catch (e) {
      print('Error is:---> $e');
      String ss = "exception 3 :$e";
    } catch (e) {
      log('$e');
    }
  }
}

Checking in Stripe dashboard:

in Stripe graph:

in Stripe Logs:

in Events:

Yes, It's Working!!!

For actual payments, Replace test keys with live keys. And it's all done.

NOTE: It's recommended to Store all keys in .envfile. Which must be ignored by git or any VCS.