Skip to content

Commit

Permalink
Add support for checkout events
Browse files Browse the repository at this point in the history
* Add support for checkout events

- Add a new message handler name that will work as the "new" native bridge.
- Add delegate for receiving events from the Checkout in the web view.

* Mark initializer as public

* Revert "Mark initializer as public"

This reverts commit 1f3f930.

* Set initializer as public

* Refactor code for handling url-schemes when not opt:ed in

* Add availability check for open URL function

* Remove invalid character

* Set TrustlyCheckoutDelegate as public

* Set all internal structs and protocols to public

* Remove the canOpenURL check in case the app did not whitelist the URL

* Specify required platform and refactor the parsing of the checkout event.

* Update README to reflect SDK changes
  • Loading branch information
victor-keinander authored Apr 19, 2021
1 parent cd08e34 commit d3d66d9
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 29 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PackageDescription

let package = Package(
name: "TrustlyIosSdk",
platforms: [.iOS(.v9)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
Expand Down
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
# TrustlyIosSdk
# Trustly iOS SDK

A description of this package.
The Trustly iOS SDK provides an easy way to implement the Trustly Checkout in your iOS app. The SDK handles communication with the Web View and exposes Checkout events that allows you to customize your Checkout flow.

## Integration
Add the SDK as a Swift Package. [More detailed intructions can be found here.](https://www.trustly.net/site/developer-portal?part=iosandroid)
1. Navigate to File -> Swift Packages -> Add Package Dependency.
2. Paste the Trustly SDK URL: https://github.com/trustly/TrustlyIosSdk
3. Select Up to Next Major version and make sure you have the latest version
4. Press finish.
5. You should now see the swift package in the project navigator.

## Usage
Pass your Checkout URL when initialising a new TrustlyWKWebView instance. The Checkout will be rendered within the TrustlyWKWebView.

Example usage:
```swift
let trustlyWebView = TrustlyWKWebView(checkoutUrl: trustlyCheckoutURLString, frame: self.view.frame)
self.view = trustlyWebView
```
### Receiving Checkout Events
If you want more control of your Checkout flow you can choose to opt-in to receiving and handling Checkout events.

You can opt-in by setting the delegate of the TrustlyWKWebView
```swift
trustlyWebView?.delegate = self
```
and conforming to the TrustlyCheckoutDelegate protocol
```swift
class ViewController: UIViewController, TrustlyCheckoutDelegate {

func onTrustlyCheckoutRequstToOpenURLScheme(urlScheme: String) {
//Requests to open URLs or third party applications.
}

func onTrustlyCheckoutSuccessfull(urlString: String?) {

}

func onTrustlyCheckoutError() {

}

func onTrustlyCheckoutAbort(urlString: String?) {

}
}
```

## Notes about URLScheme
Please note that when rendering the Trustly Checkout from a native app you are required to pass your application’s [URLScheme](https://developer.apple.com/documentation/xcode/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app) as an attribute to the order initiation request. By doing so, Trustly can redirect users back to your app after using external identification apps such as Mobile BankID. You can pass your URLScheme by including it in the "URLScheme" attribute when making an API call to Trustly. [You can read more about it here.](https://www.trustly.net/site/developer-portal?part=iosandroid)
44 changes: 44 additions & 0 deletions Sources/TrustlyIosSdk/TrustlyCheckoutDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 Trustly Group AB
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

import Foundation

public enum TrustlyCheckoutEvent: String {
case openURLScheme = "onTrustlyCheckoutRedirect"
case success = "onTrustlyCheckoutSuccess"
case error = "onTrustlyCheckoutError"
case abort = "onTrustlyCheckoutAbort"
}


public protocol TrustlyCheckoutDelegate: class {
/// Called when Checkout receives a request to open a URL Scheme.
func onTrustlyCheckoutRequstToOpenURLScheme(urlScheme: String)
/// Called when Checkout transaction is complete.
func onTrustlyCheckoutSuccessfull(urlString: String?)
/// Called when the users abort the Checkout.
func onTrustlyCheckoutAbort(urlString: String?)
/// Called when an error occurs in the Checkout.
func onTrustlyCheckoutError()
}
86 changes: 73 additions & 13 deletions Sources/TrustlyIosSdk/TrustlyOpenURLScheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,90 @@

import Foundation
import WebKit
import UIKit

/**
Will try to open the URL, then return result in callback
:param: JSON
*/
public class TrustlyWKScriptOpenURLScheme: NSObject, WKScriptMessageHandler {

public static let NAME = "trustlyOpenURLScheme"
weak var trustlyCheckoutDelegate: TrustlyCheckoutDelegate?

var webView: WKWebView

public init(webView: WKWebView) {
/// Name of the "native bridge" that will be used to communicate with the web view.
public static let NAME = "trustlySDKBridge"

init(webView: WKWebView) {
self.webView = webView
}


/**
Function to handle messages from the web client rendered in the Web View.
*/
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if let parsed = getParsedJSON(object: message.body as AnyObject),
let callback: String = parsed.object(forKey: "callback") as? String,
let urlscheme: String = parsed.object(forKey: "urlscheme") as? String
{
UIApplication.shared.openURL(NSURL(string: urlscheme)! as URL)
let js: String = String(format: "%@", [callback, urlscheme])

guard let parsedCheckoutEventObject = getParsedJSON(object: message.body as AnyObject) else {
print("TRUSTLY SDK: Message posted from script handler has an invalid format")
return
}

guard let eventType = parsedCheckoutEventObject.object(forKey: "type") as? String else {
print("TRUSTLY SDK: Found no type property on checkout event")
return
}

guard let trustlyCheckoutEvent = TrustlyCheckoutEvent(rawValue: eventType) else {
print("TRUSTLY SDK: Checkout event type not recognized")
return
}

let url: String? = parsedCheckoutEventObject.object(forKey: "url") as? String ?? nil

/// Check if the SDK user have opted into using TrustlyCheckoutEventDelegate
if trustlyCheckoutDelegate != nil {
handleCheckoutEvent(checkoutEvent: trustlyCheckoutEvent, url: url)
return
}

//Only allow redirect events to reach legacy code below, all other event types were introduced with the TrustlyCheckoutDelegate.
if trustlyCheckoutEvent != .openURLScheme {
return
}

/// Handle the message the legacy way to ensure backwards compability.
if let urlScheme = parsedCheckoutEventObject.object(forKey: "url") as? String {

if let url = URL(string: urlScheme) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}

let js: String = String(format: "%@", ["", urlScheme])
webView.evaluateJavaScript(js, completionHandler: nil)
}
}

/**
Validate and call the correct delegate method for the event.
- Parameter jsonObject: The json object sent from the Web Client.
*/
func handleCheckoutEvent(checkoutEvent: TrustlyCheckoutEvent, url: String?) {

switch checkoutEvent {
case .openURLScheme:
if let urlSchemeString = url as String? {
self.trustlyCheckoutDelegate?.onTrustlyCheckoutRequstToOpenURLScheme(urlScheme: urlSchemeString)
}
case .success:
self.trustlyCheckoutDelegate?.onTrustlyCheckoutSuccessfull(urlString: url)
case .error:
self.trustlyCheckoutDelegate?.onTrustlyCheckoutError()
case .abort:
self.trustlyCheckoutDelegate?.onTrustlyCheckoutAbort(urlString: url)
}

}

/**
Helper function that will try to parse AnyObject to JSON and return as NSDictionary
Expand Down
35 changes: 21 additions & 14 deletions Sources/TrustlyIosSdk/TrustlyWKWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,39 @@ import WebKit
import SafariServices

public class TrustlyWKWebView: UIView, WKNavigationDelegate, WKUIDelegate, SFSafariViewControllerDelegate {
var trustlyView: WKWebView?


var webView: WKWebView?
var trustlyWKScriptHandler: TrustlyWKScriptOpenURLScheme!

public weak var delegate: TrustlyCheckoutDelegate? {
didSet {
trustlyWKScriptHandler.trustlyCheckoutDelegate = delegate
}
}

public init?(checkoutUrl: String, frame: CGRect) {
super.init(frame: frame)

let userContentController: WKUserContentController = WKUserContentController()
let configuration: WKWebViewConfiguration = WKWebViewConfiguration()
configuration.userContentController = userContentController
configuration.preferences.javaScriptCanOpenWindowsAutomatically = true

webView = WKWebView(frame: frame, configuration: configuration)
guard let webView = webView else { return nil }

trustlyView = WKWebView(frame: frame, configuration: configuration)
guard let trustlyView = trustlyView else { return nil }

trustlyView.navigationDelegate = self
trustlyView.uiDelegate = self
trustlyView.navigationDelegate = self
trustlyView.uiDelegate = self
webView.navigationDelegate = self
webView.uiDelegate = self

userContentController.add(
TrustlyWKScriptOpenURLScheme(webView: trustlyView), name: TrustlyWKScriptOpenURLScheme.NAME)
trustlyWKScriptHandler = TrustlyWKScriptOpenURLScheme(webView: webView)
userContentController.add(trustlyWKScriptHandler, name: TrustlyWKScriptOpenURLScheme.NAME)

if let url = URL(string: checkoutUrl) {
trustlyView.load(URLRequest(url: url))
trustlyView.allowsBackForwardNavigationGestures = true
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}

addSubview(trustlyView)
addSubview(webView)
}

public required init?(coder aDecoder: NSCoder) {
Expand Down

0 comments on commit d3d66d9

Please sign in to comment.