Stripe Payments in Flutter: A Practical Integration Guide
A hands-on guide to taking card payments in Flutter with flutter_stripe — setup on Android and iOS, PaymentSheet flow, test cards, and security rules for handling card data.
Published on • October 29, 2026
AI Assistant

Card payments sit at the worst intersection in mobile development: one mistake and you’re handling regulated data you never wanted to see. The good news is that Flutter’s ecosystem has a mature, official-feeling path through it. The flutter_stripe package (14.2.0 at time of writing, published by the verified flutterstripe.io publisher) gives you Stripe’s native PaymentSheet, Apple Pay, Google Pay, SCA-ready 3D Secure, and PCI-compliant card collection on Android and iOS.
This guide walks through the whole integration: installing the package, configuring the publishable key and platform setup, collecting a card payment with PaymentSheet, testing with test cards, and the security do’s and don’ts that keep you out of trouble.
Before you start: where the money actually moves
Two architectural rules drive everything below:
- Your server creates the PaymentIntent; the app only confirms it. The secret key never ships in a binary.
- Card data goes directly to Stripe. The package’s stated design goal is that “sensitive data is sent directly to Stripe instead of passing through your server” — which is what keeps you inside PCI-compliant boundaries.
Also check the platform policy angle before you build: the flutter_stripe README notes that if you sell digital goods or services inside your app (subscriptions, in-game currency, premium content), Apple and Google guidelines generally require their in-app purchase APIs instead (with US developers treated differently under a 2025 court ruling). Stripe is for physical goods, services, and other allowed scenarios.
Install
dart pub add flutter_stripe
That adds flutter_stripe plus the federated stripe_android / stripe_ios implementation packages.
Configure the publishable key
Only publishableKey is required (stripeAccountId, threeDSecureParams, and merchantIdentifier are optional). Set it early, before any payment UI:
import 'package:flutter_stripe/flutter_stripe.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Stripe.publishableKey = stripePublishableKey; // pk_test_... while developing
runApp(const App());
}
Use test-mode keys while developing and live keys only when you publish.
Android setup
The Stripe Android SDK has hard requirements — miss one and PaymentSheet fails at runtime:
- Android 5.0 (API level 21)+, Kotlin 1.8.0+, Android Gradle Plugin 8+.
- Your activity must extend
FlutterFragmentActivityinstead ofFlutterActivity, because the SDK needs the Support Fragment Manager:
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()
- Use a theme that is a descendant of
Theme.AppCompat(both normal andvalues-nightvariants). - Add the ProGuard rules from the README to
proguard-rules.pro:
-dontwarn com.stripe.android.pushProvisioning.**
-dontwarn com.google.android.gms.tapandpay.**
-dontwarn kotlinx.parcelize.Parceler$DefaultImpls
-dontwarn kotlinx.parcelize.Parceler
-dontwarn kotlinx.parcelize.Parcelize
# Keep Stripe classes
-keep class com.stripe.** { *; }
- Rebuild the app — these changes do not apply through hot reload.
iOS setup
- Target iOS 13+ (
platform :ios, '13.0'in the Podfile, orIPHONEOS_DEPLOYMENT_TARGETin Xcode). - For card scanning, add
NSCameraUsageDescriptiontoInfo.plist. - For redirect-based methods (iDEAL, PayPal, Link), register a URL scheme shared by
CFBundleURLSchemes,Stripe.urlScheme, and your PaymentSheetreturnURLso the SDK can receive the callback.
Create the PaymentIntent on your server
Your backend talks to the Stripe API with the secret key and returns only the client secret (plus customer/ephemeral key details if you use saved cards). Stripe’s docs require endpoints on your server that use an official Stripe library — the underlying call is a POST /v1/payment_intents, which looks like this with Dart’s http package:
// Server-side endpoint (shelf / dart_frog / your backend of choice)
final response = await http.post(
Uri.parse('https://api.stripe.com/v1/payment_intents'),
headers: {'Authorization': 'Bearer $stripeSecretKey'}, // server-only secret key
body: {
'amount': '1499', // $14.99 — smallest currency unit
'currency': 'usd',
},
);
final clientSecret = jsonDecode(response.body)['client_secret'] as String;
return {'paymentIntent': clientSecret};
The app fetches this from your endpoint — never from Stripe directly.
Collect the payment with PaymentSheet
flutter_stripe offers three ways to take cards — PaymentSheet, CardField, and CardForm — but the README is explicit: Stripe is sunsetting CardField and CardForm in favor of PaymentSheet, which is recommended for all new integrations. It ships localization, animations, and error handling out of the box, plus Apple Pay and Google Pay.
Initialize once, before presenting:
Future<void> initPaymentSheet() async {
try {
// 1. PaymentIntent client secret from YOUR server
final data = await fetchPaymentIntentClientSecret();
// 2. Initialize
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: data['paymentIntent'],
merchantDisplayName: 'Flutter Stripe Store Demo',
customerId: data['customer'],
customerEphemeralKeySecret: data['ephemeralKey'],
returnURL: 'flutterstripe://redirect',
primaryButtonLabel: 'Pay now',
applePay: PaymentSheetApplePay(merchantCountryCode: 'US'),
googlePay: PaymentSheetGooglePay(
merchantCountryCode: 'US',
testEnv: true, // keep true while testing
),
style: ThemeMode.dark,
),
);
} catch (e) {
rethrow;
}
}
Then confirm from your checkout button:
Future<void> confirmPayment() async {
try {
await Stripe.instance.presentPaymentSheet();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment successfully completed')),
);
} on StripeException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error from Stripe: ${e.error.localizedMessage}')),
);
}
}
The SDK performs native 3D Secure authentication automatically when Strong Customer Authentication requires it — you don’t build that flow yourself.
Handle success server-side. Wait for the payment_intent.succeeded webhook rather than trusting the client callback: a user can close the app before the client ever reports back. Fulfil orders from the webhook, not from presentPaymentSheet() returning.
Test with test cards
Test in test mode with test API keys. From Stripe’s testing docs:
| Scenario | Card number | Details |
|---|---|---|
| Successful Visa payment | 4242 4242 4242 4242 | Any future date (e.g. 12/34), any 3-digit CVC |
| Successful Mastercard | 5555 5555 5555 4444 | Any future date, any 3-digit CVC |
| Generic decline | 4000 0000 0000 0002 | Returns card_declined / generic_decline |
| Insufficient funds | 4000 0000 0000 9995 | Returns insufficient_funds |
| In API code | pm_card_visa | Prefer PaymentMethod tokens over raw numbers server-side |
Rules worth memorizing:
- Never use real card details. Stripe’s Services Agreement prohibits testing in live mode with real payment method data.
- Use a valid future expiry and a three-digit CVC (four for Amex).
- Test error paths too — declines, insufficient funds, and 3DS challenges are where checkout UX usually breaks.
- In server-side test code, prefer
pm_card_visa-style PaymentMethods over card numbers; raw numbers in server code can create PCI scope when you go live.
Security do’s and don’ts
Do:
- Keep the secret key on the server, in environment variables or a secrets vault — never in the repo or app bundle.
- Let
flutter_stripehandle card entry via Stripe’s native UI, so raw card data flows directly to Stripe. - Use
Stripe.publishableKey(safe to embed) in the app, and swap to live keys only at release. - Fulfil orders from webhooks (
payment_intent.succeeded), verified server-side. - Set
testEnv: truein Google Pay params until you ship.
Don’t:
- Don’t read, log, store, or transmit PAN/CVC in your own code or backend — that’s the fastest route to losing PCI compliance.
- Don’t commit live keys in
.envfiles checked into version control. - Don’t treat the client’s “payment succeeded” message as proof of payment.
- Don’t ignore platform rules: digital goods inside the app generally belong to StoreKit/Google Play Billing.
Conclusion & next steps
You’ve wired the full loop: dart pub add flutter_stripe, publishable key at startup, FlutterFragmentActivity and AppCompat on Android, iOS 13+ target, a server-created PaymentIntent, initPaymentSheet + presentPaymentSheet in the app, webhook-based fulfilment, and test cards for every scenario.
Next steps: add saved cards with customerEphemeralKeySecret, enable Apple Pay/Google Pay wallets, run the package’s example app (cp lib/.env.example.dart lib/.env.dart) against your own test keys, and add stripe listen --forward-to localhost:4242/webhook during local development.