Setting Up Google Pay for Virtual Gift Card Generator: Solving the Issue of onGooglePayLoaded Undefined

Solving the Issue of onGooglePayLoaded Undefined

While working on the development of a virtual gift card generator for my application, I wanted to integrate Google Pay as a payment method. The goal was to allow users to pay for virtual gift cards directly on the website. However, during the setup process, I encountered an issue: the Google Pay button was not being displayed on the frontend, and I received the following error in the browser console:

Uncaught ReferenceError: onGooglePayLoaded is not defined
at HTMLScriptElement.onload (index.html:15:38)

In this article, I will walk through how I identified and solved this issue while setting up Google Pay for my application.

The Problem

The error message indicated that the browser could not find the onGooglePayLoaded function, which is essential for initializing the Google Pay client and adding the payment button to the page. The error occurred because the onGooglePayLoaded function was not available in the scope when the Google Pay API script was loaded, causing the function to be undefined when the onload event triggered.

The Google Pay Setup

Initially, I had the following Google Pay setup in my index.html and client.js files:

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<h1>Test Website</h1>
<!-- Google Pay Script -->
<script async
    src="https://pay.google.com/gp/p/js/pay.js"
    onload="onGooglePayLoaded()">
</script>

<!-- Custom JS for Google Pay setup -->
<script src="client.js"></script>
</body>
</html>

client.js:

const tokenizationSpecification = {
    type: 'PAYMENT_GATEWAY',
    parameters: {
        gateway: 'example',
        gatewayMerchantId: 'gatewayMerchantId',
    }
};

const cardPaymentMethod = {
    type: 'CARD',
    tokenizationSpecification: tokenizationSpecification,
    parameters: {
        allowedCardNetworks: ['VISA', 'MASTERCARD'],
        allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
    }
};

const googlePayConfiguration = {
    apiVersion: 2,
    apiVersionMinor: 0,
    allowedPaymentMethods: [cardPaymentMethod]
};

let googlePayClient;

function onGooglePayLoaded() {
    googlePayClient = new google.payments.api.PaymentsClient({
        environment: 'TEST',
    });

    googlePayClient.isReadyToPay(googlePayConfiguration)
        .then(response => {
            if (response.result) {
                createAndAddButton();
            }
        })
        .catch(error => console.error('isReadyToPay error: ', error));
}

function createAndAddButton() {
    const googlePayButton = googlePayClient.createButton({
        onClick: onGooglePayButtonClicked,
    });

    document.getElementById('buy-now').appendChild(googlePayButton);
}

function onGooglePayButtonClicked() {
    const paymentDataRequest = { ...googlePayConfiguration };
    paymentDataRequest.merchantInfo = {
        merchantId: 'BCR2DN4T767ILP3Z',
        merchantName: 'Pepco Virtual Gift Cards',
    };

    paymentDataRequest.transactionInfo = {
        totalPriceStatus: 'FINAL',
        totalPrice: '100.00',
        currencyCode: 'RSD',
        countryCode: 'RS',
    };

    googlePayClient.loadPaymentData(paymentDataRequest)
        .then(paymentData => processPaymentData(paymentData))
        .catch(error => console.error('loadPaymentData error:', error));
}

function processPaymentData(paymentData) {
    fetch('/process-payment', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(paymentData)
    })
    .then(response => response.json())
    .then(data => console.log('Payment successful:', data))
    .catch(error => console.error('Error processing payment:', error));
}

The Root Cause

The issue stemmed from the fact that the onGooglePayLoaded function was defined in client.js, but the script was not loaded in the correct order. Specifically, the Google Pay script (pay.js) was being loaded asynchronously in the HTML file with an inline onload event:

<script async
    src="https://pay.google.com/gp/p/js/pay.js"
    onload="onGooglePayLoaded()">
</script>

However, the onGooglePayLoaded function was defined in client.js, and there was no guarantee that client.js would be fully loaded before the Google Pay script. This caused the browser to fail to find onGooglePayLoaded, as the script was not executed at the right time.

The Solution

The solution was to load the Google Pay script programmatically and attach the onGooglePayLoaded function to the script’s onload event. This ensures that the function is executed only after the Google Pay script has been fully loaded.

Here’s the corrected implementation:

Updated client.js:

// Wait for the DOM to load before defining the function
document.addEventListener('DOMContentLoaded', function () {
    const googlePayScript = document.createElement('script');
    googlePayScript.src = 'https://pay.google.com/gp/p/js/pay.js';
    googlePayScript.async = true;

    // Once the script is loaded, initialize Google Pay
    googlePayScript.onload = onGooglePayLoaded;
    document.body.appendChild(googlePayScript);
});

const tokenizationSpecification = {
    type: 'PAYMENT_GATEWAY',
    parameters: {
        gateway: 'example',
        gatewayMerchantId: 'gatewayMerchantId',
    }
};

const cardPaymentMethod = {
    type: 'CARD',
    tokenizationSpecification: tokenizationSpecification,
    parameters: {
        allowedCardNetworks: ['VISA', 'MASTERCARD'],
        allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
    }
};

const googlePayConfiguration = {
    apiVersion: 2,
    apiVersionMinor: 0,
    allowedPaymentMethods: [cardPaymentMethod]
};

let googlePayClient;

function onGooglePayLoaded() {
    googlePayClient = new google.payments.api.PaymentsClient({
        environment: 'TEST',
    });

    googlePayClient.isReadyToPay(googlePayConfiguration)
        .then(response => {
            if (response.result) {
                createAndAddButton();
            } else {
                console.log('Google Pay is not supported on this device or browser.');
            }
        })
        .catch(error => console.error('isReadyToPay error: ', error));
}

function createAndAddButton() {
    const googlePayButton = googlePayClient.createButton({
        onClick: onGooglePayButtonClicked,
    });

    document.getElementById('buy-now').appendChild(googlePayButton);
}

function onGooglePayButtonClicked() {
    const selectedItem = { price: '100.00' };

    const paymentDataRequest = { ...googlePayConfiguration };
    paymentDataRequest.merchantInfo = {
        merchantId: 'BCR2DN4T767ILP3Z',
        merchantName: 'Pepco Virtual Gift Cards',
    };

    paymentDataRequest.transactionInfo = {
        totalPriceStatus: 'FINAL',
        totalPrice: selectedItem.price,
        currencyCode: 'RSD',
        countryCode: 'RS',
    };

    googlePayClient.loadPaymentData(paymentDataRequest)
        .then(paymentData => processPaymentData(paymentData))
        .catch(error => console.error('loadPaymentData error:', error));
}

function processPaymentData(paymentData) {
    const orderEndpointUrl = '/process-payment'; 

    fetch(orderEndpointUrl, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(paymentData)
    })
    .then(response => response.json())
    .then(data => console.log('Payment successful:', data))
    .catch(error => console.error('Error processing payment:', error));
}

Conclusion

By dynamically loading the Google Pay script and ensuring that the onGooglePayLoaded function was properly linked to the script’s onload event, the issue was resolved. The Google Pay button now appears correctly on the page, allowing users to make payments through Google Pay. This solution helped me successfully integrate Google Pay into my virtual gift card generator application, which is a key feature of the platform.

Scroll to Top