Custom Checkout Integration - iOS

The iOS integration can be completed in three steps and should not take you more than 30 minutes.

Integration Flow

  • Create Order - Create an order in Cashfree Payments system from your backend.
  • Submit the form from your application's web-view as mentioned in this document.
  • Detect the URL redirection and close the web view.

πŸ“˜

NOTE

This integration requires your application's bundle name to be whitelisted by Cashfree Payments. Please check Whitelisting Process


Step 1: Create Order

Please follow the steps mentioned in this link to create an order. This will return a "payment_session_id" value which will be used later.

πŸ“˜

Don't forget to provide the return URL while creating the order as this will be used to detect payment completion.


Step 2: Pay Order

String platform = "iosx-c-x-x-x-w-x-a-" + <BuildVersion>;
String session = "PAYMENT_SESSION_ID";

func createForm(session: String, platform: String) -> String {
        var secureURL = ""
        if CFConstants.environment == .SANDBOX {
            secureURL = "https://sandbox.cashfree.com/pg/view/sessions/checkout"
        } else {
            secureURL = "https://api.cashfree.com/pg/view/sessions/checkout"
        }
        var paymentInputFormHtml =
            "<html>"
                + "<body>"
                + "<form id=\'redirectForm\' name=\'order\' action=\'\(secureURL)\' method=\'post\'>"
        paymentInputFormHtml += "<input type=\'hidden\' name=\'payment_session_id\' value=\'\(session)\'/>" +
            "<input type=\'hidden\' name=\'platform\' value=\'\(platform)\'/>" +
            "</form>" +
        "<script\n" +
                        " type=\"text/javascript\">\t window.onload = function () { const form = document.getElementById(\"redirectForm\"); const meta = { userAgent: window.navigator.userAgent, }; const sortedMeta = Object.entries(meta).sort().reduce((o, [k, v]) => { o[k] = v; return o; }, {}); const base64Meta = btoa(JSON.stringify(sortedMeta)); FN = document.createElement('input'); FN.setAttribute('type', 'hidden'); FN.setAttribute('name', 'browser_meta'); FN.setAttribute('value', base64Meta); form.appendChild(FN); form.submit(); }  </script>\n"
            + "</body>"
            + "</html>";
        return paymentInputFormHtml
    }
override func viewDidLoad(){
self.cfWebView.loadHTMLString(createForm(session: session_id, platform: "") , baseURL: nil)
}

Step 3: Handle Redirection

Once the payment flow has ended, Cashfree Payments will redirect you to the URL specified while creating the order. Detect the URL redirection and close the web view appropriately.

public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    
    // Allowing all navigations
    decisionHandler(.allow)
    
    if navigationAction.request.url?.absoluteString ?? "" == "your return_url" {
        // close the web view
    }
}

UPI Intent from Checkout

If you are doing a custom integration of Cashfree Payments Web Checkout in iOS and want to add UPI intent functionality, follow the steps below. Read more about UPI Intent functionality here.

  1. Add the permissions in info.plist file.
    You need to add the following to your info.plist

    <key>LSApplicationCategoryType</key>
    <string></string>
    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>bhim</string>
      <string>paytmmp</string>
      <string>phonepe</string>
      <string>tez</string>
      <string>credpay</string>
    </array>
    
  2. Add the JSBridge functions for the checkout page to get the list of UPI apps installed and for opening the UPI app selected by the user.

    override func viewDidLoad() {
    
            super.viewDidLoad()
            self.cfWebView.configuration.userContentController.add(self, name: "nativeProcess")
            self.cfWebView.uiDelegate = self
            self.cfWebView.navigationDelegate = self
    }
    
    func getInstalledUPIApplications() -> [[String: String]] {
            var installedApps = [[String: String]]()
            let appName = [
                [
                    "displayName": CFConstants.googlePayName,
                    "id": "tez://",
                    "icon": (UIImage(named: "gpay", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
                ],
                [
                    "displayName": CFConstants.paytmName,
                    "id": "paytmmp://",
                    "icon": (UIImage(named: "paytm", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
                ],
                [
                    "displayName": CFConstants.phonePeName,
                    "id": "phonepe://",
                    "icon": (UIImage(named: "phonepe", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
                ],
                [
                    "displayName": CFConstants.bhimName,
                    "id": "bhim://",
                    "icon": (UIImage(named: "bhim", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
                ],
                [
                    "displayName": CFConstants.credName,
                    "id": "credpay://",
                    "icon": (UIImage(named: "credpay", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
                ]
            ]
            for app in appName {
                let url = app["id"] ?? ""
                if UIApplication.shared.canOpenURL(URL(string: url)!) {
                    installedApps.append(app)
                }
            }
            return installedApps
        }
    
    public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            var msg = message.body as? String ?? ""
            if msg == "getAppList" {
                let installedApps = getInstalledUPIApplications()
                do {
                    let jsonData = try JSONSerialization.data(withJSONObject: installedApps, options: [])
                    let jsonString = String(data: jsonData, encoding: String.Encoding.utf8)!
                    self.cfWebView.evaluateJavaScript("window.receiveAppList(\(jsonString))") { (_, _) in
                    }
                } catch {
                    print("Serialisation Failed")
                }
            } else if msg.contains("paytm") || msg.contains("phonepe") || msg.contains("tez") || msg.contains("bhim") || msg.contains("credpay") {
                if msg.contains("paytm") {
                    msg = msg.replacingOccurrences(of: "paytm:", with: "paytmmp:")
                }
                self.cfWebView.evaluateJavaScript("verifyPaymentForiOS()") { (val, error) in
                    let url = URL(string: msg)!
                    if UIApplication.shared.canOpenURL(url) {
                        UIApplication.shared.open(url, options: [:]) { (response) in
                        }
                    }
                }
            }
        }
    }
    

Sample Code

class CF2FAWebViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate {

@IBOutlet weak var cfWebView: WKWebView!

override func viewDidLoad() {

        super.viewDidLoad()
        self.cfWebView.configuration.userContentController.add(self, name: "nativeProcess")
        self.cfWebView.uiDelegate = self
        self.cfWebView.navigationDelegate = self
        self.cfWebView.loadHTMLString(createForm(session: session_id, platform: "") , baseURL: nil)
}


func getInstalledUPIApplications() -> [[String: String]] {
        var installedApps = [[String: String]]()
        let appName = [
            [
                "displayName": CFConstants.googlePayName,
                "id": CFConstants.googlePayPackage,
                "icon": (UIImage(named: "gpay", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
            ],
            [
                "displayName": CFConstants.paytmName,
                "id": CFConstants.paytmPackage,
                "icon": (UIImage(named: "paytm", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
            ],
            [
                "displayName": CFConstants.phonePeName,
                "id": CFConstants.phonePePackage,
                "icon": (UIImage(named: "phonepe", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
            ],
            [
                "displayName": CFConstants.bhimName,
                "id": CFConstants.bhimPackage,
                "icon": (UIImage(named: "bhim", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
            ],
            [
                "displayName": CFConstants.credName,
                "id": CFConstants.credPackage,
                "icon": (UIImage(named: "credpay", in: Bundle(for: CFUPIUtils.self), compatibleWith: nil)!).toBase64() ?? ""
            ]
        ]
        for app in appName {
            let url = app["id"] ?? ""
            if UIApplication.shared.canOpenURL(URL(string: url)!) {
                installedApps.append(app)
            }
        }
        return installedApps
    }

public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    
    // Allowing all navigations
    decisionHandler(.allow)
    
    if navigationAction.request.url?.absoluteString ?? "" == "your return_url" {
        // close the web view
    }
}

func createForm(session: String, platform: String) -> String {
        var secureURL = ""
        if CFConstants.environment == .SANDBOX {
            secureURL = "https://sandbox.cashfree.com/pg/view/sessions/checkout"
//            secureURL = "https://test-k8s.cashfree.com/pgnextgenapi/api/v1/view/sessions/checkout"
        } else {
            secureURL = "https://api.cashfree.com/pg/view/sessions/checkout"
//            secureURL = "https://prod.cashfree.com/pgnextgenapi-test/api/v1/view/sessions/checkout"
        }
        var paymentInputFormHtml =
            "<html>"
                + "<body>"
                + "<form id=\'redirectForm\' name=\'order\' action=\'\(secureURL)\' method=\'post\'>"
        paymentInputFormHtml += "<input type=\'hidden\' name=\'payment_session_id\' value=\'\(session)\'/>" +
            "<input type=\'hidden\' name=\'platform\' value=\'\(platform)\'/>" +
            "</form>" +
        "<script\n" +
                        " type=\"text/javascript\">\t window.onload = function () { const form = document.getElementById(\"redirectForm\"); const meta = { userAgent: window.navigator.userAgent, }; const sortedMeta = Object.entries(meta).sort().reduce((o, [k, v]) => { o[k] = v; return o; }, {}); const base64Meta = btoa(JSON.stringify(sortedMeta)); FN = document.createElement('input'); FN.setAttribute('type', 'hidden'); FN.setAttribute('name', 'browser_meta'); FN.setAttribute('value', base64Meta); form.appendChild(FN); form.submit(); }  </script>\n"
            + "</body>"
            + "</html>";
        return paymentInputFormHtml
    }


public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        var msg = message.body as? String ?? ""
        if msg == "getAppList" {
            let installedApps = getInstalledUPIApplications()
            do {
                let jsonData = try JSONSerialization.data(withJSONObject: installedApps, options: [])
                let jsonString = String(data: jsonData, encoding: String.Encoding.utf8)!
                self.cfWebView.evaluateJavaScript("window.receiveAppList(\(jsonString))") { (_, _) in
                }
            } catch {
                print("Serialisation Failed")
            }
        } else if msg.contains("paytm") || msg.contains("phonepe") || msg.contains("tez") || msg.contains("bhim") || msg.contains("credpay") {
            if msg.contains("paytm") {
                msg = msg.replacingOccurrences(of: "paytm:", with: "paytmmp:")
            }
            self.cfWebView.evaluateJavaScript("verifyPaymentForiOS()") { (val, error) in
                let url = URL(string: msg)!
                if UIApplication.shared.canOpenURL(url) {
                    UIApplication.shared.open(url, options: [:]) { (response) in
                    }
                }
            }
        }
    }
}