payment IOS app
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.

This repository is intended to demonstrate how mobile payment is working; based on following components :

  • View to collect payment information (email, amount) and submit payment (Casual payment & Apple Pay if enabled)
  • View to indicate payment result
  • Few class utils to perform payment workflow (communication with your backend, detect end of payment, detect expiration)

Table of contents

Quick start

Several quick start options are available:

  • Download [WebviewServices] & [WebviewViewController], and import it your xcode project.
  • Clone the repo: git clone, and start hacking.


Tested in Xcode 8.3.1, written in Swift 3, Demo app require Ios 9.3, or more.

What's included

|   |-- Utils.swift 
|   |-- Translator.swift
|   |-- progressHUD.swift
|   |-- WebviewUrlUtil.swift
|   |-- PaymentService.swift

We used this app as a demo for our sales; Payment integration is separated from rest of the app.

Bugs and feature requests

Have a bug or a feature request? please open a new issue.

General information

You will find whole documentation in official website :

Payment throught webviews

Payment throught webview is realized by the WebviewViewController, and utils classes include in WebviewServices.


WebviewServices is a directory containing two helpers classes.

PaymentService is responsible to communicate with your backend to get redirected payment url. It offer a completion handler, to use in WebviewViewController, returning two values :

  • Status of the request (Boolean)
  • Payment url (nil if status is false)
/// Build an URLRequest accordind : server url, email passed, amount passed, mode passed, lang passed
/// - Returns: URLRequest
func buildRequest() -> URLRequest {
    let myUrl: NSURL = NSURL(string: "\(PaymentService.SERVER_URL)/\(email)/\(amount)/\(mode)/\(lang)/\(type_card)")!
    var request = URLRequest(url:myUrl as URL)
    request.httpMethod = "GET"
    return request

/// Call server to get payment url, supply a block completion (callback)
/// - Returns: status boolean, payment url
func getPaymentContext(completion: @escaping (Bool, String, [[String : AnyObject]]?) -> ()){
    // Used to store service result and return to completion
    var urlPayment: String = ""

    // Build request
    let request = buildRequest()

    // Call server to obtain a payment Url
    // Completion is a callback, giving call status, and Payment url if success
    let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
        if error != nil{
            completion(false, "", nil )

        if let httpResponse = response as? HTTPURLResponse {
            if(httpResponse.statusCode == 200){
                // Everythings works
                do {
                    let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
                    if let parseJSON = json {
                        urlPayment = (parseJSON["redirect_url"] as? String)!
                        let cookieData = parseJSON["cookies"] as? [[String : AnyObject]]
                        completion(true, urlPayment, cookieData)
                } catch {
                    completion(false, "", nil)
                completion(false,"", nil)

static func buildWebviewUrlUtil(navigationAction: WKNavigationAction) -> WebviewUrlUtil{
    let arrayHost = navigationAction.request.url?.host?.components(separatedBy: ".")
    let paymentStatus = arrayHost?[(arrayHost?.count)!-1]

    let paramsQueryArray = navigationAction.request.url?.query?.components(separatedBy:"&")
    var dataQueryArray = [String:Any]()
    for row in paramsQueryArray! {
        let pairs = row.components(separatedBy:"=")
        dataQueryArray[pairs[0]] = pairs[1]

    let webviewUrlUtil = WebviewUrlUtil(paymentStatus: paymentStatus!, dataQueryArray: dataQueryArray)
    return webviewUrlUtil

Dont forget to modify SERVER_URL according your backend

WebviewUrlUtil is small helper class which stored get params returned by the payment platform (payment status, transaction number, authorization status etc.)

/// Return parameter include in Url
/// - Parameter name: <#name description#>
/// - Returns: <#return value description#>
func getParameter(name: String) -> String {
    guard let param = dataQueryArray[name] else {
        return ""
    return param as! String

/// Return true if url contains "success" or "return"
/// - Returns: <#return value description#>
func isSuccess() -> Bool {
    return paymentStatus == "success" || paymentStatus == "return"
    // or check vads_trans_status => getParameter("vads_trans_status") == "AUTHORIZED"


WebviewViewController is a view controller dealing with payment workflow :

  • Run a PaymentService, waiting for payment Url
let paymentService = PaymentService(email: email, amount: amount, mode: mode, lang: lang, type_card: type_card)

// Call server, and receive call status, and url payment
// When you get response for your server
paymentService.getPaymentContext { status, urlPayment, cookies in
    // If we get an error
            // present error screen
            self.goToView(reason: "NETWORK", increaseSize: true)
    // If we get a payment Url
            // urlPayment is the Url given by your payment platform
            let url = NSURL(string:urlPayment)
            let req = NSURLRequest(url:url! as URL)

            // init webview


Open up a WKWebview

// We create and load a webview pointing to this Url
self.automaticallyAdjustsScrollViewInsets = false
self.navigationController?.isNavigationBarHidden = true;
self.webView = WKWebView()

// mandatory to detect end of payment
self.webView.navigationDelegate = self
self.webView!.load(req as URLRequest)
self.webView.scrollView.frame = self.webView.frame
self.webView.scrollView.contentInset = UIEdgeInsetsMake(20,0,0,0)
self.webView.scrollView.delegate = self
self.webView.layer.zPosition = 999

self.webView.scrollView.bounces = false                    // Things like this should be handled in web code
self.webView.allowsBackForwardNavigationGestures = true   // Disable swiping to navigate

self.view = self.webView

self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.loading), options: .new, context: nil)

Analyse every Url's running inside the webview to detect payment status

/// Callback when an Url change inside webview
/// - Parameters:
///   - webView: <#webView description#>
///   - navigationAction: <#navigationAction description#>
///   - decisionHandler: <#decisionHandler description#>
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void){
    // Cause html link with "target = _blank"
    if navigationAction.targetFrame == nil {

    // We detect end of payment
    if isCallBackUrl(url: (navigationAction.request.url?.absoluteString)!) {
        goToFinalController(navigationAction: navigationAction)
    // We detect a page that should be open in a separate browser
    }else if isUrlToOpenedSeparately(url: (navigationAction.request.url?.absoluteString)!) {
    // We detect that a link in expiration page have been cliked
    }else if isExperationUrl(url: (navigationAction.request.url?.absoluteString)!) {
        goToView(reason: "EXPIRATION", increaseSize: false)