> ## Documentation Index
> Fetch the complete documentation index at: https://telr-docs.cashfree.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Checkout Integration - iOS

> Learn in detail about our custom checkout integration for iOS.

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

**Integration Flow**

* [Create Order](/api-reference/payments/latest/orders/create) - Create an order in Telr 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 Telr. Please check [Whitelisting Process](/payments/online/go-live/whitelist)
</Note>

## Step 1: Create Order

Please follow the steps mentioned in this [link](/payments/online/web/redirect#step-1-creating-an-order) to create an order. This will return a "payment\_session\_id" value which will be used later.

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

## Step 2: Pay Order

```java Sample Code theme={null}
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, Telr will redirect you to the URL specified while creating the order. Detect the URL redirection and close the web view appropriately.

```java Java theme={null}
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](https://docs.cashfree.com/docs/upi#upi-intent).

1. Add the permissions in info.plist file.\
   You need to add the following to your **info.plist**
   ```xml swift theme={null}
   <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

```java Java theme={null}
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
                    }
                }
            }
        }
    }
}
```
