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
.
You can also edit the Info.plist
file directly, if you wish.
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.
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
- ① Only pages tested to work with WKWebView are opened inside SwedbankPaySDKController. This list is updated as new pages are verified.
- ② Other pages are opened in Safari. See the section on external applications for details on how the process returns to the SDK afterwards.
- ③ See the section on external applications for details.
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 enum
s 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