Mobile SDK

iOS

Edit "iOS" on GitHub

This guide assumes that you are using the Merchant Backend Configuration and your backend implements the Merchant Backend API. If you are using a custom backend instead, the meaning of SwedbankPaySDKController arguments will be different, as well as any errors reported, but the basic process is the same. The differences will be highlighted in the chapter on custom backends.

Installation

The iOS component of the Swedbank Pay Mobile SDK is split into two libraries: SwedbankPaySDK contains the core SDK, while SwedbankPaySDKMerchantBackend contains utilities for interfacing with the Merchant Backend API. If you are using a custom backend, you to not need to install the SwedbankPaySDKMerchantBackend library.

Swift Package Manager

The SDK is available through the Swift Package Manager. This is the simplest way of adding the SDK to an Xcode project. Follow the Xcode documentation to add a SwiftPM dependency.

The package repository URL for the SDK is https://github.com/SwedbankPay/swedbank-pay-sdk-ios.git. Add the SwedbankPaySDK library, and the SwedbankPaySDKMerchantBackend if needed.

CocoaPods

The SDK is also available through CocoaPods. There are two pods: SwedbankPaySDK for the core SDK, and SwedbankPaySDKMerchantBackend for the Merchant Backend utilities.

Add the relevant dependencies in your Podfile:

1
pod 'SwedbankPaySDK', '~> 4.0.5'
1
pod 'SwedbankPaySDKMerchantBackend', '~> 4.0.5'

Url Scheme and Associated Domain

The Payment Url handling in the iOS SDK uses Universal Links, and additionally a custom url scheme as a fallback mechanism. You must therefore set these up in the app before using the SDK.

The easiest way to add a url scheme to your app is to select the project file, go to the Info tab, scroll down to URL Types, and click the + button to add a new scheme. Insert a single unique url scheme to the URL Schemes field. You can choose the url Identifier freely, but remember that that too should be unique. The Role for the url type should be Editor. Finally, to mark this url type as the Swedbank Pay payment url scheme, open the Additional url type properties, and add a property with the key com.swedbank.SwedbankPaySDK.callback, type Boolean, and value YES.

Payment url scheme added in project Info tab

You can also edit the Info.plist file directly, if you wish.

Payment url scheme added in Info.plist editor

To set up universal links in your application, you first need to add the Associated Domains capability. Then, add your Merchant Backend’s domain as an applinks associated domain. Additionally, your merchant backend must have the appropriate Apple App Site Association file configured.

Associated Domains Configured

Usage

sequenceDiagram
    participant App
    participant SDK
    participant Merchant
    participant SwedbankPay as Swedbank Pay
    participant Ext as External App

    rect rgba(238, 112, 35, 0.05)
        note left of App: Configuration
        App ->> SDK: SwedbankPaySDK.MerchantBackendConfiguration(backendUrl: "https://example.com/swedbank-pay-mobile/", headers: [:])
        SDK -->> App: configuration
    end

    opt Unless Guest Payment
        App ->> SDK: SwedbankPaySDK.Consumer(language = ..., shippingAddressRestrictedToCountryCodes = ...)
        SDK -->> App: consumer
    end

    rect rgba(138, 205, 195, 0.1)
        note left of App: Prepare Payment
        App ->> SDK: PaymentOrderUrls(configuration: configuration, language: ...)
        SDK -->> App: paymentOrderUrls
        App ->> SDK: PaymentOrder(urls: paymentOrderUrls, ...)
        SDK -->> App: paymentOrder
    end

    App ->> SDK: SwedbankPaySDKController(configuration: configuration, consumer: consumer, paymentOrder: paymentOrder)
    SDK -->> App: swedbankPaySdkController
    App ->> SDK: swedbankPaySdkController.delegate = ...
    App ->> App: Show swedbankPaySdkController

    rect rgba(138, 205, 195, 0.1)
        note left of App: Discover Endpoints
        SDK ->> Merchant: GET /swedbank-pay-mobile/
        Merchant -->> SDK: { "consumers": "/swedbank-pay-mobile/consumers", "paymentorders": "/swedbank-pay-mobile/paymentorders" }
    end

    opt Unless Guest Payment
        SDK ->> Merchant: POST /swedbank-pay-mobile/consumers
        Merchant ->> SwedbankPay: POST /psp/consumers
        SwedbankPay -->> Merchant: rel: view-consumer-identification
        Merchant -->> SDK: rel: view-consumer-identification
        SDK ->> SDK: Show html page with view-consumer-identification
        SwedbankPay ->> SDK: Consumer identification process
        SDK ->> SwedbankPay: Consumer identification process
        SwedbankPay ->> SDK: consumerProfileRef
        SDK ->> SDK: paymentOrder.payer = { consumerProfileRef }
    end

    rect rgba(138, 205, 195, 0.1)
        note left of App: Payment Menu
        SDK ->> Merchant: POST /swedbank-pay-mobile/paymentorders
        Merchant ->> SwedbankPay: POST /psp/paymentorders
        SwedbankPay -->> Merchant: rel: view-paymentorder
        Merchant -->> SDK: rel: view-paymentorder
        SDK ->> SDK: Show html page with view-paymentorder
        SwedbankPay ->> SDK: Payment process
        SDK ->> SwedbankPay: Payment process
        opt Redirect to Third-Party Page inside SwedbankPaySDKController ①
            SDK ->> SDK: Show third-party page
            SDK ->> SDK: Intercept navigation to paymentUrl
            SDK ->> SDK: Reload html page with view-paymentorder
        end
        opt Redirect to Third-Party Page in Safari ②
            SDK ->> Ext: Launch Safari
            Ext ->> SDK: Return from Safari
        end
        opt Launch External Application
            SDK ->> Ext: Start external application
            Ext ->> SDK: Return from external application ③
        end
        SDK ->> SDK: Intercept navigation to completeUrl
        SDK ->> SDK: delegate.paymentComplete()
    end

    App ->> App: Remove paymentFragment

The iOS SDK is contained in the module SwedbankPaySDK.

1
import SwedbankPaySDK

The Merchant Backend utilities are contained in the module SwedbankPaySDKMerchantBackend.

1
import SwedbankPaySDKMerchantBackend

The main component of the SDK is SwedbankPaySDKController, a UIViewController that handles a single payment order. When initializing a SwedbankPaySDKController, you must provide a SwedbankPaySDKConfiguration that describes the server environment the SwedbankPaySDKController is working with, along with a SwedbankPaySDK.PaymentOrder, and, unless making a guest payment, a SwedbankPaySDK.Consumer. Providing a SwedbankPaySDK.Consumer makes future payments by the same payer easier.

The SwedbankPaySDKConfiguration is, in most cases, static for a given server environment. Therefore, it makes sense to keep it in a convenient constant. The SwedbankPaySDK.MerchantBackendConfiguration initializer can determine your application’s custom scheme for payment urls automatically, if you have set it up as described above.

1
2
3
4
5
6
7
let swedbankPayConfig = SwedbankPaySDK.MerchantBackendConfiguration(
    backendUrl: "https://example.com/swedbank-pay-mobile/",
    headers: [:]
)

// Make it the default for all SDKControllers
SwedbankPaySDKController.defaultConfiguration = swedbankPayConfig

The semantics of SwedbankPaySDK.PaymentOrder properties are the same as the fields of the POST /psp/paymentorders request. Sensible default values are provided for many of the properties. In a similar fashion to how the Android SDK works, while there is no default value for the urls property, there are convenience constructors for the SwedbankPaySDK.PaymentOrderUrls type, which are recommended for general use. Assuming you have the iOS Payment Url Helper endpoint set up with the specified static path relative to your backend url (i.e. sdk-callback/ios-universal-link), then using one of the convenience constructors taking a SwedbankPaySDK.MerchantBackendConfiguration argument will set the paymentUrl correctly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
let paymentOrder = SwedbankPaySDK.PaymentOrder(
    currency = "SEK",
    amount = 1500,
    vatAmount = 375,
    description = "Test Purchase",
    language = .Swedish,
    urls = SwedbankPaySDK.PaymentOrderUrls(
        configuration: swedbankPayConfig,
        language: .Swedish
    ),
    payeeInfo = SwedbankPaySDK.PayeeInfo(
        // ①
        payeeName = "Merchant1",
        productCategory = "A123",
        orderReference = "or-123456",
        subsite = "MySubsite"
    ),

    orderItems = [
        SwedbankPaySDK.OrderItem(
            reference = "P1",
            name = "Product1",
            type = .Product,
            class = "ProductGroup1",
            itemUrl = URL(string: "https://example.com/products/123"),
            imageUrl = URL(string: "https://example.com/product123.jpg"),
            description = "Product 1 description",
            discountDescription = "Volume discount",
            quantity = 4,
            quantityUnit = "pcs",
            unitPrice = 300,
            discountPrice = 200,
            vatPercent = 2500,
            amount = 1000,
            vatAmount = 250
        )
    ]
)
  • ① payeeId and payeeReference are required fields, but default to the empty string. The assumption here is that your Merchant Backend will override the values set here. If your system works better with the Mobile Client setting them instead, they are available here also.

To start a payment, create a SwedbankPaySDKController and call startPayment. You can add it to the view hierarchy any way you like, and here we are using the present function. Note that this function always uses the new Digital Payments.

1
2
3
4
let paymentController = SwedbankPaySDKController()
paymentController.startPayment(paymentOrder: payment)

present(paymentController, animated: true, completion: nil)

To start a payment with consumer-checkin, you need to use CheckoutV2 and supply a consumer value. This function also allows merchants to remain on V2 while updating the SDK, and then to opt-in to V3 when ready.

1
2
3
4
5
6
7
8
9
10
let paymentController = SwedbankPaySDKController()
paymentController.startPayment(
    withCheckin: true,
    consumer: consumer,
    paymentOrder: payment,
    userData: nil
)

present(paymentController, animated: true, completion: nil)
// There are, of course, many other ways of displaying a view controller

The semantics of SwedbankPaySDK.Consumer properties are the same as the fields of the POST /psp/consumers. There are default values for the operation and language properties (.InitiateConsumerSession and .English, respectively).

1
2
3
4
let consumer = SwedbankPaySDK.Consumer(
    language = .Swedish,
    shippingAddressRestrictedToCountryCodes: = ["NO", "SE", "DK"]
)

To observe the payment process, set a delegate to the SwedbankPaySDKController. When the delegate is informed that the payment process is finished, you should remove the SwedbankPaySDKController and inform the user of the result.

1
paymentController.delegate = self
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func paymentComplete() {
    dismiss(animated: true, completion: nil)
    // Check payment status from your backend
    // Notify user
}

func paymentcancelled() {
    dismiss(animated: true, completion: nil)
    // Notify user
}

func paymentFailed(error: Error) {
    dismiss(animated: true, completion: nil)
    // Notify user
}

Note that checking the payment status after completion is outside the scope of the Mobile SDK. Your backend should collect any information it needs to perform this check when it services the request to the Payment Orders endpoint made by the SwedbankPaySDKController.

Problems

If the payment fails for any reason, the cause will be made available as the argument of the paymentFailed(error:) delegate method. The error will be of any type thrown by your SwedbankPaySDKConfiguration. In the case of SwedbankPaySDK.MerchantBackendConfiguration this means SwedbankPaySDK.MerchantBackendError.

If errors are encountered in the payment process, the Merchant Backend is expected to respond with a Problem Details for HTTP APIs (RFC 7807) message. If the payment fails because of a problem, the SwedbankPaySDK.MerchantBackendError will be .problem, the associated value being the problem as parsed from the response. The iOS SDK will parse any RFC 7807 problem, but it has specialized data types for known problem types, namely the Common Problems and the Merchand Backend Problems.

Problems are expressed in Swift as enums with associated values, representing a hierarchy of problem types. At the root of the hierarchy is enum SwedbankPaySDK.Problem, with two cases: .Client and .Server. A .Client problem is one caused by client behavior, and is to be fixed by changing the request made to the server. Generally, a .Client problem is a programming error, with the possible exception of .Client(.MobileSDK(.Unauthorized)). A .Server problem is one caused by a malfunction or lack of service in the server evironment. A .Server problem is fixed by correcting the behavior of the malfunctioning server, or simply trying again later.

Both .Client and .Server have an associated value, of type SwedbankPaySDK.ClientProblem and SwedbankPaySDK.ServerProblem respectively, that further classify the problems as .MobileSDK, .SwedbankPay, .Unknown or .UnexpectedContent. MobileSDK problems are ones with Merchant Backend problem types, while SwedbankPay problems have Swedbank Pay API problem types. Unknown problems are of types that the SDK has no knowledge of. .UnexpectedContent problems are not proper RFC 7807 problems, but are emitted when the SDK cannot make sense of the response it received from the backend.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func paymentFailed(failureReason: SwedbankPaySDKController.FailureReason) {
    // remove SwedbankPaySDKController

    switch failureReason {
        case .Problem(.Client(.MobileSDK(.Unauthorized(let message, _)))):
            print("Credentials invalidated: \(message)")

        case .Problem(.Client(.MobileSDK)):
            print("Other client error at merchant backend")

        case .Problem(.Client(.SwedbankPay(let problem))) where problem.type == .InputError:
            print("Payment rejected by Swedbank Pay: \(problem.detail); Fix: \(problem.action)")

        case .Problem(.Client(.Unknown(let problem))):
            if problem.type == "https://example.com/problems/special-problem" {
                print("Special problem occurred: \(problem.detail)")
            } else {
                print("Unexpected problem: \(problem.raw)")
            }

        case .Problem(.Server(.MobileSDK(.BackendConnectionTimeout(let message, _)))):
            print("Swedbank Pay timeout: \(message)")

        case .Problem(.Server(.SwedbankPay(let problem))) where problem.type == .SystemError:
            print("Generic server error at Swedbank Pay: \(problem.detail)")

        default:
            break
    }
}

Payment URL And External Applications

The payment process may involve navigating to third-party web pages, or even launching external applications. To resume processing the payment in the payment menu, each payment order must have a Payment Url. Let us now discuss how that payment url is used in the iOS environment. In any case, using the convenience constructors for SwedbankPaySDK.PaymentOrderUrls is recommended; they will generate a unique payment url, which will be routed to the application in all cases, assuming the application and the merchant backend are configured correctly.

SwedbankPaySDKController internally uses a WKWebView, and in many cases third-party pages can be opened inside that web view. In these cases the SDK can intercept the navigation to the payment url and reload the payment menu without further setup. Unfortunately, our testing has revealed that some web pages used in confirmation flows are incompatible with being opened in a web view. Because of these cases, SwedbankPaySDKController will only open known-good pages internally, and will open other pages in Safari instead. The SDK contains a list of domain names of pages tested to work in the web view. You can also specify your own list of domains, and there are debugging features available for testin unknown pages in the web view. Pull requests updating the list of good domains in the SDK are welcome.

sequenceDiagram
    participant SDK
    participant Web as Web View
    participant Safari

    Web ->> SDK: Navigate to third-party web page
    SDK ->> SDK: Check if page is in list of good domains
    alt Domain is good
        SDK ->> Web: Allow navigation
        Web ->> Web: Load third-party page
    else Domain is not good
        SDK ->> Web: Cancel navigation
        SDK ->> Safari: Open third-party page
    end

Returning to the payment menu from inside the web view is simple: detect the navigation and override it to reload the payment menu instead.

sequenceDiagram
    participant Page as 3rd Party Page
    participant Web as Web View
    participant SDK

    Page ->> Web: Navigate to payment url
    Web ->> SDK: Navigate to payment url
    SDK ->> Web: Cancel navigation
    SDK ->> SDK: Reload payment menu

Returning to the payment menu from Safari is more involved. The merchant backend page explains the process from the backend perspective; let us now view it from the iOS side.

When the third party page wants to return to the payment menu, it navigates to the payment url. As this navigation is happening inside Safari, the payment url must provide some meaningful respose when Safari makes the request. However, even before that happens, consider the case where the payment url is a universal link for the application using the SDK. Assuming the conditions for opening universal links in the registered application are met, then Safari will never actually request the payment url, but will instead open the application, giving it the universal link in its Application Delegate’s application(_:continue:restorationHandler:) method. Recall that we enabled universal links for the backend url’s domain in the installation instructions. Note that the merchant backend must also be properly configured to enable universal links.

The application delegate is, of course, squarely in the domain of the application; the SDK cannot hook into it automatically. Therefore, you need to implement the application(_:continue:restorationHandler:) method, and pass control over to the SDK when a Swedbank Pay SDK Payment Url is passed into it. Do this by calling the SwedbankPaySDK.continue(userActivity:) method.

1
2
3
4
5
6
7
    func application(
        _ application: UIApplication,
        continue userActivity: NSUserActivity,
        restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
    ) -> Bool {
        return SwedbankPaySDK.continue(userActivity: userActivity)
    }
sequenceDiagram
    participant Page as 3rd Party Page
    participant Safari
    participant App as Application
    participant SDK

    Page ->> Safari: Navigate to payment url
    Safari ->> Safari: Recognize universal link
    Safari ->> App: Bring to foreground
    Safari ->> App: application(application, continue: userActivity, restorationHandler: restorationHandler)
    App ->> SDK: SwedbankPaySDK.continue(userActivity: userActivity)
    SDK ->> SDK: Reload payment menu

Testing has shown, however, that the navigation to the payment url is not always processed as a universal link, and is instead opened in Safari. A major reason for this happening are the conditions placed on routing a universal link to the registered application. A crucial condition to consider is that the navigation must have started from user interaction. It appears that many third party pages involved in verification flows will navigate to the payment url not from user interaction directly, but through e.g. timers. This will, unfortunately, prevent the link from being opened in the application.

As it stands, we need a way to get back to the application even when the payment url is opened in Safari. The simplest way of accomplishing this is to respond with a redirect to a custom scheme url. Doing that will, however, always show an unattractive confirmation alert before the user is directed to the application. Therefore, let us first consider if there is a way to reattempt the universal link navigation, while attempting to maximize the chance of it being routed to the application.

Reviewing the conditions for universal links opening in the registered application, we note two things: Firstly, the navigation must originate from user interaction. Thus, opening the payment url in Safari must produce a page with a control the user can interact with, which must trigger a navigation to the payment url. Secondly, the navigation must be to a domain different to the current page. This means that opening the payment url must redirect to a page on a different domain, so that a navigation back to the payment url from that page is given to the application to handle.

As explained on the mechant backend page, we solve this by having the payment url respond with a redirect response to a page with a link to the payment url (but see below).

sequenceDiagram
    participant User
    participant Page as 3rd Party Page
    participant Safari
    participant Merchant as Payment Url Host
    participant Trampoline as Payment Url Trampoline
    participant App as Application
    participant SDK

    Page ->> Safari: Navigate to payment url (at Merchant)
    Safari ->> Merchant: GET <payment url>
    Merchant ->> Safari: 301 Moved Permanently Location: <redirect url>
    Safari ->> Trampoline: GET <redirect url>
    Trampoline ->> Safari: <http>...<a href="<payment url>">...</http>
    Safari ->> User: Page with "Back to Application" button
    User ->> Safari: Tap on button

    rect rgba(238, 112, 35, 0.05)
      note left of Safari: Same as direct path
      Safari ->> Safari: Recognize universal link
      Safari ->> App: Bring to foreground
      Safari ->> App: application(application, continue: userActivity, restorationHandler: restorationHandler)
      App ->> SDK: SwedbankPaySDK.continue(userActivity: userActivity)
      SDK ->> SDK: Reload payment menu
    end

Finally, to prevent the user being stuck in a situation where universal links fail to work despite our efforts, and to help in the development phase where configurations may end up being broken from time to time, we also have a custom scheme fallback. The way this works is that the when the payment url link is tapped on the page where the payment url redirected to, then in that instance the payment url will redirect to a custom scheme url instead. Now this is, of course, more or less impossible to do, so we relax the requirements of the payment url slightly: In addition to the original payment url, the SDK accepts a payment url with any number of additional query parameters added (note that none may be removed or modified, though). This enables us to alter the behavior of the backend on the “same” payment url.

To forward the custom-scheme payment urls to the SDK, implement the application(_:open:options:) method in your application delegate, and call SwedbankPaySDK.open(url: url) to let the SDK handle the url.

1
2
3
4
5
6
7
    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey : Any] = [:]
    ) -> Bool {
        return SwedbankPaySDK.open(url: url)
    }
sequenceDiagram
    participant User
    participant Page as 3rd Party Page
    participant Safari
    participant Merchant as Payment Url Host
    participant Trampoline as Payment Url Trampoline
    participant App as Application
    participant SDK

    Page ->> Safari: Navigate to payment url (at Merchant)
    Safari ->> Merchant: GET <payment url>
    Merchant ->> Safari: 301 Moved Permanently Location: <redirect url>
    Safari ->> Trampoline: GET <redirect url>
    Trampoline ->> Safari: <http>...<a href="<payment url with fallback flag>">...</http>
    Safari ->> User: Page with "Back to Application" button
    User ->> Safari: Tap on button
    Safari ->> Merchant: GET <payment url with fallback flag>
    Merchant ->> Safari: 301 Moved Permanently Location: <custom scheme url>
    Safari ->> User: Confirmation Dialog
    User ->> Safari: Accept App Launch
    Safari ->> App: Bring to foreground
    Safari ->> App: application(application, open: url)
    App ->> SDK: SwedbankPaySDK.open(url: url)
    SDK ->> SDK: Reload payment menu