Learn Apple Pay configuration, Stripe PaymentSheet integration, secure backend payment creation, transaction verification, testing, and production best practices.
Category: Flutter Development · Updated: October 2026
Apple Pay provides a convenient checkout experience for customers using supported Apple devices. For Flutter developers, integrating Apple Pay involves more than displaying a payment button. You must configure Apple's merchant capabilities, integrate a payment provider, securely create transactions on the backend, and verify payment results before fulfilling orders.
In this guide, we will integrate Apple Pay into a Flutter application using Stripe's Flutter SDK and PaymentSheet. We will cover the iOS configuration, Dart implementation, backend integration, error handling, security, and testing strategy required for a production-oriented payment workflow.
- How Apple Pay works in a Flutter application.
- How to configure an Apple Merchant ID and Xcode capability.
- How to integrate Apple Pay using Stripe PaymentSheet.
- How to create PaymentIntents securely on a backend.
- How to handle cancellations, failures, and successful payments.
- How to test and prepare the integration for release.
Table of Contents
- Understanding Apple Pay in Flutter
- Payment architecture and transaction flow
- Prerequisites and requirements
- Configure Apple Pay and Merchant ID
- Install and configure Stripe
- Create a secure backend PaymentIntent
- Implement Apple Pay in Flutter
- Verify payment completion
- Handle errors and cancellations
- Testing and debugging
- Security and production best practices
- Frequently asked questions
1. Understanding Apple Pay in Flutter
Apple Pay is Apple's digital wallet and payment experience. Customers can use eligible cards stored in Apple Wallet to authorize purchases without manually entering card details during checkout.
Flutter does not provide a built-in, provider-independent Apple Pay payment processor. A production integration typically uses a payment service provider, such as Stripe, together with Apple's native payment capabilities.
Apple Pay
Provides the wallet interface and payment authorization experience on supported Apple devices.
Stripe SDK
Connects the Flutter application to Stripe's payment flow and presents the configured payment interface.
Backend
Calculates the order amount, creates payment requests, and verifies payment status before fulfillment.
Important: Apple Pay is a payment method, not a replacement for your payment processor or backend. This tutorial uses Stripe; the exact setup differs if you use another provider.
2. Understand the Payment Architecture
The following diagram shows the main components involved in a typical Flutter and Stripe integration.
Customer taps Apple Pay
Native payment experience
Customer authenticates the payment
PaymentIntent and payment status
Payment lifecycle
Step 1 — Create an order
The application sends the cart or order reference to your backend.
Step 2 — Create a PaymentIntent
The backend validates the order and calculates the amount using trusted server-side data.
Step 3 — Present Apple Pay
The Flutter SDK presents PaymentSheet with Apple Pay enabled.
Step 4 — Process the payment
The payment provider processes the transaction and determines its status.
Step 5 — Verify and fulfill
Your backend uses a verified payment event or provider status to mark the order paid and trigger fulfillment.
This separation is important because closing the payment sheet or receiving a client-side success callback should not, by itself, be the authority for shipping goods, activating subscriptions, or granting paid access.
3. Prerequisites and Requirements
Before starting, make sure you have:
- A Flutter project with an iOS target.
- An Apple Developer account with the appropriate permissions to configure Apple Pay.
- An Apple Merchant ID and the Apple Pay capability enabled for your app.
- A Stripe account with Apple Pay configured for your integration.
- A backend capable of creating Stripe PaymentIntents securely.
- A supported Apple Pay test environment and an eligible device or simulator configuration.
The current flutter_stripe package supports Apple Pay on iOS. Check its package requirements and current setup documentation before choosing your minimum iOS deployment target.
Official references:
4. Configure Apple Pay and Merchant ID
Apple Pay requires merchant configuration before the application can use the native payment experience. Follow these steps for an iOS app using Stripe.
Step 1: Create a Merchant ID
- Sign in to your Apple Developer account.
- Open Certificates, Identifiers & Profiles.
- Choose Identifiers and create a new Merchant ID.
- Use a unique identifier, for example
merchant.com.example.store. - Save the Merchant ID and use the same value consistently in your Apple Pay and Flutter configuration.
Step 2: Enable Apple Pay in Xcode
- Open the iOS workspace in Xcode.
- Select your application target.
- Open Signing & Capabilities.
- Click + Capability and add Apple Pay.
- Select your registered Merchant ID.
- Confirm that your development team and signing configuration are correct.
For a Flutter project, the workspace is normally located at ios/Runner.xcworkspace.
Step 3: Configure Apple Pay in Stripe
Follow Stripe's current native Apple Pay setup guide to register your app and configure the required payment processing certificate. Use the certificate signing request provided by Stripe where its documented workflow requires it.
Also confirm that Apple Pay is enabled and available for your Stripe account, region, currency, and intended transaction type.
Read Stripe's Apple Pay integration guide.
5. Install and Configure Stripe in Flutter
Add the official Flutter Stripe package:
flutter pub add flutter_stripe
Use a version compatible with your Flutter SDK and iOS deployment target. The code below illustrates the PaymentSheet API; confirm exact API signatures against the package version resolved in your project.
Initialize Stripe
Configure Stripe before starting the application. The publishable key can be included in the client configuration; the secret API key must remain on the backend.
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
Stripe.publishableKey = 'pk_test_REPLACE_WITH_YOUR_KEY';
Stripe.merchantIdentifier =
'merchant.com.example.store';
await Stripe.instance.applySettings();
runApp(const MyApp());
}
Replace both example values with the correct configuration for your application. Use your production publishable key in production builds and test keys in test environments. Never put a Stripe secret key in Flutter source code, assets, or remotely delivered client configuration.
6. Create a Secure Backend PaymentIntent
A production payment flow requires a server endpoint that creates the PaymentIntent. The client should send an order identifier or cart details, while the server calculates the final price using trusted product and pricing data.
Here is a simplified Node.js example using Stripe's official server SDK. It demonstrates the essential flow, not a complete production checkout service.
import express from 'express';
import Stripe from 'stripe';
const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
app.use(express.json());
app.post('/api/payments/create-intent', async (req, res) => {
try {
const { orderId } = req.body;
// Authenticate the user and load the order from your database.
// Validate ownership, inventory, prices, tax, and currency.
// Never trust a total amount supplied by the mobile client.
const order = await loadAndValidateOrder(orderId);
if (!order) {
return res.status(400).json({
error: 'Invalid order'
});
}
const paymentIntent = await stripe.paymentIntents.create({
amount: order.totalAmountInMinorUnits,
currency: order.currency.toLowerCase(),
automatic_payment_methods: {
enabled: true
},
metadata: {
orderId: order.id
}
}, {
idempotencyKey: `order-${order.id}`
});
return res.json({
clientSecret: paymentIntent.client_secret
});
} catch (error) {
console.error('PaymentIntent creation failed');
return res.status(500).json({
error: 'Unable to initialize payment'
});
}
});
app.listen(3000);
Important: loadAndValidateOrder() is an application-specific placeholder. Implement it using your database and authentication system. Also implement authorization, request validation, idempotency appropriate to your retry model, and safe error handling before production use.
The example uses a PaymentIntent and returns its client secret to the authenticated customer for the corresponding checkout. Never return your Stripe secret API key to the app.
Why use a PaymentIntent?
A PaymentIntent represents the payment lifecycle for a particular transaction. It allows the provider to handle the relevant authentication and payment status transitions. Your backend can then use verified provider events to determine when an order is paid.
7. Implement Apple Pay in Flutter
With the backend endpoint available and Apple Pay configured, the Flutter application can initialize PaymentSheet and present the native payment interface.
Step 1: Fetch the client secret from your backend
The following example uses the http package for a simple backend request. Add it to your project with flutter pub add http.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<String> createPaymentIntent({
required String orderId,
}) async {
final response = await http.post(
Uri.parse(
'https://api.example.com/api/payments/create-intent',
),
headers: {
'Content-Type': 'application/json',
// Add your real authentication token here.
// Do not hard-code a user's token.
},
body: jsonEncode({
'orderId': orderId,
}),
);
if (response.statusCode != 200) {
throw Exception('Unable to initialize payment');
}
final body = jsonDecode(response.body)
as Map<String, dynamic>;
final clientSecret = body['clientSecret'];
if (clientSecret is! String || clientSecret.isEmpty) {
throw Exception('Invalid payment response');
}
return clientSecret;
}
Replace the example URL with your HTTPS backend endpoint. Add your application's authentication mechanism, request cancellation or timeout policy where appropriate, and safe logging.
Step 2: Initialize PaymentSheet with Apple Pay enabled
Create a payment service that retrieves the client secret and configures the payment sheet.
import 'package:flutter_stripe/flutter_stripe.dart';
class ApplePayService {
Future<void> preparePayment({
required String orderId,
}) async {
final clientSecret = await createPaymentIntent(
orderId: orderId,
);
await Stripe.instance.initPaymentSheet(
paymentSheetParameters:
SetupPaymentSheetParameters(
paymentIntentClientSecret: clientSecret,
merchantDisplayName: 'Example Store',
applePay: const PaymentSheetApplePay(
merchantCountryCode: 'US',
),
),
);
}
Future<void> presentPaymentSheet() async {
await Stripe.instance.presentPaymentSheet();
}
}
The country code in PaymentSheetApplePay represents the merchant's country, not automatically the customer's location. Configure it to match your business and Stripe account requirements. Confirm supported parameters for your installed SDK version.
Step 3: Connect the payment service to a checkout button
The following minimal screen demonstrates how to prepare and present PaymentSheet. In a real shopping app, obtain the order ID from your checkout state rather than hard-coding it.
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
class CheckoutScreen extends StatefulWidget {
const CheckoutScreen({super.key});
@override
State<CheckoutScreen> createState() =>
_CheckoutScreenState();
}
class _CheckoutScreenState extends State<CheckoutScreen> {
bool isLoading = false;
String message = '';
final paymentService = ApplePayService();
Future<void> pay() async {
if (isLoading) return;
setState(() {
isLoading = true;
message = '';
});
try {
await paymentService.preparePayment(
orderId: 'ORDER_ID_FROM_YOUR_CHECKOUT',
);
await paymentService.presentPaymentSheet();
if (!mounted) return;
setState(() {
message =
'Payment flow completed. Verifying order status...';
});
// Fetch the order status from your backend.
// Do not fulfill the order based only on this callback.
} on StripeException catch (error) {
if (!mounted) return;
setState(() {
message = error.error.localizedMessage ??
'The payment could not be completed.';
});
} catch (_) {
if (!mounted) return;
setState(() {
message = 'Unable to start the payment. Please try again.';
});
} finally {
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Checkout')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.shopping_bag_outlined,
size: 60,
),
const SizedBox(height: 16),
const Text(
'Secure checkout',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isLoading ? null : pay,
child: isLoading
? const CircularProgressIndicator()
: const Text('Continue to Apple Pay'),
),
),
if (message.isNotEmpty) ...[
const SizedBox(height: 16),
Text(message),
],
],
),
),
),
);
}
}
This example uses an ordinary Flutter button to launch PaymentSheet. The native payment interface is presented by the Stripe SDK. If you want to display Apple's branded Apple Pay button directly, follow the provider's supported Apple Pay button integration and Apple's interface guidelines rather than drawing a custom imitation.
8. Verify Payment Completion
A reliable payment integration must distinguish the customer's interaction with the payment sheet from the authoritative payment state held by the provider.
For Stripe, configure a webhook endpoint on your backend and verify the webhook signature using Stripe's official SDK. For a PaymentIntent-based flow, commonly relevant events include:
payment_intent.succeeded— the payment succeeded.payment_intent.payment_failed— the payment failed.payment_intent.canceled— the payment was canceled.
Process events idempotently so that receiving the same event more than once does not fulfill the order multiple times. Confirm that the PaymentIntent matches the expected order, amount, and currency.
Example webhook structure in Node.js:
app.post(
'/api/stripe/webhook',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (_) {
return res.status(400).send('Invalid webhook signature');
}
if (event.type === 'payment_intent.succeeded') {
const paymentIntent = event.data.object;
// Load the order using trusted server-side data.
// Verify amount, currency, and payment-to-order mapping.
// Process the event idempotently.
// Mark the order paid only after validation.
}
return res.json({ received: true });
}
);
Configure this route before any JSON body-parsing middleware that would consume the raw webhook request body. Implement the omitted database operations, duplicate-event protection, and order validation before using the endpoint in production.
Official reference: Stripe webhooks documentation.
9. Handle Errors and Cancellations
Payment failures should be handled gracefully. Avoid presenting every error as a generic payment decline because different problems require different responses.
| Scenario | Recommended handling |
|---|---|
| Customer cancels | Return to checkout without marking the order paid. |
| Payment declined | Display a safe message and let the customer retry with an eligible payment method. |
| Network timeout | Check order status before retrying to avoid duplicate orders or charges. |
| Apple Pay unavailable | Offer another supported payment method when available. |
| Payment pending | Show a pending state and wait for the verified backend result. |
10. Testing and Debugging Apple Pay
Do not wait until production to test payment failures and cancellation behavior. Create a test plan covering the following scenarios.
- Apple Pay appears when the device and payment configuration support it.
- The customer can complete an authorized test payment.
- The customer can cancel the payment sheet.
- Declined payments show an appropriate message.
- Network interruptions do not cause duplicate order fulfillment.
- Webhook events update order status correctly and only once.
- Other payment methods continue to work if Apple Pay is unavailable.
Use Stripe's test mode and follow its current Apple Pay testing instructions. Availability can depend on device configuration, wallet setup, region, and the testing environment. Validate the complete flow on supported physical devices before release.
Common problems
11. Security and Production Best Practices
- Keep secrets on the server. Never bundle Stripe secret API keys in a Flutter application.
- Calculate prices on the backend. Do not trust the amount supplied by the client.
- Verify webhook signatures. Do not trust arbitrary requests claiming a payment succeeded.
- Use idempotency. Protect order creation and payment processing from duplicate requests.
- Validate every transaction. Match the payment, order, amount, currency, and customer to trusted records.
- Use HTTPS. Protect client-to-server communication and apply appropriate authentication and authorization.
- Log safely. Never log payment credentials, secret keys, or unnecessary sensitive customer information.
- Review store policies. Digital goods and physical goods can have different App Store payment requirements. Check current rules for your product and distribution region.
- Plan for certificate maintenance. Track applicable certificate expiration and renewal requirements for your provider setup.
For digital content or features, Apple may require in-app purchase mechanisms depending on the transaction and applicable storefront rules. Apple Pay is generally intended for eligible purchases of physical goods and services. Review the current App Store Review Guidelines before choosing your payment architecture.
12. Frequently Asked Questions
Can I integrate Apple Pay in Flutter without Stripe?
Yes. You can use another supported payment service provider or build a native integration with Apple's payment APIs. The implementation, certificates, token handling, and backend requirements depend on your chosen provider.
Does Apple Pay work on Android?
Apple Pay is Apple's wallet experience for supported Apple devices. For Android customers, consider a suitable payment option such as Google Pay where supported by your payment provider.
Do I need a backend to integrate Apple Pay?
For a production Stripe PaymentIntent integration, you need a trusted server to create payment requests using secret credentials, validate orders, and verify payment status. The mobile app must not contain the secret API key.
Is PaymentSheet the same as a custom Apple Pay button?
No. PaymentSheet is a provider-managed checkout interface that can offer Apple Pay when configured and supported. A dedicated Apple Pay button is a separate UI integration. Choose the approach supported by your provider and follow Apple's interface guidelines.
How can I know whether a payment really succeeded?
Use verified payment status from your payment provider and backend, commonly through signed webhooks and server-side transaction checks. A client callback alone should not be the final authority for fulfillment.
Can I test Apple Pay without charging real money?
Use your payment provider's test environment and follow its current Apple Pay testing procedure. Confirm the supported device and wallet requirements before testing.
Conclusion
Integrating Apple Pay into Flutter requires coordination between Apple's merchant configuration, the Flutter SDK, a payment provider, and a secure backend.
By using Stripe PaymentSheet, you can simplify the client-side checkout experience while keeping payment creation and verification on the server. A robust implementation also handles cancellations, network failures, duplicate requests, and asynchronous payment updates.
Before launching, test the entire transaction lifecycle, verify your Apple Pay setup, and review the latest provider documentation and App Store requirements.
Enjoyed this Flutter payment tutorial?
Explore the official documentation to build a secure and reliable payment experience for your next Flutter project.
Explore Stripe Apple Pay DocsOfficial References
- Flutter Stripe package and API
- Stripe Apple Pay integration for iOS
- Stripe PaymentIntents
- Stripe webhooks
- Apple Developer: Setting up Apple Pay
- Apple App Store Review Guidelines
Disclaimer: This article is for educational purposes. Payment provider APIs, Apple Pay requirements, regional availability, and App Store policies can change. Verify the current official documentation and have your payment implementation reviewed before deploying to production.
Comments
Post a Comment