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.com and example.com are 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


Did this page help you?