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 PaymentFragment
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 Android component of the Swedbank Pay Mobile SDK is distributed through
Maven Central. It is split into two libraries in the
com.swedbankpay.mobilesdk
group:
- Core SDK:
com.swedbankpay.mobilesdk:mobilesdk
- Merchant Backend Utilities:
com.swedbankpay.mobilesdk:mobilesdk-merchantbackend
If you are not using the Merchant Backend API in your backend, you only need to use the first one. Otherwise, you should add both libraries to your project to get utilities for interfacing with your Merchant Backend server.
Most applications can integrate the SDK by simply adding the dependency in
the build.gradle
file:
1
2
3
4
dependencies {
implementation 'com.swedbankpay.mobilesdk:mobilesdk:4.1.1'
implementation 'com.swedbankpay.mobilesdk:mobilesdk-merchantbackend:4.1.1'
}
Please refer to Maven Central for the latest versions of the libraries.
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: MerchantBackendConfiguration.Builder("https://example.com/swedbank-pay-mobile/").build()
SDK -->> App: configuration
App ->> SDK: PaymentFragment.defaultConfiguration = configuration
end
opt Unless Guest Payment
App ->> SDK: Consumer(language = ..., shippingAddressRestrictedToCountryCodes = ...)
SDK -->> App: consumer
end
rect rgba(138, 205, 195, 0.1)
note left of App: Prepare Payment
App ->> SDK: PaymentOrderUrls(context, "https://example.com/swedbank-pay-mobile/")
SDK -->> App: paymentOrderUrls
App ->> SDK: PaymentOrder(urls = paymentOrderUrls, ...)
SDK -->> App: paymentOrder
end
App ->> SDK: activity.paymentViewModel.[rich]state.observe(...)
App ->> SDK: PaymentFragment.ArgumentsBuilder().consumer(consumer).paymentOrder(paymentOrder).build()
SDK -->> App: arguments
App ->> SDK: PaymentFragment()
SDK -->> App: paymentFragment
App ->> SDK: paymentFragment.arguments = arguments
App ->> App: Show paymentFragment
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
SDK ->> SDK: Show third-party page
SDK ->> SDK: Intercept navigation to paymentUrl
SDK ->> SDK: Reload html page with view-paymentorder
end
opt Launch External Application
SDK ->> Ext: Start external application
Ext ->> Merchant: Open paymentUrl
Merchant ->> Ext: Redirect intent://<...>action=com.swedbankpay.mobilesdk.VIEW_PAYMENTORDER
Ext ->> SDK: Start activity\naction=com.swedbankpay.mobilesdk.VIEW_PAYMENTORDER
SDK ->> SDK: Reload html page with view-paymentorder
end
SDK ->> SDK: Intercept navigation to completeUrl
SDK ->> SDK: paymentViewModel.state <- SUCCESS
SDK ->> App: observer.onChanged(SUCCESS)
end
App ->> App: Remove paymentFragment
The public API of the Android SDK is in the package
com.swedbankpay.mobilesdk
. The main component is
PaymentFragment
, a Fragment
that handles a single payment
order. To use a PaymentFragment
, it must have a
Configuration
. In most cases it is enough to construct a
single Configuration
and set it as the default. In
more advanced cases you will need to subclass PaymentFragment
and override
getConfiguration
.
For using a backend implementing the Merchant Backend API, the SDK also provides
utility classes in the package
com.swedbankpay.mobilesdk.merchantbackend
. The examples on
this page make use of these, including the Configuration
implementation
MerchantBackendConfiguration
.
1
2
3
4
5
val backendUrl = "https://example.com/swedbank-pay-mobile/"
val configuration = MerchantBackendConfiguration.Builder(backendUrl)
.build()
PaymentFragment.defaultConfiguration = configuration
To start a payment, you need a PaymentOrder
, and, unless
making a guest payment, a Consumer
. Using a Consumer
makes
future payments by the same payer easier.
The semantics of Consumer
properties are the same as the fields of the POST
/psp/consumers
request. There are default values for the
operation
and language
properties
(ConsumerOperation.INITIATE_CONSUMER_SESSION
and Language.ENGLISH
,
respectively).
1
2
3
4
val consumer = Consumer(
language = Language.SWEDISH,
shippingAddressRestrictedToCountryCodes = listOf("NO", "SE", "DK")
)
Similarly, the semantics of PaymentOrder
properties are the same as the fields
of the POST /psp/paymentorders
request. Sensible
default values are provided for many of the properties. The urls
property has
no default per se, but there are convenience
constructors available for it, and it is
recommended that you use them. Assuming you have the Android Payment Url Helper
endpoint set up with the specified static path relative to your backend url
(i.e. sdk-callback/android-intent
), then using the one of the
PaymentOrderUrls(context: Context, backendUrl: String)
variants 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
val paymentOrder = PaymentOrder(
currency = Currency.getInstance("SEK"),
amount = 1500L,
vatAmount = 375L,
description = "Test Purchase",
language = Language.SWEDISH,
urls = PaymentOrderUrls(context, backendUrl),
payeeInfo = PayeeInfo(
// ①
payeeName = "Merchant1",
productCategory = "A123",
orderReference = "or-123456",
subsite = "MySubsite"
),
orderItems = listOf(
OrderItem(
reference = "P1",
name = "Product1",
type = ItemType.PRODUCT,
`class` = "ProductGroup1",
itemUrl = "https://example.com/products/123",
imageUrl = "https://example.com/product123.jpg",
description = "Product 1 description",
discountDescription = "Volume discount",
quantity = 4,
quantityUnit = "pcs",
unitPrice = 300L,
discountPrice = 200L,
vatPercent = 2500,
amount = 1000L,
vatAmount = 250L
)
)
)
- ① 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 PaymentFragment
and set its arguments according
to the payment. The
PaymentFragment.ArgumentsBuilder
class is provided
to help with creating the argument bundle. In most cases you only need to worry
about the paymentOrder
property. The
payment process starts as soon as the PaymentFragment
is visible. Note that
Digital Payments is currently opt-in, so that merchants can upgrade without too
much breaking changes and start using the new Digital Payments when ready.
1
2
3
4
5
6
7
8
9
10
11
12
val arguments = PaymentFragment.ArgumentsBuilder()
.checkoutV3(true)
.paymentOrder(paymentOrder)
.build()
val paymentFragment = PaymentFragment()
paymentFragment.arguments = arguments
// Now use FragmentManager to show paymentFragment.
// You can also make a navigation graph with PaymentFragment
// and do something like
// findNavController().navigate(R.id.showPaymentFragment, arguments)
Note that the SDK only supports customer-checkin for version 2, and provides
fallback for merchants in need of this. Then you need to supply a
consumer
and the ckeckoutV3 setting
becomes irrelevant.
1
2
3
4
5
6
7
8
9
val arguments = PaymentFragment.ArgumentsBuilder()
.consumer(consumer)
.paymentOrder(paymentOrder)
.build()
val paymentFragment = PaymentFragment()
paymentFragment.arguments = arguments
// Now handle the fragment the same way as previously.
To observe the payment process, use the PaymentViewModel
of the containing Activity
. When the
PaymentViewModel
signals that the payment process
has reached a final state, you should remove
the PaymentFragment
and inform the user of the result.
1
2
3
4
5
6
7
paymentViewModel.state.observe(this, Observer {
if (it.isFinal == true) {
// Remove PaymentFragment
// Check payment status from your backend
// 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 PaymentFragment
.
Errors
If any errors happen in the payment, the PaymentViewModel
will report a state
of either FAILURE
or RETRYABLE_ERROR
. If the error is retryable, the
PaymentFragment
will show an error message and a retry control (this is
configurable), but you can also trigger a retry by calling retryPreviousAction
on the PaymentViewModel
.
When the state is FAILURE
or RETRYABLE_ERROR
, and the error condition was
caused by an exception thrown from the Configuration
, that exception is
available in
PaymentViewModel.richState.exception
.
The exception will be of any type throw by your Configuration
. When using
MerchantBackendConfiguration
, this means it will be an IOException
if there
was a problem communicating with the backend, and an IllegalStateException
if
you have made a programming error (consult the exception message). A particular
IOException
to check for is
RequestProblemException
, which signals that the
backend responded with a Problem message. Another one is
UnexpectedResponseException
, which signals that
the SDK did not understand the backend response.
Problems
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 a problem occurs, the application can receive it by observing the
richState
of the PaymentViewModel
. If a
problem has occurred, the exception
property of the RichState
will contain a
RequestProblemException
. The problem is then
accessible as exception.problem
. The
Android 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 presented as a class hierarchy representing
different problem categories. All problems parsed from RFC 7807 messages are
classified as either Client
or
Server
problems. 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
Problem.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.
Further, both Client
and Server
problems are categorized as MobileSDK
,
SwedbankPay
, or Unknown
. 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. There is also the interface
SwedbankPayProblem
, which encompasses both
Client
and
Server
type SwedbankPay
problems.
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
paymentViewModel.richState.observe(this, Observer {
if (it.state.isFinal == true) {
val exeption = it.exception as? RequestProblemException
if (exception != null) (
when (val problem = exception.problem) {
is MerchantBackendProblem.Client.MobileSDK.Unauthorized ->
Log.d(TAG, "Credentials invalidated: ${problem.message}")
if MerchantBackendProblem.Client.MobileSDK ->
Log.d(TAG, "Other client error at Merchant Backend: ${problem.raw}")
is MerchantBackendProblem.Client.SwedbankPay.InputError ->
Log.d(TAG, "Payment rejected by Swedbank Pay: ${problem.detail}; Fix: ${problem.action}")
is MerchantBackendProblem.Client.Unknown ->
if (problem.type == "https://example.com/problems/special-problem") {
Log.d(TAG, "Special problem occurred: ${problem.detail}")
} else {
Log.d(TAG, "Unexpected problem: ${problem.raw}")
}
is MerchantBackendProblem.Server.MobileSDK.BackendConnectionTimeout ->
Log.d(TAG, "Swedbank Pay timeout: ${problem.message}")
is MerchantBackendProblem.Server.SwedbankPay.SystemError ->
Log.d(TAG, "Generic server error at Swedbank Pay: ${problem.detail}")
is SwedbankPayProblem ->
Log.d(TAG, "Other problem at Swedbank Pay: ${problem.detail}; Fix: ${problem.action}")
else ->
Log.d(TAG, "Unexpected problem: ${problem.raw}")
}
}
}
})
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. As mentioned
above, the SDK has convenience constructors to set up a payment url for you, and
as the SDK handles showing third-party web pages inside the PaymentFragment
,
it automatically intercepts any navigation to the payment url, and reloads the
payment menu. This requires no additional setup.
If a third party application is launched, it will signal the return to the
payment menu by opening the payment url, using a standard ACTION_VIEW
Intent
. The payment url is built such that it uses the Android Payment Url
Helper, which serves an html page that converts the url to an
intent url and redirects to it. The SDK has an intent
filter for that intent, so the SDK will receive it, bringing the containing
application to the foreground, and reloading the payment menu. If your Merchant
Backend serves the Android Payment Url Helper endpoint at the specified path, no
further setup is needed.
Note that there is an argument for debugging purposes that cause third-party web pages to be opened in an external application. In that case the process continues analogously to the external application case. Using this argument should not be necessary, however. If you do find a case that does not work inside the PaymentFragment, but does work when using the browser for third-party sites, please file a bug on the Android SDK.