Integrating Stripe in a Flutter App with Node.js
In this Article, we’ll take a hands-on approach to integrating Stripe payments into your Flutter app using a well-structured Node.js (TypeScript) backend.
Stripe is one of the most reliable and developer-friendly payment platforms, making it an excellent choice for handling secure payments in modern apps.
We’ll cover both frontend and backend setup, using Stripe’s Payment Intents API for one-time payments ( you can also adapt it for subscriptions with a small changes ). This guide assumes you have a working Flutter app and a Node.js server set up.
Prerequisites
Before we begin, make sure you have the following:
- A Stripe account → https://dashboard.stripe.com/register
- Flutter SDK set up
- Node.js (with TypeScript) backend
- A mobile app project (Flutter) and a backend server project
- Basic knowledge of HTTP requests and async operations
1. Setting Up Stripe
- Go to the Stripe Dashboard.
- Create a new project or use your existing one.
- Copy your Publishable key and Secret key from Developers > API keys.
- Use the test mode for development.
2. Backend: Node.js + TypeScript Setup
🔹 Step 1: Install Dependencies
npm install stripe express dotenv
npm install --save-dev @types/stripe @types/express
🔹 Step 2: Setup .env
STRIPE_SECRET_KEY=sk_test_YourSecretKeyHere
🔹 Step 3: Initialize Stripe and Create Payment Intent Route
import { Router } from "express";
import dotenv from "dotenv";
dotenv.config();
export const router = Router();
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
router.post("/create-payment-intent", async (req: any, res: any) => {
try {
const { amount, currency = "USD", description } = req.body;
// Use an existing Customer ID if this is a returning customer.
const customer = await stripe.customers.create();
const ephemeralKey = await stripe.ephemeralKeys.create(
{ customer: customer.id },
{ apiVersion: "2023-10-16" }
);
const paymentIntent = await stripe.paymentIntents.create({
amount, // in cents
currency,
customer: customer.id,
description,
automatic_payment_methods: { enabled: true },
});
const data = {
paymentintent: paymentIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
};
return res.status(200).json({ status: "Success", data });
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Payment Intent creation failed" });
}
});
export default router;
3. Flutter: Setup Stripe
🔹 Step 1: Install Dependencies
dependencies:
flutter:
sdk: flutter
flutter_stripe: ^10.0.0
http: ^0.14.0
🔹 Step 2: Flutter payment Flow
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
Future<void> makePayment() async {
try {
// Create payment intent data
var response = await stripePaymentInit();
if (response == null) {
// if you use state managment you can show what went wrong here easily.
print("Something went Wrong!!!");
return;
}
var publishablekey = response['publishableKey'];
var paymentIntent = response['paymentIntent'];
var emphemeralKey = response['ephemeralKey'];
Stripe.publishableKey = publishablekey;
// initialise the payment sheet setup
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
customFlow: false,
appearance: const PaymentSheetAppearance(
colors: PaymentSheetAppearanceColors(
background: Colors.white,
primary: Colors.redAccent,
componentBorder: Colors.red,
),
primaryButton: PaymentSheetPrimaryButtonAppearance(
shapes: PaymentSheetPrimaryButtonShape(blurRadius: 8),
colors: PaymentSheetPrimaryButtonTheme(
light: PaymentSheetPrimaryButtonThemeColors(
background: Colors.black,
text: Colors.white,
)))),
// Client secret key from payment data
paymentIntentClientSecret: paymentIntent,
customerEphemeralKeySecret: emphemeralKey,
googlePay: const PaymentSheetGooglePay(
currencyCode: "USD",
merchantCountryCode: "US",
),
// Merchant Name
merchantDisplayName: 'Marchant Display Name',
style: ThemeMode.light,
),
);
// Display payment sheet
displayPaymentSheet();
} catch (e) {
if (kDebugMode) {
print("exception $e");
}
if (e is StripeConfigException) {
if (kDebugMode) {
print("Stripe exception ${e.message}");
}
} else {
if (kDebugMode) {
print("exception $e");
}
}
}
}
stripePaymentInit() async {
var response = await http.post(
Uri.parse('https://yourserver.com/create-payment-intent'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'amount': 1000}), // $10.00
);
if (response.statusCode >= 200 && response.statusCode < 300) {
// success.
final body = jsonDecode(response.body);
return body['data'];
}
return null;
}
displayPaymentSheet() async {
try {
// "Display payment sheet";
await Stripe.instance.presentPaymentSheet();
// Show when payment is done
// Displaying snackbar for it(optional)
// update user data here(depending on your need)
} on StripeException catch (e) {
// If any error comes during payment
// so payment will be cancelled
if (kDebugMode) {
print('Error: $e');
}
} catch (e) {
if (kDebugMode) {
print("Error in displaying");
}
if (kDebugMode) {
print('$e');
}
}
}
4. Setting up a Webhook
🔹 Step 1: Go to the Webhooks Section
- Log in to your Stripe Dashboard → https://dashboard.stripe.com
- In the left sidebar, go to Developers → Webhooks
🔹 Step 2: Add a New Endpoint
- Click on “+ Add endpoint”
- In the Endpoint URL, enter your backend webhook route (e.g.):
https://yourdomain.com/webhook
🔹 Step 3: Select Events to Listen For
Stripe supports dozens of events. For typical apps, you might select:
payment_intent.succeededpayment_intent.payment_failedcheckout.session.completedinvoice.payment_succeeded(for subscriptions)customer.subscription.createdcustomer.subscription.deleted
You can also select “Receive all events” during testing.
🔹 Step 4: Copy the Webhook Secret
Once the webhook is created:
- Click on the newly added endpoint
- Find the “Signing secret” and click “Reveal”
- Copy it and add to your
.envfile:
STRIPE_WEBHOOK_SECRET=whsec_YourSecretHere
Make sure this secret is used when verifying events with stripe.webhooks.constructEvent() in your backend.
🔹 Step 5: Add Webhook Endpoint to Backend
Stripe sends a POST request to a URL you define, containing the event details.
Here’s a secure way to implement it:
// src/routes/webhook.ts
import express from 'express';
import Stripe from 'stripe';
import dotenv from 'dotenv';
import bodyParser from 'body-parser';
dotenv.config();
const router = express.Router();
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
// Use raw body for Stripe signature verification
router.post(
'/webhook',
bodyParser.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['stripe-signature'] as string;
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
console.error(`Webhook signature verification failed: ${err}`);
return res.sendStatus(400);
}
// ✅ Handle different event types
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent;
console.log(`💰 PaymentIntent succeeded: ${paymentIntent.id}`);
// Update order/payment status in DB here
break;
case 'invoice.payment_succeeded':
console.log(`✅ Subscription payment succeeded`);
break;
case 'customer.subscription.deleted':
console.log(`⚠️ Subscription canceled`);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.sendStatus(200);
}
);
export default router;
5. Tips on Implementing Subscription Payments with Stripe
Subscription payments in Stripe are designed to handle recurring billing — perfect for apps offering premium plans, memberships, or SaaS products. Below are some important considerations and best practices when implementing subscriptions in your Flutter and Node.js application:
🏷️ 1. Create Products and Prices in Stripe
Before initiating a subscription, you need to:
- Create a product on your Stripe Dashboard
- Under that product, define one or more prices — these represent your subscription plans (e.g., monthly, yearly).
- Stripe generates a unique Price ID (e.g.,
price_12345) for each plan.
💡 Tip: Store the Price ID in your backend database and associate it with your actual app products or plans. This approach keeps your pricing structure flexible and decoupled from hardcoded values in your code.
👤 2. Link App Users with Stripe Customers
Stripe uses Customer IDs to track billing history, subscriptions, and payment methods.
- When a user registers or subscribes for the first time, create a Stripe Customer using their email or user ID.
- Store the returned
customer.id(e.g.,cus_ABC123) in your app’s user table. - Reuse the same Customer ID for future subscriptions or billing updates.
This helps you:
- Manage payment methods
- View customer invoices
- Retrieve or cancel subscriptions programmatically
🔁 3. Create and Manage Subscriptions
Once you have the priceId and customerId, create a subscription:
const subscription = await stripe.subscriptions.create({
customer: user.customerId,
items: [{ price: priceId }],
payment_behavior: "default_incomplete",
payment_settings: { save_default_payment_method: "on_subscription" },
expand: ["latest_invoice.payment_intent"],
});
You can then use the payment_intent.client_secret to confirm the payment on the frontend (similar to one-time payments as discussed above).
📬 4. Keep User Subscriptions in Sync via Webhooks
It’s essential to keep your database updated with the latest subscription status. Stripe notifies you about events like:
invoice.payment_succeeded– Payment completedinvoice.payment_failed– Payment failedcustomer.subscription.updated– Subscription plan changedcustomer.subscription.deleted– Subscription canceled
Use webhooks to:
- Mark users as active or inactive
- Update access to premium features
- Send reminders or emails based on billing state
Conclusion
🤗 Phew! You’ve made it! You’ve just pulled off something pretty cool: getting Stripe to play nicely with your Flutter app and that sleek Node.js (TypeScript) backend. This means your app is now ready to handle both those one-off purchases and ongoing subscriptions like a pro. Seriously, by diving into Stripe’s Payment Intents for single payments and getting a handle on the whole Stripe Subscriptions journey, you’ve built a super solid payment foundation. High five!👋
⛔️ Don’t forget, those webhooks are your secret weapon. They’re what keep your app and Stripe constantly talking, making sure everyone knows exactly what’s going on with payments and subscriptions. This real-time update magic is key to keeping your users happy and giving them a smooth, reliable experience.
#FLutter #Stripe #StripeIntegration #PymentIntegration #PaymentGateway #MobileAppDevelopment