Apple Pay / Google Pay

Apple Pay and Google Pay digital wallet integration through NMI's Collect.js providing secure, convenient mobile payment options with tokenization for Vrio API processing.

Overview

This guide explains how to implement Apple Pay and Google Pay using NMI's Collect.js and process payments through Vrio's API. Collect.js handles wallet detection, button rendering, and token creation. On submit, it returns a single-use payment_token that you pass to Vrio's API.

Integration Approach:

NMI's Collect.js renders Apple Pay and Google Pay buttons automatically when available on the customer's device and browser. The callback returns a payment_token that replaces credit card fields (ccnumber, ccexp, cvv) in the transaction request.

Important: The payment token returned by Collect.js is single-use and expires after 24 hours if not used. It cannot be reused for recurring or subsequent transactions. For renewals, Vrio will use the Customer Vault ID (if vault is enabled) or the previous transaction ID to process the charge.

Supported Payment Methods & Prerequisites

Apple Pay

Supported Devices: iPhone, iPad, Mac with Touch ID/Face ID, Apple Watch

Setup Requirements:

  1. Domain Verification: Upload Apple's domain association file to /.well-known/apple-developer-merchantid-domain-association on your checkout domain
  2. SSL Certificate: Must use HTTPS (localhost does NOT work - must be a real domain)
  3. NMI Configuration: Apple Pay must be enabled in your NMI merchant account
  4. Tokenization Key: A public tokenization key from NMI (found in your NMI gateway settings)

Google Pay

Supported Devices: Android devices, Chrome browser (desktop and mobile)

Setup Requirements:

  1. SSL Certificate: Must use HTTPS (localhost does NOT work - must be a real domain)
  2. NMI Configuration: Google Pay must be enabled in your NMI merchant account
  3. Tokenization Key: A public tokenization key from NMI (found in your NMI gateway settings)

NMI Account Setup

Before implementing the frontend, configure the following in your NMI merchant account:

  1. Log in to your NMI gateway at secure.nmi.com
  2. Navigate to Settings > Security Keys to find or create your Tokenization Key (public key used by Collect.js)
  3. Enable Apple Pay and/or Google Pay in your NMI account settings
  4. For Apple Pay, download the domain association file from NMI and upload it to your checkout domain at /.well-known/apple-developer-merchantid-domain-association

Implementation Example

You will need the following values from your NMI account:

ValueWhere to Find
Tokenization KeySettings > Security Keys > Tokenization Key
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NMI - Apple Pay & Google Pay</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;
        }
        #applepaybutton, #googlepaybutton {
            min-height: 50px;
        }
        pre {
            background: #f4f4f4;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 12px;
            font-size: 13px;
            overflow-x: auto;
            white-space: pre-wrap;
            word-break: break-all;
        }
    </style>
</head>
<body>
    <h1>NMI Digital Wallet Test</h1>

    <div class="info">
        <strong>Testing Requirements:</strong>
        <ul>
            <li><strong>Apple Pay:</strong> Safari on iOS/macOS with cards in Apple Wallet + domain verification file uploaded</li>
            <li><strong>Google Pay:</strong> Chrome browser with Google Pay configured</li>
            <li><strong>Both:</strong> HTTPS required (localhost does NOT work — must be a real domain)</li>
        </ul>
    </div>

    <div class="section">
        <h3>Configuration</h3>
        <div class="form-group">
            <label for="tokenization-key">Tokenization Key (Public API Key):</label>
            <input type="text" id="tokenization-key" placeholder="YOUR_PUBLIC_KEY_HERE">
        </div>
        <div class="form-group">
            <label for="price">Price:</label>
            <input type="number" id="price" value="1.00" min="0.01" step="0.01">
        </div>
        <div class="form-group">
            <label for="currency">Currency:</label>
            <select id="currency">
                <option value="USD">USD</option>
                <option value="EUR">EUR</option>
                <option value="GBP">GBP</option>
                <option value="CAD">CAD</option>
            </select>
        </div>
        <div class="form-group">
            <label for="country">Country:</label>
            <select id="country">
                <option value="US">US</option>
                <option value="CA">CA</option>
                <option value="GB">GB</option>
            </select>
        </div>
        <button onclick="initialize()">Initialize Collect.js</button>
        <div id="init-status"></div>
    </div>

    <div class="section">
        <h3>Apple Pay</h3>
        <div id="applepaybutton"></div>
        <div id="applepay-status"></div>
    </div>

    <div class="section">
        <h3>Google Pay</h3>
        <div id="googlepaybutton"></div>
        <div id="googlepay-status"></div>
    </div>

    <div class="section">
        <h3>Payment Token Response</h3>
        <div id="payment-result"><em>No token yet.</em></div>
    </div>

    <script>
        function initialize() {
            const tokenizationKey = document.getElementById('tokenization-key').value.trim();
            const price           = document.getElementById('price').value;
            const currency        = document.getElementById('currency').value;
            const country         = document.getElementById('country').value;
            const statusDiv       = document.getElementById('init-status');

            if (!tokenizationKey) {
                statusDiv.innerHTML = '<div class="error">Please enter your tokenization key.</div>';
                return;
            }

            // Remove any previously loaded Collect.js instance
            const existing = document.getElementById('collectjs');
            if (existing) existing.remove();

            // Clear button containers so Collect.js re-renders them
            document.getElementById('applepaybutton').innerHTML = '';
            document.getElementById('googlepaybutton').innerHTML = '';
            document.getElementById('payment-result').innerHTML = '<em>No token yet.</em>';

            statusDiv.innerHTML = '<div class="info">Loading Collect.js...</div>';

            const script = document.createElement('script');
            script.id  = 'collectjs';
            script.src = 'https://secure.nmi.com/token/Collect.js';

            // Required attributes
            script.setAttribute('data-tokenization-key', tokenizationKey);
            script.setAttribute('data-variant', 'inline');
            script.setAttribute('data-price', price);
            script.setAttribute('data-currency', currency);
            script.setAttribute('data-country', country);

            // Apple Pay button selector
            script.setAttribute('data-field-apple-pay-selector', '#applepaybutton');

            // Apple Pay: capture billing + shipping name and address
            script.setAttribute('data-field-apple-pay-required-billing-contact-fields', '["postalAddress","name"]');
            script.setAttribute('data-field-apple-pay-required-shipping-contact-fields', '["postalAddress","name"]');
            script.setAttribute('data-field-apple-pay-contact-fields', '["phone","email"]');
            script.setAttribute('data-field-apple-pay-contact-fields-mapped-to', 'billing');

            // Apple Pay button style
            script.setAttribute('data-field-apple-pay-type', 'buy');
            script.setAttribute('data-field-apple-pay-style-height', '48px');
            script.setAttribute('data-field-apple-pay-style-border-radius', '4px');
            script.setAttribute('data-field-apple-pay-total-label', 'Total');

            // Google Pay button selector + billing/shipping
            script.setAttribute('data-field-google-pay-selector', '#googlepaybutton');
            script.setAttribute('data-field-google-pay-billing-address-required', 'true');
            script.setAttribute('data-field-google-pay-billing-address-parameters-format', 'FULL');
            script.setAttribute('data-field-google-pay-billing-address-parameters-phone-number-required', 'true');
            script.setAttribute('data-field-google-pay-shipping-address-required', 'true');
            script.setAttribute('data-field-google-pay-shipping-address-parameters-phone-number-required', 'true');
            script.setAttribute('data-field-google-pay-shipping-address-parameters-allowed-country-codes', 'US,CA,GB');

            script.onload = function () {
                statusDiv.innerHTML = '<div class="success">Collect.js loaded. Waiting for digital wallet buttons...</div>';

                CollectJS.configure({
                    callback: function (token) {
                        console.log('Received token:', token);
                        const tokenType = token.tokenType || 'unknown';
                        const method    = tokenType === 'applePay' ? 'Apple Pay' : tokenType === 'googlePay' ? 'Google Pay' : tokenType;

                        document.getElementById('payment-result').innerHTML = `
                            <div class="success">
                                <strong>Token received via ${method}</strong><br>
                                <strong>Token:</strong> <code>${token.token}</code><br>
                                <strong>Type:</strong> ${token.tokenType}<br>
                                <strong>Card network:</strong> ${token.wallet?.cardNetwork || token.card?.type || 'N/A'}<br>
                                <strong>Last 4:</strong> ${token.wallet?.cardDetails || 'N/A'}<br>
                                <strong>Email:</strong> ${token.wallet?.email || 'N/A'}<br>
                            </div>
                            <pre>${JSON.stringify(token, null, 2)}</pre>
                        `;
                    }
                });
            };

            script.onerror = function () {
                statusDiv.innerHTML = '<div class="error">Failed to load Collect.js. Check your tokenization key and that you are on HTTPS.</div>';
            };

            document.head.appendChild(script);
        }
    </script>
</body>
</html>

Note: The example above includes both Apple Pay and Google Pay buttons. You can include only the payment method you need by removing the corresponding data-field-apple-pay-* or data-field-google-pay-* attributes.

Collect.js Configuration Reference

Required Attributes

AttributeDescription
data-tokenization-keyYour NMI public tokenization key
data-variantSet to inline for inline button rendering
data-priceTransaction amount (e.g., 1.00)
data-currencyCurrency code (e.g., USD)
data-countryCountry code (e.g., US)

Apple Pay Attributes

AttributeDescription
data-field-apple-pay-selectorCSS selector for the Apple Pay button container
data-field-apple-pay-required-billing-contact-fieldsBilling fields to collect (e.g., ["postalAddress","name"])
data-field-apple-pay-required-shipping-contact-fieldsShipping fields to collect (e.g., ["postalAddress","name"])
data-field-apple-pay-contact-fieldsAdditional contact fields (e.g., ["phone","email"])
data-field-apple-pay-contact-fields-mapped-toMap contact fields to billing or shipping
data-field-apple-pay-typeButton type: buy, pay, plain, etc.
data-field-apple-pay-style-heightButton height (e.g., 48px)
data-field-apple-pay-style-border-radiusButton border radius (e.g., 4px)
data-field-apple-pay-total-labelLabel shown on the Apple Pay sheet (e.g., Total)

Google Pay Attributes

AttributeDescription
data-field-google-pay-selectorCSS selector for the Google Pay button container
data-field-google-pay-billing-address-requiredSet to true to require billing address
data-field-google-pay-billing-address-parameters-formatAddress format: FULL or MIN
data-field-google-pay-billing-address-parameters-phone-number-requiredSet to true to require phone number
data-field-google-pay-shipping-address-requiredSet to true to require shipping address
data-field-google-pay-shipping-address-parameters-phone-number-requiredSet to true to require shipping phone
data-field-google-pay-shipping-address-parameters-allowed-country-codesComma-separated allowed country codes

Sending Tokens to Vrio API

Once you have obtained a payment token from Apple Pay or Google Pay via Collect.js, you can process the payment through Vrio's API. The callback function provides the token in token.token.

Step 1: Configure Your Merchant Account

Ensure your NMI merchant account is properly configured in your Vrio dashboard with the API Username, API Password, and has Apple Pay/Google Pay as available payment methods.

Step 2: Create an Order

First, create an order using Vrio's order creation endpoint (NOTE: you can also pass the payment_token in Step 1 if you are using the process action when creating the order):

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\":0.50}]",
    "email": "[email protected]",
    "bill_fname": "John",
    "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": "John",
    "ship_lname": "Doe",
    "ship_address1": "123 Main St",
    "ship_city": "Anytown",
    "ship_state": "NY",
    "ship_zipcode": "12345",
    "ship_country": "US"
  }'

This will return an order object with an order_id.

Step 3: Process Payment with Token

Use the order processing endpoint with the wallet token from Collect.js:

Apple Pay Payment Processing

curl -X POST "https://api.vrio.app/orders/{order_id}/process" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -d '{
    "payment_method_id": 4,
    "payment_token": "COLLECT_JS_TOKEN",
    "merchant_id": 456
  }'

Google Pay Payment Processing

curl -X POST "https://api.vrio.app/orders/{order_id}/process" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -d '{
    "payment_method_id": 3,
    "payment_token": "COLLECT_JS_TOKEN",
    "merchant_id": 456
  }'

Note: Be sure to pass the merchant_id associated with the payment_token if there are multiple NMI merchants available on a router.

Recurring Transactions

The Collect.js payment_token is single-use and cannot be reused for recurring charges. For subsequent renewal transactions, Vrio handles this automatically using the following priority:

  1. Customer Vault ID - If Customer Vault is enabled on the merchant, the initial Apple Pay / Google Pay transaction will store the card in NMI's vault. All subsequent renewals will use the vault token.
  2. Transaction ID - If Customer Vault is not enabled, Vrio will use the transactionid from the previous successful transaction to process the renewal.

Recommendation: If your NMI account supports Customer Vault, enable it on your merchant in Vrio for the most reliable recurring transaction processing with digital wallets.

Troubleshooting

Apple Pay Domain Verification

Apple Pay requires a domain verification file hosted at /.well-known/apple-developer-merchantid-domain-association on your checkout domain. If Apple Pay buttons are not appearing:

  • Verify the file is accessible at https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association
  • Ensure the file returns a 200 status (not a redirect or error)
  • Check that your web server is not blocking access to the .well-known directory
  • The file must be served over HTTPS
  • Confirm the file content matches what NMI provided

Common Issues

"Apple Pay / Google Pay buttons not appearing"

  • Ensure you're using HTTPS on a real domain (localhost does NOT work for NMI Collect.js)
  • Verify you're using a supported browser (Safari for Apple Pay, Chrome for Google Pay)
  • Check that payment methods are added to your wallet (Apple Wallet or Google Pay)
  • Confirm Apple Pay / Google Pay are enabled in your NMI account
  • Verify your tokenization key is correct

Apple Pay Not Appearing

  • Use Safari on macOS or iOS device
  • Ensure Touch ID, Face ID, or Apple Watch is set up
  • Add cards to Apple Wallet
  • Verify domain association file is uploaded and accessible
  • Check that the domain is registered with Apple through NMI

Google Pay Not Appearing

  • Use Chrome browser (desktop or Android)
  • Sign in to your Google account in Chrome
  • Add payment methods in Chrome settings (chrome://settings/payments)
  • Enable "Pay with Google Pay" in Chrome settings

"Failed to load Collect.js"

  • Verify your tokenization key is correct
  • Ensure the page is served over HTTPS
  • Check browser console for additional error details

Token is empty or undefined

  • Verify the callback function is receiving the token object
  • Check the browser console for the full token response
  • Ensure the customer completed the wallet authentication (Touch ID, Face ID, etc.)

For additional support, consult the NMI Integration Portal.