Flutter

How to Add Stripe to Your Flutter App Without Leaking Secret Keys

Adding payment processing to a mobile app sounds daunting. Stripe's official Flutter SDK promises to handle the hard parts—the secure card input, the compliance overhead, the UI complexity. But here's the catch that trips up plenty of developers: you cannot store secret keys in your Flutter code. Getting that wrong means opening your app to fraudulent transactions. This guide walks through the exact setup, the secure flow, and what to watch before you go live.

At a Glance

  • Official flutter_stripe package handles UI and security on Android and iOS
  • Payment intent must be created on your backend server
  • Never expose your secret key in mobile app code
  • Test mode available before processing real payments
  • Webhook validation ensures reliable payment confirmation

Setting Up Your Stripe Account First

Before touching your Flutter project, collect your credentials from the Stripe dashboard. You need three items: a publishable key, a client secret, and a payment intent generated server-side.

The publishable key initializes the SDK in your app:

“`

Stripe.publishableKey = "YOUR_PUBLISHABLE_KEY";

“`

Your secret key stays on the backend. If you paste it into Flutter code, anyone can extract it and start creating payment intents on your Stripe account. That is not a hypothetical risk—it happens regularly when developers rush to ship.

Building the Payment Flow

The user interface can be minimal. A single button triggers everything:

“`

ElevatedButton(

OnPressed: makePayment,

Child: Text("Make Payment"),

)

“`

The actual logic involves two steps. First, initialize the payment sheet with parameters from your backend:

“`

Stripe.instance.initPaymentSheet(

PaymentSheetParameters: SetupPaymentSheetParameters(

PaymentIntentClientSecret: "…",

MerchantDisplayName: "Your Business Name",

),

);

“`

Then present it to the user:

“`

Await Stripe.instance.presentPaymentSheet();

“`

The SDK shows a native form that handles card validation, error states, and 3D Secure authentication. Your app receives a result indicating success or failure. Handle both cases in your UI—never assume the payment went through without checking.

Why This Approach Works

Stripe tokenizes card data client-side. Raw card numbers never touch your servers or your app code. That matters because PCI compliance becomes dramatically simpler when you are not storing sensitive payment information. For small teams, this means fewer security audits and less custom code to maintain.

The tradeoff is dependency on Stripe's UI flow. If you need a highly customized payment experience—branded cards, embedded flows, complex multi-currency logic—the SDK might eventually limit you. For most apps though, the built-in sheet strikes the right balance between security and flexibility.

A Worked Example

Imagine you are building a small business invoice app. Customers should pay directly from a notification. When an invoice is sent, your backend creates a payment intent and sends the client secret to the Flutter app via push notification. The customer taps "Pay Now," your app initializes the payment sheet with that secret, presents it, and waits. On success, your backend marks the invoice paid via webhook. On failure, the customer sees the error and can retry. No card data ever touches your servers.

Going Live: What to Watch

Switch from test mode to production credentials before shipping. Use Stripe's test card numbers to simulate declined cards, insufficient funds, and 3D Secure challenges. Validate that your webhook endpoint reliably receives payment events—if the user closes the app before confirmation, you need that webhook to update your records.

Bottom line: the Flutter Stripe SDK handles payment UI securely and keeps your app out of PCI scope. The discipline is keeping secrets server-side and testing the full flow before launch.

Key takeaways

  • Initialize Stripe with your publishable key; keep secret keys on the backend at all times
  • Payment intent creation must happen server-side, never in the Flutter app
  • Present the payment sheet after initialization; the SDK handles card input securely
  • Test thoroughly in Stripe test mode before processing real transactions
  • Validate webhook flow so your backend captures payment events reliably

Frequently asked questions

Where should I create the payment intent?

Create it on your backend server. Never include your secret key in a mobile app or frontend code.

What does the payment sheet require to display?

It needs the client secret from your payment intent, a merchant display name, and optionally theme styling.

How do I test payments without using real money?

Enable test mode in your Stripe dashboard and use Stripe's published test card numbers to simulate various outcomes safely.

What happens if a payment fails?

The presentPaymentSheet call returns a result your app can check. Show appropriate error feedback and let the user retry.

What must I verify before going to production?

Switch to live credentials, test the full flow with Stripe test cards, and confirm your webhook endpoint receives payment events correctly.

Related