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', '~> 5.0.0'
1
pod 'SwedbankPaySDKMerchantBackend', '~> 5.0.0'

Custom 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

AppSDKMerchantSwedbank PayExternal AppConfigurationSwedbankPaySDK.MerchantBackendConfiguration(backendUrl: "https://example.com/swedbank-pay-mobile/", headers: [:])configurationSwedbankPaySDK.Consumer(language = ..., shippingAddressRestrictedToCountryCodes = ...)consumeropt[ Unless Guest Payment ]Prepare PaymentPaymentOrderUrls(configuration: configuration, language: ...)paymentOrderUrlsPaymentOrder(urls: paymentOrderUrls, ...)paymentOrderSwedbankPaySDKController(configuration: configuration, consumer: consumer, paymentOrder: paymentOrder)swedbankPaySdkControllerswedbankPaySdkController.delegate = ...Show swedbankPaySdkControllerDiscover EndpointsGET /swedbank-pay-mobile/{ "consumers": "/swedbank-pay-mobile/consumers", "paymentorders": "/swedbank-pay-mobile/paymentorders" }POST /swedbank-pay-mobile/consumersPOST /psp/consumersrel: view-consumer-identificationrel: view-consumer-identificationShow html page with view-consumer-identificationConsumer identification processConsumer identification processconsumerProfileRefpaymentOrder.payer = { consumerProfileRef }opt[ Unless Guest Payment ]Payment MenuPOST /swedbank-pay-mobile/paymentordersPOST /psp/paymentordersrel: view-paymentorderrel: view-paymentorderShow html page with view-paymentorderPayment processPayment processShow third-party pageIntercept navigation to paymentUrlReload html page with view-paymentorderopt[ Redirect to Third-Party Page inside SwedbankPaySDKController ① ]Launch SafariReturn from Safariopt[ Redirect to Third-Party Page in Safari ② ]Start external applicationReturn from external application ③opt[ Launch External Application ]Intercept navigation to completeUrldelegate.paymentComplete()Remove paymentFragmentAppSDKMerchantSwedbank PayExternal App

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.

SDKWeb ViewSafariNavigate to third-party web pageCheck if page is in list of good domainsAllow navigationLoad third-party pageCancel navigationOpen third-party pagealt[ Domain is good ][ Domain is not good ]SDKWeb ViewSafari

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

3rd Party PageWeb ViewSDKNavigate to payment urlNavigate to payment urlCancel navigationReload payment menu3rd Party PageWeb ViewSDK

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 thevmerchant 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)
    }
3rd Party PageSafariApplicationSDKNavigate to payment urlRecognize universal linkBring to foregroundapplication(application, continue: userActivity, restorationHandler: restorationHandler)SwedbankPaySDK.continue(userActivity: userActivity)Reload payment menu3rd Party PageSafariApplicationSDK

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 merchant 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).

User3rd Party PageSafariPayment Url HostPayment Url TrampolineApplicationSDKNavigate to payment url (at Merchant)GET <payment url>301 Moved Permanently Location: <redirect url>GET <redirect url><http>...<a href="<payment url>">...</http>Page with "Back to Application" buttonTap on buttonSame as direct pathRecognize universal linkBring to foregroundapplication(application, continue: userActivity, restorationHandler: restorationHandler)SwedbankPaySDK.continue(userActivity: userActivity)Reload payment menuUser3rd Party PageSafariPayment Url HostPayment Url TrampolineApplicationSDK

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)
    }
User3rd Party PageSafariPayment Url HostPayment Url TrampolineApplicationSDKNavigate to payment url (at Merchant)GET <payment url>301 Moved Permanently Location: <redirect url>GET <redirect url><http>...<a href="<payment url with fallback flag>">...</http>Page with "Back to Application" buttonTap on buttonGET <payment url with fallback flag>301 Moved Permanently Location: <custom scheme url>Confirmation DialogAccept App LaunchBring to foregroundapplication(application, open: url)SwedbankPaySDK.open(url: url)Reload payment menuUser3rd Party PageSafariPayment Url HostPayment Url TrampolineApplicationSDK