#Cross platform integration with your own payment gateway
This quickstart explains how to integrate when you opt for your own payment gateway. Use this guide for Setu’s default UPI payment option.
In case you are using a cross platform solution—like Flutter, React Native, Ionic—for your Android and iOS apps, you can refer to the code snippets provided below for a custom payment integration. The integration process remains the same, but changes as per the language you use.
The code snippets provided below deal specifically with how the web view is loaded and dismissed within your Android/iOS app. Usage of the Create Link API will remain unaffected.
#Flutter
#Step 1 - Implement webview in your app
The following steps need to be taken for webview integration—
- Get link from the backend
- Open the link in
InAppWebViewand attach aWebViewController - Execute payment flow and redirect back to the
InWebViewpost payment
Switch control from your app to Setu
The link returned by the Create link API should be loaded within the Webview. Our sample below uses the flutter_inappwebview plugin for webviews. Feel free to use a plugin as per your requirements.
The controller will need to implement two JavaScript handlers -
initiatePayment- Used to initiate payment & transfer control from Setu webview to the flutter application.unload- Used by the parent app to dismiss the webview
Special use case: This unload function can also be used for dismissing the webview and redirecting a user back to your native app once a bill payment journey is completed (i.e. payment is successful) via a CTA from the Setu webview. Please let our team know if you would like to enable this use case for your app.
Note: For downloading and saving transaction receipts from the webview, the webview will need to handle a onDownloadStartRequest (or a similar function as per your webview plugin). Our sample below uses the flutter_downloader and path_provider plugins for downloading and saving transaction receipts. Please follow the plugin specific documentation for implementation. Make sure you have added necessary permissions in your AndroidManifest.xml and Info.plist files for downloading and saving files.
import 'dart:io';import 'dart:isolate';import 'dart:ui';import 'package:flutter/material.dart';import 'package:url_launcher/url_launcher.dart';import 'package:flutter_inappwebview/flutter_inappwebview.dart';import 'package:flutter_downloader/flutter_downloader.dart';import 'package:path_provider/path_provider.dart';Future main() async {WidgetsFlutterBinding.ensureInitialized();await FlutterDownloader.initialize(debug: true, // optional: set false to disable printing logs to console);runApp(new MyApp());}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: RpWebPage(link: 'https://billpay.setu.co/1234',), // Replace with the actual URL);}}class RpWebPage extends StatefulWidget {const RpWebPage({super.key, required this.link});final String link;@overrideState<RpWebPage> createState() => _RpWebPageState();}class _RpWebPageState extends State<RpWebPage> {late InAppWebViewController webView;bool isButtonClicked = false;@overridevoid initState() {super.initState();}void _loadUrl() {setState(() {isButtonClicked = true;});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Bill Payments'),),body: Column(children: [if (!isButtonClicked)ElevatedButton(onPressed: _loadUrl,child: Text('Load webview'),),Expanded(child: isButtonClicked? InAppWebView(initialSettings: InAppWebViewSettings(allowFileAccess: true,allowFileAccessFromFileURLs: true),initialUrlRequest: URLRequest(url: WebUri.uri(Uri.parse(widget.link))),onWebViewCreated: (InAppWebViewController controller) {webView = controller;},onLoadStop: (controller, url) async {controller.addJavaScriptHandler(handlerName: 'initiatePayment',callback: (args) async {print('Received from WebView: $args');final paymentData = args[0];print('Payment data: $paymentData');// Your app can access the fields directly from args[0] (which is typically a Map)String orderId = paymentData['orderId'];String amount = paymentData['amount'];String postPaymentUrl = paymentData['postPaymentUrl'];String customerMobileNumber = paymentData['customerMobileNumber'];String customerId = paymentData['customerId']; // OptionalString allowedPaymentModes = paymentData['allowedPaymentModes'];// Add your custom payment logic here},);controller.addJavaScriptHandler(handlerName: 'unload',callback: (args) async {// logic to close webview and return to app},);},// Handle downloading the receiptonDownloadStartRequest: (controller, request) async {print("onDownloadStart $request");final dir = Platform.isAndroid? (await getExternalStorageDirectory())?.path: (await getApplicationDocumentsDirectory()).uri.path;print("saving in $dir");final taskId = await FlutterDownloader.enqueue(url: request.url.toString(),savedDir: dir!,showNotification: true, // show download progress in status bar (for Android)openFileFromNotification: true, // click on notification to open downloaded file (for Android)saveInPublicStorage: true,allowCellular: true);// Open the downloaded fileawait FlutterDownloader.open(taskId: taskId!);},): Container(),),],),);}}
Handle payment flow
The initiatePayment JavaScript handler in the controller receives a single argument (args[0] in the Dart callback), which is a direct object (typically a Map<String, dynamic> in Dart) containing the payment details. Your application code can access these details directly from this object. The object will contain the following fields:
orderId(String, mandatory): Order ID on the Setu COU System. It always starts withCOUWL. Example—COUWLZ7pFtTgr9LtOamount(String, mandatory): Amount to be paid by the customer. Example —3522.20postPaymentUrl(String, mandatory): URL which needs to be loaded in the webview after the payment is completed by your app. Example —https://billpay.setu.co/payment-callback/68c7217b-8fa7-4c1f-8e5f-317ff3027668customerMobileNumber(String, mandatory): Mobile number of the customer used for the session. Example -9876543210customerId(String, optional): Customer ID linked to a particular session. Example -cust@1234allowedPaymentModes(String, optional): Comma separated list of modes allowed for this payment. One of these should be sent in thepaymentModein the payment confirmation webhook.
Example of the structure of the payment details object received by the handler:
{"orderId": "COUWLZ7pFtTgr9LtO","amount": "3522.20","postPaymentUrl": "https://billpay.setu.co/payment-callback/68c7217b-8fa7-4c1f-8e5f-317ff3027668","customerMobileNumber": "9876543210","customerId": "cust@1234","allowedPaymentModes": "UPI,NETBANKING"}
When the initiatePayment handler is called by the webview, your app needs to do the following—
- Remove the webview
- Initiate the payment flow
- Send Setu the webhook event for payment—this need not be done for UPI transfers in real time
- Once payment is executed, load the webview again with the callback URL
#Step 2 — Optionally configure webhook
You may optionally want to listen to user events—like successful or failed bill fetch, bill payment status and more—through webhooks. Refer to this guide for more information.
#Step 3 — Get Production credentials and go live
Once you are done testing your integration, ensure that all KYC and legal agreements are submitted. Contact Setu for getting enabled on production.
We are updating the docs for custom payment based integration for other cross platform frameworks like React native Please bear with us for a while.
Was this page helpful?
