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

  1. Go to the Stripe Dashboard.
  2. Create a new project or use your existing one.
  3. Copy your Publishable key and Secret key from Developers > API keys.
  4. 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

  1. Log in to your Stripe Dashboard → https://dashboard.stripe.com
  2. In the left sidebar, go to Developers → Webhooks

🔹 Step 2: Add a New Endpoint

  1. Click on “+ Add endpoint”
  2. 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.succeeded
  • payment_intent.payment_failed
  • checkout.session.completed
  • invoice.payment_succeeded (for subscriptions)
  • customer.subscription.created
  • customer.subscription.deleted

You can also select “Receive all events” during testing.

🔹 Step 4: Copy the Webhook Secret

Once the webhook is created:

  1. Click on the newly added endpoint
  2. Find the “Signing secret” and click “Reveal”
  3. Copy it and add to your .env file:
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 completed
  • invoice.payment_failed – Payment failed
  • customer.subscription.updated – Subscription plan changed
  • customer.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

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *