Credit Card Payment with 3DS
Contact your relationship manager to obtain the values for orgUnitId, iss, and secret before starting this integration.
Credit card payment in the Direct Model requires four steps. Three parties are involved: your application (frontend and backend), BBMSL, and Cardinal (the 3DS verification service provider). Payment data is exchanged securely across all three parties.
The diagram below shows the complete flow for reference.

1. 3DS session initialization
What you do: Initialize a 3DS session with Cardinal by posting the card BIN (first 6–9 digits) and a signed JWT to the Collect endpoint via an invisible iframe. Cardinal responds with a SessionId via a JavaScript postMessage.
Expected result: Cardinal returns a SessionId delivered to your page via a JavaScript postMessage event.
Next step: Pass the SessionId to PayAPI Auth in Step 2.
You need three static values to complete this step: orgUnitId, iss, and secret.
Place an invisible iframe element on your page to make an HTTP request to Cardinal. The API details are as follows:
- URL:
https://centinelapistag.cardinalcommerce.com/V1/Cruise/Collect - Method:
POST - Query Parameters:
| Parameter | Type | Mandatory | Description |
|---|---|---|---|
| Bin | String | M | The card number (PAN). Minimum of first 6-digits |
| JWT | String | M | Generated JWT token |
We strongly recommend providing the first 9-digits of the PAN, which may increase the authentication success rate.
To generate the JWT token, use the following sample code with JJWT or any compatible library 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 of requesting Cardinal to submit a form using the above values.
<!-- 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>
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, Cardinal notifies your page via a JavaScript postMessage. Listen for this message to retrieve the SessionId value before proceeding to Step 2.
Here is an example of listening for the event and extracting SessionId from the 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
}
Place the event listener outside the iframe to ensure the event is captured.
2. PayAPI: Auth
What you do: Pass the SessionId from Step 1 in the ddcSession field when calling PayAPI Auth: /direct/auth. BBMSL forwards the request to the card issuer, which may require additional authentication from the cardholder.
Expected result: The response responseCode determines the next action.
Next step: If responseCode is 0000, the payment is complete — no further action is needed. If 5000, proceed to Step 3.
| 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 shown above, if the response code is 0000 the payment has already succeeded and no further action is required.
3. 3DS challenge
What you do: Display a 3DS challenge webpage from the issuing bank so the cardholder can complete the additional authentication required by the issuer.
Expected result: The customer completes the verification, and the browser redirects to your returnUrl with stan and orderId as query parameters.
Next step: Pass stan and orderId to PayAPI Complete Authentication in Step 4.
If the Auth API responds with 5000, the issuing party requires extra authentication from the cardholder. Request the 3DS challenge page from Cardinal using the details below.
- URL:
https://centinelapistag.cardinalcommerce.com/V2/Cruise/StepUp - Method:
POST - Query Parameters:
| Parameter | Type | Mandatory | Description |
|---|---|---|---|
| JWT | String | M | Generated JWT token |
Request URL example
https://centinelapistag.cardinalcommerce.com/V2/Cruise/StepUp?JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkOTcyYWZlNi1kMmRmLTQ3MGEtOTFiYy1mZjY3NWM4NjY2ZTIiLCJpYXQiOjE2NTgxMzA2ODIsImlzcyI6IjYwZWMwNDQzNGJmNTdkMDdhZDM4NjlkOCIsIk9yZ1VuaXRJZCI6IjYwZWMwNDQzNGJmNTdkMDdhZDM4NjlkNyIsIlJldHVyblVybCI6Imh0dHBzOi8vd3d3LmJibXNsLmNvbS9yZWRpcmVjdD9zdGFuPVM0NTU2Jm9yZGVySWQ9NjU5MCIsIlBheWxvYWQiOnsiUGF5bG9hZCI6ImVOcFZVVjF2Z2pBVWZlK3ZJTXVlYWFrZzAxeWI2TWpVVFJZejNiTHRyVUl6TVZDd2dNaS9YNHRmMjlzOTkvT2NjMkc5VlVJRUt4SFZTakFJUlZueUgyRWw4ZWh1UWZzNzhYUm9wNGYranFvaUxKMlUzREZZanQvRW5zRkJxRExKSlhOc1lsUEFGNGowQ2hWdHVhd1k4R2cvbWI4eTEzVWR4d0Y4aGdneW9lWUJDOTdEOEF2d0NTQ1FQQk5zTWdsWEMydkoyMHpJeXByeVNqUzhCZHpWRUVSNUxTdlZNdC9UQnk4QVFhMVN0cTJxWW9oeDB6VDJacE9WcVIzbEdXQlRRWUJ2akphMWlVb3Q5SmpFYkIzTUtjOCtwckZNWi9IdWV4L0s1MmI5R1I0M3dYZ0UySFFnaURVSFJnbWx4SGNlTE9JUDNjSFFkUUYzZVFROE16VFk3T1hlSXpZaFd1UXBnYUF3cDhZbjVCRlQrcHZSWW1xbGhJeGExalBycmdpQk9CYTUxUEtaRm5tTnRZb2I5OGVaOFRhcXRHK2VQNkJrNEJwek85eU5KOW9XMm5OSU41OTBIbUV6ZzgrdncrY3Y2K2pmOTM4QnBaK3Fkdz09IiwiQUNTVXJsIjoiaHR0cHM6Ly9tZXJjaGFudGFjc3N0YWcuY2FyZGluYWxjb21tZXJjZS5jb20vTWVyY2hhbnRBQ1NXZWIvcGFyZXEuanNwP3ZhYT1iJmdvbGQ9QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBIiwiVHJhbnNhY3Rpb25JZCI6IkwyNmplRnZ5R3Y2ajJycE1zMWwwIn0sIk9iamVjdGlmeVBheWxvYWQiOnRydWV9.hPPwSYaSGlb03PXK3hidTyhkIvK99Oe9-ZlGrDxq48M
The request parameters are constructed as query parameters instead of the request body as shown in the example.
Similar to the 3DS Collect step, a signed JWT token is required to obtain the 3DS challenge session from Cardinal. Map the threeDSChallengeDetails from the Auth response to HashMap format. The returnUrl is where the browser redirects after the customer completes the 3DS challenge; stan and orderId are appended as query parameters. See the sample code 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 returns a 3DS challenge webpage from the issuing bank. Display this HTML page directly in your application for the customer to complete authentication.
<!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 the customer completes verification, the browser redirects to the provided returnUrl with stan and orderId as query parameters. An example redirect for returnUrl='https://merchant-owned-website.com/return' is shown below.
https://merchant-owned-website.com/return?stan=S4556&orderId=6590
4. PayAPI: Complete Authentication
What you do: Notify BBMSL that the 3DS challenge is complete by calling PayAPI Complete Authentication: /direct/complete-authentication with the stan and orderId values from the returnUrl query parameters.
Expected result: BBMSL records the completed order. A payment success notification is sent to your backend notifyUrl if one was provided.
Next step: The payment process is complete. Handle the result in your application workflow. See Result Notification for notification handling details.