Google / Apple Pay
Google Pay & Apple Pay Integration with Vrio API using Stripe
Overview
This guide explains how to implement Apple Pay and Google Pay using Stripe.js and process payments through Vrio's API. Both payment methods provide secure, convenient checkout experiences for customers on supported devices.
Two Integration Approaches Available:
- Express Checkout Element (Recommended) - Modern approach using confirmation tokens
- Payment Request API - Legacy approach, simpler for basic implementations
Supported Payment Methods & Prerequisites
Apple Pay
Supported Devices: iPhone, iPad, Mac with Touch ID/Face ID, Apple Watch
Setup Requirements:
- Apple Developer Account: Register your domain with Apple
- Domain Verification: Upload Apple's domain association file to your server
- SSL Certificate: Must use HTTPS in production (localhost works for testing)
- Stripe Configuration: Enable Apple Pay in your Stripe Dashboard
Google Pay
Supported Devices: Android devices, Chrome browser (desktop and mobile)
Setup Requirements:
- Google Pay API: Register with Google Pay and get merchant credentials
- Business Registration: Verify your business with Google
- SSL Certificate: Must use HTTPS in production (localhost works for testing)
- Stripe Configuration: Enable Google Pay in your Stripe Dashboard
Integration Approach Comparison
Feature | Express Checkout Element | Payment Request API |
---|---|---|
Status | ✅ Current (Recommended) | ⚠️ Legacy (Still Supported) |
Token Type | Confirmation Tokens | Payment Method Tokens |
Setup Complexity | Moderate | Simple |
Future Support | Long-term | May be deprecated |
Additional Features | Shipping, multiple wallets | Basic functionality |
When to Use | Production applications | Quick prototypes, simple use cases |
Implementation Examples
Option 1: Express Checkout Element (Recommended)
This is the modern approach using Stripe's Express Checkout Element with confirmation tokens.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Apple Pay & Google Pay - Express Checkout</title>
<script src="https://js.stripe.com/v3/"></script>
<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;
}
#express-checkout-element {
margin-top: 20px;
margin-bottom: 20px;
}
.section {
border: 1px solid #ddd;
padding: 20px;
margin-bottom: 20px;
border-radius: 4px;
}
.section h3 {
margin-top: 0;
}
</style>
</head>
<body>
<h1>Express Checkout Integration</h1>
<div class="info">
<strong>Testing Requirements:</strong>
<ul>
<li><strong>Apple Pay:</strong> Safari on iOS/macOS with cards in Apple Wallet</li>
<li><strong>Google Pay:</strong> Chrome browser with Google Pay configured</li>
<li><strong>Both:</strong> HTTPS required (localhost works for testing)</li>
</ul>
</div>
<div class="section">
<h3>Configuration</h3>
<div class="form-group">
<label for="publishable-key">Stripe Publishable Key:</label>
<input type="text" id="publishable-key" placeholder="pk_test_..." required>
</div>
<div class="form-group">
<label for="amount">Amount (in cents):</label>
<input type="number" id="amount" value="50" min="50" required>
</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>
<button onclick="initializeExpressCheckout()">Initialize Express Checkout</button>
<div id="init-status"></div>
</div>
<div class="section">
<h3>Express Checkout</h3>
<div id="express-checkout-element">
<!-- Express Checkout buttons will appear here -->
</div>
<div id="button-status"></div>
</div>
<div class="section">
<h3>Payment Results</h3>
<div id="payment-result"></div>
</div>
<script>
let stripe;
let elements;
let expressCheckoutElement;
function initializeExpressCheckout() {
const publishableKey = document.getElementById('publishable-key').value.trim();
const amount = parseInt(document.getElementById('amount').value);
const currency = document.getElementById('currency').value;
const statusDiv = document.getElementById('init-status');
const buttonStatusDiv = document.getElementById('button-status');
if (!publishableKey) {
statusDiv.innerHTML = '<div class="error">Please enter a Stripe publishable key</div>';
return;
}
if (!amount || amount < 50) {
statusDiv.innerHTML = '<div class="error">Amount must be at least 50 cents</div>';
return;
}
try {
// Initialize Stripe
stripe = Stripe(publishableKey);
// Create elements instance in payment mode for confirmation tokens
elements = stripe.elements({
mode: 'payment',
amount: amount,
currency: currency,
setupFutureUsage: 'off_session',
captureMethod: 'manual',
payment_method_types: ['card'],
});
// Create Express Checkout Element
expressCheckoutElement = elements.create('expressCheckout', {
buttonType: {
applePay: 'buy',
googlePay: 'buy',
},
buttonHeight: 48,
buttonTheme: {
applePay: 'black',
googlePay: 'black',
},
paymentMethods: {
applePay: 'auto',
googlePay: 'auto',
paypal: 'never',
amazonPay: 'never',
klarna: 'never',
link: 'never',
},
});
// Mount the Express Checkout Element
expressCheckoutElement.mount('#express-checkout-element');
// Check for available payment methods
expressCheckoutElement.on('ready', function(event) {
console.log('Express Checkout ready:', event);
statusDiv.innerHTML = '<div class="success">Express Checkout initialized successfully</div>';
const availablePaymentMethods = event.availablePaymentMethods || {};
let availablePayments = [];
if (availablePaymentMethods.applePay) availablePayments.push('Apple Pay');
if (availablePaymentMethods.googlePay) availablePayments.push('Google Pay');
if (availablePayments.length > 0) {
buttonStatusDiv.innerHTML = `<div class="success">✅ ${availablePayments.join(' and ')} available</div>`;
} else {
buttonStatusDiv.innerHTML = '<div class="info">No express payment methods available on this device/browser</div>';
}
});
// Handle the confirm event with createConfirmationToken
expressCheckoutElement.on('confirm', async function(event) {
console.log('Express Checkout confirm event:', event);
try {
// First submit the elements to validate
const submit = await elements.submit();
if (submit && !submit.error) {
// Create confirmation token
const confirmation = await stripe.createConfirmationToken({
elements: elements
});
if (confirmation && !confirmation.error) {
const confirmationToken = confirmation.confirmationToken;
console.log('Confirmation Token created:', confirmationToken);
// Get the method used
let method = 'unknown';
let methodId = null;
if (confirmationToken.payment_method_preview?.card?.wallet?.type === 'apple_pay') {
method = 'Apple Pay';
methodId = 4;
} else if (confirmationToken.payment_method_preview?.card?.wallet?.type === 'google_pay') {
method = 'Google Pay';
methodId = 3;
}
// Display the confirmation token information
const resultDiv = document.getElementById('payment-result');
resultDiv.innerHTML = `
<div class="success">
<strong>Confirmation Token Created!</strong><br>
<strong>Token ID:</strong> <code>${confirmationToken.id}</code><br>
<strong>Payment Method:</strong> ${method} (ID: ${methodId})<br>
<strong>Card Brand:</strong> ${confirmationToken.payment_method_preview?.card?.brand || 'N/A'}<br>
<strong>Last 4:</strong> ${confirmationToken.payment_method_preview?.card?.last4 || 'N/A'}<br>
<strong>Email:</strong> ${confirmationToken.payment_method_preview?.billing_details?.email || 'N/A'}<br>
<strong>Name:</strong> ${confirmationToken.shipping?.name || 'N/A'}<br>
<small>Use this confirmation token ID as payment_token in your Vrio API call</small>
</div>
`;
} else {
console.error('Confirmation token creation error:', confirmation.error);
const resultDiv = document.getElementById('payment-result');
resultDiv.innerHTML = `<div class="error">Confirmation token creation failed: ${confirmation.error.message}</div>`;
}
} else {
console.error('Elements submit error:', submit.error);
const resultDiv = document.getElementById('payment-result');
resultDiv.innerHTML = `<div class="error">Elements submit failed: ${submit.error.message}</div>`;
}
} catch (err) {
console.error('Unexpected error:', err);
const resultDiv = document.getElementById('payment-result');
resultDiv.innerHTML = `<div class="error">Unexpected error: ${err.message}</div>`;
}
});
// Handle click event
expressCheckoutElement.on('click', function(event) {
console.log('Express Checkout click event:', event);
event.resolve();
});
console.log('Express Checkout initialized');
} catch (error) {
statusDiv.innerHTML = '<div class="error">Error initializing Stripe: ' + error.message + '</div>';
console.error('Stripe initialization error:', error);
}
}
</script>
</body>
</html>
Option 2: Payment Request API (Legacy)
This is the original approach using the Payment Request API for simpler implementations.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Apple Pay & Google Pay - Payment Request API</title>
<script src="https://js.stripe.com/v3/"></script>
<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;
}
#payment-request-button {
margin-top: 20px;
margin-bottom: 20px;
}
.section {
border: 1px solid #ddd;
padding: 20px;
margin-bottom: 20px;
border-radius: 4px;
}
.section h3 {
margin-top: 0;
}
</style>
</head>
<body>
<h1>Payment Request API Integration</h1>
<div class="info">
<strong>Testing Requirements:</strong>
<ul>
<li><strong>Apple Pay:</strong> Safari on iOS/macOS with cards in Apple Wallet</li>
<li><strong>Google Pay:</strong> Chrome browser with Google Pay configured</li>
<li><strong>Both:</strong> HTTPS required (localhost works for testing)</li>
<li><strong>Note:</strong> Legacy approach - consider Express Checkout Element for new implementations</li>
</ul>
</div>
<div class="section">
<h3>Configuration</h3>
<div class="form-group">
<label for="publishable-key">Stripe Publishable Key:</label>
<input type="text" id="publishable-key" placeholder="pk_test_..." required>
</div>
<div class="form-group">
<label for="amount">Amount (in cents):</label>
<input type="number" id="amount" value="50" min="50" required>
</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>
<button onclick="initializePaymentRequest()">Initialize Payment Buttons</button>
<div id="init-status"></div>
</div>
<div class="section">
<h3>Payment Buttons</h3>
<div id="payment-request-button">
<!-- Apple Pay or Google Pay button will appear here -->
</div>
<div id="button-status"></div>
</div>
<div class="section">
<h3>Payment Results</h3>
<div id="payment-result"></div>
</div>
<script>
let stripe;
let paymentRequest;
let prButton;
function initializePaymentRequest() {
const publishableKey = document.getElementById('publishable-key').value.trim();
const amount = parseInt(document.getElementById('amount').value);
const currency = document.getElementById('currency').value;
const statusDiv = document.getElementById('init-status');
const buttonStatusDiv = document.getElementById('button-status');
if (!publishableKey) {
statusDiv.innerHTML = '<div class="error">Please enter a Stripe publishable key</div>';
return;
}
if (!amount || amount < 50) {
statusDiv.innerHTML = '<div class="error">Amount must be at least 50 cents</div>';
return;
}
try {
// Initialize Stripe
stripe = Stripe(publishableKey);
// Create payment request
paymentRequest = stripe.paymentRequest({
country: 'US',
currency: currency,
total: {
label: 'Demo Purchase',
amount: amount,
},
requestPayerName: true,
requestPayerEmail: true,
});
// Create elements
const elements = stripe.elements();
prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
style: {
paymentRequestButton: {
type: 'default',
theme: 'dark',
height: '48px',
},
},
});
// Check if Payment Request is available
paymentRequest.canMakePayment().then(function(result) {
if (result) {
prButton.mount('#payment-request-button');
statusDiv.innerHTML = '<div class="success">Payment buttons initialized successfully</div>';
let availablePayments = [];
if (result.applePay) availablePayments.push('Apple Pay');
if (result.googlePay) availablePayments.push('Google Pay');
buttonStatusDiv.innerHTML = `<div class="success">✅ ${availablePayments.join(' and ')} available</div>`;
} else {
statusDiv.innerHTML = '<div class="error">Apple Pay and Google Pay are not available</div>';
buttonStatusDiv.innerHTML = '<div class="info">Payment buttons not available on this device/browser</div>';
}
});
// Handle payment method creation
paymentRequest.on('paymentmethod', function(ev) {
console.log('Payment Method received:', ev.paymentMethod);
// Use the payment method ID directly as Vrio supports pm_ tokens
const paymentToken = ev.paymentMethod.id;
console.log('Payment Method Token for Vrio:', paymentToken);
// Display the payment method information
const resultDiv = document.getElementById('payment-result');
resultDiv.innerHTML = `
<div class="success">
<strong>Payment Method Created!</strong><br>
<strong>Payment Method ID:</strong> <code>${paymentToken}</code><br>
<strong>Type:</strong> ${ev.paymentMethod.type}<br>
<strong>Brand:</strong> ${ev.paymentMethod.card?.brand || 'N/A'}<br>
<strong>Last 4:</strong> ${ev.paymentMethod.card?.last4 || 'N/A'}<br>
<strong>Email:</strong> ${ev.paymentMethod.billing_details?.email || 'N/A'}<br>
<strong>Name:</strong> ${ev.paymentMethod.billing_details?.name || 'N/A'}<br>
<small>Use this payment method ID as payment_token in your Vrio API call</small>
</div>
`;
// Complete the payment (simulate success)
ev.complete('success');
// Here you would send ev.paymentMethod.id to your server for Vrio processing
console.log('Payment Method ID for Vrio:', paymentToken);
});
console.log('Payment Request initialized:', paymentRequest);
} catch (error) {
statusDiv.innerHTML = '<div class="error">Error initializing Stripe: ' + error.message + '</div>';
console.error('Stripe initialization error:', error);
}
}
</script>
</body>
</html>
Sending Tokens to Vrio API
Once you have obtained a payment token from Apple Pay or Google Pay, you can process the payment through Vrio's API. The token type depends on your integration approach:
- Express Checkout Element: Creates confirmation tokens (format:
ctoken_xxxxx
) - Payment Request API: Creates payment method tokens (format:
pm_xxxxx
)
Both token types are supported by Vrio's API.
Step 1: Configure Your Merchant Account
Ensure your Stripe merchant account is properly configured in your Vrio dashboard with the appropriate API keys 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 your Stripe payment token:
Apple Pay Payment Processing
Express Checkout Element (Confirmation Token):
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": "ctoken_1Nxxxxxxxxxxxxxx",
"merchant_id": 456
}'
Payment Request API (Payment Method Token):
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": "pm_1Nxxxxxxxxxxxxxx",
"merchant_id": 456
}'
Google Pay Payment Processing
Express Checkout Element (Confirmation Token):
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": "ctoken_1Nxxxxxxxxxxxxxx",
"merchant_id": 456
}'
Payment Request API (Payment Method Token):
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": "pm_1Nxxxxxxxxxxxxxx",
"merchant_id": 456
}'
Note: Be sure to pass the merchant_id
associated with the payment_token if there are multiple Stripe merchants available on a router.
Troubleshooting
Common Issues
"Payment buttons not available on this device/browser"
- Ensure you're using HTTPS (required for both Apple Pay and Google Pay)
- 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 your Stripe account has Apple Pay/Google Pay enabled
Apple Pay Not Appearing
- Use Safari on macOS or iOS device
- Ensure Touch ID, Face ID, or Apple Watch is set up
- Add test cards to Apple Wallet
- Verify domain is registered with Apple (for production)
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
- Check for corporate/managed browser restrictions
"Invalid token id" Error
- Verify the token was created successfully before sending to Vrio
- Check that your Stripe keys match the token's mode (test vs live)
- Ensure the token belongs to the correct merchant in Vrio
Token Creation Fails
- Verify your Stripe publishable key is correct
- Check browser console for Stripe.js errors
- Ensure payment request configuration is minimal (avoid unnecessary fields)
- Confirm user completed the payment flow before token creation
For additional support, consult the Stripe Testing Guide and Stripe.js Reference.
Updated 21 days ago