Unsupported
The SDK is at an early stage of development and is not supported as of yet by Swedbank Pay. It is provided as a convenience to speed up your development, so please feel free to play around. However, if you need support, please wait for a future, stable release.
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 JCenter, which is a default repository in Android Studio projects. Therefore, most applications can integrate the SDK by simply adding the dependency. Please refer to the Bintray page for the latest version of the SDK.
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 consumer
and paymentOrder
properties. The payment process starts as soon as the PaymentFragment
is visible.
1
2
3
4
5
6
7
8
9
10
11
12
val arguments = PaymentFragment.ArgumentsBuilder()
.consumer(consumer)
.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)
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 behaviour, 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 behaviour 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.