How To - Using iOS to listen for events

How to capture our events from the Card and ACH Token (bloqs) and the Embedded Forms

The following code will help you read the events that our embedded forms and app bloqs send back to the parent window (webView) on iOS:

Using UIKit

(for iOS <= 13)

import UIKit
import WebKit

//Class that handles iFrame display, response retrieval and decoding of the JSON response
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
    var webView: WKWebView!
    
    //Global variables that are changed based on which merchant is creating a transaction
    var appid: String = "T6554252567241061980"
    var clientid: String = "01dffeb784c64d098c8c691ea589eb82"
    
    //Codable object that represents first level of response JSON object
    struct response: Codable {
        let event: String
        let data: Body
    }
    
    //Codable object that represents second level of response JSON object (is dependent on actual contents and will throw an error if field does not exist when decoding JSON)
    struct Body: Codable {
        let token: String
    }
    
    //View object that handles what is being displayed to screen
    override func loadView() {
        //Initial WebKit config and content controller creation
        let webConfiguration = WKWebViewConfiguration()
        let contentController = WKUserContentController()
        
        //JS script creation and setup for when in process of loading iFrame to inject script
        let js: String = "window.addEventListener('message', function(e){ window.webkit.messageHandlers.callbackHandler.postMessage(JSON.stringify(e.data)); });"
        let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
        contentController.removeAllUserScripts()
        contentController.addUserScript(userScript)
        
        //Setup to allow for ContentController class to find and receive response as a result of JS script injection
        contentController.add(
            self,
            name: "callbackHandler"
        )
        webConfiguration.userContentController = contentController
        
        //Required calls to allow iFrame to be displayed on screen
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        webView.navigationDelegate = self
        view = webView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //URL to iFrame initialization
        let myURL = URL(string: "https://secure.forms.qorcommerce.io/vault/card/token/?app_id=\(appid)&client_id=\(clientid)&profile_id=10203047&request_id=12345909&intent=create&cvv_field=true&button_text=Save%20Payment%20Method&textfield_type=filled")
        let myRequest = URLRequest(url: myURL!)
        
        //URL to iFrame loaded
        webView.load(myRequest)
    }
    
    //Function to handle the content being returned from iFrame JS script injection (in this case it is the JSON responses)
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        
        //Checking to see if message.body is not empty, and if so then setting a new variable as a String version of it
        if let bodyString = message.body as? String {
            //Converting new string as a Data object encoded as utf-8 which will allow for it to be decoded as JSON
            let body = Data(bodyString.utf8)
            do {
                //JSON decoder code using the Codable objects initialized at beginning of file
                let jsonResponse: response = try JSONDecoder().decode(response.self, from: body)
                //Checks the "event" field which coincides with the JSON response type received
                if (jsonResponse.event == "success_response") {
                    //Calls PopUpWindow class created in another swift file and displays the token that is created and returned from iFrame
                    //Note: Extra swift file is simply the popup window initializer that contains all the configurations desired such as size, colors and what information it contains for example
                    var popUpWindow: PopUpWindow!
                    popUpWindow = PopUpWindow(title: "Payment Token", text: "\(jsonResponse.data.token)", buttontext: "OK")
                    self.present(popUpWindow, animated: true, completion: nil)
                }
            //Used to catch errors thrown that include JSON encode/decode problems
            } catch {
                //Will print error only to the console of application in Xcode and not to the actual screen
                print(error)
            }
        }
        
    }

}

Using SwiftUI:

(for iOS > 13)

import SwiftUI
import WebKit

//Global variable to hold response token id
var token = ""

//Global variables that are changed based on which merchant is creating a transaction
var appid: String = "T6554252567241061980"
var clientid: String = "01dffeb784c64d098c8c691ea589eb82"

//Codable object that represents first level of response JSON object
struct response: Codable {
    let event: String
    let data: Body
}

//Codable object that represents second level of response JSON object (is dependent on actual contents and will throw an error if field does not exist when decoding JSON)
struct Body: Codable {
    let token: String
}

//View object that handles what is being displayed to screen
struct ContentView: View {
    
    //State variable that allows for Alert to toggle on and off
    @State private var showingAlert = false
        
    var body: some View {
        //Class that handles all JS iFrame needs
        WebView()
        //Example of a UI popup to show the resulting token from tokenization response
        .alert(isPresented: $showingAlert) {
            Alert(title: Text("Response Token"), message: Text("\(token)"), dismissButton: .default(Text("OK")))
        }
        .onReceive(NotificationCenter.default.publisher(for: WebView.showAlertMsg)) { msg in
            self.showingAlert = true // simply change the state of the View
        }
    }
}

//Class that handles calling JS iFrame and injecting JS script to get response back from tokenization process
struct WebView: UIViewRepresentable {
    
    //Static variable that is used when sending a notification to listener in the ContentView to create our example popup
    static let showAlertMsg = Notification.Name("ALERT_MSG")
    
    //Function that sets up WebKit config to handle JS script injection and content controller
    func makeUIView(context: Context) -> WKWebView {
        //Initial WebKit config and content controller creation
        let webConfiguration = WKWebViewConfiguration()
        let wkcontentController = WKUserContentController()
        
        //JS script creation and setup for when in process of loading iFrame to inject script
        let js: String = "window.addEventListener('message', function(e){ window.webkit.messageHandlers.callbackHandler.postMessage(JSON.stringify(e.data)); });"
        let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
        wkcontentController.addUserScript(userScript)
        
        //Setup to allow for ContentController class to find and receive response as a result of JS script injection
        wkcontentController.add(context.coordinator, name: "callbackHandler")
        webConfiguration.userContentController = wkcontentController
        
        //Required calls to allow iFrame to be displayed on screen
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        context.coordinator.parent = webView
        
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        //URL to iFrame initialization
        let request = URLRequest(url: URL(string: "https://secure.forms.qorcommerce.io/vault/card/token/?app_id=\(appid)&client_id=\(clientid)&profile_id=10203047&request_id=12345909&intent=create&cvv_field=true&button_text=Save%20Payment%20Method&textfield_type=filled")!)
        
        //URL to iFrame loaded
        uiView.load(request)
    }
    
    //Function that allows ContentController to be handled by an iOS handler
    func makeCoordinator() -> ContentController {
        ContentController()
    }
    
    //Class that contains ContentController functionality
    class ContentController: NSObject, WKScriptMessageHandler {

        weak var parent: WKWebView?

        //Function to handle the content being returned from iFrame JS script injection (in this case it is the JSON responses)
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            
            //Checking to see if message.body is not empty, and if so then setting a new variable as a String version of it
            if let bodyString = message.body as? String {
                //Converting new string as a Data object encoded as utf8 which will allow for it to be decoded as JSON
                let body = Data(bodyString.utf8)
                do {
                    //JSON decoder code using the Codable objects initialized at beginning of file
                    let jsonResponse: response = try JSONDecoder().decode(response.self, from: body)
                    //Checks the "event" field which coincides with the JSON response type received
                    if (jsonResponse.event == "success_response") {
                        //Set global "token" variable as the token found in JSON response
                        token = jsonResponse.data.token
                        //Send out notification so that the Alert created in ContentView knows to display itself on screen
                        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                            NotificationCenter.default.post(name: WebView.showAlertMsg, object: nil)
                        }
                    }
                //Used to catch errors thrown that include JSON encode/decode problems
                } catch {
                    //Will print error only to the console of application in Xcode and not to the actual screen
                    print(error)
                }
            }
        }
    }

}