3DS2 SDK process
The diagram below shows the 3DS2 SDK payment process.
Be aware that many issuers' ACS don't work properly with 3DS Mobile SDKs.
- Client creates an order.
- Mobile Server registers that order in Payment Gateway via register.do.
- Mobile Server receives a unique order number
mdOrder
in response. - Client fills in payment data.
- Mobile App sends payment data to Mobile Server.
- Mobile Server uses that payment data to make a payment via paymentorder.do request.
- Send
threeDSSDK=true
to indicate that 3DS2 SDK should be used.
- Send
- Mobile Server gets a response with no ACS keys. It means that payment is completed and we need to go to step 22.
- Mobile Server gets a response with ACS keys.
- Response must contain
threeDSServerTransId
andthreeDSSDKKey
. - Response should not contain
threeDSMethodURL
which is used in case of browser based redirect to ACS.
- Response must contain
- Mobile Server sends 3DS2 SDK data to Mobile App
- Mobile App initiates 3DS2 SDK via
createTransaction
method.-
directoryServerID
depends on Payment System (for tests A000000003 can be used). -
messageVersion
is 2.1.0 for now. -
pemPublicKey
for Android and for iOS is a pem certificate that is got in response inthreeDSSDKKey
on step 10. -
dsRoot
for Android and for iOS depends on Payment System. Download test key.
-
- 3DS2 SDK collects device data and encrypts it.
- Mobile App sends encrypted device data to Mobile Server.
- Mobile Server initiates second payment API call via paymentorder.do request.
-
sdkEncData
- encrypted device data that is returned increateTransaction
method in 3DS2 SDK -
threeDSServerTransactionID
is returned in response to first payment call on step 10 inthreeDSServerTransId
parameter -
threeDSSDKReferenceNumber
is returned increateTransaction
method in 3DS2 SDK -
sdkEphemPubKey
is returned increateTransaction
method in 3DS2 SDK -
sdkAppID
is returned increateTransaction
method in 3DS2 SDK -
sdkTransID
is returned increateTransaction
method in 3DS2 SDK
-
- Payment Gateway returns new special parameters for 3DS2 SDK.
- Mobile Server sends those parameters to Mobile App.
- Mobile App initiates challenge flow via
doChallenge
method.- doChallenge
acsTransactionID
parameter corresponds tothreeDSAcsTransactionId
in Payment Gateway response - doChallenge
acsRefNumber
parameter corresponds tothreeDSAcsRefNumber
in Payment Gateway response - doChallenge
acsSignedContent
parameter corresponds tothreeDSAcsSignedContent
in Payment Gateway response - doChallenge
3DSServerTransactionID
parameter corresponds tothreeDSServerTransId
in Payment Gateway response
- doChallenge
- 3DS2 SDK communicates with issuers ACS via CReq/CRes API until Client confirms the payment.
- ACS sends RReq to Payment Gateway to confirm or reject the payment.
- 3DS2 SDK informs Mobile App that 3DS2 flow is over via
ChallengeStatusReceiver
. - Mobile Server finalizes the payment with finish3dsVer2Payment.do.
- Payment Gateway makes a payment.
- Payment Gateway sends callback notification to Merchant server if it's configured for the merchant.
- Mobile Server checks the final payment status via getOrderStatusExtended.do.
- Mobile App shows payment result to the client.
IOS
iOS Integration
For integration SDK ThreeDS:
-
Download the latest version of the framework and add
ThreeDSSDK.xcframework
to the project;
- Mark ThreeDSSDK.xcframework as
Embed & Sign
intagret -> general
;
iOS Configuration
Example ThreeDSSDK
Full example can be found here: TransactionManager
import UIKit
import ThreeDSSDK
....
var _service: ThreeDS2Service = Ecom3DS2Service()
// Init ThreeDS SDK
try _service.initialize(configParameters: ConfigParameters(), locale: Locale.current.languageCode, uiCustomization: _setUpTheme())
var _sdkTransaction = try _service.createTransaction(directoryServerID: "", messageVersion: nil, publicKeyBase64: "", rootCertificateBase64: "", logoBase64: "")
// get progress view
var _sdkProgressDialog = try _sdkTransaction!.getProgressView()
// Show progress dialog
_sdkProgressDialog?.show()
// get authentication request parameters, to be sent to the payment gateway
var authParameters = _sdkTransaction!.getAuthenticationRequestParameters()
var deviceData = authParameters.getDeviceData()
var ephemeralPublickKey = authParameters.getSDKEphemeralPublicKey()
var threeDSSDKAppId = authParameters.getSDKAppID()
var threeDSSDKTransId = authParameters.getSDKTransactionID()
// if you get error during initial sdk, you can close spinner.
// _sdkProgressDialog?.close()
// Set challenge parameters and start challenge flow
let challengeParameters = ChallengeParameters()
challengeParameters.setAcsSignedContent("acsSignedContent")
challengeParameters.setAcsRefNumber("acsReferenceNumber")
challengeParameters.setAcsTransactionID("acsTransID")
challengeParameters.set3DSServerTransactionID("threeDSServerTransID")
// Start challenge flow
self._sdkTransaction?.doChallenge(challengeParameters: challengeParameters, challengeStatusReceiver: self, timeOut: 5)
// If you get error during sdk flow, you can finish sdk transaction
// _sdkTransaction?.close()
// Delegate functions
extension TransactionManager: ChallengeStatusReceiver {
public func completed(completionEvent e: CompletionEvent) {}
public func cancelled() {}
public func timedout() {}
public func protocolError(protocolErrorEvent e: ProtocolErrorEvent) {}
public func runtimeError(runtimeErrorEvent: RuntimeErrorEvent) {}
}
Logging
Internal processes are logged with the SDK-ThreeDS
tag.
You can also log your processes.
Logging is available through the ThreeDSLogger
class which has an instance available through the static shared
field.
- To add log-interfaces you should call the ThreeDSLogger
-method addLogInterface()
.
Example:
...
final class Logger: LogProtocol {
func log<T>(classMethod: T.Type, tag: String, message: String, exception: (any Error)?) {
print("\(classMethod), \(tag), \(message), \(exception)")
}
}
ThreeDSLogger.shared.addLogInterface(logger: Logger())
...
The default tag is SDK-ThreeDS
. You can set your own one if you like.
- To log own events you should call the
Logger
-methodlog()
.
Example:
...
ThreeDSLogger.shared.log(classMethod: type(of: self), tag: "My tag", message: "My message", exception: nil)
...
- You can upload logs for specific
sdkAppId
. You need to meet requirements of theLogUploaderConfigProvider
protocol and setup ThreeDSLogger. OnlySentry
is available so far.
Example:
...
ThreeDSLogger.shared.setupLogUploaderConfigProvider(configProvider: ViewController())
...
extension ViewController: LogUploaderConfigProvider {
func provideConfig(sdkAppId: String?) -> ThreeDSSDK.LogUploaderConfig? {
// use the sdkAppId to enable/disable logs or not
// enable logs upload
return .sentry(url: "SentryURL", key: "SentryKey")
// disable logs upload
return nil
}
}
...
Android
Android integration
Connecting the 3DS2 library
You need to add the sdk_threeds-release.aar
library file to the libs
folder, then specify the
dependency on the added library.
In SDK versions <= 2.5.5 add the sdk_listeners_android.jar
file to the libs
folder. Since SDK
2.5.6 there is no need to add the sdk_listeners_android.jar
file.
build.gradle.kts
allprojects {
repositories {
// ...
flatDir {
dirs("libs")
}
}
}
dependencies {
// dependency for connecting the confirmation functionality via 3DS
implementation(group = "", name = "sdk_threeds-release", ext = "aar")
implementation("com.google.code.gson:gson:2.8.5")
implementation("com.squareup.okhttp3:okhttp:3.11.0")
implementation("com.google.android.gms:play-services-ads:17.2.1")
implementation("com.google.android.gms:play-services-location:16.0.0")
}
build.gradle
allprojects {
repositories {
// ...
flatDir {
dirs 'libs'
}
}
}
dependencies {
// dependency for connecting the confirmation functionality via 3DS
implementation(group = "", name = "sdk_threeds-release", ext = "aar")
implementation("com.google.code.gson:gson:2.8.5")
implementation("com.squareup.okhttp3:okhttp:3.11.0")
implementation("com.google.android.gms:play-services-ads:17.2.1")
implementation("com.google.android.gms:play-services-location:16.0.0")
}
Android Configuration
Payment execution with confirmation via 3DS2
private val factory = Factory()
threeDS2Service = factory.newThreeDS2Service()
val configParams = factory.newConfigParameters()
val uiCustomization = factory.newUiCustomization()
threeDS2Service.initialize(
context,
configParams,
"en-US",
uiCustomization,
sslContext, // Optional field (needs if you want to pass a custom SSL certificate)
trustManager, // Optional field (needs if you want to pass a custom SSL certificate)
)
// An example of creating a transaction with custom SSL certificate.
// To create a transaction with a custom certificate, you need to create an SSLContext object and a TrustManager.
// Then pass it to initialize method of threeDS2Service.
// Both (sslContext and trustManager) parameters must be passed, or neither (they are optional).
//
// threeDS2Service.initialize(
// context,
// configParams,
// "en-US",
// uiCustomization,
// sslContext, // Optional field
// trustManager, // Optional field
// )
val dsRoot: String = "MII346GU349HDE5FH..." //your root-certificate in base64 format
val transaction = threeDS2Service.createTransaction("F000000000", "", "2.1.0", dsRoot)
// An example of creating a transaction with deviceInfo encryption with a transmitted RSA key.
// val rsaPem: String = ...
// val dsRoot: String = ...
// transaction = threeDS2Service.createTransaction(
// "",
// rsaPem,
// "2.2.0",
// dsRoot
// )
// An example of creating a transaction with deviceInfo encryption with a transmitted EC key.
// val ecPem: String = ...
// val directoryServerID: String = ...
// val dsRoot: String = ...
// transaction = threeDS2Service.createTransaction(
// directoryServerID,
// ecPem,
// "2.2.0",
// dsRoot
// )
// Available data, to be sent to the payment gateway
val authRequestParams = transaction.authenticationRequestParameters!!
val encryptedDeviceInfo: String = authRequestParams.deviceData
val sdkTransactionID: String = authRequestParams.sdkTransactionID
val sdkAppId: String = authRequestParams.sdkAppID
val sdkEphmeralPublicKey: String = authRequestParams.sdkEphemeralPublicKey
val sdkReferenceNumber: String = authRequestParams.sdkReferenceNumber
val challengeParameters = factory.newChallengeParameters()
// Parameters for starting Challenge Flow.
challengeParameters.acsTransactionID =
paymentOrderSecondStepResponse.threeDSAcsTransactionId
challengeParameters.acsRefNumber = paymentOrderSecondStepResponse.threeDSAcsRefNumber
challengeParameters.acsSignedContent =
paymentOrderSecondStepResponse.threeDSAcsSignedContent
challengeParameters.set3DSServerTransactionID(paymentOrderResponse.threeDSServerTransId)
// Listener to handle the Challenge Flow execution process.
val challengeStatusReceiver: ChallengeStatusReceiver = object : ChallengeStatusReceiver {
override fun cancelled() {}
override fun protocolError(protocolErrorEvent: ProtocolErrorEvent) {}
override fun runtimeError(runtimeErrorEvent: RuntimeErrorEvent) {}
override fun completed(completionEvent: CompletionEvent) {}
override fun timedout() {}
}
val timeOut = 5
// Starting Challenge Flow.
transaction.doChallenge(
activity,
challengeParameters,
challengeStatusReceiver,
timeOut
)
You can see a complete code sample in the
file net.payrdr.mobile.payment.sample.kotlin.threeds.ThreeDSActivity
.
Features of app self-test with connected 3DS2
When initializing the ThreeDS2Service
, the application performs a series of self-compromising
checks. The list with warnings is returned using the ThreeDS2Service.getWarnings
method. If there
are no warnings, an empty list is returned.
The list of checks includes:
- Checking root access on the device (according to
SW01
rule )- It returns a warning when triggered
The device is rooted
.
- It returns a warning when triggered
- Checking to install the app only from approved app stores (according to
SW02
rule)- It returns a warning when triggered
The integrity of the SDK has been tampered
. - By default, sdk allows installation of the application only from the Play Market.
- If it is necessary to add other markets (for example, App Gallery), they can be passed
to
configParams
.
- It returns a warning when triggered
val factory = Factory()
val threeDS2Service = factory.newThreeDS2Service()
val configParams = factory.newConfigParameters()
val trustedAppStores = listOf<String>("com.android.vending", "com.huawei.appmarket")
configParams.removeParam("security", "trustedAppStores")
configParams.addParam("security", "trustedAppStores", StringUtils.makeString(trustedAppStores))
- Checking application signature mismatch (according to
SW02
rule)- It returns a warning when triggered
The integrity of the SDK has been tampered.
- An example of passing a signature in
configParams
:
- It returns a warning when triggered
val factory = Factory()
val threeDS2Service = factory.newThreeDS2Service()
val configParams = factory.newConfigParameters()
val youAppSignature = //signature of your application
configParams.removeParam("security", "appSignature")
configParams.addParam("security", "appSignature", youAppSignature)
- Checking for malicious applications on the device (according to
SW02
rule)- It returns a warning when triggered
The integrity of the SDK has been tampered
. - By default, applications with the following package names are denied:
- de.robv.android.xposed
- de.robv.android.xposed.installer
- com.saurik.substrate
- If it is necessary to set your own list of prohibited applications, the names of their
packages can be passed to
configParams
.
- It returns a warning when triggered
val factory = Factory()
val threeDS2Service = factory.newThreeDS2Service()
val configParams = factory.newConfigParameters()
val maliciousApps = listOf<String>("com.zhiliaoapp.musically", "com.othersapp.harmful")
configParams.removeParam("security", "maliciousApps")
configParameters.addParam("security", "maliciousApps", StringUtils.makeString(maliciousApps))
- Checking to run the application from under the emulator (according to
SW03
rule)- It returns a warning when triggered
An emulator is being used to run the App
.
- It returns a warning when triggered
- Checking for the presence of a debugger connected to the application (according to
SW04
rule)- It returns a warning when triggered
A debugger is attached to the App
.
- It returns a warning when triggered
- Checking that the Android version is up-to-date (according to
SW05
rule)- It returns a warning when triggered
The OS or the OS version is not supported.
- Versions over API 16 (JELLY BEAN) are considered to be up-to-date (safe)
- It returns a warning when triggered
Under documentation/threeds
, you can find more information describing the
available ConfigParameters
and UiCustomization
parameters to customize the 3DS screen, as well as the description of the 3DS SDK
server application interaction process.
Example of a confirmation code input screen:
Logging
Internal processes are logged with the SDK-ThreeDS
tag.
You can also log your processes.
Logging is available through the ThreeDSLogger
class which has an instance available through the static INSTANCE
field .
- To add log- interfaces you should call the ThreeDSLogger
-method addLogInterface()
.
Example for logging into LogCat:
...
ThreeDSLogger.INSTANCE.addLogInterface(object : net.payrdr.mobile.payment.sdk.threeds.LogInterface {
override fun log(classMethod: Class<*>, tag: String, message: String, p3: Throwable?) {
Log.i(tag, "$classMethod: $message")
}
})
...
The default tag is SDK-ThreeDS
. You can set your own one if you like.
-
To log own events you should call the
Logger
-methodlog()
.Example:
...
ThreeDSLogger.INSTANCE.log(this.javaClass, "MyTag", "My process", null)
...
- You can upload logs for specific
sdkAppId
. You need to setup LogUploaderConfigProvider for ThreeDSLogger. OnlySentry
is available so far.
Example:
...
ThreeDSLogger.INSTANCE.setupLogUploaderConfigProvider { sdkAppId ->
// use the sdkAppId to enable/disable logs or not
// enable logs upload
SentryLogUploaderConfig.Builder()
.withUrl("SentryURL")
.withKey("SentryKey")
.build()
// disable logs upload
null
}
...
3DS2 stub
3DS2 stub can be used with the SDK. SDK screens depend on the order amount in 3DS2 stub.
Radio group
Amount: 111
Multiple choice
Amount: 222
Web (embedded HTML)
Amount: 333
One-Time password
Amount: any other