Hosted Payment Pages Integration

You can integrate BlueSnap’s Hosted Payment Pages into your iOS application using the Swift programming language and WKWebView. This requires that you redirect your shopper to a secure page hosted by BlueSnap to complete the payment.

Prerequisites

  • BlueSnap account (Sandbox or Production).
  • Backend server that can generate a JSON Web Token (JWT) for each transaction. For details about JWT payload requirements such as transaction details and callback URLs (returnSURL, returnFURL, cancelURL), see Create a JWT Token.

Step 1: Generate a JWT

Before you can initiate a payment in your iOS app, your backend server must generate a JWT. This token encapsulates all necessary transaction details and security credentials.

The actual implementation to generate a JWT depends on your backend programming language, but the JWT must meet the following criteria:

  • Sign the JWT with your BlueSnap API key.
  • The JWT payload must include the following:
    • Shopper details
    • Transaction amount and currency
    • Callback URL for each payment outcome for your iOS app (succeeded.html, failed.html, cancelled.html, etc.). These URLs must be relative to your domain or use custom URL schemes for your app.

Step 2: Prepare your Application

  1. Import WebKit. Ensure you import the WebKit framework in your UIViewController:
    import UIKit  
    import WebKit
    
  2. Setup WKWebView and WKNavigationDelegate.
    In your UIViewController that hosts the payment page, set up a WKWebView and conform to WKNavigationDelegate to handle navigation events:
    class CheckoutViewController: UIViewController, WKNavigationDelegate {
        var webView: WKWebView!
        var bluesnapCheckoutURL: String = "https://sandbox.bluesnap.com/buynow/checkout?token=" // For production, use https://ws.bluesnap.com/buynow/checkout?token=
        var generatedJWT: String = "" // This will be fetched from your server
      
        // Define your callback URL prefixes or full URLs
        // These should match what you configure in your JWT or BlueSnap account
        let successURLPrefix = "https://yourdomain.com/succeeded.html" // Or your custom scheme like "yourapp://payment/success"
        let failureURLPrefix = "https://yourdomain.com/failed.html"   // Or "yourapp://payment/failure"
        let cancelURLPrefix = "https://yourdomain.com/cancelled.html" // Or "yourapp://payment/cancel"
      
        override func viewDidLoad() {
            super.viewDidLoad()
      
            let webViewConfiguration = WKWebViewConfiguration()
            webView = WKWebView(frame: view.bounds, configuration: webViewConfiguration)
            webView.navigationDelegate = self
            view.addSubview(webView)
      
            // TODO: Fetch the JWT from your server
            // For demonstration, we'll assume it's populated in `generatedJWT`
            // self.fetchJWT { [weak self] jwt in
            //     guard let self = self, let jwt = jwt else { return }
            //     self.generatedJWT = jwt
            //     self.loadHostedPage()
            // }
            
            // Example: Directly set for testing if JWT is already available
            // self.generatedJWT = "YOUR_SERVER_GENERATED_JWT" 
            // if !self.generatedJWT.isEmpty {
            //    self.loadHostedPage()
            // } else {
            //    print("Error: JWT is missing.")
            //    // Handle missing JWT (e.g., show an error, prevent proceeding)
            // }
        }
      
        func loadHostedPage() {
            guard !generatedJWT.isEmpty else {
                print("Error: JWT is not available.")
                // Handle error: show message to user, etc.
                return
            }
            
            if let url = URL(string: bluesnapCheckoutURL + generatedJWT) {
                let request = URLRequest(url: url)
                webView.load(request)
      
              } else {
                print("Error: Invalid BlueSnap Checkout URL.")
                // Handle error
            }
        }
      
        // Placeholder for fetching JWT from your server
        // func fetchJWT(completion: @escaping (String?) -> Void) {
        //     // Implement your network request to your server to get the JWT
        //     // For example:
        //     // URLSession.shared.dataTask(with: yourServerJWTEndpoint) { data, response, error in
        //     //     // ... handle response, parse JWT ...
        //     //     completion(parsedJWT)
        //     // }.resume()
        //     completion("YOUR_SERVER_GENERATED_JWT_HERE") // Replace with actual fetch
        // }
    }
    

Step 3: Load the BlueSnap Hosted Page

After you have the JWT from your server, construct the full BlueSnap Hosted Page URL and load it into the WKWebView. loadHostedPage() in the previous code snippet provides an example implementation:

class CheckoutViewController: UIViewController, WKNavigationDelegate {
...
		func loadHostedPage() {
        guard !generatedJWT.isEmpty else {
            print("Error: JWT is not available.")
            // Handle error: show message to user, etc.
            return
        }
        
        if let url = URL(string: bluesnapCheckoutURL + generatedJWT) {
            let request = URLRequest(url: url)
            webView.load(request)
  
          } else {
            print("Error: Invalid BlueSnap Checkout URL.")
            // Handle error
        }
    }
		...
}

Step 4: Handle Payment Outcome

The WKNavigationDelegate monitors navigation changes within the WKWebView. When BlueSnap processes the payment, it redirects to one of the callback URLs you specified when you created the JWT.

The following code snippet implements this logic:

  • webView(_:decidePolicyFor:decisionHandler:): This delegate method is called before a navigation request is processed.
  • Inspect the navigationAction.request.url.
  • If the URL matches one of your predefined successURLPrefix, failureURLPrefix, or cancelURLPrefix, the payment flow has completed.
  • You should then call decisionHandler(.cancel) to prevent the WKWebView from loading your callback page, unless the page is designed to be displayed.
  • Handle the outcome in your app. For example, dismiss the CheckoutViewController, update your app’s state, or inform the user.
  • webView(_:didFailProvisionalNavigation:withError:) and webView(_:didFail:withError:) catch loading errors for the BlueSnap page.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url?.absoluteString {
        print("Navigating to: \(url)")

        if url.hasPrefix(successURLPrefix) {
            // Payment Succeeded
            print("Payment Succeeded")
            // Close WebView, show success message, update UI
            // self.dismiss(animated: true, completion: nil) 
            // self.delegate?.paymentDidSucceed()
            decisionHandler(.cancel) // Stop loading the redirect URL itself
            return
        } else if url.hasPrefix(failureURLPrefix) {
            // Payment Failed
            print("Payment Failed")
                // Close WebView, show error message, update UI
                // self.dismiss(animated: true, completion: nil)
                // self.delegate?.paymentDidFail()
                decisionHandler(.cancel)
                return
        } else if url.hasPrefix(cancelURLPrefix) {
            // Payment Cancelled by user
            print("Payment Cancelled")
            // Close WebView, handle cancellation
            // self.dismiss(animated: true, completion: nil)
            // self.delegate?.paymentDidCancel()
            decisionHandler(.cancel)
            return
        }
    }
    decisionHandler(.allow) // Allow other navigations



    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        print("WebView failed to load: \(error.localizedDescription)")
        // Handle error, e.g., show an alert to the user
        // self.dismiss(animated: true, completion: nil)
        // self.delegate?.paymentDidFail(error: error)
    }

    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("WebView navigation failed: \(error.localizedDescription)")
        // Handle error
        // self.dismiss(animated: true, completion: nil)
        // self.delegate?.paymentDidFail(error: error)
    }
}

// Optional: Define a delegate protocol to communicate results back
// protocol CheckoutDelegate: AnyObject {
//    func paymentDidSucceed()
//    func paymentDidFail(error: Error?)
//    func paymentDidCancel()
// }

Step 5: Server-Side Confirmation (Recommended)

The iOS app can immediately determine the payment status with URL redirection, but you must verify the final transaction status with your backend server. You can accomplish this with the following methods:

  • BlueSnap Webhooks — Configure your BlueSnap account to send notifications to your server upon transaction events.
  • API requests — Your server can make API calls to BlueSnap to retrieve the transaction details using the transaction ID.

Server-side confirmation ensures data integrity and handles cases where the client-side communication might be interrupted.

Important considerations

  • JWT Security — Ensure JWTs are generated securely on your server and transmitted to the app over HTTPS. Do not hardcode sensitive API keys in the app.
  • Callback URLs — Custom URL schemes can provide a cleaner way to hand off back to your app. For example, yourapp://payment/success. Configure these in your app’s Info.plist.
    If using HTTPS callback URLs, ensure they are distinct and your app can reliably parse them.
  • User Experience — Provide clear loading indicators while the WKWebView is active and appropriate feedback messages for success, failure, or cancellation.
  • Error Handling — Implement robust error handling for network issues, JWT fetching failures, and WKWebView loading problems.
  • Production URL — Remember to switch bluesnapCheckoutURL to the production BlueSnap endpoint when going live:
    https://ws.bluesnap.com/buynow/checkout?token=