Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Create Custom Payments in FlutterFlow

Choose your payment method based on your use case: Stripe Checkout (Cloud Function creates CheckoutSession → Launch URL) for simplest PCI-compliant one-time payments, Stripe Payment Intent with CardField Custom Widget for in-app card entry without redirects, in_app_purchase package for App Store and Play Store native subscriptions, or PayPal via WebView for PayPal-preferred audiences. Never build a custom card form — always use Stripe's pre-built components.

What you'll learn

  • The four payment implementation patterns and a decision framework for choosing the right one
  • How to implement Stripe Checkout: Cloud Function creates a CheckoutSession and FlutterFlow launches the hosted payment URL
  • How to use the in_app_purchase package for App Store and Play Store native subscription billing
  • Why building a custom card entry form makes you PCI DSS compliant and why Stripe's CardField eliminates that requirement
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner13 min read35-50 minFlutterFlow Free+ (Stripe Checkout); FlutterFlow Pro (custom CardField requires code export for App Store)March 2026RapidDev Engineering Team
TL;DR

Choose your payment method based on your use case: Stripe Checkout (Cloud Function creates CheckoutSession → Launch URL) for simplest PCI-compliant one-time payments, Stripe Payment Intent with CardField Custom Widget for in-app card entry without redirects, in_app_purchase package for App Store and Play Store native subscriptions, or PayPal via WebView for PayPal-preferred audiences. Never build a custom card form — always use Stripe's pre-built components.

Add payments to your FlutterFlow app using the right method for your platform and audience

Payment implementation is one of the most consequential technical decisions in any app. The wrong choice means either app store rejection, PCI compliance liability, or a checkout experience so friction-filled that users abandon. FlutterFlow supports four payment patterns, each with different trade-offs on complexity, UX quality, platform compatibility, and compliance requirements. This tutorial walks through all four — Stripe Checkout hosted page, Stripe Payment Intent with custom CardField, Apple/Google native in-app purchases, and PayPal via WebView — with the setup steps and a clear decision guide for which to use.

Prerequisites

  • A Stripe account (stripe.com — free to sign up, test mode available without business verification)
  • For in-app purchases: an Apple Developer account ($99/year) and/or Google Play Console account ($25 one-time) with your app already registered
  • Firebase project with Cloud Functions enabled (Blaze plan) for Stripe server-side operations
  • A FlutterFlow project with the page or flow where users will make a payment

Step-by-step guide

1

Choose your payment method using the decision framework

Answer these four questions to pick the right payment pattern. Question 1 — Will this app be sold on the App Store or Google Play Store, and do you charge for subscriptions or digital content? If yes: you MUST use in-app purchases (in_app_purchase package) for digital goods and subscriptions — Apple and Google mandate this and take 15-30%. Physical goods and services are exempt. Question 2 — Do you need full payment UI inside your app with no redirect? If yes: use Stripe Payment Intent + CardField Custom Widget (available in FlutterFlow via Custom Widget with Stripe's Flutter SDK). Note: App Store and Google Play do not allow custom card entry forms for digital goods — only in-app purchase. Question 3 — Is simplest possible integration the priority, or do you sell physical goods/services? If yes: use Stripe Checkout — a Cloud Function creates a CheckoutSession, you get a URL, Launch URL opens Stripe's hosted payment page. Stripe handles PCI compliance, address collection, fraud detection. Question 4 — Does your audience prefer PayPal over card entry? PayPal via WebView is viable for web-first audiences or international markets where PayPal is dominant.

Expected result: You have selected the appropriate payment pattern for your app's use case and platform distribution channel.

2

Implement Stripe Checkout — the simplest path to production payments

Stripe Checkout is a Stripe-hosted payment page — you create a session server-side and redirect the user to Stripe's URL. All card entry, fraud detection, address collection, and PCI compliance are handled by Stripe. Create a Cloud Function named createCheckoutSession. It receives: priceId (from your Stripe Dashboard product), quantity, successUrl, cancelUrl, and optionally customerId. The function calls Stripe's API to create a session and returns the URL. In FlutterFlow: add the createCheckoutSession API Call to your API Manager → TransactionService group. On your pricing or cart page, add a 'Buy Now' or 'Subscribe' button. Action Flow: call createCheckoutSession with the price ID and quantity, get the session URL from the response, then add a Launch URL action using the returned sessionUrl. The user is taken to Stripe's hosted page, pays, and Stripe redirects back to your successUrl. Create a Success page in FlutterFlow and use deep links or Firebase Dynamic Links to bring the user back into the app after payment.

functions/checkout.js
1// functions/index.js — Stripe Checkout session creator
2// Install: cd functions && npm install stripe
3const functions = require('firebase-functions');
4const admin = require('firebase-admin');
5const Stripe = require('stripe');
6
7admin.initializeApp();
8
9exports.createCheckoutSession = functions.https.onRequest(async (req, res) => {
10 res.set('Access-Control-Allow-Origin', '*');
11 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }
12
13 const stripe = new Stripe(functions.config().stripe.secret_key);
14 const {
15 priceId,
16 quantity = 1,
17 successUrl,
18 cancelUrl,
19 customerId,
20 userId, // Firebase Auth UID for post-payment webhook
21 mode = 'payment' // 'payment' or 'subscription'
22 } = req.body;
23
24 try {
25 const session = await stripe.checkout.sessions.create({
26 mode,
27 line_items: [{ price: priceId, quantity }],
28 success_url: successUrl ||
29 'https://your-app.com/payment-success?session_id={CHECKOUT_SESSION_ID}',
30 cancel_url: cancelUrl ||
31 'https://your-app.com/payment-cancelled',
32 customer: customerId || undefined,
33 metadata: { userId }, // Pass userId for webhook
34 ...(mode === 'subscription' && {
35 subscription_data: { metadata: { userId } },
36 }),
37 });
38
39 res.json({
40 sessionId: session.id,
41 sessionUrl: session.url,
42 });
43 } catch (err) {
44 console.error('Checkout session error:', err);
45 res.status(500).json({ error: err.message });
46 }
47});
48
49// Set config: firebase functions:config:set stripe.secret_key='sk_test_...'

Expected result: Buy button calls createCheckoutSession, receives a URL, and launches Stripe's hosted payment page. Test with Stripe's test card 4242 4242 4242 4242 / any future date / any 3-digit CVC.

3

Implement in-app purchases for App Store and Google Play subscriptions

If your app has subscriptions or sells digital content (premium features, coins, additional storage) and will be distributed on the App Store or Google Play, you must use in-app purchases — Apple and Google require it for all digital goods sold in mobile apps. In FlutterFlow: Custom Code → Pubspec Dependencies → add in_app_purchase at the current version. Create a Custom Action named initializePurchases that sets up the purchase stream and listens for purchase updates. Create a Custom Action named purchaseProduct that takes a productId (the ID you created in App Store Connect or Google Play Console) and calls InAppPurchase.instance.buyNonConsumable or buyConsumable. Create a Custom Action named restorePurchases for the required 'Restore Purchases' button (App Store requirement). Configure products: in App Store Connect → your app → In-App Purchases → add a product with identifier (e.g., com.yourapp.premium_monthly). In Google Play Console → Monetize → Subscriptions. Test using Sandbox accounts (App Store) or test accounts (Google Play).

in_app_purchase_actions.dart
1// Custom Action: initializePurchases
2// Add to Pubspec: in_app_purchase: ^3.2.0
3import 'dart:async';
4import 'package:in_app_purchase/in_app_purchase.dart';
5
6// Call once on app init or on the pricing page
7Future<void> initializePurchases(
8 Future Function(PurchaseDetails) onPurchaseSuccess,
9) async {
10 final bool available = await InAppPurchase.instance.isAvailable();
11 if (!available) {
12 // Store not available (e.g., no Google Play on emulator)
13 return;
14 }
15
16 // Listen to purchase updates
17 InAppPurchase.instance.purchaseStream.listen(
18 (List<PurchaseDetails> purchaseDetailsList) async {
19 for (final purchase in purchaseDetailsList) {
20 if (purchase.status == PurchaseStatus.purchased ||
21 purchase.status == PurchaseStatus.restored) {
22 // Verify receipt server-side via Cloud Function
23 // then deliver the entitlement
24 await onPurchaseSuccess(purchase);
25 // MUST call completePurchase to finish the transaction
26 await InAppPurchase.instance.completePurchase(purchase);
27 } else if (purchase.status == PurchaseStatus.error) {
28 // Handle error
29 print('Purchase error: ${purchase.error}');
30 }
31 }
32 },
33 );
34}
35
36// Custom Action: purchaseProduct
37Future<void> purchaseProduct(String productId) async {
38 final ProductDetailsResponse response =
39 await InAppPurchase.instance.queryProductDetails({productId});
40
41 if (response.notFoundIDs.isNotEmpty) {
42 throw Exception('Product not found: $productId');
43 }
44
45 final ProductDetails product = response.productDetails.first;
46 final PurchaseParam params = PurchaseParam(productDetails: product);
47
48 // Use buyConsumable for one-time consumables (coins, credits)
49 // Use buyNonConsumable for permanent unlocks
50 await InAppPurchase.instance.buyNonConsumable(purchaseParam: params);
51}

Expected result: Tapping 'Subscribe' on iOS/Android triggers the native App Store or Google Play purchase sheet. On successful purchase, the purchase stream fires the success callback.

4

Use Stripe PaymentIntent with CardField for in-app card entry

If you need card entry inside your app (no redirect to a browser), use Stripe's Flutter SDK with the CardField widget — this is the only PCI-compliant way to accept card numbers in a custom UI. The CardField widget is provided by the flutter_stripe package and tokenizes the card data on-device before it ever leaves the phone — your Cloud Function never sees the raw card number. In FlutterFlow: Custom Code → Pubspec Dependencies → add flutter_stripe. Create a Custom Widget that embeds the CardField. The workflow: (1) Cloud Function creates a PaymentIntent and returns the clientSecret, (2) the CardField captures the card details, (3) the Custom Action calls Stripe.instance.confirmPayment(clientSecret, params) to process the payment, (4) on success the Custom Action returns the paymentIntentId to FlutterFlow. This pattern requires FlutterFlow Pro for downloading and testing the compiled custom widget, and additional Stripe SDK configuration in the iOS and Android platform files.

Expected result: A CardField widget appears inline in your checkout page. Users enter card details and tap Pay — the payment processes in-app without a browser redirect.

5

Handle payment confirmation with a Stripe webhook

Regardless of which payment method you choose, confirm payment server-side via a webhook before granting access to paid features. Never trust the client to report its own payment status. After a Stripe Checkout payment completes, Stripe fires checkout.session.completed and payment_intent.succeeded webhooks to your Cloud Function endpoint (registered in Stripe Dashboard → Developers → Webhooks). Your Cloud Function handler: verify the Stripe-Signature header, extract the userId from session.metadata, update the user's Firestore document: subscriptionStatus: 'active', subscriptionTier: 'pro', subscriptionExpiry: (current date + 30 days for monthly). In FlutterFlow, the user's profile page has a real-time Backend Query on the users/{uid} document — when the webhook writes the update, FlutterFlow's listener sees it within 1-2 seconds and the UI updates to show the unlocked premium features automatically.

Expected result: After successful payment in Stripe's hosted checkout, the user's FlutterFlow app shows premium features unlocked within 2-3 seconds without any manual refresh.

Complete working example

Payment Implementation Decision Guide
1Payment Method Decision Framework
2====================================
3
4Digital goods / subscriptions on iOS or Android?
5 MUST USE: in_app_purchase (Apple/Google mandate)
6 Apple takes 15-30%, Google takes 15-30%
7 Set up in: App Store Connect + Google Play Console
8
9Physical goods, services, SaaS, web app?
10 USE: Stripe Checkout (simplest) OR
11 Stripe Payment Intent + CardField (custom UI)
12
13Priority: simplicity and PCI compliance delegation?
14 Stripe Checkout (recommended for most apps)
15
16Priority: in-app UX without browser redirect?
17 Stripe Payment Intent + flutter_stripe CardField
18
19Audience strongly prefers PayPal?
20 PayPal via WebView (JS SDK checkout flow)
21
22Stripe Checkout Architecture
23==============================
24FlutterFlow createCheckoutSession CF
25 Input: priceId, quantity, userId, mode
26 Output: sessionUrl
27
28FlutterFlow Launch URL (sessionUrl)
29 Stripe-hosted payment page
30 After payment redirect to successUrl
31
32Stripe stripeWebhook CF
33 Events: checkout.session.completed
34 Action: update Firestore users/{uid}
35 subscriptionStatus: 'active'
36 subscriptionTier: 'pro'
37
38FlutterFlow real-time Firestore listener
39 UI updates automatically when Firestore changes
40
41In-App Purchase Architecture
42==============================
43App Store Connect / Google Play Console
44 Create product (com.app.premium_monthly)
45 Set price and trial period
46
47FlutterFlow Custom Actions:
48 initializePurchases() listen to purchase stream
49 purchaseProduct(productId) trigger native sheet
50 restorePurchases() required for App Store
51
52Cloud Function: verifyReceipt
53 POST to Apple verifyReceipt / Google Play API
54 Update Firestore on valid receipt
55
56Stripe Test Cards
57==================
58Success: 4242 4242 4242 4242
59Declined: 4000 0000 0000 0002
603DS required: 4000 0025 0000 3155
61Expiry: any future date, CVC: any 3 digits

Common mistakes when creating Custom Payments in FlutterFlow

Why it's a problem: Building a custom HTML input form for card number entry in the Flutter app instead of using Stripe's CardField widget

How to avoid: Use Stripe's flutter_stripe package and embed the CardField widget in your Custom Widget. CardField never exposes the raw card number to your app code — it returns a PaymentMethod token. This qualifies you for PCI SAQ A-EP, the simplest compliance path. Alternatively, use Stripe Checkout which gives you PCI SAQ A, the simplest of all.

Why it's a problem: Selling digital subscriptions or in-app content using Stripe Checkout instead of Apple/Google in-app purchases in an app distributed on the App Store or Google Play

How to avoid: Use the in_app_purchase Flutter package for any digital goods or subscriptions in apps distributed on App Store or Google Play. Stripe is allowed for physical goods (merchandise, food delivery), services (booking appointments), and in-app features that do not constitute 'digital content' under Apple/Google's definitions.

Why it's a problem: Granting premium access client-side immediately after the in-app purchase callback without server-side receipt verification

How to avoid: Send the purchase receipt (verificationData from PurchaseDetails) to a Cloud Function that calls Apple's App Store receipt verification endpoint or Google Play Developer API to validate the purchase before writing subscriptionStatus: 'active' to Firestore. Only grant access after the server confirms the receipt is genuine.

Best practices

  • Use Stripe's pre-built components (Checkout, CardField) rather than building custom payment UIs — they handle PCI compliance, accessibility, and localization automatically
  • Always verify payment success server-side via Stripe webhooks before granting premium access — never trust client-reported payment status
  • Store your Stripe secret key in Firebase Functions config only — never in FlutterFlow API Manager, environment variables, or source code
  • Use Stripe's test mode and test card numbers extensively before switching to live keys — test the full flow including webhook delivery
  • For in-app purchases, always implement and test Restore Purchases — Apple requires this button and users on new devices need it to recover purchased content
  • Show clear pricing before the payment step — unexpected price reveals cause cart abandonment and chargebacks
  • Handle payment failures gracefully with user-friendly error messages: 'Your card was declined — please check the card number and expiry date' is better than 'Error 402'

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a FlutterFlow app with Firebase and want to add Stripe Checkout for one-time payments. Write a Firebase Cloud Function in Node.js that creates a Stripe Checkout session with a priceId, quantity, userId in metadata, successUrl, and cancelUrl. Also write the Stripe webhook handler Cloud Function that listens for checkout.session.completed, verifies the signature, and updates the user's Firestore document with subscriptionStatus: 'active' and the tier from the product metadata.

FlutterFlow Prompt

Add a 'Get Pro' button on the Pricing page that calls the createCheckoutSession Cloud Function API with the monthly subscription price ID. On success, take the sessionUrl from the API response and use a Launch URL action to open Stripe's hosted checkout. After the user returns from checkout, show the updated subscription status from the Firestore user document.

Frequently asked questions

Does Apple take a cut of Stripe payments in my iOS app?

Apple only takes a cut (15-30%) of digital goods and subscriptions processed through their In-App Purchase system. Physical goods, services, and B2B SaaS are generally exempt. If you use Stripe Checkout for selling a physical product or a service (like a consulting session or delivery), Apple does not take a cut and you pay only Stripe's 2.9% + 30 cents. However, if you sell premium app features, virtual currency, or subscriptions to digital content and distribute on the App Store, you must use IAP and Apple takes their percentage.

How do I handle recurring subscriptions with Stripe?

Use Stripe Checkout in 'subscription' mode: set mode: 'subscription' in the createCheckoutSession call and use a recurring price ID (created in Stripe Dashboard → Products → Add product → pricing: recurring). Stripe handles all subsequent charges automatically. For cancellations: create a Cloud Function that calls stripe.subscriptions.cancel(subscriptionId) or use Stripe's hosted Customer Portal (stripe.billingportal.sessions.create) where users manage their own subscriptions. Set up webhook handlers for customer.subscription.updated and customer.subscription.deleted to update Firestore accordingly.

How do I show a paywall that gates premium features?

In FlutterFlow, read the user's Firestore document (users/{uid}) which has the subscriptionStatus and subscriptionTier fields set by your webhook handler. On gated pages or features, add a Conditional Widget: if subscriptionStatus == 'active' show the premium content, else show the paywall/upgrade prompt. Since this is backed by Firestore data, not a client-side flag, it cannot be bypassed by modifying app variables. The Firestore security rules allow users to read only their own document.

What is the difference between a PaymentIntent and a Checkout Session?

A CheckoutSession creates Stripe's hosted payment page — you get a URL and redirect the user there. Stripe handles the UI, PCI compliance, and all payment methods. A PaymentIntent is a lower-level object representing a payment to be confirmed — you build your own UI (using CardField) and confirm the intent client-side. CheckoutSession is simpler and handles more automatically. PaymentIntent gives you full UI control. For most FlutterFlow apps, CheckoutSession is the right choice.

Can I accept payments in multiple currencies?

Yes — Stripe supports 135+ currencies. In your createCheckoutSession Cloud Function, set currency on the line item or use Stripe's automatic currency conversion. Alternatively, create separate prices in different currencies in Stripe Dashboard and pass the appropriate priceId based on the user's location (from their device locale via Global Properties). Stripe displays the price in the user's currency on the hosted checkout page automatically.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.