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.