I am implementing google pay and it is working fine if popup window
is allowed in the browser otherwise it redirect to new tab with error message
Unexpected developer error, please try again later.
When I check the console I am getting below error.
Uncaught (in promise) Error:
{"statusCode":"DEVELOPER_ERROR","errorCode":2,"statusMessage":"Popup
blocker detected, you must handle the clickEvent synchronously!"}
v //mss/boq-payments-consumer//js/k=boq-payments-consumer.InstantbuyFrontendBuyflowPayUi.en_US.BwS96tLC-ZI.es5.O/am=MAwACA/d=1/excm=IZT63,Juhsoe,LEikZe,PrPYRd,_b,_r,_tp,hc6Ubd,payview,ws9Tlc/ed=1/dg=0/wt=2/rs=AMitfrhsY7Wj0ReJi7QIlOlnB0W-J-o0TA/m=_b,_tp,_r,Juhsoe,LEikZe,PrPYRd,hc6Ubd,IZT63,ws9Tlc:693
pay:693:472
I am calling function onGooglePayLoaded()
on my laravel blade
file as below
eventBus.$on('after-payment-method-selected', function(payment) {
$('.paypal-buttons').remove();
document.getElementById("googlepay-button-container").innerHTML = "";
document.getElementById("payment-request-button").innerHTML = "";
$('#card-element').removeClass('StripeElement StripeElement--empty');
document.getElementById("card-element").innerHTML = "";
document.getElementById("stripe-payment-gateway").style.display = 'none';
if (payment.method == 'googlepay') {
setTimeout(function () {
const DivEle= document.querySelector(".checkout-payment");
DivEleHight = DivEle.offsetHeight;
DivEleHightAdd= DivEleHight+50;
document.querySelector(".checkout-stepper").style.height = DivEleHightAdd + "px";
}, 300);
onGooglePayLoaded()
}
and JS is as below which I found on developer.google
<script>
var cartResponseData;
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
};
/**
* Card networks supported by your site and your gateway
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
* @todo confirm card networks supported by your site and gateway
*/
const allowedCardNetworks = ["AMEX", "DISCOVER", "JCB", "MASTERCARD", "MIR", "VISA"];
/**
* Card authentication methods supported by your site and your gateway
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
* @todo confirm your processor supports Android device tokens for your
* supported card networks
*/
const allowedCardAuthMethods = ["PAN_ONLY", "CRYPTOGRAM_3DS"];
/**
* Identify your gateway and your site's gateway merchant identifier
*
* The Google Pay API response will return an encrypted payment method capable
* of being charged by a supported gateway after payer authorization
*
* @todo check with your gateway on the parameters to pass
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#gateway|PaymentMethodTokenizationSpecification}
*/
const tokenizationSpecification = {
type: 'PAYMENT_GATEWAY',
parameters: {
"gateway": "stripe",
"stripe:version": "2018-10-31",
"stripe:publishableKey": "{{ $clientId }}"
}
};
/**
* Describe your site's support for the CARD payment method and its required
* fields
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
*/
const baseCardPaymentMethod = {
type: 'CARD',
parameters: {
allowedAuthMethods: allowedCardAuthMethods,
allowedCardNetworks: allowedCardNetworks
}
};
/**
* Describe your site's support for the CARD payment method including optional
* fields
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
*/
const cardPaymentMethod = Object.assign(
{},
baseCardPaymentMethod,
{
tokenizationSpecification: tokenizationSpecification
}
);
/**
* An initialized google.payments.api.PaymentsClient object or null if not yet set
*
* @see {@link getGooglePaymentsClient}
*/
let paymentsClient = null;
/**
* Configure your site's support for payment methods supported by the Google Pay
* API.
*
* Each member of allowedPaymentMethods should contain only the required fields,
* allowing reuse of this base request when determining a viewer's ability
* to pay and later requesting a supported payment method
*
* @returns {object} Google Pay API version, payment methods supported by the site
*/
function getGoogleIsReadyToPayRequest() {
return Object.assign(
{},
baseRequest,
{
allowedPaymentMethods: [baseCardPaymentMethod]
}
);
}
/**
* Configure support for the Google Pay API
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest|PaymentDataRequest}
* @returns {object} PaymentDataRequest fields
*/
function getGooglePaymentDataRequest(cartData) {
const paymentDataRequest = Object.assign({}, baseRequest);
paymentDataRequest.allowedPaymentMethods = [cardPaymentMethod];
paymentDataRequest.transactionInfo = getGoogleTransactionInfo(cartData);
paymentDataRequest.merchantInfo = {
merchantName: 'Example Merchant'
};
paymentDataRequest.callbackIntents = ["PAYMENT_AUTHORIZATION"];
//paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"];
//paymentDataRequest.shippingAddressRequired = true;
//paymentDataRequest.shippingAddressParameters = getGoogleShippingAddressParameters();
//paymentDataRequest.shippingOptionRequired = true;
return paymentDataRequest;
}
/**
* Return an active PaymentsClient or initialize
*
* @see {@link https://developers.google.com/pay/api/web/reference/client#PaymentsClient|PaymentsClient constructor}
* @returns {google.payments.api.PaymentsClient} Google Pay API client
*/
function getGooglePaymentsClient() {
if ( paymentsClient === null ) {
paymentsClient = new google.payments.api.PaymentsClient({
environment: "{{ $cartDetail['sandbox'] ? 'TEST' : 'PRODUCTION' }}",
merchantInfo: {
merchantName: "{{ $cartDetail['title'] }}",
merchantId: "{{ $cartDetail['merchantid'] }}"
},
paymentDataCallbacks: {
onPaymentAuthorized: onPaymentAuthorized
//onPaymentDataChanged: onPaymentDataChanged
}
});
}
return paymentsClient;
}
function onPaymentAuthorized(paymentData) {
return new Promise(function(resolve, reject){
// handle the response
processPayment(paymentData)
.then(function() {
const collection2 = document.getElementsByClassName("paymentLoaderBody");
collection2[0].style.display = "inline-block";
resolve({transactionState: 'SUCCESS'});
})
.catch(function() {
resolve({
transactionState: 'ERROR',
error: {
intent: 'PAYMENT_AUTHORIZATION',
message: 'Insufficient funds',
reason: 'PAYMENT_DATA_INVALID'
}
});
});
});
}
/**
* Handles dynamic buy flow shipping address and shipping options callback intents.
*
* @param {object} itermediatePaymentData response from Google Pay API a shipping address or shipping option is selected in the payment sheet.
* @see {@link https://developers.google.com/pay/api/web/reference/response-objects#IntermediatePaymentData|IntermediatePaymentData object reference}
*
* @see {@link https://developers.google.com/pay/api/web/reference/response-objects#PaymentDataRequestUpdate|PaymentDataRequestUpdate}
* @returns Promise<{object}> Promise of PaymentDataRequestUpdate object to update the payment sheet.
*/
function onPaymentDataChanged(intermediatePaymentData) {
return new Promise(function(resolve, reject) {
let shippingAddress = intermediatePaymentData.shippingAddress;
let shippingOptionData = intermediatePaymentData.shippingOptionData;
let paymentDataRequestUpdate = {};
if (intermediatePaymentData.callbackTrigger == "INITIALIZE" || intermediatePaymentData.callbackTrigger == "SHIPPING_ADDRESS") {
if(shippingAddress.administrativeArea == "NJ") {
paymentDataRequestUpdate.error = getGoogleUnserviceableAddressError();
}
else {
paymentDataRequestUpdate.newShippingOptionParameters = getGoogleDefaultShippingOptions();
let selectedShippingOptionId = paymentDataRequestUpdate.newShippingOptionParameters.defaultSelectedOptionId;
paymentDataRequestUpdate.newTransactionInfo = calculateNewTransactionInfo(selectedShippingOptionId);
}
}
else if (intermediatePaymentData.callbackTrigger == "SHIPPING_OPTION") {
paymentDataRequestUpdate.newTransactionInfo = calculateNewTransactionInfo(shippingOptionData.id);
}
resolve(paymentDataRequestUpdate);
});
}
/**
* Helper function to create a new TransactionInfo object.
* @param string shippingOptionId respresenting the selected shipping option in the payment sheet.
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#TransactionInfo|TransactionInfo}
* @returns {object} transaction info, suitable for use as transactionInfo property of PaymentDataRequest
*/
function calculateNewTransactionInfo(shippingOptionId) {
let newTransactionInfo = getGoogleTransactionInfo();
let shippingCost = "{{isset($cartDetail['shipping']['price']) ? number_format($cartDetail['shipping']['price'], 2) : ''}}";
newTransactionInfo.displayItems.push({
type: "LINE_ITEM",
label: "Shipping Cost",
price: shippingCost,
status: "FINAL"
});
let totalPrice = 0.00;
newTransactionInfo.displayItems.forEach(displayItem => totalPrice += parseFloat(displayItem.price));
newTransactionInfo.totalPrice = totalPrice.toString();
return newTransactionInfo;
}
/**
* Initialize Google PaymentsClient after Google-hosted JavaScript has loaded
*
* Display a Google Pay payment button after confirmation of the viewer's
* ability to pay.
*/
function onGooglePayLoaded() {
const paymentsClient = getGooglePaymentsClient();
paymentsClient.isReadyToPay(getGoogleIsReadyToPayRequest())
.then(function(response) {
if (response.result) {
addGooglePayButton();
}
})
.catch(function(err) {
console.error(err);
});
}
/**
* Add a Google Pay purchase button alongside an existing checkout button
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#ButtonOptions|Button options}
* @see {@link https://developers.google.com/pay/api/web/guides/brand-guidelines|Google Pay brand guidelines}
*/
function addGooglePayButton() {
const paymentsClient = getGooglePaymentsClient();
const button =
paymentsClient.createButton({
onClick: onGooglePaymentButtonClicked,
allowedPaymentMethods: [baseCardPaymentMethod]
});
document.getElementById('googlepay-button-container').appendChild(button);
}
/**
* Provide Google Pay API with a payment amount, currency, and amount status
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#TransactionInfo|TransactionInfo}
* @returns {object} transaction info, suitable for use as transactionInfo property of PaymentDataRequest
*/
function getGoogleTransactionInfo(data) {
return {
displayItems: [
{
label: data.order_info.display_items.item_total.label,
type: data.order_info.display_items.item_total.type,
price: data.order_info.display_items.item_total.value,
},
{
label: data.order_info.display_items.tax_total.label,
type: data.order_info.display_items.tax_total.type,
price: data.order_info.display_items.tax_total.value,
},
{
label: data.order_info.display_items.shipping.label,
type: data.order_info.display_items.shipping.type,
price: data.order_info.display_items.shipping.value,
status: "FINAL",
},
],
countryCode: "{{$cartDetail['country']}}",
currencyCode: data.order_info.currency_code,
totalPriceStatus: "FINAL",
totalPrice: data.order_info.total,
totalPriceLabel: "Total"
};
}
/**
* Provide Google Pay API with shipping address parameters when using dynamic buy flow.
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#ShippingAddressParameters|ShippingAddressParameters}
* @returns {object} shipping address details, suitable for use as shippingAddressParameters property of PaymentDataRequest
*/
function getGoogleShippingAddressParameters() {
return {
allowedCountryCodes: ['US'],
phoneNumberRequired: true
};
}
/**
* Provide Google Pay API with shipping options and a default selected shipping option.
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#ShippingOptionParameters|ShippingOptionParameters}
* @returns {object} shipping option parameters, suitable for use as shippingOptionParameters property of PaymentDataRequest
*/
function getGoogleDefaultShippingOptions() {
return {
defaultSelectedOptionId: "shipping-001",
shippingOptions: [
{
"id": "shipping-001",
"label": "Free: Standard shipping",
"description": "Free Shipping delivered in 5 business days."
},
{
"id": "shipping-002",
"label": "$1.99: Standard shipping",
"description": "Standard shipping delivered in 3 business days."
},
{
"id": "shipping-003",
"label": "$10: Express shipping",
"description": "Express shipping delivered in 1 business day."
},
]
};
}
/**
* Provide Google Pay API with a payment data error.
*
* @see {@link https://developers.google.com/pay/api/web/reference/response-objects#PaymentDataError|PaymentDataError}
* @returns {object} payment data error, suitable for use as error property of PaymentDataRequestUpdate
*/
function getGoogleUnserviceableAddressError() {
return {
reason: "SHIPPING_ADDRESS_UNSERVICEABLE",
message: "Cannot ship to the selected address",
intent: "SHIPPING_ADDRESS"
};
}
function getShippingCosts() {
return {
"shipping-001": "0.00",
"shipping-002": "1.99",
"shipping-003": "10.00"
}
}
/**
* Prefetch payment data to improve performance
*
* @see {@link https://developers.google.com/pay/api/web/reference/client#prefetchPaymentData|prefetchPaymentData()}
*/
function prefetchGooglePaymentData() {
const paymentDataRequest = getGooglePaymentDataRequest();
// transactionInfo must be set but does not affect cache
paymentDataRequest.transactionInfo = {
totalPriceStatus: 'NOT_CURRENTLY_KNOWN',
currencyCode: 'USD'
};
const paymentsClient = getGooglePaymentsClient();
paymentsClient.prefetchPaymentData(paymentDataRequest);
}
/**
* Show Google Pay payment sheet when Google Pay payment button is clicked
*/
async function onGooglePaymentButtonClicked() {
var cartData = await getCartDetails();
const paymentDataRequest = getGooglePaymentDataRequest(cartData);
//paymentDataRequest.transactionInfo = getGoogleTransactionInfo(cartData);
const paymentsClient = getGooglePaymentsClient();
paymentsClient.loadPaymentData(paymentDataRequest);
}
function getCartDetails(){
return new Promise(function(resolve, reject) {
jQuery.ajax({
url: "{{ route('googlepay.smart-button.create-order') }}",
method: 'GET',
success: function(response){
resolve(response);
}
});
});
}
/**
* Process payment data returned by the Google Pay API
*
* @param {object} paymentData response from Google Pay API after user approves payment
* @see {@link https://developers.google.com/pay/api/web/reference/response-objects#PaymentData|PaymentData object reference}
*/
function processPayment(paymentData) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
//show returned data in developer console for debugging
paymentToken = paymentData.paymentMethodData.tokenizationData.token;
if(paymentToken != ''){
jQuery.ajax({
url: "{{ route('googlepay.standard.success') }}",
method: 'POST',
data: { data : paymentData,
_token: "{{ csrf_token() }}"},
success: function(result){
if(result.success){
window.location.href = result.url;
}
}});
}
resolve({});
}, 3000);
});
}
</script>
<script async src="https://pay.google.com/gp/p/js/pay.js"></script>
I am using vue js
with laravel
How can I fix that. I have searched a lot but didn’t find anything related to.
2
Answers
Ran into the same issue with my implementation. The issue is your
await getCartDetails();
inonGooglePaymentButtonClicked
; this function must be handled synchronously. I was attempting to submit my Blazor form and using a promise, continue to load Google Pay in the same function. Instead I’m now calling validation prior to displaying the Google Pay button to the end user.I think that without popup enabled Google Pay will not work in e.g. Safari where google can not open native payment sheet dialog but relies on popup window. But do not worry because Safari blocks popup by default, but does not block popup opened after user interaction e.g. click and popup open (call window.open()) within time < 1 second. Within that 1 second you can do anything – synchronous and asynchronous code (whatever callback, setTimeout, Promise.then(), async/await.
See my detailed answer in another question https://stackoverflow.com/a/77968696/3826175