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:
- Domain Verification: Upload Apple's domain association file to
/.well-known/apple-developer-merchantid-domain-associationon your checkout domain - SSL Certificate: Must use HTTPS (localhost does NOT work - must be a real domain)
- NMI Configuration: Apple Pay must be enabled in your NMI merchant account
- 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:
- SSL Certificate: Must use HTTPS (localhost does NOT work - must be a real domain)
- NMI Configuration: Google Pay must be enabled in your NMI merchant account
- 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:
- Log in to your NMI gateway at secure.nmi.com
- Navigate to Settings > Security Keys to find or create your Tokenization Key (public key used by Collect.js)
- Enable Apple Pay and/or Google Pay in your NMI account settings
- 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:
| Value | Where to Find |
|---|---|
| Tokenization Key | Settings > 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-*ordata-field-google-pay-*attributes.
Collect.js Configuration Reference
Required Attributes
| Attribute | Description |
|---|---|
data-tokenization-key | Your NMI public tokenization key |
data-variant | Set to inline for inline button rendering |
data-price | Transaction amount (e.g., 1.00) |
data-currency | Currency code (e.g., USD) |
data-country | Country code (e.g., US) |
Apple Pay Attributes
| Attribute | Description |
|---|---|
data-field-apple-pay-selector | CSS selector for the Apple Pay button container |
data-field-apple-pay-required-billing-contact-fields | Billing fields to collect (e.g., ["postalAddress","name"]) |
data-field-apple-pay-required-shipping-contact-fields | Shipping fields to collect (e.g., ["postalAddress","name"]) |
data-field-apple-pay-contact-fields | Additional contact fields (e.g., ["phone","email"]) |
data-field-apple-pay-contact-fields-mapped-to | Map contact fields to billing or shipping |
data-field-apple-pay-type | Button type: buy, pay, plain, etc. |
data-field-apple-pay-style-height | Button height (e.g., 48px) |
data-field-apple-pay-style-border-radius | Button border radius (e.g., 4px) |
data-field-apple-pay-total-label | Label shown on the Apple Pay sheet (e.g., Total) |
Google Pay Attributes
| Attribute | Description |
|---|---|
data-field-google-pay-selector | CSS selector for the Google Pay button container |
data-field-google-pay-billing-address-required | Set to true to require billing address |
data-field-google-pay-billing-address-parameters-format | Address format: FULL or MIN |
data-field-google-pay-billing-address-parameters-phone-number-required | Set to true to require phone number |
data-field-google-pay-shipping-address-required | Set to true to require shipping address |
data-field-google-pay-shipping-address-parameters-phone-number-required | Set to true to require shipping phone |
data-field-google-pay-shipping-address-parameters-allowed-country-codes | Comma-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:
- 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.
- Transaction ID - If Customer Vault is not enabled, Vrio will use the
transactionidfrom 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-knowndirectory - 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
callbackfunction 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.
Updated about 4 hours ago
