Take encrypted server-to-server 3D Secure payments
Overview
The OnlinePay eCommerce API supports server-to-server payments, allowing merchants to process 3D Secure (3DS) transactions without requiring a client-side integration. This solution uses the verifone.js
script, which is used to encrypt card details before they are sent to the API, as well as the Cardinal Commerce 3D Secure service to handle the 3D Secure authentication process via the songbird.js
library.
The verifone.js
library is used to capture and encrypt card details on your website. This allows you to securely capture card details without the card details touching your server, reducing the complexity of PCI compliance, and providing seamless integration into your existing website checkout pages.
CardinalCommerce's songbird.js
is a JavaScript library used to facilitate 3D Secure (3DS) authentication in online payments. The songbird.js
library is loaded on the client-side (browser), and is responsible for initialising the 3D Secure process using a JWT (JSON Web Token), obtained from your 3DS Contract ID. It handles the authentication flow, including challenge prompts when required by the card issuer, and returns the authentication result to your backend server for processing.
Refer to the following recipe in addition to the complete documentation for more information on how to implement server-to-server 3D Secure payments:
Runtime Execution
The following steps must be executed in sequence during an actual payment transaction:
- Server-side: Generate JWT token from Verifone 3DS API (
/oidc/3ds-service/v2/jwt/create
). - Client-side: Initialise 3D Secure with JWT token from server.
- Client-side: Collect session ID from
setupComplete
event. - Client-side: Encrypt card details with
verifone.getEncryptedCardDetails()
. - Server-side: Perform 3DS lookup with encrypted card data and session ID (
/oidc/3ds-service/v2/lookup
). - Client-side: Handle authentication challenge if required (
pares_status = "C"
). - Server-side: Process final payment with 3DS authentication data (
/oidc/api/v2/transactions/card
).
The runtime execution steps have strict dependencies. For example, the 3DS lookup requires both the encrypted card details (from client) and the session ID (from Cardinal setup), while the final payment requires authentication data from the 3DS lookup process.
3D Secure Implementation Process
The complete 3D Secure payment flow consists of steps that must be implemented in a specific order to ensure proper functionality.
Client-side and server-side preparations can be done in parallel, but the runtime execution flow must follow the order outlined above.
Client-side Setup
The following configuration elements are required to set up the client-side for server-to-server 3D Secure payments:
- Set up client-side with required JavaScript libraries
- Configure the 3D Secure script
- Set up event listeners
- Initialise 3D Secure with a JWT token
- Implement card number detection
Server-side Setup
- Encrypt card details with Verifone.js
- Create a 3DS lookup endpoint
- Create a payment processing endpoint to complete the payment
Client-side Setup
1. Set up client-side with required JavaScript libraries
To capture and encrypt card details using verifone.js
, you need to include the verifone.js
library in the head of your web page used to capture card details. You also need to include the Cardinal Commerce songbird.js
library to handle the 3D Secure authentication process.
<head>
<script src="https://au.jsclient.verifone.cloud/verifone.js"></script>
<script src="https://songbird.cardinalcommerce.com/edge/v1/songbird.js"></script>
</head>
2. Configure the 3D Secure script
Configure the 3D Secure script using the Cardinal.configure()
method. This sets up the Cardinal Commerce library with appropriate timeout and retry settings:
Cardinal.configure({
timeout: 6000,
maxRequestRetries: 3,
logging: { level: "verbose" }
});
3. Set up event listeners
Set up event listeners for Cardinal Commerce events:
Cardinal.on("payments.setupComplete", function(setupCompleteData) {
sessionId = setupCompleteData.sessionId;
document.getElementById("encryptBtn").disabled = false;
});
Cardinal.on("payments.validated", function(data, jwt) {
switch(data.ActionCode) {
case "SUCCESS":
processPayment(data.Payment.ExtendedData);
break;
case "NOACTION":
// Proceed with payment without 3DS
break;
case "FAILURE":
case "ERROR":
alert('Authentication failed. Please try again.');
break;
}
});
4. Initialise 3D Secure with JWT token
Initialise the Cardinal Commerce 3D Secure with a JWT token obtained from the 3D Secure API using the /oidc/3ds-service/v2/jwt/create
endpoint.
This JWT token is required to start the 3D Secure authentication process.
async function initializeThreeDS() {
try {
const tokenData = await getJWTFromServer();
Cardinal.setup("init", { jwt: tokenData.jwt });
} catch (error) {
console.error("Failed to initialise 3DS:", error);
}
}
The
getJWTFromServer()
function should be implemented to fetch the JWT token from your server-side endpoint that generates the token using the/oidc/3ds-service/v2/jwt/create
endpoint. See 3D Secure API documentation for more details.
async function getJWTFromServer() {
const response = await fetch("/api/auth/jwt-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
threeds_contract_id: "your_3ds_contract_id", // Replace with your actual 3DS contract ID
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result.jwt;
}
5. Implement card number detection
Add the Cardinal field decorator to your card number input:
<input type="text" id="cardNumber" data-cardinal-field="AccountNumber" />
Encrypt card details with verifone.js
Capture and encrypt card using the verifone.getEncryptedCardDetails()
method. This method requires the card details and your public Secure Card Capture key to encrypt the card data.
const encrypt = async () => {
try {
const publicKey = "LS0tLS1CRUd........."; // Replace with your actual Secure Card Capture key
const card = {
cardNumber: document.getElementById("cardNumber").value,
expiryMonth: document.getElementById("expiryMonth").value,
expiryYear: document.getElementById("expiryYear").value,
cvv: document.getElementById("cardCvv").value,
};
console.log("Encrypting card with Verifone.js");
const result = await verifone.getEncryptedCardDetails(
card,
publicKey
);
// Store the encrypted card details in a variable for later use:
paymentEncryptedCard = result.encryptedCard;
await performThreeDSLookup(result.encryptedCard);
} catch (error) {
console.error("Encryption failed:", error);
alert(`Card encryption failed: ${error.message}`);
}
};
verifone.encryptCard
returns a Promise. Not all browsers support async-await, which you should consider in your implementation.
Perform 3DS lookup with encrypted card data
Send the encrypted card details and session ID to your server for 3DS lookup. Your server-side logic should be configured to handle the 3DS lookup to the 3D Secure API using the /oidc/3ds-service/v2/lookup
endpoint. This endpoint requires the encrypted card details and session ID to perform the lookup.
async function performThreeDSLookup(encryptedCard) {
const lookupData = {
amount: 1000,
currency_code: "AUD",
encrypted_card: encryptedCard,
device_info_id: sessionId,
threeds_contract_id: "your_3ds_contract_id"
};
// Send the lookup data to your server
const response = await fetch('/api/3ds-lookup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(lookupData)
});
const result = await response.json();
handleThreeDSResult(result);
};
Handle authentication challenge
Handle the 3DS authentication challenge if required:
function handleThreeDSResult(result) {
if (result.pares_status === "C") {
Cardinal.continue("cca", {
AcsUrl: result.acs_url,
Payload: result.payload
},{
OrderDetails: {
TransactionId: result.transaction_id,
}
});
} else if (result.pares_status === "Y") {
// Proceed to payment
processPayment(result);
} else {
console.log("⚠️ 3DS status unclear:", result.pares_status);
alert(`3DS completed with status: ${result.pares_status}`);
}
};
Process final payment with 3DS authentication data
When the 3DS authentication is successful, you can call the processPayment
function with the 3DS data returned from the lookup and the encrypted card details.
Your server-side logic should be configured to handle the final payment processing using the /oidc/api/v2/transactions/card endpoint.
This endpoint requires the encrypted card details, session ID, and 3DS authentication data to process the payment.
async function processPayment(threeDSData) {
try {
const paymentData = {
amount: 1000,
currency_code: "AUD",
encrypted_card: paymentEncryptedCard,
device_info_id: sessionId,
threeds_contract_id: "your_3ds_contract_id",
three_ds_data: threeDSData
};
// Send the payment data to your server
const response = await fetch('/api/process-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(paymentData)
});
const result = await response.json();
if (!response.ok || !result.success) {
throw new Error(
`Payment failed: ${result.message || "Unknown error"}`
);
}
} catch (error) {
console.error("Payment processing failed:", error);
alert(`❌ Payment Failed!\n\n${error.message}`);
}
};
See the Secure Card Capture Key documentation for more information on how to obtain your Secure Card Capture key.
See the eCommerce API documentation for more information on how to transact using the Secure Card Capture with the eCommerce API.
Updated 1 day ago