Payments Vault Examples
A complete, runnable Payments Vault integration you can save to a file and open in a browser — render the card fields, create a payment token, and process the payment through Vrio's API.
Overview
This is a complete working sample of the Payments Vault SDK. The page below renders Vrio-hosted card fields, exchanges the card for a single-use payment token, and shows you the token and card details so you can process the payment through Vrio's API.
Save it to a .html file, serve it over HTTPS from a domain registered against your key, and open it in a browser. Enter your public key, click Initialize Card Fields, and you have a working tokenization flow to test against.
For the step-by-step walkthrough of building this into your own checkout, see Integrating the Payments Vault.
Testing Requirements:
- HTTPS is required — the Vault will not load over plain HTTP, on any domain
- Your domain must be registered against your public key. This is an exact host match, so
localhost,www.example.comandexample.comare separate entries- Use a test campaign — point the order call at a campaign routed to the Test gateway so nothing reaches a real processor
- Test cards — the standard test card numbers behave as they do everywhere else
Implementation Example
This example covers the full browser side: loading the SDK, rendering all five card fields, detecting the card brand as the customer types, and creating the payment token.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vrio Payments Vault - Tokenization</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #007cba;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-bottom: 10px;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.error {
color: #e74c3c;
margin-top: 5px;
}
.success {
color: #27ae60;
margin-top: 5px;
}
.info {
background-color: #e8f4fd;
border: 1px solid #bee5eb;
border-radius: 4px;
padding: 10px;
margin-bottom: 20px;
}
.section {
border: 1px solid #ddd;
padding: 20px;
margin-bottom: 20px;
border-radius: 4px;
}
.section h3 {
margin-top: 0;
}
/* The card fields are hosted by Vrio, so your CSS cannot style the
input itself. Draw the box on the container instead - it is the
container that controls the size of the field. */
.vault-field {
height: 42px;
padding: 0 10px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
box-sizing: border-box;
}
.row {
display: flex;
gap: 10px;
}
.row .form-group {
flex: 1;
}
</style>
</head>
<body>
<h1>Payments Vault Integration</h1>
<div class="info">
<strong>Testing Requirements:</strong>
<ul>
<li><strong>HTTPS:</strong> Required, including on localhost</li>
<li><strong>Domain registration:</strong> This page's hostname must be registered against your public key</li>
</ul>
</div>
<div class="section">
<h3>Configuration</h3>
<div class="form-group">
<label for="public-key">Payments Vault Public Key:</label>
<input type="text" id="public-key" placeholder="pk_live_..." required>
</div>
<button onclick="initializeVault()">Initialize Card Fields</button>
<div id="init-status"></div>
</div>
<div class="section">
<h3>Card Details</h3>
<div class="form-group">
<label>Card Number:</label>
<div id="card-number" class="vault-field"></div>
</div>
<div class="row">
<div class="form-group">
<label>Month:</label>
<div id="card-exp-month" class="vault-field"></div>
</div>
<div class="form-group">
<label>Year:</label>
<div id="card-exp-year" class="vault-field"></div>
</div>
<div class="form-group">
<label>CVV:</label>
<div id="card-cvv" class="vault-field"></div>
</div>
</div>
<button id="tokenize-button" onclick="tokenizeCard()" disabled>Create Payment Token</button>
<div id="field-status"></div>
</div>
<div class="section">
<h3>Tokenization Results</h3>
<div id="token-result"></div>
</div>
<script>
let vault;
const SDK_HOST = 'payments.vrio.app';
// Processing a token works for Visa, Mastercard, Discover and Amex.
// The SDK will tokenize other brands, but the payment will not go
// through - so catch them while the customer is still typing.
const UNSUPPORTED_BRANDS = ['diners', 'dinersclub', 'jcb', 'unionpay'];
// The public key goes on the SDK's script URL, so the script is loaded
// after the key is entered rather than being hardcoded in the page.
function loadSdk(publicKey) {
return new Promise(function(resolve, reject) {
const existing = document.getElementById('vault-sdk');
if (existing) {
existing.remove();
}
const script = document.createElement('script');
script.id = 'vault-sdk';
script.src = 'https://' + SDK_HOST + '/sdk/v1/vault.js?key=' + encodeURIComponent(publicKey);
script.onload = resolve;
script.onerror = function() {
reject(new Error(
'The SDK could not be loaded. Check that your key is correct and ' +
'that this page\'s hostname is registered against it.'
));
};
document.head.appendChild(script);
});
}
async function initializeVault() {
const publicKey = document.getElementById('public-key').value.trim();
const statusDiv = document.getElementById('init-status');
const fieldStatusDiv = document.getElementById('field-status');
if (!publicKey) {
statusDiv.innerHTML = '<div class="error">Please enter a Payments Vault public key</div>';
return;
}
try {
await loadSdk(publicKey);
// Only one instance can be active at a time, so tear down any
// previous one before initializing again.
if (vault) {
vault.destroy();
}
vault = PaymentVault.init({ publicKey: publicKey });
// Each field renders as its own frame hosted by Vrio. Your page
// cannot read what the customer types into them.
vault.createField('cardNumber', {
container: document.getElementById('card-number'),
placeholder: '4111 1111 1111 1111',
styles: cardFieldStyles(),
onChange: handleCardNumberChange
});
vault.createField('expirationMonth', {
container: document.getElementById('card-exp-month'),
styles: cardFieldStyles()
});
vault.createField('expirationYear', {
container: document.getElementById('card-exp-year'),
styles: cardFieldStyles()
});
vault.createField('cvv', {
container: document.getElementById('card-cvv'),
placeholder: '123',
styles: cardFieldStyles()
});
console.log('Payments Vault initialized');
statusDiv.innerHTML = '<div class="success">Card fields initialized successfully</div>';
fieldStatusDiv.innerHTML = '';
document.getElementById('tokenize-button').disabled = false;
} catch (error) {
console.error('Payments Vault initialization error:', error);
statusDiv.innerHTML = '<div class="error">' + error.message + '</div>';
document.getElementById('tokenize-button').disabled = true;
}
}
// Because the input lives inside Vrio's frame, your stylesheet cannot
// reach it. Styles are passed in when the field is created. Here the
// container draws the box and the input itself is transparent.
function cardFieldStyles() {
return {
fontFamily: 'Arial, sans-serif',
fontSize: '16px',
color: '#333333',
placeholderColor: '#999999',
backgroundColor: 'transparent',
borderWidth: '0',
outlineColor: 'transparent',
padding: '0'
};
}
// onChange reports the detected brand as soon as the number is
// recognizable - usually within the first few digits.
function handleCardNumberChange(state) {
const fieldStatusDiv = document.getElementById('field-status');
const brand = String(state && state.cardBrand || '')
.toLowerCase()
.replace(/[^a-z]/g, '');
if (brand && UNSUPPORTED_BRANDS.indexOf(brand) !== -1) {
fieldStatusDiv.innerHTML =
'<div class="error">That card type is not accepted. ' +
'Please use Visa, Mastercard, Discover or American Express.</div>';
document.getElementById('tokenize-button').disabled = true;
} else {
fieldStatusDiv.innerHTML = brand
? '<div class="success">Detected card brand: ' + state.cardBrand + '</div>'
: '';
document.getElementById('tokenize-button').disabled = false;
}
}
async function tokenizeCard() {
const resultDiv = document.getElementById('token-result');
const button = document.getElementById('tokenize-button');
if (!vault) {
resultDiv.innerHTML = '<div class="error">Initialize the card fields first</div>';
return;
}
button.disabled = true;
try {
// The card goes straight from the browser to the Vault. It
// never passes through this page.
const result = await vault.tokenize();
console.log('Payment token created:', result.token);
console.log('Card details:', result.card);
// The token can only be used once and expires 15 minutes
// after it is created, so hand it straight to whatever creates
// your orders - your own endpoint, or the Vrio API from here.
resultDiv.innerHTML =
'<div class="success">' +
'<strong>Payment Token Created!</strong><br>' +
'<strong>Token:</strong> <code>' + result.token + '</code><br>' +
'<strong>Card Brand:</strong> ' + (result.card.cardType || 'N/A') + '<br>' +
'<strong>Last 4:</strong> ' + (result.card.last4 || 'N/A') + '<br>' +
'<strong>Expiration:</strong> ' +
(result.card.expirationMonth || 'N/A') + '/' +
(result.card.expirationYear || 'N/A') + '<br>' +
'<small>Use this token as payment_token in your Vrio API call, ' +
'with payment_method_id set to 16</small>' +
'</div>';
} catch (error) {
// The customer left something blank or mistyped it.
console.error('Tokenization error:', error);
const messages = (error.errors || [error.message]).join('<br>');
resultDiv.innerHTML = '<div class="error"><strong>Tokenization failed:</strong><br>' + messages + '</div>';
}
button.disabled = false;
}
</script>
</body>
</html>Tokens are single use
A token is destroyed the moment it is processed, and expires on its own 15 minutes after it is created. Create one when the customer submits — not while they are still filling in the rest of your checkout.
If you write the token into a hidden form field, clear that field on page load. Browsers restore hidden inputs on reload and on back/forward navigation, and a spent token posted on a retry fails for a reason that has nothing to do with the original problem.
Processing the Token
The page above gets you a token. Sending it to Vrio is one call: pass it as payment_token with payment_method_id set to 16, and send no card fields at all.
curl -X POST "https://api.vrio.app/orders" \
-H "Content-Type: application/json" \
-H "X-API-KEY: YOUR_API_KEY" \
-d '{
"connection_id": 1,
"campaign_id": 18,
"offers": "[{\"offer_id\":89, \"order_offer_quantity\": 1,\"order_offer_price\":25.00}]",
"action": "process",
"payment_method_id": 16,
"payment_token": "tok_use2_aB3dEf7hJk9mNp2qRs4tUv6w",
"email": "[email protected]",
"bill_fname": "Jane",
"bill_lname": "Doe",
"bill_phone": "+1234567890",
"bill_address1": "123 Main St",
"bill_city": "Anytown",
"bill_state": "NY",
"bill_zipcode": "12345",
"bill_country": "US",
"ship_fname": "Jane",
"ship_lname": "Doe",
"ship_address1": "123 Main St",
"ship_city": "Anytown",
"ship_state": "NY",
"ship_zipcode": "12345",
"ship_country": "US"
}'The response is the standard order response — nothing about it indicates a token was involved:
{
"success": true,
"response_code": 100,
"transaction_id": 6501056,
"customer_id": 470177,
"order_id": 1114897,
"order": {
"order_id": 1114897,
"customer_card_id": 980231,
"customer_id": 470177
}
}Note the customer_card_id. The card is now stored against the order, and that — not the token — is the long-term handle on it. From here nothing about the order is token-specific: upsells, captures, refunds, subscription cycles and payment routing all work exactly as they do for any other order.
For the full walkthrough — multi-step checkouts, what the different token failures mean, and what to check when the fields don't render — see Integrating the Payments Vault.
Related Documentation
- Payments Vault Overview — What it is and when to use it
- Integrating the Payments Vault — The full walkthrough, step by step
- Styling the Card Fields — Make the fields match your checkout
- Upsell Processing — Bill additional offers against the order
- Auth & Capture — Authorize now, capture later
Updated about 1 hour ago
