Run autofix code style

This commit is contained in:
Emili Castells Guasch 2024-07-23 14:36:22 +02:00
commit cf8eaba0a3
173 changed files with 14902 additions and 11906 deletions

View file

@ -25,6 +25,10 @@ if (!defined('ABSPATH')) {
define('ABSPATH', ''); define('ABSPATH', '');
} }
if (!defined('PPCP_PAYPAL_BN_CODE')) {
define('PPCP_PAYPAL_BN_CODE', 'Woo_PPCP');
}
/** /**
* Cancel the next occurrence of a scheduled action. * Cancel the next occurrence of a scheduled action.
* *

View file

@ -1,5 +1,23 @@
*** Changelog *** *** Changelog ***
= 2.8.2 - 2024-07-22 =
* Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415
* Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400
* Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403
* Fix - Shipping methods during callback not updated correctly #2421
* Fix - Preserve subscription renewal processing when switching Subscriptions Mode or disabling gateway #2394
* Fix - Remove shipping callback for Venmo express button #2374
* Fix - Google Pay: Fix issuse with data.paymentSource being undefined #2390
* Fix - Loading of non-Order as a WC_Order causes warnings and potential data corruption #2343
* Fix - Apple Pay and Google Pay buttons don't appear in PayPal Button stack on multi-step Checkout #2372
* Fix - Apple Pay: Fix when shipping is disabled #2391
* Fix - Wrong string in smart button preview on Standard Payments tab #2409
* Fix - Don't break orders screen when there is an exception for package tracking #2369
* Fix - Pay Later button preview is missing #2371
* Fix - Apple Pay button layout #2367
* Enhancement - Remove BCDC button from block Express Checkout area #2381
* Enhancement - Extend Advanced Card Processing country eligibility for China #2397
= 2.8.1 - 2024-07-01 = = 2.8.1 - 2024-07-01 =
* Fix - Don't render tracking metabox if PayPal order does not belong to connected merchant #2360 * Fix - Don't render tracking metabox if PayPal order does not belong to connected merchant #2360
* Fix - Fatal error when the ppcp-paylater-configurator module is disabled via code snippet #2327 * Fix - Fatal error when the ppcp-paylater-configurator module is disabled via code snippet #2327

View file

@ -723,6 +723,30 @@ return array(
'TWD', 'TWD',
'USD', 'USD',
), ),
'CN' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CY' => array( 'CY' => array(
'AUD', 'AUD',
'BRL', 'BRL',
@ -1416,6 +1440,10 @@ return array(
'visa' => array(), 'visa' => array(),
'amex' => array(), 'amex' => array(),
), ),
'CN' => array(
'mastercard' => array(),
'visa' => array(),
),
'CY' => array( 'CY' => array(
'mastercard' => array(), 'mastercard' => array(),
'visa' => array(), 'visa' => array(),

View file

@ -42,7 +42,7 @@ trait RequestTrait {
*/ */
$args = apply_filters( 'ppcp_request_args', $args, $url ); $args = apply_filters( 'ppcp_request_args', $args, $url );
if ( ! isset( $args['headers']['PayPal-Partner-Attribution-Id'] ) ) { if ( ! isset( $args['headers']['PayPal-Partner-Attribution-Id'] ) ) {
$args['headers']['PayPal-Partner-Attribution-Id'] = 'Woo_PPCP'; $args['headers']['PayPal-Partner-Attribution-Id'] = PPCP_PAYPAL_BN_CODE;
} }
$response = wp_remote_get( $url, $args ); $response = wp_remote_get( $url, $args );

File diff suppressed because it is too large Load diff

View file

@ -1,55 +1,52 @@
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
import ApplepayButton from "./ApplepayButton"; import ApplepayButton from './ApplepayButton';
class ApplepayManager { class ApplepayManager {
constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.ApplePayConfig = null;
this.buttons = [];
constructor(buttonConfig, ppcpConfig) { buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => {
const button = new ApplepayButton(
bootstrap.context,
bootstrap.handler,
buttonConfig,
ppcpConfig
);
this.buttonConfig = buttonConfig; this.buttons.push( button );
this.ppcpConfig = ppcpConfig;
this.ApplePayConfig = null;
this.buttons = [];
buttonModuleWatcher.watchContextBootstrap((bootstrap) => { if ( this.ApplePayConfig ) {
const button = new ApplepayButton( button.init( this.ApplePayConfig );
bootstrap.context, }
bootstrap.handler, } );
buttonConfig, }
ppcpConfig,
);
this.buttons.push(button); init() {
( async () => {
await this.config();
for ( const button of this.buttons ) {
button.init( this.ApplePayConfig );
}
} )();
}
if (this.ApplePayConfig) { reinit() {
button.init(this.ApplePayConfig); for ( const button of this.buttons ) {
} button.reinit();
}); }
} }
init() {
(async () => {
await this.config();
for (const button of this.buttons) {
button.init(this.ApplePayConfig);
}
})();
}
reinit() {
for (const button of this.buttons) {
button.reinit();
}
}
/**
* Gets ApplePay configuration of the PayPal merchant.
* @returns {Promise<null>}
*/
async config() {
this.ApplePayConfig = await paypal.Applepay().config();
return this.ApplePayConfig;
}
/**
* Gets ApplePay configuration of the PayPal merchant.
* @return {Promise<null>}
*/
async config() {
this.ApplePayConfig = await paypal.Applepay().config();
return this.ApplePayConfig;
}
} }
export default ApplepayManager; export default ApplepayManager;

View file

@ -1,36 +1,34 @@
import ApplepayButton from "./ApplepayButton"; import ApplepayButton from './ApplepayButton';
class ApplepayManagerBlockEditor { class ApplepayManagerBlockEditor {
constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.applePayConfig = null;
}
constructor(buttonConfig, ppcpConfig) { init() {
this.buttonConfig = buttonConfig; ( async () => {
this.ppcpConfig = ppcpConfig; await this.config();
this.applePayConfig = null; } )();
} }
init() { async config() {
(async () => { try {
await this.config(); this.applePayConfig = await paypal.Applepay().config();
})();
}
async config() { const button = new ApplepayButton(
try { this.ppcpConfig.context,
this.applePayConfig = await paypal.Applepay().config(); null,
this.buttonConfig,
this.ppcpConfig
);
const button = new ApplepayButton( button.init( this.applePayConfig );
this.ppcpConfig.context, } catch ( error ) {
null, console.error( 'Failed to initialize Apple Pay:', error );
this.buttonConfig, }
this.ppcpConfig, }
);
button.init(this.applePayConfig);
} catch (error) {
console.error('Failed to initialize Apple Pay:', error);
}
}
} }
export default ApplepayManagerBlockEditor; export default ApplepayManagerBlockEditor;

View file

@ -1,95 +1,88 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
import CartActionHandler import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler';
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler"; import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription';
import {isPayPalSubscription} from "../../../../ppcp-blocks/resources/js/Helper/Subscription";
class BaseHandler { class BaseHandler {
constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
constructor(buttonConfig, ppcpConfig) { isVaultV3Mode() {
this.buttonConfig = buttonConfig; return (
this.ppcpConfig = ppcpConfig; this.ppcpConfig?.save_payment_methods?.id_token && // vault v3
} ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled && // not PayPal Subscriptions mode
this.ppcpConfig?.can_save_vault_token
); // vault is enabled
}
isVaultV3Mode() { validateContext() {
return this.ppcpConfig?.save_payment_methods?.id_token // vault v3 if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) {
&& ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled // not PayPal Subscriptions mode return this.isVaultV3Mode();
&& this.ppcpConfig?.can_save_vault_token; // vault is enabled }
} return true;
}
validateContext() { shippingAllowed() {
if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { return this.buttonConfig.product.needsShipping;
return this.isVaultV3Mode(); }
}
return true;
}
shippingAllowed() { transactionInfo() {
return this.buttonConfig.product.needsShipping; return new Promise( ( resolve, reject ) => {
} const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint;
const separator = endpoint.indexOf( '?' ) !== -1 ? '&' : '?';
transactionInfo() { fetch( endpoint + separator + 'shipping=1', {
return new Promise((resolve, reject) => { method: 'GET',
const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint; credentials: 'same-origin',
const separator = (endpoint.indexOf('?') !== -1) ? '&' : '?'; } )
.then( ( result ) => result.json() )
.then( ( result ) => {
if ( ! result.success ) {
return;
}
fetch( // handle script reload
endpoint + separator + 'shipping=1', const data = result.data;
{
method: 'GET',
credentials: 'same-origin'
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload resolve( {
const data = result.data; countryCode: data.country_code,
currencyCode: data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.total_str,
chosenShippingMethods:
data.chosen_shipping_methods || null,
shippingPackages: data.shipping_packages || null,
} );
} );
} );
}
resolve({ createOrder() {
countryCode: data.country_code, return this.actionHandler().configuration().createOrder( null, null );
currencyCode: data.currency_code, }
totalPriceStatus: 'FINAL',
totalPrice: data.total_str,
chosenShippingMethods: data.chosen_shipping_methods || null,
shippingPackages: data.shipping_packages || null,
});
}); approveOrder( data, actions ) {
}); return this.actionHandler().configuration().onApprove( data, actions );
} }
createOrder() { actionHandler() {
return this.actionHandler().configuration().createOrder(null, null); return new CartActionHandler( this.ppcpConfig, this.errorHandler() );
} }
approveOrder(data, actions) { errorHandler() {
return this.actionHandler().configuration().onApprove(data, actions); return new ErrorHandler(
} this.ppcpConfig.labels.error.generic,
document.querySelector( '.woocommerce-notices-wrapper' )
actionHandler() { );
return new CartActionHandler( }
this.ppcpConfig,
this.errorHandler(),
);
}
errorHandler() {
return new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
}
errorHandler() {
return new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
}
errorHandler() {
return new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector( '.woocommerce-notices-wrapper' )
);
}
} }
export default BaseHandler; export default BaseHandler;

View file

@ -1,7 +1,5 @@
import BaseHandler from "./BaseHandler"; import BaseHandler from './BaseHandler';
class CartBlockHandler extends BaseHandler { class CartBlockHandler extends BaseHandler {}
}
export default CartBlockHandler; export default CartBlockHandler;

View file

@ -1,7 +1,5 @@
import BaseHandler from "./BaseHandler"; import BaseHandler from './BaseHandler';
class CartHandler extends BaseHandler { class CartHandler extends BaseHandler {}
}
export default CartHandler; export default CartHandler;

View file

@ -1,7 +1,5 @@
import BaseHandler from "./BaseHandler"; import BaseHandler from './BaseHandler';
class CheckoutBlockHandler extends BaseHandler{ class CheckoutBlockHandler extends BaseHandler {}
}
export default CheckoutBlockHandler; export default CheckoutBlockHandler;

View file

@ -1,17 +1,15 @@
import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner';
import CheckoutActionHandler import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler';
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler"; import BaseHandler from './BaseHandler';
import BaseHandler from "./BaseHandler";
class CheckoutHandler extends BaseHandler { class CheckoutHandler extends BaseHandler {
actionHandler() {
actionHandler() { return new CheckoutActionHandler(
return new CheckoutActionHandler( this.ppcpConfig,
this.ppcpConfig, this.errorHandler(),
this.errorHandler(), new Spinner()
new Spinner() );
); }
}
} }
export default CheckoutHandler; export default CheckoutHandler;

View file

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

View file

@ -1,7 +1,5 @@
import BaseHandler from "./BaseHandler"; import BaseHandler from './BaseHandler';
class MiniCartHandler extends BaseHandler { class MiniCartHandler extends BaseHandler {}
}
export default MiniCartHandler; export default MiniCartHandler;

View file

@ -1,38 +1,35 @@
import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner';
import BaseHandler from "./BaseHandler"; import BaseHandler from './BaseHandler';
import CheckoutActionHandler import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler';
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
class PayNowHandler extends BaseHandler { class PayNowHandler extends BaseHandler {
validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) {
return this.isVaultV3Mode();
}
return true;
}
validateContext() { transactionInfo() {
if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { return new Promise( async ( resolve, reject ) => {
return this.isVaultV3Mode(); const data = this.ppcpConfig.pay_now;
}
return true;
}
transactionInfo() { resolve( {
return new Promise(async (resolve, reject) => { countryCode: data.country_code,
const data = this.ppcpConfig['pay_now']; currencyCode: data.currency_code,
totalPriceStatus: 'FINAL',
resolve({ totalPrice: data.total_str,
countryCode: data.country_code, } );
currencyCode: data.currency_code, } );
totalPriceStatus: 'FINAL', }
totalPrice: data.total_str
});
});
}
actionHandler() {
return new CheckoutActionHandler(
this.ppcpConfig,
this.errorHandler(),
new Spinner()
);
}
actionHandler() {
return new CheckoutActionHandler(
this.ppcpConfig,
this.errorHandler(),
new Spinner()
);
}
} }
export default PayNowHandler; export default PayNowHandler;

View file

@ -1,37 +1,35 @@
import BaseHandler from "./BaseHandler"; import BaseHandler from './BaseHandler';
class PreviewHandler extends BaseHandler { class PreviewHandler extends BaseHandler {
constructor( buttonConfig, ppcpConfig, externalHandler ) {
super( buttonConfig, ppcpConfig, externalHandler );
}
constructor(buttonConfig, ppcpConfig, externalHandler) { transactionInfo() {
super(buttonConfig, ppcpConfig, externalHandler); // We need to return something as ApplePay button initialization expects valid data.
} return {
countryCode: 'US',
currencyCode: 'USD',
totalPrice: '10.00',
totalPriceStatus: 'FINAL',
};
}
transactionInfo() { createOrder() {
// We need to return something as ApplePay button initialization expects valid data. throw new Error( 'Create order fail. This is just a preview.' );
return { }
countryCode: "US",
currencyCode: "USD",
totalPrice: "10.00",
totalPriceStatus: "FINAL"
}
}
createOrder() { approveOrder( data, actions ) {
throw new Error('Create order fail. This is just a preview.'); throw new Error( 'Approve order fail. This is just a preview.' );
} }
approveOrder(data, actions) { actionHandler() {
throw new Error('Approve order fail. This is just a preview.'); throw new Error( 'Action handler fail. This is just a preview.' );
} }
actionHandler() {
throw new Error('Action handler fail. This is just a preview.');
}
errorHandler() {
throw new Error('Error handler fail. This is just a preview.');
}
errorHandler() {
throw new Error( 'Error handler fail. This is just a preview.' );
}
} }
export default PreviewHandler; export default PreviewHandler;

View file

@ -1,83 +1,82 @@
import SingleProductActionHandler import SingleProductActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler';
from "../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler"; import SimulateCart from '../../../../ppcp-button/resources/js/modules/Helper/SimulateCart';
import SimulateCart from "../../../../ppcp-button/resources/js/modules/Helper/SimulateCart"; import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; import UpdateCart from '../../../../ppcp-button/resources/js/modules/Helper/UpdateCart';
import UpdateCart from "../../../../ppcp-button/resources/js/modules/Helper/UpdateCart"; import BaseHandler from './BaseHandler';
import BaseHandler from "./BaseHandler";
class SingleProductHandler extends BaseHandler { class SingleProductHandler extends BaseHandler {
validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.product ) {
return this.isVaultV3Mode();
}
return true;
}
validateContext() { transactionInfo() {
if ( this.ppcpConfig?.locations_with_subscription_product?.product ) { const errorHandler = new ErrorHandler(
return this.isVaultV3Mode(); this.ppcpConfig.labels.error.generic,
} document.querySelector( '.woocommerce-notices-wrapper' )
return true; );
}
transactionInfo() { function form() {
const errorHandler = new ErrorHandler( return document.querySelector( 'form.cart' );
this.ppcpConfig.labels.error.generic, }
document.querySelector('.woocommerce-notices-wrapper')
);
function form() { const actionHandler = new SingleProductActionHandler(
return document.querySelector('form.cart'); null,
} null,
form(),
errorHandler
);
const actionHandler = new SingleProductActionHandler( const hasSubscriptions =
null, PayPalCommerceGateway.data_client_id.has_subscriptions &&
null, PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
form(),
errorHandler,
);
const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions const products = hasSubscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; ? actionHandler.getSubscriptionProducts()
: actionHandler.getProducts();
const products = hasSubscriptions return new Promise( ( resolve, reject ) => {
? actionHandler.getSubscriptionProducts() new SimulateCart(
: actionHandler.getProducts(); 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,
} );
}, products );
} );
}
return new Promise((resolve, reject) => { createOrder() {
(new SimulateCart( return this.actionHandler()
this.ppcpConfig.ajax.simulate_cart.endpoint, .configuration()
this.ppcpConfig.ajax.simulate_cart.nonce, .createOrder( null, null, {
)).simulate((data) => { updateCartOptions: {
keepShipping: true,
},
} );
}
resolve({ actionHandler() {
countryCode: data.country_code, return new SingleProductActionHandler(
currencyCode: data.currency_code, this.ppcpConfig,
totalPriceStatus: 'FINAL', new UpdateCart(
totalPrice: data.total_str this.ppcpConfig.ajax.change_cart.endpoint,
}); this.ppcpConfig.ajax.change_cart.nonce
),
document.querySelector( 'form.cart' ),
this.errorHandler()
);
}
}, products); products() {
}); return this.actionHandler().getProducts();
} }
createOrder() {
return this.actionHandler().configuration().createOrder(null, null, {
'updateCartOptions': {
'keepShipping': true
}
});
}
actionHandler() {
return new SingleProductActionHandler(
this.ppcpConfig,
new UpdateCart(
this.ppcpConfig.ajax.change_cart.endpoint,
this.ppcpConfig.ajax.change_cart.nonce,
),
document.querySelector('form.cart'),
this.errorHandler(),
);
}
products() {
return this.actionHandler().getProducts();
}
} }
export default SingleProductHandler; export default SingleProductHandler;

View file

@ -1,10 +1,12 @@
export function createAppleErrors(errors) { export function createAppleErrors( errors ) {
const errorList = [] const errorList = [];
for (const error of errors) { for ( const error of errors ) {
const {contactField = null, code = null, message = null} = error const { contactField = null, code = null, message = null } = error;
const appleError = contactField ? new ApplePayError(code, contactField, message) : new ApplePayError(code) const appleError = contactField
errorList.push(appleError) ? new ApplePayError( code, contactField, message )
} : new ApplePayError( code );
errorList.push( appleError );
}
return errorList return errorList;
} }

View file

@ -1,8 +1,8 @@
export const buttonID = 'applepay-container'; export const buttonID = 'applepay-container';
export const endpoints = { export const endpoints = {
validation: '_apple_pay_validation', validation: '_apple_pay_validation',
createOrderCart: '_apple_pay_create_order_cart', createOrderCart: '_apple_pay_create_order_cart',
createOrderProduct: '_apple_pay_create_order_product', createOrderProduct: '_apple_pay_create_order_product',
updateShippingMethod: '_apple_pay_update_shipping_contact', updateShippingMethod: '_apple_pay_update_shipping_contact',
updateShippingContact: '_apple_pay_update_billing_contact', updateShippingContact: '_apple_pay_update_billing_contact',
} };

View file

@ -6,106 +6,118 @@ import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Rend
* Accessor that creates and returns a single PreviewButtonManager instance. * Accessor that creates and returns a single PreviewButtonManager instance.
*/ */
const buttonManager = () => { const buttonManager = () => {
if (!ApplePayPreviewButtonManager.instance) { if ( ! ApplePayPreviewButtonManager.instance ) {
ApplePayPreviewButtonManager.instance = new ApplePayPreviewButtonManager(); ApplePayPreviewButtonManager.instance =
} new ApplePayPreviewButtonManager();
}
return ApplePayPreviewButtonManager.instance; return ApplePayPreviewButtonManager.instance;
}; };
/** /**
* Manages all Apple Pay preview buttons on this page. * Manages all Apple Pay preview buttons on this page.
*/ */
class ApplePayPreviewButtonManager extends PreviewButtonManager { class ApplePayPreviewButtonManager extends PreviewButtonManager {
constructor() { constructor() {
const args = { const args = {
methodName: 'ApplePay', methodName: 'ApplePay',
buttonConfig: window.wc_ppcp_applepay_admin, buttonConfig: window.wc_ppcp_applepay_admin,
}; };
super(args); super( args );
} }
/** /**
* Responsible for fetching and returning the PayPal configuration object for this payment * Responsible for fetching and returning the PayPal configuration object for this payment
* method. * method.
* *
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
* @return {Promise<{}>} * @return {Promise<{}>}
*/ */
async fetchConfig(payPal) { async fetchConfig( payPal ) {
const apiMethod = payPal?.Applepay()?.config; const apiMethod = payPal?.Applepay()?.config;
if (!apiMethod) { if ( ! apiMethod ) {
this.error('configuration object cannot be retrieved from PayPal'); this.error(
return {}; 'configuration object cannot be retrieved from PayPal'
} );
return {};
}
return await apiMethod(); return await apiMethod();
} }
/** /**
* This method is responsible for creating a new PreviewButton instance and returning it. * This method is responsible for creating a new PreviewButton instance and returning it.
* *
* @param {string} wrapperId - CSS ID of the wrapper element. * @param {string} wrapperId - CSS ID of the wrapper element.
* @return {ApplePayPreviewButton} * @return {ApplePayPreviewButton}
*/ */
createButtonInstance(wrapperId) { createButtonInstance( wrapperId ) {
return new ApplePayPreviewButton({ return new ApplePayPreviewButton( {
selector: wrapperId, selector: wrapperId,
apiConfig: this.apiConfig, apiConfig: this.apiConfig,
}); } );
} }
} }
/** /**
* A single Apple Pay preview button instance. * A single Apple Pay preview button instance.
*/ */
class ApplePayPreviewButton extends PreviewButton { class ApplePayPreviewButton extends PreviewButton {
constructor(args) { constructor( args ) {
super(args); super( args );
this.selector = `${args.selector}ApplePay`; this.selector = `${ args.selector }ApplePay`;
this.defaultAttributes = { this.defaultAttributes = {
button: { button: {
type: 'pay', type: 'pay',
color: 'black', color: 'black',
lang: 'en', lang: 'en',
}, },
}; };
} }
createNewWrapper() { createNewWrapper() {
const element = super.createNewWrapper(); const element = super.createNewWrapper();
element.addClass('ppcp-button-applepay'); element.addClass( 'ppcp-button-applepay' );
return element; return element;
} }
createButton(buttonConfig) { createButton( buttonConfig ) {
const button = new ApplepayButton('preview', null, buttonConfig, this.ppcpConfig); const button = new ApplepayButton(
'preview',
null,
buttonConfig,
this.ppcpConfig
);
button.init(this.apiConfig); button.init( this.apiConfig );
} }
/** /**
* Merge form details into the config object for preview. * Merge form details into the config object for preview.
* Mutates the previewConfig object; no return value. * Mutates the previewConfig object; no return value.
*/ * @param buttonConfig
dynamicPreviewConfig(buttonConfig, ppcpConfig) { * @param ppcpConfig
// The Apple Pay button expects the "wrapper" to be an ID without `#` prefix! */
buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(/^#/, ''); dynamicPreviewConfig( buttonConfig, ppcpConfig ) {
// The Apple Pay button expects the "wrapper" to be an ID without `#` prefix!
buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(
/^#/,
''
);
// Merge the current form-values into the preview-button configuration. // Merge the current form-values into the preview-button configuration.
if (ppcpConfig.button) { if ( ppcpConfig.button ) {
buttonConfig.button.type = ppcpConfig.button.style.type; buttonConfig.button.type = ppcpConfig.button.style.type;
buttonConfig.button.color = ppcpConfig.button.style.color; buttonConfig.button.color = ppcpConfig.button.style.color;
buttonConfig.button.lang = buttonConfig.button.lang =
ppcpConfig.button.style?.lang || ppcpConfig.button.style.language; ppcpConfig.button.style?.lang ||
} ppcpConfig.button.style.language;
} }
}
} }
// Initialize the preview button manager. // Initialize the preview button manager.

View file

@ -1,79 +1,81 @@
import {useEffect, useState} from '@wordpress/element'; import { useEffect, useState } from '@wordpress/element';
import {registerExpressPaymentMethod} from '@woocommerce/blocks-registry'; import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry';
import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription' import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
import {loadCustomScript} from "@paypal/paypal-js"; import { loadCustomScript } from '@paypal/paypal-js';
import CheckoutHandler from "./Context/CheckoutHandler"; import CheckoutHandler from './Context/CheckoutHandler';
import ApplepayManager from "./ApplepayManager"; import ApplepayManager from './ApplepayManager';
import ApplepayManagerBlockEditor from "./ApplepayManagerBlockEditor"; import ApplepayManagerBlockEditor from './ApplepayManagerBlockEditor';
const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data'); const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
const ppcpConfig = ppcpData.scriptData; const ppcpConfig = ppcpData.scriptData;
const buttonData = wc.wcSettings.getSetting('ppcp-applepay_data'); const buttonData = wc.wcSettings.getSetting( 'ppcp-applepay_data' );
const buttonConfig = buttonData.scriptData; const buttonConfig = buttonData.scriptData;
if (typeof window.PayPalCommerceGateway === 'undefined') { if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
window.PayPalCommerceGateway = ppcpConfig; window.PayPalCommerceGateway = ppcpConfig;
} }
const ApplePayComponent = ( props ) => { const ApplePayComponent = ( props ) => {
const [bootstrapped, setBootstrapped] = useState(false); const [ bootstrapped, setBootstrapped ] = useState( false );
const [paypalLoaded, setPaypalLoaded] = useState(false); const [ paypalLoaded, setPaypalLoaded ] = useState( false );
const [applePayLoaded, setApplePayLoaded] = useState(false); const [ applePayLoaded, setApplePayLoaded ] = useState( false );
const bootstrap = function () { const bootstrap = function () {
const ManagerClass = props.isEditing ? ApplepayManagerBlockEditor : ApplepayManager; const ManagerClass = props.isEditing
const manager = new ManagerClass(buttonConfig, ppcpConfig); ? ApplepayManagerBlockEditor
manager.init(); : ApplepayManager;
}; const manager = new ManagerClass( buttonConfig, ppcpConfig );
manager.init();
};
useEffect(() => { useEffect( () => {
// Load ApplePay SDK // Load ApplePay SDK
loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
setApplePayLoaded(true); setApplePayLoaded( true );
}); } );
ppcpConfig.url_params.components += ',applepay'; ppcpConfig.url_params.components += ',applepay';
// Load PayPal // Load PayPal
loadPaypalScript(ppcpConfig, () => { loadPaypalScript( ppcpConfig, () => {
setPaypalLoaded(true); setPaypalLoaded( true );
}); } );
}, []); }, [] );
useEffect(() => { useEffect( () => {
if (!bootstrapped && paypalLoaded && applePayLoaded) { if ( ! bootstrapped && paypalLoaded && applePayLoaded ) {
setBootstrapped(true); setBootstrapped( true );
bootstrap(); bootstrap();
} }
}, [paypalLoaded, applePayLoaded]); }, [ paypalLoaded, applePayLoaded ] );
return ( return (
<div <div
id={buttonConfig.button.wrapper.replace('#', '')} id={ buttonConfig.button.wrapper.replace( '#', '' ) }
className="ppcp-button-apm ppcp-button-applepay"> className="ppcp-button-apm ppcp-button-applepay"
</div> ></div>
); );
} };
const features = ['products']; const features = [ 'products' ];
if ( if (
cartHasSubscriptionProducts(ppcpConfig) cartHasSubscriptionProducts( ppcpConfig ) &&
&& (new CheckoutHandler(buttonConfig, ppcpConfig)).isVaultV3Mode() new CheckoutHandler( buttonConfig, ppcpConfig ).isVaultV3Mode()
) { ) {
features.push('subscriptions'); features.push( 'subscriptions' );
} }
registerExpressPaymentMethod({ registerExpressPaymentMethod( {
name: buttonData.id, name: buttonData.id,
label: <div dangerouslySetInnerHTML={{__html: buttonData.title}}/>, label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
content: <ApplePayComponent isEditing={false}/>, content: <ApplePayComponent isEditing={ false } />,
edit: <ApplePayComponent isEditing={true}/>, edit: <ApplePayComponent isEditing={ true } />,
ariaLabel: buttonData.title, ariaLabel: buttonData.title,
canMakePayment: () => buttonData.enabled, canMakePayment: () => buttonData.enabled,
supports: { supports: {
features: features, features,
}, },
}); } );

View file

@ -1,71 +1,62 @@
import {loadCustomScript} from "@paypal/paypal-js"; import { loadCustomScript } from '@paypal/paypal-js';
import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
import ApplepayManager from "./ApplepayManager"; import ApplepayManager from './ApplepayManager';
import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
(function ({ ( function ( { buttonConfig, ppcpConfig, jQuery } ) {
buttonConfig, let manager;
ppcpConfig,
jQuery
}) {
let manager; const bootstrap = function () {
manager = new ApplepayManager( buttonConfig, ppcpConfig );
manager.init();
};
const bootstrap = function () { setupButtonEvents( function () {
manager = new ApplepayManager(buttonConfig, ppcpConfig); if ( manager ) {
manager.init(); manager.reinit();
}; }
} );
setupButtonEvents(function() { document.addEventListener( 'DOMContentLoaded', () => {
if (manager) { if (
manager.reinit(); typeof buttonConfig === 'undefined' ||
} typeof ppcpConfig === 'undefined'
}); ) {
return;
}
const isMiniCart = ppcpConfig.mini_cart_buttons_enabled;
const isButton = jQuery( '#' + buttonConfig.button.wrapper ).length > 0;
// If button wrapper is not present then there is no need to load the scripts.
// minicart loads later?
if ( ! isMiniCart && ! isButton ) {
return;
}
document.addEventListener( let bootstrapped = false;
'DOMContentLoaded', let paypalLoaded = false;
() => { let applePayLoaded = false;
if (
(typeof (buttonConfig) === 'undefined') ||
(typeof (ppcpConfig) === 'undefined')
) {
return;
}
const isMiniCart = ppcpConfig.mini_cart_buttons_enabled;
const isButton = jQuery('#' + buttonConfig.button.wrapper).length > 0;
// If button wrapper is not present then there is no need to load the scripts.
// minicart loads later?
if (!isMiniCart && !isButton) {
return;
}
let bootstrapped = false; const tryToBoot = () => {
let paypalLoaded = false; if ( ! bootstrapped && paypalLoaded && applePayLoaded ) {
let applePayLoaded = false; bootstrapped = true;
bootstrap();
}
};
const tryToBoot = () => { // Load ApplePay SDK
if (!bootstrapped && paypalLoaded && applePayLoaded) { loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
bootstrapped = true; applePayLoaded = true;
bootstrap(); tryToBoot();
} } );
}
// Load ApplePay SDK // Load PayPal
loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { loadPaypalScript( ppcpConfig, () => {
applePayLoaded = true; paypalLoaded = true;
tryToBoot(); tryToBoot();
}); } );
} );
// Load PayPal } )( {
loadPaypalScript(ppcpConfig, () => { buttonConfig: window.wc_ppcp_applepay,
paypalLoaded = true; ppcpConfig: window.PayPalCommerceGateway,
tryToBoot(); jQuery: window.jQuery,
}); } );
},
);
})({
buttonConfig: window.wc_ppcp_applepay,
ppcpConfig: window.PayPalCommerceGateway,
jQuery: window.jQuery
});

File diff suppressed because it is too large Load diff

View file

@ -1,39 +1,37 @@
class DomElement { class DomElement {
constructor( config ) {
this.$ = jQuery;
this.config = config;
this.selector = this.config.selector;
this.id = this.config.id || null;
this.className = this.config.className || null;
this.attributes = this.config.attributes || null;
this.anchorSelector = this.config.anchorSelector || null;
}
constructor(config) { trigger( action ) {
this.$ = jQuery; this.$( this.selector ).trigger( action );
this.config = config; }
this.selector = this.config.selector;
this.id = this.config.id || null;
this.className = this.config.className || null;
this.attributes = this.config.attributes || null;
this.anchorSelector = this.config.anchorSelector || null;
}
trigger(action) { on( action, callable ) {
this.$(this.selector).trigger(action); this.$( document ).on( action, this.selector, callable );
} }
on(action, callable) { hide() {
this.$(document).on(action, this.selector, callable); this.$( this.selector ).hide();
} }
hide() { show() {
this.$(this.selector).hide(); this.$( this.selector ).show();
} }
show() { click() {
this.$(this.selector).show(); this.get().click();
} }
click() {
this.get().click();
}
get() {
return document.querySelector(this.selector);
}
get() {
return document.querySelector( this.selector );
}
} }
export default DomElement; export default DomElement;

View file

@ -1,116 +1,116 @@
import DomElement from "./DomElement"; import DomElement from './DomElement';
class DomElementCollection { class DomElementCollection {
constructor() {
this.gatewayRadioButton = new DomElement( {
selector: '#payment_method_ppcp-axo-gateway',
} );
constructor() { this.gatewayDescription = new DomElement( {
this.gatewayRadioButton = new DomElement({ selector: '.payment_box.payment_method_ppcp-axo-gateway',
selector: '#payment_method_ppcp-axo-gateway', } );
});
this.gatewayDescription = new DomElement({ this.defaultSubmitButton = new DomElement( {
selector: '.payment_box.payment_method_ppcp-axo-gateway', selector: '#place_order',
}); } );
this.defaultSubmitButton = new DomElement({ this.paymentContainer = new DomElement( {
selector: '#place_order', id: 'ppcp-axo-payment-container',
}); selector: '#ppcp-axo-payment-container',
className: 'ppcp-axo-payment-container',
} );
this.paymentContainer = new DomElement({ this.watermarkContainer = new DomElement( {
id: 'ppcp-axo-payment-container', id: 'ppcp-axo-watermark-container',
selector: '#ppcp-axo-payment-container', selector: '#ppcp-axo-watermark-container',
className: 'ppcp-axo-payment-container' className:
}); 'ppcp-axo-watermark-container ppcp-axo-watermark-loading loader',
} );
this.watermarkContainer = new DomElement({ this.customerDetails = new DomElement( {
id: 'ppcp-axo-watermark-container', selector: '#customer_details > *:not(#ppcp-axo-customer-details)',
selector: '#ppcp-axo-watermark-container', } );
className: 'ppcp-axo-watermark-container ppcp-axo-watermark-loading loader'
});
this.customerDetails = new DomElement({ this.axoCustomerDetails = new DomElement( {
selector: '#customer_details > *:not(#ppcp-axo-customer-details)' id: 'ppcp-axo-customer-details',
}); selector: '#ppcp-axo-customer-details',
className: 'ppcp-axo-customer-details',
anchorSelector: '#customer_details',
} );
this.axoCustomerDetails = new DomElement({ this.emailWidgetContainer = new DomElement( {
id: 'ppcp-axo-customer-details', id: 'ppcp-axo-email-widget',
selector: '#ppcp-axo-customer-details', selector: '#ppcp-axo-email-widget',
className: 'ppcp-axo-customer-details', className: 'ppcp-axo-email-widget',
anchorSelector: '#customer_details' } );
});
this.emailWidgetContainer = new DomElement({ this.shippingAddressContainer = new DomElement( {
id: 'ppcp-axo-email-widget', id: 'ppcp-axo-shipping-address-container',
selector: '#ppcp-axo-email-widget', selector: '#ppcp-axo-shipping-address-container',
className: 'ppcp-axo-email-widget' className: 'ppcp-axo-shipping-address-container',
}); } );
this.shippingAddressContainer = new DomElement({ this.billingAddressContainer = new DomElement( {
id: 'ppcp-axo-shipping-address-container', id: 'ppcp-axo-billing-address-container',
selector: '#ppcp-axo-shipping-address-container', selector: '#ppcp-axo-billing-address-container',
className: 'ppcp-axo-shipping-address-container' className: 'ppcp-axo-billing-address-container',
}); } );
this.billingAddressContainer = new DomElement({ this.fieldBillingEmail = new DomElement( {
id: 'ppcp-axo-billing-address-container', selector: '#billing_email_field',
selector: '#ppcp-axo-billing-address-container', } );
className: 'ppcp-axo-billing-address-container'
});
this.fieldBillingEmail = new DomElement({ this.billingEmailFieldWrapper = new DomElement( {
selector: '#billing_email_field' id: 'ppcp-axo-billing-email-field-wrapper',
}); selector: '#ppcp-axo-billing-email-field-wrapper',
} );
this.billingEmailFieldWrapper = new DomElement({ this.billingEmailSubmitButton = new DomElement( {
id: 'ppcp-axo-billing-email-field-wrapper', id: 'ppcp-axo-billing-email-submit-button',
selector: '#ppcp-axo-billing-email-field-wrapper', selector: '#ppcp-axo-billing-email-submit-button',
}); className:
'ppcp-axo-billing-email-submit-button-hidden button alt wp-element-button wc-block-components-button',
} );
this.billingEmailSubmitButton = new DomElement({ this.billingEmailSubmitButtonSpinner = new DomElement( {
id: 'ppcp-axo-billing-email-submit-button', id: 'ppcp-axo-billing-email-submit-button-spinner',
selector: '#ppcp-axo-billing-email-submit-button', selector: '#ppcp-axo-billing-email-submit-button-spinner',
className: 'ppcp-axo-billing-email-submit-button-hidden button alt wp-element-button wc-block-components-button' className: 'loader ppcp-axo-overlay',
}); } );
this.billingEmailSubmitButtonSpinner = new DomElement({ this.submitButtonContainer = new DomElement( {
id: 'ppcp-axo-billing-email-submit-button-spinner', selector: '#ppcp-axo-submit-button-container',
selector: '#ppcp-axo-billing-email-submit-button-spinner', } );
className: 'loader ppcp-axo-overlay'
});
this.submitButtonContainer = new DomElement({ this.submitButton = new DomElement( {
selector: '#ppcp-axo-submit-button-container', selector: '#ppcp-axo-submit-button-container button',
}); } );
this.submitButton = new DomElement({ this.changeShippingAddressLink = new DomElement( {
selector: '#ppcp-axo-submit-button-container button' selector: '*[data-ppcp-axo-change-shipping-address]',
}); attributes: 'data-ppcp-axo-change-shipping-address',
} );
this.changeShippingAddressLink = new DomElement({ this.changeBillingAddressLink = new DomElement( {
selector: '*[data-ppcp-axo-change-shipping-address]', selector: '*[data-ppcp-axo-change-billing-address]',
attributes: 'data-ppcp-axo-change-shipping-address', attributes: 'data-ppcp-axo-change-billing-address',
}); } );
this.changeBillingAddressLink = new DomElement({ this.changeCardLink = new DomElement( {
selector: '*[data-ppcp-axo-change-billing-address]', selector: '*[data-ppcp-axo-change-card]',
attributes: 'data-ppcp-axo-change-billing-address', attributes: 'data-ppcp-axo-change-card',
}); } );
this.changeCardLink = new DomElement({ this.showGatewaySelectionLink = new DomElement( {
selector: '*[data-ppcp-axo-change-card]', selector: '*[data-ppcp-axo-show-gateway-selection]',
attributes: 'data-ppcp-axo-change-card', attributes: 'data-ppcp-axo-show-gateway-selection',
}); } );
this.showGatewaySelectionLink = new DomElement({ this.axoNonceInput = new DomElement( {
selector: '*[data-ppcp-axo-show-gateway-selection]', id: 'ppcp-axo-nonce',
attributes: 'data-ppcp-axo-show-gateway-selection', selector: '#ppcp-axo-nonce',
}); } );
}
this.axoNonceInput = new DomElement({
id: 'ppcp-axo-nonce',
selector: '#ppcp-axo-nonce',
});
}
} }
export default DomElementCollection; export default DomElementCollection;

View file

@ -1,153 +1,159 @@
class FormFieldGroup { class FormFieldGroup {
constructor( config ) {
this.data = {};
constructor(config) { this.baseSelector = config.baseSelector;
this.data = {}; this.contentSelector = config.contentSelector;
this.fields = config.fields || {};
this.template = config.template;
this.baseSelector = config.baseSelector; this.active = false;
this.contentSelector = config.contentSelector; }
this.fields = config.fields || {};
this.template = config.template;
this.active = false; setData( data ) {
} this.data = data;
this.refresh();
}
setData(data) { dataValue( fieldKey ) {
this.data = data; if ( ! fieldKey || ! this.fields[ fieldKey ] ) {
this.refresh(); return '';
} }
dataValue(fieldKey) { if ( typeof this.fields[ fieldKey ].valueCallback === 'function' ) {
if (!fieldKey || !this.fields[fieldKey]) { return this.fields[ fieldKey ].valueCallback( this.data );
return ''; }
}
if (typeof this.fields[fieldKey].valueCallback === 'function') { const path = this.fields[ fieldKey ].valuePath;
return this.fields[fieldKey].valueCallback(this.data);
}
const path = this.fields[fieldKey].valuePath; if ( ! path ) {
return '';
}
if (!path) { const value = path
return ''; .split( '.' )
} .reduce(
( acc, key ) =>
acc && acc[ key ] !== undefined ? acc[ key ] : undefined,
this.data
);
return value ? value : '';
}
const value = path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, this.data); activate() {
return value ? value : ''; this.active = true;
} this.refresh();
}
activate() { deactivate() {
this.active = true; this.active = false;
this.refresh(); this.refresh();
} }
deactivate() { toggle() {
this.active = false; this.active ? this.deactivate() : this.activate();
this.refresh(); }
}
toggle() { refresh() {
this.active ? this.deactivate() : this.activate(); const content = document.querySelector( this.contentSelector );
}
refresh() { if ( ! content ) {
let content = document.querySelector(this.contentSelector); return;
}
if (!content) { content.innerHTML = '';
return;
}
content.innerHTML = ''; if ( ! this.active ) {
this.hideField( this.contentSelector );
} else {
this.showField( this.contentSelector );
}
if (!this.active) { Object.keys( this.fields ).forEach( ( key ) => {
this.hideField(this.contentSelector); const field = this.fields[ key ];
} else {
this.showField(this.contentSelector);
}
Object.keys(this.fields).forEach((key) => { if ( this.active && ! field.showInput ) {
const field = this.fields[key]; this.hideField( field.selector );
} else {
this.showField( field.selector );
}
} );
if (this.active && !field.showInput) { if ( typeof this.template === 'function' ) {
this.hideField(field.selector); content.innerHTML = this.template( {
} else { value: ( fieldKey ) => {
this.showField(field.selector); return this.dataValue( fieldKey );
} },
}); isEmpty: () => {
let isEmpty = true;
Object.keys( this.fields ).forEach( ( fieldKey ) => {
if ( this.dataValue( fieldKey ) ) {
isEmpty = false;
return false;
}
} );
return isEmpty;
},
} );
}
}
if (typeof this.template === 'function') { showField( selector ) {
content.innerHTML = this.template({ const field = document.querySelector(
value: (fieldKey) => { this.baseSelector + ' ' + selector
return this.dataValue(fieldKey); );
}, if ( field ) {
isEmpty: () => { field.classList.remove( 'ppcp-axo-field-hidden' );
let isEmpty = true; }
Object.keys(this.fields).forEach((fieldKey) => { }
if (this.dataValue(fieldKey)) {
isEmpty = false;
return false;
}
});
return isEmpty;
}
});
}
} hideField( selector ) {
const field = document.querySelector(
this.baseSelector + ' ' + selector
);
if ( field ) {
field.classList.add( 'ppcp-axo-field-hidden' );
}
}
showField(selector) { inputElement( name ) {
const field = document.querySelector(this.baseSelector + ' ' + selector); const baseSelector = this.fields[ name ].selector;
if (field) {
field.classList.remove('ppcp-axo-field-hidden');
}
}
hideField(selector) { const select = document.querySelector( baseSelector + ' select' );
const field = document.querySelector(this.baseSelector + ' ' + selector); if ( select ) {
if (field) { return select;
field.classList.add('ppcp-axo-field-hidden'); }
}
}
inputElement(name) { const input = document.querySelector( baseSelector + ' input' );
const baseSelector = this.fields[name].selector; if ( input ) {
return input;
}
const select = document.querySelector(baseSelector + ' select'); return null;
if (select) { }
return select;
}
const input = document.querySelector(baseSelector + ' input'); inputValue( name ) {
if (input) { const el = this.inputElement( name );
return input; return el ? el.value : '';
} }
return null; toSubmitData( data ) {
} Object.keys( this.fields ).forEach( ( fieldKey ) => {
const field = this.fields[ fieldKey ];
inputValue(name) { if ( ! field.valuePath || ! field.selector ) {
const el = this.inputElement(name); return true;
return el ? el.value : ''; }
}
toSubmitData(data) { const inputElement = this.inputElement( fieldKey );
Object.keys(this.fields).forEach((fieldKey) => {
const field = this.fields[fieldKey];
if (!field.valuePath || !field.selector) { if ( ! inputElement ) {
return true; return true;
} }
const inputElement = this.inputElement(fieldKey);
if (!inputElement) {
return true;
}
data[inputElement.name] = this.dataValue(fieldKey);
});
}
data[ inputElement.name ] = this.dataValue( fieldKey );
} );
}
} }
export default FormFieldGroup; export default FormFieldGroup;

View file

@ -1,42 +1,42 @@
class Fastlane { class Fastlane {
construct() {
this.connection = null;
this.identity = null;
this.profile = null;
this.FastlaneCardComponent = null;
this.FastlanePaymentComponent = null;
this.FastlaneWatermarkComponent = null;
}
construct() { connect( config ) {
this.connection = null; return new Promise( ( resolve, reject ) => {
this.identity = null; window.paypal
this.profile = null; .Fastlane( config )
this.FastlaneCardComponent = null; .then( ( result ) => {
this.FastlanePaymentComponent = null; this.init( result );
this.FastlaneWatermarkComponent = null; resolve();
} } )
.catch( ( error ) => {
console.error( error );
reject();
} );
} );
}
connect(config) { init( connection ) {
return new Promise((resolve, reject) => { this.connection = connection;
window.paypal.Fastlane(config) this.identity = this.connection.identity;
.then((result) => { this.profile = this.connection.profile;
this.init(result); this.FastlaneCardComponent = this.connection.FastlaneCardComponent;
resolve(); this.FastlanePaymentComponent =
}) this.connection.FastlanePaymentComponent;
.catch((error) => { this.FastlaneWatermarkComponent =
console.error(error) this.connection.FastlaneWatermarkComponent;
reject(); }
});
});
}
init(connection) {
this.connection = connection;
this.identity = this.connection.identity;
this.profile = this.connection.profile;
this.FastlaneCardComponent = this.connection.FastlaneCardComponent;
this.FastlanePaymentComponent = this.connection.FastlanePaymentComponent;
this.FastlaneWatermarkComponent = this.connection.FastlaneWatermarkComponent
}
setLocale(locale) {
this.connection.setLocale(locale);
}
setLocale( locale ) {
this.connection.setLocale( locale );
}
} }
export default Fastlane; export default Fastlane;

View file

@ -1,29 +1,29 @@
export function log(message, level = 'info') { export function log( message, level = 'info' ) {
const wpDebug = window.wc_ppcp_axo?.wp_debug; const wpDebug = window.wc_ppcp_axo?.wp_debug;
const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint; const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint;
if (!endpoint) { if ( ! endpoint ) {
return; return;
} }
fetch(endpoint, { fetch( endpoint, {
method: 'POST', method: 'POST',
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: window.wc_ppcp_axo.ajax.frontend_logger.nonce, nonce: window.wc_ppcp_axo.ajax.frontend_logger.nonce,
log: { log: {
message, message,
level, level,
} },
}) } ),
}).then(() => { } ).then( () => {
if (wpDebug) { if ( wpDebug ) {
switch (level) { switch ( level ) {
case 'error': case 'error':
console.error(`[AXO] ${message}`); console.error( `[AXO] ${ message }` );
break; break;
default: default:
console.log(`[AXO] ${message}`); console.log( `[AXO] ${ message }` );
} }
} }
}); } );
} }

View file

@ -1,58 +1,55 @@
class PayPalInsights { class PayPalInsights {
constructor() {
window.paypalInsightDataLayer = window.paypalInsightDataLayer || [];
document.paypalInsight = () => {
paypalInsightDataLayer.push( arguments );
};
}
constructor() { /**
window.paypalInsightDataLayer = window.paypalInsightDataLayer || []; * @return {PayPalInsights}
document.paypalInsight = () => { */
paypalInsightDataLayer.push(arguments); static init() {
} if ( ! PayPalInsights.instance ) {
} PayPalInsights.instance = new PayPalInsights();
}
return PayPalInsights.instance;
}
/** static track( eventName, data ) {
* @returns {PayPalInsights} PayPalInsights.init();
*/ paypalInsight( 'event', eventName, data );
static init() { }
if (!PayPalInsights.instance) {
PayPalInsights.instance = new PayPalInsights();
}
return PayPalInsights.instance;
}
static track(eventName, data) { static config( clientId, data ) {
PayPalInsights.init(); PayPalInsights.init();
paypalInsight('event', eventName, data); paypalInsight( 'config', clientId, data );
} }
static config (clientId, data) { static setSessionId( sessionId ) {
PayPalInsights.init(); PayPalInsights.init();
paypalInsight('config', clientId, data); paypalInsight( 'set', { session_id: sessionId } );
} }
static setSessionId (sessionId) { static trackJsLoad() {
PayPalInsights.init(); PayPalInsights.track( 'js_load', { timestamp: Date.now() } );
paypalInsight('set', { session_id: sessionId }); }
}
static trackJsLoad () { static trackBeginCheckout( data ) {
PayPalInsights.track('js_load', { timestamp: Date.now() }); PayPalInsights.track( 'begin_checkout', data );
} }
static trackBeginCheckout (data) { static trackSubmitCheckoutEmail( data ) {
PayPalInsights.track('begin_checkout', data); PayPalInsights.track( 'submit_checkout_email', data );
} }
static trackSubmitCheckoutEmail (data) { static trackSelectPaymentMethod( data ) {
PayPalInsights.track('submit_checkout_email', data); PayPalInsights.track( 'select_payment_method', data );
} }
static trackSelectPaymentMethod (data) {
PayPalInsights.track('select_payment_method', data);
}
static trackEndCheckout (data) {
PayPalInsights.track('end_checkout', data);
}
static trackEndCheckout( data ) {
PayPalInsights.track( 'end_checkout', data );
}
} }
export default PayPalInsights; export default PayPalInsights;

View file

@ -1,121 +1,124 @@
import FormFieldGroup from "../Components/FormFieldGroup"; import FormFieldGroup from '../Components/FormFieldGroup';
class BillingView { class BillingView {
constructor( selector, elements ) {
this.el = elements;
constructor(selector, elements) { this.group = new FormFieldGroup( {
this.el = elements; baseSelector: '.woocommerce-checkout',
contentSelector: selector,
template: ( data ) => {
const valueOfSelect = ( selectSelector, key ) => {
if ( ! key ) {
return '';
}
const selectElement =
document.querySelector( selectSelector );
this.group = new FormFieldGroup({ if ( ! selectElement ) {
baseSelector: '.woocommerce-checkout', return key;
contentSelector: selector, }
template: (data) => {
const valueOfSelect = (selectSelector, key) => {
if (!key) {
return '';
}
const selectElement = document.querySelector(selectSelector);
if (!selectElement) { const option = selectElement.querySelector(
return key; `option[value="${ key }"]`
} );
return option ? option.textContent : key;
};
const option = selectElement.querySelector(`option[value="${key}"]`); if ( data.isEmpty() ) {
return option ? option.textContent : key; return `
}
if (data.isEmpty()) {
return `
<div style="margin-bottom: 20px;"> <div style="margin-bottom: 20px;">
<div class="axo-checkout-header-section"> <div class="axo-checkout-header-section">
<h3>Billing</h3> <h3>Billing</h3>
<a href="javascript:void(0)" ${this.el.changeBillingAddressLink.attributes}>Edit</a> <a href="javascript:void(0)" ${ this.el.changeBillingAddressLink.attributes }>Edit</a>
</div> </div>
<div>Please fill in your billing details.</div> <div>Please fill in your billing details.</div>
</div> </div>
`; `;
} }
return ''; return '';
}, },
fields: { fields: {
email: { email: {
'valuePath': 'email', valuePath: 'email',
}, },
firstName: { firstName: {
'selector': '#billing_first_name_field', selector: '#billing_first_name_field',
'valuePath': null valuePath: null,
}, },
lastName: { lastName: {
'selector': '#billing_last_name_field', selector: '#billing_last_name_field',
'valuePath': null valuePath: null,
}, },
street1: { street1: {
'selector': '#billing_address_1_field', selector: '#billing_address_1_field',
'valuePath': 'billing.address.addressLine1', valuePath: 'billing.address.addressLine1',
}, },
street2: { street2: {
'selector': '#billing_address_2_field', selector: '#billing_address_2_field',
'valuePath': null valuePath: null,
}, },
postCode: { postCode: {
'selector': '#billing_postcode_field', selector: '#billing_postcode_field',
'valuePath': 'billing.address.postalCode', valuePath: 'billing.address.postalCode',
}, },
city: { city: {
'selector': '#billing_city_field', selector: '#billing_city_field',
'valuePath': 'billing.address.adminArea2', valuePath: 'billing.address.adminArea2',
}, },
stateCode: { stateCode: {
'selector': '#billing_state_field', selector: '#billing_state_field',
'valuePath': 'billing.address.adminArea1', valuePath: 'billing.address.adminArea1',
}, },
countryCode: { countryCode: {
'selector': '#billing_country_field', selector: '#billing_country_field',
'valuePath': 'billing.address.countryCode', valuePath: 'billing.address.countryCode',
}, },
company: { company: {
'selector': '#billing_company_field', selector: '#billing_company_field',
'valuePath': null, valuePath: null,
}, },
phone: { phone: {
'selector': '#billing_phone_field', selector: '#billing_phone_field',
'valuePath': 'billing.phoneNumber' valuePath: 'billing.phoneNumber',
} },
} },
}); } );
} }
isActive() { isActive() {
return this.group.active; return this.group.active;
} }
activate() { activate() {
this.group.activate(); this.group.activate();
} }
deactivate() { deactivate() {
this.group.deactivate(); this.group.deactivate();
} }
refresh() { refresh() {
this.group.refresh(); this.group.refresh();
} }
setData(data) { setData( data ) {
this.group.setData(data); this.group.setData( data );
} }
inputValue(name) { inputValue( name ) {
return this.group.inputValue(name); return this.group.inputValue( name );
} }
fullName() { fullName() {
return `${this.inputValue('firstName')} ${this.inputValue('lastName')}`.trim(); return `${ this.inputValue( 'firstName' ) } ${ this.inputValue(
} 'lastName'
) }`.trim();
toSubmitData(data) { }
return this.group.toSubmitData(data);
}
toSubmitData( data ) {
return this.group.toSubmitData( data );
}
} }
export default BillingView; export default BillingView;

View file

@ -1,116 +1,126 @@
import FormFieldGroup from "../Components/FormFieldGroup"; import FormFieldGroup from '../Components/FormFieldGroup';
class CardView { class CardView {
constructor( selector, elements, manager ) {
this.el = elements;
this.manager = manager;
constructor(selector, elements, manager) { this.group = new FormFieldGroup( {
this.el = elements; baseSelector: '.ppcp-axo-payment-container',
this.manager = manager; contentSelector: selector,
template: ( data ) => {
const selectOtherPaymentMethod = () => {
if ( ! this.manager.hideGatewaySelection ) {
return '';
}
return `<p style="margin-top: 40px; text-align: center;"><a href="javascript:void(0)" ${ this.el.showGatewaySelectionLink.attributes }>Select other payment method</a></p>`;
};
this.group = new FormFieldGroup({ if ( data.isEmpty() ) {
baseSelector: '.ppcp-axo-payment-container', return `
contentSelector: selector,
template: (data) => {
const selectOtherPaymentMethod = () => {
if (!this.manager.hideGatewaySelection) {
return '';
}
return `<p style="margin-top: 40px; text-align: center;"><a href="javascript:void(0)" ${this.el.showGatewaySelectionLink.attributes}>Select other payment method</a></p>`;
};
if (data.isEmpty()) {
return `
<div style="margin-bottom: 20px; text-align: center;"> <div style="margin-bottom: 20px; text-align: center;">
${selectOtherPaymentMethod()} ${ selectOtherPaymentMethod() }
</div> </div>
`; `;
} }
const expiry = data.value('expiry').split('-'); const expiry = data.value( 'expiry' ).split( '-' );
const cardIcons = { const cardIcons = {
'VISA': 'visa-light.svg', VISA: 'visa-light.svg',
'MASTER_CARD': 'mastercard-light.svg', MASTER_CARD: 'mastercard-light.svg',
'AMEX': 'amex-light.svg', AMEX: 'amex-light.svg',
'DISCOVER': 'discover-light.svg', DISCOVER: 'discover-light.svg',
'DINERS': 'dinersclub-light.svg', DINERS: 'dinersclub-light.svg',
'JCB': 'jcb-light.svg', JCB: 'jcb-light.svg',
'UNIONPAY': 'unionpay-light.svg', UNIONPAY: 'unionpay-light.svg',
}; };
return ` return `
<div style="margin-bottom: 20px;"> <div style="margin-bottom: 20px;">
<div class="axo-checkout-header-section"> <div class="axo-checkout-header-section">
<h3>Card Details</h3> <h3>Card Details</h3>
<a href="javascript:void(0)" ${this.el.changeCardLink.attributes}>Edit</a> <a href="javascript:void(0)" ${
this.el.changeCardLink.attributes
}>Edit</a>
</div> </div>
<div style="border:2px solid #cccccc; border-radius: 10px; padding: 16px 20px; background-color:#f6f6f6"> <div style="border:2px solid #cccccc; border-radius: 10px; padding: 16px 20px; background-color:#f6f6f6">
<div style="float: right;"> <div style="float: right;">
<img <img
class="ppcp-card-icon" class="ppcp-card-icon"
title="${data.value('brand')}" title="${ data.value( 'brand' ) }"
src="${window.wc_ppcp_axo.icons_directory}${cardIcons[data.value('brand')]}" src="${
alt="${data.value('brand')}" window.wc_ppcp_axo.icons_directory
}${ cardIcons[ data.value( 'brand' ) ] }"
alt="${ data.value( 'brand' ) }"
> >
</div> </div>
<div style="font-family: monospace; font-size: 1rem; margin-top: 10px;">${data.value('lastDigits') ? '**** **** **** ' + data.value('lastDigits'): ''}</div> <div style="font-family: monospace; font-size: 1rem; margin-top: 10px;">${
<div>${expiry[1]}/${expiry[0]}</div> data.value( 'lastDigits' )
<div style="text-transform: uppercase">${data.value('name')}</div> ? '**** **** **** ' +
data.value( 'lastDigits' )
: ''
}</div>
<div>${ expiry[ 1 ] }/${ expiry[ 0 ] }</div>
<div style="text-transform: uppercase">${ data.value(
'name'
) }</div>
</div> </div>
${selectOtherPaymentMethod()} ${ selectOtherPaymentMethod() }
</div> </div>
`; `;
}, },
fields: { fields: {
brand: { brand: {
'valuePath': 'card.paymentSource.card.brand', valuePath: 'card.paymentSource.card.brand',
}, },
expiry: { expiry: {
'valuePath': 'card.paymentSource.card.expiry', valuePath: 'card.paymentSource.card.expiry',
}, },
lastDigits: { lastDigits: {
'valuePath': 'card.paymentSource.card.lastDigits', valuePath: 'card.paymentSource.card.lastDigits',
}, },
name: { name: {
'valuePath': 'card.paymentSource.card.name', valuePath: 'card.paymentSource.card.name',
}, },
} },
}); } );
} }
activate() { activate() {
this.group.activate(); this.group.activate();
} }
deactivate() { deactivate() {
this.group.deactivate(); this.group.deactivate();
} }
refresh() { refresh() {
this.group.refresh(); this.group.refresh();
} }
setData(data) { setData( data ) {
this.group.setData(data); this.group.setData( data );
} }
toSubmitData(data) { toSubmitData( data ) {
const name = this.group.dataValue('name'); const name = this.group.dataValue( 'name' );
const { firstName, lastName } = this.splitName(name); const { firstName, lastName } = this.splitName( name );
data['billing_first_name'] = firstName; data.billing_first_name = firstName;
data['billing_last_name'] = lastName ? lastName : firstName; data.billing_last_name = lastName ? lastName : firstName;
return this.group.toSubmitData(data); return this.group.toSubmitData( data );
} }
splitName(fullName) { splitName( fullName ) {
let nameParts = fullName.trim().split(' '); const nameParts = fullName.trim().split( ' ' );
let firstName = nameParts[0]; const firstName = nameParts[ 0 ];
let lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; const lastName =
nameParts.length > 1 ? nameParts[ nameParts.length - 1 ] : '';
return { firstName, lastName };
}
return { firstName, lastName };
}
} }
export default CardView; export default CardView;

View file

@ -1,170 +1,185 @@
import FormFieldGroup from "../Components/FormFieldGroup"; import FormFieldGroup from '../Components/FormFieldGroup';
class ShippingView { class ShippingView {
constructor( selector, elements, states ) {
this.el = elements;
this.states = states;
this.group = new FormFieldGroup( {
baseSelector: '.woocommerce-checkout',
contentSelector: selector,
template: ( data ) => {
const valueOfSelect = ( selectSelector, key ) => {
if ( ! key ) {
return '';
}
const selectElement =
document.querySelector( selectSelector );
constructor(selector, elements, states) { if ( ! selectElement ) {
this.el = elements; return key;
this.states = states; }
this.group = new FormFieldGroup({
baseSelector: '.woocommerce-checkout',
contentSelector: selector,
template: (data) => {
const valueOfSelect = (selectSelector, key) => {
if (!key) {
return '';
}
const selectElement = document.querySelector(selectSelector);
if (!selectElement) { const option = selectElement.querySelector(
return key; `option[value="${ key }"]`
} );
return option ? option.textContent : key;
};
const option = selectElement.querySelector(`option[value="${key}"]`); if ( data.isEmpty() ) {
return option ? option.textContent : key; return `
}
if (data.isEmpty()) {
return `
<div style="margin-bottom: 20px;"> <div style="margin-bottom: 20px;">
<div class="axo-checkout-header-section"> <div class="axo-checkout-header-section">
<h3>Shipping</h3> <h3>Shipping</h3>
<a href="javascript:void(0)" ${this.el.changeShippingAddressLink.attributes}>Edit</a> <a href="javascript:void(0)" ${ this.el.changeShippingAddressLink.attributes }>Edit</a>
</div> </div>
<div>Please fill in your shipping details.</div> <div>Please fill in your shipping details.</div>
</div> </div>
`; `;
} }
const countryCode = data.value('countryCode'); const countryCode = data.value( 'countryCode' );
const stateCode = data.value('stateCode'); const stateCode = data.value( 'stateCode' );
const stateName = (this.states[countryCode] && this.states[countryCode][stateCode]) ? this.states[countryCode][stateCode] : stateCode; const stateName =
this.states[ countryCode ] &&
this.states[ countryCode ][ stateCode ]
? this.states[ countryCode ][ stateCode ]
: stateCode;
if( if ( this.hasEmptyValues( data, stateName ) ) {
this.hasEmptyValues(data, stateName) return `
) {
return `
<div style="margin-bottom: 20px;"> <div style="margin-bottom: 20px;">
<div class="axo-checkout-header-section"> <div class="axo-checkout-header-section">
<h3>Shipping</h3> <h3>Shipping</h3>
<a href="javascript:void(0)" ${this.el.changeShippingAddressLink.attributes}>Edit</a> <a href="javascript:void(0)" ${ this.el.changeShippingAddressLink.attributes }>Edit</a>
</div> </div>
<div>Please fill in your shipping details.</div> <div>Please fill in your shipping details.</div>
</div> </div>
`; `;
} }
return ` return `
<div style="margin-bottom: 20px;"> <div style="margin-bottom: 20px;">
<div class="axo-checkout-header-section"> <div class="axo-checkout-header-section">
<h3>Shipping</h3> <h3>Shipping</h3>
<a href="javascript:void(0)" ${this.el.changeShippingAddressLink.attributes}>Edit</a> <a href="javascript:void(0)" ${
this.el.changeShippingAddressLink.attributes
}>Edit</a>
</div> </div>
<div>${data.value('email')}</div> <div>${ data.value( 'email' ) }</div>
<div>${data.value('company')}</div> <div>${ data.value( 'company' ) }</div>
<div>${data.value('firstName')} ${data.value('lastName')}</div> <div>${ data.value( 'firstName' ) } ${ data.value(
<div>${data.value('street1')}</div> 'lastName'
<div>${data.value('street2')}</div> ) }</div>
<div>${data.value('city')}, ${stateName} ${data.value('postCode')}</div> <div>${ data.value( 'street1' ) }</div>
<div>${valueOfSelect('#billing_country', countryCode)}</div> <div>${ data.value( 'street2' ) }</div>
<div>${data.value('phone')}</div> <div>${ data.value(
'city'
) }, ${ stateName } ${ data.value( 'postCode' ) }</div>
<div>${ valueOfSelect(
'#billing_country',
countryCode
) }</div>
<div>${ data.value( 'phone' ) }</div>
</div> </div>
`; `;
}, },
fields: { fields: {
email: { email: {
'valuePath': 'email', valuePath: 'email',
}, },
firstName: { firstName: {
'key': 'firstName', key: 'firstName',
'selector': '#shipping_first_name_field', selector: '#shipping_first_name_field',
'valuePath': 'shipping.name.firstName', valuePath: 'shipping.name.firstName',
}, },
lastName: { lastName: {
'selector': '#shipping_last_name_field', selector: '#shipping_last_name_field',
'valuePath': 'shipping.name.lastName', valuePath: 'shipping.name.lastName',
}, },
street1: { street1: {
'selector': '#shipping_address_1_field', selector: '#shipping_address_1_field',
'valuePath': 'shipping.address.addressLine1', valuePath: 'shipping.address.addressLine1',
}, },
street2: { street2: {
'selector': '#shipping_address_2_field', selector: '#shipping_address_2_field',
'valuePath': null valuePath: null,
}, },
postCode: { postCode: {
'selector': '#shipping_postcode_field', selector: '#shipping_postcode_field',
'valuePath': 'shipping.address.postalCode', valuePath: 'shipping.address.postalCode',
}, },
city: { city: {
'selector': '#shipping_city_field', selector: '#shipping_city_field',
'valuePath': 'shipping.address.adminArea2', valuePath: 'shipping.address.adminArea2',
}, },
stateCode: { stateCode: {
'selector': '#shipping_state_field', selector: '#shipping_state_field',
'valuePath': 'shipping.address.adminArea1', valuePath: 'shipping.address.adminArea1',
}, },
countryCode: { countryCode: {
'selector': '#shipping_country_field', selector: '#shipping_country_field',
'valuePath': 'shipping.address.countryCode', valuePath: 'shipping.address.countryCode',
}, },
company: { company: {
'selector': '#shipping_company_field', selector: '#shipping_company_field',
'valuePath': null, valuePath: null,
}, },
shipDifferentAddress: { shipDifferentAddress: {
'selector': '#ship-to-different-address', selector: '#ship-to-different-address',
'valuePath': null, valuePath: null,
}, },
phone: { phone: {
//'selector': '#billing_phone_field', // There is no shipping phone field. //'selector': '#billing_phone_field', // There is no shipping phone field.
'valueCallback': function (data) { valueCallback( data ) {
let phone = ''; let phone = '';
const cc = data?.shipping?.phoneNumber?.countryCode; const cc = data?.shipping?.phoneNumber?.countryCode;
const number = data?.shipping?.phoneNumber?.nationalNumber; const number =
data?.shipping?.phoneNumber?.nationalNumber;
if (cc) { if ( cc ) {
phone = `+${cc} `; phone = `+${ cc } `;
} }
phone += number; phone += number;
return phone; return phone;
} },
} },
} },
}); } );
} }
hasEmptyValues(data, stateName) { hasEmptyValues( data, stateName ) {
return !data.value('email') return (
|| !data.value('firstName') ! data.value( 'email' ) ||
|| !data.value('lastName') ! data.value( 'firstName' ) ||
|| !data.value('street1') ! data.value( 'lastName' ) ||
|| !data.value('city') ! data.value( 'street1' ) ||
|| !stateName; ! data.value( 'city' ) ||
} ! stateName
);
}
isActive() { isActive() {
return this.group.active; return this.group.active;
} }
activate() { activate() {
this.group.activate(); this.group.activate();
} }
deactivate() { deactivate() {
this.group.deactivate(); this.group.deactivate();
} }
refresh() { refresh() {
this.group.refresh(); this.group.refresh();
} }
setData(data) { setData( data ) {
this.group.setData(data); this.group.setData( data );
} }
toSubmitData(data) {
return this.group.toSubmitData(data);
}
toSubmitData( data ) {
return this.group.toSubmitData( data );
}
} }
export default ShippingView; export default ShippingView;

View file

@ -1,33 +1,24 @@
import AxoManager from "./AxoManager"; import AxoManager from './AxoManager';
import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
(function ({ ( function ( { axoConfig, ppcpConfig, jQuery } ) {
axoConfig, const bootstrap = () => {
ppcpConfig, new AxoManager( axoConfig, ppcpConfig );
jQuery };
}) {
const bootstrap = () => { document.addEventListener( 'DOMContentLoaded', () => {
new AxoManager(axoConfig, ppcpConfig); if ( ! typeof PayPalCommerceGateway ) {
} console.error( 'AXO could not be configured.' );
return;
}
document.addEventListener( // Load PayPal
'DOMContentLoaded', loadPaypalScript( ppcpConfig, () => {
() => { bootstrap();
if (!typeof (PayPalCommerceGateway)) { } );
console.error('AXO could not be configured.'); } );
return; } )( {
} axoConfig: window.wc_ppcp_axo,
ppcpConfig: window.PayPalCommerceGateway,
// Load PayPal jQuery: window.jQuery,
loadPaypalScript(ppcpConfig, () => { } );
bootstrap();
});
},
);
})({
axoConfig: window.wc_ppcp_axo,
ppcpConfig: window.PayPalCommerceGateway,
jQuery: window.jQuery
});

View file

@ -1,20 +1,20 @@
import * as $ from 'jquery'; import * as $ from 'jquery';
import DomElement from "../Components/DomElement"; import DomElement from '../Components/DomElement';
import FormFieldGroup from "../Components/FormFieldGroup"; import FormFieldGroup from '../Components/FormFieldGroup';
global['$'] = global['jQuery'] = $; global.$ = global.jQuery = $;
test('get dom element selector', () => { test( 'get dom element selector', () => {
const element = new DomElement({selector: '.foo'}); const element = new DomElement( { selector: '.foo' } );
expect(element.selector).toBe('.foo') expect( element.selector ).toBe( '.foo' );
}); } );
test('form field group activate', () => { test( 'form field group activate', () => {
const formFieldGroup = new FormFieldGroup({}); const formFieldGroup = new FormFieldGroup( {} );
expect(formFieldGroup.active).toBe(false) expect( formFieldGroup.active ).toBe( false );
formFieldGroup.activate() formFieldGroup.activate();
expect(formFieldGroup.active).toBe(true) expect( formFieldGroup.active ).toBe( true );
}); } );

View file

@ -140,12 +140,17 @@ class AxoModule implements ModuleInterface {
); );
add_action( add_action(
'init', 'wp_loaded',
function () use ( $c ) { function () use ( $c ) {
$module = $this; $module = $this;
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
// Check if the module is applicable, correct country, currency, ... etc. // Check if the module is applicable, correct country, currency, ... etc.
if ( ! $c->get( 'axo.eligible' ) || 'continuation' === $c->get( 'button.context' ) ) { if ( ! $c->get( 'axo.eligible' )
|| 'continuation' === $c->get( 'button.context' )
|| $subscription_helper->cart_contains_subscription() ) {
return; return;
} }
@ -334,15 +339,11 @@ class AxoModule implements ModuleInterface {
$is_axo_enabled = $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ?? false; $is_axo_enabled = $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ?? false;
$is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false; $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false;
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
return ! is_user_logged_in() return ! is_user_logged_in()
&& CartCheckoutDetector::has_classic_checkout() && CartCheckoutDetector::has_classic_checkout()
&& $is_axo_enabled && $is_axo_enabled
&& $is_dcc_enabled && $is_dcc_enabled
&& ! $this->is_excluded_endpoint() && ! $this->is_excluded_endpoint();
&& ! $subscription_helper->cart_contains_subscription();
} }
/** /**

View file

@ -1,55 +1,62 @@
import MessagesBootstrap from "../../../../ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap"; import MessagesBootstrap from '../../../../ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap';
import {debounce} from "../Helper/debounce"; import { debounce } from '../Helper/debounce';
class BlockCheckoutMessagesBootstrap { class BlockCheckoutMessagesBootstrap {
constructor(scriptData) { constructor( scriptData ) {
this.messagesBootstrap = new MessagesBootstrap(scriptData, null); this.messagesBootstrap = new MessagesBootstrap( scriptData, null );
this.lastCartTotal = null; this.lastCartTotal = null;
} }
init() { init() {
this.messagesBootstrap.init(); this.messagesBootstrap.init();
this._updateCartTotal(); this._updateCartTotal();
if (wp.data?.subscribe) { if ( wp.data?.subscribe ) {
wp.data.subscribe(debounce(() => { wp.data.subscribe(
this._updateCartTotal(); debounce( () => {
}, 300)); this._updateCartTotal();
} }, 300 )
} );
}
}
/** /**
* @private * @private
*/ */
_getCartTotal() { _getCartTotal() {
if (!wp.data.select) { if ( ! wp.data.select ) {
return null; return null;
} }
const cart = wp.data.select('wc/store/cart') const cart = wp.data.select( 'wc/store/cart' );
if (!cart) { if ( ! cart ) {
return null; return null;
} }
const totals = cart.getCartTotals(); const totals = cart.getCartTotals();
return parseInt(totals.total_price, 10) / 10 ** totals.currency_minor_unit; return (
} parseInt( totals.total_price, 10 ) /
10 ** totals.currency_minor_unit
);
}
/** /**
* @private * @private
*/ */
_updateCartTotal() { _updateCartTotal() {
const currentTotal = this._getCartTotal(); const currentTotal = this._getCartTotal();
if (currentTotal === null) { if ( currentTotal === null ) {
return; return;
} }
if (currentTotal !== this.lastCartTotal) { if ( currentTotal !== this.lastCartTotal ) {
this.lastCartTotal = currentTotal; this.lastCartTotal = currentTotal;
jQuery(document.body).trigger('ppcp_block_cart_total_updated', [currentTotal]); jQuery( document.body ).trigger( 'ppcp_block_cart_total_updated', [
} currentTotal,
} ] );
}
}
} }
export default BlockCheckoutMessagesBootstrap; export default BlockCheckoutMessagesBootstrap;

View file

@ -1,87 +1,95 @@
import {useEffect, useState} from '@wordpress/element'; import { useEffect, useState } from '@wordpress/element';
import { import {
PayPalScriptProvider, PayPalScriptProvider,
PayPalCardFieldsProvider, PayPalCardFieldsProvider,
PayPalCardFieldsForm, PayPalCardFieldsForm,
} from "@paypal/react-paypal-js"; } from '@paypal/react-paypal-js';
import {CheckoutHandler} from "./checkout-handler"; import { CheckoutHandler } from './checkout-handler';
import {createOrder, onApprove} from "../card-fields-config"; import { createOrder, onApprove } from '../card-fields-config';
import {cartHasSubscriptionProducts} from "../Helper/Subscription"; import { cartHasSubscriptionProducts } from '../Helper/Subscription';
export function CardFields({config, eventRegistration, emitResponse, components}) { export function CardFields( {
const {onPaymentSetup} = eventRegistration; config,
const {responseTypes} = emitResponse; eventRegistration,
const { PaymentMethodIcons } = components; emitResponse,
components,
} ) {
const { onPaymentSetup } = eventRegistration;
const { responseTypes } = emitResponse;
const { PaymentMethodIcons } = components;
const [cardFieldsForm, setCardFieldsForm] = useState(); const [ cardFieldsForm, setCardFieldsForm ] = useState();
const getCardFieldsForm = (cardFieldsForm) => { const getCardFieldsForm = ( cardFieldsForm ) => {
setCardFieldsForm(cardFieldsForm) setCardFieldsForm( cardFieldsForm );
} };
const getSavePayment = (savePayment) => { const getSavePayment = ( savePayment ) => {
localStorage.setItem('ppcp-save-card-payment', savePayment); localStorage.setItem( 'ppcp-save-card-payment', savePayment );
} };
const hasSubscriptionProducts = cartHasSubscriptionProducts(config.scriptData); const hasSubscriptionProducts = cartHasSubscriptionProducts(
useEffect(() => { config.scriptData
localStorage.removeItem('ppcp-save-card-payment'); );
useEffect( () => {
localStorage.removeItem( 'ppcp-save-card-payment' );
if(hasSubscriptionProducts) { if ( hasSubscriptionProducts ) {
localStorage.setItem('ppcp-save-card-payment', 'true'); localStorage.setItem( 'ppcp-save-card-payment', 'true' );
} }
}, [ hasSubscriptionProducts ] );
}, [hasSubscriptionProducts]) useEffect(
() =>
onPaymentSetup( () => {
async function handlePaymentProcessing() {
await cardFieldsForm.submit().catch( ( error ) => {
return {
type: responseTypes.ERROR,
};
} );
useEffect( return {
() => type: responseTypes.SUCCESS,
onPaymentSetup(() => { };
async function handlePaymentProcessing() { }
await cardFieldsForm.submit()
.catch((error) => {
return {
type: responseTypes.ERROR,
}
});
return { return handlePaymentProcessing();
type: responseTypes.SUCCESS, } ),
} [ onPaymentSetup, cardFieldsForm ]
} );
return handlePaymentProcessing(); return (
}), <>
[onPaymentSetup, cardFieldsForm] <PayPalScriptProvider
); options={ {
clientId: config.scriptData.client_id,
return ( components: 'card-fields',
<> dataNamespace: 'ppcp-block-card-fields',
<PayPalScriptProvider } }
options={{ >
clientId: config.scriptData.client_id, <PayPalCardFieldsProvider
components: "card-fields", createOrder={ createOrder }
dataNamespace: 'ppcp-block-card-fields', onApprove={ onApprove }
}} onError={ ( err ) => {
> console.error( err );
<PayPalCardFieldsProvider } }
createOrder={createOrder} >
onApprove={onApprove} <PayPalCardFieldsForm />
onError={(err) => { <PaymentMethodIcons
console.error(err); icons={ config.card_icons }
}} align="left"
> />
<PayPalCardFieldsForm/> <CheckoutHandler
<PaymentMethodIcons icons={config.card_icons} align="left" /> getCardFieldsForm={ getCardFieldsForm }
<CheckoutHandler getSavePayment={ getSavePayment }
getCardFieldsForm={getCardFieldsForm} hasSubscriptionProducts={ hasSubscriptionProducts }
getSavePayment={getSavePayment} saveCardText={ config.save_card_text }
hasSubscriptionProducts={hasSubscriptionProducts} is_vaulting_enabled={ config.is_vaulting_enabled }
saveCardText={config.save_card_text} />
is_vaulting_enabled={config.is_vaulting_enabled} </PayPalCardFieldsProvider>
/> </PayPalScriptProvider>
</PayPalCardFieldsProvider> </>
</PayPalScriptProvider> );
</>
)
} }

View file

@ -1,28 +1,34 @@
import {useEffect} from '@wordpress/element'; import { useEffect } from '@wordpress/element';
import {usePayPalCardFields} from "@paypal/react-paypal-js"; import { usePayPalCardFields } from '@paypal/react-paypal-js';
export const CheckoutHandler = ({getCardFieldsForm, getSavePayment, hasSubscriptionProducts, saveCardText, is_vaulting_enabled}) => { export const CheckoutHandler = ( {
const {cardFieldsForm} = usePayPalCardFields(); getCardFieldsForm,
getSavePayment,
hasSubscriptionProducts,
saveCardText,
is_vaulting_enabled,
} ) => {
const { cardFieldsForm } = usePayPalCardFields();
useEffect(() => { useEffect( () => {
getCardFieldsForm(cardFieldsForm) getCardFieldsForm( cardFieldsForm );
}, []); }, [] );
if (!is_vaulting_enabled) { if ( ! is_vaulting_enabled ) {
return null; return null;
} }
return ( return (
<> <>
<input <input
type="checkbox" type="checkbox"
id="save" id="save"
name="save" name="save"
onChange={(e) => getSavePayment(e.target.checked)} onChange={ ( e ) => getSavePayment( e.target.checked ) }
defaultChecked={hasSubscriptionProducts} defaultChecked={ hasSubscriptionProducts }
disabled={hasSubscriptionProducts} disabled={ hasSubscriptionProducts }
/> />
<label htmlFor="save">{saveCardText}</label> <label htmlFor="save">{ saveCardText }</label>
</> </>
) );
} };

View file

@ -1,171 +1,179 @@
/** /**
* @param {String} fullName * @param {string} fullName
* @returns {Array} * @return {Array}
*/ */
export const splitFullName = (fullName) => { export const splitFullName = ( fullName ) => {
fullName = fullName.trim() fullName = fullName.trim();
if (!fullName.includes(' ')) { if ( ! fullName.includes( ' ' ) ) {
return [fullName, '']; return [ fullName, '' ];
} }
const parts = fullName.split(' '); const parts = fullName.split( ' ' );
const firstName = parts[0]; const firstName = parts[ 0 ];
parts.shift(); parts.shift();
const lastName = parts.join(' '); const lastName = parts.join( ' ' );
return [firstName, lastName]; return [ firstName, lastName ];
} };
/** /**
* @param {Object} address * @param {Object} address
* @returns {Object} * @return {Object}
*/ */
export const paypalAddressToWc = (address) => { export const paypalAddressToWc = ( address ) => {
let map = { let map = {
country_code: 'country', country_code: 'country',
address_line_1: 'address_1', address_line_1: 'address_1',
address_line_2: 'address_2', address_line_2: 'address_2',
admin_area_1: 'state', admin_area_1: 'state',
admin_area_2: 'city', admin_area_2: 'city',
postal_code: 'postcode', postal_code: 'postcode',
}; };
if (address.city) { // address not from API, such as onShippingChange if ( address.city ) {
map = { // address not from API, such as onShippingChange
country_code: 'country', map = {
state: 'state', country_code: 'country',
city: 'city', state: 'state',
postal_code: 'postcode', city: 'city',
}; postal_code: 'postcode',
} };
const result = {}; }
Object.entries(map).forEach(([paypalKey, wcKey]) => { const result = {};
if (address[paypalKey]) { Object.entries( map ).forEach( ( [ paypalKey, wcKey ] ) => {
result[wcKey] = address[paypalKey]; if ( address[ paypalKey ] ) {
} result[ wcKey ] = address[ paypalKey ];
}); }
} );
const defaultAddress = { const defaultAddress = {
first_name: '', first_name: '',
last_name: '', last_name: '',
company: '', company: '',
address_1: '', address_1: '',
address_2: '', address_2: '',
city: '', city: '',
state: '', state: '',
postcode: '', postcode: '',
country: '', country: '',
phone: '', phone: '',
}; };
return {...defaultAddress, ...result}; return { ...defaultAddress, ...result };
} };
/** /**
* @param {Object} shipping * @param {Object} shipping
* @returns {Object} * @return {Object}
*/ */
export const paypalShippingToWc = (shipping) => { export const paypalShippingToWc = ( shipping ) => {
const [firstName, lastName] = (shipping.name ? splitFullName(shipping.name.full_name) : ['','']); const [ firstName, lastName ] = shipping.name
return { ? splitFullName( shipping.name.full_name )
...paypalAddressToWc(shipping.address), : [ '', '' ];
first_name: firstName, return {
last_name: lastName, ...paypalAddressToWc( shipping.address ),
} first_name: firstName,
} last_name: lastName,
};
};
/** /**
* @param {Object} payer * @param {Object} payer
* @returns {Object} * @return {Object}
*/ */
export const paypalPayerToWc = (payer) => { export const paypalPayerToWc = ( payer ) => {
const firstName = payer?.name?.given_name ?? ''; const firstName = payer?.name?.given_name ?? '';
const lastName = payer?.name?.surname ?? ''; const lastName = payer?.name?.surname ?? '';
const address = payer.address ? paypalAddressToWc(payer.address) : {}; const address = payer.address ? paypalAddressToWc( payer.address ) : {};
return { return {
...address, ...address,
first_name: firstName, first_name: firstName,
last_name: lastName, last_name: lastName,
email: payer.email_address, email: payer.email_address,
} };
} };
/** /**
* @param {Object} subscriber * @param {Object} subscriber
* @returns {Object} * @return {Object}
*/ */
export const paypalSubscriberToWc = (subscriber) => { export const paypalSubscriberToWc = ( subscriber ) => {
const firstName = subscriber?.name?.given_name ?? ''; const firstName = subscriber?.name?.given_name ?? '';
const lastName = subscriber?.name?.surname ?? ''; const lastName = subscriber?.name?.surname ?? '';
const address = subscriber.address ? paypalAddressToWc(subscriber.shipping_address.address) : {}; const address = subscriber.address
return { ? paypalAddressToWc( subscriber.shipping_address.address )
...address, : {};
first_name: firstName, return {
last_name: lastName, ...address,
email: subscriber.email_address, first_name: firstName,
} last_name: lastName,
} email: subscriber.email_address,
};
};
/** /**
* @param {Object} order * @param {Object} order
* @returns {Object} * @return {Object}
*/ */
export const paypalOrderToWcShippingAddress = (order) => { export const paypalOrderToWcShippingAddress = ( order ) => {
const shipping = order.purchase_units[0].shipping; const shipping = order.purchase_units[ 0 ].shipping;
if (!shipping) { if ( ! shipping ) {
return {}; return {};
} }
const res = paypalShippingToWc(shipping); const res = paypalShippingToWc( shipping );
// use the name from billing if the same, to avoid possible mistakes when splitting full_name // use the name from billing if the same, to avoid possible mistakes when splitting full_name
if (order.payer) { if ( order.payer ) {
const billingAddress = paypalPayerToWc(order.payer); const billingAddress = paypalPayerToWc( order.payer );
if (`${res.first_name} ${res.last_name}` === `${billingAddress.first_name} ${billingAddress.last_name}`) { if (
res.first_name = billingAddress.first_name; `${ res.first_name } ${ res.last_name }` ===
res.last_name = billingAddress.last_name; `${ billingAddress.first_name } ${ billingAddress.last_name }`
} ) {
} res.first_name = billingAddress.first_name;
res.last_name = billingAddress.last_name;
}
}
return res; return res;
} };
/** /**
* *
* @param order * @param order
* @returns {{shippingAddress: Object, billingAddress: Object}} * @return {{shippingAddress: Object, billingAddress: Object}}
*/ */
export const paypalOrderToWcAddresses = (order) => { export const paypalOrderToWcAddresses = ( order ) => {
const shippingAddress = paypalOrderToWcShippingAddress(order); const shippingAddress = paypalOrderToWcShippingAddress( order );
let billingAddress = shippingAddress; let billingAddress = shippingAddress;
if (order.payer) { if ( order.payer ) {
billingAddress = paypalPayerToWc(order.payer); billingAddress = paypalPayerToWc( order.payer );
// no billing address, such as if billing address retrieval is not allowed in the merchant account // no billing address, such as if billing address retrieval is not allowed in the merchant account
if (!billingAddress.address_line_1) { if ( ! billingAddress.address_line_1 ) {
// use only non empty values from payer address, otherwise it will override shipping address // use only non empty values from payer address, otherwise it will override shipping address
let payerAddress = Object.fromEntries( const payerAddress = Object.fromEntries(
Object.entries(billingAddress).filter( Object.entries( billingAddress ).filter(
([key, value]) => value !== '' && key !== 'country' ( [ key, value ] ) => value !== '' && key !== 'country'
) )
); );
billingAddress = { billingAddress = {
...shippingAddress, ...shippingAddress,
...payerAddress ...payerAddress,
}; };
} }
} }
return {billingAddress, shippingAddress}; return { billingAddress, shippingAddress };
} };
/** /**
* *
* @param subscription * @param subscription
* @returns {{shippingAddress: Object, billingAddress: Object}} * @return {{shippingAddress: Object, billingAddress: Object}}
*/ */
export const paypalSubscriptionToWcAddresses = (subscription) => { export const paypalSubscriptionToWcAddresses = ( subscription ) => {
const shippingAddress = paypalSubscriberToWc(subscription.subscriber); const shippingAddress = paypalSubscriberToWc( subscription.subscriber );
let billingAddress = shippingAddress; const billingAddress = shippingAddress;
return {billingAddress, shippingAddress}; return { billingAddress, shippingAddress };
} };
/** /**
* Merges two WC addresses. * Merges two WC addresses.
@ -173,22 +181,28 @@ export const paypalSubscriptionToWcAddresses = (subscription) => {
* *
* @param {Object} address1 * @param {Object} address1
* @param {Object} address2 * @param {Object} address2
* @returns {any} * @return {any}
*/ */
export const mergeWcAddress = (address1, address2) => { export const mergeWcAddress = ( address1, address2 ) => {
if ('billingAddress' in address1) { if ( 'billingAddress' in address1 ) {
return { return {
billingAddress: mergeWcAddress(address1.billingAddress, address2.billingAddress), billingAddress: mergeWcAddress(
shippingAddress: mergeWcAddress(address1.shippingAddress, address2.shippingAddress), address1.billingAddress,
} address2.billingAddress
} ),
shippingAddress: mergeWcAddress(
address1.shippingAddress,
address2.shippingAddress
),
};
}
let address2WithoutEmpty = {...address2}; const address2WithoutEmpty = { ...address2 };
Object.keys(address2).forEach(key => { Object.keys( address2 ).forEach( ( key ) => {
if (address2[key] === '') { if ( address2[ key ] === '' ) {
delete address2WithoutEmpty[key]; delete address2WithoutEmpty[ key ];
} }
}); } );
return {...address1, ...address2WithoutEmpty}; return { ...address1, ...address2WithoutEmpty };
} };

View file

@ -1,22 +1,24 @@
/** /**
* @param str * @param str
* @returns {string} * @return {string}
*/ */
export const toSnakeCase = (str) => { export const toSnakeCase = ( str ) => {
return str.replace(/[\w]([A-Z])/g, function(m) { return str
return m[0] + "_" + m[1]; .replace( /[\w]([A-Z])/g, function ( m ) {
}).toLowerCase(); return m[ 0 ] + '_' + m[ 1 ];
} } )
.toLowerCase();
};
/** /**
* @param obj * @param obj
* @returns {{}} * @return {{}}
*/ */
export const convertKeysToSnakeCase = (obj) => { export const convertKeysToSnakeCase = ( obj ) => {
const newObj = {}; const newObj = {};
Object.keys(obj).forEach((key) => { Object.keys( obj ).forEach( ( key ) => {
const newKey = toSnakeCase(key); const newKey = toSnakeCase( key );
newObj[newKey] = obj[key]; newObj[ newKey ] = obj[ key ];
}); } );
return newObj; return newObj;
} };

View file

@ -1,16 +1,18 @@
/** /**
* @param {Object} scriptData * @param {Object} scriptData
* @returns {Boolean} * @return {boolean}
*/ */
export const isPayPalSubscription = (scriptData) => { export const isPayPalSubscription = ( scriptData ) => {
return scriptData.data_client_id.has_subscriptions return (
&& scriptData.data_client_id.paypal_subscriptions_enabled; scriptData.data_client_id.has_subscriptions &&
} scriptData.data_client_id.paypal_subscriptions_enabled
);
};
/** /**
* @param {Object} scriptData * @param {Object} scriptData
* @returns {Boolean} * @return {boolean}
*/ */
export const cartHasSubscriptionProducts = (scriptData) => { export const cartHasSubscriptionProducts = ( scriptData ) => {
return !! scriptData?.locations_with_subscription_product?.cart; return !! scriptData?.locations_with_subscription_product?.cart;
} };

View file

@ -1,9 +1,9 @@
export const debounce = (callback, delayMs) => { export const debounce = ( callback, delayMs ) => {
let timeoutId = null; let timeoutId = null;
return (...args) => { return ( ...args ) => {
window.clearTimeout(timeoutId); window.clearTimeout( timeoutId );
timeoutId = window.setTimeout(() => { timeoutId = window.setTimeout( () => {
callback.apply(null, args); callback.apply( null, args );
}, delayMs); }, delayMs );
}; };
}; };

View file

@ -1,17 +1,19 @@
import { registerPaymentMethod } from '@woocommerce/blocks-registry'; import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import {CardFields} from "./Components/card-fields"; import { CardFields } from './Components/card-fields';
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
registerPaymentMethod({ registerPaymentMethod( {
name: config.id, name: config.id,
label: <div dangerouslySetInnerHTML={{__html: config.title}}/>, label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
content: <CardFields config={config}/>, content: <CardFields config={ config } />,
edit: <div></div>, edit: <div></div>,
ariaLabel: config.title, ariaLabel: config.title,
canMakePayment: () => {return true}, canMakePayment: () => {
supports: { return true;
showSavedCards: true, },
features: config.supports supports: {
} showSavedCards: true,
}) features: config.supports,
},
} );

View file

@ -1,45 +1,46 @@
export async function createOrder() { export async function createOrder() {
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
return fetch(config.scriptData.ajax.create_order.endpoint, { return fetch( config.scriptData.ajax.create_order.endpoint, {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify( {
nonce: config.scriptData.ajax.create_order.nonce, nonce: config.scriptData.ajax.create_order.nonce,
context: config.scriptData.context, context: config.scriptData.context,
payment_method: 'ppcp-credit-card-gateway', payment_method: 'ppcp-credit-card-gateway',
save_payment_method: localStorage.getItem('ppcp-save-card-payment') === 'true', save_payment_method:
}), localStorage.getItem( 'ppcp-save-card-payment' ) === 'true',
}) } ),
.then((response) => response.json()) } )
.then((order) => { .then( ( response ) => response.json() )
return order.data.id; .then( ( order ) => {
}) return order.data.id;
.catch((err) => { } )
console.error(err); .catch( ( err ) => {
}); console.error( err );
} );
} }
export async function onApprove(data) { export async function onApprove( data ) {
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
return fetch(config.scriptData.ajax.approve_order.endpoint, { return fetch( config.scriptData.ajax.approve_order.endpoint, {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify( {
order_id: data.orderID, order_id: data.orderID,
nonce: config.scriptData.ajax.approve_order.nonce, nonce: config.scriptData.ajax.approve_order.nonce,
}), } ),
}) } )
.then((response) => response.json()) .then( ( response ) => response.json() )
.then((data) => { .then( ( data ) => {
localStorage.removeItem('ppcp-save-card-payment'); localStorage.removeItem( 'ppcp-save-card-payment' );
}) } )
.catch((err) => { .catch( ( err ) => {
console.error(err); console.error( err );
}); } );
} }

File diff suppressed because it is too large Load diff

View file

@ -1,34 +1,34 @@
import {render, screen} from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import {CheckoutHandler} from "../Components/checkout-handler"; import { CheckoutHandler } from '../Components/checkout-handler';
test('checkbox label displays the given text', async () => { test( 'checkbox label displays the given text', async () => {
render( render(
<CheckoutHandler <CheckoutHandler
getCardFieldsForm={() => {}} getCardFieldsForm={ () => {} }
saveCardText="Foo" saveCardText="Foo"
is_vaulting_enabled={true} is_vaulting_enabled={ true }
/> />
); );
await expect(screen.getByLabelText('Foo')).toBeInTheDocument(); await expect( screen.getByLabelText( 'Foo' ) ).toBeInTheDocument();
}); } );
test('click checkbox calls function passing checked value', async () => { test( 'click checkbox calls function passing checked value', async () => {
const getSavePayment = jest.fn(); const getSavePayment = jest.fn();
render( render(
<CheckoutHandler <CheckoutHandler
getSavePayment={getSavePayment} getSavePayment={ getSavePayment }
getCardFieldsForm={() => {}} getCardFieldsForm={ () => {} }
saveCardText="Foo" saveCardText="Foo"
is_vaulting_enabled={true} is_vaulting_enabled={ true }
/> />
); );
await userEvent.click(screen.getByLabelText('Foo')); await userEvent.click( screen.getByLabelText( 'Foo' ) );
await expect(getSavePayment.mock.calls).toHaveLength(1); await expect( getSavePayment.mock.calls ).toHaveLength( 1 );
await expect(getSavePayment.mock.calls[0][0]).toBe(true); await expect( getSavePayment.mock.calls[ 0 ][ 0 ] ).toBe( true );
}); } );

View file

@ -52,7 +52,7 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
* *
* @var Settings * @var Settings
*/ */
protected $settings; protected $plugin_settings;
/** /**
* AdvancedCardPaymentMethod constructor. * AdvancedCardPaymentMethod constructor.
@ -70,12 +70,12 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
$smart_button, $smart_button,
Settings $settings Settings $settings
) { ) {
$this->name = CreditCardGateway::ID; $this->name = CreditCardGateway::ID;
$this->module_url = $module_url; $this->module_url = $module_url;
$this->version = $version; $this->version = $version;
$this->gateway = $gateway; $this->gateway = $gateway;
$this->smart_button = $smart_button; $this->smart_button = $smart_button;
$this->settings = $settings; $this->plugin_settings = $settings;
} }
/** /**
@ -118,8 +118,8 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
'scriptData' => $script_data, 'scriptData' => $script_data,
'supports' => $this->gateway->supports, 'supports' => $this->gateway->supports,
'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ), 'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ),
'is_vaulting_enabled' => $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ), 'is_vaulting_enabled' => $this->plugin_settings->has( 'vault_enabled_dcc' ) && $this->plugin_settings->get( 'vault_enabled_dcc' ),
'card_icons' => $this->settings->has( 'card_icons' ) ? (array) $this->settings->get( 'card_icons' ) : array(), 'card_icons' => $this->plugin_settings->has( 'card_icons' ) ? (array) $this->plugin_settings->get( 'card_icons' ) : array(),
); );
} }

View file

@ -22,7 +22,8 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
* Class UpdateShippingEndpoint * Class UpdateShippingEndpoint
*/ */
class UpdateShippingEndpoint implements EndpointInterface { class UpdateShippingEndpoint implements EndpointInterface {
const ENDPOINT = 'ppc-update-shipping'; const ENDPOINT = 'ppc-update-shipping';
const WC_STORE_API_ENDPOINT = '/wp-json/wc/store/cart/';
/** /**
* The Request Data Helper. * The Request Data Helper.

View file

@ -2,329 +2,414 @@ import MiniCartBootstap from './modules/ContextBootstrap/MiniCartBootstap';
import SingleProductBootstap from './modules/ContextBootstrap/SingleProductBootstap'; import SingleProductBootstap from './modules/ContextBootstrap/SingleProductBootstap';
import CartBootstrap from './modules/ContextBootstrap/CartBootstap'; import CartBootstrap from './modules/ContextBootstrap/CartBootstap';
import CheckoutBootstap from './modules/ContextBootstrap/CheckoutBootstap'; import CheckoutBootstap from './modules/ContextBootstrap/CheckoutBootstap';
import PayNowBootstrap from "./modules/ContextBootstrap/PayNowBootstrap"; import PayNowBootstrap from './modules/ContextBootstrap/PayNowBootstrap';
import Renderer from './modules/Renderer/Renderer'; import Renderer from './modules/Renderer/Renderer';
import ErrorHandler from './modules/ErrorHandler'; import ErrorHandler from './modules/ErrorHandler';
import HostedFieldsRenderer from "./modules/Renderer/HostedFieldsRenderer"; import HostedFieldsRenderer from './modules/Renderer/HostedFieldsRenderer';
import CardFieldsRenderer from "./modules/Renderer/CardFieldsRenderer"; import CardFieldsRenderer from './modules/Renderer/CardFieldsRenderer';
import MessageRenderer from "./modules/Renderer/MessageRenderer"; import MessageRenderer from './modules/Renderer/MessageRenderer';
import Spinner from "./modules/Helper/Spinner"; import Spinner from './modules/Helper/Spinner';
import { import {
getCurrentPaymentMethod, getCurrentPaymentMethod,
ORDER_BUTTON_SELECTOR, ORDER_BUTTON_SELECTOR,
PaymentMethods PaymentMethods,
} from "./modules/Helper/CheckoutMethodState"; } from './modules/Helper/CheckoutMethodState';
import {setVisibleByClass} from "./modules/Helper/Hiding"; import { setVisibleByClass } from './modules/Helper/Hiding';
import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; import { isChangePaymentPage } from './modules/Helper/Subscriptions';
import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler"; import FreeTrialHandler from './modules/ActionHandler/FreeTrialHandler';
import MultistepCheckoutHelper from "./modules/Helper/MultistepCheckoutHelper"; import MultistepCheckoutHelper from './modules/Helper/MultistepCheckoutHelper';
import FormSaver from './modules/Helper/FormSaver'; import FormSaver from './modules/Helper/FormSaver';
import FormValidator from "./modules/Helper/FormValidator"; import FormValidator from './modules/Helper/FormValidator';
import {loadPaypalScript} from "./modules/Helper/ScriptLoading"; import { loadPaypalScript } from './modules/Helper/ScriptLoading';
import buttonModuleWatcher from "./modules/ButtonModuleWatcher"; import buttonModuleWatcher from './modules/ButtonModuleWatcher';
import MessagesBootstrap from "./modules/ContextBootstrap/MessagesBootstap"; import MessagesBootstrap from './modules/ContextBootstrap/MessagesBootstap';
import {apmButtonsInit} from "./modules/Helper/ApmButtons"; import { apmButtonsInit } from './modules/Helper/ApmButtons';
// TODO: could be a good idea to have a separate spinner for each gateway, // 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. // but I think we care mainly about the script loading, so one spinner should be enough.
const buttonsSpinner = new Spinner(document.querySelector('.ppc-button-wrapper')); const buttonsSpinner = new Spinner(
const cardsSpinner = new Spinner('#ppcp-hosted-fields'); document.querySelector( '.ppc-button-wrapper' )
);
const cardsSpinner = new Spinner( '#ppcp-hosted-fields' );
const bootstrap = () => { const bootstrap = () => {
const checkoutFormSelector = 'form.woocommerce-checkout'; const checkoutFormSelector = 'form.woocommerce-checkout';
const context = PayPalCommerceGateway.context; const context = PayPalCommerceGateway.context;
const errorHandler = new ErrorHandler( const errorHandler = new ErrorHandler(
PayPalCommerceGateway.labels.error.generic, PayPalCommerceGateway.labels.error.generic,
document.querySelector(checkoutFormSelector) ?? document.querySelector('.woocommerce-notices-wrapper') document.querySelector( checkoutFormSelector ) ??
); document.querySelector( '.woocommerce-notices-wrapper' )
const spinner = new Spinner(); );
const spinner = new Spinner();
const formSaver = new FormSaver( const formSaver = new FormSaver(
PayPalCommerceGateway.ajax.save_checkout_form.endpoint, PayPalCommerceGateway.ajax.save_checkout_form.endpoint,
PayPalCommerceGateway.ajax.save_checkout_form.nonce, PayPalCommerceGateway.ajax.save_checkout_form.nonce
); );
const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ? const formValidator =
new FormValidator( PayPalCommerceGateway.early_checkout_validation_enabled
PayPalCommerceGateway.ajax.validate_checkout.endpoint, ? new FormValidator(
PayPalCommerceGateway.ajax.validate_checkout.nonce, PayPalCommerceGateway.ajax.validate_checkout.endpoint,
) : null; PayPalCommerceGateway.ajax.validate_checkout.nonce
)
: null;
const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, formValidator, spinner, errorHandler); const freeTrialHandler = new FreeTrialHandler(
PayPalCommerceGateway,
checkoutFormSelector,
formSaver,
formValidator,
spinner,
errorHandler
);
new MultistepCheckoutHelper(checkoutFormSelector); new MultistepCheckoutHelper( checkoutFormSelector );
jQuery('form.woocommerce-checkout input').on('keydown', e => { jQuery( 'form.woocommerce-checkout input' ).on( 'keydown', ( e ) => {
if (e.key === 'Enter' && [ if (
PaymentMethods.PAYPAL, e.key === 'Enter' &&
PaymentMethods.CARDS, [
PaymentMethods.CARD_BUTTON, PaymentMethods.PAYPAL,
].includes(getCurrentPaymentMethod())) { PaymentMethods.CARDS,
e.preventDefault(); PaymentMethods.CARD_BUTTON,
} ].includes( getCurrentPaymentMethod() )
}); ) {
e.preventDefault();
}
} );
const hasMessages = () => { const hasMessages = () => {
return PayPalCommerceGateway.messages.is_hidden === false return (
&& document.querySelector(PayPalCommerceGateway.messages.wrapper); PayPalCommerceGateway.messages.is_hidden === false &&
} document.querySelector( PayPalCommerceGateway.messages.wrapper )
);
};
const doBasicCheckoutValidation = () => { const doBasicCheckoutValidation = () => {
if (PayPalCommerceGateway.basic_checkout_validation_enabled) { if ( PayPalCommerceGateway.basic_checkout_validation_enabled ) {
// A quick fix to get the errors about empty form fields before attempting PayPal order, // A quick fix to get the errors about empty form fields before attempting PayPal order,
// it should solve #513 for most of the users, but it is not a proper solution. // it should solve #513 for most of the users, but it is not a proper solution.
// Currently it is disabled by default because a better solution is now implemented // Currently it is disabled by default because a better solution is now implemented
// (see woocommerce_paypal_payments_basic_checkout_validation_enabled, // (see woocommerce_paypal_payments_basic_checkout_validation_enabled,
// woocommerce_paypal_payments_early_wc_checkout_validation_enabled filters). // woocommerce_paypal_payments_early_wc_checkout_validation_enabled filters).
const invalidFields = Array.from(jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible')); const invalidFields = Array.from(
if (invalidFields.length) { jQuery(
const billingFieldsContainer = document.querySelector('.woocommerce-billing-fields'); 'form.woocommerce-checkout .validate-required.woocommerce-invalid:visible'
const shippingFieldsContainer = document.querySelector('.woocommerce-shipping-fields'); )
);
if ( invalidFields.length ) {
const billingFieldsContainer = document.querySelector(
'.woocommerce-billing-fields'
);
const shippingFieldsContainer = document.querySelector(
'.woocommerce-shipping-fields'
);
const nameMessageMap = PayPalCommerceGateway.labels.error.required.elements; const nameMessageMap =
const messages = invalidFields.map(el => { PayPalCommerceGateway.labels.error.required.elements;
const name = el.querySelector('[name]')?.getAttribute('name'); const messages = invalidFields
if (name && name in nameMessageMap) { .map( ( el ) => {
return nameMessageMap[name]; const name = el
} .querySelector( '[name]' )
let label = el.querySelector('label').textContent ?.getAttribute( 'name' );
.replaceAll('*', '') if ( name && name in nameMessageMap ) {
.trim(); return nameMessageMap[ name ];
if (billingFieldsContainer?.contains(el)) { }
label = PayPalCommerceGateway.labels.billing_field.replace('%s', label); let label = el
} .querySelector( 'label' )
if (shippingFieldsContainer?.contains(el)) { .textContent.replaceAll( '*', '' )
label = PayPalCommerceGateway.labels.shipping_field.replace('%s', label); .trim();
} if ( billingFieldsContainer?.contains( el ) ) {
return PayPalCommerceGateway.labels.error.required.field label =
.replace('%s', `<strong>${label}</strong>`) PayPalCommerceGateway.labels.billing_field.replace(
}).filter(s => s.length > 2); '%s',
label
);
}
if ( shippingFieldsContainer?.contains( el ) ) {
label =
PayPalCommerceGateway.labels.shipping_field.replace(
'%s',
label
);
}
return PayPalCommerceGateway.labels.error.required.field.replace(
'%s',
`<strong>${ label }</strong>`
);
} )
.filter( ( s ) => s.length > 2 );
errorHandler.clear(); errorHandler.clear();
if (messages.length) { if ( messages.length ) {
errorHandler.messages(messages); errorHandler.messages( messages );
} else { } else {
errorHandler.message(PayPalCommerceGateway.labels.error.required.generic); errorHandler.message(
} PayPalCommerceGateway.labels.error.required.generic
);
}
return false; return false;
} }
} }
return true; return true;
}; };
const onCardFieldsBeforeSubmit = () => { const onCardFieldsBeforeSubmit = () => {
return doBasicCheckoutValidation(); return doBasicCheckoutValidation();
}; };
const onSmartButtonClick = async (data, actions) => { const onSmartButtonClick = async ( data, actions ) => {
window.ppcpFundingSource = data.fundingSource; window.ppcpFundingSource = data.fundingSource;
const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input'); const requiredFields = jQuery(
requiredFields.each((i, input) => { 'form.woocommerce-checkout .validate-required:visible :input'
jQuery(input).trigger('validate'); );
}); requiredFields.each( ( i, input ) => {
jQuery( input ).trigger( 'validate' );
} );
if (!doBasicCheckoutValidation()) { if ( ! doBasicCheckoutValidation() ) {
return actions.reject(); return actions.reject();
} }
const form = document.querySelector(checkoutFormSelector); const form = document.querySelector( checkoutFormSelector );
if (form) { if ( form ) {
jQuery('#ppcp-funding-source-form-input').remove(); jQuery( '#ppcp-funding-source-form-input' ).remove();
form.insertAdjacentHTML( form.insertAdjacentHTML(
'beforeend', 'beforeend',
`<input type="hidden" name="ppcp-funding-source" value="${data.fundingSource}" id="ppcp-funding-source-form-input">` `<input type="hidden" name="ppcp-funding-source" value="${ data.fundingSource }" id="ppcp-funding-source-form-input">`
) );
} }
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
if ( if (
isFreeTrial isFreeTrial &&
&& data.fundingSource !== 'card' data.fundingSource !== 'card' &&
&& ! PayPalCommerceGateway.subscription_plan_id ! PayPalCommerceGateway.subscription_plan_id &&
&& ! PayPalCommerceGateway.vault_v3_enabled ! PayPalCommerceGateway.vault_v3_enabled
) { ) {
freeTrialHandler.handle(); freeTrialHandler.handle();
return actions.reject(); return actions.reject();
} }
if (context === 'checkout') { if ( context === 'checkout' ) {
try { try {
await formSaver.save(form); await formSaver.save( form );
} catch (error) { } catch ( error ) {
console.error(error); console.error( error );
} }
} }
}; };
const onSmartButtonsInit = () => { const onSmartButtonsInit = () => {
jQuery(document).trigger('ppcp-smart-buttons-init', this); jQuery( document ).trigger( 'ppcp-smart-buttons-init', this );
buttonsSpinner.unblock(); buttonsSpinner.unblock();
}; };
let creditCardRenderer = new HostedFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner); let creditCardRenderer = new HostedFieldsRenderer(
if (typeof paypal.CardFields !== 'undefined') { PayPalCommerceGateway,
creditCardRenderer = new CardFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner, onCardFieldsBeforeSubmit); errorHandler,
} spinner
);
if ( typeof paypal.CardFields !== 'undefined' ) {
creditCardRenderer = new CardFieldsRenderer(
PayPalCommerceGateway,
errorHandler,
spinner,
onCardFieldsBeforeSubmit
);
}
const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit); const renderer = new Renderer(
const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages); creditCardRenderer,
PayPalCommerceGateway,
onSmartButtonClick,
onSmartButtonsInit
);
const messageRenderer = new MessageRenderer(
PayPalCommerceGateway.messages
);
if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') { if ( PayPalCommerceGateway.mini_cart_buttons_enabled === '1' ) {
const miniCartBootstrap = new MiniCartBootstap( const miniCartBootstrap = new MiniCartBootstap(
PayPalCommerceGateway, PayPalCommerceGateway,
renderer, renderer,
errorHandler, errorHandler
); );
miniCartBootstrap.init(); miniCartBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('mini-cart', miniCartBootstrap); buttonModuleWatcher.registerContextBootstrap(
} 'mini-cart',
miniCartBootstrap
);
}
if ( if (
context === 'product' context === 'product' &&
&& ( ( PayPalCommerceGateway.single_product_buttons_enabled === '1' ||
PayPalCommerceGateway.single_product_buttons_enabled === '1' hasMessages() )
|| hasMessages() ) {
) const singleProductBootstrap = new SingleProductBootstap(
) { PayPalCommerceGateway,
const singleProductBootstrap = new SingleProductBootstap( renderer,
PayPalCommerceGateway, errorHandler
renderer, );
errorHandler,
);
singleProductBootstrap.init(); singleProductBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('product', singleProductBootstrap); buttonModuleWatcher.registerContextBootstrap(
} 'product',
singleProductBootstrap
);
}
if (context === 'cart') { if ( context === 'cart' ) {
const cartBootstrap = new CartBootstrap( const cartBootstrap = new CartBootstrap(
PayPalCommerceGateway, PayPalCommerceGateway,
renderer, renderer,
errorHandler, errorHandler
); );
cartBootstrap.init(); cartBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('cart', cartBootstrap); buttonModuleWatcher.registerContextBootstrap( 'cart', cartBootstrap );
} }
if (context === 'checkout') { if ( context === 'checkout' ) {
const checkoutBootstap = new CheckoutBootstap( const checkoutBootstap = new CheckoutBootstap(
PayPalCommerceGateway, PayPalCommerceGateway,
renderer, renderer,
spinner, spinner,
errorHandler, errorHandler
); );
checkoutBootstap.init(); checkoutBootstap.init();
buttonModuleWatcher.registerContextBootstrap('checkout', checkoutBootstap); buttonModuleWatcher.registerContextBootstrap(
} 'checkout',
checkoutBootstap
);
}
if (context === 'pay-now' ) { if ( context === 'pay-now' ) {
const payNowBootstrap = new PayNowBootstrap( const payNowBootstrap = new PayNowBootstrap(
PayPalCommerceGateway, PayPalCommerceGateway,
renderer, renderer,
spinner, spinner,
errorHandler, errorHandler
); );
payNowBootstrap.init(); payNowBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('pay-now', payNowBootstrap); buttonModuleWatcher.registerContextBootstrap(
} 'pay-now',
payNowBootstrap
);
}
const messagesBootstrap = new MessagesBootstrap( const messagesBootstrap = new MessagesBootstrap(
PayPalCommerceGateway, PayPalCommerceGateway,
messageRenderer, messageRenderer
); );
messagesBootstrap.init(); messagesBootstrap.init();
apmButtonsInit(PayPalCommerceGateway); apmButtonsInit( PayPalCommerceGateway );
}; };
document.addEventListener( document.addEventListener( 'DOMContentLoaded', () => {
'DOMContentLoaded', if ( ! typeof PayPalCommerceGateway ) {
() => { console.error( 'PayPal button could not be configured.' );
if (!typeof (PayPalCommerceGateway)) { return;
console.error('PayPal button could not be configured.'); }
return;
}
if ( if (
PayPalCommerceGateway.context !== 'checkout' PayPalCommerceGateway.context !== 'checkout' &&
&& PayPalCommerceGateway.data_client_id.user === 0 PayPalCommerceGateway.data_client_id.user === 0 &&
&& PayPalCommerceGateway.data_client_id.has_subscriptions PayPalCommerceGateway.data_client_id.has_subscriptions
) { ) {
return; return;
} }
const paypalButtonGatewayIds = [ const paypalButtonGatewayIds = [
PaymentMethods.PAYPAL, PaymentMethods.PAYPAL,
...Object.entries(PayPalCommerceGateway.separate_buttons).map(([k, data]) => data.id), ...Object.entries( PayPalCommerceGateway.separate_buttons ).map(
] ( [ k, data ] ) => data.id
),
];
// Sometimes PayPal script takes long time to load, // Sometimes PayPal script takes long time to load,
// so we additionally hide the standard order button here to avoid failed orders. // so we additionally hide the standard order button here to avoid failed orders.
// Normally it is hidden later after the script load. // Normally it is hidden later after the script load.
const hideOrderButtonIfPpcpGateway = () => { const hideOrderButtonIfPpcpGateway = () => {
// only in checkout and pay now page, otherwise it may break things (e.g. payment via product page), // only in checkout and pay now page, otherwise it may break things (e.g. payment via product page),
// and also the loading spinner may look weird on other pages // and also the loading spinner may look weird on other pages
if ( if (
!['checkout', 'pay-now'].includes(PayPalCommerceGateway.context) ! [ 'checkout', 'pay-now' ].includes(
|| isChangePaymentPage() PayPalCommerceGateway.context
|| (PayPalCommerceGateway.is_free_trial_cart && PayPalCommerceGateway.vaulted_paypal_email !== '') ) ||
) { isChangePaymentPage() ||
return; ( PayPalCommerceGateway.is_free_trial_cart &&
} PayPalCommerceGateway.vaulted_paypal_email !== '' )
) {
return;
}
const currentPaymentMethod = getCurrentPaymentMethod(); const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypalButton = paypalButtonGatewayIds.includes(currentPaymentMethod); const isPaypalButton =
const isCards = currentPaymentMethod === PaymentMethods.CARDS; paypalButtonGatewayIds.includes( currentPaymentMethod );
const isCards = currentPaymentMethod === PaymentMethods.CARDS;
setVisibleByClass(ORDER_BUTTON_SELECTOR, !isPaypalButton && !isCards, 'ppcp-hidden'); setVisibleByClass(
ORDER_BUTTON_SELECTOR,
! isPaypalButton && ! isCards,
'ppcp-hidden'
);
if (isPaypalButton) { if ( isPaypalButton ) {
// stopped after the first rendering of the buttons, in onInit // stopped after the first rendering of the buttons, in onInit
buttonsSpinner.block(); buttonsSpinner.block();
} else { } else {
buttonsSpinner.unblock(); buttonsSpinner.unblock();
} }
if (isCards) { if ( isCards ) {
cardsSpinner.block(); cardsSpinner.block();
} else { } else {
cardsSpinner.unblock(); cardsSpinner.unblock();
} }
} };
jQuery(document).on('hosted_fields_loaded', () => { jQuery( document ).on( 'hosted_fields_loaded', () => {
cardsSpinner.unblock(); cardsSpinner.unblock();
}); } );
let bootstrapped = false; let bootstrapped = false;
let failed = false; let failed = false;
hideOrderButtonIfPpcpGateway(); hideOrderButtonIfPpcpGateway();
jQuery(document.body).on('updated_checkout payment_method_selected', () => { jQuery( document.body ).on(
if (bootstrapped || failed) { 'updated_checkout payment_method_selected',
return; () => {
} if ( bootstrapped || failed ) {
return;
}
hideOrderButtonIfPpcpGateway(); hideOrderButtonIfPpcpGateway();
}); }
);
loadPaypalScript(PayPalCommerceGateway, () => { loadPaypalScript(
bootstrapped = true; PayPalCommerceGateway,
() => {
bootstrapped = true;
bootstrap(); bootstrap();
}, () => { },
failed = true; () => {
failed = true;
setVisibleByClass(ORDER_BUTTON_SELECTOR, true, 'ppcp-hidden'); setVisibleByClass( ORDER_BUTTON_SELECTOR, true, 'ppcp-hidden' );
buttonsSpinner.unblock(); buttonsSpinner.unblock();
cardsSpinner.unblock(); cardsSpinner.unblock();
}); }
}, );
); } );

View file

@ -1,89 +1,99 @@
import onApprove from '../OnApproveHandler/onApproveForContinue.js'; import onApprove from '../OnApproveHandler/onApproveForContinue.js';
import {payerData} from "../Helper/PayerData"; import { payerData } from '../Helper/PayerData';
import {PaymentMethods} from "../Helper/CheckoutMethodState"; import { PaymentMethods } from '../Helper/CheckoutMethodState';
class CartActionHandler { class CartActionHandler {
constructor( config, errorHandler ) {
this.config = config;
this.errorHandler = errorHandler;
}
constructor(config, errorHandler) { subscriptionsConfiguration( subscription_plan_id ) {
this.config = config; return {
this.errorHandler = errorHandler; createSubscription: ( data, actions ) => {
} return actions.subscription.create( {
plan_id: subscription_plan_id,
} );
},
onApprove: ( data, actions ) => {
fetch( this.config.ajax.approve_subscription.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID,
should_create_wc_order:
! context.config.vaultingEnabled ||
data.paymentSource !== 'venmo',
} ),
} )
.then( ( res ) => {
return res.json();
} )
.then( ( data ) => {
if ( ! data.success ) {
console.log( data );
throw Error( data.data.message );
}
subscriptionsConfiguration(subscription_plan_id) { const orderReceivedUrl = data.data?.order_received_url;
return {
createSubscription: (data, actions) => {
return actions.subscription.create({
'plan_id': subscription_plan_id
});
},
onApprove: (data, actions) => {
fetch(this.config.ajax.approve_subscription.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID,
should_create_wc_order: !context.config.vaultingEnabled || data.paymentSource !== 'venmo'
})
}).then((res)=>{
return res.json();
}).then((data) => {
if (!data.success) {
console.log(data)
throw Error(data.data.message);
}
let orderReceivedUrl = data.data?.order_received_url location.href = orderReceivedUrl
? orderReceivedUrl
: context.config.redirect;
} );
},
onError: ( err ) => {
console.error( err );
},
};
}
location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect; configuration() {
}); const createOrder = ( data, actions ) => {
}, const payer = payerData();
onError: (err) => { const bnCode =
console.error(err); typeof this.config.bn_codes[ this.config.context ] !==
} 'undefined'
} ? this.config.bn_codes[ this.config.context ]
} : '';
return fetch( this.config.ajax.create_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.config.ajax.create_order.nonce,
purchase_units: [],
payment_method: PaymentMethods.PAYPAL,
funding_source: window.ppcpFundingSource,
bn_code: bnCode,
payer,
context: this.config.context,
} ),
} )
.then( function ( res ) {
return res.json();
} )
.then( function ( data ) {
if ( ! data.success ) {
console.error( data );
throw Error( data.data.message );
}
return data.data.id;
} );
};
configuration() { return {
const createOrder = (data, actions) => { createOrder,
const payer = payerData(); onApprove: onApprove( this, this.errorHandler ),
const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ? onError: ( error ) => {
this.config.bn_codes[this.config.context] : ''; this.errorHandler.genericError();
return fetch(this.config.ajax.create_order.endpoint, { },
method: 'POST', };
headers: { }
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,
purchase_units: [],
payment_method: PaymentMethods.PAYPAL,
funding_source: window.ppcpFundingSource,
bn_code:bnCode,
payer,
context:this.config.context
}),
}).then(function(res) {
return res.json();
}).then(function(data) {
if (!data.success) {
console.error(data);
throw Error(data.data.message);
}
return data.data.id;
});
};
return {
createOrder,
onApprove: onApprove(this, this.errorHandler),
onError: (error) => {
this.errorHandler.genericError();
}
};
}
} }
export default CartActionHandler; export default CartActionHandler;

View file

@ -1,197 +1,233 @@
import 'formdata-polyfill'; import 'formdata-polyfill';
import onApprove from '../OnApproveHandler/onApproveForPayNow.js'; import onApprove from '../OnApproveHandler/onApproveForPayNow.js';
import {payerData} from "../Helper/PayerData"; import { payerData } from '../Helper/PayerData';
import {getCurrentPaymentMethod} from "../Helper/CheckoutMethodState"; import { getCurrentPaymentMethod } from '../Helper/CheckoutMethodState';
import validateCheckoutForm from "../Helper/CheckoutFormValidation"; import validateCheckoutForm from '../Helper/CheckoutFormValidation';
class CheckoutActionHandler { class CheckoutActionHandler {
constructor( config, errorHandler, spinner ) {
this.config = config;
this.errorHandler = errorHandler;
this.spinner = spinner;
}
constructor(config, errorHandler, spinner) { subscriptionsConfiguration( subscription_plan_id ) {
this.config = config; return {
this.errorHandler = errorHandler; createSubscription: async ( data, actions ) => {
this.spinner = spinner; try {
} await validateCheckoutForm( this.config );
} catch ( error ) {
throw { type: 'form-validation-error' };
}
subscriptionsConfiguration(subscription_plan_id) { return actions.subscription.create( {
return { plan_id: subscription_plan_id,
createSubscription: async (data, actions) => { } );
try { },
await validateCheckoutForm(this.config); onApprove: ( data, actions ) => {
} catch (error) { fetch( this.config.ajax.approve_subscription.endpoint, {
throw {type: 'form-validation-error'}; method: 'POST',
} credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID,
} ),
} )
.then( ( res ) => {
return res.json();
} )
.then( ( data ) => {
document.querySelector( '#place_order' ).click();
} );
},
onError: ( err ) => {
console.error( err );
},
};
}
return actions.subscription.create({ configuration() {
'plan_id': subscription_plan_id const spinner = this.spinner;
}); const createOrder = ( data, actions ) => {
}, const payer = payerData();
onApprove: (data, actions) => { const bnCode =
fetch(this.config.ajax.approve_subscription.endpoint, { typeof this.config.bn_codes[ this.config.context ] !==
method: 'POST', 'undefined'
credentials: 'same-origin', ? this.config.bn_codes[ this.config.context ]
body: JSON.stringify({ : '';
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID
})
}).then((res)=>{
return res.json();
}).then((data) => {
document.querySelector('#place_order').click();
});
},
onError: (err) => {
console.error(err);
}
}
}
configuration() { const errorHandler = this.errorHandler;
const spinner = this.spinner;
const createOrder = (data, actions) => {
const payer = payerData();
const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ?
this.config.bn_codes[this.config.context] : '';
const errorHandler = this.errorHandler; const formSelector =
this.config.context === 'checkout'
? 'form.checkout'
: 'form#order_review';
const formData = new FormData(
document.querySelector( formSelector )
);
const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review'; const createaccount = jQuery( '#createaccount' ).is( ':checked' )
const formData = new FormData(document.querySelector(formSelector)); ? true
: false;
const createaccount = jQuery('#createaccount').is(":checked") ? true : false; const paymentMethod = getCurrentPaymentMethod();
const fundingSource = window.ppcpFundingSource;
const paymentMethod = getCurrentPaymentMethod(); const savePaymentMethod = !! document.getElementById(
const fundingSource = window.ppcpFundingSource; 'wc-ppcp-credit-card-gateway-new-payment-method'
)?.checked;
const savePaymentMethod = !!document.getElementById('wc-ppcp-credit-card-gateway-new-payment-method')?.checked; return fetch( this.config.ajax.create_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.config.ajax.create_order.nonce,
payer,
bn_code: bnCode,
context: this.config.context,
order_id: this.config.order_id,
payment_method: paymentMethod,
funding_source: fundingSource,
// send as urlencoded string to handle complex fields via PHP functions the same as normal form submit
form_encoded: new URLSearchParams( formData ).toString(),
createaccount,
save_payment_method: savePaymentMethod,
} ),
} )
.then( function ( res ) {
return res.json();
} )
.then( function ( data ) {
if ( ! data.success ) {
spinner.unblock();
//handle both messages sent from Woocommerce (data.messages) and this plugin (data.data.message)
if ( typeof data.messages !== 'undefined' ) {
const domParser = new DOMParser();
errorHandler.appendPreparedErrorMessageElement(
domParser
.parseFromString(
data.messages,
'text/html'
)
.querySelector( 'ul' )
);
} else {
errorHandler.clear();
return fetch(this.config.ajax.create_order.endpoint, { if ( data.data.refresh ) {
method: 'POST', jQuery( document.body ).trigger(
headers: { 'update_checkout'
'Content-Type': 'application/json' );
}, }
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,
payer,
bn_code:bnCode,
context:this.config.context,
order_id:this.config.order_id,
payment_method: paymentMethod,
funding_source: fundingSource,
// send as urlencoded string to handle complex fields via PHP functions the same as normal form submit
form_encoded: new URLSearchParams(formData).toString(),
createaccount: createaccount,
save_payment_method: savePaymentMethod
})
}).then(function (res) {
return res.json();
}).then(function (data) {
if (!data.success) {
spinner.unblock();
//handle both messages sent from Woocommerce (data.messages) and this plugin (data.data.message)
if (typeof(data.messages) !== 'undefined' )
{
const domParser = new DOMParser();
errorHandler.appendPreparedErrorMessageElement(
domParser.parseFromString(data.messages, 'text/html')
.querySelector('ul')
);
} else {
errorHandler.clear();
if (data.data.refresh) { if ( data.data.errors?.length > 0 ) {
jQuery( document.body ).trigger( 'update_checkout' ); errorHandler.messages( data.data.errors );
} } else if ( data.data.details?.length > 0 ) {
errorHandler.message(
data.data.details
.map(
( d ) =>
`${ d.issue } ${ d.description }`
)
.join( '<br/>' )
);
} else {
errorHandler.message( data.data.message );
}
if (data.data.errors?.length > 0) { // fire WC event for other plugins
errorHandler.messages(data.data.errors); jQuery( document.body ).trigger( 'checkout_error', [
} else if (data.data.details?.length > 0) { errorHandler.currentHtml(),
errorHandler.message(data.data.details.map(d => `${d.issue} ${d.description}`).join('<br/>')); ] );
} else { }
errorHandler.message(data.data.message);
}
// fire WC event for other plugins throw { type: 'create-order-error', data: data.data };
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] ); }
} const input = document.createElement( 'input' );
input.setAttribute( 'type', 'hidden' );
input.setAttribute( 'name', 'ppcp-resume-order' );
input.setAttribute( 'value', data.data.custom_id );
document.querySelector( formSelector ).appendChild( input );
return data.data.id;
} );
};
return {
createOrder,
onApprove: onApprove( this, this.errorHandler, this.spinner ),
onCancel: () => {
spinner.unblock();
},
onError: ( err ) => {
console.error( err );
spinner.unblock();
throw {type: 'create-order-error', data: data.data}; if ( err && err.type === 'create-order-error' ) {
} return;
const input = document.createElement('input'); }
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'ppcp-resume-order');
input.setAttribute('value', data.data.custom_id);
document.querySelector(formSelector).appendChild(input);
return data.data.id;
});
}
return {
createOrder,
onApprove:onApprove(this, this.errorHandler, this.spinner),
onCancel: () => {
spinner.unblock();
},
onError: (err) => {
console.error(err);
spinner.unblock();
if (err && err.type === 'create-order-error') { this.errorHandler.genericError();
return; },
} };
}
this.errorHandler.genericError(); addPaymentMethodConfiguration() {
} return {
} createVaultSetupToken: async () => {
} const response = await fetch(
this.config.ajax.create_setup_token.endpoint,
{
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( {
nonce: this.config.ajax.create_setup_token.nonce,
} ),
}
);
addPaymentMethodConfiguration() { const result = await response.json();
return { if ( result.data.id ) {
createVaultSetupToken: async () => { return result.data.id;
const response = await fetch(this.config.ajax.create_setup_token.endpoint, { }
method: "POST",
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonce: this.config.ajax.create_setup_token.nonce,
})
});
const result = await response.json() console.error( result );
if (result.data.id) { },
return result.data.id onApprove: async ( { vaultSetupToken } ) => {
} const response = await fetch(
this.config.ajax.create_payment_token_for_guest.endpoint,
{
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( {
nonce: this.config.ajax
.create_payment_token_for_guest.nonce,
vault_setup_token: vaultSetupToken,
} ),
}
);
console.error(result) const result = await response.json();
}, if ( result.success === true ) {
onApprove: async ({vaultSetupToken}) => { document.querySelector( '#place_order' ).click();
const response = await fetch(this.config.ajax.create_payment_token_for_guest.endpoint, { return;
method: "POST", }
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonce: this.config.ajax.create_payment_token_for_guest.nonce,
vault_setup_token: vaultSetupToken,
})
})
const result = await response.json(); console.error( result );
if (result.success === true) { },
document.querySelector('#place_order').click() onError: ( error ) => {
return; console.error( error );
} },
};
console.error(result) }
},
onError: (error) => {
console.error(error)
}
}
}
} }
export default CheckoutActionHandler; export default CheckoutActionHandler;

View file

@ -1,80 +1,85 @@
class FreeTrialHandler { class FreeTrialHandler {
/** /**
* @param config * @param config
* @param formSelector * @param formSelector
* @param {FormSaver} formSaver * @param {FormSaver} formSaver
* @param {FormValidator|null} formValidator * @param {FormValidator|null} formValidator
* @param {Spinner} spinner * @param {Spinner} spinner
* @param {ErrorHandler} errorHandler * @param {ErrorHandler} errorHandler
*/ */
constructor( constructor(
config, config,
formSelector, formSelector,
formSaver, formSaver,
formValidator, formValidator,
spinner, spinner,
errorHandler errorHandler
) { ) {
this.config = config; this.config = config;
this.formSelector = formSelector; this.formSelector = formSelector;
this.formSaver = formSaver; this.formSaver = formSaver;
this.formValidator = formValidator; this.formValidator = formValidator;
this.spinner = spinner; this.spinner = spinner;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
} }
async handle() async handle() {
{ this.spinner.block();
this.spinner.block();
try { try {
await this.formSaver.save(document.querySelector(this.formSelector)); await this.formSaver.save(
} catch (error) { document.querySelector( this.formSelector )
console.error(error); );
} } catch ( error ) {
console.error( error );
}
try { try {
if (this.formValidator) { if ( this.formValidator ) {
try { try {
const errors = await this.formValidator.validate(document.querySelector(this.formSelector)); const errors = await this.formValidator.validate(
if (errors.length > 0) { document.querySelector( this.formSelector )
this.spinner.unblock(); );
this.errorHandler.messages(errors); if ( errors.length > 0 ) {
this.spinner.unblock();
this.errorHandler.messages( errors );
// fire WC event for other plugins // fire WC event for other plugins
jQuery( document.body ).trigger( 'checkout_error' , [ this.errorHandler.currentHtml() ] ); jQuery( document.body ).trigger( 'checkout_error', [
this.errorHandler.currentHtml(),
] );
return; return;
} }
} catch (error) { } catch ( error ) {
console.error(error); console.error( error );
} }
} }
const res = await fetch(this.config.ajax.vault_paypal.endpoint, { const res = await fetch( this.config.ajax.vault_paypal.endpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: this.config.ajax.vault_paypal.nonce, nonce: this.config.ajax.vault_paypal.nonce,
return_url: location.href, return_url: location.href,
}), } ),
}); } );
const data = await res.json(); const data = await res.json();
if (!data.success) { if ( ! data.success ) {
throw Error(data.data.message); throw Error( data.data.message );
} }
location.href = data.data.approve_link; location.href = data.data.approve_link;
} catch (error) { } catch ( error ) {
this.spinner.unblock(); this.spinner.unblock();
console.error(error); console.error( error );
this.errorHandler.message(data.data.message); this.errorHandler.message( data.data.message );
} }
} }
} }
export default FreeTrialHandler; export default FreeTrialHandler;

View file

@ -1,229 +1,247 @@
import Product from '../Entity/Product'; import Product from '../Entity/Product';
import BookingProduct from "../Entity/BookingProduct"; import BookingProduct from '../Entity/BookingProduct';
import onApprove from '../OnApproveHandler/onApproveForContinue'; import onApprove from '../OnApproveHandler/onApproveForContinue';
import {payerData} from "../Helper/PayerData"; import { payerData } from '../Helper/PayerData';
import {PaymentMethods} from "../Helper/CheckoutMethodState"; import { PaymentMethods } from '../Helper/CheckoutMethodState';
import CartHelper from "../Helper/CartHelper"; import CartHelper from '../Helper/CartHelper';
import FormHelper from "../Helper/FormHelper"; import FormHelper from '../Helper/FormHelper';
class SingleProductActionHandler { class SingleProductActionHandler {
constructor( config, updateCart, formElement, errorHandler ) {
this.config = config;
this.updateCart = updateCart;
this.formElement = formElement;
this.errorHandler = errorHandler;
this.cartHelper = null;
}
constructor( subscriptionsConfiguration( subscription_plan ) {
config, return {
updateCart, createSubscription: ( data, actions ) => {
formElement, return actions.subscription.create( {
errorHandler plan_id: subscription_plan,
) { } );
this.config = config; },
this.updateCart = updateCart; onApprove: ( data, actions ) => {
this.formElement = formElement; fetch( this.config.ajax.approve_subscription.endpoint, {
this.errorHandler = errorHandler; method: 'POST',
this.cartHelper = null; credentials: 'same-origin',
} body: JSON.stringify( {
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID,
} ),
} )
.then( ( res ) => {
return res.json();
} )
.then( () => {
const products = this.getSubscriptionProducts();
subscriptionsConfiguration(subscription_plan) { fetch( this.config.ajax.change_cart.endpoint, {
return { method: 'POST',
createSubscription: (data, actions) => { headers: {
return actions.subscription.create({ 'Content-Type': 'application/json',
'plan_id': subscription_plan },
}); credentials: 'same-origin',
}, body: JSON.stringify( {
onApprove: (data, actions) => { nonce: this.config.ajax.change_cart.nonce,
fetch(this.config.ajax.approve_subscription.endpoint, { products,
method: 'POST', } ),
credentials: 'same-origin', } )
body: JSON.stringify({ .then( ( result ) => {
nonce: this.config.ajax.approve_subscription.nonce, return result.json();
order_id: data.orderID, } )
subscription_id: data.subscriptionID .then( ( result ) => {
}) if ( ! result.success ) {
}).then((res)=>{ console.log( result );
return res.json(); throw Error( result.data.message );
}).then(() => { }
const products = this.getSubscriptionProducts();
fetch(this.config.ajax.change_cart.endpoint, { location.href = this.config.redirect;
method: 'POST', } );
headers: { } );
'Content-Type': 'application/json' },
}, onError: ( err ) => {
credentials: 'same-origin', console.error( err );
body: JSON.stringify({ },
nonce: this.config.ajax.change_cart.nonce, };
products, }
})
}).then((result) => {
return result.json();
}).then((result) => {
if (!result.success) {
console.log(result)
throw Error(result.data.message);
}
location.href = this.config.redirect; getSubscriptionProducts() {
}) const id = document.querySelector( '[name="add-to-cart"]' ).value;
}); return [ new Product( id, 1, this.variations(), this.extraFields() ) ];
}, }
onError: (err) => {
console.error(err);
}
}
}
getSubscriptionProducts() configuration() {
{ return {
const id = document.querySelector('[name="add-to-cart"]').value; createOrder: this.createOrder(),
return [new Product(id, 1, this.variations(), this.extraFields())]; onApprove: onApprove( this, this.errorHandler ),
} onError: ( error ) => {
this.refreshMiniCart();
configuration() if ( this.isBookingProduct() && error.message ) {
{ this.errorHandler.clear();
return { this.errorHandler.message( error.message );
createOrder: this.createOrder(), return;
onApprove: onApprove(this, this.errorHandler), }
onError: (error) => { this.errorHandler.genericError();
this.refreshMiniCart(); },
onCancel: () => {
// Could be used for every product type,
// but only clean the cart for Booking products for now.
if ( this.isBookingProduct() ) {
this.cleanCart();
} else {
this.refreshMiniCart();
}
},
};
}
if (this.isBookingProduct() && error.message) { getProducts() {
this.errorHandler.clear(); if ( this.isBookingProduct() ) {
this.errorHandler.message(error.message); const id = document.querySelector( '[name="add-to-cart"]' ).value;
return; return [
} new BookingProduct(
this.errorHandler.genericError(); id,
}, 1,
onCancel: () => { FormHelper.getPrefixedFields(
// Could be used for every product type, this.formElement,
// but only clean the cart for Booking products for now. 'wc_bookings_field'
if (this.isBookingProduct()) { ),
this.cleanCart(); this.extraFields()
} else { ),
this.refreshMiniCart(); ];
} } else if ( this.isGroupedProduct() ) {
} const products = [];
} this.formElement
} .querySelectorAll( 'input[type="number"]' )
.forEach( ( element ) => {
if ( ! element.value ) {
return;
}
const elementName = element
.getAttribute( 'name' )
.match( /quantity\[([\d]*)\]/ );
if ( elementName.length !== 2 ) {
return;
}
const id = parseInt( elementName[ 1 ] );
const quantity = parseInt( element.value );
products.push(
new Product( id, quantity, null, this.extraFields() )
);
} );
return products;
}
const id = document.querySelector( '[name="add-to-cart"]' ).value;
const qty = document.querySelector( '[name="quantity"]' ).value;
const variations = this.variations();
return [ new Product( id, qty, variations, this.extraFields() ) ];
}
getProducts() extraFields() {
{ return FormHelper.getFilteredFields(
if ( this.isBookingProduct() ) { this.formElement,
const id = document.querySelector('[name="add-to-cart"]').value; [ 'add-to-cart', 'quantity', 'product_id', 'variation_id' ],
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"), this.extraFields())]; [ 'attribute_', 'wc_bookings_field' ]
} else if ( this.isGroupedProduct() ) { );
const products = []; }
this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
if (! element.value) {
return;
}
const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
if (elementName.length !== 2) {
return;
}
const id = parseInt(elementName[1]);
const quantity = parseInt(element.value);
products.push(new Product(id, quantity, null, this.extraFields()));
})
return products;
} else {
const id = document.querySelector('[name="add-to-cart"]').value;
const qty = document.querySelector('[name="quantity"]').value;
const variations = this.variations();
return [new Product(id, qty, variations, this.extraFields())];
}
}
extraFields() { createOrder() {
return FormHelper.getFilteredFields( this.cartHelper = null;
this.formElement,
['add-to-cart', 'quantity', 'product_id', 'variation_id'],
['attribute_', 'wc_bookings_field']
);
}
createOrder() return ( data, actions, options = {} ) => {
{ this.errorHandler.clear();
this.cartHelper = null;
return (data, actions, options = {}) => { const onResolve = ( purchase_units ) => {
this.errorHandler.clear(); this.cartHelper = new CartHelper().addFromPurchaseUnits(
purchase_units
);
const onResolve = (purchase_units) => { const payer = payerData();
this.cartHelper = (new CartHelper()).addFromPurchaseUnits(purchase_units); const bnCode =
typeof this.config.bn_codes[ this.config.context ] !==
'undefined'
? this.config.bn_codes[ this.config.context ]
: '';
return fetch( this.config.ajax.create_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.config.ajax.create_order.nonce,
purchase_units,
payer,
bn_code: bnCode,
payment_method: PaymentMethods.PAYPAL,
funding_source: window.ppcpFundingSource,
context: this.config.context,
} ),
} )
.then( function ( res ) {
return res.json();
} )
.then( function ( data ) {
if ( ! data.success ) {
console.error( data );
throw Error( data.data.message );
}
return data.data.id;
} );
};
const payer = payerData(); return this.updateCart.update(
const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ? onResolve,
this.config.bn_codes[this.config.context] : ''; this.getProducts(),
return fetch(this.config.ajax.create_order.endpoint, { options.updateCartOptions || {}
method: 'POST', );
headers: { };
'Content-Type': 'application/json' }
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,
purchase_units,
payer,
bn_code:bnCode,
payment_method: PaymentMethods.PAYPAL,
funding_source: window.ppcpFundingSource,
context:this.config.context
})
}).then(function (res) {
return res.json();
}).then(function (data) {
if (!data.success) {
console.error(data);
throw Error(data.data.message);
}
return data.data.id;
});
};
return this.updateCart.update(onResolve, this.getProducts(), options.updateCartOptions || {}); variations() {
}; if ( ! this.hasVariations() ) {
} return null;
}
return [
...this.formElement.querySelectorAll( "[name^='attribute_']" ),
].map( ( element ) => {
return {
value: element.value,
name: element.name,
};
} );
}
variations() hasVariations() {
{ return this.formElement.classList.contains( 'variations_form' );
if (! this.hasVariations()) { }
return null;
}
return [...this.formElement.querySelectorAll("[name^='attribute_']")].map(
(element) => {
return {
value:element.value,
name:element.name
}
}
);
}
hasVariations() isGroupedProduct() {
{ return this.formElement.classList.contains( 'grouped_form' );
return this.formElement.classList.contains('variations_form'); }
}
isGroupedProduct() isBookingProduct() {
{ // detection for "woocommerce-bookings" plugin
return this.formElement.classList.contains('grouped_form'); return !! this.formElement.querySelector( '.wc-booking-product-id' );
} }
isBookingProduct() cleanCart() {
{ this.cartHelper
// detection for "woocommerce-bookings" plugin .removeFromCart()
return !!this.formElement.querySelector('.wc-booking-product-id'); .then( () => {
} this.refreshMiniCart();
} )
cleanCart() { .catch( ( error ) => {
this.cartHelper.removeFromCart().then(() => { this.refreshMiniCart();
this.refreshMiniCart(); } );
}).catch(error => { }
this.refreshMiniCart();
});
}
refreshMiniCart() {
jQuery(document.body).trigger('wc_fragment_refresh');
}
refreshMiniCart() {
jQuery( document.body ).trigger( 'wc_fragment_refresh' );
}
} }
export default SingleProductActionHandler; export default SingleProductActionHandler;

View file

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

View file

@ -1,116 +1,132 @@
import CartActionHandler from '../ActionHandler/CartActionHandler'; import CartActionHandler from '../ActionHandler/CartActionHandler';
import BootstrapHelper from "../Helper/BootstrapHelper"; import BootstrapHelper from '../Helper/BootstrapHelper';
class CartBootstrap { class CartBootstrap {
constructor(gateway, renderer, errorHandler) { constructor( gateway, renderer, errorHandler ) {
this.gateway = gateway; this.gateway = gateway;
this.renderer = renderer; this.renderer = renderer;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { this.renderer.onButtonsInit(
this.handleButtonStatus(); this.gateway.button.wrapper,
}, true); () => {
} this.handleButtonStatus();
},
true
);
}
init() { init() {
if (this.shouldRender()) { if ( this.shouldRender() ) {
this.render(); this.render();
this.handleButtonStatus(); this.handleButtonStatus();
} }
jQuery(document.body).on('updated_cart_totals updated_checkout', () => { jQuery( document.body ).on(
if (this.shouldRender()) { 'updated_cart_totals updated_checkout',
this.render(); () => {
this.handleButtonStatus(); if ( this.shouldRender() ) {
} this.render();
this.handleButtonStatus();
}
fetch( fetch( this.gateway.ajax.cart_script_params.endpoint, {
this.gateway.ajax.cart_script_params.endpoint, method: 'GET',
{ credentials: 'same-origin',
method: 'GET', } )
credentials: 'same-origin', .then( ( result ) => result.json() )
} .then( ( result ) => {
) if ( ! result.success ) {
.then(result => result.json()) return;
.then(result => { }
if (! result.success) {
return;
}
// handle script reload // handle script reload
const newParams = result.data.url_params; const newParams = result.data.url_params;
const reloadRequired = JSON.stringify(this.gateway.url_params) !== JSON.stringify(newParams); const reloadRequired =
JSON.stringify( this.gateway.url_params ) !==
JSON.stringify( newParams );
if (reloadRequired) { if ( reloadRequired ) {
this.gateway.url_params = newParams; this.gateway.url_params = newParams;
jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons'); jQuery( this.gateway.button.wrapper ).trigger(
} 'ppcp-reload-buttons'
);
}
// handle button status // handle button status
const newData = {}; const newData = {};
if (result.data.button) { if ( result.data.button ) {
newData.button = result.data.button; newData.button = result.data.button;
} }
if (result.data.messages) { if ( result.data.messages ) {
newData.messages = result.data.messages; newData.messages = result.data.messages;
} }
if (newData) { if ( newData ) {
BootstrapHelper.updateScriptData(this, newData); BootstrapHelper.updateScriptData( this, newData );
this.handleButtonStatus(); this.handleButtonStatus();
} }
jQuery(document.body).trigger('ppcp_cart_total_updated', [result.data.amount]); jQuery( document.body ).trigger(
}); 'ppcp_cart_total_updated',
}); [ result.data.amount ]
} );
} );
}
);
}
handleButtonStatus() { handleButtonStatus() {
BootstrapHelper.handleButtonStatus(this); BootstrapHelper.handleButtonStatus( this );
} }
shouldRender() { shouldRender() {
return document.querySelector(this.gateway.button.wrapper) !== null; return document.querySelector( this.gateway.button.wrapper ) !== null;
} }
shouldEnable() { shouldEnable() {
return BootstrapHelper.shouldEnable(this); return BootstrapHelper.shouldEnable( this );
} }
render() { render() {
if (!this.shouldRender()) { if ( ! this.shouldRender() ) {
return; return;
} }
const actionHandler = new CartActionHandler( const actionHandler = new CartActionHandler(
PayPalCommerceGateway, PayPalCommerceGateway,
this.errorHandler, this.errorHandler
); );
if( if (
PayPalCommerceGateway.data_client_id.has_subscriptions PayPalCommerceGateway.data_client_id.has_subscriptions &&
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) { ) {
let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id let subscription_plan_id =
if(PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== '') { PayPalCommerceGateway.subscription_plan_id;
subscription_plan_id = PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart if (
} PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !==
''
) {
subscription_plan_id =
PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart;
}
this.renderer.render(actionHandler.subscriptionsConfiguration(subscription_plan_id)); this.renderer.render(
actionHandler.subscriptionsConfiguration( subscription_plan_id )
);
if(!PayPalCommerceGateway.subscription_product_allowed) { if ( ! PayPalCommerceGateway.subscription_product_allowed ) {
this.gateway.button.is_disabled = true; this.gateway.button.is_disabled = true;
this.handleButtonStatus(); this.handleButtonStatus();
} }
return; return;
} }
this.renderer.render( this.renderer.render( actionHandler.configuration() );
actionHandler.configuration()
);
jQuery(document.body).trigger('ppcp_cart_rendered'); jQuery( document.body ).trigger( 'ppcp_cart_rendered' );
} }
} }
export default CartBootstrap; export default CartBootstrap;

View file

@ -1,209 +1,315 @@
import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
import {setVisible, setVisibleByClass} from '../Helper/Hiding'; import { setVisible, setVisibleByClass } from '../Helper/Hiding';
import { import {
getCurrentPaymentMethod, getCurrentPaymentMethod,
isSavedCardSelected, ORDER_BUTTON_SELECTOR, isSavedCardSelected,
PaymentMethods ORDER_BUTTON_SELECTOR,
} from "../Helper/CheckoutMethodState"; PaymentMethods,
import BootstrapHelper from "../Helper/BootstrapHelper"; } from '../Helper/CheckoutMethodState';
import BootstrapHelper from '../Helper/BootstrapHelper';
class CheckoutBootstap { class CheckoutBootstap {
constructor(gateway, renderer, spinner, errorHandler) { constructor( gateway, renderer, spinner, errorHandler ) {
this.gateway = gateway; this.gateway = gateway;
this.renderer = renderer; this.renderer = renderer;
this.spinner = spinner; this.spinner = spinner;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR; this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR;
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { this.renderer.onButtonsInit(
this.handleButtonStatus(); this.gateway.button.wrapper,
}, true); () => {
} this.handleButtonStatus();
},
true
);
}
init() { init() {
this.render(); this.render();
this.handleButtonStatus(); this.handleButtonStatus();
// Unselect saved card. // Unselect saved card.
// WC saves form values, so with our current UI it would be a bit weird // WC saves form values, so with our current UI it would be a bit weird
// if the user paid with saved, then after some time tries to pay again, // if the user paid with saved, then after some time tries to pay again,
// but wants to enter a new card, and to do that they have to choose “Select payment” in the list. // but wants to enter a new card, and to do that they have to choose “Select payment” in the list.
jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val()); jQuery( '#saved-credit-card' ).val(
jQuery( '#saved-credit-card option:first' ).val()
);
jQuery(document.body).on('updated_checkout', () => { jQuery( document.body ).on( 'updated_checkout', () => {
this.render() this.render();
this.handleButtonStatus(); this.handleButtonStatus();
if (this.shouldShowMessages() && document.querySelector(this.gateway.messages.wrapper)) { // currently we need amount only for Pay Later if (
fetch( this.shouldShowMessages() &&
this.gateway.ajax.cart_script_params.endpoint, document.querySelector( this.gateway.messages.wrapper )
{ ) {
method: 'GET', // currently we need amount only for Pay Later
credentials: 'same-origin', fetch( this.gateway.ajax.cart_script_params.endpoint, {
} method: 'GET',
) credentials: 'same-origin',
.then(result => result.json()) } )
.then(result => { .then( ( result ) => result.json() )
if (! result.success) { .then( ( result ) => {
return; if ( ! result.success ) {
} return;
}
jQuery(document.body).trigger('ppcp_checkout_total_updated', [result.data.amount]); jQuery( document.body ).trigger(
}); 'ppcp_checkout_total_updated',
} [ result.data.amount ]
}); );
} );
}
} );
jQuery(document.body).on('updated_checkout payment_method_selected', () => { jQuery( document.body ).on(
this.updateUi(); 'updated_checkout payment_method_selected',
}); () => {
this.updateUi();
}
);
jQuery(document).on('hosted_fields_loaded', () => { jQuery( document ).on( 'hosted_fields_loaded', () => {
jQuery('#saved-credit-card').on('change', () => { jQuery( '#saved-credit-card' ).on( 'change', () => {
this.updateUi(); this.updateUi();
}) } );
}); } );
jQuery(document).on('ppcp_should_show_messages', (e, data) => { jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => {
if (!this.shouldShowMessages()) { if ( ! this.shouldShowMessages() ) {
data.result = false; data.result = false;
} }
}); } );
this.updateUi(); this.updateUi();
} }
handleButtonStatus() { handleButtonStatus() {
BootstrapHelper.handleButtonStatus(this); BootstrapHelper.handleButtonStatus( this );
} }
shouldRender() { shouldRender() {
if (document.querySelector(this.gateway.button.cancel_wrapper)) { if ( document.querySelector( this.gateway.button.cancel_wrapper ) ) {
return false; return false;
} }
return document.querySelector(this.gateway.button.wrapper) !== null || document.querySelector(this.gateway.hosted_fields.wrapper) !== null; return (
} document.querySelector( this.gateway.button.wrapper ) !== null ||
document.querySelector( this.gateway.hosted_fields.wrapper ) !==
null
);
}
shouldEnable() { shouldEnable() {
return BootstrapHelper.shouldEnable(this); return BootstrapHelper.shouldEnable( this );
} }
render() { render() {
if (!this.shouldRender()) { if ( ! this.shouldRender() ) {
return; return;
} }
if (document.querySelector(this.gateway.hosted_fields.wrapper + '>div')) { if (
document.querySelector(this.gateway.hosted_fields.wrapper + '>div').setAttribute('style', ''); document.querySelector(
} this.gateway.hosted_fields.wrapper + '>div'
const actionHandler = new CheckoutActionHandler( )
PayPalCommerceGateway, ) {
this.errorHandler, document
this.spinner .querySelector( this.gateway.hosted_fields.wrapper + '>div' )
); .setAttribute( 'style', '' );
}
const actionHandler = new CheckoutActionHandler(
PayPalCommerceGateway,
this.errorHandler,
this.spinner
);
if( if (
PayPalCommerceGateway.data_client_id.has_subscriptions PayPalCommerceGateway.data_client_id.has_subscriptions &&
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) { ) {
let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id let subscription_plan_id =
if(PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== '') { PayPalCommerceGateway.subscription_plan_id;
subscription_plan_id = PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart if (
} PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !==
this.renderer.render(actionHandler.subscriptionsConfiguration(subscription_plan_id), {}, actionHandler.configuration()); ''
) {
subscription_plan_id =
PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart;
}
this.renderer.render(
actionHandler.subscriptionsConfiguration(
subscription_plan_id
),
{},
actionHandler.configuration()
);
if(!PayPalCommerceGateway.subscription_product_allowed) { if ( ! PayPalCommerceGateway.subscription_product_allowed ) {
this.gateway.button.is_disabled = true; this.gateway.button.is_disabled = true;
this.handleButtonStatus(); this.handleButtonStatus();
} }
return; return;
} }
if( if (
PayPalCommerceGateway.is_free_trial_cart PayPalCommerceGateway.is_free_trial_cart &&
&& PayPalCommerceGateway.vault_v3_enabled PayPalCommerceGateway.vault_v3_enabled
) { ) {
this.renderer.render(actionHandler.addPaymentMethodConfiguration(), {}, actionHandler.configuration()); this.renderer.render(
return; actionHandler.addPaymentMethodConfiguration(),
} {},
actionHandler.configuration()
);
return;
}
this.renderer.render(actionHandler.configuration(), {}, actionHandler.configuration()); this.renderer.render(
} actionHandler.configuration(),
{},
actionHandler.configuration()
);
}
updateUi() { updateUi() {
const currentPaymentMethod = getCurrentPaymentMethod(); const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
const isCard = currentPaymentMethod === PaymentMethods.CARDS; const isCard = currentPaymentMethod === PaymentMethods.CARDS;
const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod); const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes(
const isSavedCard = isCard && isSavedCardSelected(); currentPaymentMethod
const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway; );
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const isGooglePayMethod =
const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; currentPaymentMethod === PaymentMethods.GOOGLEPAY;
const isSavedCard = isCard && isSavedCardSelected();
const isNotOurGateway =
! isPaypal &&
! isCard &&
! isSeparateButtonGateway &&
! isGooglePayMethod;
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
const hasVaultedPaypal =
PayPalCommerceGateway.vaulted_paypal_email !== '';
const paypalButtonWrappers = { const paypalButtonWrappers = {
...Object.entries(PayPalCommerceGateway.separate_buttons) ...Object.entries( PayPalCommerceGateway.separate_buttons ).reduce(
.reduce((result, [k, data]) => { ( result, [ k, data ] ) => {
return {...result, [data.id]: data.wrapper} return { ...result, [ data.id ]: data.wrapper };
}, {}), },
}; {}
),
};
setVisibleByClass(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, 'ppcp-hidden'); setVisibleByClass(
setVisible('.ppcp-vaulted-paypal-details', isPaypal); this.standardOrderButtonSelector,
setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); ( isPaypal && isFreeTrial && hasVaultedPaypal ) ||
setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); isNotOurGateway ||
for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) { isSavedCard,
setVisible(wrapper, gatewayId === currentPaymentMethod); 'ppcp-hidden'
} );
setVisible( '.ppcp-vaulted-paypal-details', isPaypal );
setVisible(
this.gateway.button.wrapper,
isPaypal && ! ( isFreeTrial && hasVaultedPaypal )
);
setVisible(
this.gateway.hosted_fields.wrapper,
isCard && ! isSavedCard
);
for ( const [ gatewayId, wrapper ] of Object.entries(
paypalButtonWrappers
) ) {
setVisible( wrapper, gatewayId === currentPaymentMethod );
}
if (isCard) { if ( isCard ) {
if (isSavedCard) { if ( isSavedCard ) {
this.disableCreditCardFields(); this.disableCreditCardFields();
} else { } else {
this.enableCreditCardFields(); this.enableCreditCardFields();
} }
} }
jQuery(document.body).trigger('ppcp_checkout_rendered'); setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod );
}
shouldShowMessages() { jQuery( document.body ).trigger( 'ppcp_checkout_rendered' );
// hide when another method selected only if messages are near buttons }
const messagesWrapper = document.querySelector(this.gateway.messages.wrapper);
if (getCurrentPaymentMethod() !== PaymentMethods.PAYPAL &&
messagesWrapper && jQuery(messagesWrapper).closest('.ppc-button-wrapper').length
) {
return false;
}
return !PayPalCommerceGateway.is_free_trial_cart; shouldShowMessages() {
} // hide when another method selected only if messages are near buttons
const messagesWrapper = document.querySelector(
this.gateway.messages.wrapper
);
if (
getCurrentPaymentMethod() !== PaymentMethods.PAYPAL &&
messagesWrapper &&
jQuery( messagesWrapper ).closest( '.ppc-button-wrapper' ).length
) {
return false;
}
disableCreditCardFields() { return ! PayPalCommerceGateway.is_free_trial_cart;
jQuery('label[for="ppcp-credit-card-gateway-card-number"]').addClass('ppcp-credit-card-gateway-form-field-disabled') }
jQuery('#ppcp-credit-card-gateway-card-number').addClass('ppcp-credit-card-gateway-form-field-disabled')
jQuery('label[for="ppcp-credit-card-gateway-card-expiry"]').addClass('ppcp-credit-card-gateway-form-field-disabled')
jQuery('#ppcp-credit-card-gateway-card-expiry').addClass('ppcp-credit-card-gateway-form-field-disabled')
jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').addClass('ppcp-credit-card-gateway-form-field-disabled')
jQuery('#ppcp-credit-card-gateway-card-cvc').addClass('ppcp-credit-card-gateway-form-field-disabled')
jQuery('label[for="vault"]').addClass('ppcp-credit-card-gateway-form-field-disabled')
jQuery('#ppcp-credit-card-vault').addClass('ppcp-credit-card-gateway-form-field-disabled')
jQuery('#ppcp-credit-card-vault').attr("disabled", true)
this.renderer.disableCreditCardFields()
}
enableCreditCardFields() { disableCreditCardFields() {
jQuery('label[for="ppcp-credit-card-gateway-card-number"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') jQuery( 'label[for="ppcp-credit-card-gateway-card-number"]' ).addClass(
jQuery('#ppcp-credit-card-gateway-card-number').removeClass('ppcp-credit-card-gateway-form-field-disabled') 'ppcp-credit-card-gateway-form-field-disabled'
jQuery('label[for="ppcp-credit-card-gateway-card-expiry"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') );
jQuery('#ppcp-credit-card-gateway-card-expiry').removeClass('ppcp-credit-card-gateway-form-field-disabled') jQuery( '#ppcp-credit-card-gateway-card-number' ).addClass(
jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') 'ppcp-credit-card-gateway-form-field-disabled'
jQuery('#ppcp-credit-card-gateway-card-cvc').removeClass('ppcp-credit-card-gateway-form-field-disabled') );
jQuery('label[for="vault"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') jQuery( 'label[for="ppcp-credit-card-gateway-card-expiry"]' ).addClass(
jQuery('#ppcp-credit-card-vault').removeClass('ppcp-credit-card-gateway-form-field-disabled') 'ppcp-credit-card-gateway-form-field-disabled'
jQuery('#ppcp-credit-card-vault').attr("disabled", false) );
this.renderer.enableCreditCardFields() jQuery( '#ppcp-credit-card-gateway-card-expiry' ).addClass(
} 'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( 'label[for="ppcp-credit-card-gateway-card-cvc"]' ).addClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( '#ppcp-credit-card-gateway-card-cvc' ).addClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( 'label[for="vault"]' ).addClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( '#ppcp-credit-card-vault' ).addClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( '#ppcp-credit-card-vault' ).attr( 'disabled', true );
this.renderer.disableCreditCardFields();
}
enableCreditCardFields() {
jQuery(
'label[for="ppcp-credit-card-gateway-card-number"]'
).removeClass( 'ppcp-credit-card-gateway-form-field-disabled' );
jQuery( '#ppcp-credit-card-gateway-card-number' ).removeClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery(
'label[for="ppcp-credit-card-gateway-card-expiry"]'
).removeClass( 'ppcp-credit-card-gateway-form-field-disabled' );
jQuery( '#ppcp-credit-card-gateway-card-expiry' ).removeClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( 'label[for="ppcp-credit-card-gateway-card-cvc"]' ).removeClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( '#ppcp-credit-card-gateway-card-cvc' ).removeClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( 'label[for="vault"]' ).removeClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( '#ppcp-credit-card-vault' ).removeClass(
'ppcp-credit-card-gateway-form-field-disabled'
);
jQuery( '#ppcp-credit-card-vault' ).attr( 'disabled', false );
this.renderer.enableCreditCardFields();
}
} }
export default CheckoutBootstap export default CheckoutBootstap;

View file

@ -1,98 +1,111 @@
import {setVisible} from "../Helper/Hiding"; import { setVisible } from '../Helper/Hiding';
import MessageRenderer from "../Renderer/MessageRenderer"; import MessageRenderer from '../Renderer/MessageRenderer';
class MessagesBootstrap { class MessagesBootstrap {
constructor(gateway, messageRenderer) { constructor( gateway, messageRenderer ) {
this.gateway = gateway; this.gateway = gateway;
this.renderers = []; this.renderers = [];
this.lastAmount = this.gateway.messages.amount; this.lastAmount = this.gateway.messages.amount;
if (messageRenderer) { if ( messageRenderer ) {
this.renderers.push(messageRenderer); this.renderers.push( messageRenderer );
} }
} }
async init() { async init() {
if (this.gateway.messages?.block?.enabled) { if ( this.gateway.messages?.block?.enabled ) {
await this.attemptDiscoverBlocks(3); // Try up to 3 times await this.attemptDiscoverBlocks( 3 ); // Try up to 3 times
} }
jQuery(document.body).on('ppcp_cart_rendered ppcp_checkout_rendered', () => { jQuery( document.body ).on(
this.render(); 'ppcp_cart_rendered ppcp_checkout_rendered',
}); () => {
jQuery(document.body).on('ppcp_script_data_changed', (e, data) => { this.render();
this.gateway = data; }
this.render(); );
}); jQuery( document.body ).on( 'ppcp_script_data_changed', ( e, data ) => {
jQuery(document.body).on('ppcp_cart_total_updated ppcp_checkout_total_updated ppcp_product_total_updated ppcp_block_cart_total_updated', (e, amount) => { this.gateway = data;
if (this.lastAmount !== amount) { this.render();
this.lastAmount = amount; } );
this.render(); jQuery( document.body ).on(
} 'ppcp_cart_total_updated ppcp_checkout_total_updated ppcp_product_total_updated ppcp_block_cart_total_updated',
}); ( e, amount ) => {
if ( this.lastAmount !== amount ) {
this.lastAmount = amount;
this.render();
}
}
);
this.render(); this.render();
} }
attemptDiscoverBlocks(retries) { attemptDiscoverBlocks( retries ) {
return new Promise((resolve, reject) => { return new Promise( ( resolve, reject ) => {
this.discoverBlocks().then(found => { this.discoverBlocks().then( ( found ) => {
if (!found && retries > 0) { if ( ! found && retries > 0 ) {
setTimeout(() => { setTimeout( () => {
this.attemptDiscoverBlocks(retries - 1).then(resolve); this.attemptDiscoverBlocks( retries - 1 ).then(
}, 2000); // Wait 2 seconds before retrying resolve
} else { );
resolve(); }, 2000 ); // Wait 2 seconds before retrying
} } else {
}); resolve();
}); }
} } );
} );
}
discoverBlocks() { discoverBlocks() {
return new Promise((resolve) => { return new Promise( ( resolve ) => {
const elements = document.querySelectorAll('.ppcp-messages'); const elements = document.querySelectorAll( '.ppcp-messages' );
if (elements.length === 0) { if ( elements.length === 0 ) {
resolve(false); resolve( false );
return; return;
} }
Array.from(elements).forEach(blockElement => { Array.from( elements ).forEach( ( blockElement ) => {
if (!blockElement.id) { if ( ! blockElement.id ) {
blockElement.id = `ppcp-message-${Math.random().toString(36).substr(2, 9)}`; // Ensure each block has a unique ID blockElement.id = `ppcp-message-${ Math.random()
} .toString( 36 )
const config = {wrapper: '#' + blockElement.id}; .substr( 2, 9 ) }`; // Ensure each block has a unique ID
if (!blockElement.getAttribute('data-pp-placement')) { }
config.placement = this.gateway.messages.placement; const config = { wrapper: '#' + blockElement.id };
} if ( ! blockElement.getAttribute( 'data-pp-placement' ) ) {
this.renderers.push(new MessageRenderer(config)); config.placement = this.gateway.messages.placement;
}); }
resolve(true); this.renderers.push( new MessageRenderer( config ) );
}); } );
} resolve( true );
} );
}
shouldShow(renderer) { shouldShow( renderer ) {
if (this.gateway.messages.is_hidden === true) { if ( this.gateway.messages.is_hidden === true ) {
return false; return false;
} }
const eventData = {result: true} const eventData = { result: true };
jQuery(document.body).trigger('ppcp_should_show_messages', [eventData, renderer.config.wrapper]); jQuery( document.body ).trigger( 'ppcp_should_show_messages', [
return eventData.result; eventData,
} renderer.config.wrapper,
] );
return eventData.result;
}
render() { render() {
this.renderers.forEach(renderer => { this.renderers.forEach( ( renderer ) => {
const shouldShow = this.shouldShow(renderer); const shouldShow = this.shouldShow( renderer );
setVisible(renderer.config.wrapper, shouldShow); setVisible( renderer.config.wrapper, shouldShow );
if (!shouldShow) { if ( ! shouldShow ) {
return; return;
} }
if (!renderer.shouldRender()) { if ( ! renderer.shouldRender() ) {
return; return;
} }
renderer.renderWithAmount(this.lastAmount); renderer.renderWithAmount( this.lastAmount );
}); } );
} }
} }
export default MessagesBootstrap; export default MessagesBootstrap;

View file

@ -1,66 +1,74 @@
import CartActionHandler from '../ActionHandler/CartActionHandler'; import CartActionHandler from '../ActionHandler/CartActionHandler';
import BootstrapHelper from "../Helper/BootstrapHelper"; import BootstrapHelper from '../Helper/BootstrapHelper';
class MiniCartBootstap { class MiniCartBootstap {
constructor(gateway, renderer, errorHandler) { constructor( gateway, renderer, errorHandler ) {
this.gateway = gateway; this.gateway = gateway;
this.renderer = renderer; this.renderer = renderer;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.actionHandler = null; this.actionHandler = null;
} }
init() { init() {
this.actionHandler = new CartActionHandler(
PayPalCommerceGateway,
this.errorHandler
);
this.render();
this.handleButtonStatus();
this.actionHandler = new CartActionHandler( jQuery( document.body ).on(
PayPalCommerceGateway, 'wc_fragments_loaded wc_fragments_refreshed',
this.errorHandler, () => {
); this.render();
this.render(); this.handleButtonStatus();
this.handleButtonStatus(); }
);
jQuery(document.body).on('wc_fragments_loaded wc_fragments_refreshed', () => { this.renderer.onButtonsInit(
this.render(); this.gateway.button.mini_cart_wrapper,
this.handleButtonStatus(); () => {
}); this.handleButtonStatus();
},
true
);
}
this.renderer.onButtonsInit(this.gateway.button.mini_cart_wrapper, () => { handleButtonStatus() {
this.handleButtonStatus(); BootstrapHelper.handleButtonStatus( this, {
}, true); wrapper: this.gateway.button.mini_cart_wrapper,
} skipMessages: true,
} );
}
handleButtonStatus() { shouldRender() {
BootstrapHelper.handleButtonStatus(this, { return (
wrapper: this.gateway.button.mini_cart_wrapper, document.querySelector( this.gateway.button.mini_cart_wrapper ) !==
skipMessages: true null ||
}); document.querySelector(
} this.gateway.hosted_fields.mini_cart_wrapper
) !== null
);
}
shouldRender() { shouldEnable() {
return document.querySelector(this.gateway.button.mini_cart_wrapper) !== null return BootstrapHelper.shouldEnable( this, {
|| document.querySelector(this.gateway.hosted_fields.mini_cart_wrapper) !== null; isDisabled: !! this.gateway.button.is_mini_cart_disabled,
} } );
}
shouldEnable() { render() {
return BootstrapHelper.shouldEnable(this, { if ( ! this.shouldRender() ) {
isDisabled: !!this.gateway.button.is_mini_cart_disabled return;
}); }
}
render() { this.renderer.render( this.actionHandler.configuration(), {
if (!this.shouldRender()) { button: {
return; wrapper: this.gateway.button.mini_cart_wrapper,
} style: this.gateway.button.mini_cart_style,
},
this.renderer.render( } );
this.actionHandler.configuration(), }
{
button: {
wrapper: this.gateway.button.mini_cart_wrapper,
style: this.gateway.button.mini_cart_style,
},
}
);
}
} }
export default MiniCartBootstap; export default MiniCartBootstap;

View file

@ -1,18 +1,18 @@
import CheckoutBootstap from './CheckoutBootstap' import CheckoutBootstap from './CheckoutBootstap';
import {isChangePaymentPage} from "../Helper/Subscriptions"; import { isChangePaymentPage } from '../Helper/Subscriptions';
class PayNowBootstrap extends CheckoutBootstap { class PayNowBootstrap extends CheckoutBootstap {
constructor(gateway, renderer, spinner, errorHandler) { constructor( gateway, renderer, spinner, errorHandler ) {
super(gateway, renderer, spinner, errorHandler) super( gateway, renderer, spinner, errorHandler );
} }
updateUi() { updateUi() {
if (isChangePaymentPage()) { if ( isChangePaymentPage() ) {
return return;
} }
super.updateUi(); super.updateUi();
} }
} }
export default PayNowBootstrap; export default PayNowBootstrap;

View file

@ -1,288 +1,362 @@
import UpdateCart from "../Helper/UpdateCart"; import UpdateCart from '../Helper/UpdateCart';
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler"; import SingleProductActionHandler from '../ActionHandler/SingleProductActionHandler';
import {hide, show} from "../Helper/Hiding"; import { hide, show } from '../Helper/Hiding';
import BootstrapHelper from "../Helper/BootstrapHelper"; import BootstrapHelper from '../Helper/BootstrapHelper';
import {loadPaypalJsScript} from "../Helper/ScriptLoading"; import { loadPaypalJsScript } from '../Helper/ScriptLoading';
import {getPlanIdFromVariation} from "../Helper/Subscriptions" import { getPlanIdFromVariation } from '../Helper/Subscriptions';
import SimulateCart from "../Helper/SimulateCart"; import SimulateCart from '../Helper/SimulateCart';
import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils"; import { strRemoveWord, strAddWord, throttle } from '../Helper/Utils';
import merge from "deepmerge"; import merge from 'deepmerge';
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
class SingleProductBootstap { class SingleProductBootstap {
constructor(gateway, renderer, errorHandler) { constructor( gateway, renderer, errorHandler ) {
this.gateway = gateway; this.gateway = gateway;
this.renderer = renderer; this.renderer = renderer;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.mutationObserver = new MutationObserver(this.handleChange.bind(this)); this.mutationObserver = new MutationObserver(
this.formSelector = 'form.cart'; this.handleChange.bind( this )
);
this.formSelector = 'form.cart';
// Prevent simulate cart being called too many times in a burst. // Prevent simulate cart being called too many times in a burst.
this.simulateCartThrottled = throttle(this.simulateCart, this.gateway.simulate_cart.throttling || 5000); this.simulateCartThrottled = throttle(
this.simulateCart.bind( this ),
this.gateway.simulate_cart.throttling || 5000
);
this.debouncedHandleChange = debounce(
this.handleChange.bind( this ),
100
);
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { this.renderer.onButtonsInit(
this.handleChange(); this.gateway.button.wrapper,
}, true); () => {
this.handleChange();
},
true
);
this.subscriptionButtonsLoaded = false this.subscriptionButtonsLoaded = false;
} }
form() { form() {
return document.querySelector(this.formSelector); return document.querySelector( this.formSelector );
} }
handleChange() { handleChange() {
this.subscriptionButtonsLoaded = false this.subscriptionButtonsLoaded = false;
if (!this.shouldRender()) { if ( ! this.shouldRender() ) {
this.renderer.disableSmartButtons(this.gateway.button.wrapper); this.renderer.disableSmartButtons( this.gateway.button.wrapper );
hide(this.gateway.button.wrapper, this.formSelector); hide( this.gateway.button.wrapper, this.formSelector );
return; return;
} }
this.render(); this.render();
this.renderer.enableSmartButtons(this.gateway.button.wrapper); this.renderer.enableSmartButtons( this.gateway.button.wrapper );
show(this.gateway.button.wrapper); show( this.gateway.button.wrapper );
this.handleButtonStatus(); this.handleButtonStatus();
} }
handleButtonStatus(simulateCart = true) { handleButtonStatus( simulateCart = true ) {
BootstrapHelper.handleButtonStatus(this, { BootstrapHelper.handleButtonStatus( this, {
formSelector: this.formSelector formSelector: this.formSelector,
}); } );
if (simulateCart) { if ( simulateCart ) {
this.simulateCartThrottled(); this.simulateCartThrottled();
} }
} }
init() { init() {
const form = this.form(); const form = this.form();
if (!form) { if ( ! form ) {
return; return;
} }
jQuery(document).on('change', this.formSelector, () => { jQuery( document ).on( 'change', this.formSelector, () => {
this.handleChange(); this.debouncedHandleChange();
}); } );
this.mutationObserver.observe(form, { childList: true, subtree: true }); this.mutationObserver.observe( form, {
childList: true,
subtree: true,
} );
const addToCartButton = form.querySelector('.single_add_to_cart_button'); const addToCartButton = form.querySelector(
'.single_add_to_cart_button'
);
if (addToCartButton) { if ( addToCartButton ) {
(new MutationObserver(this.handleButtonStatus.bind(this))) new MutationObserver(
.observe(addToCartButton, { attributes : true }); this.handleButtonStatus.bind( this )
} ).observe( addToCartButton, { attributes: true } );
}
jQuery(document).on('ppcp_should_show_messages', (e, data) => { jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => {
if (!this.shouldRender()) { if ( ! this.shouldRender() ) {
data.result = false; data.result = false;
} }
}); } );
if (!this.shouldRender()) { if ( ! this.shouldRender() ) {
return; return;
} }
this.render(); this.render();
this.handleChange(); this.handleChange();
} }
shouldRender() { shouldRender() {
return this.form() !== null return this.form() !== null && ! this.isWcsattSubscriptionMode();
&& !this.isWcsattSubscriptionMode(); }
}
shouldEnable() { shouldEnable() {
const form = this.form(); const form = this.form();
const addToCartButton = form ? form.querySelector('.single_add_to_cart_button') : null; const addToCartButton = form
? form.querySelector( '.single_add_to_cart_button' )
: null;
return BootstrapHelper.shouldEnable(this) return (
&& !this.priceAmountIsZero() BootstrapHelper.shouldEnable( this ) &&
&& ((null === addToCartButton) || !addToCartButton.classList.contains('disabled')); ! this.priceAmountIsZero() &&
} ( null === addToCartButton ||
! addToCartButton.classList.contains( 'disabled' ) )
);
}
priceAmount(returnOnUndefined = 0) { priceAmount( returnOnUndefined = 0 ) {
const priceText = [ const priceText = [
() => document.querySelector('form.cart ins .woocommerce-Price-amount')?.innerText, () =>
() => document.querySelector('form.cart .woocommerce-Price-amount')?.innerText, document.querySelector(
() => { 'form.cart ins .woocommerce-Price-amount'
const priceEl = document.querySelector('.product .woocommerce-Price-amount'); )?.innerText,
// variable products show price like 10.00 - 20.00 here () =>
// but the second price also can be the suffix with the price incl/excl tax document.querySelector( 'form.cart .woocommerce-Price-amount' )
if (priceEl) { ?.innerText,
const allPriceElements = Array.from(priceEl.parentElement.querySelectorAll('.woocommerce-Price-amount')) () => {
.filter(el => !el.parentElement.classList.contains('woocommerce-price-suffix')); const priceEl = document.querySelector(
if (allPriceElements.length === 1) { '.product .woocommerce-Price-amount'
return priceEl.innerText; );
} // variable products show price like 10.00 - 20.00 here
} // but the second price also can be the suffix with the price incl/excl tax
return null; if ( priceEl ) {
}, const allPriceElements = Array.from(
].map(f => f()).find(val => val); priceEl.parentElement.querySelectorAll(
'.woocommerce-Price-amount'
)
).filter(
( el ) =>
! el.parentElement.classList.contains(
'woocommerce-price-suffix'
)
);
if ( allPriceElements.length === 1 ) {
return priceEl.innerText;
}
}
return null;
},
]
.map( ( f ) => f() )
.find( ( val ) => val );
if (typeof priceText === 'undefined') { if ( typeof priceText === 'undefined' ) {
return returnOnUndefined; return returnOnUndefined;
} }
if (!priceText) { if ( ! priceText ) {
return 0; return 0;
} }
return parseFloat(priceText.replace(/,/g, '.').replace(/([^\d,\.\s]*)/g, '')); return parseFloat(
} priceText.replace( /,/g, '.' ).replace( /([^\d,\.\s]*)/g, '' )
);
}
priceAmountIsZero() { priceAmountIsZero() {
const price = this.priceAmount(-1); const price = this.priceAmount( -1 );
// if we can't find the price in the DOM we want to return true so the button is visible. // if we can't find the price in the DOM we want to return true so the button is visible.
if (price === -1) { if ( price === -1 ) {
return false; return false;
} }
return !price || price === 0; return ! price || price === 0;
} }
isWcsattSubscriptionMode() { isWcsattSubscriptionMode() {
// Check "All products for subscriptions" plugin. // Check "All products for subscriptions" plugin.
return document.querySelector('.wcsatt-options-product:not(.wcsatt-options-product--hidden) .subscription-option input[type="radio"]:checked') !== null return (
|| document.querySelector('.wcsatt-options-prompt-label-subscription input[type="radio"]:checked') !== null; // grouped document.querySelector(
} '.wcsatt-options-product:not(.wcsatt-options-product--hidden) .subscription-option input[type="radio"]:checked'
) !== null ||
document.querySelector(
'.wcsatt-options-prompt-label-subscription input[type="radio"]:checked'
) !== null
); // grouped
}
variations() { variations() {
if (!this.hasVariations()) { if ( ! this.hasVariations() ) {
return null; return null;
} }
return [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map( return [
(element) => { ...document
return { .querySelector( 'form.cart' )
value: element.value, ?.querySelectorAll( "[name^='attribute_']" ),
name: element.name ].map( ( element ) => {
} return {
} value: element.value,
); name: element.name,
} };
} );
}
hasVariations() { hasVariations() {
return document.querySelector('form.cart')?.classList.contains('variations_form'); return document
} .querySelector( 'form.cart' )
?.classList.contains( 'variations_form' );
}
render() { render() {
const actionHandler = new SingleProductActionHandler( const actionHandler = new SingleProductActionHandler(
this.gateway, this.gateway,
new UpdateCart( new UpdateCart(
this.gateway.ajax.change_cart.endpoint, this.gateway.ajax.change_cart.endpoint,
this.gateway.ajax.change_cart.nonce, this.gateway.ajax.change_cart.nonce
), ),
this.form(), this.form(),
this.errorHandler, this.errorHandler
); );
if( if (
PayPalCommerceGateway.data_client_id.has_subscriptions PayPalCommerceGateway.data_client_id.has_subscriptions &&
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) { ) {
const buttonWrapper = document.getElementById('ppc-button-ppcp-gateway'); const buttonWrapper = document.getElementById(
buttonWrapper.innerHTML = ''; 'ppc-button-ppcp-gateway'
);
buttonWrapper.innerHTML = '';
const subscription_plan = this.variations() !== null const subscription_plan =
? getPlanIdFromVariation(this.variations()) this.variations() !== null
: PayPalCommerceGateway.subscription_plan_id ? getPlanIdFromVariation( this.variations() )
if(!subscription_plan) { : PayPalCommerceGateway.subscription_plan_id;
return; if ( ! subscription_plan ) {
} return;
}
if(this.subscriptionButtonsLoaded) return if ( this.subscriptionButtonsLoaded ) {
loadPaypalJsScript( return;
{ }
clientId: PayPalCommerceGateway.client_id, loadPaypalJsScript(
currency: PayPalCommerceGateway.currency, {
intent: 'subscription', clientId: PayPalCommerceGateway.client_id,
vault: true currency: PayPalCommerceGateway.currency,
}, intent: 'subscription',
actionHandler.subscriptionsConfiguration(subscription_plan), vault: true,
this.gateway.button.wrapper },
); actionHandler.subscriptionsConfiguration( subscription_plan ),
this.gateway.button.wrapper
);
this.subscriptionButtonsLoaded = true this.subscriptionButtonsLoaded = true;
return; return;
} }
this.renderer.render( this.renderer.render( actionHandler.configuration() );
actionHandler.configuration() }
);
}
simulateCart() { simulateCart() {
if (!this.gateway.simulate_cart.enabled) { if ( ! this.gateway.simulate_cart.enabled ) {
return; return;
} }
const actionHandler = new SingleProductActionHandler( const actionHandler = new SingleProductActionHandler(
null, null,
null, null,
this.form(), this.form(),
this.errorHandler, this.errorHandler
); );
const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions const hasSubscriptions =
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; PayPalCommerceGateway.data_client_id.has_subscriptions &&
PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
const products = hasSubscriptions const products = hasSubscriptions
? actionHandler.getSubscriptionProducts() ? actionHandler.getSubscriptionProducts()
: actionHandler.getProducts(); : actionHandler.getProducts();
(new SimulateCart( new SimulateCart(
this.gateway.ajax.simulate_cart.endpoint, this.gateway.ajax.simulate_cart.endpoint,
this.gateway.ajax.simulate_cart.nonce, this.gateway.ajax.simulate_cart.nonce
)).simulate((data) => { ).simulate( ( data ) => {
jQuery( document.body ).trigger( 'ppcp_product_total_updated', [
data.total,
] );
jQuery(document.body).trigger('ppcp_product_total_updated', [data.total]); let newData = {};
if ( typeof data.button.is_disabled === 'boolean' ) {
newData = merge( newData, {
button: { is_disabled: data.button.is_disabled },
} );
}
if ( typeof data.messages.is_hidden === 'boolean' ) {
newData = merge( newData, {
messages: { is_hidden: data.messages.is_hidden },
} );
}
if ( newData ) {
BootstrapHelper.updateScriptData( this, newData );
}
let newData = {}; if ( this.gateway.single_product_buttons_enabled !== '1' ) {
if (typeof data.button.is_disabled === 'boolean') { return;
newData = merge(newData, {button: {is_disabled: data.button.is_disabled}}); }
}
if (typeof data.messages.is_hidden === 'boolean') {
newData = merge(newData, {messages: {is_hidden: data.messages.is_hidden}});
}
if (newData) {
BootstrapHelper.updateScriptData(this, newData);
}
if ( this.gateway.single_product_buttons_enabled !== '1' ) { let enableFunding = this.gateway.url_params[ 'enable-funding' ];
return; let disableFunding = this.gateway.url_params[ 'disable-funding' ];
}
let enableFunding = this.gateway.url_params['enable-funding']; for ( const [ fundingSource, funding ] of Object.entries(
let disableFunding = this.gateway.url_params['disable-funding']; data.funding
) ) {
if ( funding.enabled === true ) {
enableFunding = strAddWord( enableFunding, fundingSource );
disableFunding = strRemoveWord(
disableFunding,
fundingSource
);
} else if ( funding.enabled === false ) {
enableFunding = strRemoveWord(
enableFunding,
fundingSource
);
disableFunding = strAddWord(
disableFunding,
fundingSource
);
}
}
for (const [fundingSource, funding] of Object.entries(data.funding)) { if (
if (funding.enabled === true) { enableFunding !== this.gateway.url_params[ 'enable-funding' ] ||
enableFunding = strAddWord(enableFunding, fundingSource); disableFunding !== this.gateway.url_params[ 'disable-funding' ]
disableFunding = strRemoveWord(disableFunding, fundingSource); ) {
} else if (funding.enabled === false) { this.gateway.url_params[ 'enable-funding' ] = enableFunding;
enableFunding = strRemoveWord(enableFunding, fundingSource); this.gateway.url_params[ 'disable-funding' ] = disableFunding;
disableFunding = strAddWord(disableFunding, fundingSource); jQuery( this.gateway.button.wrapper ).trigger(
} 'ppcp-reload-buttons'
} );
}
if ( this.handleButtonStatus( false );
(enableFunding !== this.gateway.url_params['enable-funding']) || }, products );
(disableFunding !== this.gateway.url_params['disable-funding']) }
) {
this.gateway.url_params['enable-funding'] = enableFunding;
this.gateway.url_params['disable-funding'] = disableFunding;
jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons');
}
this.handleButtonStatus(false);
}, products);
}
} }
export default SingleProductBootstap; export default SingleProductBootstap;

View file

@ -1,62 +1,71 @@
import {loadScript} from "@paypal/paypal-js"; import { loadScript } from '@paypal/paypal-js';
const storageKey = 'ppcp-data-client-id'; const storageKey = 'ppcp-data-client-id';
const validateToken = (token, user) => { const validateToken = ( token, user ) => {
if (! token) { if ( ! token ) {
return false; return false;
} }
if (token.user !== user) { if ( token.user !== user ) {
return false; return false;
} }
const currentTime = new Date().getTime(); const currentTime = new Date().getTime();
const isExpired = currentTime >= token.expiration * 1000; const isExpired = currentTime >= token.expiration * 1000;
return ! isExpired; return ! isExpired;
} };
const storedTokenForUser = (user) => { const storedTokenForUser = ( user ) => {
const token = JSON.parse(sessionStorage.getItem(storageKey)); const token = JSON.parse( sessionStorage.getItem( storageKey ) );
if (validateToken(token, user)) { if ( validateToken( token, user ) ) {
return token.token; return token.token;
} }
return null; return null;
} };
const storeToken = (token) => { const storeToken = ( token ) => {
sessionStorage.setItem(storageKey, JSON.stringify(token)); sessionStorage.setItem( storageKey, JSON.stringify( token ) );
} };
const dataClientIdAttributeHandler = (scriptOptions, config, callback, errorCallback = null) => { const dataClientIdAttributeHandler = (
fetch(config.endpoint, { scriptOptions,
method: 'POST', config,
headers: { callback,
'Content-Type': 'application/json' errorCallback = null
}, ) => {
credentials: 'same-origin', fetch( config.endpoint, {
body: JSON.stringify({ method: 'POST',
nonce: config.nonce headers: {
}) 'Content-Type': 'application/json',
}).then((res)=>{ },
return res.json(); credentials: 'same-origin',
}).then((data)=>{ body: JSON.stringify( {
const isValid = validateToken(data, config.user); nonce: config.nonce,
if (!isValid) { } ),
return; } )
} .then( ( res ) => {
storeToken(data); return res.json();
} )
.then( ( data ) => {
const isValid = validateToken( data, config.user );
if ( ! isValid ) {
return;
}
storeToken( data );
scriptOptions['data-client-token'] = data.token; scriptOptions[ 'data-client-token' ] = data.token;
loadScript(scriptOptions).then((paypal) => { loadScript( scriptOptions )
if (typeof callback === 'function') { .then( ( paypal ) => {
callback(paypal); if ( typeof callback === 'function' ) {
} callback( paypal );
}).catch(err => { }
if (typeof errorCallback === 'function') { } )
errorCallback(err); .catch( ( err ) => {
} if ( typeof errorCallback === 'function' ) {
}); errorCallback( err );
}); }
} } );
} );
};
export default dataClientIdAttributeHandler; export default dataClientIdAttributeHandler;

View file

@ -1,18 +1,17 @@
import Product from "./Product"; import Product from './Product';
class BookingProduct extends Product { class BookingProduct extends Product {
constructor( id, quantity, booking, extra ) {
super( id, quantity, null, extra );
this.booking = booking;
}
constructor(id, quantity, booking, extra) { data() {
super(id, quantity, null, extra); return {
this.booking = booking; ...super.data(),
} booking: this.booking,
};
data() { }
return {
...super.data(),
booking: this.booking
}
}
} }
export default BookingProduct; export default BookingProduct;

View file

@ -1,19 +1,18 @@
class Product { class Product {
constructor( id, quantity, variations, extra ) {
constructor(id, quantity, variations, extra) { this.id = id;
this.id = id; this.quantity = quantity;
this.quantity = quantity; this.variations = variations;
this.variations = variations; this.extra = extra;
this.extra = extra; }
} data() {
data() { return {
return { id: this.id,
id:this.id, quantity: this.quantity,
quantity: this.quantity, variations: this.variations,
variations: this.variations, extra: this.extra,
extra: this.extra, };
} }
}
} }
export default Product; export default Product;

View file

@ -1,108 +1,98 @@
class ErrorHandler { class ErrorHandler {
/**
* @param {string} genericErrorText
* @param {Element} wrapper
*/
constructor( genericErrorText, wrapper ) {
this.genericErrorText = genericErrorText;
this.wrapper = wrapper;
}
/** genericError() {
* @param {String} genericErrorText this.clear();
* @param {Element} wrapper this.message( this.genericErrorText );
*/ }
constructor(genericErrorText, wrapper)
{
this.genericErrorText = genericErrorText;
this.wrapper = wrapper;
}
genericError() { appendPreparedErrorMessageElement( errorMessageElement ) {
this.clear(); this._getMessageContainer().replaceWith( errorMessageElement );
this.message(this.genericErrorText) }
}
appendPreparedErrorMessageElement(errorMessageElement) /**
{ * @param {string} text
this._getMessageContainer().replaceWith(errorMessageElement); */
} message( text ) {
this._addMessage( text );
/** this._scrollToMessages();
* @param {String} text }
*/
message(text)
{
this._addMessage(text);
this._scrollToMessages(); /**
} * @param {Array} texts
*/
messages( texts ) {
texts.forEach( ( t ) => this._addMessage( t ) );
/** this._scrollToMessages();
* @param {Array} texts }
*/
messages(texts)
{
texts.forEach(t => this._addMessage(t));
this._scrollToMessages(); /**
} * @return {string}
*/
currentHtml() {
const messageContainer = this._getMessageContainer();
return messageContainer.outerHTML;
}
/** /**
* @returns {String} * @private
*/ * @param {string} text
currentHtml() */
{ _addMessage( text ) {
const messageContainer = this._getMessageContainer(); if ( ! typeof String || text.length === 0 ) {
return messageContainer.outerHTML; throw new Error( 'A new message text must be a non-empty string.' );
} }
/** const messageContainer = this._getMessageContainer();
* @private
* @param {String} text
*/
_addMessage(text)
{
if(! typeof String || text.length === 0) {
throw new Error('A new message text must be a non-empty string.');
}
const messageContainer = this._getMessageContainer(); const messageNode = this._prepareMessageElement( text );
messageContainer.appendChild( messageNode );
}
let messageNode = this._prepareMessageElement(text); /**
messageContainer.appendChild(messageNode); * @private
} */
_scrollToMessages() {
jQuery.scroll_to_notices( jQuery( '.woocommerce-error' ) );
}
/** /**
* @private * @private
*/ */
_scrollToMessages() _getMessageContainer() {
{ let messageContainer = document.querySelector( 'ul.woocommerce-error' );
jQuery.scroll_to_notices(jQuery('.woocommerce-error')); if ( messageContainer === null ) {
} messageContainer = document.createElement( 'ul' );
messageContainer.setAttribute( 'class', 'woocommerce-error' );
messageContainer.setAttribute( 'role', 'alert' );
jQuery( this.wrapper ).prepend( messageContainer );
}
return messageContainer;
}
/** /**
* @private * @param message
*/ * @private
_getMessageContainer() */
{ _prepareMessageElement( message ) {
let messageContainer = document.querySelector('ul.woocommerce-error'); const li = document.createElement( 'li' );
if (messageContainer === null) { li.innerHTML = message;
messageContainer = document.createElement('ul');
messageContainer.setAttribute('class', 'woocommerce-error');
messageContainer.setAttribute('role', 'alert');
jQuery(this.wrapper).prepend(messageContainer);
}
return messageContainer;
}
/** return li;
* @private }
*/
_prepareMessageElement(message)
{
const li = document.createElement('li');
li.innerHTML = message;
return li; clear() {
} jQuery( '.woocommerce-error, .woocommerce-message' ).remove();
}
clear()
{
jQuery( '.woocommerce-error, .woocommerce-message' ).remove();
}
} }
export default ErrorHandler; export default ErrorHandler;

View file

@ -1,120 +1,129 @@
export const apmButtonsInit = ( config, selector = '.ppcp-button-apm' ) => {
let selectorInContainer = selector;
export const apmButtonsInit = (config, selector = '.ppcp-button-apm') => { if ( window.ppcpApmButtons ) {
let selectorInContainer = selector; return;
}
if (window.ppcpApmButtons) { if ( config && config.button ) {
return; // If it's separate gateways, modify wrapper to account for the individual buttons as individual APMs.
} const wrapper = config.button.wrapper;
const isSeparateGateways =
jQuery( wrapper ).children( 'div[class^="item-"]' ).length > 0;
if (config && config.button) { if ( isSeparateGateways ) {
selector += `, ${ wrapper } div[class^="item-"]`;
selectorInContainer += `, div[class^="item-"]`;
}
}
// If it's separate gateways, modify wrapper to account for the individual buttons as individual APMs. window.ppcpApmButtons = new ApmButtons( selector, selectorInContainer );
const wrapper = config.button.wrapper; };
const isSeparateGateways = jQuery(wrapper).children('div[class^="item-"]').length > 0;
if (isSeparateGateways) {
selector += `, ${wrapper} div[class^="item-"]`;
selectorInContainer += `, div[class^="item-"]`;
}
}
window.ppcpApmButtons = new ApmButtons(selector, selectorInContainer);
}
export class ApmButtons { export class ApmButtons {
constructor( selector, selectorInContainer ) {
this.selector = selector;
this.selectorInContainer = selectorInContainer;
this.containers = [];
constructor(selector, selectorInContainer) { // Reloads button containers.
this.selector = selector; this.reloadContainers();
this.selectorInContainer = selectorInContainer;
this.containers = [];
// Reloads button containers. // Refresh button layout.
this.reloadContainers(); jQuery( window )
.resize( () => {
this.refresh();
} )
.resize();
// Refresh button layout. jQuery( document ).on( 'ppcp-smart-buttons-init', () => {
jQuery(window).resize(() => { this.refresh();
this.refresh(); } );
}).resize();
jQuery(document).on('ppcp-smart-buttons-init', () => { jQuery( document ).on(
this.refresh(); 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
}); ( ev, data ) => {
this.refresh();
setTimeout( this.refresh.bind( this ), 200 );
}
);
jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => { // Observes for new buttons.
this.refresh(); new MutationObserver(
setTimeout(this.refresh.bind(this), 200); this.observeElementsCallback.bind( this )
}); ).observe( document.body, { childList: true, subtree: true } );
}
// Observes for new buttons. observeElementsCallback( mutationsList, observer ) {
(new MutationObserver(this.observeElementsCallback.bind(this))) const observeSelector =
.observe(document.body, { childList: true, subtree: true }); this.selector +
} ', .widget_shopping_cart, .widget_shopping_cart_content';
observeElementsCallback(mutationsList, observer) { let shouldReload = false;
const observeSelector = this.selector + ', .widget_shopping_cart, .widget_shopping_cart_content'; for ( const mutation of mutationsList ) {
if ( mutation.type === 'childList' ) {
mutation.addedNodes.forEach( ( node ) => {
if ( node.matches && node.matches( observeSelector ) ) {
shouldReload = true;
}
} );
}
}
let shouldReload = false; if ( shouldReload ) {
for (let mutation of mutationsList) { this.reloadContainers();
if (mutation.type === 'childList') { this.refresh();
mutation.addedNodes.forEach(node => { }
if (node.matches && node.matches(observeSelector)) { }
shouldReload = true;
}
});
}
}
if (shouldReload) { reloadContainers() {
this.reloadContainers(); jQuery( this.selector ).each( ( index, el ) => {
this.refresh(); const parent = jQuery( el ).parent();
} if ( ! this.containers.some( ( $el ) => $el.is( parent ) ) ) {
}; this.containers.push( parent );
}
} );
}
reloadContainers() { refresh() {
jQuery(this.selector).each((index, el) => { for ( const container of this.containers ) {
const parent = jQuery(el).parent(); const $container = jQuery( container );
if (!this.containers.some($el => $el.is(parent))) {
this.containers.push(parent);
}
});
}
refresh() { // Check width and add classes
for (const container of this.containers) { const width = $container.width();
const $container = jQuery(container);
// Check width and add classes $container.removeClass(
const width = $container.width(); 'ppcp-width-500 ppcp-width-300 ppcp-width-min'
);
$container.removeClass('ppcp-width-500 ppcp-width-300 ppcp-width-min'); if ( width >= 500 ) {
$container.addClass( 'ppcp-width-500' );
} else if ( width >= 300 ) {
$container.addClass( 'ppcp-width-300' );
} else {
$container.addClass( 'ppcp-width-min' );
}
if (width >= 500) { // Check first apm button
$container.addClass('ppcp-width-500'); const $firstElement = $container.children( ':visible' ).first();
} else if (width >= 300) {
$container.addClass('ppcp-width-300');
} else {
$container.addClass('ppcp-width-min');
}
// Check first apm button // Assign margins to buttons
const $firstElement = $container.children(':visible').first(); $container.find( this.selectorInContainer ).each( ( index, el ) => {
const $el = jQuery( el );
// Assign margins to buttons if ( $el.is( $firstElement ) ) {
$container.find(this.selectorInContainer).each((index, el) => { $el.css( 'margin-top', `0px` );
const $el = jQuery(el); return true;
}
if ($el.is($firstElement)) {
$el.css('margin-top', `0px`);
return true;
}
const minMargin = 11; // Minimum margin.
const height = $el.height();
const margin = Math.max(minMargin, Math.round(height * 0.3));
$el.css('margin-top', `${margin}px`);
});
}
}
const minMargin = 11; // Minimum margin.
const height = $el.height();
const margin = Math.max(
minMargin,
Math.round( height * 0.3 )
);
$el.css( 'margin-top', `${ margin }px` );
} );
}
}
} }

View file

@ -1,51 +1,54 @@
import {disable, enable, isDisabled} from "./ButtonDisabler"; import { disable, enable, isDisabled } from './ButtonDisabler';
import merge from "deepmerge"; import merge from 'deepmerge';
/** /**
* Common Bootstrap methods to avoid code repetition. * Common Bootstrap methods to avoid code repetition.
*/ */
export default class BootstrapHelper { export default class BootstrapHelper {
static handleButtonStatus( bs, options ) {
options = options || {};
options.wrapper = options.wrapper || bs.gateway.button.wrapper;
static handleButtonStatus(bs, options) { const wasDisabled = isDisabled( options.wrapper );
options = options || {}; const shouldEnable = bs.shouldEnable();
options.wrapper = options.wrapper || bs.gateway.button.wrapper;
const wasDisabled = isDisabled(options.wrapper); // Handle enable / disable
const shouldEnable = bs.shouldEnable(); if ( shouldEnable && wasDisabled ) {
bs.renderer.enableSmartButtons( options.wrapper );
enable( options.wrapper );
} else if ( ! shouldEnable && ! wasDisabled ) {
bs.renderer.disableSmartButtons( options.wrapper );
disable( options.wrapper, options.formSelector || null );
}
// Handle enable / disable if ( wasDisabled !== ! shouldEnable ) {
if (shouldEnable && wasDisabled) { jQuery( options.wrapper ).trigger( 'ppcp_buttons_enabled_changed', [
bs.renderer.enableSmartButtons(options.wrapper); shouldEnable,
enable(options.wrapper); ] );
} else if (!shouldEnable && !wasDisabled) { }
bs.renderer.disableSmartButtons(options.wrapper); }
disable(options.wrapper, options.formSelector || null);
}
if (wasDisabled !== !shouldEnable) { static shouldEnable( bs, options ) {
jQuery(options.wrapper).trigger('ppcp_buttons_enabled_changed', [shouldEnable]); options = options || {};
} if ( typeof options.isDisabled === 'undefined' ) {
} options.isDisabled = bs.gateway.button.is_disabled;
}
static shouldEnable(bs, options) { return bs.shouldRender() && options.isDisabled !== true;
options = options || {}; }
if (typeof options.isDisabled === 'undefined') {
options.isDisabled = bs.gateway.button.is_disabled;
}
return bs.shouldRender() static updateScriptData( bs, newData ) {
&& options.isDisabled !== true; const newObj = merge( bs.gateway, newData );
}
static updateScriptData(bs, newData) { const isChanged =
const newObj = merge(bs.gateway, newData); JSON.stringify( bs.gateway ) !== JSON.stringify( newObj );
const isChanged = JSON.stringify(bs.gateway) !== JSON.stringify(newObj); bs.gateway = newObj;
bs.gateway = newObj; if ( isChanged ) {
jQuery( document.body ).trigger( 'ppcp_script_data_changed', [
if (isChanged) { newObj,
jQuery(document.body).trigger('ppcp_script_data_changed', [newObj]); ] );
} }
} }
} }

View file

@ -1,83 +1,86 @@
/** /**
* @param selectorOrElement * @param selectorOrElement
* @returns {Element} * @return {Element}
*/ */
const getElement = (selectorOrElement) => { const getElement = ( selectorOrElement ) => {
if (typeof selectorOrElement === 'string') { if ( typeof selectorOrElement === 'string' ) {
return document.querySelector(selectorOrElement); return document.querySelector( selectorOrElement );
} }
return selectorOrElement; return selectorOrElement;
}
const triggerEnabled = (selectorOrElement, element) => {
jQuery(document).trigger('ppcp-enabled', {
'handler': 'ButtonsDisabler.setEnabled',
'action': 'enable',
'selector': selectorOrElement,
'element': element
});
}
const triggerDisabled = (selectorOrElement, element) => {
jQuery(document).trigger('ppcp-disabled', {
'handler': 'ButtonsDisabler.setEnabled',
'action': 'disable',
'selector': selectorOrElement,
'element': element
});
}
export const setEnabled = (selectorOrElement, enable, form = null) => {
const element = getElement(selectorOrElement);
if (!element) {
return;
}
if (enable) {
jQuery(element)
.removeClass('ppcp-disabled')
.off('mouseup')
.find('> *')
.css('pointer-events', '');
triggerEnabled(selectorOrElement, element);
} else {
jQuery(element)
.addClass('ppcp-disabled')
.on('mouseup', function(event) {
event.stopImmediatePropagation();
if (form) {
// Trigger form submit to show the error message
let $form = jQuery(form);
if ($form.find('.single_add_to_cart_button').hasClass('disabled')) {
$form.find(':submit').trigger('click');
}
}
})
.find('> *')
.css('pointer-events', 'none');
triggerDisabled(selectorOrElement, element);
}
}; };
export const isDisabled = (selectorOrElement) => { const triggerEnabled = ( selectorOrElement, element ) => {
const element = getElement(selectorOrElement); jQuery( document ).trigger( 'ppcp-enabled', {
handler: 'ButtonsDisabler.setEnabled',
if (!element) { action: 'enable',
return false; selector: selectorOrElement,
} element,
} );
return jQuery(element).hasClass('ppcp-disabled');
}; };
export const disable = (selectorOrElement, form = null) => { const triggerDisabled = ( selectorOrElement, element ) => {
setEnabled(selectorOrElement, false, form); jQuery( document ).trigger( 'ppcp-disabled', {
handler: 'ButtonsDisabler.setEnabled',
action: 'disable',
selector: selectorOrElement,
element,
} );
}; };
export const enable = (selectorOrElement) => { export const setEnabled = ( selectorOrElement, enable, form = null ) => {
setEnabled(selectorOrElement, true); const element = getElement( selectorOrElement );
if ( ! element ) {
return;
}
if ( enable ) {
jQuery( element )
.removeClass( 'ppcp-disabled' )
.off( 'mouseup' )
.find( '> *' )
.css( 'pointer-events', '' );
triggerEnabled( selectorOrElement, element );
} else {
jQuery( element )
.addClass( 'ppcp-disabled' )
.on( 'mouseup', function ( event ) {
event.stopImmediatePropagation();
if ( form ) {
// Trigger form submit to show the error message
const $form = jQuery( form );
if (
$form
.find( '.single_add_to_cart_button' )
.hasClass( 'disabled' )
) {
$form.find( ':submit' ).trigger( 'click' );
}
}
} )
.find( '> *' )
.css( 'pointer-events', 'none' );
triggerDisabled( selectorOrElement, element );
}
};
export const isDisabled = ( selectorOrElement ) => {
const element = getElement( selectorOrElement );
if ( ! element ) {
return false;
}
return jQuery( element ).hasClass( 'ppcp-disabled' );
};
export const disable = ( selectorOrElement, form = null ) => {
setEnabled( selectorOrElement, false, form );
};
export const enable = ( selectorOrElement ) => {
setEnabled( selectorOrElement, true );
}; };

View file

@ -9,7 +9,7 @@ const REFRESH_BUTTON_EVENT = 'ppcp_refresh_payment_buttons';
* Use this function on the front-end to update payment buttons after the checkout form was updated. * Use this function on the front-end to update payment buttons after the checkout form was updated.
*/ */
export function refreshButtons() { export function refreshButtons() {
document.dispatchEvent(new Event(REFRESH_BUTTON_EVENT)); document.dispatchEvent( new Event( REFRESH_BUTTON_EVENT ) );
} }
/** /**
@ -18,20 +18,26 @@ export function refreshButtons() {
* *
* @param {Function} refresh - Callback responsible to re-render the payment button. * @param {Function} refresh - Callback responsible to re-render the payment button.
*/ */
export function setupButtonEvents(refresh) { export function setupButtonEvents( refresh ) {
const miniCartInitDelay = 1000; const miniCartInitDelay = 1000;
const debouncedRefresh = debounce(refresh, 50); const debouncedRefresh = debounce( refresh, 50 );
// Listen for our custom refresh event. // Listen for our custom refresh event.
document.addEventListener(REFRESH_BUTTON_EVENT, debouncedRefresh); document.addEventListener( REFRESH_BUTTON_EVENT, debouncedRefresh );
// Listen for cart and checkout update events. // Listen for cart and checkout update events.
document.body.addEventListener('updated_cart_totals', debouncedRefresh); document.body.addEventListener( 'updated_cart_totals', debouncedRefresh );
document.body.addEventListener('updated_checkout', debouncedRefresh); document.body.addEventListener( 'updated_checkout', debouncedRefresh );
// Use setTimeout for fragment events to avoid unnecessary refresh on initial render. // Use setTimeout for fragment events to avoid unnecessary refresh on initial render.
setTimeout(() => { setTimeout( () => {
document.body.addEventListener('wc_fragments_loaded', debouncedRefresh); document.body.addEventListener(
document.body.addEventListener('wc_fragments_refreshed', debouncedRefresh); 'wc_fragments_loaded',
}, miniCartInitDelay); debouncedRefresh
);
document.body.addEventListener(
'wc_fragments_refreshed',
debouncedRefresh
);
}, miniCartInitDelay );
} }

View file

@ -1,50 +1,50 @@
export const cardFieldStyles = (field) => { export const cardFieldStyles = ( field ) => {
const allowedProperties = [ const allowedProperties = [
'appearance', 'appearance',
'color', 'color',
'direction', 'direction',
'font', 'font',
'font-family', 'font-family',
'font-size', 'font-size',
'font-size-adjust', 'font-size-adjust',
'font-stretch', 'font-stretch',
'font-style', 'font-style',
'font-variant', 'font-variant',
'font-variant-alternates', 'font-variant-alternates',
'font-variant-caps', 'font-variant-caps',
'font-variant-east-asian', 'font-variant-east-asian',
'font-variant-ligatures', 'font-variant-ligatures',
'font-variant-numeric', 'font-variant-numeric',
'font-weight', 'font-weight',
'letter-spacing', 'letter-spacing',
'line-height', 'line-height',
'opacity', 'opacity',
'outline', 'outline',
'padding', 'padding',
'padding-bottom', 'padding-bottom',
'padding-left', 'padding-left',
'padding-right', 'padding-right',
'padding-top', 'padding-top',
'text-shadow', 'text-shadow',
'transition', 'transition',
'-moz-appearance', '-moz-appearance',
'-moz-osx-font-smoothing', '-moz-osx-font-smoothing',
'-moz-tap-highlight-color', '-moz-tap-highlight-color',
'-moz-transition', '-moz-transition',
'-webkit-appearance', '-webkit-appearance',
'-webkit-osx-font-smoothing', '-webkit-osx-font-smoothing',
'-webkit-tap-highlight-color', '-webkit-tap-highlight-color',
'-webkit-transition', '-webkit-transition',
]; ];
const stylesRaw = window.getComputedStyle(field); const stylesRaw = window.getComputedStyle( field );
const styles = {}; const styles = {};
Object.values(stylesRaw).forEach((prop) => { Object.values( stylesRaw ).forEach( ( prop ) => {
if (!stylesRaw[prop] || !allowedProperties.includes(prop)) { if ( ! stylesRaw[ prop ] || ! allowedProperties.includes( prop ) ) {
return; return;
} }
styles[prop] = '' + stylesRaw[prop]; styles[ prop ] = '' + stylesRaw[ prop ];
}); } );
return styles; return styles;
} };

View file

@ -1,74 +1,77 @@
class CartHelper { class CartHelper {
constructor( cartItemKeys = [] ) {
this.cartItemKeys = cartItemKeys;
}
constructor(cartItemKeys = []) getEndpoint() {
{ let ajaxUrl = '/?wc-ajax=%%endpoint%%';
this.cartItemKeys = cartItemKeys;
}
getEndpoint() { if (
let ajaxUrl = "/?wc-ajax=%%endpoint%%"; typeof wc_cart_fragments_params !== 'undefined' &&
wc_cart_fragments_params.wc_ajax_url
) {
ajaxUrl = wc_cart_fragments_params.wc_ajax_url;
}
if ((typeof wc_cart_fragments_params !== 'undefined') && wc_cart_fragments_params.wc_ajax_url) { return ajaxUrl.toString().replace( '%%endpoint%%', 'remove_from_cart' );
ajaxUrl = wc_cart_fragments_params.wc_ajax_url; }
}
return ajaxUrl.toString().replace('%%endpoint%%', 'remove_from_cart'); addFromPurchaseUnits( purchaseUnits ) {
} for ( const purchaseUnit of purchaseUnits || [] ) {
for ( const item of purchaseUnit.items || [] ) {
if ( ! item.cart_item_key ) {
continue;
}
this.cartItemKeys.push( item.cart_item_key );
}
}
addFromPurchaseUnits(purchaseUnits) { return this;
for (const purchaseUnit of purchaseUnits || []) { }
for (const item of purchaseUnit.items || []) {
if (!item.cart_item_key) {
continue;
}
this.cartItemKeys.push(item.cart_item_key);
}
}
return this; removeFromCart() {
} return new Promise( ( resolve, reject ) => {
if ( ! this.cartItemKeys || ! this.cartItemKeys.length ) {
resolve();
return;
}
removeFromCart() const numRequests = this.cartItemKeys.length;
{ let numResponses = 0;
return new Promise((resolve, reject) => {
if (!this.cartItemKeys || !this.cartItemKeys.length) {
resolve();
return;
}
const numRequests = this.cartItemKeys.length; const tryToResolve = () => {
let numResponses = 0; numResponses++;
if ( numResponses >= numRequests ) {
resolve();
}
};
const tryToResolve = () => { for ( const cartItemKey of this.cartItemKeys ) {
numResponses++; const params = new URLSearchParams();
if (numResponses >= numRequests) { params.append( 'cart_item_key', cartItemKey );
resolve();
}
}
for (const cartItemKey of this.cartItemKeys) { if ( ! cartItemKey ) {
const params = new URLSearchParams(); tryToResolve();
params.append('cart_item_key', cartItemKey); continue;
}
if (!cartItemKey) { fetch( this.getEndpoint(), {
tryToResolve(); method: 'POST',
continue; credentials: 'same-origin',
} body: params,
} )
fetch(this.getEndpoint(), { .then( function ( res ) {
method: 'POST', return res.json();
credentials: 'same-origin', } )
body: params .then( () => {
}).then(function (res) { tryToResolve();
return res.json(); } )
}).then(() => { .catch( () => {
tryToResolve(); tryToResolve();
}).catch(() => { } );
tryToResolve(); }
}); } );
} }
});
}
} }
export default CartHelper; export default CartHelper;

View file

@ -1,48 +1,55 @@
import Spinner from "./Spinner"; import Spinner from './Spinner';
import FormValidator from "./FormValidator"; import FormValidator from './FormValidator';
import ErrorHandler from "../ErrorHandler"; import ErrorHandler from '../ErrorHandler';
const validateCheckoutForm = function (config) { const validateCheckoutForm = function ( config ) {
return new Promise(async (resolve, reject) => { return new Promise( async ( resolve, reject ) => {
try { try {
const spinner = new Spinner(); const spinner = new Spinner();
const errorHandler = new ErrorHandler( const errorHandler = new ErrorHandler(
config.labels.error.generic, config.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper') document.querySelector( '.woocommerce-notices-wrapper' )
); );
const formSelector = config.context === 'checkout' ? 'form.checkout' : 'form#order_review'; const formSelector =
const formValidator = config.early_checkout_validation_enabled ? config.context === 'checkout'
new FormValidator( ? 'form.checkout'
config.ajax.validate_checkout.endpoint, : 'form#order_review';
config.ajax.validate_checkout.nonce, const formValidator = config.early_checkout_validation_enabled
) : null; ? new FormValidator(
config.ajax.validate_checkout.endpoint,
config.ajax.validate_checkout.nonce
)
: null;
if (!formValidator) { if ( ! formValidator ) {
resolve(); resolve();
return; return;
} }
formValidator.validate(document.querySelector(formSelector)).then((errors) => { formValidator
if (errors.length > 0) { .validate( document.querySelector( formSelector ) )
spinner.unblock(); .then( ( errors ) => {
errorHandler.clear(); if ( errors.length > 0 ) {
errorHandler.messages(errors); spinner.unblock();
errorHandler.clear();
errorHandler.messages( errors );
// fire WC event for other plugins // fire WC event for other plugins
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] ); jQuery( document.body ).trigger( 'checkout_error', [
errorHandler.currentHtml(),
] );
reject(); reject();
} else { } else {
resolve(); resolve();
} }
}); } );
} catch ( error ) {
} catch (error) { console.error( error );
console.error(error); reject();
reject(); }
} } );
}); };
}
export default validateCheckoutForm; export default validateCheckoutForm;

View file

@ -1,22 +1,23 @@
export const PaymentMethods = { export const PaymentMethods = {
PAYPAL: 'ppcp-gateway', PAYPAL: 'ppcp-gateway',
CARDS: 'ppcp-credit-card-gateway', CARDS: 'ppcp-credit-card-gateway',
OXXO: 'ppcp-oxxo-gateway', OXXO: 'ppcp-oxxo-gateway',
CARD_BUTTON: 'ppcp-card-button-gateway', CARD_BUTTON: 'ppcp-card-button-gateway',
GOOGLEPAY: 'ppcp-googlepay',
}; };
export const ORDER_BUTTON_SELECTOR = '#place_order'; export const ORDER_BUTTON_SELECTOR = '#place_order';
export const getCurrentPaymentMethod = () => { export const getCurrentPaymentMethod = () => {
const el = document.querySelector('input[name="payment_method"]:checked'); const el = document.querySelector( 'input[name="payment_method"]:checked' );
if (!el) { if ( ! el ) {
return null; return null;
} }
return el.value; return el.value;
}; };
export const isSavedCardSelected = () => { export const isSavedCardSelected = () => {
const savedCardList = document.querySelector('#saved-credit-card'); const savedCardList = document.querySelector( '#saved-credit-card' );
return savedCardList && savedCardList.value !== ''; return savedCardList && savedCardList.value !== '';
}; };

View file

@ -1,17 +1,21 @@
const dccInputFactory = (original) => { const dccInputFactory = ( original ) => {
const styles = window.getComputedStyle(original); const styles = window.getComputedStyle( original );
const newElement = document.createElement('span'); const newElement = document.createElement( 'span' );
newElement.setAttribute('id', original.id); newElement.setAttribute( 'id', original.id );
newElement.setAttribute('class', original.className); newElement.setAttribute( 'class', original.className );
Object.values(styles).forEach( (prop) => { Object.values( styles ).forEach( ( prop ) => {
if (! styles[prop] || ! isNaN(prop) || prop === 'background-image' ) { if (
return; ! styles[ prop ] ||
} ! isNaN( prop ) ||
newElement.style.setProperty(prop,'' + styles[prop]); prop === 'background-image'
}); ) {
return newElement; return;
} }
newElement.style.setProperty( prop, '' + styles[ prop ] );
} );
return newElement;
};
export default dccInputFactory; export default dccInputFactory;

View file

@ -1,50 +1,52 @@
/** /**
* Common Form utility methods * Common Form utility methods
*/ */
export default class FormHelper { export default class FormHelper {
static getPrefixedFields( formElement, prefix ) {
const formData = new FormData( formElement );
const fields = {};
static getPrefixedFields(formElement, prefix) { for ( const [ name, value ] of formData.entries() ) {
const formData = new FormData(formElement); if ( ! prefix || name.startsWith( prefix ) ) {
let fields = {}; fields[ name ] = value;
}
}
for (const [name, value] of formData.entries()) { return fields;
if (!prefix || name.startsWith(prefix)) { }
fields[name] = value;
}
}
return fields; static getFilteredFields( formElement, exactFilters, prefixFilters ) {
} const formData = new FormData( formElement );
const fields = {};
const counters = {};
static getFilteredFields(formElement, exactFilters, prefixFilters) { for ( let [ name, value ] of formData.entries() ) {
const formData = new FormData(formElement); // Handle array format
let fields = {}; if ( name.indexOf( '[]' ) !== -1 ) {
let counters = {}; const k = name;
counters[ k ] = counters[ k ] || 0;
name = name.replace( '[]', `[${ counters[ k ] }]` );
counters[ k ]++;
}
for (let [name, value] of formData.entries()) { if ( ! name ) {
continue;
}
if ( exactFilters && exactFilters.indexOf( name ) !== -1 ) {
continue;
}
if (
prefixFilters &&
prefixFilters.some( ( prefixFilter ) =>
name.startsWith( prefixFilter )
)
) {
continue;
}
// Handle array format fields[ name ] = value;
if (name.indexOf('[]') !== -1) { }
const k = name;
counters[k] = counters[k] || 0;
name = name.replace('[]', `[${counters[k]}]`);
counters[k]++;
}
if (!name) { return fields;
continue; }
}
if (exactFilters && (exactFilters.indexOf(name) !== -1)) {
continue;
}
if (prefixFilters && prefixFilters.some(prefixFilter => name.startsWith(prefixFilter))) {
continue;
}
fields[name] = value;
}
return fields;
}
} }

View file

@ -1,28 +1,28 @@
export default class FormSaver { export default class FormSaver {
constructor(url, nonce) { constructor( url, nonce ) {
this.url = url; this.url = url;
this.nonce = nonce; this.nonce = nonce;
} }
async save(form) { async save( form ) {
const formData = new FormData(form); const formData = new FormData( form );
const res = await fetch(this.url, { const res = await fetch( this.url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: this.nonce, nonce: this.nonce,
form_encoded: new URLSearchParams(formData).toString(), form_encoded: new URLSearchParams( formData ).toString(),
}), } ),
}); } );
const data = await res.json(); const data = await res.json();
if (!data.success) { if ( ! data.success ) {
throw Error(data.data.message); throw Error( data.data.message );
} }
} }
} }

View file

@ -1,37 +1,37 @@
export default class FormValidator { export default class FormValidator {
constructor(url, nonce) { constructor( url, nonce ) {
this.url = url; this.url = url;
this.nonce = nonce; this.nonce = nonce;
} }
async validate(form) { async validate( form ) {
const formData = new FormData(form); const formData = new FormData( form );
const res = await fetch(this.url, { const res = await fetch( this.url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: this.nonce, nonce: this.nonce,
form_encoded: new URLSearchParams(formData).toString(), form_encoded: new URLSearchParams( formData ).toString(),
}), } ),
}); } );
const data = await res.json(); const data = await res.json();
if (!data.success) { if ( ! data.success ) {
if (data.data.refresh) { if ( data.data.refresh ) {
jQuery( document.body ).trigger( 'update_checkout' ); jQuery( document.body ).trigger( 'update_checkout' );
} }
if (data.data.errors) { if ( data.data.errors ) {
return data.data.errors; return data.data.errors;
} }
throw Error(data.data.message); throw Error( data.data.message );
} }
return []; return [];
} }
} }

View file

@ -1,85 +1,92 @@
/** /**
* @param selectorOrElement * @param selectorOrElement
* @returns {Element} * @return {Element}
*/ */
const getElement = (selectorOrElement) => { const getElement = ( selectorOrElement ) => {
if (typeof selectorOrElement === 'string') { if ( typeof selectorOrElement === 'string' ) {
return document.querySelector(selectorOrElement); return document.querySelector( selectorOrElement );
} }
return selectorOrElement; return selectorOrElement;
}
const triggerHidden = (handler, selectorOrElement, element) => {
jQuery(document).trigger('ppcp-hidden', {
'handler': handler,
'action': 'hide',
'selector': selectorOrElement,
'element': element
});
}
const triggerShown = (handler, selectorOrElement, element) => {
jQuery(document).trigger('ppcp-shown', {
'handler': handler,
'action': 'show',
'selector': selectorOrElement,
'element': element
});
}
export const isVisible = (element) => {
return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}
export const setVisible = (selectorOrElement, show, important = false) => {
const element = getElement(selectorOrElement);
if (!element) {
return;
}
const currentValue = element.style.getPropertyValue('display');
if (!show) {
if (currentValue === 'none') {
return;
}
element.style.setProperty('display', 'none', important ? 'important' : '');
triggerHidden('Hiding.setVisible', selectorOrElement, element);
} else {
if (currentValue === 'none') {
element.style.removeProperty('display');
triggerShown('Hiding.setVisible', selectorOrElement, element);
}
// still not visible (if something else added display: none in CSS)
if (!isVisible(element)) {
element.style.setProperty('display', 'block');
triggerShown('Hiding.setVisible', selectorOrElement, element);
}
}
}; };
export const setVisibleByClass = (selectorOrElement, show, hiddenClass) => { const triggerHidden = ( handler, selectorOrElement, element ) => {
const element = getElement(selectorOrElement); jQuery( document ).trigger( 'ppcp-hidden', {
if (!element) { handler,
return; action: 'hide',
} selector: selectorOrElement,
element,
if (show) { } );
element.classList.remove(hiddenClass);
triggerShown('Hiding.setVisibleByClass', selectorOrElement, element);
} else {
element.classList.add(hiddenClass);
triggerHidden('Hiding.setVisibleByClass', selectorOrElement, element);
}
}; };
export const hide = (selectorOrElement, important = false) => { const triggerShown = ( handler, selectorOrElement, element ) => {
setVisible(selectorOrElement, false, important); jQuery( document ).trigger( 'ppcp-shown', {
handler,
action: 'show',
selector: selectorOrElement,
element,
} );
}; };
export const show = (selectorOrElement) => { export const isVisible = ( element ) => {
setVisible(selectorOrElement, true); return !! (
element.offsetWidth ||
element.offsetHeight ||
element.getClientRects().length
);
};
export const setVisible = ( selectorOrElement, show, important = false ) => {
const element = getElement( selectorOrElement );
if ( ! element ) {
return;
}
const currentValue = element.style.getPropertyValue( 'display' );
if ( ! show ) {
if ( currentValue === 'none' ) {
return;
}
element.style.setProperty(
'display',
'none',
important ? 'important' : ''
);
triggerHidden( 'Hiding.setVisible', selectorOrElement, element );
} else {
if ( currentValue === 'none' ) {
element.style.removeProperty( 'display' );
triggerShown( 'Hiding.setVisible', selectorOrElement, element );
}
// still not visible (if something else added display: none in CSS)
if ( ! isVisible( element ) ) {
element.style.setProperty( 'display', 'block' );
triggerShown( 'Hiding.setVisible', selectorOrElement, element );
}
}
};
export const setVisibleByClass = ( selectorOrElement, show, hiddenClass ) => {
const element = getElement( selectorOrElement );
if ( ! element ) {
return;
}
if ( show ) {
element.classList.remove( hiddenClass );
triggerShown( 'Hiding.setVisibleByClass', selectorOrElement, element );
} else {
element.classList.add( hiddenClass );
triggerHidden( 'Hiding.setVisibleByClass', selectorOrElement, element );
}
};
export const hide = ( selectorOrElement, important = false ) => {
setVisible( selectorOrElement, false, important );
};
export const show = ( selectorOrElement ) => {
setVisible( selectorOrElement, true );
}; };

View file

@ -9,112 +9,115 @@ const DEFAULT_TRIGGER_ELEMENT_SELECTOR = '.woocommerce-checkout-payment';
* invisibility period of wrappers, some payment buttons fail to initialize, * invisibility period of wrappers, some payment buttons fail to initialize,
* so we wait for the payment element to be visible. * so we wait for the payment element to be visible.
* *
* @property {HTMLElement} form - Checkout form element. * @property {HTMLElement} form - Checkout form element.
* @property {HTMLElement} triggerElement - Element, which visibility we need to detect. * @property {HTMLElement} triggerElement - Element, which visibility we need to detect.
* @property {boolean} isVisible - Whether the triggerElement is visible. * @property {boolean} isVisible - Whether the triggerElement is visible.
*/ */
class MultistepCheckoutHelper { class MultistepCheckoutHelper {
/**
* Selector that defines the HTML element we are waiting to become visible.
* @type {string}
*/
#triggerElementSelector;
/** /**
* Selector that defines the HTML element we are waiting to become visible. * Interval (in milliseconds) in which the visibility of the trigger element is checked.
* @type {string} * @type {number}
*/ */
#triggerElementSelector; #intervalTime = 150;
/** /**
* Interval (in milliseconds) in which the visibility of the trigger element is checked. * The interval ID returned by the setInterval() method.
* @type {number} * @type {number|false}
*/ */
#intervalTime = 150; #intervalId;
/** /**
* The interval ID returned by the setInterval() method. * Selector passed to the constructor that identifies the checkout form
* @type {number|false} * @type {string}
*/ */
#intervalId; #formSelector;
/** /**
* Selector passed to the constructor that identifies the checkout form * @param {string} formSelector - Selector of the checkout form
* @type {string} * @param {string} triggerElementSelector - Optional. Selector of the dependant element.
*/ */
#formSelector; constructor( formSelector, triggerElementSelector = '' ) {
this.#formSelector = formSelector;
this.#triggerElementSelector =
triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR;
this.#intervalId = false;
/** /*
* @param {string} formSelector - Selector of the checkout form
* @param {string} triggerElementSelector - Optional. Selector of the dependant element.
*/
constructor(formSelector, triggerElementSelector = '') {
this.#formSelector = formSelector;
this.#triggerElementSelector = triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR;
this.#intervalId = false;
/*
Start the visibility checker after a brief delay. This allows eventual multistep plugins to Start the visibility checker after a brief delay. This allows eventual multistep plugins to
dynamically prepare the checkout page, so we can decide whether this helper is needed. dynamically prepare the checkout page, so we can decide whether this helper is needed.
*/ */
setTimeout(() => { setTimeout( () => {
if (this.form && !this.isVisible) { if ( this.form && ! this.isVisible ) {
this.start(); this.start();
} }
}, 250); }, 250 );
} }
/** /**
* The checkout form element. * The checkout form element.
* @returns {Element|null} - Form element or null. * @return {Element|null} - Form element or null.
*/ */
get form() { get form() {
return document.querySelector(this.#formSelector); return document.querySelector( this.#formSelector );
} }
/** /**
* The element which must be visible before payment buttons should be initialized. * The element which must be visible before payment buttons should be initialized.
* @returns {Element|null} - Trigger element or null. * @return {Element|null} - Trigger element or null.
*/ */
get triggerElement() { get triggerElement() {
return this.form?.querySelector(this.#triggerElementSelector); return this.form?.querySelector( this.#triggerElementSelector );
} }
/** /**
* Checks the visibility of the payment button wrapper. * Checks the visibility of the payment button wrapper.
* @returns {boolean} - returns boolean value on the basis of visibility of element. * @return {boolean} - returns boolean value on the basis of visibility of element.
*/ */
get isVisible() { get isVisible() {
const box = this.triggerElement?.getBoundingClientRect(); const box = this.triggerElement?.getBoundingClientRect();
return !!(box && box.width && box.height); return !! ( box && box.width && box.height );
} }
/** /**
* Starts the observation of the DOM, initiates monitoring the checkout form. * Starts the observation of the DOM, initiates monitoring the checkout form.
* To ensure multiple calls to start don't create multiple intervals, we first call stop. * To ensure multiple calls to start don't create multiple intervals, we first call stop.
*/ */
start() { start() {
this.stop(); this.stop();
this.#intervalId = setInterval(() => this.checkElement(), this.#intervalTime); this.#intervalId = setInterval(
} () => this.checkElement(),
this.#intervalTime
);
}
/** /**
* Stops the observation of the checkout form. * Stops the observation of the checkout form.
* Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist. * Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist.
*/ */
stop() { stop() {
if (this.#intervalId) { if ( this.#intervalId ) {
clearInterval(this.#intervalId); clearInterval( this.#intervalId );
this.#intervalId = false; this.#intervalId = false;
} }
} }
/** /**
* Checks if the trigger element is visible. * Checks if the trigger element is visible.
* If visible, it initialises the payment buttons and stops the observation. * If visible, it initialises the payment buttons and stops the observation.
*/ */
checkElement() { checkElement() {
if (this.isVisible) { if ( this.isVisible ) {
refreshButtons(); refreshButtons();
this.stop(); this.stop();
} }
} }
} }
export default MultistepCheckoutHelper; export default MultistepCheckoutHelper;

View file

@ -1,34 +1,59 @@
export const payerData = () => { export const payerData = () => {
const payer = PayPalCommerceGateway.payer; const payer = PayPalCommerceGateway.payer;
if (! payer) { if ( ! payer ) {
return null; return null;
} }
const phone = (document.querySelector('#billing_phone') || typeof payer.phone !== 'undefined') ? const phone =
{ document.querySelector( '#billing_phone' ) ||
phone_type:"HOME", typeof payer.phone !== 'undefined'
phone_number:{ ? {
national_number : (document.querySelector('#billing_phone')) ? document.querySelector('#billing_phone').value : payer.phone.phone_number.national_number phone_type: 'HOME',
} phone_number: {
} : null; national_number: document.querySelector(
const payerData = { '#billing_phone'
email_address:(document.querySelector('#billing_email')) ? document.querySelector('#billing_email').value : payer.email_address, )
name : { ? document.querySelector( '#billing_phone' ).value
surname: (document.querySelector('#billing_last_name')) ? document.querySelector('#billing_last_name').value : payer.name.surname, : payer.phone.phone_number.national_number,
given_name: (document.querySelector('#billing_first_name')) ? document.querySelector('#billing_first_name').value : payer.name.given_name },
}, }
address : { : null;
country_code : (document.querySelector('#billing_country')) ? document.querySelector('#billing_country').value : payer.address.country_code, const payerData = {
address_line_1 : (document.querySelector('#billing_address_1')) ? document.querySelector('#billing_address_1').value : payer.address.address_line_1, email_address: document.querySelector( '#billing_email' )
address_line_2 : (document.querySelector('#billing_address_2')) ? document.querySelector('#billing_address_2').value : payer.address.address_line_2, ? document.querySelector( '#billing_email' ).value
admin_area_1 : (document.querySelector('#billing_state')) ? document.querySelector('#billing_state').value : payer.address.admin_area_1, : payer.email_address,
admin_area_2 : (document.querySelector('#billing_city')) ? document.querySelector('#billing_city').value : payer.address.admin_area_2, name: {
postal_code : (document.querySelector('#billing_postcode')) ? document.querySelector('#billing_postcode').value : payer.address.postal_code surname: document.querySelector( '#billing_last_name' )
} ? document.querySelector( '#billing_last_name' ).value
}; : payer.name.surname,
given_name: document.querySelector( '#billing_first_name' )
? document.querySelector( '#billing_first_name' ).value
: payer.name.given_name,
},
address: {
country_code: document.querySelector( '#billing_country' )
? document.querySelector( '#billing_country' ).value
: payer.address.country_code,
address_line_1: document.querySelector( '#billing_address_1' )
? document.querySelector( '#billing_address_1' ).value
: payer.address.address_line_1,
address_line_2: document.querySelector( '#billing_address_2' )
? document.querySelector( '#billing_address_2' ).value
: payer.address.address_line_2,
admin_area_1: document.querySelector( '#billing_state' )
? document.querySelector( '#billing_state' ).value
: payer.address.admin_area_1,
admin_area_2: document.querySelector( '#billing_city' )
? document.querySelector( '#billing_city' ).value
: payer.address.admin_area_2,
postal_code: document.querySelector( '#billing_postcode' )
? document.querySelector( '#billing_postcode' ).value
: payer.address.postal_code,
},
};
if (phone) { if ( phone ) {
payerData.phone = phone; payerData.phone = phone;
} }
return payerData; return payerData;
} };

View file

@ -1,109 +1,110 @@
import dataClientIdAttributeHandler from "../DataClientIdAttributeHandler"; import dataClientIdAttributeHandler from '../DataClientIdAttributeHandler';
import {loadScript} from "@paypal/paypal-js"; import { loadScript } from '@paypal/paypal-js';
import widgetBuilder from "../Renderer/WidgetBuilder"; import widgetBuilder from '../Renderer/WidgetBuilder';
import merge from "deepmerge"; import merge from 'deepmerge';
import {keysToCamelCase} from "./Utils"; import { keysToCamelCase } from './Utils';
import {getCurrentPaymentMethod} from "./CheckoutMethodState"; import { getCurrentPaymentMethod } from './CheckoutMethodState';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
// This component may be used by multiple modules. This assures that options are shared between all instances. // This component may be used by multiple modules. This assures that options are shared between all instances.
let options = window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || { const options = ( window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || {
isLoading: false, isLoading: false,
onLoadedCallbacks: [], onLoadedCallbacks: [],
onErrorCallbacks: [], onErrorCallbacks: [],
} );
export const loadPaypalScript = ( config, onLoaded, onError = null ) => {
// If PayPal is already loaded call the onLoaded callback and return.
if ( typeof paypal !== 'undefined' ) {
onLoaded();
return;
}
// Add the onLoaded callback to the onLoadedCallbacks stack.
options.onLoadedCallbacks.push( onLoaded );
if ( onError ) {
options.onErrorCallbacks.push( onError );
}
// Return if it's still loading.
if ( options.isLoading ) {
return;
}
options.isLoading = true;
const resetState = () => {
options.isLoading = false;
options.onLoadedCallbacks = [];
options.onErrorCallbacks = [];
};
// Callback to be called once the PayPal script is loaded.
const callback = ( paypal ) => {
widgetBuilder.setPaypal( paypal );
for ( const onLoadedCallback of options.onLoadedCallbacks ) {
onLoadedCallback();
}
resetState();
};
const errorCallback = ( err ) => {
for ( const onErrorCallback of options.onErrorCallbacks ) {
onErrorCallback( err );
}
resetState();
};
// Build the PayPal script options.
let scriptOptions = keysToCamelCase( config.url_params );
if ( config.script_attributes ) {
scriptOptions = merge( scriptOptions, config.script_attributes );
}
// Axo SDK options
const sdkClientToken = config?.axo?.sdk_client_token;
const uuid = uuidv4().replace( /-/g, '' );
if ( sdkClientToken ) {
scriptOptions[ 'data-sdk-client-token' ] = sdkClientToken;
scriptOptions[ 'data-client-metadata-id' ] = uuid;
}
// Load PayPal script for special case with data-client-token
if ( config.data_client_id?.set_attribute ) {
dataClientIdAttributeHandler(
scriptOptions,
config.data_client_id,
callback,
errorCallback
);
return;
}
// Adds data-user-id-token to script options.
const userIdToken = config?.save_payment_methods?.id_token;
if ( userIdToken && ! sdkClientToken ) {
scriptOptions[ 'data-user-id-token' ] = userIdToken;
}
// Load PayPal script
loadScript( scriptOptions ).then( callback ).catch( errorCallback );
}; };
export const loadPaypalScript = (config, onLoaded, onError = null) => { export const loadPaypalScriptPromise = ( config ) => {
// If PayPal is already loaded call the onLoaded callback and return. return new Promise( ( resolve, reject ) => {
if (typeof paypal !== 'undefined') { loadPaypalScript( config, resolve, reject );
onLoaded(); } );
return; };
}
// Add the onLoaded callback to the onLoadedCallbacks stack. export const loadPaypalJsScript = ( options, buttons, container ) => {
options.onLoadedCallbacks.push(onLoaded); loadScript( options ).then( ( paypal ) => {
if (onError) { paypal.Buttons( buttons ).render( container );
options.onErrorCallbacks.push(onError); } );
} };
// Return if it's still loading. export const loadPaypalJsScriptPromise = ( options ) => {
if (options.isLoading) { return new Promise( ( resolve, reject ) => {
return; loadScript( options ).then( resolve ).catch( reject );
} } );
options.isLoading = true; };
const resetState = () => {
options.isLoading = false;
options.onLoadedCallbacks = [];
options.onErrorCallbacks = [];
}
// Callback to be called once the PayPal script is loaded.
const callback = (paypal) => {
widgetBuilder.setPaypal(paypal);
for (const onLoadedCallback of options.onLoadedCallbacks) {
onLoadedCallback();
}
resetState();
}
const errorCallback = (err) => {
for (const onErrorCallback of options.onErrorCallbacks) {
onErrorCallback(err);
}
resetState();
}
// Build the PayPal script options.
let scriptOptions = keysToCamelCase(config.url_params);
if (config.script_attributes) {
scriptOptions = merge(scriptOptions, config.script_attributes);
}
// Axo SDK options
const sdkClientToken = config?.axo?.sdk_client_token;
const uuid = uuidv4().replace(/-/g, '');
if(sdkClientToken) {
scriptOptions['data-sdk-client-token'] = sdkClientToken;
scriptOptions['data-client-metadata-id'] = uuid;
}
// Load PayPal script for special case with data-client-token
if (config.data_client_id?.set_attribute) {
dataClientIdAttributeHandler(scriptOptions, config.data_client_id, callback, errorCallback);
return;
}
// Adds data-user-id-token to script options.
const userIdToken = config?.save_payment_methods?.id_token;
if(userIdToken && !sdkClientToken) {
scriptOptions['data-user-id-token'] = userIdToken;
}
// Load PayPal script
loadScript(scriptOptions)
.then(callback)
.catch(errorCallback);
}
export const loadPaypalScriptPromise = (config) => {
return new Promise((resolve, reject) => {
loadPaypalScript(config, resolve, reject)
});
}
export const loadPaypalJsScript = (options, buttons, container) => {
loadScript(options).then((paypal) => {
paypal.Buttons(buttons).render(container);
});
}
export const loadPaypalJsScriptPromise = (options) => {
return new Promise((resolve, reject) => {
loadScript(options)
.then(resolve)
.catch(reject);
});
}

View file

@ -1,128 +1,151 @@
import {paypalAddressToWc} from "../../../../../ppcp-blocks/resources/js/Helper/Address.js"; import { paypalAddressToWc } from '../../../../../ppcp-blocks/resources/js/Helper/Address.js';
import {convertKeysToSnakeCase} from "../../../../../ppcp-blocks/resources/js/Helper/Helper.js"; import { convertKeysToSnakeCase } from '../../../../../ppcp-blocks/resources/js/Helper/Helper.js';
/** /**
* Handles the shipping option change in PayPal. * Handles the shipping option change in PayPal.
* *
* @param data * @param data
* @param actions * @param actions
* @param config * @param config
* @returns {Promise<void>} * @return {Promise<void>}
*/ */
export const handleShippingOptionsChange = async (data, actions, config) => { export const handleShippingOptionsChange = async ( data, actions, config ) => {
try { try {
const shippingOptionId = data.selectedShippingOption?.id; const shippingOptionId = data.selectedShippingOption?.id;
if (shippingOptionId) { if ( shippingOptionId ) {
await fetch(config.ajax.update_customer_shipping.shipping_options.endpoint, { await fetch(
method: 'POST', config.ajax.update_customer_shipping.shipping_options.endpoint,
credentials: 'same-origin', {
headers: { method: 'POST',
'Content-Type': 'application/json', credentials: 'same-origin',
'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce, headers: {
}, 'Content-Type': 'application/json',
body: JSON.stringify({ 'X-WC-Store-API-Nonce':
rate_id: shippingOptionId, config.ajax.update_customer_shipping.wp_rest_nonce,
}) },
}) body: JSON.stringify( {
.then(response => { rate_id: shippingOptionId,
return response.json(); } ),
}) }
.then(cardData => { )
const shippingMethods = document.querySelectorAll('.shipping_method'); .then( ( response ) => {
return response.json();
} )
.then( ( cardData ) => {
const shippingMethods =
document.querySelectorAll( '.shipping_method' );
shippingMethods.forEach(function(method) { shippingMethods.forEach( function ( method ) {
if (method.value === shippingOptionId) { if ( method.value === shippingOptionId ) {
method.checked = true; method.checked = true;
} }
}); } );
}) } );
} }
if (!config.data_client_id.has_subscriptions) { if ( ! config.data_client_id.has_subscriptions ) {
const res = await fetch(config.ajax.update_shipping.endpoint, { const res = await fetch( config.ajax.update_shipping.endpoint, {
method: 'POST', method: 'POST',
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce, nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID, order_id: data.orderID,
}) } ),
}); } );
const json = await res.json(); const json = await res.json();
if (!json.success) { if ( ! json.success ) {
throw new Error(json.data.message); throw new Error( json.data.message );
} }
} }
} catch (e) { } catch ( e ) {
console.error(e); console.error( e );
actions.reject(); actions.reject();
} }
}; };
/** /**
* Handles the shipping address change in PayPal. * Handles the shipping address change in PayPal.
* *
* @param data * @param data
* @param actions * @param actions
* @param config * @param config
* @returns {Promise<void>} * @return {Promise<void>}
*/ */
export const handleShippingAddressChange = async (data, actions, config) => { export const handleShippingAddressChange = async ( data, actions, config ) => {
try { try {
const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress)); const address = paypalAddressToWc(
convertKeysToSnakeCase( data.shippingAddress )
);
// Retrieve current cart contents // Retrieve current cart contents
await fetch(config.ajax.update_customer_shipping.shipping_address.cart_endpoint) await fetch(
.then(response => { config.ajax.update_customer_shipping.shipping_address.cart_endpoint
return response.json(); )
}) .then( ( response ) => {
.then(cartData => { return response.json();
// Update shipping address in the cart data } )
cartData.shipping_address.address_1 = address.address_1; .then( ( cartData ) => {
cartData.shipping_address.address_2 = address.address_2; // Update shipping address in the cart data
cartData.shipping_address.city = address.city; cartData.shipping_address.address_1 = address.address_1;
cartData.shipping_address.state = address.state; cartData.shipping_address.address_2 = address.address_2;
cartData.shipping_address.postcode = address.postcode; cartData.shipping_address.city = address.city;
cartData.shipping_address.country = address.country; cartData.shipping_address.state = address.state;
cartData.shipping_address.postcode = address.postcode;
cartData.shipping_address.country = address.country;
// Send update request // Send update request
return fetch(config.ajax.update_customer_shipping.shipping_address.update_customer_endpoint, { return fetch(
method: 'POST', config.ajax.update_customer_shipping.shipping_address
credentials: 'same-origin', .update_customer_endpoint,
headers: { {
'Content-Type': 'application/json', method: 'POST',
'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce, credentials: 'same-origin',
}, headers: {
body: JSON.stringify({ 'Content-Type': 'application/json',
shipping_address: cartData.shipping_address, 'X-WC-Store-API-Nonce':
}) config.ajax.update_customer_shipping
}).then(function (res) { .wp_rest_nonce,
return res.json(); },
}).then(function (customerData) { body: JSON.stringify( {
jQuery(".cart_totals .shop_table").load(location.href + " " + ".cart_totals .shop_table" + ">*", ""); shipping_address: cartData.shipping_address,
}) } ),
}) }
)
.then( function ( res ) {
return res.json();
} )
.then( function ( customerData ) {
jQuery( '.cart_totals .shop_table' ).load(
location.href +
' ' +
'.cart_totals .shop_table' +
'>*',
''
);
} );
} );
const res = await fetch(config.ajax.update_shipping.endpoint, { const res = await fetch( config.ajax.update_shipping.endpoint, {
method: 'POST', method: 'POST',
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce, nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID, order_id: data.orderID,
}) } ),
}); } );
const json = await res.json(); const json = await res.json();
if (!json.success) { if ( ! json.success ) {
throw new Error(json.data.message); throw new Error( json.data.message );
} }
} catch (e) { } catch ( e ) {
console.error(e); console.error( e );
actions.reject(); actions.reject();
} }
}; };

View file

@ -1,48 +1,42 @@
class SimulateCart { class SimulateCart {
constructor( endpoint, nonce ) {
this.endpoint = endpoint;
this.nonce = nonce;
}
constructor(endpoint, nonce) /**
{ *
this.endpoint = endpoint; * @param onResolve
this.nonce = nonce; * @param {Product[]} products
} * @return {Promise<unknown>}
*/
simulate( onResolve, products ) {
return new Promise( ( resolve, reject ) => {
fetch( this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.nonce,
products,
} ),
} )
.then( ( result ) => {
return result.json();
} )
.then( ( result ) => {
if ( ! result.success ) {
reject( result.data );
return;
}
/** const resolved = onResolve( result.data );
* resolve( resolved );
* @param onResolve } );
* @param {Product[]} products } );
* @returns {Promise<unknown>} }
*/
simulate(onResolve, products)
{
return new Promise((resolve, reject) => {
fetch(
this.endpoint,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.nonce,
products,
})
}
).then(
(result) => {
return result.json();
}
).then((result) => {
if (! result.success) {
reject(result.data);
return;
}
const resolved = onResolve(result.data);
resolve(resolved);
})
});
}
} }
export default SimulateCart; export default SimulateCart;

View file

@ -1,28 +1,25 @@
class Spinner { class Spinner {
constructor( target = 'form.woocommerce-checkout' ) {
this.target = target;
}
constructor(target = 'form.woocommerce-checkout') { setTarget( target ) {
this.target = target; this.target = target;
} }
setTarget(target) { block() {
this.target = target; jQuery( this.target ).block( {
} message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6,
},
} );
}
block() { unblock() {
jQuery( this.target ).unblock();
jQuery( this.target ).block({ }
message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6
}
});
}
unblock() {
jQuery( this.target ).unblock();
}
} }
export default Spinner; export default Spinner;

View file

@ -1,20 +1,20 @@
export const normalizeStyleForFundingSource = (style, fundingSource) => { export const normalizeStyleForFundingSource = ( style, fundingSource ) => {
const commonProps = {}; const commonProps = {};
['shape', 'height'].forEach(prop => { [ 'shape', 'height' ].forEach( ( prop ) => {
if (style[prop]) { if ( style[ prop ] ) {
commonProps[prop] = style[prop]; commonProps[ prop ] = style[ prop ];
} }
}); } );
switch (fundingSource) { switch ( fundingSource ) {
case 'paypal': case 'paypal':
return style; return style;
case 'paylater': case 'paylater':
return { return {
color: style.color, color: style.color,
...commonProps ...commonProps,
}; };
default: default:
return commonProps; return commonProps;
} }
} };

View file

@ -1,20 +1,28 @@
export const isChangePaymentPage = () => { export const isChangePaymentPage = () => {
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams( window.location.search );
return urlParams.has('change_payment_method'); return urlParams.has( 'change_payment_method' );
} };
export const getPlanIdFromVariation = (variation) => { export const getPlanIdFromVariation = ( variation ) => {
let subscription_plan = ''; let subscription_plan = '';
PayPalCommerceGateway.variable_paypal_subscription_variations.forEach((element) => { PayPalCommerceGateway.variable_paypal_subscription_variations.forEach(
let obj = {}; ( element ) => {
variation.forEach(({name, value}) => { const obj = {};
Object.assign(obj, {[name.replace('attribute_', '')]: value}); variation.forEach( ( { name, value } ) => {
}) Object.assign( obj, {
[ name.replace( 'attribute_', '' ) ]: value,
} );
} );
if(JSON.stringify(obj) === JSON.stringify(element.attributes) && element.subscription_plan !== '') { if (
subscription_plan = element.subscription_plan; JSON.stringify( obj ) ===
} JSON.stringify( element.attributes ) &&
}); element.subscription_plan !== ''
) {
subscription_plan = element.subscription_plan;
}
}
);
return subscription_plan; return subscription_plan;
} };

View file

@ -1,51 +1,45 @@
import Product from "../Entity/Product"; import Product from '../Entity/Product';
class UpdateCart { class UpdateCart {
constructor( endpoint, nonce ) {
this.endpoint = endpoint;
this.nonce = nonce;
}
constructor(endpoint, nonce) /**
{ *
this.endpoint = endpoint; * @param onResolve
this.nonce = nonce; * @param {Product[]} products
} * @param {Object} options
* @return {Promise<unknown>}
*/
update( onResolve, products, options = {} ) {
return new Promise( ( resolve, reject ) => {
fetch( this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.nonce,
products,
...options,
} ),
} )
.then( ( result ) => {
return result.json();
} )
.then( ( result ) => {
if ( ! result.success ) {
reject( result.data );
return;
}
/** const resolved = onResolve( result.data );
* resolve( resolved );
* @param onResolve } );
* @param {Product[]} products } );
* @param {Object} options }
* @returns {Promise<unknown>}
*/
update(onResolve, products, options = {})
{
return new Promise((resolve, reject) => {
fetch(
this.endpoint,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.nonce,
products,
...options
})
}
).then(
(result) => {
return result.json();
}
).then((result) => {
if (! result.success) {
reject(result.data);
return;
}
const resolved = onResolve(result.data);
resolve(resolved);
})
});
}
} }
export default UpdateCart; export default UpdateCart;

View file

@ -1,69 +1,69 @@
export const toCamelCase = (str) => { export const toCamelCase = ( str ) => {
return str.replace(/([-_]\w)/g, function(match) { return str.replace( /([-_]\w)/g, function ( match ) {
return match[1].toUpperCase(); return match[ 1 ].toUpperCase();
}); } );
}
export const keysToCamelCase = (obj) => {
let output = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
output[toCamelCase(key)] = obj[key];
}
}
return output;
}
export const strAddWord = (str, word, separator = ',') => {
let arr = str.split(separator);
if (!arr.includes(word)) {
arr.push(word);
}
return arr.join(separator);
}; };
export const strRemoveWord = (str, word, separator = ',') => { export const keysToCamelCase = ( obj ) => {
let arr = str.split(separator); const output = {};
let index = arr.indexOf(word); for ( const key in obj ) {
if (index !== -1) { if ( Object.prototype.hasOwnProperty.call( obj, key ) ) {
arr.splice(index, 1); output[ toCamelCase( key ) ] = obj[ key ];
} }
return arr.join(separator); }
return output;
}; };
export const throttle = (func, limit) => { export const strAddWord = ( str, word, separator = ',' ) => {
let inThrottle, lastArgs, lastContext; const arr = str.split( separator );
if ( ! arr.includes( word ) ) {
arr.push( word );
}
return arr.join( separator );
};
function execute() { export const strRemoveWord = ( str, word, separator = ',' ) => {
inThrottle = true; const arr = str.split( separator );
func.apply(this, arguments); const index = arr.indexOf( word );
setTimeout(() => { if ( index !== -1 ) {
inThrottle = false; arr.splice( index, 1 );
if (lastArgs) { }
const nextArgs = lastArgs; return arr.join( separator );
const nextContext = lastContext; };
lastArgs = lastContext = null;
execute.apply(nextContext, nextArgs);
}
}, limit);
}
return function() { export const throttle = ( func, limit ) => {
if (!inThrottle) { let inThrottle, lastArgs, lastContext;
execute.apply(this, arguments);
} else { function execute() {
lastArgs = arguments; inThrottle = true;
lastContext = this; func.apply( this, arguments );
} setTimeout( () => {
}; inThrottle = false;
} if ( lastArgs ) {
const nextArgs = lastArgs;
const nextContext = lastContext;
lastArgs = lastContext = null;
execute.apply( nextContext, nextArgs );
}
}, limit );
}
return function () {
if ( ! inThrottle ) {
execute.apply( this, arguments );
} else {
lastArgs = arguments;
lastContext = this;
}
};
};
const Utils = { const Utils = {
toCamelCase, toCamelCase,
keysToCamelCase, keysToCamelCase,
strAddWord, strAddWord,
strRemoveWord, strRemoveWord,
throttle throttle,
}; };
export default Utils; export default Utils;

View file

@ -1,34 +1,38 @@
const onApprove = (context, errorHandler) => { const onApprove = ( context, errorHandler ) => {
return (data, actions) => { return ( data, actions ) => {
return fetch(context.config.ajax.approve_order.endpoint, { return fetch( context.config.ajax.approve_order.endpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: context.config.ajax.approve_order.nonce, nonce: context.config.ajax.approve_order.nonce,
order_id:data.orderID, order_id: data.orderID,
funding_source: window.ppcpFundingSource, funding_source: window.ppcpFundingSource,
should_create_wc_order: !context.config.vaultingEnabled || data.paymentSource !== 'venmo' should_create_wc_order:
}) ! context.config.vaultingEnabled ||
}).then((res)=>{ data.paymentSource !== 'venmo',
return res.json(); } ),
}).then((data)=>{ } )
if (!data.success) { .then( ( res ) => {
errorHandler.genericError(); return res.json();
return actions.restart().catch(err => { } )
errorHandler.genericError(); .then( ( data ) => {
}); if ( ! data.success ) {
} errorHandler.genericError();
return actions.restart().catch( ( err ) => {
errorHandler.genericError();
} );
}
let orderReceivedUrl = data.data?.order_received_url const orderReceivedUrl = data.data?.order_received_url;
location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect; location.href = orderReceivedUrl
? orderReceivedUrl
}); : context.config.redirect;
} );
} };
} };
export default onApprove; export default onApprove;

View file

@ -1,38 +1,42 @@
const onApprove = (context, errorHandler, spinner) => { const onApprove = ( context, errorHandler, spinner ) => {
return (data, actions) => { return ( data, actions ) => {
spinner.block(); spinner.block();
errorHandler.clear(); errorHandler.clear();
return fetch(context.config.ajax.approve_order.endpoint, { return fetch( context.config.ajax.approve_order.endpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({ body: JSON.stringify( {
nonce: context.config.ajax.approve_order.nonce, nonce: context.config.ajax.approve_order.nonce,
order_id:data.orderID, order_id: data.orderID,
funding_source: window.ppcpFundingSource, funding_source: window.ppcpFundingSource,
}) } ),
}).then((res)=>{ } )
return res.json(); .then( ( res ) => {
}).then((data)=>{ return res.json();
spinner.unblock(); } )
if (!data.success) { .then( ( data ) => {
if (data.data.code === 100) { spinner.unblock();
errorHandler.message(data.data.message); if ( ! data.success ) {
} else { if ( data.data.code === 100 ) {
errorHandler.genericError(); errorHandler.message( data.data.message );
} } else {
if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') { errorHandler.genericError();
return actions.restart(); }
} if (
throw new Error(data.data.message); typeof actions !== 'undefined' &&
} typeof actions.restart !== 'undefined'
document.querySelector('#place_order').click() ) {
}); return actions.restart();
}
} throw new Error( data.data.message );
} }
document.querySelector( '#place_order' ).click();
} );
};
};
export default onApprove; export default onApprove;

View file

@ -1,152 +1,186 @@
import {show} from "../Helper/Hiding"; import { show } from '../Helper/Hiding';
import {cardFieldStyles} from "../Helper/CardFieldsHelper"; import { cardFieldStyles } from '../Helper/CardFieldsHelper';
class CardFieldsRenderer { class CardFieldsRenderer {
constructor(
defaultConfig,
errorHandler,
spinner,
onCardFieldsBeforeSubmit
) {
this.defaultConfig = defaultConfig;
this.errorHandler = errorHandler;
this.spinner = spinner;
this.cardValid = false;
this.formValid = false;
this.emptyFields = new Set( [ 'number', 'cvv', 'expirationDate' ] );
this.currentHostedFieldsInstance = null;
this.onCardFieldsBeforeSubmit = onCardFieldsBeforeSubmit;
}
constructor(defaultConfig, errorHandler, spinner, onCardFieldsBeforeSubmit) { render( wrapper, contextConfig ) {
this.defaultConfig = defaultConfig; if (
this.errorHandler = errorHandler; ( this.defaultConfig.context !== 'checkout' &&
this.spinner = spinner; this.defaultConfig.context !== 'pay-now' ) ||
this.cardValid = false; wrapper === null ||
this.formValid = false; document.querySelector( wrapper ) === null
this.emptyFields = new Set(['number', 'cvv', 'expirationDate']); ) {
this.currentHostedFieldsInstance = null; return;
this.onCardFieldsBeforeSubmit = onCardFieldsBeforeSubmit; }
}
render(wrapper, contextConfig) { const buttonSelector = wrapper + ' button';
if (
(
this.defaultConfig.context !== 'checkout'
&& this.defaultConfig.context !== 'pay-now'
)
|| wrapper === null
|| document.querySelector(wrapper) === null
) {
return;
}
const buttonSelector = wrapper + ' button'; const gateWayBox = document.querySelector(
'.payment_box.payment_method_ppcp-credit-card-gateway'
);
if ( ! gateWayBox ) {
return;
}
const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway'); const oldDisplayStyle = gateWayBox.style.display;
if (!gateWayBox) { gateWayBox.style.display = 'block';
return
}
const oldDisplayStyle = gateWayBox.style.display; const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' );
gateWayBox.style.display = 'block'; if ( hideDccGateway ) {
hideDccGateway.parentNode.removeChild( hideDccGateway );
}
const hideDccGateway = document.querySelector('#ppcp-hide-dcc'); const cardField = paypal.CardFields( {
if (hideDccGateway) { createOrder: contextConfig.createOrder,
hideDccGateway.parentNode.removeChild(hideDccGateway); onApprove( data ) {
} return contextConfig.onApprove( data );
},
onError( error ) {
console.error( error );
this.spinner.unblock();
},
} );
const cardField = paypal.CardFields({ if ( cardField.isEligible() ) {
createOrder: contextConfig.createOrder, const nameField = document.getElementById(
onApprove: function (data) { 'ppcp-credit-card-gateway-card-name'
return contextConfig.onApprove(data); );
}, if ( nameField ) {
onError: function (error) { const styles = cardFieldStyles( nameField );
console.error(error) const fieldOptions = {
this.spinner.unblock(); style: { input: styles },
} };
}); if ( nameField.getAttribute( 'placeholder' ) ) {
fieldOptions.placeholder =
nameField.getAttribute( 'placeholder' );
}
cardField
.NameField( fieldOptions )
.render( nameField.parentNode );
nameField.remove();
}
if (cardField.isEligible()) { const numberField = document.getElementById(
const nameField = document.getElementById('ppcp-credit-card-gateway-card-name'); 'ppcp-credit-card-gateway-card-number'
if (nameField) { );
let styles = cardFieldStyles(nameField); if ( numberField ) {
let fieldOptions = { const styles = cardFieldStyles( numberField );
style: { 'input': styles } const fieldOptions = {
} style: { input: styles },
if (nameField.getAttribute('placeholder')) { };
fieldOptions.placeholder = nameField.getAttribute('placeholder'); if ( numberField.getAttribute( 'placeholder' ) ) {
} fieldOptions.placeholder =
cardField.NameField(fieldOptions).render(nameField.parentNode); numberField.getAttribute( 'placeholder' );
nameField.remove(); }
} cardField
.NumberField( fieldOptions )
.render( numberField.parentNode );
numberField.remove();
}
const numberField = document.getElementById('ppcp-credit-card-gateway-card-number'); const expiryField = document.getElementById(
if (numberField) { 'ppcp-credit-card-gateway-card-expiry'
let styles = cardFieldStyles(numberField); );
let fieldOptions = { if ( expiryField ) {
style: { 'input': styles } const styles = cardFieldStyles( expiryField );
} const fieldOptions = {
if (numberField.getAttribute('placeholder')) { style: { input: styles },
fieldOptions.placeholder = numberField.getAttribute('placeholder'); };
} if ( expiryField.getAttribute( 'placeholder' ) ) {
cardField.NumberField(fieldOptions).render(numberField.parentNode); fieldOptions.placeholder =
numberField.remove(); expiryField.getAttribute( 'placeholder' );
} }
cardField
.ExpiryField( fieldOptions )
.render( expiryField.parentNode );
expiryField.remove();
}
const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry'); const cvvField = document.getElementById(
if (expiryField) { 'ppcp-credit-card-gateway-card-cvc'
let styles = cardFieldStyles(expiryField); );
let fieldOptions = { if ( cvvField ) {
style: { 'input': styles } const styles = cardFieldStyles( cvvField );
} const fieldOptions = {
if (expiryField.getAttribute('placeholder')) { style: { input: styles },
fieldOptions.placeholder = expiryField.getAttribute('placeholder'); };
} if ( cvvField.getAttribute( 'placeholder' ) ) {
cardField.ExpiryField(fieldOptions).render(expiryField.parentNode); fieldOptions.placeholder =
expiryField.remove(); cvvField.getAttribute( 'placeholder' );
} }
cardField
.CVVField( fieldOptions )
.render( cvvField.parentNode );
cvvField.remove();
}
const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc'); document.dispatchEvent( new CustomEvent( 'hosted_fields_loaded' ) );
if (cvvField) { }
let styles = cardFieldStyles(cvvField);
let fieldOptions = {
style: { 'input': styles }
}
if (cvvField.getAttribute('placeholder')) {
fieldOptions.placeholder = cvvField.getAttribute('placeholder');
}
cardField.CVVField(fieldOptions).render(cvvField.parentNode);
cvvField.remove();
}
document.dispatchEvent(new CustomEvent("hosted_fields_loaded")); gateWayBox.style.display = oldDisplayStyle;
}
gateWayBox.style.display = oldDisplayStyle; show( buttonSelector );
show(buttonSelector); if ( this.defaultConfig.cart_contains_subscription ) {
const saveToAccount = document.querySelector(
'#wc-ppcp-credit-card-gateway-new-payment-method'
);
if ( saveToAccount ) {
saveToAccount.checked = true;
saveToAccount.disabled = true;
}
}
if(this.defaultConfig.cart_contains_subscription) { document
const saveToAccount = document.querySelector('#wc-ppcp-credit-card-gateway-new-payment-method'); .querySelector( buttonSelector )
if(saveToAccount) { .addEventListener( 'click', ( event ) => {
saveToAccount.checked = true; event.preventDefault();
saveToAccount.disabled = true; this.spinner.block();
} this.errorHandler.clear();
}
document.querySelector(buttonSelector).addEventListener("click", (event) => { const paymentToken = document.querySelector(
event.preventDefault(); 'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked'
this.spinner.block(); )?.value;
this.errorHandler.clear(); if ( paymentToken && paymentToken !== 'new' ) {
document.querySelector( '#place_order' ).click();
return;
}
const paymentToken = document.querySelector('input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked')?.value if (
if(paymentToken && paymentToken !== 'new') { typeof this.onCardFieldsBeforeSubmit === 'function' &&
document.querySelector('#place_order').click(); ! this.onCardFieldsBeforeSubmit()
return; ) {
} this.spinner.unblock();
return;
}
if (typeof this.onCardFieldsBeforeSubmit === 'function' && !this.onCardFieldsBeforeSubmit()) { cardField.submit().catch( ( error ) => {
this.spinner.unblock(); this.spinner.unblock();
return; console.error( error );
} this.errorHandler.message(
this.defaultConfig.hosted_fields.labels.fields_not_valid
);
} );
} );
}
cardField.submit() disableFields() {}
.catch((error) => { enableFields() {}
this.spinner.unblock();
console.error(error)
this.errorHandler.message(this.defaultConfig.hosted_fields.labels.fields_not_valid);
});
});
}
disableFields() {}
enableFields() {}
} }
export default CardFieldsRenderer; export default CardFieldsRenderer;

View file

@ -1,274 +1,357 @@
import dccInputFactory from "../Helper/DccInputFactory"; import dccInputFactory from '../Helper/DccInputFactory';
import {show} from "../Helper/Hiding"; import { show } from '../Helper/Hiding';
class HostedFieldsRenderer { class HostedFieldsRenderer {
constructor( defaultConfig, errorHandler, spinner ) {
this.defaultConfig = defaultConfig;
this.errorHandler = errorHandler;
this.spinner = spinner;
this.cardValid = false;
this.formValid = false;
this.emptyFields = new Set( [ 'number', 'cvv', 'expirationDate' ] );
this.currentHostedFieldsInstance = null;
}
constructor(defaultConfig, errorHandler, spinner) { render( wrapper, contextConfig ) {
this.defaultConfig = defaultConfig; if (
this.errorHandler = errorHandler; ( this.defaultConfig.context !== 'checkout' &&
this.spinner = spinner; this.defaultConfig.context !== 'pay-now' ) ||
this.cardValid = false; wrapper === null ||
this.formValid = false; document.querySelector( wrapper ) === null
this.emptyFields = new Set(['number', 'cvv', 'expirationDate']); ) {
this.currentHostedFieldsInstance = null; return;
} }
render(wrapper, contextConfig) { if (
if ( typeof paypal.HostedFields !== 'undefined' &&
( paypal.HostedFields.isEligible()
this.defaultConfig.context !== 'checkout' ) {
&& this.defaultConfig.context !== 'pay-now' const buttonSelector = wrapper + ' button';
)
|| wrapper === null
|| document.querySelector(wrapper) === null
) {
return;
}
if (typeof paypal.HostedFields !== 'undefined' && paypal.HostedFields.isEligible()) { if ( this.currentHostedFieldsInstance ) {
const buttonSelector = wrapper + ' button'; this.currentHostedFieldsInstance
.teardown()
.catch( ( err ) =>
console.error(
`Hosted fields teardown error: ${ err }`
)
);
this.currentHostedFieldsInstance = null;
}
if (this.currentHostedFieldsInstance) { const gateWayBox = document.querySelector(
this.currentHostedFieldsInstance.teardown() '.payment_box.payment_method_ppcp-credit-card-gateway'
.catch(err => console.error(`Hosted fields teardown error: ${err}`)); );
this.currentHostedFieldsInstance = null; if ( ! gateWayBox ) {
} return;
}
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway'); const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' );
if (!gateWayBox) { if ( hideDccGateway ) {
return hideDccGateway.parentNode.removeChild( hideDccGateway );
} }
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
const hideDccGateway = document.querySelector('#ppcp-hide-dcc'); const cardNumberField = document.querySelector(
if (hideDccGateway) { '#ppcp-credit-card-gateway-card-number'
hideDccGateway.parentNode.removeChild(hideDccGateway); );
}
const cardNumberField = document.querySelector('#ppcp-credit-card-gateway-card-number'); const stylesRaw = window.getComputedStyle( cardNumberField );
const styles = {};
Object.values( stylesRaw ).forEach( ( prop ) => {
if ( ! stylesRaw[ prop ] ) {
return;
}
styles[ prop ] = '' + stylesRaw[ prop ];
} );
const stylesRaw = window.getComputedStyle(cardNumberField); const cardNumber = dccInputFactory( cardNumberField );
let styles = {}; cardNumberField.parentNode.replaceChild(
Object.values(stylesRaw).forEach((prop) => { cardNumber,
if (!stylesRaw[prop]) { cardNumberField
return; );
}
styles[prop] = '' + stylesRaw[prop];
});
const cardNumber = dccInputFactory(cardNumberField); const cardExpiryField = document.querySelector(
cardNumberField.parentNode.replaceChild(cardNumber, cardNumberField); '#ppcp-credit-card-gateway-card-expiry'
);
const cardExpiry = dccInputFactory( cardExpiryField );
cardExpiryField.parentNode.replaceChild(
cardExpiry,
cardExpiryField
);
const cardExpiryField = document.querySelector('#ppcp-credit-card-gateway-card-expiry'); const cardCodeField = document.querySelector(
const cardExpiry = dccInputFactory(cardExpiryField); '#ppcp-credit-card-gateway-card-cvc'
cardExpiryField.parentNode.replaceChild(cardExpiry, cardExpiryField); );
const cardCode = dccInputFactory( cardCodeField );
cardCodeField.parentNode.replaceChild( cardCode, cardCodeField );
const cardCodeField = document.querySelector('#ppcp-credit-card-gateway-card-cvc'); gateWayBox.style.display = oldDisplayStyle;
const cardCode = dccInputFactory(cardCodeField);
cardCodeField.parentNode.replaceChild(cardCode, cardCodeField);
gateWayBox.style.display = oldDisplayStyle; const formWrapper =
'.payment_box payment_method_ppcp-credit-card-gateway';
if (
this.defaultConfig.enforce_vault &&
document.querySelector(
formWrapper + ' .ppcp-credit-card-vault'
)
) {
document.querySelector(
formWrapper + ' .ppcp-credit-card-vault'
).checked = true;
document
.querySelector( formWrapper + ' .ppcp-credit-card-vault' )
.setAttribute( 'disabled', true );
}
paypal.HostedFields.render( {
createOrder: contextConfig.createOrder,
styles: {
input: styles,
},
fields: {
number: {
selector: '#ppcp-credit-card-gateway-card-number',
placeholder:
this.defaultConfig.hosted_fields.labels
.credit_card_number,
},
cvv: {
selector: '#ppcp-credit-card-gateway-card-cvc',
placeholder:
this.defaultConfig.hosted_fields.labels.cvv,
},
expirationDate: {
selector: '#ppcp-credit-card-gateway-card-expiry',
placeholder:
this.defaultConfig.hosted_fields.labels.mm_yy,
},
},
} ).then( ( hostedFields ) => {
document.dispatchEvent(
new CustomEvent( 'hosted_fields_loaded' )
);
this.currentHostedFieldsInstance = hostedFields;
const formWrapper = '.payment_box payment_method_ppcp-credit-card-gateway'; hostedFields.on( 'inputSubmitRequest', () => {
if ( this._submit( contextConfig );
this.defaultConfig.enforce_vault } );
&& document.querySelector(formWrapper + ' .ppcp-credit-card-vault') hostedFields.on( 'cardTypeChange', ( event ) => {
) { if ( ! event.cards.length ) {
document.querySelector(formWrapper + ' .ppcp-credit-card-vault').checked = true; this.cardValid = false;
document.querySelector(formWrapper + ' .ppcp-credit-card-vault').setAttribute('disabled', true); return;
} }
paypal.HostedFields.render({ const validCards =
createOrder: contextConfig.createOrder, this.defaultConfig.hosted_fields.valid_cards;
styles: { this.cardValid =
'input': styles validCards.indexOf( event.cards[ 0 ].type ) !== -1;
},
fields: {
number: {
selector: '#ppcp-credit-card-gateway-card-number',
placeholder: this.defaultConfig.hosted_fields.labels.credit_card_number,
},
cvv: {
selector: '#ppcp-credit-card-gateway-card-cvc',
placeholder: this.defaultConfig.hosted_fields.labels.cvv,
},
expirationDate: {
selector: '#ppcp-credit-card-gateway-card-expiry',
placeholder: this.defaultConfig.hosted_fields.labels.mm_yy,
}
}
}).then(hostedFields => {
document.dispatchEvent(new CustomEvent("hosted_fields_loaded"));
this.currentHostedFieldsInstance = hostedFields;
hostedFields.on('inputSubmitRequest', () => { const className = this._cardNumberFiledCLassNameByCardType(
this._submit(contextConfig); event.cards[ 0 ].type
}); );
hostedFields.on('cardTypeChange', (event) => { this._recreateElementClassAttribute(
if (!event.cards.length) { cardNumber,
this.cardValid = false; cardNumberField.className
return; );
} if ( event.cards.length === 1 ) {
const validCards = this.defaultConfig.hosted_fields.valid_cards; cardNumber.classList.add( className );
this.cardValid = validCards.indexOf(event.cards[0].type) !== -1; }
} );
hostedFields.on( 'validityChange', ( event ) => {
this.formValid = Object.keys( event.fields ).every(
function ( key ) {
return event.fields[ key ].isValid;
}
);
} );
hostedFields.on( 'empty', ( event ) => {
this._recreateElementClassAttribute(
cardNumber,
cardNumberField.className
);
this.emptyFields.add( event.emittedBy );
} );
hostedFields.on( 'notEmpty', ( event ) => {
this.emptyFields.delete( event.emittedBy );
} );
const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type); show( buttonSelector );
this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
if (event.cards.length === 1) {
cardNumber.classList.add(className);
}
})
hostedFields.on('validityChange', (event) => {
this.formValid = Object.keys(event.fields).every(function (key) {
return event.fields[key].isValid;
});
});
hostedFields.on('empty', (event) => {
this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
this.emptyFields.add(event.emittedBy);
});
hostedFields.on('notEmpty', (event) => {
this.emptyFields.delete(event.emittedBy);
});
show(buttonSelector); if (
document
.querySelector( wrapper )
.getAttribute( 'data-ppcp-subscribed' ) !== true
) {
document
.querySelector( buttonSelector )
.addEventListener( 'click', ( event ) => {
event.preventDefault();
this._submit( contextConfig );
} );
if (document.querySelector(wrapper).getAttribute('data-ppcp-subscribed') !== true) { document
document.querySelector(buttonSelector).addEventListener( .querySelector( wrapper )
'click', .setAttribute( 'data-ppcp-subscribed', true );
event => { }
event.preventDefault(); } );
this._submit(contextConfig);
}
);
document.querySelector(wrapper).setAttribute('data-ppcp-subscribed', true); document
} .querySelector( '#payment_method_ppcp-credit-card-gateway' )
}); .addEventListener( 'click', () => {
document
.querySelector(
'label[for=ppcp-credit-card-gateway-card-number]'
)
.click();
} );
document.querySelector('#payment_method_ppcp-credit-card-gateway').addEventListener( return;
'click', }
() => {
document.querySelector('label[for=ppcp-credit-card-gateway-card-number]').click();
}
)
return; const wrapperElement = document.querySelector( wrapper );
} wrapperElement.parentNode.removeChild( wrapperElement );
}
const wrapperElement = document.querySelector(wrapper); disableFields() {
wrapperElement.parentNode.removeChild(wrapperElement); if ( this.currentHostedFieldsInstance ) {
} this.currentHostedFieldsInstance.setAttribute( {
field: 'number',
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.setAttribute( {
field: 'cvv',
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.setAttribute( {
field: 'expirationDate',
attribute: 'disabled',
} );
}
}
disableFields() { enableFields() {
if (this.currentHostedFieldsInstance) { if ( this.currentHostedFieldsInstance ) {
this.currentHostedFieldsInstance.setAttribute({ this.currentHostedFieldsInstance.removeAttribute( {
field: 'number', field: 'number',
attribute: 'disabled' attribute: 'disabled',
}) } );
this.currentHostedFieldsInstance.setAttribute({ this.currentHostedFieldsInstance.removeAttribute( {
field: 'cvv', field: 'cvv',
attribute: 'disabled' attribute: 'disabled',
}) } );
this.currentHostedFieldsInstance.setAttribute({ this.currentHostedFieldsInstance.removeAttribute( {
field: 'expirationDate', field: 'expirationDate',
attribute: 'disabled' attribute: 'disabled',
}) } );
} }
} }
enableFields() { _submit( contextConfig ) {
if (this.currentHostedFieldsInstance) { this.spinner.block();
this.currentHostedFieldsInstance.removeAttribute({ this.errorHandler.clear();
field: 'number',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'cvv',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'expirationDate',
attribute: 'disabled'
})
}
}
_submit(contextConfig) { if ( this.formValid && this.cardValid ) {
this.spinner.block(); const save_card = this.defaultConfig.can_save_vault_token
this.errorHandler.clear(); ? true
: false;
let vault = document.getElementById( 'ppcp-credit-card-vault' )
? document.getElementById( 'ppcp-credit-card-vault' ).checked
: save_card;
if ( this.defaultConfig.enforce_vault ) {
vault = true;
}
const contingency = this.defaultConfig.hosted_fields.contingency;
const hostedFieldsData = {
vault,
};
if ( contingency !== 'NO_3D_SECURE' ) {
hostedFieldsData.contingencies = [ contingency ];
}
if (this.formValid && this.cardValid) { if ( this.defaultConfig.payer ) {
const save_card = this.defaultConfig.can_save_vault_token ? true : false; hostedFieldsData.cardholderName =
let vault = document.getElementById('ppcp-credit-card-vault') ? this.defaultConfig.payer.name.given_name +
document.getElementById('ppcp-credit-card-vault').checked : save_card; ' ' +
if (this.defaultConfig.enforce_vault) { this.defaultConfig.payer.name.surname;
vault = true; }
} if ( ! hostedFieldsData.cardholderName ) {
const contingency = this.defaultConfig.hosted_fields.contingency; const firstName = document.getElementById(
const hostedFieldsData = { 'billing_first_name'
vault: vault )
}; ? document.getElementById( 'billing_first_name' ).value
if (contingency !== 'NO_3D_SECURE') { : '';
hostedFieldsData.contingencies = [contingency]; const lastName = document.getElementById( 'billing_last_name' )
} ? document.getElementById( 'billing_last_name' ).value
: '';
if (this.defaultConfig.payer) { hostedFieldsData.cardholderName = firstName + ' ' + lastName;
hostedFieldsData.cardholderName = this.defaultConfig.payer.name.given_name + ' ' + this.defaultConfig.payer.name.surname; }
}
if (!hostedFieldsData.cardholderName) {
const firstName = document.getElementById('billing_first_name') ? document.getElementById('billing_first_name').value : '';
const lastName = document.getElementById('billing_last_name') ? document.getElementById('billing_last_name').value : '';
hostedFieldsData.cardholderName = firstName + ' ' + lastName; this.currentHostedFieldsInstance
} .submit( hostedFieldsData )
.then( ( payload ) => {
payload.orderID = payload.orderId;
this.spinner.unblock();
return contextConfig.onApprove( payload );
} )
.catch( ( err ) => {
this.spinner.unblock();
this.errorHandler.clear();
this.currentHostedFieldsInstance.submit(hostedFieldsData).then((payload) => { if ( err.data?.details?.length ) {
payload.orderID = payload.orderId; this.errorHandler.message(
this.spinner.unblock(); err.data.details
return contextConfig.onApprove(payload); .map(
}).catch(err => { ( d ) => `${ d.issue } ${ d.description }`
this.spinner.unblock(); )
this.errorHandler.clear(); .join( '<br/>' )
);
} else if ( err.details?.length ) {
this.errorHandler.message(
err.details
.map(
( d ) => `${ d.issue } ${ d.description }`
)
.join( '<br/>' )
);
} else if ( err.data?.errors?.length > 0 ) {
this.errorHandler.messages( err.data.errors );
} else if ( err.data?.message ) {
this.errorHandler.message( err.data.message );
} else if ( err.message ) {
this.errorHandler.message( err.message );
} else {
this.errorHandler.genericError();
}
} );
} else {
this.spinner.unblock();
if (err.data?.details?.length) { let message = this.defaultConfig.labels.error.generic;
this.errorHandler.message(err.data.details.map(d => `${d.issue} ${d.description}`).join('<br/>')); if ( this.emptyFields.size > 0 ) {
} else if (err.details?.length) { message = this.defaultConfig.hosted_fields.labels.fields_empty;
this.errorHandler.message(err.details.map(d => `${d.issue} ${d.description}`).join('<br/>')); } else if ( ! this.cardValid ) {
} else if (err.data?.errors?.length > 0) { message =
this.errorHandler.messages(err.data.errors); this.defaultConfig.hosted_fields.labels.card_not_supported;
} else if (err.data?.message) { } else if ( ! this.formValid ) {
this.errorHandler.message(err.data.message); message =
} else if (err.message) { this.defaultConfig.hosted_fields.labels.fields_not_valid;
this.errorHandler.message(err.message); }
} else {
this.errorHandler.genericError();
}
});
} else {
this.spinner.unblock();
let message = this.defaultConfig.labels.error.generic; this.errorHandler.message( message );
if (this.emptyFields.size > 0) { }
message = this.defaultConfig.hosted_fields.labels.fields_empty; }
} else if (!this.cardValid) {
message = this.defaultConfig.hosted_fields.labels.card_not_supported;
} else if (!this.formValid) {
message = this.defaultConfig.hosted_fields.labels.fields_not_valid;
}
this.errorHandler.message(message); _cardNumberFiledCLassNameByCardType( cardType ) {
} return cardType === 'american-express'
} ? 'amex'
: cardType.replace( '-', '' );
}
_cardNumberFiledCLassNameByCardType(cardType) { _recreateElementClassAttribute( element, newClassName ) {
return cardType === 'american-express' ? 'amex' : cardType.replace('-', ''); element.removeAttribute( 'class' );
} element.setAttribute( 'class', newClassName );
}
_recreateElementClassAttribute(element, newClassName) {
element.removeAttribute('class')
element.setAttribute('class', newClassName);
}
} }
export default HostedFieldsRenderer; export default HostedFieldsRenderer;

View file

@ -1,65 +1,72 @@
import widgetBuilder from "./WidgetBuilder"; import widgetBuilder from './WidgetBuilder';
class MessageRenderer { class MessageRenderer {
constructor( config ) {
this.config = config;
this.optionsFingerprint = null;
this.currentNumber = 0;
}
constructor(config) { renderWithAmount( amount ) {
this.config = config; if ( ! this.shouldRender() ) {
this.optionsFingerprint = null; return;
this.currentNumber = 0; }
}
renderWithAmount(amount) { const options = {
if (! this.shouldRender()) { amount,
return; };
} if ( this.config.placement ) {
options.placement = this.config.placement;
}
if ( this.config.style ) {
options.style = this.config.style;
}
const options = { // sometimes the element is destroyed while the options stay the same
amount, if (
}; document
if (this.config.placement) { .querySelector( this.config.wrapper )
options.placement = this.config.placement; .getAttribute( 'data-render-number' ) !==
} this.currentNumber.toString()
if (this.config.style) { ) {
options.style = this.config.style; this.optionsFingerprint = null;
} }
// sometimes the element is destroyed while the options stay the same if ( this.optionsEqual( options ) ) {
if (document.querySelector(this.config.wrapper).getAttribute('data-render-number') !== this.currentNumber.toString()) { return;
this.optionsFingerprint = null; }
}
if (this.optionsEqual(options)) { const wrapper = document.querySelector( this.config.wrapper );
return; this.currentNumber++;
} wrapper.setAttribute( 'data-render-number', this.currentNumber );
const wrapper = document.querySelector(this.config.wrapper); widgetBuilder.registerMessages( this.config.wrapper, options );
this.currentNumber++; widgetBuilder.renderMessages( this.config.wrapper );
wrapper.setAttribute('data-render-number', this.currentNumber); }
widgetBuilder.registerMessages(this.config.wrapper, options); optionsEqual( options ) {
widgetBuilder.renderMessages(this.config.wrapper); const fingerprint = JSON.stringify( options );
}
optionsEqual(options) { if ( this.optionsFingerprint === fingerprint ) {
const fingerprint = JSON.stringify(options); return true;
}
if (this.optionsFingerprint === fingerprint) { this.optionsFingerprint = fingerprint;
return true; return false;
} }
this.optionsFingerprint = fingerprint; shouldRender() {
return false; if (
} typeof paypal === 'undefined' ||
typeof paypal.Messages === 'undefined' ||
shouldRender() { typeof this.config.wrapper === 'undefined'
) {
if (typeof paypal === 'undefined' || typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) { return false;
return false; }
} if ( ! document.querySelector( this.config.wrapper ) ) {
if (! document.querySelector(this.config.wrapper)) { return false;
return false; }
} return true;
return true; }
}
} }
export default MessageRenderer; export default MessageRenderer;

View file

@ -4,151 +4,163 @@ import merge from 'deepmerge';
* Base class for APM button previews, used on the plugin's settings page. * Base class for APM button previews, used on the plugin's settings page.
*/ */
class PreviewButton { class PreviewButton {
/** /**
* @param {string} selector - CSS ID of the wrapper, including the `#` * @param {string} selector - CSS ID of the wrapper, including the `#`
* @param {object} apiConfig - PayPal configuration object; retrieved via a * @param {Object} apiConfig - PayPal configuration object; retrieved via a
* widgetBuilder API method * widgetBuilder API method
*/ */
constructor({ constructor( { selector, apiConfig } ) {
selector, this.apiConfig = apiConfig;
apiConfig, this.defaultAttributes = {};
}) { this.buttonConfig = {};
this.apiConfig = apiConfig; this.ppcpConfig = {};
this.defaultAttributes = {}; this.isDynamic = true;
this.buttonConfig = {};
this.ppcpConfig = {};
this.isDynamic = true;
// The selector is usually overwritten in constructor of derived class. // The selector is usually overwritten in constructor of derived class.
this.selector = selector; this.selector = selector;
this.wrapper = selector; this.wrapper = selector;
this.domWrapper = null; this.domWrapper = null;
} }
/** /**
* Creates a new DOM node to contain the preview button. * Creates a new DOM node to contain the preview button.
* *
* @return {jQuery} Always a single jQuery element with the new DOM node. * @return {jQuery} Always a single jQuery element with the new DOM node.
*/ */
createNewWrapper() { createNewWrapper() {
const previewId = this.selector.replace('#', ''); const previewId = this.selector.replace( '#', '' );
const previewClass = 'ppcp-button-apm'; const previewClass = 'ppcp-button-apm';
return jQuery(`<div id='${previewId}' class='${previewClass}'>`); return jQuery( `<div id='${ previewId }' class='${ previewClass }'>` );
} }
/** /**
* Toggle the "dynamic" nature of the preview. * Toggle the "dynamic" nature of the preview.
* When the button is dynamic, it will reflect current form values. A static button always * When the button is dynamic, it will reflect current form values. A static button always
* uses the settings that were provided via PHP. * uses the settings that were provided via PHP.
* *
* @return {this} Reference to self, for chaining. * @param state
*/ * @return {this} Reference to self, for chaining.
setDynamic(state) { */
this.isDynamic = state; setDynamic( state ) {
return this; this.isDynamic = state;
} return this;
}
/** /**
* Sets server-side configuration for the button. * Sets server-side configuration for the button.
* *
* @return {this} Reference to self, for chaining. * @param config
*/ * @return {this} Reference to self, for chaining.
setButtonConfig(config) { */
this.buttonConfig = merge(this.defaultAttributes, config); setButtonConfig( config ) {
this.buttonConfig.button.wrapper = this.selector; this.buttonConfig = merge( this.defaultAttributes, config );
this.buttonConfig.button.wrapper = this.selector;
return this; return this;
} }
/** /**
* Updates the button configuration with current details from the form. * Updates the button configuration with current details from the form.
* *
* @return {this} Reference to self, for chaining. * @param config
*/ * @return {this} Reference to self, for chaining.
setPpcpConfig(config) { */
this.ppcpConfig = merge({}, config); setPpcpConfig( config ) {
this.ppcpConfig = merge( {}, config );
return this; return this;
} }
/** /**
* Merge form details into the config object for preview. * Merge form details into the config object for preview.
* Mutates the previewConfig object; no return value. * Mutates the previewConfig object; no return value.
*/ * @param previewConfig
dynamicPreviewConfig(previewConfig, formConfig) { * @param formConfig
// Implement in derived class. */
} dynamicPreviewConfig( previewConfig, formConfig ) {
// Implement in derived class.
}
/** /**
* Responsible for creating the actual payment button preview. * Responsible for creating the actual payment button preview.
* Called by the `render()` method, after the wrapper DOM element is ready. * Called by the `render()` method, after the wrapper DOM element is ready.
*/ * @param previewConfig
createButton(previewConfig) { */
throw new Error('The "createButton" method must be implemented by the derived class'); createButton( previewConfig ) {
} throw new Error(
'The "createButton" method must be implemented by the derived class'
);
}
/** /**
* Refreshes the button in the DOM. * Refreshes the button in the DOM.
* Will always create a new button in the DOM. * Will always create a new button in the DOM.
*/ */
render() { render() {
// The APM button is disabled and cannot be enabled on the current page: Do not render it. // The APM button is disabled and cannot be enabled on the current page: Do not render it.
if (!this.isDynamic && !this.buttonConfig.is_enabled) { if ( ! this.isDynamic && ! this.buttonConfig.is_enabled ) {
return; return;
} }
if (!this.domWrapper) { if ( ! this.domWrapper ) {
if (!this.wrapper) { if ( ! this.wrapper ) {
console.error('Skip render, button is not configured yet'); console.error( 'Skip render, button is not configured yet' );
return; return;
} }
this.domWrapper = this.createNewWrapper(); this.domWrapper = this.createNewWrapper();
this.domWrapper.insertAfter(this.wrapper); this.domWrapper.insertAfter( this.wrapper );
} else { } else {
this.domWrapper.empty().show(); this.domWrapper.empty().show();
} }
this.isVisible = true; this.isVisible = true;
const previewButtonConfig = merge({}, this.buttonConfig); const previewButtonConfig = merge( {}, this.buttonConfig );
const previewPpcpConfig = this.isDynamic ? merge({}, this.ppcpConfig) : {}; const previewPpcpConfig = this.isDynamic
previewButtonConfig.button.wrapper = this.selector; ? merge( {}, this.ppcpConfig )
: {};
previewButtonConfig.button.wrapper = this.selector;
this.dynamicPreviewConfig(previewButtonConfig, previewPpcpConfig); this.dynamicPreviewConfig( previewButtonConfig, previewPpcpConfig );
/* /*
* previewButtonConfig.button.wrapper must be different from this.ppcpConfig.button.wrapper! * previewButtonConfig.button.wrapper must be different from this.ppcpConfig.button.wrapper!
* If both selectors point to the same element, an infinite loop is triggered. * If both selectors point to the same element, an infinite loop is triggered.
*/ */
const buttonWrapper = previewButtonConfig.button.wrapper.replace(/^#/, ''); const buttonWrapper = previewButtonConfig.button.wrapper.replace(
const ppcpWrapper = this.ppcpConfig.button.wrapper.replace(/^#/, ''); /^#/,
''
);
const ppcpWrapper = this.ppcpConfig.button.wrapper.replace( /^#/, '' );
if (buttonWrapper === ppcpWrapper) { if ( buttonWrapper === ppcpWrapper ) {
throw new Error(`[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${buttonWrapper}"`); throw new Error(
} `[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${ buttonWrapper }"`
);
}
this.createButton(previewButtonConfig); this.createButton( previewButtonConfig );
/* /*
* Unfortunately, a hacky way that is required to guarantee that this preview button is * Unfortunately, a hacky way that is required to guarantee that this preview button is
* actually visible after calling the `render()` method. On some sites, we've noticed that * actually visible after calling the `render()` method. On some sites, we've noticed that
* certain JS events (like `ppcp-hidden`) do not fire in the expected order. This causes * certain JS events (like `ppcp-hidden`) do not fire in the expected order. This causes
* problems with preview buttons not being displayed instantly. * problems with preview buttons not being displayed instantly.
* *
* Using a timeout here will make the button visible again at the end of the current * Using a timeout here will make the button visible again at the end of the current
* event queue. * event queue.
*/ */
setTimeout(() => this.domWrapper.show()); setTimeout( () => this.domWrapper.show() );
} }
remove() { remove() {
this.isVisible = false; this.isVisible = false;
if (this.domWrapper) { if ( this.domWrapper ) {
this.domWrapper.hide().empty(); this.domWrapper.hide().empty();
} }
} }
} }
export default PreviewButton; export default PreviewButton;

View file

@ -6,342 +6,376 @@ import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounc
* Manages all PreviewButton instances of a certain payment method on the page. * Manages all PreviewButton instances of a certain payment method on the page.
*/ */
class PreviewButtonManager { class PreviewButtonManager {
/** /**
* Resolves the promise. * Resolves the promise.
* Used by `this.boostrap()` to process enqueued initialization logic. * Used by `this.boostrap()` to process enqueued initialization logic.
*/ */
#onInitResolver; #onInitResolver;
/** /**
* A deferred Promise that is resolved once the page is ready. * A deferred Promise that is resolved once the page is ready.
* Deferred init logic can be added by using `this.#onInit.then(...)` * Deferred init logic can be added by using `this.#onInit.then(...)`
* *
* @param {Promise<void>|null} * @param {Promise<void>|null}
*/ */
#onInit; #onInit;
constructor({ constructor( { methodName, buttonConfig, defaultAttributes } ) {
methodName, // Define the payment method name in the derived class.
buttonConfig, this.methodName = methodName;
defaultAttributes,
}) {
// Define the payment method name in the derived class.
this.methodName = methodName;
this.buttonConfig = buttonConfig; this.buttonConfig = buttonConfig;
this.defaultAttributes = defaultAttributes; this.defaultAttributes = defaultAttributes;
this.isEnabled = true; this.isEnabled = true;
this.buttons = {}; this.buttons = {};
this.apiConfig = null; this.apiConfig = null;
this.apiError = ''; this.apiError = '';
this.#onInit = new Promise(resolve => { this.#onInit = new Promise( ( resolve ) => {
this.#onInitResolver = resolve; this.#onInitResolver = resolve;
}); } );
this.bootstrap = this.bootstrap.bind(this); this.bootstrap = this.bootstrap.bind( this );
this.renderPreview = this.renderPreview.bind(this); this.renderPreview = this.renderPreview.bind( this );
/** /**
* The "configureAllButtons" method applies ppcpConfig to all buttons that were created * The "configureAllButtons" method applies ppcpConfig to all buttons that were created
* by this PreviewButtonManager instance. We debounce this method, as it should invoke * by this PreviewButtonManager instance. We debounce this method, as it should invoke
* only once, even if called multiple times in a row. * only once, even if called multiple times in a row.
* *
* This is required, as the `ppcp_paypal_render_preview` event does not fire for all * This is required, as the `ppcp_paypal_render_preview` event does not fire for all
* buttons, but only a single time, passing in a random button's wrapper-ID; however, * buttons, but only a single time, passing in a random button's wrapper-ID; however,
* that event should always refresh all preview buttons, not only that single button. * that event should always refresh all preview buttons, not only that single button.
*/ */
this._configureAllButtons = debounce(this._configureAllButtons.bind(this), 100); this._configureAllButtons = debounce(
this._configureAllButtons.bind( this ),
100
);
this.registerEventListeners(); this.registerEventListeners();
} }
/** /**
* Protected method that needs to be implemented by the derived class. * Protected method that needs to be implemented by the derived class.
* Responsible for fetching and returning the PayPal configuration object for this payment * Responsible for fetching and returning the PayPal configuration object for this payment
* method. * method.
* *
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
* @return {Promise<{}>} * @return {Promise<{}>}
*/ */
async fetchConfig(payPal) { async fetchConfig( payPal ) {
throw new Error('The "fetchConfig" method must be implemented by the derived class'); throw new Error(
} 'The "fetchConfig" method must be implemented by the derived class'
);
}
/** /**
* Protected method that needs to be implemented by the derived class. * Protected method that needs to be implemented by the derived class.
* This method is responsible for creating a new PreviewButton instance and returning it. * This method is responsible for creating a new PreviewButton instance and returning it.
* *
* @param {string} wrapperId - CSS ID of the wrapper element. * @param {string} wrapperId - CSS ID of the wrapper element.
* @return {PreviewButton} * @return {PreviewButton}
*/ */
createButtonInstance(wrapperId) { createButtonInstance( wrapperId ) {
throw new Error('The "createButtonInstance" method must be implemented by the derived class'); throw new Error(
} 'The "createButtonInstance" method must be implemented by the derived class'
);
}
/** /**
* In case the button SDK could not be loaded from PayPal, we display this dummy button * In case the button SDK could not be loaded from PayPal, we display this dummy button
* instead of keeping the preview box empty. * instead of keeping the preview box empty.
* *
* This dummy is only visible on the admin side, and not rendered on the front-end. * This dummy is only visible on the admin side, and not rendered on the front-end.
* *
* @todo Consider refactoring this into a new class that extends the PreviewButton class. * @todo Consider refactoring this into a new class that extends the PreviewButton class.
* @param wrapperId * @param wrapperId
* @return {any} * @return {any}
*/ */
createDummy(wrapperId) { createDummy( wrapperId ) {
const elButton = document.createElement('div'); const elButton = document.createElement( 'div' );
elButton.classList.add('ppcp-button-apm', 'ppcp-button-dummy'); elButton.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' );
elButton.innerHTML = `<span>${this.apiError ?? 'Not Available'}</span>`; elButton.innerHTML = `<span>${
this.apiError ?? 'Not Available'
}</span>`;
document.querySelector(wrapperId).appendChild(elButton); document.querySelector( wrapperId ).appendChild( elButton );
const instDummy = { const instDummy = {
setDynamic: () => instDummy, setDynamic: () => instDummy,
setPpcpConfig: () => instDummy, setPpcpConfig: () => instDummy,
render: () => {}, render: () => {},
remove: () => {}, remove: () => {},
}; };
return instDummy; return instDummy;
} }
registerEventListeners() { registerEventListeners() {
jQuery(document).one('DOMContentLoaded', this.bootstrap); jQuery( document ).one( 'DOMContentLoaded', this.bootstrap );
// General event that all APM buttons react to. // General event that all APM buttons react to.
jQuery(document).on('ppcp_paypal_render_preview', this.renderPreview); jQuery( document ).on(
'ppcp_paypal_render_preview',
this.renderPreview
);
// Specific event to only (re)render the current APM button type. // Specific event to only (re)render the current APM button type.
jQuery(document).on(`ppcp_paypal_render_preview_${this.methodName}`, this.renderPreview); jQuery( document ).on(
} `ppcp_paypal_render_preview_${ this.methodName }`,
this.renderPreview
);
}
/** /**
* Output an error message to the console, with a module-specific prefix. * Output an error message to the console, with a module-specific prefix.
*/ * @param message
error(message, ...args) { * @param {...any} args
console.error(`${this.methodName} ${message}`, ...args); */
} error( message, ...args ) {
console.error( `${ this.methodName } ${ message }`, ...args );
}
/** /**
* Whether this is a dynamic preview of the APM button. * Whether this is a dynamic preview of the APM button.
* A dynamic preview adjusts to the current form settings, while a static preview uses the * A dynamic preview adjusts to the current form settings, while a static preview uses the
* style settings that were provided from server-side. * style settings that were provided from server-side.
*/ */
isDynamic() { isDynamic() {
return !!document.querySelector(`[data-ppcp-apm-name="${this.methodName}"]`); return !! document.querySelector(
} `[data-ppcp-apm-name="${ this.methodName }"]`
);
}
/** /**
* Load dependencies and bootstrap the module. * Load dependencies and bootstrap the module.
* Returns a Promise that resolves once all dependencies were loaded and the module can be * Returns a Promise that resolves once all dependencies were loaded and the module can be
* used without limitation. * used without limitation.
* *
* @return {Promise<void>} * @return {Promise<void>}
*/ */
async bootstrap() { async bootstrap() {
const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds. const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds.
const RESOLVE_INTERVAL = 200; const RESOLVE_INTERVAL = 200;
if (!this.buttonConfig?.sdk_url || !widgetBuilder) { if ( ! this.buttonConfig?.sdk_url || ! widgetBuilder ) {
this.error('Button could not be configured.'); this.error( 'Button could not be configured.' );
return; return;
} }
// This is a localization object of "gateway-settings.js". If it's missing, the script was // This is a localization object of "gateway-settings.js". If it's missing, the script was
// not loaded. // not loaded.
if (!window.PayPalCommerceGatewaySettings) { if ( ! window.PayPalCommerceGatewaySettings ) {
this.error( this.error(
'PayPal settings are not fully loaded. Please clear the cache and reload the page.'); 'PayPal settings are not fully loaded. Please clear the cache and reload the page.'
return; );
} return;
}
// A helper function that clears the interval and resolves/rejects the promise. // A helper function that clears the interval and resolves/rejects the promise.
const resolveOrReject = (resolve, reject, id, success = true) => { const resolveOrReject = ( resolve, reject, id, success = true ) => {
clearInterval(id); clearInterval( id );
success ? resolve() : reject('Timeout while waiting for widgetBuilder.paypal'); success
}; ? resolve()
: reject( 'Timeout while waiting for widgetBuilder.paypal' );
};
// Wait for the PayPal SDK to be ready. // Wait for the PayPal SDK to be ready.
const paypalPromise = new Promise((resolve, reject) => { const paypalPromise = new Promise( ( resolve, reject ) => {
let elapsedTime = 0; let elapsedTime = 0;
const id = setInterval(() => { const id = setInterval( () => {
if (widgetBuilder.paypal) { if ( widgetBuilder.paypal ) {
resolveOrReject(resolve, reject, id); resolveOrReject( resolve, reject, id );
} else if (elapsedTime >= MAX_WAIT_TIME) { } else if ( elapsedTime >= MAX_WAIT_TIME ) {
resolveOrReject(resolve, reject, id, false); resolveOrReject( resolve, reject, id, false );
} }
elapsedTime += RESOLVE_INTERVAL; elapsedTime += RESOLVE_INTERVAL;
}, RESOLVE_INTERVAL); }, RESOLVE_INTERVAL );
}); } );
// Load the custom SDK script. // Load the custom SDK script.
const customScriptPromise = loadCustomScript({ url: this.buttonConfig.sdk_url }); const customScriptPromise = loadCustomScript( {
url: this.buttonConfig.sdk_url,
} );
// Wait for both promises to resolve before continuing. // Wait for both promises to resolve before continuing.
await Promise await Promise.all( [ customScriptPromise, paypalPromise ] ).catch(
.all([customScriptPromise, paypalPromise]) ( err ) => {
.catch(err => { console.log(
console.log(`Failed to load ${this.methodName} dependencies:`, err); `Failed to load ${ this.methodName } dependencies:`,
}); err
);
}
);
/* /*
The fetchConfig method requires two objects to succeed: The fetchConfig method requires two objects to succeed:
(a) the SDK custom-script (a) the SDK custom-script
(b) the `widgetBuilder.paypal` object (b) the `widgetBuilder.paypal` object
*/ */
try { try {
this.apiConfig = await this.fetchConfig(widgetBuilder.paypal); this.apiConfig = await this.fetchConfig( widgetBuilder.paypal );
} catch (error) { } catch ( error ) {
this.apiConfig = null; this.apiConfig = null;
} }
// Avoid errors when there was a problem with loading the SDK. // Avoid errors when there was a problem with loading the SDK.
await this.#onInitResolver(); await this.#onInitResolver();
this.#onInit = null; this.#onInit = null;
} }
/** /**
* Event handler, fires on `ppcp_paypal_render_preview` * Event handler, fires on `ppcp_paypal_render_preview`
* *
* @param ev - Ignored * @param ev - Ignored
* @param ppcpConfig - The button settings for the preview. * @param ppcpConfig - The button settings for the preview.
*/ */
renderPreview(ev, ppcpConfig) { renderPreview( ev, ppcpConfig ) {
const id = ppcpConfig.button.wrapper; const id = ppcpConfig.button.wrapper;
if (!id) { if ( ! id ) {
this.error('Button did not provide a wrapper ID', ppcpConfig); this.error( 'Button did not provide a wrapper ID', ppcpConfig );
return; return;
} }
if (!this.shouldInsertPreviewButton(id)) { if ( ! this.shouldInsertPreviewButton( id ) ) {
return; return;
} }
if (!this.buttons[id]) { if ( ! this.buttons[ id ] ) {
this._addButton(id, ppcpConfig); this._addButton( id, ppcpConfig );
} else { } else {
// This is a debounced method, that fires after 100ms. // This is a debounced method, that fires after 100ms.
this._configureAllButtons(ppcpConfig); this._configureAllButtons( ppcpConfig );
} }
} }
/** /**
* Determines if the preview box supports the current button. * Determines if the preview box supports the current button.
* *
* When this function returns false, this manager instance does not create a new preview button. * When this function returns false, this manager instance does not create a new preview button.
* *
* @param {string} previewId - ID of the inner preview box container. * @param {string} previewId - ID of the inner preview box container.
* @return {boolean} True if the box is eligible for the preview button, false otherwise. * @return {boolean} True if the box is eligible for the preview button, false otherwise.
*/ */
shouldInsertPreviewButton(previewId) { shouldInsertPreviewButton( previewId ) {
const container = document.querySelector(previewId); const container = document.querySelector( previewId );
const box = container.closest('.ppcp-preview'); const box = container.closest( '.ppcp-preview' );
const limit = box.dataset.ppcpPreviewBlock ?? 'all'; const limit = box.dataset.ppcpPreviewBlock ?? 'all';
return ('all' === limit) || (this.methodName === limit); return 'all' === limit || this.methodName === limit;
} }
/** /**
* Applies a new configuration to an existing preview button. * Applies a new configuration to an existing preview button.
*/ * @param id
_configureButton(id, ppcpConfig) { * @param ppcpConfig
this.buttons[id] */
.setDynamic(this.isDynamic()) _configureButton( id, ppcpConfig ) {
.setPpcpConfig(ppcpConfig) this.buttons[ id ]
.render(); .setDynamic( this.isDynamic() )
} .setPpcpConfig( ppcpConfig )
.render();
}
/** /**
* Apples the provided configuration to all existing preview buttons. * Apples the provided configuration to all existing preview buttons.
*/ * @param ppcpConfig
_configureAllButtons(ppcpConfig) { */
Object.entries(this.buttons).forEach(([id, button]) => { _configureAllButtons( ppcpConfig ) {
this._configureButton(id, { Object.entries( this.buttons ).forEach( ( [ id, button ] ) => {
...ppcpConfig, this._configureButton( id, {
button: { ...ppcpConfig,
...ppcpConfig.button, button: {
...ppcpConfig.button,
// The ppcpConfig object might refer to a different wrapper. // The ppcpConfig object might refer to a different wrapper.
// Fix the selector, to avoid unintentionally hidden preview buttons. // Fix the selector, to avoid unintentionally hidden preview buttons.
wrapper: button.wrapper, wrapper: button.wrapper,
}, },
}); } );
}); } );
} }
/** /**
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves. * Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
*/ * @param id
_addButton(id, ppcpConfig) { * @param ppcpConfig
const createButton = () => { */
if (!this.buttons[id]) { _addButton( id, ppcpConfig ) {
let newInst; const createButton = () => {
if (this.apiConfig && 'object' === typeof this.apiConfig) { if ( ! this.buttons[ id ] ) {
newInst = this.createButtonInstance(id).setButtonConfig(this.buttonConfig); let newInst;
} else { if ( this.apiConfig && 'object' === typeof this.apiConfig ) {
newInst = this.createDummy(id); newInst = this.createButtonInstance( id ).setButtonConfig(
} this.buttonConfig
);
} else {
newInst = this.createDummy( id );
}
this.buttons[id] = newInst; this.buttons[ id ] = newInst;
} }
this._configureButton(id, ppcpConfig); this._configureButton( id, ppcpConfig );
}; };
if (this.#onInit) { if ( this.#onInit ) {
this.#onInit.then(createButton); this.#onInit.then( createButton );
} else { } else {
createButton(); createButton();
} }
} }
/** /**
* Refreshes all buttons using the latest buttonConfig. * Refreshes all buttons using the latest buttonConfig.
* *
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
renderButtons() { renderButtons() {
if (this.isEnabled) { if ( this.isEnabled ) {
Object.values(this.buttons).forEach(button => button.render()); Object.values( this.buttons ).forEach( ( button ) =>
} else { button.render()
Object.values(this.buttons).forEach(button => button.remove()); );
} } else {
Object.values( this.buttons ).forEach( ( button ) =>
button.remove()
);
}
return this; return this;
} }
/** /**
* Enables this payment method, which re-creates or refreshes all buttons. * Enables this payment method, which re-creates or refreshes all buttons.
* *
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
enable() { enable() {
if (!this.isEnabled) { if ( ! this.isEnabled ) {
this.isEnabled = true; this.isEnabled = true;
this.renderButtons(); this.renderButtons();
} }
return this; return this;
} }
/** /**
* Disables this payment method, effectively removing all preview buttons. * Disables this payment method, effectively removing all preview buttons.
* *
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
disable() { disable() {
if (!this.isEnabled) { if ( ! this.isEnabled ) {
this.isEnabled = false; this.isEnabled = false;
this.renderButtons(); this.renderButtons();
} }
return this; return this;
} }
} }
export default PreviewButtonManager; export default PreviewButtonManager;

View file

@ -1,214 +1,290 @@
import merge from "deepmerge"; import merge from 'deepmerge';
import {loadScript} from "@paypal/paypal-js"; import { loadScript } from '@paypal/paypal-js';
import {keysToCamelCase} from "../Helper/Utils"; import { keysToCamelCase } from '../Helper/Utils';
import widgetBuilder from "./WidgetBuilder"; import widgetBuilder from './WidgetBuilder';
import {normalizeStyleForFundingSource} from "../Helper/Style"; import { normalizeStyleForFundingSource } from '../Helper/Style';
import { import {
handleShippingOptionsChange, handleShippingOptionsChange,
handleShippingAddressChange, handleShippingAddressChange,
} from "../Helper/ShippingHandler.js"; } from '../Helper/ShippingHandler.js';
class Renderer { class Renderer {
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) { constructor(
this.defaultSettings = defaultSettings; creditCardRenderer,
this.creditCardRenderer = creditCardRenderer; defaultSettings,
this.onSmartButtonClick = onSmartButtonClick; onSmartButtonClick,
this.onSmartButtonsInit = onSmartButtonsInit; onSmartButtonsInit
) {
this.defaultSettings = defaultSettings;
this.creditCardRenderer = creditCardRenderer;
this.onSmartButtonClick = onSmartButtonClick;
this.onSmartButtonsInit = onSmartButtonsInit;
this.buttonsOptions = {}; this.buttonsOptions = {};
this.onButtonsInitListeners = {}; this.onButtonsInitListeners = {};
this.renderedSources = new Set(); this.renderedSources = new Set();
this.reloadEventName = 'ppcp-reload-buttons'; this.reloadEventName = 'ppcp-reload-buttons';
} }
render(contextConfig, settingsOverride = {}, contextConfigOverride = () => {}) { render(
const settings = merge(this.defaultSettings, settingsOverride); contextConfig,
settingsOverride = {},
contextConfigOverride = () => {}
) {
const settings = merge( this.defaultSettings, settingsOverride );
const enabledSeparateGateways = Object.fromEntries(Object.entries( const enabledSeparateGateways = Object.fromEntries(
settings.separate_buttons).filter(([s, data]) => document.querySelector(data.wrapper) Object.entries( settings.separate_buttons ).filter(
)); ( [ s, data ] ) => document.querySelector( data.wrapper )
const hasEnabledSeparateGateways = Object.keys(enabledSeparateGateways).length !== 0; )
);
const hasEnabledSeparateGateways =
Object.keys( enabledSeparateGateways ).length !== 0;
if (!hasEnabledSeparateGateways) { if ( ! hasEnabledSeparateGateways ) {
this.renderButtons( this.renderButtons(
settings.button.wrapper, settings.button.wrapper,
settings.button.style, settings.button.style,
contextConfig, contextConfig,
hasEnabledSeparateGateways hasEnabledSeparateGateways
); );
} else { } else {
// render each button separately // render each button separately
for (const fundingSource of paypal.getFundingSources().filter(s => !(s in enabledSeparateGateways))) { for ( const fundingSource of paypal
const style = normalizeStyleForFundingSource(settings.button.style, fundingSource); .getFundingSources()
.filter( ( s ) => ! ( s in enabledSeparateGateways ) ) ) {
const style = normalizeStyleForFundingSource(
settings.button.style,
fundingSource
);
this.renderButtons( this.renderButtons(
settings.button.wrapper, settings.button.wrapper,
style, style,
contextConfig, contextConfig,
hasEnabledSeparateGateways, hasEnabledSeparateGateways,
fundingSource fundingSource
); );
} }
} }
if (this.creditCardRenderer) { if ( this.creditCardRenderer ) {
this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfigOverride); this.creditCardRenderer.render(
} settings.hosted_fields.wrapper,
contextConfigOverride
);
}
for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) { for ( const [ fundingSource, data ] of Object.entries(
this.renderButtons( enabledSeparateGateways
data.wrapper, ) ) {
data.style, this.renderButtons(
contextConfig, data.wrapper,
hasEnabledSeparateGateways, data.style,
fundingSource contextConfig,
); hasEnabledSeparateGateways,
} fundingSource
} );
}
}
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) { renderButtons(
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) { wrapper,
// Try to render registered buttons again in case they were removed from the DOM by an external source. style,
widgetBuilder.renderButtons([wrapper, fundingSource]); contextConfig,
return; hasEnabledSeparateGateways,
} fundingSource = null
) {
if (
! document.querySelector( wrapper ) ||
this.isAlreadyRendered(
wrapper,
fundingSource,
hasEnabledSeparateGateways
)
) {
// Try to render registered buttons again in case they were removed from the DOM by an external source.
widgetBuilder.renderButtons( [ wrapper, fundingSource ] );
return;
}
if (fundingSource) { if ( fundingSource ) {
contextConfig.fundingSource = fundingSource; contextConfig.fundingSource = fundingSource;
} }
let venmoButtonClicked = false; let venmoButtonClicked = false;
const buttonsOptions = () => { const buttonsOptions = () => {
const options = { const options = {
style, style,
...contextConfig, ...contextConfig,
onClick: (data, actions) => { onClick: ( data, actions ) => {
if (this.onSmartButtonClick) { if ( this.onSmartButtonClick ) {
this.onSmartButtonClick(data, actions); this.onSmartButtonClick( data, actions );
} }
venmoButtonClicked = false; venmoButtonClicked = false;
if (data.fundingSource === 'venmo') { if ( data.fundingSource === 'venmo' ) {
venmoButtonClicked = true; venmoButtonClicked = true;
} }
}, },
onInit: (data, actions) => { onInit: ( data, actions ) => {
if (this.onSmartButtonsInit) { if ( this.onSmartButtonsInit ) {
this.onSmartButtonsInit(data, actions); this.onSmartButtonsInit( data, actions );
} }
this.handleOnButtonsInit(wrapper, data, actions); this.handleOnButtonsInit( wrapper, data, actions );
}, },
}; };
// Check the condition and add the handler if needed // Check the condition and add the handler if needed
if (this.defaultSettings.should_handle_shipping_in_paypal) { if ( this.defaultSettings.should_handle_shipping_in_paypal ) {
options.onShippingOptionsChange = (data, actions) => { options.onShippingOptionsChange = ( data, actions ) => {
!this.isVenmoButtonClickedWhenVaultingIsEnabled(venmoButtonClicked) let shippingOptionsChange =
? handleShippingOptionsChange(data, actions, this.defaultSettings) ! this.isVenmoButtonClickedWhenVaultingIsEnabled(
: null; venmoButtonClicked
} )
options.onShippingAddressChange = (data, actions) => { ? handleShippingOptionsChange(
!this.isVenmoButtonClickedWhenVaultingIsEnabled(venmoButtonClicked) data,
? handleShippingAddressChange(data, actions, this.defaultSettings) actions,
: null; this.defaultSettings
} )
} : null;
return options; return shippingOptionsChange
}; };
options.onShippingAddressChange = ( data, actions ) => {
let shippingAddressChange =
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
venmoButtonClicked
)
? handleShippingAddressChange(
data,
actions,
this.defaultSettings
)
: null;
jQuery(document) return shippingAddressChange
.off(this.reloadEventName, wrapper) };
.on(this.reloadEventName, wrapper, (event, settingsOverride = {}, triggeredFundingSource) => { }
// Only accept events from the matching funding source return options;
if (fundingSource && triggeredFundingSource && (triggeredFundingSource !== fundingSource)) { };
return;
}
const settings = merge(this.defaultSettings, settingsOverride); jQuery( document )
let scriptOptions = keysToCamelCase(settings.url_params); .off( this.reloadEventName, wrapper )
scriptOptions = merge(scriptOptions, settings.script_attributes); .on(
this.reloadEventName,
wrapper,
( event, settingsOverride = {}, triggeredFundingSource ) => {
// Only accept events from the matching funding source
if (
fundingSource &&
triggeredFundingSource &&
triggeredFundingSource !== fundingSource
) {
return;
}
loadScript(scriptOptions).then((paypal) => { const settings = merge(
widgetBuilder.setPaypal(paypal); this.defaultSettings,
widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions()); settingsOverride
widgetBuilder.renderAll(); );
}); let scriptOptions = keysToCamelCase( settings.url_params );
}); scriptOptions = merge(
scriptOptions,
settings.script_attributes
);
this.renderedSources.add(wrapper + (fundingSource ?? '')); loadScript( scriptOptions ).then( ( paypal ) => {
widgetBuilder.setPaypal( paypal );
widgetBuilder.registerButtons(
[ wrapper, fundingSource ],
buttonsOptions()
);
widgetBuilder.renderAll();
} );
}
);
if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') { this.renderedSources.add( wrapper + ( fundingSource ?? '' ) );
widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions());
widgetBuilder.renderButtons([wrapper, fundingSource]);
}
}
isVenmoButtonClickedWhenVaultingIsEnabled = (venmoButtonClicked) => { if (
return venmoButtonClicked && this.defaultSettings.vaultingEnabled; typeof paypal !== 'undefined' &&
} typeof paypal.Buttons !== 'undefined'
) {
widgetBuilder.registerButtons(
[ wrapper, fundingSource ],
buttonsOptions()
);
widgetBuilder.renderButtons( [ wrapper, fundingSource ] );
}
}
isAlreadyRendered(wrapper, fundingSource) { isVenmoButtonClickedWhenVaultingIsEnabled = ( venmoButtonClicked ) => {
return this.renderedSources.has(wrapper + (fundingSource ?? '')); return venmoButtonClicked && this.defaultSettings.vaultingEnabled;
} };
disableCreditCardFields() { isAlreadyRendered( wrapper, fundingSource ) {
this.creditCardRenderer.disableFields(); return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) );
} }
enableCreditCardFields() { disableCreditCardFields() {
this.creditCardRenderer.enableFields(); this.creditCardRenderer.disableFields();
} }
onButtonsInit(wrapper, handler, reset) { enableCreditCardFields() {
this.onButtonsInitListeners[wrapper] = reset ? [] : (this.onButtonsInitListeners[wrapper] || []); this.creditCardRenderer.enableFields();
this.onButtonsInitListeners[wrapper].push(handler); }
}
handleOnButtonsInit(wrapper, data, actions) { onButtonsInit( wrapper, handler, reset ) {
this.onButtonsInitListeners[ wrapper ] = reset
? []
: this.onButtonsInitListeners[ wrapper ] || [];
this.onButtonsInitListeners[ wrapper ].push( handler );
}
this.buttonsOptions[wrapper] = { handleOnButtonsInit( wrapper, data, actions ) {
data: data, this.buttonsOptions[ wrapper ] = {
actions: actions data,
} actions,
};
if (this.onButtonsInitListeners[wrapper]) { if ( this.onButtonsInitListeners[ wrapper ] ) {
for (let handler of this.onButtonsInitListeners[wrapper]) { for ( const handler of this.onButtonsInitListeners[ wrapper ] ) {
if (typeof handler === 'function') { if ( typeof handler === 'function' ) {
handler({ handler( {
wrapper: wrapper, wrapper,
...this.buttonsOptions[wrapper] ...this.buttonsOptions[ wrapper ],
}); } );
} }
} }
} }
} }
disableSmartButtons(wrapper) { disableSmartButtons( wrapper ) {
if (!this.buttonsOptions[wrapper]) { if ( ! this.buttonsOptions[ wrapper ] ) {
return; return;
} }
try { try {
this.buttonsOptions[wrapper].actions.disable(); this.buttonsOptions[ wrapper ].actions.disable();
} catch (err) { } catch ( err ) {
console.log('Failed to disable buttons: ' + err); console.log( 'Failed to disable buttons: ' + err );
} }
} }
enableSmartButtons(wrapper) { enableSmartButtons( wrapper ) {
if (!this.buttonsOptions[wrapper]) { if ( ! this.buttonsOptions[ wrapper ] ) {
return; return;
} }
try { try {
this.buttonsOptions[wrapper].actions.enable(); this.buttonsOptions[ wrapper ].actions.enable();
} catch (err) { } catch ( err ) {
console.log('Failed to enable buttons: ' + err); console.log( 'Failed to enable buttons: ' + err );
} }
} }
} }
export default Renderer; export default Renderer;

View file

@ -3,179 +3,178 @@
* To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource]. * To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource].
*/ */
class WidgetBuilder { class WidgetBuilder {
constructor() {
this.paypal = null;
this.buttons = new Map();
this.messages = new Map();
constructor() { this.renderEventName = 'ppcp-render';
this.paypal = null;
this.buttons = new Map();
this.messages = new Map();
this.renderEventName = 'ppcp-render'; document.ppcpWidgetBuilderStatus = () => {
console.log( {
buttons: this.buttons,
messages: this.messages,
} );
};
document.ppcpWidgetBuilderStatus = () => { jQuery( document )
console.log({ .off( this.renderEventName )
buttons: this.buttons, .on( this.renderEventName, () => {
messages: this.messages, this.renderAll();
}); } );
} }
jQuery(document) setPaypal( paypal ) {
.off(this.renderEventName) this.paypal = paypal;
.on(this.renderEventName, () => { jQuery( document ).trigger( 'ppcp-paypal-loaded', paypal );
this.renderAll(); }
});
}
setPaypal(paypal) { registerButtons( wrapper, options ) {
this.paypal = paypal; wrapper = this.sanitizeWrapper( wrapper );
jQuery(document).trigger('ppcp-paypal-loaded', paypal);
}
registerButtons(wrapper, options) { this.buttons.set( this.toKey( wrapper ), {
wrapper = this.sanitizeWrapper(wrapper); wrapper,
options,
} );
}
this.buttons.set(this.toKey(wrapper), { renderButtons( wrapper ) {
wrapper: wrapper, wrapper = this.sanitizeWrapper( wrapper );
options: options,
});
}
renderButtons(wrapper) { if ( ! this.buttons.has( this.toKey( wrapper ) ) ) {
wrapper = this.sanitizeWrapper(wrapper); return;
}
if (!this.buttons.has(this.toKey(wrapper))) { if ( this.hasRendered( wrapper ) ) {
return; return;
} }
if (this.hasRendered(wrapper)) { const entry = this.buttons.get( this.toKey( wrapper ) );
return; const btn = this.paypal.Buttons( entry.options );
}
const entry = this.buttons.get(this.toKey(wrapper)); if ( ! btn.isEligible() ) {
const btn = this.paypal.Buttons(entry.options); this.buttons.delete( this.toKey( wrapper ) );
return;
}
if (!btn.isEligible()) { const target = this.buildWrapperTarget( wrapper );
this.buttons.delete(this.toKey(wrapper));
return;
}
let target = this.buildWrapperTarget(wrapper); if ( ! target ) {
return;
}
if (!target) { btn.render( target );
return; }
}
btn.render(target); renderAllButtons() {
} for ( const [ wrapper, entry ] of this.buttons ) {
this.renderButtons( wrapper );
}
}
renderAllButtons() { registerMessages( wrapper, options ) {
for (const [wrapper, entry] of this.buttons) { this.messages.set( wrapper, {
this.renderButtons(wrapper); wrapper,
} options,
} } );
}
registerMessages(wrapper, options) { renderMessages( wrapper ) {
this.messages.set(wrapper, { if ( ! this.messages.has( wrapper ) ) {
wrapper: wrapper, return;
options: options }
});
}
renderMessages(wrapper) { const entry = this.messages.get( wrapper );
if (!this.messages.has(wrapper)) {
return;
}
const entry = this.messages.get(wrapper); if ( this.hasRendered( wrapper ) ) {
const element = document.querySelector( wrapper );
element.setAttribute( 'data-pp-amount', entry.options.amount );
return;
}
if (this.hasRendered(wrapper)) { const btn = this.paypal.Messages( entry.options );
const element = document.querySelector(wrapper);
element.setAttribute('data-pp-amount', entry.options.amount);
return;
}
const btn = this.paypal.Messages(entry.options); btn.render( entry.wrapper );
btn.render(entry.wrapper); // watchdog to try to handle some strange cases where the wrapper may not be present
setTimeout( () => {
if ( ! this.hasRendered( wrapper ) ) {
btn.render( entry.wrapper );
}
}, 100 );
}
// watchdog to try to handle some strange cases where the wrapper may not be present renderAllMessages() {
setTimeout(() => { for ( const [ wrapper, entry ] of this.messages ) {
if (!this.hasRendered(wrapper)) { this.renderMessages( wrapper );
btn.render(entry.wrapper); }
} }
}, 100);
}
renderAllMessages() { renderAll() {
for (const [wrapper, entry] of this.messages) { this.renderAllButtons();
this.renderMessages(wrapper); this.renderAllMessages();
} }
}
renderAll() { hasRendered( wrapper ) {
this.renderAllButtons(); let selector = wrapper;
this.renderAllMessages();
}
hasRendered(wrapper) { if ( Array.isArray( wrapper ) ) {
let selector = wrapper; selector = wrapper[ 0 ];
for ( const item of wrapper.slice( 1 ) ) {
selector += ' .item-' + item;
}
}
if (Array.isArray(wrapper)) { const element = document.querySelector( selector );
selector = wrapper[0]; return element && element.hasChildNodes();
for (const item of wrapper.slice(1)) { }
selector += ' .item-' + item;
}
}
const element = document.querySelector(selector); sanitizeWrapper( wrapper ) {
return element && element.hasChildNodes(); if ( Array.isArray( wrapper ) ) {
} wrapper = wrapper.filter( ( item ) => !! item );
if ( wrapper.length === 1 ) {
wrapper = wrapper[ 0 ];
}
}
return wrapper;
}
sanitizeWrapper(wrapper) { buildWrapperTarget( wrapper ) {
if (Array.isArray(wrapper)) { let target = wrapper;
wrapper = wrapper.filter(item => !!item);
if (wrapper.length === 1) {
wrapper = wrapper[0];
}
}
return wrapper;
}
buildWrapperTarget(wrapper) { if ( Array.isArray( wrapper ) ) {
let target = wrapper; const $wrapper = jQuery( wrapper[ 0 ] );
if (Array.isArray(wrapper)) { if ( ! $wrapper.length ) {
const $wrapper = jQuery(wrapper[0]); return;
}
if (!$wrapper.length) { const itemClass = 'item-' + wrapper[ 1 ];
return;
}
const itemClass = 'item-' + wrapper[1]; // Check if the parent element exists and it doesn't already have the div with the class
let $item = $wrapper.find( '.' + itemClass );
// Check if the parent element exists and it doesn't already have the div with the class if ( ! $item.length ) {
let $item = $wrapper.find('.' + itemClass); $item = jQuery( `<div class="${ itemClass }"></div>` );
$wrapper.append( $item );
}
if (!$item.length) { target = $item.get( 0 );
$item = jQuery(`<div class="${itemClass}"></div>`); }
$wrapper.append($item);
}
target = $item.get(0); if ( ! jQuery( target ).length ) {
} return null;
}
if (!jQuery(target).length) { return target;
return null; }
}
return target; toKey( wrapper ) {
} if ( Array.isArray( wrapper ) ) {
return JSON.stringify( wrapper );
toKey(wrapper) { }
if (Array.isArray(wrapper)) { return wrapper;
return JSON.stringify(wrapper); }
}
return wrapper;
}
} }
window.widgetBuilder = window.widgetBuilder || new WidgetBuilder(); window.widgetBuilder = window.widgetBuilder || new WidgetBuilder();

View file

@ -829,7 +829,9 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
*/ */
do_action( "ppcp_before_{$location_hook}_message_wrapper" ); do_action( "ppcp_before_{$location_hook}_message_wrapper" );
$messages_placeholder = '<div class="ppcp-messages" data-partner-attribution-id="Woo_PPCP"></div>'; $bn_code = PPCP_PAYPAL_BN_CODE;
$messages_placeholder = '<div class="ppcp-messages" data-partner-attribution-id="' . esc_attr( $bn_code ) . '"></div>';
if ( is_array( $block_params ) && ( $block_params['blockName'] ?? false ) ) { if ( is_array( $block_params ) && ( $block_params['blockName'] ?? false ) ) {
$this->render_after_block( $this->render_after_block(
@ -1163,11 +1165,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
), ),
'update_customer_shipping' => array( 'update_customer_shipping' => array(
'shipping_options' => array( 'shipping_options' => array(
'endpoint' => '/wp-json/wc/store/cart/select-shipping-rate', 'endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'select-shipping-rate' ),
), ),
'shipping_address' => array( 'shipping_address' => array(
'cart_endpoint' => '/wp-json/wc/store/cart/', 'cart_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT ),
'update_customer_endpoint' => '/wp-json/wc/store/v1/cart/update-customer/', 'update_customer_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'update-customer' ),
), ),
'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ), 'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ),
'update_shipping_method' => \WC_AJAX::get_endpoint( 'update_shipping_method' ), 'update_shipping_method' => \WC_AJAX::get_endpoint( 'update_shipping_method' ),
@ -1511,7 +1513,10 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
private function bn_code_for_context( string $context ): string { private function bn_code_for_context( string $context ): string {
$codes = $this->bn_codes(); $codes = $this->bn_codes();
return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : 'Woo_PPCP';
$bn_code = PPCP_PAYPAL_BN_CODE;
return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : $bn_code;
} }
/** /**
@ -1519,13 +1524,15 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
* *
* @return array * @return array
*/ */
private function bn_codes(): array { private function bn_codes() : array {
$bn_code = PPCP_PAYPAL_BN_CODE;
return array( return array(
'checkout' => 'Woo_PPCP', 'checkout' => $bn_code,
'cart' => 'Woo_PPCP', 'cart' => $bn_code,
'mini-cart' => 'Woo_PPCP', 'mini-cart' => $bn_code,
'product' => 'Woo_PPCP', 'product' => $bn_code,
); );
} }

View file

@ -14,8 +14,10 @@ use WC_Cart;
use WC_Order; use WC_Order;
use WC_Order_Item_Product; use WC_Order_Item_Product;
use WC_Order_Item_Shipping; use WC_Order_Item_Shipping;
use WC_Product;
use WC_Subscription; use WC_Subscription;
use WC_Subscriptions_Product; use WC_Subscriptions_Product;
use WC_Tax;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping; use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
@ -106,6 +108,7 @@ class WooCommerceOrderCreator {
* @param Payer|null $payer The payer. * @param Payer|null $payer The payer.
* @param Shipping|null $shipping The shipping. * @param Shipping|null $shipping The shipping.
* @return void * @return void
* @psalm-suppress InvalidScalarArgument
*/ */
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void { protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void {
$cart_contents = $wc_cart->get_cart(); $cart_contents = $wc_cart->get_cart();
@ -130,18 +133,21 @@ class WooCommerceOrderCreator {
return; return;
} }
$total = $product->get_price() * $quantity; $subtotal = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
$subtotal = apply_filters( 'woocommerce_paypal_payments_shipping_callback_cart_line_item_total', $subtotal, $cart_item );
$item->set_name( $product->get_name() ); $item->set_name( $product->get_name() );
$item->set_subtotal( $total ); $item->set_subtotal( $subtotal );
$item->set_total( $total ); $item->set_total( $subtotal );
$this->configure_taxes( $product, $item, $subtotal );
$product_id = $product->get_id(); $product_id = $product->get_id();
if ( $this->is_subscription( $product_id ) ) { if ( $this->is_subscription( $product_id ) ) {
$subscription = $this->create_subscription( $wc_order, $product_id ); $subscription = $this->create_subscription( $wc_order, $product_id );
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product ); $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product );
$subscription_total = $total + $sign_up_fee; $subscription_total = (float) $subtotal + (float) $sign_up_fee;
$item->set_subtotal( $subscription_total ); $item->set_subtotal( $subscription_total );
$item->set_total( $subscription_total ); $item->set_total( $subscription_total );
@ -282,6 +288,30 @@ class WooCommerceOrderCreator {
} }
} }
/**
* Configures the taxes.
*
* @param WC_Product $product The Product.
* @param WC_Order_Item_Product $item The line item.
* @param float|string $subtotal The subtotal.
* @return void
* @psalm-suppress InvalidScalarArgument
*/
protected function configure_taxes( WC_Product $product, WC_Order_Item_Product $item, $subtotal ): void {
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
$taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, true );
$item->set_tax_class( $product->get_tax_class() );
$item->set_total_tax( (float) array_sum( $taxes ) );
foreach ( $taxes as $tax_rate_id => $tax_amount ) {
if ( $tax_amount > 0 ) {
$item->add_meta_data( 'tax_rate_id', $tax_rate_id, true );
$item->add_meta_data( 'tax_amount', $tax_amount, true );
}
}
}
/** /**
* Checks if the product with given ID is WC subscription. * Checks if the product with given ID is WC subscription.
* *

View file

@ -150,6 +150,30 @@ return array(
'TWD', 'TWD',
'USD', 'USD',
), ),
'CN' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CY' => array( 'CY' => array(
'AUD', 'AUD',
'BRL', 'BRL',

View file

@ -1,66 +1,93 @@
document.addEventListener( document.addEventListener( 'DOMContentLoaded', () => {
'DOMContentLoaded', const config = PayPalCommerceGatewayOrderTrackingCompat;
() => {
const config = PayPalCommerceGatewayOrderTrackingCompat;
const orderTrackingContainerId = "ppcp_order-tracking"; const orderTrackingContainerId = 'ppcp_order-tracking';
const orderTrackingContainerSelector = "#ppcp_order-tracking .ppcp-tracking-column.shipments"; const orderTrackingContainerSelector =
const gzdSaveButton = document.getElementById('order-shipments-save'); '#ppcp_order-tracking .ppcp-tracking-column.shipments';
const loadLocation = location.href + " " + orderTrackingContainerSelector + ">*"; const gzdSaveButton = document.getElementById( 'order-shipments-save' );
const gzdSyncEnabled = config.gzd_sync_enabled; const loadLocation =
const wcShipmentSyncEnabled = config.wc_shipment_sync_enabled; location.href + ' ' + orderTrackingContainerSelector + '>*';
const wcShippingTaxSyncEnabled = config.wc_shipping_tax_sync_enabled; const gzdSyncEnabled = config.gzd_sync_enabled;
const wcShipmentSaveButton = document.querySelector('#woocommerce-shipment-tracking .button-save-form'); const wcShipmentSyncEnabled = config.wc_shipment_sync_enabled;
const wcShipmentTaxBuyLabelButtonSelector = '.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button'; const wcShippingTaxSyncEnabled = config.wc_shipping_tax_sync_enabled;
const wcShipmentSaveButton = document.querySelector(
'#woocommerce-shipment-tracking .button-save-form'
);
const wcShipmentTaxBuyLabelButtonSelector =
'.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button';
const toggleLoaderVisibility = function() { const toggleLoaderVisibility = function () {
const loader = document.querySelector('.ppcp-tracking-loader'); const loader = document.querySelector( '.ppcp-tracking-loader' );
if (loader) { if ( loader ) {
if (loader.style.display === 'none' || loader.style.display === '') { if (
loader.style.display = 'block'; loader.style.display === 'none' ||
} else { loader.style.display === ''
loader.style.display = 'none'; ) {
} loader.style.display = 'block';
} } else {
} loader.style.display = 'none';
}
}
};
const waitForTrackingUpdate = function (elementToCheck) { const waitForTrackingUpdate = function ( elementToCheck ) {
if (elementToCheck.css('display') !== 'none') { if ( elementToCheck.css( 'display' ) !== 'none' ) {
setTimeout(() => waitForTrackingUpdate(elementToCheck), 100); setTimeout( () => waitForTrackingUpdate( elementToCheck ), 100 );
} else { } else {
jQuery(orderTrackingContainerSelector).load(loadLocation, "", function(){ jQuery( orderTrackingContainerSelector ).load(
toggleLoaderVisibility(); loadLocation,
}); '',
} function () {
} toggleLoaderVisibility();
}
);
}
};
if (gzdSyncEnabled && typeof(gzdSaveButton) != 'undefined' && gzdSaveButton != null) { if (
gzdSaveButton.addEventListener('click', function (event) { gzdSyncEnabled &&
toggleLoaderVisibility(); typeof gzdSaveButton !== 'undefined' &&
waitForTrackingUpdate(jQuery('#order-shipments-save')); gzdSaveButton != null
}) ) {
} gzdSaveButton.addEventListener( 'click', function ( event ) {
toggleLoaderVisibility();
waitForTrackingUpdate( jQuery( '#order-shipments-save' ) );
} );
}
if (wcShipmentSyncEnabled && typeof(wcShipmentSaveButton) != 'undefined' && wcShipmentSaveButton != null) { if (
wcShipmentSaveButton.addEventListener('click', function (event) { wcShipmentSyncEnabled &&
toggleLoaderVisibility(); typeof wcShipmentSaveButton !== 'undefined' &&
waitForTrackingUpdate(jQuery('#shipment-tracking-form')); wcShipmentSaveButton != null
}) ) {
} wcShipmentSaveButton.addEventListener( 'click', function ( event ) {
toggleLoaderVisibility();
waitForTrackingUpdate( jQuery( '#shipment-tracking-form' ) );
} );
}
if (wcShippingTaxSyncEnabled && typeof(wcShippingTaxSyncEnabled) != 'undefined' && wcShippingTaxSyncEnabled != null) { if (
document.addEventListener('click', function(event) { wcShippingTaxSyncEnabled &&
const wcShipmentTaxBuyLabelButton = event.target.closest(wcShipmentTaxBuyLabelButtonSelector); typeof wcShippingTaxSyncEnabled !== 'undefined' &&
wcShippingTaxSyncEnabled != null
) {
document.addEventListener( 'click', function ( event ) {
const wcShipmentTaxBuyLabelButton = event.target.closest(
wcShipmentTaxBuyLabelButtonSelector
);
if (wcShipmentTaxBuyLabelButton) { if ( wcShipmentTaxBuyLabelButton ) {
toggleLoaderVisibility(); toggleLoaderVisibility();
setTimeout(function () { setTimeout( function () {
jQuery(orderTrackingContainerSelector).load(loadLocation, "", function(){ jQuery( orderTrackingContainerSelector ).load(
toggleLoaderVisibility(); loadLocation,
}); '',
}, 10000); function () {
} toggleLoaderVisibility();
}); }
} );
}, }, 10000 );
); }
} );
}
} );

Some files were not shown because too many files have changed in this diff Show more