Prerequisites๐
Dart Version: Supports 3.11.4 and upper
Flutter Version:Supports 3.41.6 and upper
Minimum Version support for ios is 14
Download the SDK from below link
Dart Version: Supports 3.11.4 and upper
Flutter Version:Supports 3.41.6 and upper
Minimum Version support for ios is 14
Download the SDK from below link
Please refer Order API Documentation for more details.
Authorization: basic authentication with base64encoded
PayGAuthentication โ
<MerchantAuthenticationKey>:<MerchantAuthenticationToken>:M:<MID>
{
"MID": "3EMF9915EF35567",
"UniqueRequestId": "TEST512scx3",
"UserDefinedData": {
"UserDefined1": ""
},
"ProductData": "{'PaymentReason':'OnlineOrder for OrderNo- 1234'}",
"RequestDateTime": "06232023",
"RedirectUrl": "https://payg.in",
"TransactionData": {
"AcceptedPaymentTypes": "",
"PaymentType": "UPIINTENT",
"SurchargeType": "",
"SurchargeValue": "",
"RefTransactionId": "",
"IndustrySpecificationCode": "",
"PartialPaymentOption": ""
},
"OrderAmount": "3",
"OrderType": "",
"OrderAmountData": {
"AmountTypeDesc": "3",
"Amount": "2"
},
"CustomerData": {
"CustomerId": "123456",
"CustomerNotes": "Mens clothing",
"FirstName": "Ayush",
"LastName": "Rane",
"MobileNo": "7887673322",
"Email": "ayushtesting12@gmail.com",
"EmailReceipt": "true",
"BillingAddress": "76, Sch. 115",
"BillingCity": "Indore",
"BillingState": "MP",
"BillingCountry": "India",
"BillingZipCode": "452011",
"ShippingFirstName": "Ayush",
"ShippingLastName": "Rane",
"ShippingAddress": "76, Sch. 115",
"ShippingCity": "Indore",
"ShippingState": "MP",
"ShippingCountry": "India",
"ShippingZipCode": "452011",
"ShippingMobileNo": "9987887733"
},
"IntegrationData": {
"UserName": "Ayush",
"Source": "3213",
"IntegrationType": "11",
"HashData": "",
"PlatformId": "1"
}
}
var data = {
"Merchantkeyid": MerchantKeyId,
"UniqueRequestId": getRandomString(10),
"OrderStatus": 'Initiating',
"UserDefinedData": {"UserDefined1": ''},
"RequestDateTime": "11/08/2021",
"RedirectUrl": RedirectUrl,
"TransactionData": {
"AcceptedPaymentTypes": '',
"PaymentType": orderType,
"SurchargeType": orderType,
"SurchargeValue": orderType,
"RefTransactionId": orderType,
"IndustrySpecificationCode": orderType,
"PartialPaymentOption": orderType,
},
"OrderAmount": orderAmountController.text,
"OrderType": 'MOBILE',
"OrderAmountData": {"AmountTypeDesc": amountTypeDesc, "Amount": "20"},
"CustomerData": {
'CustomerId': CustomerId,
'CustomerNotes': customerNotes,
'FirstName': orderFirstNameController.text,
'LastName': orderLastNameController.text,
'MobileNo': mobileNoController.text,
'Email': email,
'EmailReceipt': emailReceipt,
'BillingAddress': billingAddress,
'BillingCity': billingCity,
'BillingState': billingState,
'BillingCountry': billingCountry,
'BillingZipCode': billingZipCode,
'ShippingFirstName': shippingFirstName,
'ShippingLastName': shippingLastName,
'ShippingAddress': shippingAddress,
'ShippingCity': shippingCity,
'ShippingState': shippingState,
'ShippingCountry': shippingCountry,
'ShippingZipCode': shippingZipCode,
'ShippingMobileNo': shippingMobileNo,
},
"IntegrationData": {
"UserName": userName,
"Source": 'MobileSDK',
"IntegrationType": "11",
"HashData": HashData,
"PlatformId": PlatformId,
}
};
{
"OrderKeyId": "93281250227M10404UTEST512scx3",
"MerchantKeyId": 10121,
"UniqueRequestId": "TEST512scx3",
"OrderType": "PAYMENT",
"OrderAmount": 3,
"OrderId": null,
"OrderStatus": null,
"OrderPaymentStatus": 0,
"OrderPaymentStatusText": null,
"PaymentStatus": 0,
"PaymentTransactionId": null,
"PaymentResponseCode": 0,
"PaymentApprovalCode": null,
"PaymentReasonCode": null,
"PaymentTransactionRefNo": null,
"PaymentResponseText": null,
"PaymentMethod": null,
"PaymentAccount": null,
"OrderRefundTransactionId": 0,
"RefundPaymentResponseCode": 0,
"RefundPaymentResponseText": null,
"RefundDateTime": null,
"CardType": null,
"CardLevel": null,
"OrderNotes": null,
"PaymentDateTime": null,
"UpdatedDateTime": null,
"PaymentProcessUrl": "null",
"CustomerData": {
"CustomerId": "123456",
"CustomerNotes": "Mens clothing",
"FirstName": "Ayush",
"LastName": "Rane",
"MobileNo": "7887673322",
"Email": "ayushtesting12@gmail.com",
"EmailReceipt": true,
"BillingAddress": "76, Sch. 115",
"BillingCity": "Indore",
"BillingState": "MP",
"BillingCountry": "India",
"BillingZipCode": "452011",
"ShippingFirstName": "Ayush",
"ShippingLastName": "Rane",
"ShippingAddress": "76, Sch. 115",
"ShippingCity": "Indore",
"ShippingState": "MP",
"ShippingCountry": "India",
"ShippingZipCode": "452011",
"ShippingMobileNo": "9987887733"
},
"ProductData": "{'PaymentReason':'OnlineOrder for OrderNo- 1234'}",
"OrderPaymentCustomerData": {
"FirstName": "Ayush",
"LastName": null,
"Address": null,
"City": null,
"State": null,
"ZipCode": null,
"Country": null,
"MobileNo": "7887673322",
"Email": "ayushtesting12@gmail.com",
"UserId": null,
"IpAddress": null
},
"UpiLink": "upi://pay?pa=businesspayg@isis&am=1&tr=Y564417909M21764D543216&cu=INR&mc=2006&pn=Test Account"
"UserDefinedData": {
"UserDefined1": "",
"UserDefined2": null,
"UserDefined3": null,
"UserDefined4": null,
"UserDefined5": null,
"UserDefined6": null,
"UserDefined7": null,
"UserDefined8": null,
"UserDefined9": null,
"UserDefined10": null,
"UserDefined11": null,
"UserDefined12": null,
"UserDefined13": null,
"UserDefined14": null,
"UserDefined15": null,
"UserDefined16": null,
"UserDefined17": null,
"UserDefined18": null,
"UserDefined19": null,
"UserDefined20": null
},
"SplitPaymentResponse": null,
"Id": 93281
}
Once your order is created successfully,then you will UPI link in response. This payment url is used to redirect to actual payment screen.
UPI Link is for UPI payment. You can use either payment process URL or UPI based on your requirement.
Add the package to your pubspec.yaml (adjust source depending on whether you're publishing to pub.dev or using a local):
dependencies:
flutter:
sdk: flutter
payg_payment_package:
path: payg_payment_package
Then run:
flutter pub get
Since UPI apps are launched via Android intents, you need to declare the UPI apps you intend to query/launch in your AndroidManifest.xml (required from Android 11 / API 30 onward due to package visibility restrictions):
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="upi" />
</intent>
</queries>
Without this block, getUpiApps() may return an empty list on newer Android versions even if UPI apps are installed.
import 'package:payg_payment_package/payg_payment.dart';
final payment = PaygPayment(
mid: "3EMF9915EF35567",
paymentReason: "OnlineOrder for OrderNo- 1234",
orderAmount: "1",
mobileNo: "8875292912",
shippingZipCode: "500001",
shippingMobileNo: "9876543210",
context: context,
isUAT: false,
authKey: 'gt44e35823e54f088ea2e41da9c098d4',
authtoken: 'y56tf2782aa348d6tb1a73a89438t4rd',
merchantName: 'Bloom Retail Pvt. Ltd.',
);
await payment.initPaygPayment().then((v) {
Utility.logPrint("response initPaygPayment: $v");
});
UPI itself has no formal iOS equivalent of Android's intent-based app discovery, so iOS support works fundamentally differently and needs its own native setup. This matters because your UI (as shown above) falls back to aDropdownButtonFormField on iOS instead of the app grid used on Android โ which only works if the plugin's iOS side is actually able to detect, open, and receive a callback from installed UPI apps.
There are two separate pieces of iOS setup: (A) letting your app query and open other UPI apps, and (B) letting other UPI apps call back into your app once the payment is done. Both are required.
iOS can't enumerate "anything that handles upi://" the way Android'sPackageManager can. Each app's scheme must be checked one by one viacanOpenURL, and must be whitelisted in Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>tez</string>
<string>phonepe</string>
<string>paytmmp</string>
<string>credpay</string>
<string>bhim</string>
<string>upi</string>
</array>
Without this, canOpenURL (and therefore getUpiApps() on iOS) will report every app as "not installed," even if it is.
For a UPI app to hand control back to your app after the payment completes, your app needs its own registered URL scheme that you pass as the callback/return URL when building the UPI link. Add this to Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.payg.upi.callback</string>
<key>CFBundleURLSchemes</key>
<array>
<string>payg</string>
</array>
</dict>
</array>
Here payg is the scheme the plugin listens on โ when a UPI app finishes and redirects to something like payg://response?Status=SUCCESS&..., iOS routes that back into your app rather than dropping it.
Your AppDelegate needs to forward the incoming URL to the plugin so it can parse it and resolve the Future that launchUpiApp() is waiting on:
// MARK: - Handle URL Scheme Callbacks
override func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
print("๐ฑ AppDelegate - Received URL: \(url.absoluteString)")
// Let the RanaPayUpiPlugin handle the URL
if let plugin = RanaPayUpiPlugin.shared {
let handled = plugin.handleOpenURL(url)
if handled {
print("โ
RanaPayUpiPlugin handled URL successfully")
return true
}
}
// If not handled by plugin, pass to Flutter's default handler
print("โ ๏ธ URL not handled by plugin, passing to Flutter")
return super.application(app, open: url, options: options)
}
// MARK: - Handle Universal Links
override func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
print("๐ฑ Received universal link: \(url.absoluteString)")
if let plugin = RanaPayUpiPlugin.shared {
if plugin.handleOpenURL(url) {
print("โ
Plugin handled universal link")
return true
}
}
}
return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
application(_:open:options:) fires when a UPI app redirects back into your app via the payg:// custom scheme (ยง6.2). It hands the URL toRanaPayUpiPlugin.shared.handleOpenURL(url), which is presumably what parses the query string and completes the pending launchUpiApp() call on the Dart side.
application(_:continue:restorationHandler:) covers the case where the redirect comes back as a universal link (https://...) instead of a custom scheme โ some UPI apps prefer this. Both paths funnel into the samehandleOpenURL, so you don't need separate Dart-side handling for each.
The Facebook (fb...) scheme check is just making sure this callback handler doesn't swallow unrelated URL callbacks your app already relies on โ keep any other third-party SDK scheme checks alongside it if you add more in the future.
If neither the plugin nor a known third-party scheme handles it, it falls through to Flutter's own default URL handling so nothing else in your app breaks.
โ ๏ธ Make sure this code lives in your ios/Runner/AppDelegate.swift, and that RanaPayUpiPlugin.shared is actually being set somewhere in the plugin's register(with:) โ otherwise plugin will be nil and every callback silently falls through to Flutter's default handler, meaninglaunchUpiApp() will simply hang/timeout on iOS regardless of correctInfo.plist setup.
6. General Requirements
๐
AndroidManifest.xml <queries> block for package visibility (Android 11+).
Info.plist LSApplicationQueriesSchemes entries for iOS (querying apps).
Info.plist CFBundleURLTypes entry for iOS (receiving the callback).
AppDelegate.swift URL + universal-link handlers wired to
RanaPayUpiPlugin.shared.handleOpenURL(url).
Real UPI-enabled bank account and VPA (pa) for testing actual payments.
A backend payment-status endpoint โ required on both platforms, but especially load-bearing on iOS (see ยง6.4).