woocommerce-paypal-payments/modules/ppcp-googlepay/resources/js/GooglepayButton.js

398 lines
14 KiB
JavaScript
Raw Normal View History

import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
2023-10-11 18:55:42 +01:00
import UpdatePaymentData from "./Helper/UpdatePaymentData";
2023-12-11 17:14:43 +00:00
import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/ApmButtons";
class GooglepayButton {
constructor(context, externalHandler, buttonConfig, ppcpConfig, contextHandler) {
apmButtonsInit(ppcpConfig);
2023-12-07 18:20:58 +00:00
this.isInitialized = false;
this.context = context;
2023-08-29 15:00:07 +01:00
this.externalHandler = externalHandler;
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.contextHandler = contextHandler;
this.paymentsClient = null;
2023-10-13 14:36:11 +01:00
this.log = function() {
if (this.buttonConfig.is_debug) {
2024-04-11 17:14:22 +01:00
//console.log('[GooglePayButton]', ...arguments);
}
}
}
init(config, transactionInfo) {
if (this.isInitialized) {
return;
}
this.isInitialized = true;
2023-08-31 17:38:23 +01:00
if (!this.validateConfig()) {
return;
}
if (!this.contextHandler.validateContext()) {
return;
}
this.googlePayConfig = config;
this.transactionInfo = transactionInfo;
this.allowedPaymentMethods = config.allowedPaymentMethods;
this.baseCardPaymentMethod = this.allowedPaymentMethods[0];
this.initClient();
this.initEventHandlers();
2023-08-29 14:57:10 +01:00
this.paymentsClient.isReadyToPay(
this.buildReadyToPayRequest(this.allowedPaymentMethods, config)
)
.then((response) => {
if (response.result) {
this.addButton(this.baseCardPaymentMethod);
}
})
.catch(function(err) {
console.error(err);
});
}
reinit() {
if (!this.googlePayConfig) {
return;
}
this.isInitialized = false;
this.init(this.googlePayConfig, this.transactionInfo);
}
2023-08-31 17:38:23 +01:00
validateConfig() {
if (['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) {
2023-08-31 17:38:23 +01:00
console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment);
return false;
}
if (!this.contextHandler) {
2023-08-31 17:38:23 +01:00
console.error('[GooglePayButton] Invalid context handler.', this.contextHandler);
return false;
}
return true;
}
/**
* Returns configurations relative to this button context.
*/
contextConfig() {
2023-09-07 16:49:22 +01:00
let config = {
wrapper: this.buttonConfig.button.wrapper,
ppcpStyle: this.ppcpConfig.button.style,
buttonStyle: this.buttonConfig.button.style,
ppcpButtonWrapper: this.ppcpConfig.button.wrapper
}
2023-09-07 16:49:22 +01:00
if (this.context === 'mini-cart') {
config.wrapper = this.buttonConfig.button.mini_cart_wrapper;
config.ppcpStyle = this.ppcpConfig.button.mini_cart_style;
config.buttonStyle = this.buttonConfig.button.mini_cart_style;
config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper;
2023-09-11 10:32:46 +01:00
// Handle incompatible types.
if (config.buttonStyle.type === 'buy') {
config.buttonStyle.type = 'pay';
}
2023-09-07 16:49:22 +01:00
}
if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) {
2023-11-23 08:46:48 +02:00
config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway-paypal';
2023-09-07 16:49:22 +01:00
}
return config;
}
initClient() {
2023-09-28 18:04:46 +01:00
const callbacks = {
onPaymentAuthorized: this.onPaymentAuthorized.bind(this)
}
if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) {
2023-09-28 18:04:46 +01:00
callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this);
}
this.paymentsClient = new google.payments.api.PaymentsClient({
2023-08-31 17:38:23 +01:00
environment: this.buttonConfig.environment,
// add merchant info maybe
2023-09-28 18:04:46 +01:00
paymentDataCallbacks: callbacks
});
}
initEventHandlers() {
const { wrapper, ppcpButtonWrapper } = this.contextConfig();
if (wrapper === ppcpButtonWrapper) {
throw new Error(`[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${wrapper}"`);
}
const syncButtonVisibility = () => {
const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper);
setVisible(wrapper, $ppcpButtonWrapper.is(':visible'));
setEnabled(wrapper, !$ppcpButtonWrapper.hasClass('ppcp-disabled'));
}
jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => {
if (jQuery(data.selector).is(ppcpButtonWrapper)) {
syncButtonVisibility();
}
});
syncButtonVisibility();
}
buildReadyToPayRequest(allowedPaymentMethods, baseRequest) {
return Object.assign({}, baseRequest, {
allowedPaymentMethods: allowedPaymentMethods,
});
}
/**
* Add a Google Pay purchase button
*/
addButton(baseCardPaymentMethod) {
this.log('addButton', this.context);
const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig();
2023-08-29 17:39:11 +01:00
this.waitForWrapper(wrapper, () => {
jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape);
if (ppcpStyle.height) {
jQuery(wrapper).css('height', `${ppcpStyle.height}px`)
}
const button =
this.paymentsClient.createButton({
onClick: this.onButtonClick.bind(this),
allowedPaymentMethods: [baseCardPaymentMethod],
buttonColor: buttonStyle.color || 'black',
buttonType: buttonStyle.type || 'pay',
buttonLocale: buttonStyle.language || 'en',
buttonSizeMode: 'fill',
});
jQuery(wrapper).append(button);
});
}
waitForWrapper(selector, callback, delay = 100, timeout = 2000) {
const startTime = Date.now();
2023-10-17 10:50:38 +01:00
const interval = setInterval(() => {
const el = document.querySelector(selector);
const timeElapsed = Date.now() - startTime;
if (el) {
clearInterval(interval);
callback(el);
} else if (timeElapsed > timeout) {
clearInterval(interval);
}
}, delay);
}
//------------------------
// Button click
//------------------------
/**
* Show Google Pay payment sheet when Google Pay payment button is clicked
*/
2024-07-15 10:58:03 +02:00
onButtonClick() {
this.log('onButtonClick', this.context);
const paymentDataRequest = this.paymentDataRequest();
this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context);
2023-08-29 14:57:10 +01:00
2023-09-11 10:11:40 +01:00
window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler.
this.paymentsClient.loadPaymentData(paymentDataRequest);
}
paymentDataRequest() {
let baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
const googlePayConfig = this.googlePayConfig;
const paymentDataRequest = Object.assign({}, baseRequest);
paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods;
paymentDataRequest.transactionInfo = this.transactionInfo;
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
2023-09-28 09:32:09 +01:00
if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) {
paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"];
2023-09-28 18:04:46 +01:00
paymentDataRequest.shippingAddressRequired = true;
2023-09-29 18:10:06 +01:00
paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters();
2023-09-28 18:04:46 +01:00
paymentDataRequest.shippingOptionRequired = true;
} else {
paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION'];
}
2023-09-28 09:32:09 +01:00
return paymentDataRequest;
}
2023-09-29 18:10:06 +01:00
//------------------------
// Shipping processing
//------------------------
shippingAddressParameters() {
2023-09-28 09:32:09 +01:00
return {
2023-09-29 18:10:06 +01:00
allowedCountryCodes: this.buttonConfig.shipping.countries,
2023-09-28 09:32:09 +01:00
phoneNumberRequired: true
};
}
2023-09-28 09:32:09 +01:00
onPaymentDataChanged(paymentData) {
this.log('onPaymentDataChanged', this.context);
this.log('paymentData', paymentData);
2023-09-29 18:10:06 +01:00
return new Promise(async (resolve, reject) => {
try {
let paymentDataRequestUpdate = {};
2023-09-29 18:10:06 +01:00
const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData);
const transactionInfo = this.transactionInfo;
2023-10-13 10:04:17 +01:00
this.log('onPaymentDataChanged:updatedData', updatedData);
this.log('onPaymentDataChanged:transactionInfo', transactionInfo);
2023-10-13 14:36:11 +01:00
updatedData.country_code = transactionInfo.countryCode;
updatedData.currency_code = transactionInfo.currencyCode;
updatedData.total_str = transactionInfo.totalPrice;
2023-09-29 18:10:06 +01:00
// Handle unserviceable address.
if (!(updatedData.shipping_options?.shippingOptions?.length)) {
paymentDataRequestUpdate.error = this.unserviceableShippingAddressError();
resolve(paymentDataRequestUpdate);
return;
}
2023-09-29 18:10:06 +01:00
switch (paymentData.callbackTrigger) {
case 'INITIALIZE':
case 'SHIPPING_ADDRESS':
paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options;
paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData);
break;
case 'SHIPPING_OPTION':
paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData);
break;
}
2023-09-29 18:10:06 +01:00
resolve(paymentDataRequestUpdate);
} catch(error) {
console.error('Error during onPaymentDataChanged:', error);
reject(error);
}
2023-09-29 18:10:06 +01:00
});
2023-09-28 09:32:09 +01:00
}
2023-09-29 18:10:06 +01:00
unserviceableShippingAddressError() {
return {
reason: "SHIPPING_ADDRESS_UNSERVICEABLE",
message: "Cannot ship to the selected address",
intent: "SHIPPING_ADDRESS"
};
}
2023-10-11 18:55:42 +01:00
calculateNewTransactionInfo(updatedData) {
2023-09-29 18:10:06 +01:00
return {
2023-10-11 18:55:42 +01:00
countryCode: updatedData.country_code,
currencyCode: updatedData.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: updatedData.total_str
2023-09-29 18:10:06 +01:00
};
}
2023-10-11 18:55:42 +01:00
2023-09-29 18:10:06 +01:00
//------------------------
// Payment process
//------------------------
onPaymentAuthorized(paymentData) {
this.log('onPaymentAuthorized', this.context);
2023-08-29 14:57:10 +01:00
return this.processPayment(paymentData);
}
async processPayment(paymentData) {
this.log('processPayment', this.context);
return new Promise(async (resolve, reject) => {
try {
let id = await this.contextHandler.createOrder();
this.log('processPayment: createOrder', id, this.context);
const confirmOrderResponse = await widgetBuilder.paypal.Googlepay().confirmOrder({
orderId: id,
paymentMethodData: paymentData.paymentMethodData
});
this.log('processPayment: confirmOrder', confirmOrderResponse, this.context);
2023-08-29 14:57:10 +01:00
/** Capture the Order on the Server */
if (confirmOrderResponse.status === "APPROVED") {
let approveFailed = false;
await this.contextHandler.approveOrder({
orderID: id
}, { // actions mock object.
restart: () => new Promise((resolve, reject) => {
approveFailed = true;
resolve();
}),
order: {
get: () => new Promise((resolve, reject) => {
resolve(null);
})
}
});
2023-08-29 14:57:10 +01:00
if (!approveFailed) {
resolve(this.processPaymentResponse('SUCCESS'));
} else {
resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'FAILED TO APPROVE'));
}
} else {
2023-08-29 14:57:10 +01:00
resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'TRANSACTION FAILED'));
}
} catch(err) {
2023-08-29 14:57:10 +01:00
resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', err.message));
}
});
}
2023-08-29 14:57:10 +01:00
processPaymentResponse(state, intent = null, message = null) {
let response = {
transactionState: state,
}
if (intent || message) {
response.error = {
intent: intent,
message: message,
}
}
this.log('processPaymentResponse', response, this.context);
2023-08-29 14:57:10 +01:00
return response;
}
}
export default GooglepayButton;