3-D Secure 2 handlers for mobile apps

Most PSPs have their own certified SDKs for 3-D Secure 2 (3DS2) in mobile apps but they all have equivalent features. To abstract the details of specific 3DS2 SDKs, we define a interface PO3DSService in Kotlin and protocol PO3DS2Service in Swift that supply the functionality in a consistent way (this is the threeDSService parameter in the code samples above).

We officially support our own implementation POTest3DSService of PO3DSService that emulates the normal 3DS authentication flow but does not actually make any calls to a real Access Control Server (ACS). It is mainly useful during development in our sandbox testing environment.

The example below shows how you can create an instance of the POTest3DSService to pass as the threeDSService parameter of authorizeInvoice().

import ProcessOutUI

...

try await ProcessOut.shared.invoices.authorizeInvoice(
    request: request, threeDSService: POTest3DSService()
)
ProcessOut.instance.invoices.authorize(
    request = request,
    threeDSService = POTest3DSService(
        activity = this,
        customTabLauncher = customTabLauncher,
        returnUrl = "your.application.id://processout/return"
    )
)

With some PSPs such as Adyen you must add the version number of the 3DS2 SDK to the POInvoiceAuthorizationRequest object when you make the call to authorizeInvoice().

The example below demonstrates how you can do that and also change the preferred card scheme (for example, to carte bancaire) if the customer's card supports co-schemes.

...

let request = POInvoiceAuthorizationRequest(
    ...
    preferredScheme: "carte bancaire", // Optional
    thirdPartySdkVersion: "<VERSION>"
)

try await ProcessOut.shared.invoices.authorizeInvoice(
    request: request, threeDSService: threeDSService
)

...
val request = POInvoiceAuthorizationRequest(
    ...
    thirdPartySdkVersion = "<VERSION>",
    preferredScheme = "carte bancaire" // Optional.
)

ProcessOut.instance.invoices.authorize(
    request = request,
    threeDSService = threeDSService
)

Netcetera 3DS SDK integration on iOS

We also provide a built-in 3DS2 SDK that is available in separate ProcessOutNetcetera3DS module on iOS. The example below shows how you can create an instance of Netcetera-based PO3DSService and use it.

import ProcessOut
import ProcessOutNetcetera3DS

// 1. Define the 3DS service delegate.
final class NetceteraDelegate: PONetcetera3DS2ServiceDelegate {

    func netcetera3DS2Service(
        _ service: PONetcetera3DS2Service, shouldContinueWith warnings: [Warning]
    ) async -> Bool {
        // The default implementation ignores all warnings. You can change
        // that and for example abort 3DS2 if there is hight severity warning.
        // This method is optional.
        !warnings.contains { $0.getSeverity() == .HIGH }
    }

    func netcetera3DS2Service(
        _ service: PONetcetera3DS2Service,
        willPerformChallengeOn viewController: inout UIViewController?
    ) async {
        // Your implementation could change the view controller that will be
        // used to present challenge. This method is optional.
    }
}

// 2. Create the delegate (optional).
let threeDSServiceDelegate = NetceteraDelegate()

// 3. Create configuration (optional).
let configuration = PONetcetera3DS2ServiceConfiguration()

// 4. Create the 3DS service.
let threeDSService = PONetcetera3DSService(
    configuration: configuration, delegate: threeDSServiceDelegate
)
...

Please check the SDK reference for additional details.

Netcetera 3DS SDK integration on Android

We also provide a built-in integration with the Netcetera 3DS SDK, which is available as a separate module.

implementation("com.processout:processout-android-netcetera-3ds:<version>")

Follow the steps below to create the PONetcetera3DS2Service.

// 1. Initialize PO3DSRedirectCustomTabLauncher in the onCreate() method of an Activity or Fragment.

private lateinit var customTabLauncher: PO3DSRedirectCustomTabLauncher

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customTabLauncher = PO3DSRedirectCustomTabLauncher.create(from = this)
}

// 2. Implement the PONetcetera3DS2ServiceDelegate.

class Netcetera3DS2ServiceDelegate(
    private val provideActivity: () -> ComponentActivity?,
    private val customTabLauncher: PO3DSRedirectCustomTabLauncher,
    private val returnUrl: String
) : PONetcetera3DS2ServiceDelegate {

    override fun activity(): ComponentActivity? = provideActivity()

    override fun handle(
        redirect: PO3DSRedirect,
        callback: (ProcessOutResult<String>) -> Unit
    ) {
        customTabLauncher.launch(redirect, returnUrl, callback)
    }

    override suspend fun shouldContinue(warnings: Set<Warning>): Boolean {
        // Inspect the severity of the warnings to decide whether the flow should continue.
        // The default implementation ignores any warnings.
        return true
    }
}

// 3. Create the 3DS service.

val threeDSService = PONetcetera3DS2Service(
    delegate = Netcetera3DS2ServiceDelegate(
        provideActivity = { requireActivity() },
        customTabLauncher = customTabLauncher,
        returnUrl = "your.application.id://processout/return"
    ),
    configuration = PONetcetera3DS2ServiceConfiguration() // optional
)

🚧

Netcetera 3DS SDK renders challenge UI within the provided activity instance. Make sure to provide the correct activity instance depending on your use case:

  • When integrating with the non-UI API bindings, provide an instance of your activity.
  • When integrating with the POCardTokenizationLauncher, provide the POCardTokenizationActivity.instance.
  • When integrating with the PODynamicCheckoutLauncher, provide the PODynamicCheckoutActivity.instance.

πŸ“˜

If your app's release configuration includes shrinkResources true, make sure to add a rule to keep your themes and styles.

<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@style/*" />

πŸ“˜

If your app uses DexGuard, make sure to include the configuration specified in the dexguard.txt file.

Please check the SDK reference for additional details.

Checkout 3DS SDK integration

We also provide a built-in 3DS2 SDK that is available in separate ProcessOutCheckout3DS module on both iOS and Android.

The example below shows how you can create an instance of Checkout-based PO3DSService and use it.

import ProcessOut
import ProcessOutCheckout3DS

// 1. Define the 3DS service delegate.
final class Checkout3DSServiceDelegate: POCheckout3DSServiceDelegate {

    func checkout3DSService(
        _ service: POCheckout3DSService,
        configurationWith parameters: ThreeDS2ServiceConfiguration.ConfigParameters
    ) -> ThreeDS2ServiceConfiguration? {
        // If you don’t want to customize 3DS2 SDK appearance or behavior
        // you can omit this method as there is a default implementation. For example
        // the code below changes default timeout to 10 minutes.
        .init(configParameters: parameters, challengeTimeout: 500)
    }

    func checkout3DSService(
        _ service: POCheckout3DSService,
        shouldContinueWith warnings: Set<Warning>
    ) async -> Bool {
        // The default implementation ignores all warnings. You can change
        // that and for example abort 3DS2 on jailbroken devices.
        !warnings.contains(.jailbroken)
    }
}

// 2. Create the delegate.
let threeDSServiceDelegate = POCheckout3DSServiceDelegate()

// 3. Create the 3DS service.
let threeDSService = POCheckout3DSService(
    // Delegate is strongly referenced by service so make sure you are not
    // creating any retain cycles.
    delegate: threeDSServiceDelegate, environment: .sandbox
)
...
// 1. Initialize PO3DSRedirectCustomTabLauncher in the onCreate() method of Activity or Fragment.

private lateinit var customTabLauncher: PO3DSRedirectCustomTabLauncher

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customTabLauncher = PO3DSRedirectCustomTabLauncher.create(from = this)
}

// 2. Implement POCheckout3DSServiceDelegate and pass the launcher.

class Checkout3DSServiceDelegate(
    private val activity: Activity,
    private val customTabLauncher: PO3DSRedirectCustomTabLauncher
) : POCheckout3DSServiceDelegate {

    override fun configuration(parameters: ConfigParameters): ThreeDS2ServiceConfiguration {
        return ThreeDS2ServiceConfiguration(
            context = activity,
            configParameters = parameters,
            // Optional properties.
            locale = Locale.UK,
            uiCustomization = UICustomization(),
            appUri = Uri.parse("https://my-app-url.com"),
            challengeTimeout = 300
        )
    }

    override fun shouldContinue(warnings: Set<Warning>, callback: (Boolean) -> Unit) {
        // The default implementation ignores all warnings.
        // As an example we can define to continue the flow
        // only if there are no warnings or all warnings have low severity.
        callback(warnings.all { it.severity == Severity.LOW })
    }

    override fun handle(redirect: PO3DSRedirect, callback: (ProcessOutResult<String>) -> Unit) {
        customTabLauncher.launch(
            redirect = redirect,
            returnUrl = "your.application.id://processout/return",
            callback = callback
        )
    }

    // Optionally implement service lifecycle callbacks for logs or custom logic.

    override fun willCreateAuthenticationRequest(configuration: PO3DS2Configuration) {}

    override fun didCreateAuthenticationRequest(result: ProcessOutResult<PO3DS2AuthenticationRequest>) {}

    override fun willHandle(challenge: PO3DS2Challenge) {}

    override fun didHandle3DS2Challenge(result: ProcessOutResult<Boolean>) {}
}

// 3. Create the 3DS service.
val threeDSService = POCheckout3DSService.Builder(
    activity = this,
    delegate = Checkout3DSServiceDelegate(activity = this, customTabLauncher)
)   // Optional parameter, by default Environment.PRODUCTION.
    .with(environment = Environment.SANDBOX)
    .build()

For other providers, you can create your own PO3DSService implementation using our code as a starting point. See our GitHub repositories with the definition of the 3DS2 SDK integration for Android and iOS.