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"
// 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
}
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.
-
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>
-
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
}
}
}
}
}
}
Updated 10 months ago