Add GooglePay context handler classes

This commit is contained in:
Pedro Silva 2023-08-28 17:19:07 +01:00
parent 6a205d1413
commit 4a1d369ad2
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
11 changed files with 494 additions and 159 deletions

View file

@ -2,6 +2,7 @@ import {useEffect, useState} from '@wordpress/element';
import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
import {paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address";
import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
const config = wc.wcSettings.getSetting('ppcp-gateway_data');
@ -28,6 +29,7 @@ const PayPalComponent = ({
if (!loaded) {
loadPaypalScript(config.scriptData, () => {
setLoaded(true);
buttonModuleWatcher.registerContextBootstrap(config.scriptData.context, this);
});
}
}, [loaded]);

View file

@ -19,6 +19,7 @@ import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler";
import FormSaver from './modules/Helper/FormSaver';
import FormValidator from "./modules/Helper/FormValidator";
import {loadPaypalScript} from "./modules/Helper/ScriptLoading";
import buttonModuleWatcher from "./modules/ButtonModuleWatcher";
// TODO: could be a good idea to have a separate spinner for each gateway,
// but I think we care mainly about the script loading, so one spinner should be enough.
@ -60,6 +61,11 @@ const bootstrap = () => {
}
});
const hasMessages = () => {
return PayPalCommerceGateway.messages.is_hidden === false
&& document.querySelector(PayPalCommerceGateway.messages.wrapper);
}
const onSmartButtonClick = async (data, actions) => {
window.ppcpFundingSource = data.fundingSource;
const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input');
@ -146,6 +152,7 @@ const bootstrap = () => {
);
miniCartBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('mini-cart', miniCartBootstrap);
}
if (
@ -163,6 +170,7 @@ const bootstrap = () => {
);
singleProductBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('product', singleProductBootstrap);
}
if (context === 'cart') {
@ -174,6 +182,7 @@ const bootstrap = () => {
);
cartBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('cart', cartBootstrap);
}
if (context === 'checkout') {
@ -186,6 +195,7 @@ const bootstrap = () => {
);
checkoutBootstap.init();
buttonModuleWatcher.registerContextBootstrap('checkout', checkoutBootstap);
}
if (context === 'pay-now' ) {
@ -197,15 +207,11 @@ const bootstrap = () => {
errorHandler,
);
payNowBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('pay-now', payNowBootstrap);
}
};
const hasMessages = () => {
return PayPalCommerceGateway.messages.is_hidden === false
&& document.querySelector(PayPalCommerceGateway.messages.wrapper);
}
document.addEventListener(
'DOMContentLoaded',
() => {

View file

@ -0,0 +1,31 @@
class ButtonModuleWatcher {
constructor() {
this.contextBootstrapRegistry = {};
this.contextBootstrapWatchers = [];
}
watchContextBootstrap(callable) {
this.contextBootstrapWatchers.push(callable);
Object.values(this.contextBootstrapRegistry).forEach(callable);
}
registerContextBootstrap(context, handler) {
this.contextBootstrapRegistry[context] = {
context: context,
handler: handler
}
// Call registered watchers
for (const callable of this.contextBootstrapWatchers) {
callable(this.contextBootstrapRegistry[context]);
}
}
}
window.ppcpResources = window.ppcpResources || {};
const buttonModuleWatcher = window.ppcpResources['ButtonModuleWatcher'] = window.ppcpResources['ButtonModuleWatcher'] || new ButtonModuleWatcher();
export default buttonModuleWatcher;

View file

@ -0,0 +1,75 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import CartActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
import onApprove
from "../../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue";
class CartHandler {
constructor(buttonConfig, ppcpConfig) {
console.log('NEW CartHandler');
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
transactionInfo() {
return new Promise((resolve, reject) => {
fetch(
this.ppcpConfig.ajax.cart_script_params.endpoint,
{
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload
const data = result.data;
resolve({
countryCode: 'US', // data.country_code,
currencyCode: 'USD', // data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.amount // Your amount
});
});
});
}
createOrder() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
const actionHandler = new CartActionHandler(
this.ppcpConfig,
errorHandler,
);
return actionHandler.configuration().createOrder(null, null);
}
approveOrderForContinue(data, actions) {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
let onApproveHandler = onApprove({
config: this.ppcpConfig
}, errorHandler);
return onApproveHandler(data, actions);
}
}
export default CartHandler;

View file

@ -0,0 +1,75 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import CartActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
import onApprove
from "../../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue";
class CartHandler {
constructor(buttonConfig, ppcpConfig) {
console.log('NEW CartHandler');
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
transactionInfo() {
return new Promise((resolve, reject) => {
fetch(
this.ppcpConfig.ajax.cart_script_params.endpoint,
{
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload
const data = result.data;
resolve({
countryCode: 'US', // data.country_code,
currencyCode: 'USD', // data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.amount // Your amount
});
});
});
}
createOrder() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
const actionHandler = new CartActionHandler(
this.ppcpConfig,
errorHandler,
);
return actionHandler.configuration().createOrder(null, null);
}
approveOrderForContinue(data, actions) {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
let onApproveHandler = onApprove({
config: this.ppcpConfig
}, errorHandler);
return onApproveHandler(data, actions);
}
}
export default CartHandler;

View file

@ -0,0 +1,75 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import CartActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
import onApprove
from "../../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue";
class CartHandler {
constructor(buttonConfig, ppcpConfig) {
console.log('NEW CartHandler');
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
transactionInfo() {
return new Promise((resolve, reject) => {
fetch(
this.ppcpConfig.ajax.cart_script_params.endpoint,
{
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload
const data = result.data;
resolve({
countryCode: 'US', // data.country_code,
currencyCode: 'USD', // data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.amount // Your amount
});
});
});
}
createOrder() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
const actionHandler = new CartActionHandler(
this.ppcpConfig,
errorHandler,
);
return actionHandler.configuration().createOrder(null, null);
}
approveOrderForContinue(data, actions) {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
let onApproveHandler = onApprove({
config: this.ppcpConfig
}, errorHandler);
return onApproveHandler(data, actions);
}
}
export default CartHandler;

View file

@ -0,0 +1,79 @@
import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner";
import CheckoutActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import onApprove
from "../../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue";
class CheckoutHandler {
constructor(buttonConfig, ppcpConfig) {
console.log('NEW CheckoutHandler');
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
transactionInfo() {
return new Promise((resolve, reject) => {
fetch(
this.ppcpConfig.ajax.cart_script_params.endpoint,
{
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload
const data = result.data;
resolve({
countryCode: 'US', // data.country_code,
currencyCode: 'USD', // data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.amount // Your amount
});
});
});
}
createOrder() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
const spinner = new Spinner();
const actionHandler = new CheckoutActionHandler(
this.ppcpConfig,
errorHandler,
spinner
);
return actionHandler.configuration().createOrder(null, null);
}
approveOrderForContinue(data, actions) {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
let onApproveHandler = onApprove({
config: this.ppcpConfig
}, errorHandler);
return onApproveHandler(data, actions);
}
}
export default CheckoutHandler;

View file

@ -0,0 +1,31 @@
import SingleProductHandler from "./SingleProductHandler";
import CartHandler from "./CartHandler";
import CheckoutHandler from "./CheckoutHandler";
import CartBlockHandler from "./CartBlockHandler";
import CheckoutBlockHandler from "./CheckoutBlockHandler";
class ContextHandlerFactory {
static create(context, buttonConfig, ppcpConfig) {
switch (context) {
case 'product':
return new SingleProductHandler(buttonConfig, ppcpConfig);
case 'cart':
return new CartHandler(buttonConfig, ppcpConfig);
case 'checkout':
return new CheckoutHandler(buttonConfig, ppcpConfig);
case 'pay-now':
// todo
return null;
case 'mini-cart':
// todo
return null;
case 'cart-block':
return new CartBlockHandler(buttonConfig, ppcpConfig);
case 'checkout-block':
return new CheckoutBlockHandler(buttonConfig, ppcpConfig);
}
}
}
export default ContextHandlerFactory;

View file

@ -0,0 +1,93 @@
import SingleProductActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler";
import SimulateCart from "../../../../ppcp-button/resources/js/modules/Helper/SimulateCart";
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import UpdateCart from "../../../../ppcp-button/resources/js/modules/Helper/UpdateCart";
import onApprove
from "../../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue";
class SingleProductHandler {
constructor(buttonConfig, ppcpConfig) {
console.log('NEW SingleProductHandler');
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
transactionInfo() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
function form() {
return document.querySelector('form.cart');
}
const actionHandler = new SingleProductActionHandler(
null,
null,
form(),
errorHandler,
);
const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
const products = hasSubscriptions
? actionHandler.getSubscriptionProducts()
: actionHandler.getProducts();
return new Promise((resolve, reject) => {
(new SimulateCart(
this.ppcpConfig.ajax.simulate_cart.endpoint,
this.ppcpConfig.ajax.simulate_cart.nonce,
)).simulate((data) => {
resolve({
countryCode: data.country_code,
currencyCode: data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.total_str // Your amount
});
}, products);
});
}
createOrder() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
const actionHandler = new SingleProductActionHandler(
this.ppcpConfig,
new UpdateCart(
this.ppcpConfig.ajax.change_cart.endpoint,
this.ppcpConfig.ajax.change_cart.nonce,
),
document.querySelector('form.cart'),
errorHandler,
);
return actionHandler.configuration().createOrder();
}
approveOrderForContinue(data, actions) {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
let onApproveHandler = onApprove({
config: this.ppcpConfig
}, errorHandler);
return onApproveHandler(data, actions);
}
}
export default SingleProductHandler;

View file

@ -1,12 +1,5 @@
import SingleProductActionHandler from '../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler';
import CartActionHandler from '../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler';
import UpdateCart from '../../../ppcp-button/resources/js/modules/Helper/UpdateCart';
import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
import SimulateCart from "../../../ppcp-button/resources/js/modules/Helper/SimulateCart";
import onApprove from '../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue';
import CheckoutActionHandler
from "../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
import Spinner from "../../../ppcp-button/resources/js/modules/Helper/Spinner";
import ContextHandlerFactory from "./Context/ContextHandlerFactory";
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
class GooglepayManager {
@ -21,6 +14,20 @@ class GooglepayManager {
this.isReadyToPayRequest = null;
this.baseCardPaymentMethod = null;
// Wait for buttons context handler to be loaded
this.contextHandler = null;
buttonModuleWatcher.watchContextBootstrap((bootstrap) => {
if (bootstrap.context === 'mini-cart') {
return;
}
this.contextHandler = ContextHandlerFactory.create(
bootstrap.context,
buttonConfig,
ppcpConfig
);
});
}
init() {
@ -151,80 +158,7 @@ class GooglepayManager {
}
async transactionInfo() {
//-------------
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
//== cart & checkout page ==
return new Promise((resolve, reject) => {
fetch(
this.ppcpConfig.ajax.cart_script_params.endpoint,
{
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload
const data = result.data;
resolve({
countryCode: 'US', // data.country_code,
currencyCode: 'USD', // data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.amount // Your amount
});
});
});
// == product page ==
// function form() {
// return document.querySelector('form.cart');
// }
//
// const actionHandler = new SingleProductActionHandler(
// null,
// null,
// form(),
// errorHandler,
// );
//
// const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions
// && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
//
// const products = hasSubscriptions
// ? actionHandler.getSubscriptionProducts()
// : actionHandler.getProducts();
//
// return new Promise((resolve, reject) => {
// (new SimulateCart(
// this.ppcpConfig.ajax.simulate_cart.endpoint,
// this.ppcpConfig.ajax.simulate_cart.nonce,
// )).simulate((data) => {
//
// resolve({
// countryCode: data.country_code,
// currencyCode: data.currency_code,
// totalPriceStatus: 'FINAL',
// totalPrice: data.total_str // Your amount
// });
//
// }, products);
// });
//-------------
return this.contextHandler.transactionInfo();
}
//------------------------
@ -252,56 +186,11 @@ class GooglepayManager {
return new Promise(async (resolve, reject) => {
try {
//-------------
console.log('ppcpConfig:', this.ppcpConfig);
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
// == checkout page ==
// const spinner = new Spinner();
//
// const actionHandler = new CheckoutActionHandler(
// this.ppcpConfig,
// errorHandler,
// spinner
// );
//
// let id = await actionHandler.configuration().createOrder(null, null);
// == cart page ==
const actionHandler = new CartActionHandler(
this.ppcpConfig,
errorHandler,
);
let id = await actionHandler.configuration().createOrder(null, null);
// == product page ==
// function form() {
// return document.querySelector('form.cart');
// }
// const actionHandler = new SingleProductActionHandler(
// this.ppcpConfig,
// new UpdateCart(
// this.ppcpConfig.ajax.change_cart.endpoint,
// this.ppcpConfig.ajax.change_cart.nonce,
// ),
// form(),
// errorHandler,
// );
//
// const createOrderInPayPal = actionHandler.createOrder();
//
// let id = await createOrderInPayPal(null, null);
let id = await this.contextHandler.createOrder();
console.log('PayPal Order ID:', id);
//-------------
console.log('paypal.Googlepay().confirmOrder : paymentData', {
orderId: id,
paymentMethodData: paymentData.paymentMethodData
@ -315,20 +204,10 @@ class GooglepayManager {
/** Capture the Order on your Server */
if (confirmOrderResponse.status === "APPROVED") {
console.log('onApprove', this.ppcpConfig);
let approveFailed = false;
const approveOrderAndContinue = onApprove({
config: this.ppcpConfig
}, errorHandler);
console.log('approveOrderAndContinue', {
orderID: id
});
await approveOrderAndContinue({
await this.contextHandler.approveOrderForContinue({
orderID: id
}, {
restart: () => new Promise((resolve, reject) => {
@ -351,17 +230,6 @@ class GooglepayManager {
resolve({transactionState: 'SUCCESS'});
// if (response.capture.status === "COMPLETED")
// resolve({transactionState: 'SUCCESS'});
// else
// resolve({
// transactionState: 'ERROR',
// error: {
// intent: 'PAYMENT_AUTHORIZATION',
// message: 'TRANSACTION FAILED',
// }
// })
} else {
resolve({
transactionState: 'ERROR',

View file

@ -26,11 +26,11 @@ const GooglePayComponent = () => {
const [paypalLoaded, setPaypalLoaded] = useState(false);
const [googlePayLoaded, setGooglePayLoaded] = useState(false);
const manager = new GooglepayManager(buttonConfig, ppcpConfig);
const bootstrap = function () {
const manager = new GooglepayManager(buttonConfig, ppcpConfig);
manager.init();
};
useEffect(() => {
@ -43,7 +43,7 @@ const GooglePayComponent = () => {
loadPaypalScript(ppcpConfig, () => {
setPaypalLoaded(true);
});
});
}, []);
useEffect(() => {
if (!bootstrapped && paypalLoaded && googlePayLoaded) {