/

to search

Introducing Setu Changelog Check it out ↗

#iOS 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.


Setu provides ready to use URLs for iOS integration. Contact Setu to update your logo, colours, fonts as per your branding. You can reach out to billpay.support@setu.co for any further clarifications. This quickstart page provides the sample code for the following specs—

  • Programming language — Swift 5.* (Latest version 5.3)
  • Interface — Storyboard
  • Life Cycle delegation — UI Kit
  • iOS versions supported — 12+ (Latest version 14.4)

#Step 1 — Implement web view in your app

The following steps need to be taken for webview integration—

  1. Get link from the backend
  2. Open the link in WKWebView and attach a userContentController
  3. Execute payment flow and redirect back to the WKWebView on success/failure

Get link from the backend

This step gives a one time Setu URL to be used by your customer for the bill fetch or payment flow. The Create link API needs to be called when Android app wants to obtain a one time link to load Setu’s screen flow inside a webview.

None of the parameters aside from mobile number are mandatory, but depending on what is passed the returned link will display different UI.


You can have the following scenarios—

  • If no other input is passed, it will take the user to the home page with all BBPS categories.
  • If category code is passed, it will show user a list of billers in that category.
  • If category code and biller ID is passed, it will show the bill fetch form where a customer can enter their identifers (biller specified parameters)
  • If the category code, biller ID and parameters are passed, it will show the bill directly.

All query parameters should be url-encoded to escape special characters


The above request will return URL based on the input params. Here’s a sample—

{
"link": "https://billpay-qa.setu.co/1238993883748223",
"sessionId": "1238993883748223",
"success": true,
"traceId": "GHSUU99128DBVU"
}

Switch control from your app to Setu

The link returned by the API should be loaded within WKWebView in a ViewController which implements UIViewController, and WKScriptMessageHandler. The controller must also extend and use a custom WKNavigationDelegate class to intercept and download PDF files.

The userContentController will need to implement two functions—

  • initiate_payment — Used to initiate payment and transfer control from webview to iOS 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.


func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let messageBody = message.body as? [String: Any],
let functionToPerform = messageBody["function"] as? String else {
print("Error: Could not parse message body or function name from webview.")
return
}
switch functionToPerform {
case "initiate_payment":
guard let dataDict = messageBody["data"] as? [String: Any] else {
print("Error: 'data' object not found in message body or is not a dictionary.")
return
}
// Extract mandatory current parameters directly from the dataDict
guard let orderID = dataDict["orderId"] as? String,
let billAmount = dataDict["amount"] as? String,
let postPaymentUrl = dataDict["postPaymentUrl"] as? String, // Current field, was 'callback'
let customerMobileNumber = dataDict["customerMobileNumber"] as? String else { // Current field, was 'mobileNumber'
print("Error: Missing one or more mandatory current payment parameters (orderId, amount, postPaymentUrl, customerMobileNumber) in data object.")
return
}
// Extract optional current parameters directly from dataDict
let customerId = dataDict["customerId"] as? String
let allowedPaymentModes = dataDict["allowedPaymentModes"] as? String
// Extract deprecated parameters for informational purposes.
// These might still be sent by the webview during a transition period but should not be used for new logic.
let _ = dataDict["callback"] as? String // Deprecated: Use 'postPaymentUrl' instead.
let _ = dataDict["mobileNumber"] as? String // Deprecated: Use 'customerMobileNumber' instead.
let _ = dataDict["beneficiaryVPA"] as? String // Deprecated: No longer functionally used.
let _ = dataDict["remarks"] as? String // Deprecated: No longer functionally used.
let _ = dataDict["refId"] as? String // Deprecated: No longer functionally used.
// Note: The PaymentViewController initializer should use the current parameters (orderID, billAmount, postPaymentUrl, etc.).
// The deprecated parameters (callback, mobileNumber, beneficiaryVPA, remarks, refId)
// should not be used for new application logic.
// The successParams variable might need to be handled based on your specific needs for postPaymentUrl.
let vc = PaymentViewController(
orderID: orderID,
billAmount: billAmount,
postPaymentUrl: postPaymentUrl, // Assuming PaymentViewController is updated for this
customerMobileNumber: customerMobileNumber, // Assuming PaymentViewController is updated
customerId: customerId, // Pass if PaymentViewController accepts it
allowedPaymentModes: allowedPaymentModes // Pass if PaymentViewController accepts it
// Deprecated parameters (callback, mobileNumber, beneficiaryVPA, remarks, refId) are intentionally not passed here;
// use the current parameters (orderID, billAmount, postPaymentUrl, etc.) for the view controller's logic.
)
let navVC = UINavigationController(rootViewController: vc)
present(navVC, animated: true)
case "unload":
// Assuming closeView() is a defined method in this class or extension
closeView()
default:
print("Unknown function to perform: \(functionToPerform)")
break
}
}

The WKWebviewDownloadHelper which downloads our PDF file is used as below

override func viewDidLoad() {
super.viewDidLoad()
let mimeTypes = [MimeType(type: "pdf", fileExtension: "pdf")]
helper = WKWebviewDownloadHelper(webView: webView, mimeTypes: mimeTypes, delegate: self)
webView.configuration.userContentController.add(self, name: "ios_observer")
}

WKWebviewDownloadHelper is dependent on the custom WKNavigationDelegate class

extension WebViewController: WKWebViewDownloadHelperDelegate {
func fileDownloadedAtURL(url: URL) {
DispatchQueue.main.async {
let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self.view
activityVC.popoverPresentationController?.sourceRect = self.view.frame
activityVC.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
self.present(activityVC, animated: true, completion: nil)
}
}
}

Handle payment flow

The initiate_payment case in the userContentController is triggered when the webview executes JavaScript to post a message. The data field of this message should directly contain all the necessary payment parameters, typically like: window.webkit.messageHandlers.ios_observer.postMessage({function: "initiate_payment", data: {orderId: "...", amount: "...", postPaymentUrl: "...", customerMobileNumber: "...", customerId: "...", allowedPaymentModes: "..."}})

The data object itself (when function is initiate_payment) is a direct JSON object (dictionary in Swift) containing all payment details. Your app's userContentController method can access these fields directly from this data dictionary as shown in the Swift code example above.

The data object primarily uses the following fields for current integrations:

  • orderId (String, mandatory): Order ID on the Setu COU System. Example— COUWLZ7pFtTgr9LtO
  • amount (String, mandatory): Amount to be paid by the customer. Example— 3522.20
  • postPaymentUrl (String, mandatory): URL which needs to be loaded in the webview after the payment is completed by your app. Example—https://billpay.setu.co/cou/payment-callback/68c7217b-8fa7-4c1f-8e5f-317ff3027668
  • customerMobileNumber (String, mandatory): Mobile number of the customer used for the session. Example - 9876543210
  • customerId (String, optional): Customer ID linked to a particular session. Example - cust@1234
  • allowedPaymentModes (String, optional): Comma separated list of modes allowed for this payment. One of these should be sent in the paymentMode in the payment confirmation webhook.

Deprecated Fields (may still be present in data object but should not be used for new logic):

The following fields might still be sent by the webview as part of the data object but are considered deprecated. Your application should rely on the current fields listed above for new logic. The Swift code example shows how these can be acknowledged if necessary, but they should not drive core functionality.

  • callback (String): Deprecated. This field has been renamed to postPaymentUrl. Please use postPaymentUrl for all new logic.
  • mobileNumber (String): Deprecated. This field has been renamed to customerMobileNumber. Please use customerMobileNumber for all new logic.
  • beneficiaryVPA (String): Deprecated. This field is no longer functionally used for primary payment processing by Setu's webview and may be removed in future webview updates.
  • remarks (String): Deprecated. This field is no longer functionally used by Setu's webview and may be removed in future webview updates.
  • refId (String): Deprecated. This field is no longer functionally used by Setu's webview and may be removed in future webview updates.

Example of the structure of the data JSON object (when function is initiate_payment), which would primarily contain the current fields, but might also include deprecated ones during a transition:

{
"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 initiate_payment case is called by the webview (with the payment parameters directly in the data object), your app needs to do the following-

  1. Remove the webview
  2. Initiate the payment flow
  3. Send Setu the webhook event for payment—this need not be done for UPI transfers in real time
  4. Once payment is executed, load the webview again with the callback URL

Note that any ViewController relating to the payment flow, will need to set the property isModalInPresentation as true—to prevent user from scrolling back to the previous screen while the transfer of control is in process.


#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.


Was this page helpful?