In today’s digital economy, accepting payments online is a must-have feature for many applications. Stripe has become one of the most popular payment gateways thanks to its simplicity, security, and support for multiple payment methods. On the other hand, Flutter makes it easy to build beautiful, cross-platform apps for iOS and Android with a single codebase.
In this tutorial, we’ll combine the two and show you how to integrate Stripe with Flutter to build a secure payment app. You’ll learn how to set up a Flutter project, connect it with Stripe, handle payments via a backend, and test everything using Stripe’s test environment. By the end, you’ll have a working Flutter payment app that can serve as the foundation for e-commerce, subscriptions, or in-app purchases.
Prerequisites
Before we start, make sure you have the following installed and set up:
-
Flutter SDK (latest stable version) → Install Guide
-
Android Studio or VS Code with Flutter/Dart plugins
-
Stripe account (sign up for free at Stripe)
-
Basic knowledge of Flutter and Dart programming
-
Backend environment (we’ll use Node.js/Express in this tutorial, but you can also use Firebase Cloud Functions or any server-side stack you prefer).
You’ll also need to copy your Stripe Publishable Key and Secret Key from the Stripe Dashboard under Developers → API Keys.
Project Setup
First, let’s create a new Flutter project and add the required dependencies.
Step 1: Create a New Flutter Project
Open your terminal and run the following command:
flutter create flutter_stripe_payment
Navigate into the project folder:
cd flutter_stripe_payment
Open it with your preferred IDE (Android Studio or VS Code).
Step 2: Add Required Dependencies
We’ll use the following Flutter packages:
-
flutter_stripe
→ Official Stripe Flutter SDK. -
http
→ To call our backend API. -
provider
(optional) → For simple state management.
Open pubspec.yaml and add:
dependencies:
flutter:
sdk: flutter
flutter_stripe: ^11.0.0
http: ^1.2.2
provider: ^6.1.2
Then, run:
flutter pub get
Step 3: Configure iOS and Android
The Stripe SDK requires some additional setup for iOS and Android.
iOS Setup
Open ios/Runner/Info.plist and add the following:
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
Also, update the deployment target to at least iOS 13 in ios/Podfile:
platform :ios, '13.0'
Then run:
cd ios
pod install
cd ..
Android Setup
Open android/app/build.gradle and ensure minSdkVersion
is set to 21 or higher:
android {
defaultConfig {
minSdkVersion 21
}
}
Step 4: Initialize Stripe
In lib/main.dart, initialize Stripe with your publishable key (replace pk_test_xxx
with your own from Stripe Dashboard):
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Set the publishable key
Stripe.publishableKey = "pk_test_xxx";
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Stripe Payment',
theme: ThemeData(primarySwatch: Colors.blue),
home: const PaymentScreen(),
);
}
}
class PaymentScreen extends StatelessWidget {
const PaymentScreen({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text("Stripe Payment App Setup Complete"),
),
);
}
}
Run the app to make sure everything compiles correctly:
flutter run
At this point, the Flutter project is set up with the Stripe SDK and ready to integrate payments.
Backend Setup – Create Payment Intent with Stripe (Node.js + Express)
Stripe requires a server-side backend to create payment intents securely, because the secret key must never be exposed in your Flutter app. The backend will handle creating a PaymentIntent and returning its clientSecret
to the Flutter app.
Step 1: Initialize Node.js Project
Create a new folder for your backend:
mkdir stripe-backend
cd stripe-backend
npm init -y
Step 2: Install Dependencies
We’ll use Express for API routing and the Stripe Node.js SDK to interact with Stripe.
npm install express stripe cors dotenv
-
express
→ lightweight web framework -
stripe
→ Stripe SDK -
cors
→ allow requests from Flutter app -
dotenv
→ manage environment variables
Step 3: Configure Stripe Keys
Create a file named .env in your backend project root:
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxx
PORT=5000
⚠️ Use your Stripe Secret Key (from Dashboard → Developers → API Keys).
Step 4: Create Express Server
Create a file named server.js:
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const Stripe = require("stripe");
const app = express();
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
app.use(cors());
app.use(express.json());
// Test route
app.get("/", (req, res) => {
res.send("Stripe Payment Backend is Running 🚀");
});
// Create PaymentIntent
app.post("/create-payment-intent", async (req, res) => {
try {
const { amount, currency } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount, // in cents (e.g. 1000 = $10)
currency,
automatic_payment_methods: { enabled: true },
});
res.json({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Step 5: Run Backend
Start the server:
node server.js
You should see:
Server running on port 5000
Test the API with Postman or curl:
curl -X POST http://localhost:5000/create-payment-intent \
-H "Content-Type: application/json" \
-d '{"amount":1000,"currency":"usd"}'
Expected response:
{
"clientSecret": "pi_12345_secret_67890"
}
✅ Now the backend is ready! This API will generate a PaymentIntent and return its clientSecret
for the Flutter app to confirm the payment.
Flutter Integration – Collect Payment and Confirm with Stripe
Now that we have our backend ready, we can connect the Flutter app to create a payment intent and confirm it with Stripe.
Step 1: Create a Service to Call the Backend
Inside lib/, create a new file: payment_service.dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
class PaymentService {
final String backendUrl = "http://localhost:5000"; // change if deployed
Future<String?> createPaymentIntent(int amount, String currency) async {
try {
final response = await http.post(
Uri.parse("$backendUrl/create-payment-intent"),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"amount": amount, "currency": currency}),
);
if (response.statusCode == 200) {
final jsonResponse = jsonDecode(response.body);
return jsonResponse["clientSecret"];
} else {
if (kDebugMode) {
print("Failed to create payment intent: ${response.body}");
}
return null;
}
} catch (e) {
if (kDebugMode) {
print("Error creating payment intent: $e");
}
return null;
}
}
}
Step 2: Build Payment UI
Let’s create a simple PaymentScreen with a button to trigger a test payment.
Update lib/main.dart:
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'payment_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Stripe.publishableKey = "pk_test_xxxxxxxxxxxxx"; // replace with your key
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Stripe Payment',
theme: ThemeData(primarySwatch: Colors.blue),
home: const PaymentScreen(),
);
}
}
class PaymentScreen extends StatefulWidget {
const PaymentScreen({super.key});
@override
State<PaymentScreen> createState() => _PaymentScreenState();
}
class _PaymentScreenState extends State<PaymentScreen> {
final PaymentService _paymentService = PaymentService();
bool _isLoading = false;
Future<void> _makePayment() async {
setState(() => _isLoading = true);
try {
// Step 1: Create Payment Intent on backend
final clientSecret = await _paymentService.createPaymentIntent(
1000,
"usd",
); // $10
if (clientSecret == null) {
throw Exception("Failed to get client secret");
}
// Step 2: Initialize payment sheet
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: clientSecret,
style: ThemeMode.light,
merchantDisplayName: "Djamware Shop",
),
);
// Step 3: Present payment sheet
await Stripe.instance.presentPaymentSheet();
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text("Payment Successful ✅")));
} catch (e) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Payment Failed: $e")));
}
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Stripe Payment")),
body: Center(
child: _isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _makePayment,
child: const Text("Pay \$10"),
),
),
);
}
}
Step 3: Run the App
Start both the backend and the Flutter app:
# In backend folder
node server.js
# In Flutter project
flutter run
When you tap Pay $10, Stripe will open the Payment Sheet.
Use a Stripe test card like:
4242 4242 4242 4242
Exp: Any future date
CVC: Any 3 digits
If successful, you’ll see a Payment Successful ✅ message, and the payment will be logged in your Stripe Dashboard.
✅ At this point, we’ve integrated Stripe in Flutter to collect and confirm payments using the Payment Sheet
Handle Payment Status & Error Handling
Payments can succeed, fail, or be canceled by the user. To provide a smooth experience, we need to handle all possible outcomes gracefully.
Step 1: Update Payment Flow with Error Handling
Let’s improve _makePayment()
in PaymentScreen:
Future<void> _makePayment() async {
setState(() => _isLoading = true);
try {
// Step 1: Create Payment Intent on backend
final clientSecret =
await _paymentService.createPaymentIntent(1000, "usd"); // $10
if (clientSecret == null) {
throw Exception("Failed to get client secret");
}
// Step 2: Initialize payment sheet
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: clientSecret,
style: ThemeMode.light,
merchantDisplayName: "Djamware Shop",
),
);
// Step 3: Present payment sheet
await Stripe.instance.presentPaymentSheet();
// Success case
_showMessage("Payment Successful ✅");
} on StripeException catch (e) {
// Stripe-specific errors (canceled, card declined, etc.)
_showMessage("Payment canceled or failed: ${e.error.localizedMessage}");
} catch (e) {
// Other errors
_showMessage("Something went wrong: $e");
}
setState(() => _isLoading = false);
}
void _showMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
Step 2: Handle Common Scenarios
-
Successful Payment
-
Show a success Snackbar or redirect to a confirmation screen.
-
-
Canceled Payment
-
User closes payment sheet → handled by
StripeException
. -
Show a friendly message: “Payment canceled.”
-
-
Failed Payment (e.g., card declined)
-
Stripe returns an error message (e.g., “Your card was declined”).
-
Display to the user with
_showMessage()
.
-
Step 3: Optional – Create a Success Page
You can redirect users to a dedicated success screen after successful payment.
class SuccessScreen extends StatelessWidget {
const SuccessScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Payment Success")),
body: const Center(
child: Icon(Icons.check_circle, color: Colors.green, size: 100),
),
);
}
}
Update _makePayment()
success case:
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const SuccessScreen()),
);
✅ Now your app gracefully handles success, cancellation, and failure while keeping the user experience smooth.
Testing with Stripe
Before going live, Stripe provides a test mode with sample cards so you can safely verify your payment flow.
Step 1: Use Stripe Test Keys
Make sure your backend .env file is using the test secret key (sk_test_xxx
), and your Flutter app is using the test publishable key (pk_test_xxx
).
You can find these under:
Stripe Dashboard → Developers → API Keys
Step 2: Test Cards
Stripe provides multiple test card numbers. You can enter them in the Payment Sheet when testing.
Common examples:
Card Number | Description |
---|---|
4242 4242 4242 4242 |
Always succeeds |
4000 0027 6000 3184 |
Requires authentication |
4000 0000 0000 9995 |
Always fails (insufficient funds) |
Use any future expiry date and any 3-digit CVC.
Step 3: Run the App
-
Start your backend:
node server.js
-
Run Flutter app:
flutter run
-
Tap Pay $10 → Payment Sheet will appear.
-
Enter one of the test cards above.
-
If you use
4242 4242 4242 4242
, payment succeeds immediately. -
If you use
4000 0027 6000 3184
, Stripe may ask for 3D Secure (authentication popup). -
If you use
4000 0000 0000 9995
, the payment fails
Step 4: Monitor in Stripe Dashboard
Go to Stripe Dashboard → Payments.
You’ll see all your test payments listed with status:
-
✅ Succeeded
-
⚠️ Requires action
-
❌ Failed
This helps confirm your integration works correctly.
✅ With test mode, you can simulate all possible payment scenarios before moving to production.
Bonus – Save Customer & Payment Methods (Optional)
For many apps (e.g., e-commerce or subscriptions), it’s useful to let users save their payment methods for future use. Stripe allows you to create a Customer and attach payment methods to them.
Step 1: Update Backend to Create Customer
In server.js, add an endpoint for creating a Stripe customer:
// Create Customer
app.post("/create-customer", async (req, res) => {
try {
const { email } = req.body;
const customer = await stripe.customers.create({
email: email,
});
res.json({ customerId: customer.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Step 2: Save Payment Method
When creating a PaymentIntent, you can allow the method to be saved for future use. Update the /create-payment-intent
endpoint:
app.post("/create-payment-intent", async (req, res) => {
try {
const { amount, currency, customerId } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
customer: customerId, // attach to customer
setup_future_usage: "off_session", // save card for future use
automatic_payment_methods: { enabled: true },
});
res.json({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
-
customerId
→ Stripe customer ID (from/create-customer
). -
setup_future_usage: "off_session"
→ tells Stripe to save card for future payments.
Step 3: Flutter – Create Customer First
Update payment_service.dart:
Future<String?> createCustomer(String email) async {
try {
final response = await http.post(
Uri.parse("$backendUrl/create-customer"),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"email": email}),
);
if (response.statusCode == 200) {
final jsonResponse = jsonDecode(response.body);
return jsonResponse["customerId"];
} else {
print("Failed to create customer: ${response.body}");
return null;
}
} catch (e) {
print("Error creating customer: $e");
return null;
}
}
Step 4: Use Saved Payment Method (Future Payments)
Later, you can charge the saved customer without showing the payment sheet again:
app.post("/charge-customer", async (req, res) => {
try {
const { customerId, amount, currency } = req.body;
// Use the default saved payment method
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
customer: customerId,
off_session: true,
confirm: true,
});
res.json({ success: true, paymentIntent });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
This way, customers can pay in one tap for future purchases.
✅ With this setup, your app now supports customer accounts and saved payment methods — perfect for subscriptions or repeat purchases.
Conclusion
In this tutorial, you learned how to integrate Stripe with Flutter to build a payment app. We started by setting up a Flutter project with the flutter_stripe
package, then built a secure Node.js + Express backend to create payment intents with Stripe’s secret key.
On the Flutter side, we implemented a Payment Sheet flow to collect and confirm payments. We also covered error handling, tested with Stripe’s test cards, and optionally extended the app to save customers and payment methods for future transactions.
With this foundation, you can extend the app further by:
-
Adding Apple Pay and Google Pay support.
-
Implementing subscriptions or recurring payments.
-
Deploying the backend to Heroku, Vercel, or AWS Lambda.
-
Integrating with your own business logic (orders, invoices, receipts).
By combining Flutter’s cross-platform power with Stripe’s reliable payments API, you now have the tools to build modern, secure, and scalable payment-enabled apps. 🚀
You can get the full source code on our GitHub.
That's just the basics. If you need more deep learning about Flutter, Dart, or related, you can take the following cheap course:
- Flutter - Beginners Course
- Flutter & Dart Campus: Android & IOS App Entwicklung von A-Z
- Flutter Masterclass - (Dart, Api & More)
- Flutter Redux Essential Course (English)
- Flutter chat application with firebase
- Supabase for Flutter Developers
- Flutter & Dart: Build Real-World Apps From Scratch [2025]
- Code Smart: Flutter + AI Tools to Build SpendSage
- Flutter Profissional: DDD, Clean Architecture e SOLID 2025
- Build inDrive & UBER Clone App with Flutter & Firebase 2025
Thanks!