Appearance
3D Secure (3DS) Pass-Through with PAAY JS SDK
This guide covers how to collect 3D Secure authentication data using PAAY's standalone JavaScript SDK and pass it through to our /api/transaction endpoint. This approach is ideal when you are building your own payment form and want full control over the 3DS authentication flow, rather than using our Tokenizer.
When to Use This Approach
| Approach | Best For |
|---|---|
| Tokenizer with built-in 3DS | Standard integrations where you want the gateway to handle everything — card collection, tokenization, and 3DS — in a single iframe. See Tokenizer 3DS docs. |
| PAAY JS SDK + API pass-through (this guide) | Custom payment forms where you need full control over the checkout experience, are already PCI-compliant (SAQ D), or need to integrate 3DS into an existing payment flow that calls the Gateway API directly. |
Overview
The pass-through integration works in three stages:
- Authenticate — PAAY's JavaScript SDK authenticates the cardholder with the card issuer on your checkout page.
- Collect — After authentication, PAAY returns the 3DS values (CAVV, ECI, XID/dsTransId) to your frontend.
- Submit — Your server includes these 3DS values in the transaction request to our
/api/transactionendpoint.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Your Checkout │──▶│ PAAY 3DS │──▶│ Card Issuer │──▶│ 3DS Result │
│ Page │ │ JS SDK │ │ (ACS) │ │ (CAVV/ECI) │
└──────────────┘ └──────────────┘ └──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ Your Server │
│ │
│ POST to │
│ Gateway │
│ /api/ │
│ transaction │
└──────────────┘Prerequisites
- PAAY account — You need an active account with PAAY (3DS Integrator). Contact your account representative or PAAY directly at info@paay.co to get set up.
- PAAY API key — Found in the PAAY Portal. This is separate from your Gateway API key.
- Gateway API key — Your secret API key (
api_XXXX) for server-side transaction processing. - PCI compliance — Since you're collecting card data in your own form (not via Tokenizer), your environment must meet the appropriate PCI compliance requirements (typically SAQ D).
Step 1: Set Up Your Payment Form
Your payment form must include card inputs tagged with data-threeds attributes so the PAAY SDK can read them. You also need hidden fields for the transaction ID and amount.
html
<form id="checkout-form" action="/api/charge" method="post">
<!-- Cardholder Information -->
<label>Card Number</label>
<input type="text" name="cardNumber" maxlength="16" data-threeds="pan" />
<label>Expiration Month</label>
<select name="cardMonth" data-threeds="month">
<option value="">--</option>
<option value="01">01</option>
<option value="02">02</option>
<!-- ... -->
<option value="12">12</option>
</select>
<label>Expiration Year</label>
<select name="cardYear" data-threeds="year">
<option value="">--</option>
<option value="25">2025</option>
<option value="26">2026</option>
<!-- ... -->
<option value="30">2030</option>
</select>
<label>CVV</label>
<input type="text" name="cvv" maxlength="4" />
<!-- Hidden 3DS fields -->
<input type="hidden" name="x_transaction_id" value="" data-threeds="id" />
<input type="hidden" name="paayAmount" value="25.00" data-threeds="amount" />
<!-- 3DS results will be injected here by the SDK -->
<!-- (cavv, eci, xid are auto-appended when appendForm is true) -->
<button type="submit" id="pay-button">Pay $25.00</button>
</form>Required data-threeds Attributes
| Attribute | Input | Description |
|---|---|---|
data-threeds="pan" | Card number | The full credit card number. |
data-threeds="month" | Expiration month | 1 or 2 digit month (e.g., 01, 12). |
data-threeds="year" | Expiration year | 2 digit year (e.g., 25, 30). |
data-threeds="id" | Transaction ID | A unique identifier for this 3DS authentication session. Auto-generated — see Step 2. |
data-threeds="amount" | Transaction amount | The transaction amount in decimal format (e.g., 25.00). |
Step 2: Include the PAAY JS SDK
Add the PAAY JavaScript library and initialization script before the closing </body> tag:
html
<!-- PAAY 3DS JavaScript SDK -->
<script src="https://cdn.3dsintegrator.com/threeds.min.latest.js"></script>
<!-- Generate a unique transaction ID for this 3DS session -->
<script>
var idInput = document.querySelector('[data-threeds=id]');
idInput.value = 'id-' + Math.random().toString(36).substr(2, 16);
</script>
<!-- Initialize the PAAY ThreeDS SDK -->
<script>
var tds = new ThreeDS(
'checkout-form', // Your form's ID attribute
'YOUR_PAAY_API_KEY', // Your PAAY API key from the PAAY Portal
null, // Pass null to let the SDK generate the JWT
{
autoSubmit: false, // We'll trigger authentication manually
endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2', // Sandbox endpoint
verbose: true // Enable console logging for debugging
}
);
</script>SDK Constructor Parameters
| Parameter | Description |
|---|---|
formId | The id attribute of the <form> element containing your card inputs. |
apiKey | Your PAAY API key from the PAAY Portal. |
jwt | Pass null to let the SDK generate a JWT automatically. Or provide your own server-generated JWT. |
options | Configuration object — see below. |
SDK Options
| Option | Default | Description |
|---|---|---|
autoSubmit | true | When true, authentication triggers automatically as the customer fills in card fields. Set to false for manual control via tds.verify(). |
endpoint | (production) | The PAAY API endpoint. Use https://api-sandbox.3dsintegrator.com/v2.2 for testing, and the production endpoint for live transactions. |
verbose | false | Enable detailed console logging for debugging. |
appendForm | true | Automatically inject cavv, eci, and xid as hidden inputs in your form after authentication. |
eciInputId | eci | The name attribute for the auto-appended ECI hidden input. |
cavvInputId | cavv | The name attribute for the auto-appended CAVV hidden input. |
xidInputId | xid | The name attribute for the auto-appended XID hidden input. |
rebill | false | Set to a decimal amount (e.g., 89.95) to also authenticate a rebill amount. Returns rebill_cavv, rebill_eci, rebill_xid. |
Step 3: Trigger Authentication
Option A: Auto-Submit (Recommended for Simple Forms)
With autoSubmit: true (the default), the PAAY SDK automatically begins authentication as soon as the customer finishes entering their card number, expiration month, and expiration year. The 3DS values are injected into your form as hidden inputs.
javascript
var tds = new ThreeDS(
'checkout-form',
'YOUR_PAAY_API_KEY',
null,
{
autoSubmit: true,
endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
verbose: true
}
);When the customer clicks your submit button, the cavv, eci, and xid values will already be present as hidden fields in the form (assuming authentication completed).
Option B: Manual Verify (Recommended for Full Control)
With autoSubmit: false, you call tds.verify() yourself — typically when the customer clicks the pay button. This gives you control over when authentication happens and lets you handle the result before submitting.
javascript
var tds = new ThreeDS(
'checkout-form',
'YOUR_PAAY_API_KEY',
null,
{
autoSubmit: false,
endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
verbose: true
}
);
document.getElementById('pay-button').addEventListener('click', function(e) {
e.preventDefault();
// Optional: Only authenticate Visa and Mastercard
var cardNumber = document.querySelector('[data-threeds=pan]').value;
var firstDigit = cardNumber.charAt(0);
if (firstDigit === '4' || firstDigit === '5' || firstDigit === '2') {
// Visa (4) or Mastercard (5, 2) — run 3DS authentication
tds.verify({
resolve: function(data) {
console.log('3DS Authentication Result:', data);
console.log('Status:', data.status); // Y, A, N, U, R
console.log('ECI:', data.eci); // e.g., "05"
console.log('CAVV:', data.authenticationValue);
if (data.status === 'Y' || data.status === 'A') {
// Authentication successful — submit payment
submitPayment(data);
} else {
// Authentication failed — decide whether to proceed
alert('3DS authentication was not successful. Status: ' + data.status);
}
},
reject: function(error) {
console.error('3DS Error:', error);
// Decide whether to proceed without 3DS or show error
alert('3DS authentication could not be completed.');
}
});
} else {
// Amex (3), Discover (6), or other — submit without 3DS
submitPayment(null);
}
});Step 4: Handle the 3DS Response
After a successful authentication, the PAAY SDK returns a response object. The key fields you need for the gateway API are:
3DS Response Fields
| Field | Description | Use in API |
|---|---|---|
authenticationValue | The CAVV (Cardholder Authentication Verification Value). A Base64-encoded cryptographic proof of authentication. | Pass as cavv |
eci | Electronic Commerce Indicator. Indicates the authentication level (e.g., "05" for Visa fully authenticated, "02" for Mastercard fully authenticated). | Pass as eci |
status | Authentication status code. See table below. | Used for your decision logic |
dsTransId | Directory Server Transaction ID. Used as the XID for 3DS 2.x transactions. | Pass as xid |
acsTransId | Access Control Server transaction ID. | Optional reference |
protocolVersion | The 3DS protocol version used (e.g., "2.2.0", "2.1.0"). | Optional reference |
scaIndicator | Indicates if Strong Customer Authentication (SCA) was applied. | Optional reference |
Example 3DS Response Object
Successful Authentication (Frictionless):
json
{
"authenticationValue": "XYi1pplo2XITHfJdT21SweFz1us=",
"eci": "05",
"status": "Y",
"protocolVersion": "2.2.0",
"dsTransId": "d65e93c3-35ab-41ba-b307-767bfc19eae3",
"acsTransId": "ca5f9649-b865-47ce-be6f-54422a0fce47",
"scaIndicator": false
}Failed Authentication:
json
{
"eci": "07",
"status": "N",
"protocolVersion": "2.2.0",
"dsTransId": "d65e93c3-35ab-41ba-b307-767bfc19eae3",
"acsTransId": "65973509-34be-401c-8534-9712c089a938",
"scaIndicator": false
}3DS Status Codes
| Status | Meaning | Liability Shift | Action |
|---|---|---|---|
Y | Fully Authenticated — Cardholder identity verified. | ✅ Yes | Proceed with transaction. Include 3DS data. |
A | Attempted — Authentication attempted but issuer doesn't fully support it. | ✅ Yes (typically) | Proceed with transaction. Include 3DS data. |
N | Not Authenticated — Cardholder failed authentication. | ❌ No | Consider declining or proceeding without 3DS (at your own risk). |
R | Rejected — Authentication rejected by the issuer. | ❌ No | Do not proceed. Inform the cardholder. |
U | Unavailable — Authentication could not be performed (technical issue). | ❌ No | Proceed without 3DS or retry. |
C | Challenge Required — Cardholder was presented a challenge (handled by SDK). | Depends on outcome | Wait for final result after challenge completion. |
Step 5: Submit to the gatewway API
After collecting the 3DS authentication data, send it along with the card details to our /api/transaction endpoint from your server.
Frontend: Send Data to Your Server
javascript
function submitPayment(threedsData) {
var formData = {
cardNumber: document.querySelector('[name=cardNumber]').value,
cardMonth: document.querySelector('[name=cardMonth]').value,
cardYear: document.querySelector('[name=cardYear]').value,
cvv: document.querySelector('[name=cvv]').value,
amount: 2500 // $25.00 in cents
};
// Include 3DS data if authentication was performed
if (threedsData) {
formData.threeds = {
cavv: threedsData.authenticationValue,
eci: threedsData.eci,
xid: threedsData.dsTransId,
status: threedsData.status,
version: threedsData.protocolVersion
};
}
fetch('/api/charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
.then(function(response) { return response.json(); })
.then(function(data) {
if (data.success) {
window.location.href = '/thank-you';
} else {
alert('Payment failed: ' + data.message);
}
});
}Backend: Call the Gateway Transaction API
Your server-side code takes the card data and 3DS values and submits them to gateway. The 3DS authentication values are included in the three_d_secure object within the transaction request.
bash
curl -X POST { https://sandbox.koipay.io }/api/transaction \
-H "Authorization: YOUR_SECRET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "sale",
"amount": 2500,
"currency": "usd",
"payment_method": {
"card": {
"card_number": "4111111111111111",
"expiration_date": "12/25",
"cvc": "123"
}
},
"three_d_secure": {
"cavv": "XYi1pplo2XITHfJdT21SweFz1us=",
"eci": "05",
"xid": "d65e93c3-35ab-41ba-b307-767bfc19eae3"
}
}'Transaction Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "sale" or "auth" |
amount | integer | Yes | Transaction amount in cents (e.g., 2500 = $25.00) |
currency | string | No | Currency code (default: "usd") |
payment_method.card.card_number | string | Yes | The full card number |
payment_method.card.expiration_date | string | Yes | Card expiration in MM/YY format |
payment_method.card.cvc | string | No | Card verification code |
three_d_secure.cavv | string | Yes* | Cardholder Authentication Verification Value from PAAY |
three_d_secure.eci | string | Yes* | Electronic Commerce Indicator from PAAY |
three_d_secure.xid | string | Yes* | Transaction ID — use dsTransId from the PAAY response |
* Required when submitting a 3DS-authenticated transaction. Omit the entire three_d_secure object if 3DS was not performed.
Note: You can also use a
tokenfrom the Tokenizer inpayment_method.tokeninstead of raw card data. In that case, you would collect the card via Tokenizer, authenticate via PAAY separately, and then include both the token and 3DS data in your API call.
Complete Example
Here is a full working example with the PAAY JS SDK and manual verification:
html
<!DOCTYPE html>
<html>
<head>
<title>Checkout with 3DS</title>
<style>
body { font-family: -apple-system, sans-serif; max-width: 480px; margin: 40px auto; }
label { display: block; margin: 12px 0 4px; font-weight: 600; }
input, select { padding: 8px; width: 100%; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
.row { display: flex; gap: 12px; }
.row > div { flex: 1; }
.pay-btn { margin-top: 20px; background: #0066cc; color: white; border: none; padding: 14px;
border-radius: 6px; font-size: 16px; cursor: pointer; width: 100%; }
.pay-btn:hover { background: #0052a3; }
.pay-btn:disabled { background: #999; cursor: not-allowed; }
.status { margin-top: 12px; padding: 10px; border-radius: 4px; display: none; }
.status.info { display: block; background: #e8f4fd; color: #0066cc; }
.status.error { display: block; background: #fde8e8; color: #cc0000; }
.status.success { display: block; background: #e8fde8; color: #006600; }
</style>
</head>
<body>
<h1>Checkout — $25.00</h1>
<form id="checkout-form">
<label>Card Number</label>
<input type="text" name="cardNumber" maxlength="19" placeholder="4111 1111 1111 1111"
data-threeds="pan" autocomplete="cc-number" />
<div class="row">
<div>
<label>Month</label>
<select name="cardMonth" data-threeds="month">
<option value="">--</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="08">08</option>
<option value="09">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select>
</div>
<div>
<label>Year</label>
<select name="cardYear" data-threeds="year">
<option value="">--</option>
<option value="25">2025</option>
<option value="26">2026</option>
<option value="27">2027</option>
<option value="28">2028</option>
<option value="29">2029</option>
<option value="30">2030</option>
</select>
</div>
<div>
<label>CVV</label>
<input type="text" name="cvv" maxlength="4" placeholder="123" autocomplete="cc-csc" />
</div>
</div>
<!-- Hidden 3DS fields -->
<input type="hidden" name="x_transaction_id" value="" data-threeds="id" />
<input type="hidden" name="paayAmount" value="25.00" data-threeds="amount" />
<button type="button" class="pay-btn" id="pay-button">Pay $25.00</button>
<div id="status" class="status"></div>
</form>
<!-- PAAY 3DS JS SDK -->
<script src="https://cdn.3dsintegrator.com/threeds.min.latest.js"></script>
<script>
// Generate unique 3DS transaction ID
var idInput = document.querySelector('[data-threeds=id]');
idInput.value = 'id-' + Math.random().toString(36).substr(2, 16);
// Initialize PAAY ThreeDS SDK
var tds = new ThreeDS(
'checkout-form',
'YOUR_PAAY_API_KEY', // Replace with your PAAY API key
null,
{
autoSubmit: false, // Manual control
endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
verbose: true
}
);
// Status display helper
function showStatus(message, type) {
var el = document.getElementById('status');
el.textContent = message;
el.className = 'status ' + type;
}
// Handle pay button click
document.getElementById('pay-button').addEventListener('click', function() {
var btn = this;
btn.disabled = true;
btn.textContent = 'Processing...';
showStatus('Authenticating card...', 'info');
var cardNumber = document.querySelector('[name=cardNumber]').value.replace(/\s/g, '');
var firstDigit = cardNumber.charAt(0);
// Only run 3DS for Visa (4) and Mastercard (5, 2)
if (firstDigit === '4' || firstDigit === '5' || firstDigit === '2') {
tds.verify({
resolve: function(data) {
console.log('3DS Result:', data);
if (data.status === 'Y' || data.status === 'A') {
showStatus('Authenticated! Processing payment...', 'success');
submitToServer({
cavv: data.authenticationValue,
eci: data.eci,
xid: data.dsTransId,
status: data.status,
version: data.protocolVersion
});
} else {
showStatus('Authentication was not successful (status: ' + data.status + '). Payment not processed.', 'error');
btn.disabled = false;
btn.textContent = 'Pay $25.00';
}
},
reject: function(error) {
console.error('3DS Error:', error);
showStatus('Authentication error. You may retry or proceed without 3DS.', 'error');
btn.disabled = false;
btn.textContent = 'Pay $25.00';
}
});
} else {
// Non-Visa/MC — proceed without 3DS
showStatus('Processing payment...', 'info');
submitToServer(null);
}
});
function submitToServer(threedsData) {
var payload = {
cardNumber: document.querySelector('[name=cardNumber]').value.replace(/\s/g, ''),
cardMonth: document.querySelector('[name=cardMonth]').value,
cardYear: document.querySelector('[name=cardYear]').value,
cvv: document.querySelector('[name=cvv]').value,
amount: 2500
};
if (threedsData) {
payload.threeds = threedsData;
}
fetch('/api/charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
showStatus('Payment successful!', 'success');
setTimeout(function() { window.location.href = '/thank-you'; }, 1500);
} else {
showStatus('Payment failed: ' + (data.message || 'Unknown error'), 'error');
document.getElementById('pay-button').disabled = false;
document.getElementById('pay-button').textContent = 'Pay $25.00';
}
})
.catch(function() {
showStatus('Network error. Please try again.', 'error');
document.getElementById('pay-button').disabled = false;
document.getElementById('pay-button').textContent = 'Pay $25.00';
});
}
</script>
</body>
</html>Server-Side Example (Node.js)
Here's an example of your server receiving the frontend data and calling the Gateway API:
javascript
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
const GW_API_KEY = 'your_secret_api_key';
const GW_URL = 'https://sandbox.koipay.io/api/transaction';
app.post('/api/charge', async (req, res) => {
const { cardNumber, cardMonth, cardYear, cvv, amount, threeds } = req.body;
// Build the transaction request
const transactionRequest = {
type: 'sale',
amount: amount,
currency: 'usd',
payment_method: {
card: {
card_number: cardNumber,
expiration_date: cardMonth + '/' + cardYear,
cvc: cvv
}
}
};
// Include 3DS data if authentication was performed
if (threeds && threeds.cavv) {
transactionRequest.three_d_secure = {
cavv: threeds.cavv,
eci: threeds.eci,
xid: threeds.xid
};
}
try {
const response = await fetch(GW_URL, {
method: 'POST',
headers: {
'Authorization': GW_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(transactionRequest)
});
const data = await response.json();
if (data.status === 'success') {
res.json({ success: true, transactionId: data.data.id });
} else {
res.json({ success: false, message: data.msg });
}
} catch (error) {
console.error('Transaction error:', error);
res.json({ success: false, message: 'Internal server error' });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));Using with Tokenizer Tokens
If you want the best of both worlds — PCI scope reduction via Tokenizer AND manual 3DS control via PAAY — you can combine both approaches:
- Use our Tokenizer to collect card data and receive a token (with
paay.forceDisabled: trueto skip Tokenizer's built-in 3DS). - Use PAAY's JS SDK on a separate form with the card data to authenticate via 3DS.
- Send both the token and the 3DS values to your server.
- Submit the transaction with the token as the payment method and 3DS values as pass-through.
json
{
"type": "sale",
"amount": 2500,
"payment_method": {
"token": "the-token-from-tokenizer"
},
"three_d_secure": {
"cavv": "XYi1pplo2XITHfJdT21SweFz1us=",
"eci": "05",
"xid": "d65e93c3-35ab-41ba-b307-767bfc19eae3"
}
}Note: This hybrid approach is advanced. In most cases, using the Tokenizer's built-in 3DS support is simpler and recommended.
ECI Values Reference
The ECI (Electronic Commerce Indicator) value indicates the level of authentication achieved. Commonly returned values:
| ECI | Visa | Mastercard | Meaning |
|---|---|---|---|
05 | ✅ | — | Fully authenticated (Visa) |
06 | ✅ | — | Attempted authentication (Visa) |
07 | ✅ | — | Not authenticated / non-3DS (Visa) |
02 | — | ✅ | Fully authenticated (Mastercard) |
01 | — | ✅ | Attempted authentication (Mastercard) |
00 | — | ✅ | Not authenticated / non-3DS (Mastercard) |
Testing
PAAY Sandbox
During development, point the SDK to PAAY's sandbox environment:
javascript
{
endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
verbose: true
}Gateway Sandbox
Use your sandbox API key and the sandbox URL for transaction processing:
https://sandbox.koipay.io/api/transactionCombine with our test card numbers and PAAY sandbox test cards to simulate various 3DS outcomes.
Switching to Production
When going live, update both endpoints:
- PAAY SDK: Remove the
endpointoption or set it to the production URL provided by PAAY. - Gateway API: Switch from
https://sandbox.koipay.ioto your production gateway URL. - API Keys: Replace sandbox keys with production keys for both PAAY and the gateway.
- Verbose: Set
verbose: falseto stop console logging.
Troubleshooting
| Issue | Solution |
|---|---|
| PAAY SDK not initializing | Verify the form ID matches your <form> element's id attribute. Check the browser console for errors. |
data-threeds inputs not detected | Ensure inputs with data-threeds attributes are inside the form element referenced by the SDK. |
3DS authentication always returns U | Check that the card number is valid and the card issuer supports 3DS. In sandbox, use PAAY's test cards. |
| Challenge window not appearing | Ensure the SDK script is loaded and the form is properly configured. Check verbose: true console output. |
| The gateway rejects 3DS values | Verify that cavv, eci, and xid are passed correctly in the three_d_secure object. Values must be unaltered from the PAAY response. |
tds.verify() not calling resolve/reject | Ensure card number, month, and year fields have valid values before calling verify. |
| Authentication works in sandbox but not production | Confirm you're using production API keys and endpoints for both PAAY and the gateway. |
FAQ
What's the difference between using Tokenizer 3DS vs. PAAY JS SDK directly?
With Tokenizer's built-in 3DS, everything is handled inside the secure iframe — card collection, tokenization, and 3DS authentication. With the PAAY JS SDK approach, you manage the card form yourself and control when and how 3DS authentication occurs, then pass the results to our API. The Tokenizer approach is simpler; the PAAY SDK approach offers more flexibility.
Do I need a separate PAAY account?
Yes. PAAY provides the 3DS authentication service. Contact your gateway account representative to get set up with PAAY, or reach out to PAAY directly.
Does the PAAY JS SDK collect or see the full card number?
The PAAY SDK reads the card number, expiration month, and year from your form inputs (tagged with data-threeds attributes) to perform the 3DS authentication with the card issuer. The SDK communicates directly with PAAY's servers — the card data does not pass through your server during the 3DS authentication step.
Can I authenticate Amex and Discover cards?
PAAY supports Visa, Mastercard, American Express, and Discover. However, authentication rates vary by card brand and issuer. Check with PAAY for current support details.
What if 3DS authentication fails? Can I still process the transaction?
Yes, you can choose to process the transaction without 3DS data. However, you will not receive the liability shift benefit, meaning you remain liable for fraud-related chargebacks. Your business rules should determine whether to proceed.
Can I use this for recurring/subscription payments?
Yes. Authenticate the initial transaction with 3DS, then use the resulting credentials for subsequent recurring charges. PAAY's rebill option can also authenticate the rebill amount separately. See the Recurring API docs for setting up subscriptions.