Flutter Payment Plugin — Step‑by‑Step Integration
This guide explains how to add and use flutter_payment_plugin in any Flutter app.
1) Add the Package
- Run:
flutter pub add flutter_payment_plugin - Import:
import 'package:flutter_payment_plugin/flutter_payment_plugin.dart';
2) Android Setup
- No additional Android changes are required.
3) Web Setup
- Copy
payment_return.htmlfrom the example app into your app’sweb/folder. - Set
return_urlin payment params to your hosted page, e.g.https://your-app.com/payment_return.html. This URL must be included when you generate the payment hash. - Choose how payment is presented (see below).
- After payment, the gateway redirects to
payment_return.html, which notifies your Flutter app (tab flow) or reloads the app so you can resume (WebView / top-level flow).
Optional: add a redirect file web/payment_return (no extension) that forwards to payment_return.html if your gateway omits .html in redirects.
Browser tab (default)
- Allow popups for your site; the plugin opens a new tab and POSTs to the gateway.
payment_return.htmlsignals completion viapostMessage/BroadcastChannel.
// Default — no change needed
FlutterPaymentPlugin.webPresentation = PaymentPresentation.auto;
// or per call:
await FlutterPaymentPlugin.openPayment(
url: gatewayBaseUrl,
params: params,
presentation: PaymentPresentation.tab,
);
Embedded WebView / WebKit hosts
Payment gateways block iframes, so in-page overlays cannot load the gateway. For Flutter web apps hosted inside Android WebView or iOS WKWebView:
- Use top-level navigation (
PaymentPresentation.overlay). - On app startup (and after return), call
consumeWebReturnIfAny()to finish the flow and fetch/v2/paymentstatus.
FlutterPaymentPlugin.webPresentation = PaymentPresentation.overlay;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
// Inside your root widget / first screen:
@override
void initState() {
super.initState();
_resumeIfNeeded();
}
Future<void> _resumeIfNeeded() async {
final result = await FlutterPaymentPlugin.consumeWebReturnIfAny();
if (result != null) {
// Handle PaymentResult (success / cancelled / data)
}
}
See also the WebView shell example for a native host that loads the Flutter web build.
4) iOS Setup
- To make the deep links to open payment apps, add these URL schemes to
ios/<YourAppName>/Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
<!-- Add the schemes you plan to support -->
<string>upi</string>
<string>gpay</string>
<string>phonepe</string>
<string>paytm</string>
<!-- etc. -->
</array>
5) Prepare Your Inputs
url: your payment gateway base domain (e.g.,https://your-gateway.example)paramsmust include at least:api_key,order_id,salt,hash,mode,amount,name,phone,email,return_url- Optional fields as required by your gateway:
description,currency,country,city,state,address_line_1,address_line_2,zip_code,enable_auto_refund,udf1–udf5,split_info(JSON string for vendor settlement splits)
Hashing Logic (Recommended on Backend)
While it is highly recommended to generate the secure hash on your backend, here is the Dart logic for reference. The hash generation requires camelCase keys for sorting, even though the final request uses snake_case.
import 'dart:convert';
import 'package:crypto/crypto.dart'; // Add crypto: ^3.0.0 to pubspec.yaml
String generateHash({
required String salt,
required String apiKey,
required Map<String, String> camelCaseInputs,
}) {
// 1. Prepare map with REQUIRED camelCase keys
final Map<String, String> params = {
"aaaasalt": salt,
"apiKey": apiKey,
"mode": camelCaseInputs["mode"] ?? "",
"amount": camelCaseInputs["amount"] ?? "",
"name": camelCaseInputs["name"] ?? "",
"phone": camelCaseInputs["phone"] ?? "",
"email": camelCaseInputs["email"] ?? "",
"returnURL": camelCaseInputs["returnURL"] ?? "",
"description": camelCaseInputs["description"] ?? "",
"currency": camelCaseInputs["currency"] ?? "",
"country": camelCaseInputs["country"] ?? "",
"city": camelCaseInputs["city"] ?? "",
"state": camelCaseInputs["state"] ?? "",
"addressLine1": camelCaseInputs["addressLine1"] ?? "",
"addressLine2": camelCaseInputs["addressLine2"] ?? "",
"zipCode": camelCaseInputs["zipCode"] ?? "",
"enable_auto_refund": camelCaseInputs["enable_auto_refund"] ?? "",
"orderID": camelCaseInputs["orderID"] ?? "",
"splitInfo": camelCaseInputs["splitInfo"] ?? "", // when using split_info
};
// 2. Sort keys alphabetically
final entries = params.entries.toList()..sort((a, b) => a.key.compareTo(b.key));
// 3. Join non-empty values with '|'
final buffer = StringBuffer();
for (final e in entries) {
final v = e.value.trim();
if (v.isNotEmpty) buffer.write('|$v');
}
if (buffer.isEmpty) return "";
// 4. Remove leading '|'
final toHash = buffer.toString().substring(1);
// 5. Generate SHA-512 and convert to Uppercase Hex
final digest = sha512.convert(utf8.encode(toHash));
return digest.bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join().toUpperCase();
}
6) Trigger the Payment
Future<void> startPayment() async {
final params = <String, String>{
'api_key': 'YOUR_API_KEY',
'order_id': 'YOUR_ORDER_ID',
'salt': 'YOUR_SALT',
'hash': 'YOUR_GENERATED_HASH',
'mode': 'LIVE',
'amount': '100',
'name': 'Buyer Name',
'phone': '9999999999',
'email': 'buyer@example.com',
'return_url': 'https://your.domain/return',
// optional fields...
};
final result = await FlutterPaymentPlugin.openPayment(
url: 'https://your-gateway.example',
params: params,
title: 'Payment',
);
if (result.cancelled) {
// Handle cancellation
} else if (result.success) {
// Parse result.data — see "Payment response format" below
} else {
// Handle failure, inspect result.data
}
}
7) Payment Response Format
FlutterPaymentPlugin.openPayment returns a PaymentResult with a data field containing a JSON string.
All platforms (1.0.17+)
When the gateway redirects to your return_url, the plugin calls /v2/paymentstatus and returns the exact API response (not flattened, not the return-URL form body):
{
"data": [
{
"transaction_id": "TGDEMN...",
"order_id": "ORDER123",
"amount": "100.00",
"response_code": "0",
"response_message": "SUCCESS",
"payment_mode": "Netbanking",
"customer_name": "Buyer Name",
"customer_email": "buyer@example.com",
"customer_phone": "9999999999",
"currency": "INR",
"udf1": "..."
}
],
"hash": "DEFD1DC3..."
}
Parse it in your app:
import 'dart:convert';
void handlePaymentResult(PaymentResult result) {
if (!result.success || result.data == null) return;
final root = jsonDecode(result.data!) as Map<String, dynamic>;
final rows = root['data'] as List<dynamic>? ?? [];
final responseHash = root['hash'] as String?;
if (rows.isEmpty) {
// Payment status not ready or failed — handle accordingly
return;
}
final txn = rows.first as Map<String, dynamic>;
final orderId = txn['order_id'];
final responseCode = txn['response_code'];
// Verify responseHash on your backend before trusting the result
}
Required params for status lookup: api_key, order_id, salt, and mode must be present in the params map passed to openPayment (same values used for the payment request).
Web notes
- Host
payment_return.htmlon the same origin as your Flutter app (see Web Setup). It signals that the return URL was reached; the plugin then fetches/v2/paymentstatusfrom the browser. - The gateway must allow CORS on
/v2/paymentstatusfor browser calls. If CORS blocks the request,result.datawill be anERROR:…string — verify status on your backend in that case. - WebView hosts: set
PaymentPresentation.overlayand callconsumeWebReturnIfAny()after return (top-level navigation; do not rely on iframes).
Upgrading from 1.0.16 or earlier
| Platform | Old result.data |
New result.data (1.0.17+) |
|---|---|---|
| Android | Flat object: fields from data[0] plus hash at root |
{ "data": [ {...} ], "hash": "..." } |
| iOS | Flat object from return-URL POST (name, phone, email, etc.) |
Same as Android (/v2/paymentstatus) |
| Web | Wrapper from payment_return.html (type, query, href, …) |
Same as Android (/v2/paymentstatus) |
If your app assumed a flat object or iOS-specific field names (name vs customer_name), update parsing to use the data array and verify the top-level hash on your server.
8) Integration Tips
- Ensure all required fields are set and trimmed.
- Use a reachable
return_urlprovided by your gateway. - Prefer generating
hashsecurely on your backend. - Provide only the base
url; the plugin handles the payment endpoint internally.
9) Common Issues
- Wrong base URL: ensure your
urlpoints to the correct gateway domain. - Network errors: check device connectivity and the base
url. - iOS deep links: add URL schemes for the apps your flow targets.
- Web popups blocked: allow popups, or use
PaymentPresentation.overlay(top-level navigation) for WebView hosts; resultdatamay bePOPUP_BLOCKEDin tab mode. - Web in WebView blank / blocked: do not iframe the gateway; use
PaymentPresentation.overlay+consumeWebReturnIfAny(). - Web timeout: ensure
return_urlpoints to your same-originpayment_return.htmland the hash was generated with that exact URL. - Response parse errors after upgrade: If you upgraded to 1.0.17+,
result.datais the/v2/paymentstatusenvelope (data+hash) on all platforms, not a flat object or return-URL wrapper. See Payment Response Format.