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.
Initialization
To use the SDK, you must have a valid Configuration
for it. The API for this is a bit different in Android and iOS, but generally you will only need one Configuration
for your app. On Android set it to PaymentFragment.defaultConfiguration
; on iOS store it in a convenient variable.
If you are using a backend implementing the Merchant Backend API, there is a Configuration ready for you in the SDK. If you are designing your own backend API, you will need to create the Configuration yourself.
sequenceDiagram
alt Using Merchant Backend
alt Android
App ->> SDK: MerchantBackendConfiguration.Builder(backendUrl)...build()
SDK -->> App: configuration
App ->> SDK: PaymentFragment.defaultConfiguration = configuration
else iOS
App ->> SDK: SwedbankPaySDK.MerchantBackendConfiguration.init(backendUrl: backendUrl)
SDK -->> App: configuration
end
else Using Custom Backend
alt Android
App ->> App: class MyConfiguration : Configuration()
App ->> SDK: PaymentFragment.defaultConfiguration = configuration
else iOS
App ->> App: struct/class MyConfiguration : SwedbankPaySDK.Configuration
end
end
When you want to make a payment, create a PaymentFrament
or SwedbankPaySDKController
. SwedbankPaySDKController
’s designated initializer takes all the required arguments; for PaymentFrament
you should use PaymentFragment.ArgumentsBuilder
to create the arguments bundle.
The meaning of the arguments depends on your Configuration. If you are using MerchantBackendConfiguration
, you will always need a PaymentOrder
argument. A very important part of the PaymentOrder
is the urls
field. The SDK has convenience methods for creating one; unless your use-case is advanced, you should use these. On Android use the PaymentOrderUrls(context, backendUrl, [callbackUrl], [termsOfServiceUrl])
constructor; on iOS use the PaymentOrderUrls.init(configuration:language:[callbackUrl:][termsOfServiceUrl:])
initializer. In both cases the convenience method depends on your MerchantBackendConfiguration
(backendUrl
is part of the MerchantBackendConfiguration
), so be careful if you have multiple MerchantBackendConfigurations in your app.
Additionally, you may construct a Consumer
if you wish to identify the payer. This enables saving payment details for further payments.
If you are using a custom Configuration, you may use the PaymentOrder
or Consumer
arguments if you wish. Additionally you can use a generic data argument (of type Any
, though on Android it must implement either Parcelable
or Serializable
). By default, the existence of the Consumer
argument controls whether the consumer identfication happens, but you can also specify it explicitly.
sequenceDiagram
participant User
participant App
participant SDK
User ->> App : Begin payment
rect rgba(138, 205, 195, 0.1)
note right of App: Build Payment Order
alt Android
App ->> SDK: PaymentOrder(...) or PaymentOrder.Builder()...build()
SDK -->> App: paymentOrder
else iOS
App ->> SDK: SwedbankPaySDK.PaymentOrder.init(...)
SDK -->> App: paymentOrder
end
end
opt Build Consumer
alt Android
App ->> SDK: Consumer(...)
SDK -->> App: consumer
else iOS
App ->> SDK: Consumer.init(...)
SDK -->> App: consumer
end
end
rect rgba(138, 205, 195, 0.1)
note right of App: Create and configure payment UI component
alt Android
App ->> SDK: PaymentFragment.ArgumentsBuilder()...build()
SDK -->> App: arguments
App ->> SDK: PaymentFragment()
SDK -->> App: paymentFragment
App ->> SDK: paymentFragment.arguments = arguments
App ->> SDK: activity.paymentViewModel.[rich]state.observe(...)
else iOS
App ->> SDK: SwedbankPaySDKController.init(...)
SDK -->> App: swedbankPaySDKController
App ->> SDK: swedbankPaySDKController.delegate = ...
end
end
App ->> App : Show payment UI component
Merchant Backend: Discover Endpoints
This is an implementation detail of the Merchant Backend configuration; it is not necessary to replicate this step in a your own systems.
The Merchant Backend is specified with a single static entry point; other interfaces are accessed by following links from previous responses. A request to the static entry point currently returns links to the consumers
and paymentorders
endpoints. In most cases the response to this request can be cached, and thus only needs to be made once per App session.
sequenceDiagram
participant SDK
participant Backend
SDK ->> Backend: GET /
Backend -->> SDK: { "consumers": "[consumers]", "paymentorders": "[paymentorders]" }
Optional Checkin
Optionally, the payment beings with a “checkin” flow, where the payer is identified. This allows for saving payment details for later payments.
The checkin flow is simple: first a request is made to begin a checkin session, then an html page is constructed using the script link received from that request, and when that page successfully identifies the payer, a javascript callback is received. The consumerProfileRef
received from that callback is then used when creating the payment order in the next step.
sequenceDiagram
participant Conf as Configuration
participant SDK
participant Backend
participant SwedbankPay as Swedbank Pay
participant WebView
participant User
SDK ->> Conf: postConsumers
alt Merchant Backend
Conf ->> Backend: POST [consumers] { "operation": "initiate-consumer-session", ... }
else Custom Backend
Conf ->> Backend: Custom protocol
end
Backend ->> SwedbankPay: POST /psp/consumers/ { "operation": "initiate-consumer-session", ... }
SwedbankPay -->> Backend: { "operations": [{ "rel": "view-consumer-identification", "href": "[consumer-script]" }] }
alt Merchant Backend
Backend -->> Conf: { "operations": [{ "rel": "view-consumer-identification", "href": "[consumer-script]" }] }
else Custom Backend
Backend -->> Conf: Custom protocol
end
Conf --> SDK: ViewConsumerIdentificationInfo
SDK ->> WebView: <html>...<script src="[consumer-script]">...payex.hostedView.consumer(...)...</html>
WebView ->> User: Show checkin UI
User ->> WebView: Enter personal details
WebView ->> SDK: onConsumerIdentified({ "consumerProfileRef" : "..." })
SDK ->> SDK: store consumerProfileRef for checkout
Begin Checkout
With the Payment Order ready, the SDK begins the “checkout” flow, where the actual payment is made. The checkout flow begins similarly to the checkin flow: a request is made to create a Payment Order, then an html page is constructed and displayed to the user. In the case of the “create Payment Order” request, however, it is expected that the Merchant Backend processes
the request and response: Setting of payeeId
and paymentReference
in particular seems better left to the backend; similarly the backend is probably interested in storing the id
of the created Payment Order for capture and other possible operations.
At this point the user is interacting with the payment menu; the next step depends on the exact payment method chosen.
sequenceDiagram
participant Conf as Configuration
participant SDK
participant Backend
participant SwedbankPay as Swedbank Pay
participant WebView
participant User
SDK ->> Conf: postPaymentorders
alt Merchant Backend
Conf ->> Backend: POST [paymentorders] { paymentorder: {...} }
else Custom Backend
Conf ->> Backend: Custom protocol
end
Backend ->> Backend: Preprocess payment order (e.g. create payeeReference)
Backend ->> SwedbankPay: POST /psp/paymentorders/ { paymentorder: {...} }
SwedbankPay -->> Backend: { "id": "...", "operations": [{ "rel": "view-paymentorder", "href": "[paymentorder-script]" }], ... }
Backend ->> Backend: Postprocess payment order (e.g. store id)
alt Merchant Backend
Backend -->> Conf: { "id": "...", "operations": [{ "rel": "view-paymentorder", "href": "[paymentorder-script]" }], ... }
else Custom Backend
Backend ->> Conf: Custom protocol
end
Conf -->> SDK: ViewPaymentOrderInfo
SDK ->> WebView: <html>...<script src="[paymentorder-script]">...payex.hostedView.paymentMenu(...)...</html>
WebView ->> User: Show checkout UI
User ->> WebView: Choose payment method and enter details
External Content
While some payments may be completed inside the payment menu in their entirety, others will require interaction with some external web page, and/or application. In most cases external web pages can be opened in the same web view used to show the payment menu, and when they want to return to the payment menu, they signal this by attempting to navigate to the url set as paymentUrl
in the Payment Order. We intercept this navigation, and reload the payment menu, as appropriate.
When an external application is launched, the flow signals the return to the payment menu by again opening paymentUrl
. This time, however, we cannot intercept it. The system then handles opening that url the usual way. For maximum compatibility, paymentUrl
is a regular https url. On iOS, paymentUrl
is designed to be in format that is registered as a Universal Link to the app, which causes the system to open paymentUrl
in the app. The example backend serves a /.well-known/apple-app-site-association
file that assigns the paths under /sdk-callback/
to be Universal Links to the application set in the configuration. The SDK defaults to building paymentUrl
under this path. Combined with the proper configuration in the app and backend, this makes paymentUrl
s be Universal Links. On Android 6.0 and later it is possible to do a similar thing, but it is much more difficult to set up on the server side, and we need a solution for earlier versions anyway. Therefore, on Android, paymentUrl
will be opened in the browser.
Finally, in our testing, we have seen that certain external pages used with certain payment instruments do not work correctly inside a web view, and must be shown in the browser instead. If we determine that the external page is one of these pages, it is opened in the browser. Again, return to the payment menu is signaled by a navigation to paymentUrl
, which will, in this case be opened in the browser on both platforms (but see below for iOS details).
sequenceDiagram
participant App
participant SDK
participant WebView
participant System
participant Browser
participant Ext as External App
participant User
WebView ->> SDK: Navigate to another page
alt Navigation is to a regular http(s) URL
SDK ->> SDK: Check web view compatibility
alt Compatible with Web Wiew
SDK ->> WebView: Proceed with navigation normally ①
WebView ->> SDK: Navigate to paymentUrl ②
SDK ->> SDK: Recognize paymentUrl
SDK ->> WebView: Cancel navigation
else Not Compatible with Web Wiew
SDK ->> WebView: Cancel navigation
SDK ->> System: Open URL
System ->> Browser: Open URL in Browser
User ->> Browser: Handle external flow
Browser ->> User: Handle external flow
Browser -> Browser: Open paymentUrl in Browser
end
else Navigation is to an app-specific URL (custom scheme, Android Deep Link/App Link, iOS Universal Link)
SDK ->> WebView: Cancel navigation
SDK ->> System: Open URL
System ->> Ext: Launch URL-appropriate app
User ->> Ext: Handle external flow
Ext ->> User: Handle external flow
Ext ->> System: Open paymentUrl
alt Android
System ->> Browser: Open paymentUrl in Browser ③
else iOS
System ->> App: Launch app with paymentUrl ④
end
end
- ① The same check is repeated for any further navigation inside the WebView
- ② All properly configured authentication flows must end up here
- ③ On Android, paymentUrl is an https URL that redirects to an Android Intent URL.
- ④ On iOS, paymentUrl is a Universal Link. When an app open a Universal Link to another app, it should be routed to that app instead of the Browser. However, Univeral Links are finicky things, and it is not impossible that it gets opened in the Browser instead. In that case, the flow continues with “paymentUrl opened in Browser” below instead.
Return from Browser
If the external flow ended with paymentUrl
opened in the browser, we need a way to get back to the app. On Android, this is simple to accomplish by redirecting to an Android Intent Uri; the SDK and backend work together to construct the Intent Uri to point to the correct app. This Intent will cause the app to be brought back into focus, and the PaymentFragment will recognize the paymentUrl
and reload the payment menu. We still need to have an actual html page served at paymentUrl
, though, as the redirect may be blocked in some scenarios. If that happens, the page will also contain a link the user can tap on, which opens the same Intent Uri.
On iOS, the situation is more complicated. As mentioned above, paymentUrl
is a Universal Link, and navigation to it should be routed to the app. However, Universal Links are a bit unreliable, in that they require certain conditions to be fulfilled; otherwise, they are opened in the browser like regular links. Unfortunately, one of the conditions, namely that the navigation originated from the user pressing on a link, is often not fulfilled in the external pages used by payment methods. Therefore, we must have a system that works correctly, even if paymentUrl
is opened in the browser.
On iOS, we use the following system:
-
paymentUrl
redirects (301) to a trampoline page hosted at a different domain - the trampoline page has a button
- pressing that button navigates to
paymentUrl
but with an extra parameter -
paymentUrl
with the extra parameter redirects to a custom-scheme url
The trampoline page is on a different domain due to another requirement of Universal Links: they are only routed to the app if opened from a different domain. Now, both paymentUrl
and paymentUrl
with the extra parameter are Universal Links, and as the second navigation is “forced” to originate from User Interaction, it should be routed to the app. However, if something still goes sideways, and experience says it can, and even this “augmented” paymentUrl
is opened in the browser, then we finally redirect to a custom-scheme url, which has no choice but to be routed to the app. The reason we do not do this immediately is because using custom schemes triggers a confirmation dialog the developer has no control over, and we want to avoid that.
When the app is then launched with paymentUrl
, the augmented paymentUrl
, or the custom-scheme url constructed from paymentUrl
, the Application Delegate must then forward the url to the SDK using the SwedbankPaySDK.open(url:) or SwedbankPaySDK.continue(userActivity:) method, as the case may be. The SDK will then proceed to reload the payment menu as appropriate.
This system does have the drawback of requiring a custom url scheme, which will almost always be left unused. As we gather more data, we may be able to remove the requirement in the future.
Please see this diagram for an illustration of the different steps in the process:
sequenceDiagram
participant User
participant Browser
participant Backend
participant Trampoline as Universal Link Trampoline
participant System
participant SDK
participant App
alt Android
Browser ->> Merchant: Load paymentUrl
Merchant -->> Browser: Html document that redirects to an Intent URL ⑤
Browser ->> Browser: Parse Android Intent URL
Browser ->> System: Start activity with the parsed Intent, where the Intent Uri is paymentUrl
System ->> SDK: Start callback activity
SDK ->> SDK: Recognize paymentUrl
else iOS
alt Happiest Path
Browser ->> Browser: Recognize that paymentUrl is a Universal Link for App
Browser ->> System: Launch app with paymentUrl ⑥
else Less Happy Path
Browser ->> Merchant: Load paymentUrl
Merchant -->> Browser: 301 Moved Permanently ⑦
Browser ->> Trampoline: Load trampoline page
Browser ->> User: Show trampoline page ⑧
User ->> Browser: Press "Return to App" button
Browser ->> Browser: Navigate to paymentUrl&fallback=true
alt Less Happy Path (contd.)
Browser ->> Browser : Recognize that paymentUrl&fallback=true is a Universal Link for App
Browser ->> System : Launch app with paymentUrl&fallback=true
else Sad Path ⑨
Browser ->> Merchant: Load paymentUrl&fallback=true
Merchant -->> Browser: 301 Moved Permanently ⑩
Browser ->> User: Show confirmation dialog
User ->> Browser: Accept app launch
Browser ->> System: Launch app with customscheme://[paymentUrl-without-scheme]&fallback=true
end
end
System ->> App: Call URL handler ⑪
App ->> SDK: SwedbankPaySDK.open(url:) or SwedbankPaySDK.continue(userActivity:)
SDK ->> SDK: Recognize paymentUrl or modified paymentUrl
end
- ⑤
intent://[paymentUrl-without-scheme]/#Intent;scheme=[paymentUrl-scheme];action=com.swedbankpay.mobilesdk.VIEW_PAYMENTORDER;package=[app-package];end;
- ⑥ Universal Links have certain conditions for them to be activated. One of these is that the navigation must have started from a user interaction. As many 3D-Secure pages have an automatic redirect, this can cause the link to be opened in the Browser instead. Therefore the chance for this path to be taken is low. (N.B. It does seem than iOS 13.4 has made some change to the logic, causing this happiest path to be hit more often.)
- ⑦ Location:
https://ecom.stage.payex.com/externalresourcehost/trampoline?target=paymentUrl%26fallback=true
- ⑧ The “Trampoline Page” has a button, which links back to paymentUrl, but with an additional query parameter (actually this extra parameter is added by the backend when generating the redirect to the trampoline page). Importantly, the Trampoline is on a different domain than paymentUrl, as Universal Links are only forwarded to the app if they are opened from a different domain than the link’s domain.
- ⑨ All cases should be caught by one of these two flows. However, Universal Links remain finicky, and therefore it is good to provide one final fallback.
- ⑩ Location:
customscheme://[paymentUrl-without-scheme]&fallback=true
.customscheme
is a URL scheme unique to the App. - ⑪ Universal links result in a call to
UIApplicationDelegate.application(_:continue:restorationHandler:)
, while custom-scheme links result in a call toUIApplicationDelegate.application(_:open:options:)
.
Payment Completion
When the payment is completed, possibly after reloading the payment menu after a navigation to paymentUrl
, the payment menu will report success by attempting to navigate to completeUrl
. The SDK intercepts this and invokes a callback to your app. It is your app’s responsibility to then remove the payment UI from view and notify the user. Similarly, if the payment is canceled, the SDK intercepts the navigation to cancelUrl
and reports the cancellation status to your app.
sequenceDiagram
participant User
participant App
participant SDK
participant WebView
SDK ->> WebView: Reload <html>...<script src="[paymentorder-script]">...payex.hostedView.paymentMenu(...)...</html>
WebView ->> SDK: Navigate to completeUrl
SDK ->> WebView: Cancel navigation
alt Android
SDK ->> SDK: PaymentViewModel.state <- SUCCESS
SDK ->> App: Observer<PaymentViewModel.State>.onChanged(SUCCESS)
else iOS
SDK ->> App: SwedbankPaySDKDelegate.paymentComplete()
end
App ->> App: Remove payment UI component
App ->> User: Report payment result