3DS2 mobile SDKs References

With the new 3-D Secure 2 specifications released by EMVCo (the consortium composed of Visa, Mastercard, Amex and most card schemes), 3DS2 SDKs are normalized with common interfaces. ProcessOut therefore embraces these new principles and allows merchants to use the SDKs they want, working in a “bring your own SDK” fashion.

If you’re not yet familiar with 3DS2 and SCA, and what these involve from a technical point of view, we advise you read our guide first ↗.

You can find additional information on how to set up our mobile SDKs on their respective Android ↗ and iOS ↗ SDKs.


Handle payment on an invoice

Once you’ve created an Invoice in your backend and tokenized a card, you can initiate a payment on your mobile (iOS or Android) device. The ProcessOut SDK handles all the payment flow, including the potentially required authentication steps.

The most basic payment call on mobile is as follows:

// In your AppDelegate, configure the ProcessOut iOS SDK:
ProcessOut.Setup(projectId: "proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x")

// When you want to initiate a payment, call makeCardPayment
ProcessOut.makeCardPayment(
    "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT", 
    "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", 
    handler
);
final ProcessOut client = new ProcessOut(
    this, 
    "proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x"
);
client.makeCardPayment(
    "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT", 
    "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", 
    handler
);

handler is the 3DS handler that’ll be used to execute the fingerprinting and challenge steps during the 3DS authentication. Generally, you’ll work with two different kinds of 3DS handlers during your integration:

  • 1— The test handler;
  • 2— The handler with the production 3DS SDK

1— The test handler

The test handler is very simple: it simply emulates the 3DS authentication flow without having the make actual calls to ACS servers (servers of the banks of your customers used to process authentications).

The ProcessOut SDKs provide factories to build the 3DS2 test handler:

let handler = ProcessOut.createThreeDSTestHandler(viewController: self, completion: { (invoiceId, error) in
    if invoiceId != nil {
        // The authentication was done successfully, you can send
        // back the invoice ID to your backend to validate the payment
        print("invoice: " + invoicecId)
    } else {
        // An error occurred
        print(error)
    }
})
final ThreeDSHandler handler = ProcessOut.createDefaultTestHandler(
    MainActivity.this,
    new ProcessOut.ThreeDSHandlerTestCallback() {
        @Override
        public void onSuccess(String invoiceId) {
            // The authentication was done successfully, you can send
            // back the invoice ID to your backend to validate the payment
            Log.d("PROCESSOUT", "invoice: " + invoiceId);
        }

        @Override
        public void onError(Exception error) {
            // An error occurred
            Log.e("PROCESSOUT", error.toString());
        }
    }
);

The test handler can basically be used when integrating using ProcessOut’s sandbox, and can also be used to mock tests in your application.

Once you chose to deploy your mobile application to production, you can then start using the official 3DS SDKs from your 3DS Server partners.

2— The handler with the production 3DS SDK

As mentioned previously, the ProcessOut 3DS2 SDK works in a “bring your own SDK” manner. Most 3DS Server providers/partners will provide you with their own certified SDKs, which might also come with specific features you’d like to integrate.

To help in the process, you can find below the 3DS SDKs we officially support:

2.1— Adyen

The below code implements the ProcessOut 3DS2 SDK using Adyen’s certified SDK.

import ProcessOut
import Adyen3DS2

class AdyenThreeDSHandler: ThreeDSHandler {
    var transaction: ADYTransaction?

    func doFingerprint(directoryServerData: DirectoryServerData, completion: @escaping (ThreeDSFingerprintResponse) -> Void) {
        let parameters = ADYServiceParameters()
        parameters.directoryServerIdentifier = directoryServerData.directoryServerID
        parameters.directoryServerPublicKey = directoryServerData.directoryServerPublicKey

        ADYService.service(with: parameters, appearanceConfiguration: nil, completionHandler: {(service: ADYService) -> Void
            in
            do {
                self.transaction = try service.transaction(withMessageVersion: nil)
                    let authReqParams = self.transaction!.authenticationRequestParameters
                    if let sdkEphemPubKeyData = authReqParams.sdkEphemeralPublicKey.data(using: .utf8) {
                        let sdkEphemPubKey = try JSONDecoder().decode(ThreeDSFingerprintResponse.SDKEphemPubKey.self, from: sdkEphemPubKeyData)
                        let fingerprintResponse = ThreeDSFingerprintResponse(
                            sdkEncData: authReqParams.deviceInformation,
                            sdkAppID: authReqParams.sdkApplicationIdentifier,
                            sdkEphemPubKey: sdkEphemPubKey,
                            sdkReferenceNumber: authReqParams.sdkReferenceNumber,
                            sdkTransID: authReqParams.sdkTransactionIdentifier
                        )

                        completion(fingerprintResponse)
                    } else {
                        print("Could not encode sdkEphem")
                    }
            } catch {
                print(error)
            }
        })
    }

    func doChallenge(authentificationData: AuthentificationChallengeData, completion: @escaping (Bool) -> Void) {
        let challengeParameters = ADYChallengeParameters(
            serverTransactionIdentifier: authentificationData.threeDSServerTransID, 
            acsTransactionIdentifier: authentificationData.acsTransID, 
            acsReferenceNumber: authentificationData.acsReferenceNumber, 
            acsSignedContent: authentificationData.acsSignedContent
        )

        transaction?.performChallenge(with: challengeParameters, completionHandler: { (result, error) in
            if result != nil{
                completion(true)
            } else {
                completion(false)
            }
        })
    }

    func onSuccess(invoiceId: String) {
        // The authentication was done successfully, you can send
        // back the invoice ID to your backend to validate the payment
        print("SUCCESS:" + invoiceId)
    }

    func onError(error: ProcessOutException) {
        // An error occurred
        print(error)
    }
}
import com.adyen.threeds2.AuthenticationRequestParameters;
import com.adyen.threeds2.ChallengeStatusReceiver;
import com.adyen.threeds2.CompletionEvent;
import com.adyen.threeds2.ProtocolErrorEvent;
import com.adyen.threeds2.RuntimeErrorEvent;
import com.adyen.threeds2.ThreeDS2Service;
import com.adyen.threeds2.Transaction;
import com.adyen.threeds2.exception.SDKAlreadyInitializedException;
import com.adyen.threeds2.exception.SDKNotInitializedException;
import com.adyen.threeds2.parameters.ChallengeParameters;
import com.adyen.threeds2.parameters.ConfigParameters;
import com.adyen.threeds2.util.AdyenConfigParameters;

// ...

final ThreeDSHandler handler = new ThreeDSHandler() {
    private Transaction mTransaction;

    @Override
    public void doFingerprint(DirectoryServerData dsdata, DoFingerprintCallback callback) {
        ConfigParameters config = new AdyenConfigParameters.Builder(
            dsdata.getDirectoryServerID(), 
            dsdata.getDirectoryServerPublicKey()
        ).build();
        try {
            ThreeDS2Service.INSTANCE.initialize(MainActivity.this, config, null, null);
            mTransaction = ThreeDS2Service.INSTANCE.createTransaction(null, null);
            AuthenticationRequestParameters t = mTransaction.getAuthenticationRequestParameters();
            ThreeDSFingerprintResponse gatewayRequest = new ThreeDSFingerprintResponse(
                t.getDeviceData(), 
                t.getSDKAppID(), 
                new Gson().fromJson(t.getSDKEphemeralPublicKey(), SDKEPhemPubKey.class), 
                t.getSDKReferenceNumber(), 
                t.getSDKTransactionID()
            );
            callback.continueCallback(gatewayRequest);
        } catch (SDKAlreadyInitializedException e) {
            e.printStackTrace();
        } catch (SDKNotInitializedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doChallenge(AuthenticationChallengeData authenticateData, final DoChallengeCallback callback) {
        ChallengeParameters challengeParameters = new ChallengeParameters();
        challengeParameters.set3DSServerTransactionID(authenticateData.getThreeDSServerTransID());
        challengeParameters.setAcsTransactionID(authenticateData.getAcsTransID());
        challengeParameters.setAcsRefNumber(authenticateData.getAcsReferenceNumber());
        challengeParameters.setAcsSignedContent(authenticateData.getAcsSignedContent());

        mTransaction.doChallenge(MainActivity.this, challengeParameters, new ChallengeStatusReceiver() {
            @Override
            public void completed(CompletionEvent completionEvent) {
                callback.success();
            }

            @Override
            public void cancelled() {
                // Cancelled by the user or the App.
                callback.error();
            }

            @Override
            public void timedout() {
                // The user didn't submit the challenge within the given time, 5 minutes in this case.
                callback.error();
            }

            @Override
            public void protocolError(ProtocolErrorEvent protocolErrorEvent) {
                // An error occurred.
                callback.error();
            }

            @Override
            public void runtimeError(RuntimeErrorEvent runtimeErrorEvent) {
                // An error occurred.
                callback.error();
            }
        }, 5);
    }

    @Override
    public void onSuccess(String invoiceId) {
        // The authentication was done successfully, you can send
        // back the invoice ID to your backend to validate the payment
        Log.d("PROCESSOUT", "SUCCESS:" + invoiceId);
    }

    @Override
    public void onError(Exception error) {
        // An error occurred
        Log.e("PROCESSOUT", error.toString());
    }
};