Apple Pay Integration (Flutter)
This section explains how to integrate Apple Pay using the Novalnet Flutter SDK.
Prerequisites
Before integrating Apple Pay, ensure the following:
- Flutter SDK 3.0 or later
- Dart SDK 3.0 or later
- Apple Developer account is created
- Merchant ID is created in Apple Developer account
- Apple Pay capability is enabled for your app
- Valid payment processing certificate is generated
- Device supports Apple Pay and has a card added
- App is tested on a real iOS device (Apple Pay does not work on simulator)
Step 1: Configure Apple Pay on Merchant Side (Apple Developer Account)
The merchant must configure the following in the Apple Developer portal:
- Merchant ID – Unique identifier for Apple Pay (e.g., merchant.com.yourapp)
- Payment Processing Certificate – Required to securely process payments
- Apple Pay Capability – Must be enabled in your app ID
- Bundle ID – Your iOS app identifier (must match your app)
Create merchant identifiers:
A merchant identifier uniquely identifies you as a merchant who is able to accept Apple Pay payments.
To create merchant identifiers:
- Log in to your Apple Developer account at https://developer.apple.com.
- Follow the Apple Developer Account Help instructions https://developer.apple.com/help/account/capabilities/configure-apple-pay#create-a-merchant-identifier to create a merchant identifier.
Step 2: Install Dependency
Add the stable version of PAY plugin under dependencies in pubspec.yaml file.
dependencies: # level 0
pay: ^3.2.0 # +2 spacesRun flutter pub get to fetch and install the dependencies.
Note: Whenever you make changes or updates to the dependencies, run the flutter pub get command to apply and fetch the updated packages
Indentation Notes :
dependencies: must be at the root level (no spaces before it)
pay: must be indented with 2 spaces under dependencies
YAML is space-sensitive, so incorrect indentation will cause errors
Step 3:Import Packages
After adding the dependency, import the Packages in your Apple Pay integration file.
import 'package:pay/pay.dart';
import 'package:flutter/material.dart'; //Provides Material Design componentsStep 4: Apple Pay Configuration
This configuration defines how Apple Pay should process the payment request in your Flutter app. It includes the merchant identifier, supported card networks, merchant capabilities (such as 3D Secure), country, currency, and the display name shown to the user. The JSON is passed to the pay plugin to initialize and present the Apple Pay sheet. Ensure all values (especially merchantIdentifier) are correctly configured in your Apple Developer account before use.
static String _applePayConfig() {
return '''
{
"provider": "apple_pay",
"data": {
"merchantIdentifier": "YOUR_MERCHANT_IDENTIFIERt",
"displayName": "YOUR_DISPLAY_NAME",
"merchantCapabilities": ["3DS"],
"supportedNetworks": ["visa", "masterCard"],
"countryCode": "COUNTRY_CODE",
"currencyCode": "CURRENCY_CODE"
}
}
''';
}| Field | Placeholder | Description |
| merchantIdentifier | YOUR_MERCHANT_IDENTIFIER | Must match Apple Developer Merchant ID |
| displayName | YOUR_DISPLAY_NAME | Name shown to users in Apple Pay sheet |
| merchantCapabilities | ["3DS"] | Usually `3DS` (required for secure payments) |
| supportedNetworks | ["visa", "masterCard"] | Supported card types |
| countryCode | COUNTRY_CODE | ISO country code (e.g., `DE`, `US`, `IN`) |
| currencyCode | CURRENCY_CODE | ISO currency (e.g., `EUR`, `USD`, `INR`) |
Step 5: Environment Setup
Apple Pay determines whether a transaction is test or live based on the merchant configuration and certificate used, not from the app code.
1. If you use a development/sandbox setup (test merchant + test card), it behaves as test mode
Example: Test (Sandbox)
- Merchant Identifier: merchant.com.yourcompany.test
- Certificate: Sandbox / Development certificate
- Device: Logged in with sandbox Apple ID
- Wallet: Test card added
- Result: Test transaction (no real money)
2.If you use a production merchant identifier and valid certificate, it behaves as live mode
Example: Test (Sandbox)
- Merchant Identifier: merchant.com.yourcompany.live
- Certificate: Production payment processing certificate
- Device: Normal Apple ID
- Wallet: Real card added
- Result: Live transaction (real payment)
If the same merchant identifier is used for both environments, Apple Pay determines test or live mode based on:
- Certificate Used
- Device Setup
- Apple account (sandbox vs real)
Step 6 : Check Apple Pay Availability
This step checks whether the device is fully ready to make an Apple Pay payment before starting the payment flow. It initializes the payment configuration and uses the userCanPay method to verify support. If canPay returns false, it means Apple Pay cannot be used on the device. This typically happens if Apple Pay is not set up, no supported card is added, the device does not support Apple Pay, or the app is not properly configured (e.g., missing merchant identifier or capability).
final config = PaymentConfiguration.fromJsonString(_applePayConfig());
final payClient = Pay({PayProvider.apple_pay: config});
final canPay = await payClient.userCanPay(PayProvider.apple_pay);
If canPay returns false, Apple Pay is not supported on the device
eg:
if (!canPay) {
return {
"status": "FAILURE",
"message": "Apple pay not available",
};
}Step 7: Set payment items, Show Apple Pay Button & Get Payment Data
This step defines the payment amount and displays the Apple Pay button to the user. When the user completes the payment, the response is returned via onPaymentResult and stored in resultData.
final items = [
PaymentItem(
label: "Total",
amount: AMOUNT, // The amount must be provided as a string in standard decimal format with two digits (e.g., "82.00", not 8200)
status: PaymentItemStatus.final_price,
),
];
Map<String, dynamic>? resultData;
await showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.only(bottom: 60),
child: SizedBox(
height: 120,
child: Center(
child: ApplePayButton(
paymentConfiguration: config,
paymentItems: items,
type: ApplePayButtonType.buy,
onPaymentResult: (result) {
resultData = result;
Navigator.pop(context);
},
onError: (e) {
Navigator.pop(context);
},
),
),
),
),
);
},
);Result Data contains the payment response, including the signature, payment token, and encrypted payment data. Once received successfully, this data must be sent to Novalnet for processing and transaction booking.
Step 8: Process Payment with Novalnet SDK
After receiving the payment response from Apple Pay, the token must be sent to Novalnet for decryption and transaction processing.
- Import the Novalnet SDK in your file: : import 'package:novalnetsdk/novalnetsdk.dart';
- Call the SDK function with the payment token: :
var result = await NovalnetSDK.openWalletPaymentHandler(
context,
paymentType: "APPLEPAY",
token: resultData ?? {},
);The SDK will handle token decryption and complete the transaction processing.
Step 9: Important Notes
- Apple Pay requires proper merchant configuration in the Apple Developer account
- A valid payment processing certificate must be generated and configured
- Apple Pay works only on iOS devices (not supported on Android)
- Testing must be done on a real device (Apple Pay does not work on simulator)
- Ensure the correct merchant identifier is used and matches the app configuration
Reference Implementation (Full Payment Flow)
This example provides a complete end-to-end implementation of Apple Pay integration using the Flutter pay plugin. It includes configuration setup, availability check, payment UI handling, and capturing the payment response. Developers can use this as a reference to integrate Apple Pay quickly. The returned resultData should be sent to Novalnet for final payment processing.
import 'package:flutter/material.dart';
import 'package:pay/pay.dart';
import 'package:novalnetsdk/novalnetsdk.dart';
class ApplePayHandler {
static Future<Map<String, dynamic>?> startPayment(
BuildContext context,
) async {
try {
// Config
final config = PaymentConfiguration.fromJsonString(_applePayConfig());
// Pay client
final payClient = Pay({PayProvider.apple_pay: config});
// Availability
final canPay = await payClient.userCanPay(PayProvider.apple_pay);
if (!canPay) {
return {"status": "FAILURE", "message": "Apple pay not available"};
}
// Payment items
final items = [
PaymentItem(
label: "Total",
amount: "AMOUNT", //The amount must be provided as a string in standard decimal format with two digits (e.g., "82.00" not 8200)
status: PaymentItemStatus.final_price,
),
];
Map<String, dynamic>? resultData;
// UI
await showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.only(bottom: 60),
child: SizedBox(
height: 120,
child: Center(
child: ApplePayButton(
paymentConfiguration: config,
paymentItems: items,
type: ApplePayButtonType.buy,
onPaymentResult: (result) {
resultData = result;
Navigator.pop(context);
},
onError: (e) {
Navigator.pop(context);
},
),
),
),
),
);
},
);
if (resultData == null || resultData?.isEmpty == true) {
return {
"status": "FAILURE",
"message": "Payment cancelled due to Payment data is empty",
};
}
var result = await NovalnetSDK.openWalletPaymentHandler(
context,
paymentType: "APPLEPAY",
token: resultData ?? {},
);
return result;
} catch (e) {
return {"status": "FAILURE", "message": "Something went wrong $e"};
}
}
static String _applePayConfig() {
return '''
{
"provider": "apple_pay",
"data": {
"merchantIdentifier": "YOUR_MERCHANT_IDENTIFIERt",
"displayName": "YOUR_DISPLAY_NAME",
"merchantCapabilities": ["3DS"],
"supportedNetworks": ["visa", "masterCard"],
"countryCode": "COUNTRY_CODE",
"currencyCode": "CURRENCY_CODE"
}
}
''';
}
}The following example demonstrates how to initiate Apple Pay when the button is pressed. This is a basic example, and you can handle the response and flow based on your application requirements:
import 'applepay_handler.dart';
onPressed: () async {
var result = await ApplePayHandler.startPayment(context);
if (result == null) return;
}