2 min read

Customize your webview

Out of date

To customize your webview you need to inject your own view into the web checkout builder.

Use this guide to add the following customised elements to your webview:

  • Customise the webview navigation
  • Customise the User Agent
  • Customise the post message string
  • Customise script handling
  • Customise the web view configuration

In the below example, a custom webview called customWebView is injected. This webview conforms to the builder pattern CheckoutWebViewBuilder

You can change its behaviour by injecting the relevant part of your customisation.

Use this example to see how to provide a custom webview:

import PoqWebCheckout
import PoqFoundation
import PoqPlatform
class ClientModule: PoqModule {
func didAddToPlatform() {
// Override platform's deeplink logic.
let navigator = NavigationHelper.sharedInstance
navigator.mapRoute(navigator.cartTransferURL, toDestination: { _ in
navigator.openController(ClientModule.createWebCheckoutViewController())
})
}
static func createWebCheckoutViewController() -> UIViewController {
let router = CheckoutRouter(navigator: NavigationHelper.sharedInstance)
let supportedLocations = [CheckoutLocationDomainModel(host: "poq-web-carttransferdemo.azurewebsites.net", port: nil), CheckoutLocationDomainModel(host: "poq-web-carttransferdemo-uat.azurewebsites.net", port: nil)]
let checkoutBridgeConfiguration = CheckoutBridgeConfiguration(supportedLocations: supportedLocations, checkoutPaymentConfiguration: nil)
let checkoutType: CheckoutWebViewType = .bridge(configuration: checkoutBridgeConfiguration)
let customWebView = CheckoutWebViewBuilder(type: checkoutType).build()
return WebCheckoutBuilder<PoqCheckoutState>(router: router, type: checkoutType)
.withWebCheckoutView(customWebView)
.build()
}
}

Customise handling cookies

To change how cookies are handled in the web checkout, you need to inject your own CheckoutCookieStore in the CheckoutWebViewBuilder.

This protocol takes in a boolean shouldRefreshAllCookies on concrete class initialisations, which determines whether the cookies are clear/refreshed every time the web checkout is opened (just before they are collected from the start checkout API response) and injects them.

It implements the function handleCookies which handles the cookies that are sent from the backend. It does nothing if cookies are nil and also clears previous cookies beforehand if the flag shouldRefreshAllCookies is enabled.

import PoqWebCheckout
import PoqFoundation
import PoqPlatform
class ClientModule: PoqModule {
func didAddToPlatform() {
// Override platform's deeplink logic.
let navigator = NavigationHelper.sharedInstance
navigator.mapRoute(navigator.cartTransferURL, toDestination: { _ in
navigator.openController(ClientModule.createWebCheckoutViewController())
})
}
static func createWebCheckoutViewController() -> UIViewController {
let router = CheckoutRouter(navigator: NavigationHelper.sharedInstance)
let supportedLocations = [CheckoutLocationDomainModel(host: "poq-web-carttransferdemo.azurewebsites.net", port: nil), CheckoutLocationDomainModel(host: "poq-web-carttransferdemo-uat.azurewebsites.net", port: nil)]
let checkoutBridgeConfiguration = CheckoutBridgeConfiguration(supportedLocations: supportedLocations, checkoutPaymentConfiguration: nil)
let checkoutType: CheckoutWebViewType = .bridge(configuration: checkoutBridgeConfiguration)
let customCookieStore = CustomCheckoutCookieStore(shouldRefreshAllCookies: true)
let customWebView = CheckoutWebViewBuilder(type: checkoutType)
.withCookieStore(customCookieStore)
.build()
return WebCheckoutBuilder<PoqCheckoutState>(router: router, type: checkoutType)
.withWebCheckoutView(customWebView)
.build()
}
}
class CustomCheckoutCookieStore: CheckoutCookieStore {
var shouldRefreshAllCookies: Bool
init(shouldRefreshAllCookies: Bool) {
self.shouldRefreshAllCookies = shouldRefreshAllCookies
}
func handleCookies(cookies: [HTTPCookie]?, completion: @escaping (() -> Void)) {
// Your custom logic goes here
}
}

Customise the webview navigation

It is possible to inject your own Navigation Delegate for your webview. This component is in charge of handling the web view navigation by passing back the changes of URL to the controller.

import PoqWebCheckout
import PoqFoundation
import PoqPlatform
import WebKit
class ClientModule: PoqModule {
func didAddToPlatform() {
// Override platform's deeplink logic.
let navigator = NavigationHelper.sharedInstance
navigator.mapRoute(navigator.cartTransferURL, toDestination: { _ in
navigator.openController(ClientModule.createWebCheckoutViewController())
})
}
static func createWebCheckoutViewController() -> UIViewController {
let router = CheckoutRouter(navigator: NavigationHelper.sharedInstance)
let supportedLocations = [CheckoutLocationDomainModel(host: "poq-web-carttransferdemo.azurewebsites.net", port: nil), CheckoutLocationDomainModel(host: "poq-web-carttransferdemo-uat.azurewebsites.net", port: nil)]
let checkoutBridgeConfiguration = CheckoutBridgeConfiguration(supportedLocations: supportedLocations, checkoutPaymentConfiguration: nil)
let checkoutType: CheckoutWebViewType = .bridge(configuration: checkoutBridgeConfiguration)
let customWebViewNavigationDelegate = CustomWebViewNavigationDelegate()
let customWebView = CheckoutWebViewBuilder(type: checkoutType)
.withNavigationDelegate(customWebViewNavigationDelegate)
.build()
return WebCheckoutBuilder<PoqCheckoutState>(router: router, type: checkoutType)
.withWebCheckoutView(customWebView)
.build()
}
}
public class CustomWebViewNavigationDelegate: NSObject, WebViewNavigationDelegate {
public var checkoutWebViewPresenter: CheckoutWebViewDelegate?
public var webViewNavigationData: WebViewNavigationData?
public var currentLoadingURL: String?
// MARK: - WKNavigationDelegate.
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Your custom implementation goes here.
}
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// Your custom implementation goes here.
decisionHandler(.allow)
}
}

Customise the User Agent

The current User Agent complies with RFC standard (RFC 7231) in this format:

Full User Agent:

user-agent | Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) PoqDemoApp-UAT/13.2.0

Where the default value that the iPhone adds is:

user-agent | Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)
and the value that we concatenate is
PoqDemoApp-UAT/13.2.0

This value, that we concatenate, has two parts separated by the symbol /: The left part and the right part.

  • The left part: This is string value is taken from the client PLIST value of the key “PoqUserAgent”, therefore, any client can add whatever value they’d like in it. We recommend that you should provide the client name plus the environment.

  • The right part: This value gets automatically added by getting the CFBundleShortVersionString of the App.

If being able to provide your own left part of the string above is not enough configuration for your solution, you could inject your own User Agent and it will be concatenated to the end of the iOS User Agent.

You can inject your own user agent by providing a string value to the builder as follows:

import PoqWebCheckout
import PoqFoundation
import PoqPlatform
class ClientModule: PoqModule {
func didAddToPlatform() {
// Override platform's deeplink logic.
let navigator = NavigationHelper.sharedInstance
navigator.mapRoute(navigator.cartTransferURL, toDestination: { _ in
navigator.openController(ClientModule.createWebCheckoutViewController())
})
}
static func createWebCheckoutViewController() -> UIViewController {
let router = CheckoutRouter(navigator: NavigationHelper.sharedInstance)
let supportedLocations = [CheckoutLocationDomainModel(host: "poq-web-carttransferdemo.azurewebsites.net", port: nil), CheckoutLocationDomainModel(host: "poq-web-carttransferdemo-uat.azurewebsites.net", port: nil)]
let checkoutBridgeConfiguration = CheckoutBridgeConfiguration(supportedLocations: supportedLocations, checkoutPaymentConfiguration: nil)
let checkoutType: CheckoutWebViewType = .bridge(configuration: checkoutBridgeConfiguration)
let customUserAgent = "CustomUserAgent"
let customWebView = CheckoutWebViewBuilder(type: checkoutType)
.withUserAgent(customUserAgent)
.build()
return WebCheckoutBuilder<PoqCheckoutState>(router: router, type: checkoutType)
.withWebCheckoutView(customWebView)
.build()
}
}

In this example, the User Agent output of the above customisation will be:

user-agent | Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CustomUserAgent

Customise the post message string

If your website needs to communicate to the App through a different name in the Script Handler, you can provide your own message string as follows:

import PoqWebCheckout
import PoqFoundation
import PoqPlatform
class ClientModule: PoqModule {
func didAddToPlatform() {
// Override platform's deeplink logic.
let navigator = NavigationHelper.sharedInstance
navigator.mapRoute(navigator.cartTransferURL, toDestination: { _ in
navigator.openController(ClientModule.createWebCheckoutViewController())
})
}
static func createWebCheckoutViewController() -> UIViewController {
let router = CheckoutRouter(navigator: NavigationHelper.sharedInstance)
let supportedLocations = [CheckoutLocationDomainModel(host: "poq-web-carttransferdemo.azurewebsites.net", port: nil), CheckoutLocationDomainModel(host: "poq-web-carttransferdemo-uat.azurewebsites.net", port: nil)]
let checkoutBridgeConfiguration = CheckoutBridgeConfiguration(supportedLocations: supportedLocations, checkoutPaymentConfiguration: nil)
let checkoutType: CheckoutWebViewType = .bridge(configuration: checkoutBridgeConfiguration)
let customPostMessage = "CustomPostMessageString"
let customWebView = CheckoutWebViewBuilder(type: checkoutType)
.withPostMessageString(customPostMessage)
.build()
return WebCheckoutBuilder<PoqCheckoutState>(router: router, type: checkoutType)
.withWebCheckoutView(customWebView)
.build()
}
}

Changing this behaviour makes it very likely that you will also have to change the script handler.

Customise script handling

You can provide your own script handler by injecting a concrete implementation of the protocol ScriptMessageHandlerDelegate into the CheckoutWebViewBuilder builder.

This implementation should handle the message that the App receives from the web. The default implementation of this protocol sends back the message to the controller.

import PoqWebCheckout
import PoqFoundation
import PoqPlatform
import WebKit
class ClientModule: PoqModule {
func didAddToPlatform() {
// Override platform's deeplink logic.
let navigator = NavigationHelper.sharedInstance
navigator.mapRoute(navigator.cartTransferURL, toDestination: { _ in
navigator.openController(ClientModule.createWebCheckoutViewController())
})
}
static func createWebCheckoutViewController() -> UIViewController {
let router = CheckoutRouter(navigator: NavigationHelper.sharedInstance)
let supportedLocations = [CheckoutLocationDomainModel(host: "poq-web-carttransferdemo.azurewebsites.net", port: nil), CheckoutLocationDomainModel(host: "poq-web-carttransferdemo-uat.azurewebsites.net", port: nil)]
let checkoutBridgeConfiguration = CheckoutBridgeConfiguration(supportedLocations: supportedLocations, checkoutPaymentConfiguration: nil)
let checkoutType: CheckoutWebViewType = .bridge(configuration: checkoutBridgeConfiguration)
let customScriptHandler = CustomScriptMessageHandlerDelegate()
let customWebView = CheckoutWebViewBuilder(type: checkoutType)
.withScriptMessageHandler(customScriptHandler)
.build()
return WebCheckoutBuilder<PoqCheckoutState>(router: router, type: checkoutType)
.withWebCheckoutView(customWebView)
.build()
}
}
class CustomScriptMessageHandlerDelegate: NSObject, ScriptMessageHandlerDelegate {
var checkoutWebViewPresenter: CheckoutWebViewDelegate?
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// Your custom implementation goes here.
}
}

Customise the web view configuration

If you’d like to completely replace the webview configuration of your Poq App, you can do this through the CheckoutWebViewBuilder.

If you provide your own configuration, you also need to set:

  • A user agent (applicationNameForUserAgent).
  • A script handler.
  • A post message string to your own configuration.

Example:

import PoqWebCheckout
import PoqFoundation
import PoqPlatform
import WebKit
class ClientModule: PoqModule {
func didAddToPlatform() {
// Override platform's deeplink logic.
let navigator = NavigationHelper.sharedInstance
navigator.mapRoute(navigator.cartTransferURL, toDestination: { _ in
navigator.openController(ClientModule.createWebCheckoutViewController())
})
}
static func createWebCheckoutViewController() -> UIViewController {
let router = CheckoutRouter(navigator: NavigationHelper.sharedInstance)
let supportedLocations = [CheckoutLocationDomainModel(host: "poq-web-carttransferdemo.azurewebsites.net", port: nil), CheckoutLocationDomainModel(host: "poq-web-carttransferdemo-uat.azurewebsites.net", port: nil)]
let checkoutBridgeConfiguration = CheckoutBridgeConfiguration(supportedLocations: supportedLocations, checkoutPaymentConfiguration: nil)
let checkoutType: CheckoutWebViewType = .bridge(configuration: checkoutBridgeConfiguration)
let customUserAgent = "CustomUserAgent"
let postMessageString: String = "poqWebCheckout"
let scriptMessageHandler: ScriptMessageHandlerDelegate = PoqScriptMessageHandlerDelegate()
let config = WKWebViewConfiguration()
config.userContentController.add(scriptMessageHandler, name: postMessageString)
config.applicationNameForUserAgent = customUserAgent
let customWebView = CheckoutWebViewBuilder(type: checkoutType)
.withConfiguration(config)
.build()
return WebCheckoutBuilder<PoqCheckoutState>(router: router, type: checkoutType)
.withWebCheckoutView(customWebView)
.build()
}
}