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:

  1. Express Checkout Element (Recommended) - Modern approach using confirmation tokens
  2. 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:

  1. Apple Developer Account: Register your domain with Apple
  2. Domain Verification: Upload Apple's domain association file to your server
  3. SSL Certificate: Must use HTTPS in production (localhost works for testing)
  4. Stripe Configuration: Enable Apple Pay in your Stripe Dashboard

Google Pay

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

Setup Requirements:

  1. Google Pay API: Register with Google Pay and get merchant credentials
  2. Business Registration: Verify your business with Google
  3. SSL Certificate: Must use HTTPS in production (localhost works for testing)
  4. Stripe Configuration: Enable Google Pay in your Stripe Dashboard

Integration Approach Comparison

FeatureExpress Checkout ElementPayment Request API
Status✅ Current (Recommended)⚠️ Legacy (Still Supported)
Token TypeConfirmation TokensPayment Method Tokens
Setup ComplexityModerateSimple
Future SupportLong-termMay be deprecated
Additional FeaturesShipping, multiple walletsBasic functionality
When to UseProduction applicationsQuick 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.