Skip to main content

Credit Card Payment with 3DS

Prerequisite

Please contact your relationship manager for the value of orgUnitId, iss and secret.

To proceed with credit card payment in direct model, there are 4 steps required for exchanging payment data to BBMSL Online Payment Gateway and conducting security verification for 3-D Secure (3DS).

There are 3 parties involved in the payment process, i.e., the merchant, BBMSL and Cardinal. The merchant includes both frontend application and backend server depending on the system design. Cardinal is the provider of the 3DS verification service. During the process, payment data will be exchanged over these parties securely. A flow diagram is provided below for reference,

Docusaurus

1. 3DS Session Initialization

Before proceeding with the payment, a 3DS session is required before requesting to BBMSL Online Payment Gateway. You will need to prepare 3 static values, orgUnitId, iss and secret.

To obtain the SessionId, you need to place an invisible iframe element on your page for making an HTTP request to Cardinal. The API details are stated below,

  • URL: https://centinelapistag.cardinalcommerce.com/V1/Cruise/Collect
  • Method: POST
  • Query Parameters:
ParameterTypeRequired/optionalDescription
BinstringRequiredThe card number (PAN). Minimum of first 6-digits
JWTstringRequiredGenerated JWT token
Bin

We strongly recommend providing the first 9-digits of the PAN, which may increase the authentication success rate.

To generate the JWT token, you may use the following sample code with JJWT or any external libraries for signing the token,

public String generateJwt(String orgUnitId, String iss, String secret) {
    try {
        return Jwts.builder()
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setIssuer(iss)
                .claim("OrgUnitId", orgUnitId)
                .signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8"))
                .compact();
    } catch (UnsupportedEncodingException e) {
        return null;
    }
}

Below is an example code for requesting Cardinal to submit a form with the above value,

<!-- This is a Cardinal Commerce URL in live. -->
<form id="collectionForm" method="POST" action="https://centinelapistag.cardinalcommerce.com/V1/Cruise/Collect">
  <input type="hidden" name="Bin" value="4000000000001000" />
  <input type="hidden" name="JWT" value="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2OWFkYzE4NS0xNzQ4LTQ1MjUtOWVmOS00M2YyNTlhMWMyZDYiLCJpYXQiOjE1NDg4Mzg4NTUsImlzcyI6IjViZDllMGU0NDQ0ZGNlMTUzNDI4Yzk0MCIsIk9yZ1VuaXRJZCI6IjViZDliNTVlNDQ0NDc2MWFjMGFmMWM4MCJ9.qTyYn4rItMMNdnh6ouqW6ZmcCNzaG9JI_GdWGIaq6rY" />
</form>
<script>
  window.onload = function() {
    document.getElementById('collectionForm').submit();
  }
</script>
caution

You should embed the above code into an invisible iframe element to request the Cardinal API instead of directly requesting through non-UI code.

You can either use src or srcdoc attribute depending on your implementation,

<iframe src="/path_to_the_file.html" height="1" width="1" style="display: none;"/>
<iframe srcdoc="<p>Hello world!</p>" height="1" width="1" style="display: none;"/>

After submitting the form, you will be notified with a JavaScript postMessage. To proceed to the next step, you must listen to the notification which contains the SessionId value.

Here is an example code for listening to the event and postMessage,

window.addEventListener("message", function(event) {
   //This is a Cardinal Commerce URL in live.
   if (event.origin === "https://centinelapistag.cardinalcommerce.com") {
       var data = JSON.parse(event.data);
       console.warn('Merchant received a message:', data);
       if (data !== undefined && data.Status) {
           // Extract the value of SessionId for onward processing.
       }
   }
}, false);
{
    "MessageType": "profile.completed",
    "SessionId": "d3197c02-6f63-4ab2-801c-83633d097e32",
    "Status": true
}
Event Listener

The code should be placed outside the iframe to make sure the event can be captured.

You can either use src or srcdoc attribute depending on your implementation,

2. PayAPI: Auth

Pass the SessionId value from the previous session in the field ddcSession required in PayAPI Auth : /direct/auth for triggering the sale request through BBMSL. The credit card issuer may require additional authentication from the cardholder, which is indicated in the replied responseCode. Merchant will need different handling based on the value,

Response CodeDescriptionAction Required
0000Payment successNo further action required
50003DS Challenge Required, additional object threeDSChallengeDetails will be returned in the response.Follow the steps below to complete 3DS Challenge
OthersPayment failed with errorRetry after tackling the error

As the table mentioned, if the API responds with 0000, the payment is already succeeded and no further action is needed.

3. 3DS Challenge

If the Auth API responds with 5000, means the issuing party requires an extra authentication step from the cardholder. The merchant needs to display a webpage to allow inputting verification information. To obtain the webpage, the merchant needs to make an HTTP request to Cardinal with the details provided below,

  • URL: https://centinelapistag.cardinalcommerce.com/V2/Cruise/StepUp
  • Method: POST
  • Query Parameters:
ParameterTypeRequired/optionalDescription
JWTstringRequiredGenerated JWT token

Request URL Example

https://centinelapistag.cardinalcommerce.com/V2/Cruise/StepUp?JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkOTcyYWZlNi1kMmRmLTQ3MGEtOTFiYy1mZjY3NWM4NjY2ZTIiLCJpYXQiOjE2NTgxMzA2ODIsImlzcyI6IjYwZWMwNDQzNGJmNTdkMDdhZDM4NjlkOCIsIk9yZ1VuaXRJZCI6IjYwZWMwNDQzNGJmNTdkMDdhZDM4NjlkNyIsIlJldHVyblVybCI6Imh0dHBzOi8vd3d3LmJibXNsLmNvbS9yZWRpcmVjdD9zdGFuPVM0NTU2Jm9yZGVySWQ9NjU5MCIsIlBheWxvYWQiOnsiUGF5bG9hZCI6ImVOcFZVVjF2Z2pBVWZlK3ZJTXVlYWFrZzAxeWI2TWpVVFJZejNiTHRyVUl6TVZDd2dNaS9YNHRmMjlzOTkvT2NjMkc5VlVJRUt4SFZTakFJUlZueUgyRWw4ZWh1UWZzNzhYUm9wNGYranFvaUxKMlUzREZZanQvRW5zRkJxRExKSlhOc1lsUEFGNGowQ2hWdHVhd1k4R2cvbWI4eTEzVWR4d0Y4aGdneW9lWUJDOTdEOEF2d0NTQ1FQQk5zTWdsWEMydkoyMHpJeXByeVNqUzhCZHpWRUVSNUxTdlZNdC9UQnk4QVFhMVN0cTJxWW9oeDB6VDJacE9WcVIzbEdXQlRRWUJ2akphMWlVb3Q5SmpFYkIzTUtjOCtwckZNWi9IdWV4L0s1MmI5R1I0M3dYZ0UySFFnaURVSFJnbWx4SGNlTE9JUDNjSFFkUUYzZVFROE16VFk3T1hlSXpZaFd1UXBnYUF3cDhZbjVCRlQrcHZSWW1xbGhJeGExalBycmdpQk9CYTUxUEtaRm5tTnRZb2I5OGVaOFRhcXRHK2VQNkJrNEJwek85eU5KOW9XMm5OSU41OTBIbUV6ZzgrdncrY3Y2K2pmOTM4QnBaK3Fkdz09IiwiQUNTVXJsIjoiaHR0cHM6Ly9tZXJjaGFudGFjc3N0YWcuY2FyZGluYWxjb21tZXJjZS5jb20vTWVyY2hhbnRBQ1NXZWIvcGFyZXEuanNwP3ZhYT1iJmdvbGQ9QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBIiwiVHJhbnNhY3Rpb25JZCI6IkwyNmplRnZ5R3Y2ajJycE1zMWwwIn0sIk9iamVjdGlmeVBheWxvYWQiOnRydWV9.hPPwSYaSGlb03PXK3hidTyhkIvK99Oe9-ZlGrDxq48M
Request Parameter

The request parameters are constructed as query parameters instead of the request body as shown in the example.

Similar to 3DS Collect, a signed JWT token is required for obtaining the 3DS Challenge session from Cardinal. The threeDSChallengeDetails from the Auth response needs to be mapped to HashMap format. The returnUrl is the URL that will be directed to after the customer completed the 3DS Challenge. The stan and orderId will be placed as the query parameters when redirecting to the given returnUrl. The sample code is attached below,

public String generateStepUpJwt(String orgUnitId, String iss, String secret, String threeDSDetails, String stan, int orderId, String returnBaseUrl) {
    String returnUrl = returnBaseUrl + "?stan=" + stan;
    returnUrl += "&orderId=" + orderId;

    try {
        JSONObject object = new JSONObject(threeDSDetails);

        HashMap<String, String> payloadMap = new HashMap<>();
        payloadMap.put("ACSUrl", object.getString("acsURL"));
        payloadMap.put("Payload",object.getString("payload"));
        payloadMap.put("TransactionId",object.getString("transactionId3DS"));

        return Jwts.builder()
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setIssuer(iss)
                .claim("OrgUnitId", orgUnitId)
                .claim("ReturnUrl", returnUrl)
                .claim("Payload", payloadMap)
                .claim("ObjectifyPayload", true)
                .signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8"))
                .compact();
    } catch (UnsupportedEncodingException e) {
        return null;
    }
}

Cardinal will respond a 3DS challenge webpage from the issuing bank for authentication. Merchant can directly display the Html webpage in the application.

<!DOCTYPE html>
<html>

<head>
    <title>Cruise API - Step Up</title>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .hide {
            display: none;
        }

        #stepUpView {
            width: 100%;
            height: 100%
        }

        #stepUpView iframe {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }

        #loadingImage {
            height: 30px;
            width: 30px;
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            transform: -webkit-translate(-50%, -50%);
            transform: -moz-translate(-50%, -50%);
            transform: -ms-translate(-50%, -50%);
        }

        .outOfview {
            position: absolute;
            left: -2000px;
            top: -2000px;
        }
    </style>
</head>

<body>

    <div id="stepUpView"></div>

    <div class="hide">
        <input id="acsUrl" name="acsUrl" value="https://merchantacsstag.cardinalcommerce.com/MerchantACSWeb/pareq.jsp?vaa=b&amp;gold=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" type="hidden"/>
        <input id="payload" name="payload" value="eNpVUV1vgjAUfe+vIMueaakg01yb6MjUTRYz3bLtrUIzMVCwgMi/X4tf29s99/Occ2G9VUIEKxHVSjAIRVnyH2El8ehuQfs78XRop4f+jqoiLJ2U3DFYjt/EnsFBqDLJJXNsYlPAF4j0ChVtuawY8Gg/mb8y13UdxwF8hggyoeYBC97D8AvwCSCQPBNsMglXC2vJ20zIyprySjS8BdzVEER5LSvVMt/TBy8AQa1Stq2qYohx0zT2ZpOVqR3lGWBTQYBvjJa1iUot9JjEbB3MKc8+prFMZ/Huex/K52b9GR43wXgE2HQgiDUHRgmlxHceLOIP3cHQdQF3eQQ8MzTY7OXeIzYhWuQpgaAwp8Yn5BFT+pvRYmqlhIxa1jPrrgiBOBa51PKZFnmNtYob98eZ8TaqtG+eP6Bk4BpzO9yNJ9oW2nNIN590HmEzg8+vw+cv6+jf938BpZ+qdw==" type="hidden"/>
        <input id="mcsId" name="mcsId" value="0_7a3e6371-22e3-4e12-8abf-050ecdeff15c" type="hidden"/>
        <input id="termUrl" name="termUrl" value="https://centinelapistag.cardinalcommerce.com/V1/TermURL/Overlay/CCA" type="hidden"/>
        <input id="threeDSVersion" name="threeDSVersion" value="1" type="hidden"/>
        <input id="baseUrl" name="baseUrl" value="https://centinelapistag.cardinalcommerce.com" type="hidden"/>
        <input id="orgUnitId" name="orgUnitId" value="60ec04434bf57d07ad3869d7" type="hidden"/>
        <input id="transactionId" name="transactionId" value="L26jeFvyGv6j2rpMs1l0" type="hidden"/>
        <input id="isPostMessageEventsEnabled" name="isPostMessageEventsEnabled" value="false" type="hidden"/>
</div>

        <div class="hide">
            <form id="redirect" method="POST"
                action="https://centinelapistag.cardinalcommerce.com/V1/Cruise/TermRedirection">
                <input type="hidden" name="McsId" id="redirect-mcsId" value="0_7a3e6371-22e3-4e12-8abf-050ecdeff15c"/>
                <input type="hidden" name="CardinalJWT" id="CardinalJWT"/>
                <input type="hidden" name="Error" id="Error"/>
    </form>
        </div>

        <script src="https://centinelapistag.cardinalcommerce.com/javascript/vendors.83e0876bb78df21a4fcc.js"></script>
        <script src="https://centinelapistag.cardinalcommerce.com/javascript/stepUp.e327e52d1f61c8e6cc84.js"></script>
        <script>
            window.onload = function () {
        try {
            if (window.CruiseAPI !== undefined) {
                var configuration = {
                    acsUrl: document.getElementById('acsUrl').value,
                    baseUrl: document.getElementById('baseUrl').value,
                    encodedMcsId: document.getElementById('mcsId').value,
                    mcsId: document.getElementById('redirect-mcsId').value,
                    orgUnitId: document.getElementById('orgUnitId').value,
                    payload: document.getElementById('payload').value,
                    termUrl: document.getElementById('termUrl').value,
                    threeDSVersion: document.getElementById('threeDSVersion').value,
                    transactionId: document.getElementById('transactionId').value,
                    isPostMessageEventsEnabled: document.getElementById('isPostMessageEventsEnabled').value,
                };

                CruiseAPI.stepUp.run(configuration)
            } else {
                console.error('Global not found, unable to complete step up');
                document.getElementById("Error").value = "JS Global not found";
                document.getElementById("redirect").submit();
            }
        }catch(error) {
            if(window.CruiseAPI !== undefined){
                CruiseAPI.stepUp.handleError(error.message);
            } else {
                console.error(error);
            }
        }
    }
        </script>
</body>

</html>

After customers completed the verification process, the browser will be redirected to the provided returnUrl with the given stan and orderId. An example redirects URL of returnUrl='https://merchant-owned-website.com/return' is given below.

https://merchant-owned-website.com/return?stan=S4556&orderId=6590

4. PayAPI: Complete Authentication

After passing the 3DS Challenge verification step, you need to notify BBMSL that the authentication process is completed. Given the stan and orderId from the query parameters mentioned in previous session, merchant need to pass these two values to BBMSL Online Payment Gateway through PayAPI Complete Authentication: /direct/complete-authentication.

Until this step, the payment process is completed and your order is well recorded by BBMSL. A payment success result notification will also be sent to the merchant's backend URL if provided as well and the application can handle the result accordingly.