Skip to content
Flutter+Stripe
Stack Integration

Flutter + Stripe Payments Integration

Adding Stripe to a Flutter app requires a backend to create PaymentIntents — the secret key never touches the client. The flutter_stripe package handles the native payment sheet.

Use Cases
  1. Native payment sheet (Apple Pay, Google Pay, card)
  2. Subscription billing with Stripe + Firebase/Supabase
  3. One-time purchases and in-app payments
  4. Payment status synced to backend via webhooks
Implementation

Flow: Flutter calls your backend → backend creates PaymentIntent with Stripe secret key → returns client_secret → Flutter presents payment sheet. Never pass the Stripe secret key to the Flutter app. Use flutter_stripe package for the native payment sheet UI.

In detail

How the pieces connect

The split is strict: the Stripe secret key lives only on your server, the flutter_stripe package runs only on the device. On startup you set Stripe.publishableKey and, for Apple Pay, Stripe.merchantIdentifier. When the user checks out, Flutter hits your backend endpoint, which calls Stripe's API (stripe.paymentIntents.create) with the amount, currency, and customer id, and returns the client_secret to the app. Flutter passes that secret to Stripe.instance.initPaymentSheet(...) then presentPaymentSheet(), which renders the native sheet with card, Apple Pay, and Google Pay. The amount is fixed server-side, so a tampered client can't change what's charged. The server is also where you confirm the final state — never trust the client's success callback as the record of truth.

Webhooks and idempotency

presentPaymentSheet() resolving without an error means the sheet closed, not that the money settled — 3D Secure, async methods, and disputes all land later. Treat the Stripe webhook as the authoritative signal. Stand up an endpoint that verifies the signature with stripe.webhooks.constructEvent(rawBody, sig, endpointSecret) using the raw request body; any JSON middleware that re-serializes the payload breaks the HMAC check. Act on payment_intent.succeeded (and invoice.paid for subscriptions) to flip your order or entitlement state. Stripe retries deliveries, so handlers must be idempotent — key writes on the PaymentIntent or event id and ignore duplicates. For subscriptions, customer.subscription.updated / deleted keep plan status in sync; don't infer it from the app.

Secrets, keys, and platform setup

Only the publishable key (pk_...) belongs in the Flutter binary; the secret key (sk_...) and the webhook signing secret stay on the server in environment config. Use restricted test keys in dev and rotate if a secret ever ships in a build or log. Native setup is easy to miss: on iOS the Stripe SDK requires a deployment target of iOS 13 or higher in the Podfile, and Apple Pay needs the merchant id and the Apple Pay capability in Xcode. On Android, flutter_stripe needs your MainActivity to extend FlutterFragmentActivity (not FlutterActivity) or the payment sheet crashes. Google Pay test cards only work once your Stripe account and the right environment flag are configured.

When this combo fits

Flutter plus Stripe fits cross-platform apps that need a polished, PCI-friendly checkout without building card UI — the native sheet handles card entry, wallets, and SCA. It does require a backend you control to mint PaymentIntents and process webhooks; a pure client-only app can't do payments safely. If you're already on Firebase or Supabase, a single Cloud Function or Edge Function covers both the intent-creation and webhook endpoints. BookBed, a booking SaaS I built on Flutter, Firebase, and Stripe, runs exactly this shape — server-created intents, the native sheet on device, and webhooks writing booking and payment state back to Firestore. The pattern delivers efficiently because the moving parts are well-defined and don't need reinvention per project.

Live ExampleFlutterFlow Templates — Case Study
Other integration guidesView all →
Related

Need this built?