Overview
This guide provides step-by-step instructions for integrating S2S V3 card payments with 3D Secure authentication.
Prerequisites
- BudPay API credentials (Bearer token)
- Cardinal Commerce setup
- Backend API endpoints configured
- Frontend iframe container for device data collection
Integration Flow
Step 1: Card Data Encryption
First, encrypt your card data using the test encryption endpoint:
curl --location 'https://backendapi.budpay.com/api/s2s/test/encryption' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxx' \
--data '{
"data": {
"number": "4000000000001091",
"expiryMonth": "12",
"expiryYear": "29",
"cvv": "484"
},
"reference": "ref_21655737521418683"
}'
Expected Response:
740a75e2d40b04c29bca8ecd8fda00bceb3553a3dd31659d6f26c034cbb78c71bbf88bc31c3fc2d40669f3afa76e6f4fe517d38ab90d08cbd5d1cb64b20655db7c87a500e2edbd827c2c46931e7ce99b
Step 2: Transaction Initialization
Initialize the transaction with encrypted card data:
curl --location 'https://backendapi.budpay.com/api/s2s/transaction/initialize' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer sk_live_gqyzln6pkbukqswylpcbcnr0diyygghe7mbeq5y' \
--data-raw '{
"email": "test@email.com",
"amount": "1",
"currency": "USD",
"reference": "ref_21655737521418683",
"card" :"740a75e2d40b04c29bca8ecd8fda00bceb3553a3dd31659d6f26c034cbb78c71bbf88bc31c3fc2d40669f3afa76e6f4fe517d38ab90d08cbd5d1cb64b20655db7c87a500e2edbd827c2c46931e7ce99b"
}'
Expected Response:
{
"status": true,
"message": "Customer Authentication Successful",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MTU4ZTcwZS0zOWJhLTQwOGYtOTdjYS00ODY5ODJjMmNlYWUiLCJpYXQiOjE3NDcyMDcyODksImlzcyI6IjVkZDgzYmYwMGU0MjNkMTQ5OGRjYmFjYSIsImV4cCI6MTc0NzIxMDg4OSwiT3JnVW5pdElkIjoiNjgxNGE2ZDY3N2QxZWQ2YjNkMjMyZjVkIiwiUmVmZXJlbmNlSWQiOiI4ZmViNGU1NC0wZTllLTQxOWEtOTA5OC1kMDliNDEwZjgxYzUifQ.-1BtZuhnEKHRRQQY3KHZ0lJtkDMt82xjuPT9BrTfe4A",
"deviceDataCollectionUrl": "https://centinelapistag.cardinalcommerce.com/V1/Cruise/Collect",
"referenceId": "8feb4e54-0e9e-419a-9098-d09b410f81c5"
}
}
Step 3: Device Data Collection (HTML/JS Implementation)
Create an iframe for Cardinal Commerce device fingerprinting:
<!DOCTYPE html>
<html>
<head>
<title>Card Payment</title>
</head>
<body>
<div class="cardinal-iframe-container" style="width: 100%; height: 400px;">
<!-- Cardinal iframe will be injected here -->
</div>
<script>
const performDeviceDataCollection = (reference, callback) => {
// 1. Find iframe container
const iFrameContainer = document.querySelector('.cardinal-iframe-container');
if (!iFrameContainer) {
console.error('Iframe container not found');
callback();
return;
}
// 2. Create iframe
const iframe = document.createElement('iframe');
iframe.src = `https://backendapi.budpay.com/cr6t76y8uijkny/authdevice/${reference}`;
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = 'none';
iframe.id = 'cardinal-collection-iframe';
// 3. Listen for Cardinal events
const messageHandler = (event) => {
try {
if (event.data && event.data.MessageType === 'profile.completed') {
console.log('Cardinal profile completed');
window.removeEventListener('message', messageHandler);
clearTimeout(timeoutId);
callback();
}
} catch (error) {
console.error('Error parsing Cardinal message:', error);
}
};
// 4. Handle iframe load
iframe.onload = () => {
console.log('Cardinal iframe loaded successfully');
window.addEventListener('message', messageHandler);
};
// 5. Handle loading errors
iframe.onerror = () => {
console.error('Failed to load Cardinal iframe');
callback();
};
// 6. Add iframe to container
iFrameContainer.appendChild(iframe);
// 7. Fallback timeout (4.5 seconds)
const timeoutId = setTimeout(() => {
console.log('Cardinal timeout reached, proceeding...');
window.removeEventListener('message', messageHandler);
callback();
}, 4500);
};
// Usage example:
// Pass transaction reference used in initialize endpoint
const reference = "ref_21655737521418683"; // From initialization response
performDeviceDataCollection(reference, () => {
console.log('Device data collection completed, proceed to enrollment check');
// Proceed to Step 4
});
</script>
</body>
</html>
Step 4: Enrollment Check
After device data collection, check if 3D Secure authentication is required:
curl --location 'https://backendapi.budpay.com/api/cr6t76y8uijkny-enrollmentcheck' \
--header 'Content-Type: application/json' \
--data '{
"cardnumber": "4000000000001091",
"expiryMonth": "12",
"expiryYear": "29",
"cvv": "484",
"ref": "ref_21655737521418683"
}'
Expected Response :
{ "status": true, "message": "Proceed Payer Authentication", "data": { "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxOTAyNWJjMC03YmVhLTQzMmMtOTY0MS0wZDRhMzFjNGQzNDUiLCJpYXQiOjE3NDk3OTczMDcsImlzcyI6IjVlMjIwMDVmMzU2ZGNlMDNmMGY3ODcyZiIsImV4cCI6MTc0OTgwMDkwNywiT3JnVW5pdElkIjoiNjg0OWEzZTg5YWU5NjYzNTIyOGZmOWU3IiwiUGF5bG9hZCI6eyJBQ1NVcmwiOiJodHRwczovL2F1dGhlbnRpY2F0aW9uLmNhcmRpbmFsY29tbWVyY2UuY29tL1RocmVlRFNlY3VyZS9WMl8xXzAvQ1JlcSIsIlBheWxvYWQiOiJleUp0WlhOellXZGxWSGx3WlNJNklrTlNaWEVpTENKdFpYTnpZV2RsVm1WeWMybHZiaUk2SWpJdU1pNHdJaXdpZEdoeVpXVkVVMU5sY25abGNsUnlZVzV6U1VRaU9pSXpOVFEwWlRZeFlTMWtaVFZsTFRRek5Ea3RZams0WVMwelptTTNNbVl6WVRRNFltUWlMQ0poWTNOVWNtRnVjMGxFSWpvaU1XVmpaR0V3T1RNdFpUVXhNaTAwTjJNMUxUbGlZamN0TkdVME5qaGhZakZtWTJZNUlpd2lZMmhoYkd4bGJtZGxWMmx1Wkc5M1UybDZaU0k2SWpBeUluMCIsIlRyYW5zYWN0aW9uSWQiOiI5TVgxbXNad09CS2ZnTHRLSmlMMSJ9LCJPYmplY3RpZnlQYXlsb2FkIjp0cnVlLCJSZXR1cm5VcmwiOiJodHRwczovL2JhY2tlbmRhcGkuYnVkcGF5LmNvbS9hcGkvdGVzdC1jeWJlcnNvdXJjZS1yZXR1cm51cmwifQ.lYAnc-tTXZahxPWaVf0FFeilmOa2SuZg9LGAmTkWx_Y", "stepUpUrl": "https://centinelapi.cardinalcommerce.com/V2/Cruise/StepUp" }, "alt": "https://backendapi.budpay.com/cr6t76y8uijkny/authpayer/visa_june_12_2025_002" }
Step 5: 3D Secure Authentication (if required)
If enrollment check returns a redirect URL, open authentication window:
const handle3DSAuthentication = (redirectUrl) => { if (redirectUrl) { // Open 3DS authentication window const authWindow = window.open(redirectUrl, '_blank'); // Start monitoring payment status checkPaymentStatus(authWindow); } else { // No 3DS required, proceed to check final status checkPaymentStatus(); } };
Step 6: Payment Status Monitoring
Poll the transaction status until completion:
# Check transaction status curl --location 'https://backendapi.budpay.com/api/verify-transaction/ref_21655737521418683'
Expected Response:
{ "data": { "status": "success", // or "failed" or "pending" "message": "Transaction successful" } }
Complete Integration Example
Backend Integration (using cURL)
#!/bin/bash # Step 1: Encrypt card data ENCRYPTED_CARD=$(curl -s --location 'https://backendapi.budpay.com/api/s2s/test/encryption' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "data": { "number": "4000000000001091", "expiryMonth": "12", "expiryYear": "29", "cvv": "484" }, "reference": "ref_21655737521418683" }') echo "Encrypted card: $ENCRYPTED_CARD" # Step 2: Initialize transaction INIT_RESPONSE=$(curl -s --location 'https://backendapi.budpay.com/api/s2s/transaction/initialize' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data-raw '{ "email": "test@email.com", "amount": "1", "currency": "USD", "reference": "ref_21655737521418683", "card": "'$ENCRYPTED_CARD'" }') echo "Init response: $INIT_RESPONSE" # Extract reference used in transaction initialization for frontend use REFERENCE=$(echo $INIT_RESPONSE | jq -r '.reference') echo "Reference ID for frontend: $REFERENCE"
Frontend Integration (HTML/JS)
<!DOCTYPE html> <html> <head> <title>Cybersource Payment Integration</title> </head> <body> <h1>Payment Processing</h1> <div id="status">Initializing payment...</div> <div class="cardinal-iframe-container" style="width: 100%; height: 400px; border: 1px solid #ccc;"> <!-- Cardinal iframe will be injected here --> </div> <script> // Configuration const REFERENCE = "8feb4e54-0e9e-419a-9098-d09b410f81c5"; // From backend initialization const reference = "ref_21655737521418683"; // Update status display const updateStatus = (message) => { document.getElementById('status').textContent = message; }; // Device data collection const performDeviceDataCollection = (reference, callback) => { updateStatus('Collecting device data...'); const iFrameContainer = document.querySelector('.cardinal-iframe-container'); if (!iFrameContainer) { console.error('Iframe container not found'); callback(); return; } const iframe = document.createElement('iframe'); iframe.src = `https://backendapi.budpay.com/cr6t76y8uijkny/authdevice/${reference}`; iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms'); iframe.style.width = '100%'; iframe.style.height = '100%'; iframe.style.border = 'none'; const messageHandler = (event) => { try { if (event.data && event.data.MessageType === 'profile.completed') { console.log('Cardinal profile completed'); window.removeEventListener('message', messageHandler); clearTimeout(timeoutId); callback(); } } catch (error) { console.error('Error parsing Cardinal message:', error); } }; iframe.onload = () => { console.log('Cardinal iframe loaded'); window.addEventListener('message', messageHandler); }; iframe.onerror = () => { console.error('Cardinal iframe failed to load'); callback(); }; iFrameContainer.appendChild(iframe); const timeoutId = setTimeout(() => { console.log('Cardinal timeout reached'); window.removeEventListener('message', messageHandler); callback(); }, 4500); }; // Check payment status const checkPaymentStatus = async (authWindow = null) => { try { const response = await fetch(`https://backendapi.budpay.com/api/verify-transaction/${reference}`); const data = await response.json(); const status = data.data?.status; switch (status) { case 'pending': updateStatus('Payment pending, checking again...'); setTimeout(() => checkPaymentStatus(authWindow), 3000); break; case 'success': updateStatus('Payment successful!'); if (authWindow) authWindow.close(); break; case 'failed': updateStatus('Payment failed!'); if (authWindow) authWindow.close(); break; } } catch (error) { console.error('Error checking status:', error); updateStatus('Error checking payment status'); } }; // Start the process performDeviceDataCollection(reference, () => { updateStatus('Device data collection completed. Processing payment...'); // In a real implementation, you would make the enrollment check here // For this example, we'll proceed directly to status checking checkPaymentStatus(); }); </script> </body> </html>
Required API Endpoints
1. Card Encryption
POST https://backendapi.budpay.com/api/s2s/test/encryption Authorization: Bearer your_api_key
2. Transaction Initialization
POST https://backendapi.budpay.com/api/s2s/transaction/initialize Authorization: Bearer your_api_key
3. Device Authentication (Frontend Only)
GET https://backendapi.budpay.com/cr6t76y8uijkny/authdevice/{referenceId}
4. Enrollment Check
POST https://backendapi.budpay.com/api/cr6t76y8uijkny-enrollmentcheck
5. Transaction Verification
GET https://backendapi.budpay.com/api/verify-transaction/{reference}
Error Handling
Backend Error Handling (cURL)
# Check HTTP status codes HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" 'https://backendapi.budpay.com/api/s2s/test/encryption' \ --header 'Authorization: Bearer invalid_key') if [ $HTTP_STATUS -ne 200 ]; then echo "Error: HTTP $HTTP_STATUS" cat response.json fi
Frontend Error Handling
// Handle iframe errors iframe.onerror = () => { console.error('Cardinal iframe failed to load'); updateStatus('Error loading payment processor'); callback(); // Continue with fallback }; // Handle API errors const checkPaymentStatus = async (authWindow = null) => { try { const response = await fetch(`https://backendapi.budpay.com/api/verify-transaction/${TRANSACTION_REF}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); // ... rest of status handling } catch (error) { console.error('Status check failed:', error); updateStatus('Error checking payment status'); // Retry after delay setTimeout(() => checkPaymentStatus(authWindow), 5000); } };
Security Considerations
- API Key Security: Store API keys securely, never expose in frontend code
- Card Data Encryption: Always encrypt card data before transmission
- Iframe Sandboxing: Use proper sandbox attributes for Cardinal iframe
- HTTPS Required: All API calls must be over HTTPS
Implementation Checklist
- [ ] API credentials configured
- [ ] Card encryption endpoint working (cURL)
- [ ] Transaction initialization working (cURL)
- [ ] HTML page with iframe container ready
- [ ] Cardinal device collection implemented (JS)
- [ ] Enrollment check integration (cURL)
- [ ] 3DS window management (JS)
- [ ] Status polling implemented (JS)
- [ ] Error handling added
- [ ] Testing completed
This integration uses cURL for secure backend operations and HTML/JavaScript only for the Cardinal Commerce device data collection step, providing a secure and reliable payment processing flow.