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,
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:
Parameter | Type | Required/optional | Description |
---|---|---|---|
Bin | string | Required | The card number (PAN). Minimum of first 6-digits |
JWT | string | Required | Generated 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 Code | Description | Action Required |
---|---|---|
0000 | Payment success | No further action required |
5000 | 3DS Challenge Required, additional object threeDSChallengeDetails will be returned in the response. | Follow the steps below to complete 3DS Challenge |
Others | Payment failed with error | Retry 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:
Parameter | Type | Required/optional | Description |
---|---|---|---|
JWT | string | Required | Generated 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&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.