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', '');
}
if (!defined('PPCP_PAYPAL_BN_CODE')) {
define('PPCP_PAYPAL_BN_CODE', 'Woo_PPCP');
}
/**
* Cancel the next occurrence of a scheduled action.
*

View file

@ -1,5 +1,23 @@
*** 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 =
* 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

View file

@ -723,6 +723,30 @@ return array(
'TWD',
'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(
'AUD',
'BRL',
@ -1416,6 +1440,10 @@ return array(
'visa' => array(),
'amex' => array(),
),
'CN' => array(
'mastercard' => array(),
'visa' => array(),
),
'CY' => array(
'mastercard' => array(),
'visa' => array(),

View file

@ -42,7 +42,7 @@ trait RequestTrait {
*/
$args = apply_filters( 'ppcp_request_args', $args, $url );
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 );

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

View file

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

View file

@ -1,95 +1,88 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import CartActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
import {isPayPalSubscription} from "../../../../ppcp-blocks/resources/js/Helper/Subscription";
import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler';
import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription';
class BaseHandler {
constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
constructor(buttonConfig, ppcpConfig) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
isVaultV3Mode() {
return (
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() {
return 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
}
validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) {
return this.isVaultV3Mode();
}
return true;
}
validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) {
return this.isVaultV3Mode();
}
return true;
}
shippingAllowed() {
return this.buttonConfig.product.needsShipping;
}
shippingAllowed() {
return this.buttonConfig.product.needsShipping;
}
transactionInfo() {
return new Promise( ( resolve, reject ) => {
const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint;
const separator = endpoint.indexOf( '?' ) !== -1 ? '&' : '?';
transactionInfo() {
return new Promise((resolve, reject) => {
const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint;
const separator = (endpoint.indexOf('?') !== -1) ? '&' : '?';
fetch( endpoint + separator + 'shipping=1', {
method: 'GET',
credentials: 'same-origin',
} )
.then( ( result ) => result.json() )
.then( ( result ) => {
if ( ! result.success ) {
return;
}
fetch(
endpoint + separator + 'shipping=1',
{
method: 'GET',
credentials: 'same-origin'
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload
const data = result.data;
// handle script reload
const data = result.data;
resolve( {
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({
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,
});
createOrder() {
return this.actionHandler().configuration().createOrder( null, null );
}
});
});
}
approveOrder( data, actions ) {
return this.actionHandler().configuration().onApprove( data, actions );
}
createOrder() {
return this.actionHandler().configuration().createOrder(null, null);
}
actionHandler() {
return new CartActionHandler( this.ppcpConfig, this.errorHandler() );
}
approveOrder(data, actions) {
return this.actionHandler().configuration().onApprove(data, actions);
}
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' )
);
}
errorHandler() {
return new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector( '.woocommerce-notices-wrapper' )
);
}
}
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;

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;

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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,12 @@
export function createAppleErrors(errors) {
const errorList = []
for (const error of errors) {
const {contactField = null, code = null, message = null} = error
const appleError = contactField ? new ApplePayError(code, contactField, message) : new ApplePayError(code)
errorList.push(appleError)
}
export function createAppleErrors( errors ) {
const errorList = [];
for ( const error of errors ) {
const { contactField = null, code = null, message = null } = error;
const appleError = contactField
? 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 endpoints = {
validation: '_apple_pay_validation',
createOrderCart: '_apple_pay_create_order_cart',
createOrderProduct: '_apple_pay_create_order_product',
updateShippingMethod: '_apple_pay_update_shipping_contact',
updateShippingContact: '_apple_pay_update_billing_contact',
}
validation: '_apple_pay_validation',
createOrderCart: '_apple_pay_create_order_cart',
createOrderProduct: '_apple_pay_create_order_product',
updateShippingMethod: '_apple_pay_update_shipping_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.
*/
const buttonManager = () => {
if (!ApplePayPreviewButtonManager.instance) {
ApplePayPreviewButtonManager.instance = new ApplePayPreviewButtonManager();
}
if ( ! ApplePayPreviewButtonManager.instance ) {
ApplePayPreviewButtonManager.instance =
new ApplePayPreviewButtonManager();
}
return ApplePayPreviewButtonManager.instance;
return ApplePayPreviewButtonManager.instance;
};
/**
* Manages all Apple Pay preview buttons on this page.
*/
class ApplePayPreviewButtonManager extends PreviewButtonManager {
constructor() {
const args = {
methodName: 'ApplePay',
buttonConfig: window.wc_ppcp_applepay_admin,
};
constructor() {
const args = {
methodName: 'ApplePay',
buttonConfig: window.wc_ppcp_applepay_admin,
};
super(args);
}
super( args );
}
/**
* Responsible for fetching and returning the PayPal configuration object for this payment
* method.
*
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
* @return {Promise<{}>}
*/
async fetchConfig(payPal) {
const apiMethod = payPal?.Applepay()?.config;
/**
* Responsible for fetching and returning the PayPal configuration object for this payment
* method.
*
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
* @return {Promise<{}>}
*/
async fetchConfig( payPal ) {
const apiMethod = payPal?.Applepay()?.config;
if (!apiMethod) {
this.error('configuration object cannot be retrieved from PayPal');
return {};
}
if ( ! apiMethod ) {
this.error(
'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.
*
* @param {string} wrapperId - CSS ID of the wrapper element.
* @return {ApplePayPreviewButton}
*/
createButtonInstance(wrapperId) {
return new ApplePayPreviewButton({
selector: wrapperId,
apiConfig: this.apiConfig,
});
}
/**
* This method is responsible for creating a new PreviewButton instance and returning it.
*
* @param {string} wrapperId - CSS ID of the wrapper element.
* @return {ApplePayPreviewButton}
*/
createButtonInstance( wrapperId ) {
return new ApplePayPreviewButton( {
selector: wrapperId,
apiConfig: this.apiConfig,
} );
}
}
/**
* A single Apple Pay preview button instance.
*/
class ApplePayPreviewButton extends PreviewButton {
constructor(args) {
super(args);
constructor( args ) {
super( args );
this.selector = `${args.selector}ApplePay`;
this.defaultAttributes = {
button: {
type: 'pay',
color: 'black',
lang: 'en',
},
};
}
this.selector = `${ args.selector }ApplePay`;
this.defaultAttributes = {
button: {
type: 'pay',
color: 'black',
lang: 'en',
},
};
}
createNewWrapper() {
const element = super.createNewWrapper();
element.addClass('ppcp-button-applepay');
createNewWrapper() {
const element = super.createNewWrapper();
element.addClass( 'ppcp-button-applepay' );
return element;
}
return element;
}
createButton(buttonConfig) {
const button = new ApplepayButton('preview', null, buttonConfig, this.ppcpConfig);
createButton( buttonConfig ) {
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.
* Mutates the previewConfig object; no return value.
*/
dynamicPreviewConfig(buttonConfig, ppcpConfig) {
// The Apple Pay button expects the "wrapper" to be an ID without `#` prefix!
buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(/^#/, '');
/**
* Merge form details into the config object for preview.
* Mutates the previewConfig object; no return value.
* @param buttonConfig
* @param ppcpConfig
*/
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.
if (ppcpConfig.button) {
buttonConfig.button.type = ppcpConfig.button.style.type;
buttonConfig.button.color = ppcpConfig.button.style.color;
buttonConfig.button.lang =
ppcpConfig.button.style?.lang || ppcpConfig.button.style.language;
}
}
// Merge the current form-values into the preview-button configuration.
if ( ppcpConfig.button ) {
buttonConfig.button.type = ppcpConfig.button.style.type;
buttonConfig.button.color = ppcpConfig.button.style.color;
buttonConfig.button.lang =
ppcpConfig.button.style?.lang ||
ppcpConfig.button.style.language;
}
}
}
// Initialize the preview button manager.

View file

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

View file

@ -1,71 +1,62 @@
import {loadCustomScript} from "@paypal/paypal-js";
import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
import ApplepayManager from "./ApplepayManager";
import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
import { loadCustomScript } from '@paypal/paypal-js';
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
import ApplepayManager from './ApplepayManager';
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
(function ({
buttonConfig,
ppcpConfig,
jQuery
}) {
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
let manager;
let manager;
const bootstrap = function () {
manager = new ApplepayManager( buttonConfig, ppcpConfig );
manager.init();
};
const bootstrap = function () {
manager = new ApplepayManager(buttonConfig, ppcpConfig);
manager.init();
};
setupButtonEvents( function () {
if ( manager ) {
manager.reinit();
}
} );
setupButtonEvents(function() {
if (manager) {
manager.reinit();
}
});
document.addEventListener( 'DOMContentLoaded', () => {
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;
}
document.addEventListener(
'DOMContentLoaded',
() => {
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;
let paypalLoaded = false;
let applePayLoaded = false;
let bootstrapped = false;
let paypalLoaded = false;
let applePayLoaded = false;
const tryToBoot = () => {
if ( ! bootstrapped && paypalLoaded && applePayLoaded ) {
bootstrapped = true;
bootstrap();
}
};
const tryToBoot = () => {
if (!bootstrapped && paypalLoaded && applePayLoaded) {
bootstrapped = true;
bootstrap();
}
}
// Load ApplePay SDK
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
applePayLoaded = true;
tryToBoot();
} );
// Load ApplePay SDK
loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
applePayLoaded = true;
tryToBoot();
});
// Load PayPal
loadPaypalScript(ppcpConfig, () => {
paypalLoaded = true;
tryToBoot();
});
},
);
})({
buttonConfig: window.wc_ppcp_applepay,
ppcpConfig: window.PayPalCommerceGateway,
jQuery: window.jQuery
});
// Load PayPal
loadPaypalScript( ppcpConfig, () => {
paypalLoaded = true;
tryToBoot();
} );
} );
} )( {
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 {
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) {
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;
}
trigger( action ) {
this.$( this.selector ).trigger( action );
}
trigger(action) {
this.$(this.selector).trigger(action);
}
on( action, callable ) {
this.$( document ).on( action, this.selector, callable );
}
on(action, callable) {
this.$(document).on(action, this.selector, callable);
}
hide() {
this.$( this.selector ).hide();
}
hide() {
this.$(this.selector).hide();
}
show() {
this.$( this.selector ).show();
}
show() {
this.$(this.selector).show();
}
click() {
this.get().click();
}
get() {
return document.querySelector(this.selector);
}
click() {
this.get().click();
}
get() {
return document.querySelector( this.selector );
}
}
export default DomElement;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,33 +1,24 @@
import AxoManager from "./AxoManager";
import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
import AxoManager from './AxoManager';
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
(function ({
axoConfig,
ppcpConfig,
jQuery
}) {
( function ( { axoConfig, ppcpConfig, jQuery } ) {
const bootstrap = () => {
new AxoManager( axoConfig, ppcpConfig );
};
const bootstrap = () => {
new AxoManager(axoConfig, ppcpConfig);
}
document.addEventListener( 'DOMContentLoaded', () => {
if ( ! typeof PayPalCommerceGateway ) {
console.error( 'AXO could not be configured.' );
return;
}
document.addEventListener(
'DOMContentLoaded',
() => {
if (!typeof (PayPalCommerceGateway)) {
console.error('AXO could not be configured.');
return;
}
// Load PayPal
loadPaypalScript(ppcpConfig, () => {
bootstrap();
});
},
);
})({
axoConfig: window.wc_ppcp_axo,
ppcpConfig: window.PayPalCommerceGateway,
jQuery: window.jQuery
});
// Load PayPal
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 DomElement from "../Components/DomElement";
import FormFieldGroup from "../Components/FormFieldGroup";
import DomElement from '../Components/DomElement';
import FormFieldGroup from '../Components/FormFieldGroup';
global['$'] = global['jQuery'] = $;
global.$ = global.jQuery = $;
test('get dom element selector', () => {
const element = new DomElement({selector: '.foo'});
test( 'get dom element selector', () => {
const element = new DomElement( { selector: '.foo' } );
expect(element.selector).toBe('.foo')
});
expect( element.selector ).toBe( '.foo' );
} );
test('form field group activate', () => {
const formFieldGroup = new FormFieldGroup({});
test( 'form field group activate', () => {
const formFieldGroup = new FormFieldGroup( {} );
expect(formFieldGroup.active).toBe(false)
expect( formFieldGroup.active ).toBe( false );
formFieldGroup.activate()
expect(formFieldGroup.active).toBe(true)
});
formFieldGroup.activate();
expect( formFieldGroup.active ).toBe( true );
} );

View file

@ -140,12 +140,17 @@ class AxoModule implements ModuleInterface {
);
add_action(
'init',
'wp_loaded',
function () use ( $c ) {
$module = $this;
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
// 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;
}
@ -334,15 +339,11 @@ class AxoModule implements ModuleInterface {
$is_axo_enabled = $settings->has( 'axo_enabled' ) && $settings->get( 'axo_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()
&& CartCheckoutDetector::has_classic_checkout()
&& $is_axo_enabled
&& $is_dcc_enabled
&& ! $this->is_excluded_endpoint()
&& ! $subscription_helper->cart_contains_subscription();
&& ! $this->is_excluded_endpoint();
}
/**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,19 @@
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({
name: config.id,
label: <div dangerouslySetInnerHTML={{__html: config.title}}/>,
content: <CardFields config={config}/>,
edit: <div></div>,
ariaLabel: config.title,
canMakePayment: () => {return true},
supports: {
showSavedCards: true,
features: config.supports
}
})
registerPaymentMethod( {
name: config.id,
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
content: <CardFields config={ config } />,
edit: <div></div>,
ariaLabel: config.title,
canMakePayment: () => {
return true;
},
supports: {
showSavedCards: true,
features: config.supports,
},
} );

View file

@ -1,45 +1,46 @@
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, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nonce: config.scriptData.ajax.create_order.nonce,
context: config.scriptData.context,
payment_method: 'ppcp-credit-card-gateway',
save_payment_method: localStorage.getItem('ppcp-save-card-payment') === 'true',
}),
})
.then((response) => response.json())
.then((order) => {
return order.data.id;
})
.catch((err) => {
console.error(err);
});
return fetch( config.scriptData.ajax.create_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( {
nonce: config.scriptData.ajax.create_order.nonce,
context: config.scriptData.context,
payment_method: 'ppcp-credit-card-gateway',
save_payment_method:
localStorage.getItem( 'ppcp-save-card-payment' ) === 'true',
} ),
} )
.then( ( response ) => response.json() )
.then( ( order ) => {
return order.data.id;
} )
.catch( ( err ) => {
console.error( err );
} );
}
export async function onApprove(data) {
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data');
export async function onApprove( data ) {
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
return fetch(config.scriptData.ajax.approve_order.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
order_id: data.orderID,
nonce: config.scriptData.ajax.approve_order.nonce,
}),
})
.then((response) => response.json())
.then((data) => {
localStorage.removeItem('ppcp-save-card-payment');
})
.catch((err) => {
console.error(err);
});
return fetch( config.scriptData.ajax.approve_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( {
order_id: data.orderID,
nonce: config.scriptData.ajax.approve_order.nonce,
} ),
} )
.then( ( response ) => response.json() )
.then( ( data ) => {
localStorage.removeItem( 'ppcp-save-card-payment' );
} )
.catch( ( 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 '@testing-library/jest-dom';
import {CheckoutHandler} from "../Components/checkout-handler";
import { CheckoutHandler } from '../Components/checkout-handler';
test('checkbox label displays the given text', async () => {
render(
<CheckoutHandler
getCardFieldsForm={() => {}}
saveCardText="Foo"
is_vaulting_enabled={true}
/>
);
test( 'checkbox label displays the given text', async () => {
render(
<CheckoutHandler
getCardFieldsForm={ () => {} }
saveCardText="Foo"
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 () => {
const getSavePayment = jest.fn();
test( 'click checkbox calls function passing checked value', async () => {
const getSavePayment = jest.fn();
render(
<CheckoutHandler
getSavePayment={getSavePayment}
getCardFieldsForm={() => {}}
saveCardText="Foo"
is_vaulting_enabled={true}
/>
);
render(
<CheckoutHandler
getSavePayment={ getSavePayment }
getCardFieldsForm={ () => {} }
saveCardText="Foo"
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[0][0]).toBe(true);
});
await expect( getSavePayment.mock.calls ).toHaveLength( 1 );
await expect( getSavePayment.mock.calls[ 0 ][ 0 ] ).toBe( true );
} );

View file

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

View file

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

View file

@ -1,89 +1,99 @@
import onApprove from '../OnApproveHandler/onApproveForContinue.js';
import {payerData} from "../Helper/PayerData";
import {PaymentMethods} from "../Helper/CheckoutMethodState";
import { payerData } from '../Helper/PayerData';
import { PaymentMethods } from '../Helper/CheckoutMethodState';
class CartActionHandler {
constructor( config, errorHandler ) {
this.config = config;
this.errorHandler = errorHandler;
}
constructor(config, errorHandler) {
this.config = config;
this.errorHandler = errorHandler;
}
subscriptionsConfiguration( subscription_plan_id ) {
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 );
}
subscriptionsConfiguration(subscription_plan_id) {
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);
}
const orderReceivedUrl = data.data?.order_received_url;
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;
});
},
onError: (err) => {
console.error(err);
}
}
}
configuration() {
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 ]
: '';
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() {
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] : '';
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();
}
};
}
return {
createOrder,
onApprove: onApprove( this, this.errorHandler ),
onError: ( error ) => {
this.errorHandler.genericError();
},
};
}
}
export default CartActionHandler;

View file

@ -1,197 +1,233 @@
import 'formdata-polyfill';
import onApprove from '../OnApproveHandler/onApproveForPayNow.js';
import {payerData} from "../Helper/PayerData";
import {getCurrentPaymentMethod} from "../Helper/CheckoutMethodState";
import validateCheckoutForm from "../Helper/CheckoutFormValidation";
import { payerData } from '../Helper/PayerData';
import { getCurrentPaymentMethod } from '../Helper/CheckoutMethodState';
import validateCheckoutForm from '../Helper/CheckoutFormValidation';
class CheckoutActionHandler {
constructor( config, errorHandler, spinner ) {
this.config = config;
this.errorHandler = errorHandler;
this.spinner = spinner;
}
constructor(config, errorHandler, spinner) {
this.config = config;
this.errorHandler = errorHandler;
this.spinner = spinner;
}
subscriptionsConfiguration( subscription_plan_id ) {
return {
createSubscription: async ( data, actions ) => {
try {
await validateCheckoutForm( this.config );
} catch ( error ) {
throw { type: 'form-validation-error' };
}
subscriptionsConfiguration(subscription_plan_id) {
return {
createSubscription: async (data, actions) => {
try {
await validateCheckoutForm(this.config);
} catch (error) {
throw {type: 'form-validation-error'};
}
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,
} ),
} )
.then( ( res ) => {
return res.json();
} )
.then( ( data ) => {
document.querySelector( '#place_order' ).click();
} );
},
onError: ( err ) => {
console.error( err );
},
};
}
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
})
}).then((res)=>{
return res.json();
}).then((data) => {
document.querySelector('#place_order').click();
});
},
onError: (err) => {
console.error(err);
}
}
}
configuration() {
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 ]
: '';
configuration() {
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 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 formData = new FormData(document.querySelector(formSelector));
const createaccount = jQuery( '#createaccount' ).is( ':checked' )
? true
: false;
const createaccount = jQuery('#createaccount').is(":checked") ? true : false;
const paymentMethod = getCurrentPaymentMethod();
const fundingSource = window.ppcpFundingSource;
const paymentMethod = getCurrentPaymentMethod();
const fundingSource = window.ppcpFundingSource;
const savePaymentMethod = !! document.getElementById(
'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, {
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: 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 ) {
jQuery( document.body ).trigger(
'update_checkout'
);
}
if (data.data.refresh) {
jQuery( document.body ).trigger( 'update_checkout' );
}
if ( data.data.errors?.length > 0 ) {
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) {
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);
}
// fire WC event for other plugins
jQuery( document.body ).trigger( 'checkout_error', [
errorHandler.currentHtml(),
] );
}
// fire WC event for other plugins
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
}
throw { type: 'create-order-error', data: data.data };
}
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};
}
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' ) {
return;
}
if (err && err.type === 'create-order-error') {
return;
}
this.errorHandler.genericError();
},
};
}
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() {
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,
})
});
const result = await response.json();
if ( result.data.id ) {
return result.data.id;
}
const result = await response.json()
if (result.data.id) {
return result.data.id
}
console.error( result );
},
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)
},
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,
})
})
const result = await response.json();
if ( result.success === true ) {
document.querySelector( '#place_order' ).click();
return;
}
const result = await response.json();
if (result.success === true) {
document.querySelector('#place_order').click()
return;
}
console.error(result)
},
onError: (error) => {
console.error(error)
}
}
}
console.error( result );
},
onError: ( error ) => {
console.error( error );
},
};
}
}
export default CheckoutActionHandler;

View file

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

View file

@ -1,229 +1,247 @@
import Product from '../Entity/Product';
import BookingProduct from "../Entity/BookingProduct";
import BookingProduct from '../Entity/BookingProduct';
import onApprove from '../OnApproveHandler/onApproveForContinue';
import {payerData} from "../Helper/PayerData";
import {PaymentMethods} from "../Helper/CheckoutMethodState";
import CartHelper from "../Helper/CartHelper";
import FormHelper from "../Helper/FormHelper";
import { payerData } from '../Helper/PayerData';
import { PaymentMethods } from '../Helper/CheckoutMethodState';
import CartHelper from '../Helper/CartHelper';
import FormHelper from '../Helper/FormHelper';
class SingleProductActionHandler {
constructor( config, updateCart, formElement, errorHandler ) {
this.config = config;
this.updateCart = updateCart;
this.formElement = formElement;
this.errorHandler = errorHandler;
this.cartHelper = null;
}
constructor(
config,
updateCart,
formElement,
errorHandler
) {
this.config = config;
this.updateCart = updateCart;
this.formElement = formElement;
this.errorHandler = errorHandler;
this.cartHelper = null;
}
subscriptionsConfiguration( subscription_plan ) {
return {
createSubscription: ( data, actions ) => {
return actions.subscription.create( {
plan_id: subscription_plan,
} );
},
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,
} ),
} )
.then( ( res ) => {
return res.json();
} )
.then( () => {
const products = this.getSubscriptionProducts();
subscriptionsConfiguration(subscription_plan) {
return {
createSubscription: (data, actions) => {
return actions.subscription.create({
'plan_id': subscription_plan
});
},
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
})
}).then((res)=>{
return res.json();
}).then(() => {
const products = this.getSubscriptionProducts();
fetch( this.config.ajax.change_cart.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
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 );
}
fetch(this.config.ajax.change_cart.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
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;
} );
} );
},
onError: ( err ) => {
console.error( err );
},
};
}
location.href = this.config.redirect;
})
});
},
onError: (err) => {
console.error(err);
}
}
}
getSubscriptionProducts() {
const id = document.querySelector( '[name="add-to-cart"]' ).value;
return [ new Product( id, 1, this.variations(), this.extraFields() ) ];
}
getSubscriptionProducts()
{
const id = document.querySelector('[name="add-to-cart"]').value;
return [new Product(id, 1, this.variations(), this.extraFields())];
}
configuration() {
return {
createOrder: this.createOrder(),
onApprove: onApprove( this, this.errorHandler ),
onError: ( error ) => {
this.refreshMiniCart();
configuration()
{
return {
createOrder: this.createOrder(),
onApprove: onApprove(this, this.errorHandler),
onError: (error) => {
this.refreshMiniCart();
if ( this.isBookingProduct() && error.message ) {
this.errorHandler.clear();
this.errorHandler.message( error.message );
return;
}
this.errorHandler.genericError();
},
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) {
this.errorHandler.clear();
this.errorHandler.message(error.message);
return;
}
this.errorHandler.genericError();
},
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();
}
}
}
}
getProducts() {
if ( this.isBookingProduct() ) {
const id = document.querySelector( '[name="add-to-cart"]' ).value;
return [
new BookingProduct(
id,
1,
FormHelper.getPrefixedFields(
this.formElement,
'wc_bookings_field'
),
this.extraFields()
),
];
} 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()
{
if ( this.isBookingProduct() ) {
const id = document.querySelector('[name="add-to-cart"]').value;
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"), this.extraFields())];
} 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() {
return FormHelper.getFilteredFields(
this.formElement,
[ 'add-to-cart', 'quantity', 'product_id', 'variation_id' ],
[ 'attribute_', 'wc_bookings_field' ]
);
}
extraFields() {
return FormHelper.getFilteredFields(
this.formElement,
['add-to-cart', 'quantity', 'product_id', 'variation_id'],
['attribute_', 'wc_bookings_field']
);
}
createOrder() {
this.cartHelper = null;
createOrder()
{
this.cartHelper = null;
return ( data, actions, options = {} ) => {
this.errorHandler.clear();
return (data, actions, options = {}) => {
this.errorHandler.clear();
const onResolve = ( purchase_units ) => {
this.cartHelper = new CartHelper().addFromPurchaseUnits(
purchase_units
);
const onResolve = (purchase_units) => {
this.cartHelper = (new CartHelper()).addFromPurchaseUnits(purchase_units);
const payer = payerData();
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();
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;
});
};
return this.updateCart.update(
onResolve,
this.getProducts(),
options.updateCartOptions || {}
);
};
}
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()
{
if (! this.hasVariations()) {
return null;
}
return [...this.formElement.querySelectorAll("[name^='attribute_']")].map(
(element) => {
return {
value:element.value,
name:element.name
}
}
);
}
hasVariations() {
return this.formElement.classList.contains( 'variations_form' );
}
hasVariations()
{
return this.formElement.classList.contains('variations_form');
}
isGroupedProduct() {
return this.formElement.classList.contains( 'grouped_form' );
}
isGroupedProduct()
{
return this.formElement.classList.contains('grouped_form');
}
isBookingProduct() {
// detection for "woocommerce-bookings" plugin
return !! this.formElement.querySelector( '.wc-booking-product-id' );
}
isBookingProduct()
{
// detection for "woocommerce-bookings" plugin
return !!this.formElement.querySelector('.wc-booking-product-id');
}
cleanCart() {
this.cartHelper.removeFromCart().then(() => {
this.refreshMiniCart();
}).catch(error => {
this.refreshMiniCart();
});
}
refreshMiniCart() {
jQuery(document.body).trigger('wc_fragment_refresh');
}
cleanCart() {
this.cartHelper
.removeFromCart()
.then( () => {
this.refreshMiniCart();
} )
.catch( ( error ) => {
this.refreshMiniCart();
} );
}
refreshMiniCart() {
jQuery( document.body ).trigger( 'wc_fragment_refresh' );
}
}
export default SingleProductActionHandler;

View file

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

View file

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

View file

@ -1,209 +1,315 @@
import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
import {setVisible, setVisibleByClass} from '../Helper/Hiding';
import { setVisible, setVisibleByClass } from '../Helper/Hiding';
import {
getCurrentPaymentMethod,
isSavedCardSelected, ORDER_BUTTON_SELECTOR,
PaymentMethods
} from "../Helper/CheckoutMethodState";
import BootstrapHelper from "../Helper/BootstrapHelper";
getCurrentPaymentMethod,
isSavedCardSelected,
ORDER_BUTTON_SELECTOR,
PaymentMethods,
} from '../Helper/CheckoutMethodState';
import BootstrapHelper from '../Helper/BootstrapHelper';
class CheckoutBootstap {
constructor(gateway, renderer, spinner, errorHandler) {
this.gateway = gateway;
this.renderer = renderer;
this.spinner = spinner;
this.errorHandler = errorHandler;
constructor( gateway, renderer, spinner, errorHandler ) {
this.gateway = gateway;
this.renderer = renderer;
this.spinner = spinner;
this.errorHandler = errorHandler;
this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR;
this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR;
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
this.handleButtonStatus();
}, true);
}
this.renderer.onButtonsInit(
this.gateway.button.wrapper,
() => {
this.handleButtonStatus();
},
true
);
}
init() {
this.render();
this.handleButtonStatus();
init() {
this.render();
this.handleButtonStatus();
// Unselect saved card.
// 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,
// 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());
// Unselect saved card.
// 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,
// 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(document.body).on('updated_checkout', () => {
this.render()
this.handleButtonStatus();
jQuery( document.body ).on( 'updated_checkout', () => {
this.render();
this.handleButtonStatus();
if (this.shouldShowMessages() && document.querySelector(this.gateway.messages.wrapper)) { // currently we need amount only for Pay Later
fetch(
this.gateway.ajax.cart_script_params.endpoint,
{
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
if (
this.shouldShowMessages() &&
document.querySelector( this.gateway.messages.wrapper )
) {
// currently we need amount only for Pay Later
fetch( this.gateway.ajax.cart_script_params.endpoint, {
method: 'GET',
credentials: 'same-origin',
} )
.then( ( result ) => result.json() )
.then( ( result ) => {
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', () => {
this.updateUi();
});
jQuery( document.body ).on(
'updated_checkout payment_method_selected',
() => {
this.updateUi();
}
);
jQuery(document).on('hosted_fields_loaded', () => {
jQuery('#saved-credit-card').on('change', () => {
this.updateUi();
})
});
jQuery( document ).on( 'hosted_fields_loaded', () => {
jQuery( '#saved-credit-card' ).on( 'change', () => {
this.updateUi();
} );
} );
jQuery(document).on('ppcp_should_show_messages', (e, data) => {
if (!this.shouldShowMessages()) {
data.result = false;
}
});
jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => {
if ( ! this.shouldShowMessages() ) {
data.result = false;
}
} );
this.updateUi();
}
this.updateUi();
}
handleButtonStatus() {
BootstrapHelper.handleButtonStatus(this);
}
handleButtonStatus() {
BootstrapHelper.handleButtonStatus( this );
}
shouldRender() {
if (document.querySelector(this.gateway.button.cancel_wrapper)) {
return false;
}
shouldRender() {
if ( document.querySelector( this.gateway.button.cancel_wrapper ) ) {
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() {
return BootstrapHelper.shouldEnable(this);
}
shouldEnable() {
return BootstrapHelper.shouldEnable( this );
}
render() {
if (!this.shouldRender()) {
return;
}
if (document.querySelector(this.gateway.hosted_fields.wrapper + '>div')) {
document.querySelector(this.gateway.hosted_fields.wrapper + '>div').setAttribute('style', '');
}
const actionHandler = new CheckoutActionHandler(
PayPalCommerceGateway,
this.errorHandler,
this.spinner
);
render() {
if ( ! this.shouldRender() ) {
return;
}
if (
document.querySelector(
this.gateway.hosted_fields.wrapper + '>div'
)
) {
document
.querySelector( this.gateway.hosted_fields.wrapper + '>div' )
.setAttribute( 'style', '' );
}
const actionHandler = new CheckoutActionHandler(
PayPalCommerceGateway,
this.errorHandler,
this.spinner
);
if(
PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) {
let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id
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), {}, actionHandler.configuration());
if (
PayPalCommerceGateway.data_client_id.has_subscriptions &&
PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) {
let subscription_plan_id =
PayPalCommerceGateway.subscription_plan_id;
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
),
{},
actionHandler.configuration()
);
if(!PayPalCommerceGateway.subscription_product_allowed) {
this.gateway.button.is_disabled = true;
this.handleButtonStatus();
}
if ( ! PayPalCommerceGateway.subscription_product_allowed ) {
this.gateway.button.is_disabled = true;
this.handleButtonStatus();
}
return;
}
return;
}
if(
PayPalCommerceGateway.is_free_trial_cart
&& PayPalCommerceGateway.vault_v3_enabled
) {
this.renderer.render(actionHandler.addPaymentMethodConfiguration(), {}, actionHandler.configuration());
return;
}
if (
PayPalCommerceGateway.is_free_trial_cart &&
PayPalCommerceGateway.vault_v3_enabled
) {
this.renderer.render(
actionHandler.addPaymentMethodConfiguration(),
{},
actionHandler.configuration()
);
return;
}
this.renderer.render(actionHandler.configuration(), {}, actionHandler.configuration());
}
this.renderer.render(
actionHandler.configuration(),
{},
actionHandler.configuration()
);
}
updateUi() {
const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
const isCard = currentPaymentMethod === PaymentMethods.CARDS;
const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod);
const isSavedCard = isCard && isSavedCardSelected();
const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway;
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== '';
updateUi() {
const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
const isCard = currentPaymentMethod === PaymentMethods.CARDS;
const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes(
currentPaymentMethod
);
const isGooglePayMethod =
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 = {
...Object.entries(PayPalCommerceGateway.separate_buttons)
.reduce((result, [k, data]) => {
return {...result, [data.id]: data.wrapper}
}, {}),
};
const paypalButtonWrappers = {
...Object.entries( PayPalCommerceGateway.separate_buttons ).reduce(
( result, [ k, data ] ) => {
return { ...result, [ data.id ]: data.wrapper };
},
{}
),
};
setVisibleByClass(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, '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);
}
setVisibleByClass(
this.standardOrderButtonSelector,
( isPaypal && isFreeTrial && hasVaultedPaypal ) ||
isNotOurGateway ||
isSavedCard,
'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 (isSavedCard) {
this.disableCreditCardFields();
} else {
this.enableCreditCardFields();
}
}
if ( isCard ) {
if ( isSavedCard ) {
this.disableCreditCardFields();
} else {
this.enableCreditCardFields();
}
}
jQuery(document.body).trigger('ppcp_checkout_rendered');
}
setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod );
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;
}
jQuery( document.body ).trigger( 'ppcp_checkout_rendered' );
}
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() {
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()
}
return ! PayPalCommerceGateway.is_free_trial_cart;
}
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()
}
disableCreditCardFields() {
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() {
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 MessageRenderer from "../Renderer/MessageRenderer";
import { setVisible } from '../Helper/Hiding';
import MessageRenderer from '../Renderer/MessageRenderer';
class MessagesBootstrap {
constructor(gateway, messageRenderer) {
this.gateway = gateway;
this.renderers = [];
this.lastAmount = this.gateway.messages.amount;
if (messageRenderer) {
this.renderers.push(messageRenderer);
}
}
constructor( gateway, messageRenderer ) {
this.gateway = gateway;
this.renderers = [];
this.lastAmount = this.gateway.messages.amount;
if ( messageRenderer ) {
this.renderers.push( messageRenderer );
}
}
async init() {
if (this.gateway.messages?.block?.enabled) {
await this.attemptDiscoverBlocks(3); // Try up to 3 times
}
jQuery(document.body).on('ppcp_cart_rendered ppcp_checkout_rendered', () => {
this.render();
});
jQuery(document.body).on('ppcp_script_data_changed', (e, data) => {
this.gateway = data;
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();
}
});
async init() {
if ( this.gateway.messages?.block?.enabled ) {
await this.attemptDiscoverBlocks( 3 ); // Try up to 3 times
}
jQuery( document.body ).on(
'ppcp_cart_rendered ppcp_checkout_rendered',
() => {
this.render();
}
);
jQuery( document.body ).on( 'ppcp_script_data_changed', ( e, data ) => {
this.gateway = data;
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) {
return new Promise((resolve, reject) => {
this.discoverBlocks().then(found => {
if (!found && retries > 0) {
setTimeout(() => {
this.attemptDiscoverBlocks(retries - 1).then(resolve);
}, 2000); // Wait 2 seconds before retrying
} else {
resolve();
}
});
});
}
attemptDiscoverBlocks( retries ) {
return new Promise( ( resolve, reject ) => {
this.discoverBlocks().then( ( found ) => {
if ( ! found && retries > 0 ) {
setTimeout( () => {
this.attemptDiscoverBlocks( retries - 1 ).then(
resolve
);
}, 2000 ); // Wait 2 seconds before retrying
} else {
resolve();
}
} );
} );
}
discoverBlocks() {
return new Promise((resolve) => {
const elements = document.querySelectorAll('.ppcp-messages');
if (elements.length === 0) {
resolve(false);
return;
}
discoverBlocks() {
return new Promise( ( resolve ) => {
const elements = document.querySelectorAll( '.ppcp-messages' );
if ( elements.length === 0 ) {
resolve( false );
return;
}
Array.from(elements).forEach(blockElement => {
if (!blockElement.id) {
blockElement.id = `ppcp-message-${Math.random().toString(36).substr(2, 9)}`; // Ensure each block has a unique ID
}
const config = {wrapper: '#' + blockElement.id};
if (!blockElement.getAttribute('data-pp-placement')) {
config.placement = this.gateway.messages.placement;
}
this.renderers.push(new MessageRenderer(config));
});
resolve(true);
});
}
Array.from( elements ).forEach( ( blockElement ) => {
if ( ! blockElement.id ) {
blockElement.id = `ppcp-message-${ Math.random()
.toString( 36 )
.substr( 2, 9 ) }`; // Ensure each block has a unique ID
}
const config = { wrapper: '#' + blockElement.id };
if ( ! blockElement.getAttribute( 'data-pp-placement' ) ) {
config.placement = this.gateway.messages.placement;
}
this.renderers.push( new MessageRenderer( config ) );
} );
resolve( true );
} );
}
shouldShow(renderer) {
if (this.gateway.messages.is_hidden === true) {
return false;
}
shouldShow( renderer ) {
if ( this.gateway.messages.is_hidden === true ) {
return false;
}
const eventData = {result: true}
jQuery(document.body).trigger('ppcp_should_show_messages', [eventData, renderer.config.wrapper]);
return eventData.result;
}
const eventData = { result: true };
jQuery( document.body ).trigger( 'ppcp_should_show_messages', [
eventData,
renderer.config.wrapper,
] );
return eventData.result;
}
render() {
this.renderers.forEach(renderer => {
const shouldShow = this.shouldShow(renderer);
setVisible(renderer.config.wrapper, shouldShow);
if (!shouldShow) {
return;
}
render() {
this.renderers.forEach( ( renderer ) => {
const shouldShow = this.shouldShow( renderer );
setVisible( renderer.config.wrapper, shouldShow );
if ( ! shouldShow ) {
return;
}
if (!renderer.shouldRender()) {
return;
}
if ( ! renderer.shouldRender() ) {
return;
}
renderer.renderWithAmount(this.lastAmount);
});
}
renderer.renderWithAmount( this.lastAmount );
} );
}
}
export default MessagesBootstrap;

View file

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

View file

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

View file

@ -1,288 +1,362 @@
import UpdateCart from "../Helper/UpdateCart";
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
import {hide, show} from "../Helper/Hiding";
import BootstrapHelper from "../Helper/BootstrapHelper";
import {loadPaypalJsScript} from "../Helper/ScriptLoading";
import {getPlanIdFromVariation} from "../Helper/Subscriptions"
import SimulateCart from "../Helper/SimulateCart";
import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils";
import merge from "deepmerge";
import UpdateCart from '../Helper/UpdateCart';
import SingleProductActionHandler from '../ActionHandler/SingleProductActionHandler';
import { hide, show } from '../Helper/Hiding';
import BootstrapHelper from '../Helper/BootstrapHelper';
import { loadPaypalJsScript } from '../Helper/ScriptLoading';
import { getPlanIdFromVariation } from '../Helper/Subscriptions';
import SimulateCart from '../Helper/SimulateCart';
import { strRemoveWord, strAddWord, throttle } from '../Helper/Utils';
import merge from 'deepmerge';
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
class SingleProductBootstap {
constructor(gateway, renderer, errorHandler) {
this.gateway = gateway;
this.renderer = renderer;
this.errorHandler = errorHandler;
this.mutationObserver = new MutationObserver(this.handleChange.bind(this));
this.formSelector = 'form.cart';
constructor( gateway, renderer, errorHandler ) {
this.gateway = gateway;
this.renderer = renderer;
this.errorHandler = errorHandler;
this.mutationObserver = new MutationObserver(
this.handleChange.bind( this )
);
this.formSelector = 'form.cart';
// Prevent simulate cart being called too many times in a burst.
this.simulateCartThrottled = throttle(this.simulateCart, this.gateway.simulate_cart.throttling || 5000);
// Prevent simulate cart being called too many times in a burst.
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.handleChange();
}, true);
this.renderer.onButtonsInit(
this.gateway.button.wrapper,
() => {
this.handleChange();
},
true
);
this.subscriptionButtonsLoaded = false
}
this.subscriptionButtonsLoaded = false;
}
form() {
return document.querySelector(this.formSelector);
}
form() {
return document.querySelector( this.formSelector );
}
handleChange() {
this.subscriptionButtonsLoaded = false
handleChange() {
this.subscriptionButtonsLoaded = false;
if (!this.shouldRender()) {
this.renderer.disableSmartButtons(this.gateway.button.wrapper);
hide(this.gateway.button.wrapper, this.formSelector);
return;
}
if ( ! this.shouldRender() ) {
this.renderer.disableSmartButtons( this.gateway.button.wrapper );
hide( this.gateway.button.wrapper, this.formSelector );
return;
}
this.render();
this.render();
this.renderer.enableSmartButtons(this.gateway.button.wrapper);
show(this.gateway.button.wrapper);
this.renderer.enableSmartButtons( this.gateway.button.wrapper );
show( this.gateway.button.wrapper );
this.handleButtonStatus();
}
this.handleButtonStatus();
}
handleButtonStatus(simulateCart = true) {
BootstrapHelper.handleButtonStatus(this, {
formSelector: this.formSelector
});
handleButtonStatus( simulateCart = true ) {
BootstrapHelper.handleButtonStatus( this, {
formSelector: this.formSelector,
} );
if (simulateCart) {
this.simulateCartThrottled();
}
}
if ( simulateCart ) {
this.simulateCartThrottled();
}
}
init() {
const form = this.form();
init() {
const form = this.form();
if (!form) {
return;
}
if ( ! form ) {
return;
}
jQuery(document).on('change', this.formSelector, () => {
this.handleChange();
});
this.mutationObserver.observe(form, { childList: true, subtree: true });
jQuery( document ).on( 'change', this.formSelector, () => {
this.debouncedHandleChange();
} );
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) {
(new MutationObserver(this.handleButtonStatus.bind(this)))
.observe(addToCartButton, { attributes : true });
}
if ( addToCartButton ) {
new MutationObserver(
this.handleButtonStatus.bind( this )
).observe( addToCartButton, { attributes: true } );
}
jQuery(document).on('ppcp_should_show_messages', (e, data) => {
if (!this.shouldRender()) {
data.result = false;
}
});
jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => {
if ( ! this.shouldRender() ) {
data.result = false;
}
} );
if (!this.shouldRender()) {
return;
}
if ( ! this.shouldRender() ) {
return;
}
this.render();
this.handleChange();
}
this.render();
this.handleChange();
}
shouldRender() {
return this.form() !== null
&& !this.isWcsattSubscriptionMode();
}
shouldRender() {
return this.form() !== null && ! this.isWcsattSubscriptionMode();
}
shouldEnable() {
const form = this.form();
const addToCartButton = form ? form.querySelector('.single_add_to_cart_button') : null;
shouldEnable() {
const form = this.form();
const addToCartButton = form
? form.querySelector( '.single_add_to_cart_button' )
: null;
return BootstrapHelper.shouldEnable(this)
&& !this.priceAmountIsZero()
&& ((null === addToCartButton) || !addToCartButton.classList.contains('disabled'));
}
return (
BootstrapHelper.shouldEnable( this ) &&
! this.priceAmountIsZero() &&
( null === addToCartButton ||
! addToCartButton.classList.contains( 'disabled' ) )
);
}
priceAmount(returnOnUndefined = 0) {
const priceText = [
() => document.querySelector('form.cart ins .woocommerce-Price-amount')?.innerText,
() => document.querySelector('form.cart .woocommerce-Price-amount')?.innerText,
() => {
const priceEl = document.querySelector('.product .woocommerce-Price-amount');
// 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
if (priceEl) {
const allPriceElements = Array.from(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);
priceAmount( returnOnUndefined = 0 ) {
const priceText = [
() =>
document.querySelector(
'form.cart ins .woocommerce-Price-amount'
)?.innerText,
() =>
document.querySelector( 'form.cart .woocommerce-Price-amount' )
?.innerText,
() => {
const priceEl = document.querySelector(
'.product .woocommerce-Price-amount'
);
// 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
if ( priceEl ) {
const allPriceElements = Array.from(
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') {
return returnOnUndefined;
}
if ( typeof priceText === 'undefined' ) {
return returnOnUndefined;
}
if (!priceText) {
return 0;
}
if ( ! priceText ) {
return 0;
}
return parseFloat(priceText.replace(/,/g, '.').replace(/([^\d,\.\s]*)/g, ''));
}
return parseFloat(
priceText.replace( /,/g, '.' ).replace( /([^\d,\.\s]*)/g, '' )
);
}
priceAmountIsZero() {
const price = this.priceAmount(-1);
priceAmountIsZero() {
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 (price === -1) {
return false;
}
// if we can't find the price in the DOM we want to return true so the button is visible.
if ( price === -1 ) {
return false;
}
return !price || price === 0;
}
return ! price || price === 0;
}
isWcsattSubscriptionMode() {
// Check "All products for subscriptions" plugin.
return 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
}
isWcsattSubscriptionMode() {
// Check "All products for subscriptions" plugin.
return (
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() {
if (!this.hasVariations()) {
return null;
}
variations() {
if ( ! this.hasVariations() ) {
return null;
}
return [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map(
(element) => {
return {
value: element.value,
name: element.name
}
}
);
}
return [
...document
.querySelector( 'form.cart' )
?.querySelectorAll( "[name^='attribute_']" ),
].map( ( element ) => {
return {
value: element.value,
name: element.name,
};
} );
}
hasVariations() {
return document.querySelector('form.cart')?.classList.contains('variations_form');
}
hasVariations() {
return document
.querySelector( 'form.cart' )
?.classList.contains( 'variations_form' );
}
render() {
const actionHandler = new SingleProductActionHandler(
this.gateway,
new UpdateCart(
this.gateway.ajax.change_cart.endpoint,
this.gateway.ajax.change_cart.nonce,
),
this.form(),
this.errorHandler,
);
render() {
const actionHandler = new SingleProductActionHandler(
this.gateway,
new UpdateCart(
this.gateway.ajax.change_cart.endpoint,
this.gateway.ajax.change_cart.nonce
),
this.form(),
this.errorHandler
);
if(
PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) {
const buttonWrapper = document.getElementById('ppc-button-ppcp-gateway');
buttonWrapper.innerHTML = '';
if (
PayPalCommerceGateway.data_client_id.has_subscriptions &&
PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) {
const buttonWrapper = document.getElementById(
'ppc-button-ppcp-gateway'
);
buttonWrapper.innerHTML = '';
const subscription_plan = this.variations() !== null
? getPlanIdFromVariation(this.variations())
: PayPalCommerceGateway.subscription_plan_id
if(!subscription_plan) {
return;
}
const subscription_plan =
this.variations() !== null
? getPlanIdFromVariation( this.variations() )
: PayPalCommerceGateway.subscription_plan_id;
if ( ! subscription_plan ) {
return;
}
if(this.subscriptionButtonsLoaded) return
loadPaypalJsScript(
{
clientId: PayPalCommerceGateway.client_id,
currency: PayPalCommerceGateway.currency,
intent: 'subscription',
vault: true
},
actionHandler.subscriptionsConfiguration(subscription_plan),
this.gateway.button.wrapper
);
if ( this.subscriptionButtonsLoaded ) {
return;
}
loadPaypalJsScript(
{
clientId: PayPalCommerceGateway.client_id,
currency: PayPalCommerceGateway.currency,
intent: 'subscription',
vault: true,
},
actionHandler.subscriptionsConfiguration( subscription_plan ),
this.gateway.button.wrapper
);
this.subscriptionButtonsLoaded = true
return;
}
this.subscriptionButtonsLoaded = true;
return;
}
this.renderer.render(
actionHandler.configuration()
);
}
this.renderer.render( actionHandler.configuration() );
}
simulateCart() {
if (!this.gateway.simulate_cart.enabled) {
return;
}
simulateCart() {
if ( ! this.gateway.simulate_cart.enabled ) {
return;
}
const actionHandler = new SingleProductActionHandler(
null,
null,
this.form(),
this.errorHandler,
);
const actionHandler = new SingleProductActionHandler(
null,
null,
this.form(),
this.errorHandler
);
const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
const hasSubscriptions =
PayPalCommerceGateway.data_client_id.has_subscriptions &&
PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
const products = hasSubscriptions
? actionHandler.getSubscriptionProducts()
: actionHandler.getProducts();
const products = hasSubscriptions
? actionHandler.getSubscriptionProducts()
: actionHandler.getProducts();
(new SimulateCart(
this.gateway.ajax.simulate_cart.endpoint,
this.gateway.ajax.simulate_cart.nonce,
)).simulate((data) => {
new SimulateCart(
this.gateway.ajax.simulate_cart.endpoint,
this.gateway.ajax.simulate_cart.nonce
).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 (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);
}
if ( this.gateway.single_product_buttons_enabled !== '1' ) {
return;
}
if ( this.gateway.single_product_buttons_enabled !== '1' ) {
return;
}
let enableFunding = this.gateway.url_params[ 'enable-funding' ];
let disableFunding = this.gateway.url_params[ 'disable-funding' ];
let enableFunding = this.gateway.url_params['enable-funding'];
let disableFunding = this.gateway.url_params['disable-funding'];
for ( const [ fundingSource, funding ] of Object.entries(
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 (funding.enabled === true) {
enableFunding = strAddWord(enableFunding, fundingSource);
disableFunding = strRemoveWord(disableFunding, fundingSource);
} else if (funding.enabled === false) {
enableFunding = strRemoveWord(enableFunding, fundingSource);
disableFunding = strAddWord(disableFunding, fundingSource);
}
}
if (
enableFunding !== this.gateway.url_params[ 'enable-funding' ] ||
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'
);
}
if (
(enableFunding !== this.gateway.url_params['enable-funding']) ||
(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);
}
this.handleButtonStatus( false );
}, products );
}
}
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 validateToken = (token, user) => {
if (! token) {
return false;
}
if (token.user !== user) {
return false;
}
const currentTime = new Date().getTime();
const isExpired = currentTime >= token.expiration * 1000;
return ! isExpired;
}
const validateToken = ( token, user ) => {
if ( ! token ) {
return false;
}
if ( token.user !== user ) {
return false;
}
const currentTime = new Date().getTime();
const isExpired = currentTime >= token.expiration * 1000;
return ! isExpired;
};
const storedTokenForUser = (user) => {
const token = JSON.parse(sessionStorage.getItem(storageKey));
if (validateToken(token, user)) {
return token.token;
}
return null;
}
const storedTokenForUser = ( user ) => {
const token = JSON.parse( sessionStorage.getItem( storageKey ) );
if ( validateToken( token, user ) ) {
return token.token;
}
return null;
};
const storeToken = (token) => {
sessionStorage.setItem(storageKey, JSON.stringify(token));
}
const storeToken = ( token ) => {
sessionStorage.setItem( storageKey, JSON.stringify( token ) );
};
const dataClientIdAttributeHandler = (scriptOptions, config, callback, errorCallback = null) => {
fetch(config.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.nonce
})
}).then((res)=>{
return res.json();
}).then((data)=>{
const isValid = validateToken(data, config.user);
if (!isValid) {
return;
}
storeToken(data);
const dataClientIdAttributeHandler = (
scriptOptions,
config,
callback,
errorCallback = null
) => {
fetch( config.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: config.nonce,
} ),
} )
.then( ( res ) => {
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) => {
if (typeof callback === 'function') {
callback(paypal);
}
}).catch(err => {
if (typeof errorCallback === 'function') {
errorCallback(err);
}
});
});
}
loadScript( scriptOptions )
.then( ( paypal ) => {
if ( typeof callback === 'function' ) {
callback( paypal );
}
} )
.catch( ( err ) => {
if ( typeof errorCallback === 'function' ) {
errorCallback( err );
}
} );
} );
};
export default dataClientIdAttributeHandler;

View file

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

View file

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

View file

@ -1,108 +1,98 @@
class ErrorHandler {
/**
* @param {string} genericErrorText
* @param {Element} wrapper
*/
constructor( genericErrorText, wrapper ) {
this.genericErrorText = genericErrorText;
this.wrapper = wrapper;
}
/**
* @param {String} genericErrorText
* @param {Element} wrapper
*/
constructor(genericErrorText, wrapper)
{
this.genericErrorText = genericErrorText;
this.wrapper = wrapper;
}
genericError() {
this.clear();
this.message( this.genericErrorText );
}
genericError() {
this.clear();
this.message(this.genericErrorText)
}
appendPreparedErrorMessageElement( errorMessageElement ) {
this._getMessageContainer().replaceWith( errorMessageElement );
}
appendPreparedErrorMessageElement(errorMessageElement)
{
this._getMessageContainer().replaceWith(errorMessageElement);
}
/**
* @param {string} text
*/
message( text ) {
this._addMessage( text );
/**
* @param {String} text
*/
message(text)
{
this._addMessage(text);
this._scrollToMessages();
}
this._scrollToMessages();
}
/**
* @param {Array} texts
*/
messages( texts ) {
texts.forEach( ( t ) => this._addMessage( t ) );
/**
* @param {Array} texts
*/
messages(texts)
{
texts.forEach(t => this._addMessage(t));
this._scrollToMessages();
}
this._scrollToMessages();
}
/**
* @return {string}
*/
currentHtml() {
const messageContainer = this._getMessageContainer();
return messageContainer.outerHTML;
}
/**
* @returns {String}
*/
currentHtml()
{
const messageContainer = this._getMessageContainer();
return messageContainer.outerHTML;
}
/**
* @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.' );
}
/**
* @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 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
*/
_scrollToMessages()
{
jQuery.scroll_to_notices(jQuery('.woocommerce-error'));
}
/**
* @private
*/
_getMessageContainer() {
let messageContainer = document.querySelector( 'ul.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
*/
_getMessageContainer()
{
let messageContainer = document.querySelector('ul.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;
}
/**
* @param message
* @private
*/
_prepareMessageElement( message ) {
const li = document.createElement( 'li' );
li.innerHTML = message;
/**
* @private
*/
_prepareMessageElement(message)
{
const li = document.createElement('li');
li.innerHTML = message;
return li;
}
return li;
}
clear()
{
jQuery( '.woocommerce-error, .woocommerce-message' ).remove();
}
clear() {
jQuery( '.woocommerce-error, .woocommerce-message' ).remove();
}
}
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') => {
let selectorInContainer = selector;
if ( window.ppcpApmButtons ) {
return;
}
if (window.ppcpApmButtons) {
return;
}
if ( config && config.button ) {
// 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.
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);
}
window.ppcpApmButtons = new ApmButtons( selector, selectorInContainer );
};
export class ApmButtons {
constructor( selector, selectorInContainer ) {
this.selector = selector;
this.selectorInContainer = selectorInContainer;
this.containers = [];
constructor(selector, selectorInContainer) {
this.selector = selector;
this.selectorInContainer = selectorInContainer;
this.containers = [];
// Reloads button containers.
this.reloadContainers();
// Reloads button containers.
this.reloadContainers();
// Refresh button layout.
jQuery( window )
.resize( () => {
this.refresh();
} )
.resize();
// Refresh button layout.
jQuery(window).resize(() => {
this.refresh();
}).resize();
jQuery( document ).on( 'ppcp-smart-buttons-init', () => {
this.refresh();
} );
jQuery(document).on('ppcp-smart-buttons-init', () => {
this.refresh();
});
jQuery( document ).on(
'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) => {
this.refresh();
setTimeout(this.refresh.bind(this), 200);
});
// Observes for new buttons.
new MutationObserver(
this.observeElementsCallback.bind( this )
).observe( document.body, { childList: true, subtree: true } );
}
// Observes for new buttons.
(new MutationObserver(this.observeElementsCallback.bind(this)))
.observe(document.body, { childList: true, subtree: true });
}
observeElementsCallback( mutationsList, observer ) {
const observeSelector =
this.selector +
', .widget_shopping_cart, .widget_shopping_cart_content';
observeElementsCallback(mutationsList, observer) {
const observeSelector = this.selector + ', .widget_shopping_cart, .widget_shopping_cart_content';
let shouldReload = false;
for ( const mutation of mutationsList ) {
if ( mutation.type === 'childList' ) {
mutation.addedNodes.forEach( ( node ) => {
if ( node.matches && node.matches( observeSelector ) ) {
shouldReload = true;
}
} );
}
}
let shouldReload = false;
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.matches && node.matches(observeSelector)) {
shouldReload = true;
}
});
}
}
if ( shouldReload ) {
this.reloadContainers();
this.refresh();
}
}
if (shouldReload) {
this.reloadContainers();
this.refresh();
}
};
reloadContainers() {
jQuery( this.selector ).each( ( index, el ) => {
const parent = jQuery( el ).parent();
if ( ! this.containers.some( ( $el ) => $el.is( parent ) ) ) {
this.containers.push( parent );
}
} );
}
reloadContainers() {
jQuery(this.selector).each((index, el) => {
const parent = jQuery(el).parent();
if (!this.containers.some($el => $el.is(parent))) {
this.containers.push(parent);
}
});
}
refresh() {
for ( const container of this.containers ) {
const $container = jQuery( container );
refresh() {
for (const container of this.containers) {
const $container = jQuery(container);
// Check width and add classes
const width = $container.width();
// Check width and add classes
const width = $container.width();
$container.removeClass(
'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) {
$container.addClass('ppcp-width-500');
} else if (width >= 300) {
$container.addClass('ppcp-width-300');
} else {
$container.addClass('ppcp-width-min');
}
// Check first apm button
const $firstElement = $container.children( ':visible' ).first();
// Check first apm button
const $firstElement = $container.children(':visible').first();
// Assign margins to buttons
$container.find( this.selectorInContainer ).each( ( index, el ) => {
const $el = jQuery( el );
// Assign margins to buttons
$container.find(this.selectorInContainer).each((index, el) => {
const $el = jQuery(el);
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`);
});
}
}
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` );
} );
}
}
}

View file

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

View file

@ -1,83 +1,86 @@
/**
* @param selectorOrElement
* @returns {Element}
* @param selectorOrElement
* @return {Element}
*/
const getElement = (selectorOrElement) => {
if (typeof selectorOrElement === 'string') {
return document.querySelector(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);
}
const getElement = ( selectorOrElement ) => {
if ( typeof selectorOrElement === 'string' ) {
return document.querySelector( selectorOrElement );
}
return selectorOrElement;
};
export const isDisabled = (selectorOrElement) => {
const element = getElement(selectorOrElement);
if (!element) {
return false;
}
return jQuery(element).hasClass('ppcp-disabled');
const triggerEnabled = ( selectorOrElement, element ) => {
jQuery( document ).trigger( 'ppcp-enabled', {
handler: 'ButtonsDisabler.setEnabled',
action: 'enable',
selector: selectorOrElement,
element,
} );
};
export const disable = (selectorOrElement, form = null) => {
setEnabled(selectorOrElement, false, form);
const triggerDisabled = ( selectorOrElement, element ) => {
jQuery( document ).trigger( 'ppcp-disabled', {
handler: 'ButtonsDisabler.setEnabled',
action: 'disable',
selector: selectorOrElement,
element,
} );
};
export const enable = (selectorOrElement) => {
setEnabled(selectorOrElement, true);
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
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.
*/
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.
*/
export function setupButtonEvents(refresh) {
const miniCartInitDelay = 1000;
const debouncedRefresh = debounce(refresh, 50);
export function setupButtonEvents( refresh ) {
const miniCartInitDelay = 1000;
const debouncedRefresh = debounce( refresh, 50 );
// Listen for our custom refresh event.
document.addEventListener(REFRESH_BUTTON_EVENT, debouncedRefresh);
// Listen for our custom refresh event.
document.addEventListener( REFRESH_BUTTON_EVENT, debouncedRefresh );
// Listen for cart and checkout update events.
document.body.addEventListener('updated_cart_totals', debouncedRefresh);
document.body.addEventListener('updated_checkout', debouncedRefresh);
// Listen for cart and checkout update events.
document.body.addEventListener( 'updated_cart_totals', debouncedRefresh );
document.body.addEventListener( 'updated_checkout', debouncedRefresh );
// Use setTimeout for fragment events to avoid unnecessary refresh on initial render.
setTimeout(() => {
document.body.addEventListener('wc_fragments_loaded', debouncedRefresh);
document.body.addEventListener('wc_fragments_refreshed', debouncedRefresh);
}, miniCartInitDelay);
// Use setTimeout for fragment events to avoid unnecessary refresh on initial render.
setTimeout( () => {
document.body.addEventListener(
'wc_fragments_loaded',
debouncedRefresh
);
document.body.addEventListener(
'wc_fragments_refreshed',
debouncedRefresh
);
}, miniCartInitDelay );
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,85 +1,92 @@
/**
* @param selectorOrElement
* @returns {Element}
* @param selectorOrElement
* @return {Element}
*/
const getElement = (selectorOrElement) => {
if (typeof selectorOrElement === 'string') {
return document.querySelector(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);
}
}
const getElement = ( selectorOrElement ) => {
if ( typeof selectorOrElement === 'string' ) {
return document.querySelector( selectorOrElement );
}
return selectorOrElement;
};
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);
}
const triggerHidden = ( handler, selectorOrElement, element ) => {
jQuery( document ).trigger( 'ppcp-hidden', {
handler,
action: 'hide',
selector: selectorOrElement,
element,
} );
};
export const hide = (selectorOrElement, important = false) => {
setVisible(selectorOrElement, false, important);
const triggerShown = ( handler, selectorOrElement, element ) => {
jQuery( document ).trigger( 'ppcp-shown', {
handler,
action: 'show',
selector: selectorOrElement,
element,
} );
};
export const show = (selectorOrElement) => {
setVisible(selectorOrElement, true);
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 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,
* 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 {boolean} isVisible - Whether the triggerElement is visible.
* @property {boolean} isVisible - Whether the triggerElement is visible.
*/
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.
* @type {string}
*/
#triggerElementSelector;
/**
* Interval (in milliseconds) in which the visibility of the trigger element is checked.
* @type {number}
*/
#intervalTime = 150;
/**
* Interval (in milliseconds) in which the visibility of the trigger element is checked.
* @type {number}
*/
#intervalTime = 150;
/**
* The interval ID returned by the setInterval() method.
* @type {number|false}
*/
#intervalId;
/**
* The interval ID returned by the setInterval() method.
* @type {number|false}
*/
#intervalId;
/**
* Selector passed to the constructor that identifies the checkout form
* @type {string}
*/
#formSelector;
/**
* Selector passed to the constructor that identifies the checkout form
* @type {string}
*/
#formSelector;
/**
* @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;
/**
* @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
dynamically prepare the checkout page, so we can decide whether this helper is needed.
*/
setTimeout(() => {
if (this.form && !this.isVisible) {
this.start();
}
}, 250);
}
setTimeout( () => {
if ( this.form && ! this.isVisible ) {
this.start();
}
}, 250 );
}
/**
* The checkout form element.
* @returns {Element|null} - Form element or null.
*/
get form() {
return document.querySelector(this.#formSelector);
}
/**
* The checkout form element.
* @return {Element|null} - Form element or null.
*/
get form() {
return document.querySelector( this.#formSelector );
}
/**
* The element which must be visible before payment buttons should be initialized.
* @returns {Element|null} - Trigger element or null.
*/
get triggerElement() {
return this.form?.querySelector(this.#triggerElementSelector);
}
/**
* The element which must be visible before payment buttons should be initialized.
* @return {Element|null} - Trigger element or null.
*/
get triggerElement() {
return this.form?.querySelector( this.#triggerElementSelector );
}
/**
* Checks the visibility of the payment button wrapper.
* @returns {boolean} - returns boolean value on the basis of visibility of element.
*/
get isVisible() {
const box = this.triggerElement?.getBoundingClientRect();
/**
* Checks the visibility of the payment button wrapper.
* @return {boolean} - returns boolean value on the basis of visibility of element.
*/
get isVisible() {
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.
* To ensure multiple calls to start don't create multiple intervals, we first call stop.
*/
start() {
this.stop();
this.#intervalId = setInterval(() => this.checkElement(), this.#intervalTime);
}
/**
* 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.
*/
start() {
this.stop();
this.#intervalId = setInterval(
() => this.checkElement(),
this.#intervalTime
);
}
/**
* Stops the observation of the checkout form.
* Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist.
*/
stop() {
if (this.#intervalId) {
clearInterval(this.#intervalId);
this.#intervalId = false;
}
}
/**
* Stops the observation of the checkout form.
* Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist.
*/
stop() {
if ( this.#intervalId ) {
clearInterval( this.#intervalId );
this.#intervalId = false;
}
}
/**
* Checks if the trigger element is visible.
* If visible, it initialises the payment buttons and stops the observation.
*/
checkElement() {
if (this.isVisible) {
refreshButtons();
this.stop();
}
}
/**
* Checks if the trigger element is visible.
* If visible, it initialises the payment buttons and stops the observation.
*/
checkElement() {
if ( this.isVisible ) {
refreshButtons();
this.stop();
}
}
}
export default MultistepCheckoutHelper;

View file

@ -1,34 +1,59 @@
export const payerData = () => {
const payer = PayPalCommerceGateway.payer;
if (! payer) {
return null;
}
const payer = PayPalCommerceGateway.payer;
if ( ! payer ) {
return null;
}
const phone = (document.querySelector('#billing_phone') || typeof payer.phone !== 'undefined') ?
{
phone_type:"HOME",
phone_number:{
national_number : (document.querySelector('#billing_phone')) ? document.querySelector('#billing_phone').value : payer.phone.phone_number.national_number
}
} : null;
const payerData = {
email_address:(document.querySelector('#billing_email')) ? document.querySelector('#billing_email').value : payer.email_address,
name : {
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
}
};
const phone =
document.querySelector( '#billing_phone' ) ||
typeof payer.phone !== 'undefined'
? {
phone_type: 'HOME',
phone_number: {
national_number: document.querySelector(
'#billing_phone'
)
? document.querySelector( '#billing_phone' ).value
: payer.phone.phone_number.national_number,
},
}
: null;
const payerData = {
email_address: document.querySelector( '#billing_email' )
? document.querySelector( '#billing_email' ).value
: payer.email_address,
name: {
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) {
payerData.phone = phone;
}
return payerData;
}
if ( phone ) {
payerData.phone = phone;
}
return payerData;
};

View file

@ -1,109 +1,110 @@
import dataClientIdAttributeHandler from "../DataClientIdAttributeHandler";
import {loadScript} from "@paypal/paypal-js";
import widgetBuilder from "../Renderer/WidgetBuilder";
import merge from "deepmerge";
import {keysToCamelCase} from "./Utils";
import {getCurrentPaymentMethod} from "./CheckoutMethodState";
import dataClientIdAttributeHandler from '../DataClientIdAttributeHandler';
import { loadScript } from '@paypal/paypal-js';
import widgetBuilder from '../Renderer/WidgetBuilder';
import merge from 'deepmerge';
import { keysToCamelCase } from './Utils';
import { getCurrentPaymentMethod } from './CheckoutMethodState';
import { v4 as uuidv4 } from 'uuid';
// This component may be used by multiple modules. This assures that options are shared between all instances.
let options = window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || {
isLoading: false,
onLoadedCallbacks: [],
onErrorCallbacks: [],
const options = ( window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || {
isLoading: false,
onLoadedCallbacks: [],
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) => {
// If PayPal is already loaded call the onLoaded callback and return.
if (typeof paypal !== 'undefined') {
onLoaded();
return;
}
export const loadPaypalScriptPromise = ( config ) => {
return new Promise( ( resolve, reject ) => {
loadPaypalScript( config, resolve, reject );
} );
};
// Add the onLoaded callback to the onLoadedCallbacks stack.
options.onLoadedCallbacks.push(onLoaded);
if (onError) {
options.onErrorCallbacks.push(onError);
}
export const loadPaypalJsScript = ( options, buttons, container ) => {
loadScript( options ).then( ( paypal ) => {
paypal.Buttons( buttons ).render( container );
} );
};
// 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 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);
});
}
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 {convertKeysToSnakeCase} from "../../../../../ppcp-blocks/resources/js/Helper/Helper.js";
import { paypalAddressToWc } from '../../../../../ppcp-blocks/resources/js/Helper/Address.js';
import { convertKeysToSnakeCase } from '../../../../../ppcp-blocks/resources/js/Helper/Helper.js';
/**
* Handles the shipping option change in PayPal.
*
* @param data
* @param actions
* @param config
* @returns {Promise<void>}
* @param data
* @param actions
* @param config
* @return {Promise<void>}
*/
export const handleShippingOptionsChange = async (data, actions, config) => {
try {
const shippingOptionId = data.selectedShippingOption?.id;
export const handleShippingOptionsChange = async ( data, actions, config ) => {
try {
const shippingOptionId = data.selectedShippingOption?.id;
if (shippingOptionId) {
await fetch(config.ajax.update_customer_shipping.shipping_options.endpoint, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce,
},
body: JSON.stringify({
rate_id: shippingOptionId,
})
})
.then(response => {
return response.json();
})
.then(cardData => {
const shippingMethods = document.querySelectorAll('.shipping_method');
if ( shippingOptionId ) {
await fetch(
config.ajax.update_customer_shipping.shipping_options.endpoint,
{
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WC-Store-API-Nonce':
config.ajax.update_customer_shipping.wp_rest_nonce,
},
body: JSON.stringify( {
rate_id: shippingOptionId,
} ),
}
)
.then( ( response ) => {
return response.json();
} )
.then( ( cardData ) => {
const shippingMethods =
document.querySelectorAll( '.shipping_method' );
shippingMethods.forEach(function(method) {
if (method.value === shippingOptionId) {
method.checked = true;
}
});
})
}
shippingMethods.forEach( function ( method ) {
if ( method.value === shippingOptionId ) {
method.checked = true;
}
} );
} );
}
if (!config.data_client_id.has_subscriptions) {
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
if ( ! config.data_client_id.has_subscriptions ) {
const res = await fetch( config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
} ),
} );
const json = await res.json();
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
}
}
} catch (e) {
console.error(e);
if ( ! json.success ) {
throw new Error( json.data.message );
}
}
} catch ( e ) {
console.error( e );
actions.reject();
}
actions.reject();
}
};
/**
* Handles the shipping address change in PayPal.
*
* @param data
* @param actions
* @param config
* @returns {Promise<void>}
* @param data
* @param actions
* @param config
* @return {Promise<void>}
*/
export const handleShippingAddressChange = async (data, actions, config) => {
try {
const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress));
export const handleShippingAddressChange = async ( data, actions, config ) => {
try {
const address = paypalAddressToWc(
convertKeysToSnakeCase( data.shippingAddress )
);
// Retrieve current cart contents
await fetch(config.ajax.update_customer_shipping.shipping_address.cart_endpoint)
.then(response => {
return response.json();
})
.then(cartData => {
// Update shipping address in the cart data
cartData.shipping_address.address_1 = address.address_1;
cartData.shipping_address.address_2 = address.address_2;
cartData.shipping_address.city = address.city;
cartData.shipping_address.state = address.state;
cartData.shipping_address.postcode = address.postcode;
cartData.shipping_address.country = address.country;
// Retrieve current cart contents
await fetch(
config.ajax.update_customer_shipping.shipping_address.cart_endpoint
)
.then( ( response ) => {
return response.json();
} )
.then( ( cartData ) => {
// Update shipping address in the cart data
cartData.shipping_address.address_1 = address.address_1;
cartData.shipping_address.address_2 = address.address_2;
cartData.shipping_address.city = address.city;
cartData.shipping_address.state = address.state;
cartData.shipping_address.postcode = address.postcode;
cartData.shipping_address.country = address.country;
// Send update request
return fetch(config.ajax.update_customer_shipping.shipping_address.update_customer_endpoint, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce,
},
body: JSON.stringify({
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" + ">*", "");
})
})
// Send update request
return fetch(
config.ajax.update_customer_shipping.shipping_address
.update_customer_endpoint,
{
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WC-Store-API-Nonce':
config.ajax.update_customer_shipping
.wp_rest_nonce,
},
body: JSON.stringify( {
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, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
const res = await fetch( config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
} ),
} );
const json = await res.json();
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
}
} catch (e) {
console.error(e);
if ( ! json.success ) {
throw new Error( json.data.message );
}
} catch ( e ) {
console.error( e );
actions.reject();
}
actions.reject();
}
};

View file

@ -1,48 +1,42 @@
class SimulateCart {
constructor( endpoint, nonce ) {
this.endpoint = endpoint;
this.nonce = nonce;
}
constructor(endpoint, nonce)
{
this.endpoint = endpoint;
this.nonce = nonce;
}
/**
*
* @param onResolve
* @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;
}
/**
*
* @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);
})
});
}
const resolved = onResolve( result.data );
resolve( resolved );
} );
} );
}
}
export default SimulateCart;

View file

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

View file

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

View file

@ -1,20 +1,28 @@
export const isChangePaymentPage = () => {
const urlParams = new URLSearchParams(window.location.search)
return urlParams.has('change_payment_method');
}
const urlParams = new URLSearchParams( window.location.search );
return urlParams.has( 'change_payment_method' );
};
export const getPlanIdFromVariation = (variation) => {
let subscription_plan = '';
PayPalCommerceGateway.variable_paypal_subscription_variations.forEach((element) => {
let obj = {};
variation.forEach(({name, value}) => {
Object.assign(obj, {[name.replace('attribute_', '')]: value});
})
export const getPlanIdFromVariation = ( variation ) => {
let subscription_plan = '';
PayPalCommerceGateway.variable_paypal_subscription_variations.forEach(
( element ) => {
const obj = {};
variation.forEach( ( { name, value } ) => {
Object.assign( obj, {
[ name.replace( 'attribute_', '' ) ]: value,
} );
} );
if(JSON.stringify(obj) === JSON.stringify(element.attributes) && element.subscription_plan !== '') {
subscription_plan = element.subscription_plan;
}
});
if (
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 {
constructor( endpoint, nonce ) {
this.endpoint = endpoint;
this.nonce = nonce;
}
constructor(endpoint, nonce)
{
this.endpoint = endpoint;
this.nonce = nonce;
}
/**
*
* @param onResolve
* @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;
}
/**
*
* @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);
})
});
}
const resolved = onResolve( result.data );
resolve( resolved );
} );
} );
}
}
export default UpdateCart;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,274 +1,357 @@
import dccInputFactory from "../Helper/DccInputFactory";
import {show} from "../Helper/Hiding";
import dccInputFactory from '../Helper/DccInputFactory';
import { show } from '../Helper/Hiding';
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) {
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;
}
render( wrapper, contextConfig ) {
if (
( this.defaultConfig.context !== 'checkout' &&
this.defaultConfig.context !== 'pay-now' ) ||
wrapper === null ||
document.querySelector( wrapper ) === null
) {
return;
}
render(wrapper, contextConfig) {
if (
(
this.defaultConfig.context !== 'checkout'
&& this.defaultConfig.context !== 'pay-now'
)
|| wrapper === null
|| document.querySelector(wrapper) === null
) {
return;
}
if (
typeof paypal.HostedFields !== 'undefined' &&
paypal.HostedFields.isEligible()
) {
const buttonSelector = wrapper + ' button';
if (typeof paypal.HostedFields !== 'undefined' && paypal.HostedFields.isEligible()) {
const buttonSelector = wrapper + ' button';
if ( this.currentHostedFieldsInstance ) {
this.currentHostedFieldsInstance
.teardown()
.catch( ( err ) =>
console.error(
`Hosted fields teardown error: ${ err }`
)
);
this.currentHostedFieldsInstance = null;
}
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.teardown()
.catch(err => console.error(`Hosted fields teardown error: ${err}`));
this.currentHostedFieldsInstance = null;
}
const gateWayBox = document.querySelector(
'.payment_box.payment_method_ppcp-credit-card-gateway'
);
if ( ! gateWayBox ) {
return;
}
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway');
if (!gateWayBox) {
return
}
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' );
if ( hideDccGateway ) {
hideDccGateway.parentNode.removeChild( hideDccGateway );
}
const hideDccGateway = document.querySelector('#ppcp-hide-dcc');
if (hideDccGateway) {
hideDccGateway.parentNode.removeChild(hideDccGateway);
}
const cardNumberField = document.querySelector(
'#ppcp-credit-card-gateway-card-number'
);
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);
let styles = {};
Object.values(stylesRaw).forEach((prop) => {
if (!stylesRaw[prop]) {
return;
}
styles[prop] = '' + stylesRaw[prop];
});
const cardNumber = dccInputFactory( cardNumberField );
cardNumberField.parentNode.replaceChild(
cardNumber,
cardNumberField
);
const cardNumber = dccInputFactory(cardNumberField);
cardNumberField.parentNode.replaceChild(cardNumber, cardNumberField);
const cardExpiryField = document.querySelector(
'#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 cardExpiry = dccInputFactory(cardExpiryField);
cardExpiryField.parentNode.replaceChild(cardExpiry, cardExpiryField);
const cardCodeField = document.querySelector(
'#ppcp-credit-card-gateway-card-cvc'
);
const cardCode = dccInputFactory( cardCodeField );
cardCodeField.parentNode.replaceChild( cardCode, cardCodeField );
const cardCodeField = document.querySelector('#ppcp-credit-card-gateway-card-cvc');
const cardCode = dccInputFactory(cardCodeField);
cardCodeField.parentNode.replaceChild(cardCode, cardCodeField);
gateWayBox.style.display = oldDisplayStyle;
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';
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;
hostedFields.on( 'inputSubmitRequest', () => {
this._submit( contextConfig );
} );
hostedFields.on( 'cardTypeChange', ( event ) => {
if ( ! event.cards.length ) {
this.cardValid = false;
return;
}
const validCards =
this.defaultConfig.hosted_fields.valid_cards;
this.cardValid =
validCards.indexOf( event.cards[ 0 ].type ) !== -1;
hostedFields.on('inputSubmitRequest', () => {
this._submit(contextConfig);
});
hostedFields.on('cardTypeChange', (event) => {
if (!event.cards.length) {
this.cardValid = false;
return;
}
const validCards = this.defaultConfig.hosted_fields.valid_cards;
this.cardValid = validCards.indexOf(event.cards[0].type) !== -1;
const className = this._cardNumberFiledCLassNameByCardType(
event.cards[ 0 ].type
);
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 );
} );
const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type);
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 );
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.querySelector(buttonSelector).addEventListener(
'click',
event => {
event.preventDefault();
this._submit(contextConfig);
}
);
document
.querySelector( wrapper )
.setAttribute( 'data-ppcp-subscribed', true );
}
} );
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(
'click',
() => {
document.querySelector('label[for=ppcp-credit-card-gateway-card-number]').click();
}
)
return;
}
return;
}
const wrapperElement = document.querySelector( wrapper );
wrapperElement.parentNode.removeChild( wrapperElement );
}
const wrapperElement = document.querySelector(wrapper);
wrapperElement.parentNode.removeChild(wrapperElement);
}
disableFields() {
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() {
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.setAttribute({
field: 'number',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.setAttribute({
field: 'cvv',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.setAttribute({
field: 'expirationDate',
attribute: 'disabled'
})
}
}
enableFields() {
if ( this.currentHostedFieldsInstance ) {
this.currentHostedFieldsInstance.removeAttribute( {
field: 'number',
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.removeAttribute( {
field: 'cvv',
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.removeAttribute( {
field: 'expirationDate',
attribute: 'disabled',
} );
}
}
enableFields() {
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.removeAttribute({
field: 'number',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'cvv',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'expirationDate',
attribute: 'disabled'
})
}
}
_submit( contextConfig ) {
this.spinner.block();
this.errorHandler.clear();
_submit(contextConfig) {
this.spinner.block();
this.errorHandler.clear();
if ( this.formValid && this.cardValid ) {
const save_card = this.defaultConfig.can_save_vault_token
? 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) {
const save_card = this.defaultConfig.can_save_vault_token ? 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: vault
};
if (contingency !== 'NO_3D_SECURE') {
hostedFieldsData.contingencies = [contingency];
}
if ( this.defaultConfig.payer ) {
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
: '';
if (this.defaultConfig.payer) {
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;
}
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) => {
payload.orderID = payload.orderId;
this.spinner.unblock();
return contextConfig.onApprove(payload);
}).catch(err => {
this.spinner.unblock();
this.errorHandler.clear();
if ( err.data?.details?.length ) {
this.errorHandler.message(
err.data.details
.map(
( d ) => `${ d.issue } ${ d.description }`
)
.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) {
this.errorHandler.message(err.data.details.map(d => `${d.issue} ${d.description}`).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();
let message = this.defaultConfig.labels.error.generic;
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;
}
let message = this.defaultConfig.labels.error.generic;
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 );
}
}
this.errorHandler.message(message);
}
}
_cardNumberFiledCLassNameByCardType( cardType ) {
return cardType === 'american-express'
? 'amex'
: cardType.replace( '-', '' );
}
_cardNumberFiledCLassNameByCardType(cardType) {
return cardType === 'american-express' ? 'amex' : cardType.replace('-', '');
}
_recreateElementClassAttribute(element, newClassName) {
element.removeAttribute('class')
element.setAttribute('class', newClassName);
}
_recreateElementClassAttribute( element, newClassName ) {
element.removeAttribute( 'class' );
element.setAttribute( 'class', newClassName );
}
}
export default HostedFieldsRenderer;

View file

@ -1,65 +1,72 @@
import widgetBuilder from "./WidgetBuilder";
import widgetBuilder from './WidgetBuilder';
class MessageRenderer {
constructor( config ) {
this.config = config;
this.optionsFingerprint = null;
this.currentNumber = 0;
}
constructor(config) {
this.config = config;
this.optionsFingerprint = null;
this.currentNumber = 0;
}
renderWithAmount( amount ) {
if ( ! this.shouldRender() ) {
return;
}
renderWithAmount(amount) {
if (! this.shouldRender()) {
return;
}
const options = {
amount,
};
if ( this.config.placement ) {
options.placement = this.config.placement;
}
if ( this.config.style ) {
options.style = this.config.style;
}
const options = {
amount,
};
if (this.config.placement) {
options.placement = this.config.placement;
}
if (this.config.style) {
options.style = this.config.style;
}
// sometimes the element is destroyed while the options stay the same
if (
document
.querySelector( this.config.wrapper )
.getAttribute( 'data-render-number' ) !==
this.currentNumber.toString()
) {
this.optionsFingerprint = null;
}
// sometimes the element is destroyed while the options stay the same
if (document.querySelector(this.config.wrapper).getAttribute('data-render-number') !== this.currentNumber.toString()) {
this.optionsFingerprint = null;
}
if ( this.optionsEqual( options ) ) {
return;
}
if (this.optionsEqual(options)) {
return;
}
const wrapper = document.querySelector( this.config.wrapper );
this.currentNumber++;
wrapper.setAttribute( 'data-render-number', this.currentNumber );
const wrapper = document.querySelector(this.config.wrapper);
this.currentNumber++;
wrapper.setAttribute('data-render-number', this.currentNumber);
widgetBuilder.registerMessages( this.config.wrapper, options );
widgetBuilder.renderMessages( this.config.wrapper );
}
widgetBuilder.registerMessages(this.config.wrapper, options);
widgetBuilder.renderMessages(this.config.wrapper);
}
optionsEqual( options ) {
const fingerprint = JSON.stringify( options );
optionsEqual(options) {
const fingerprint = JSON.stringify(options);
if ( this.optionsFingerprint === fingerprint ) {
return true;
}
if (this.optionsFingerprint === fingerprint) {
return true;
}
this.optionsFingerprint = fingerprint;
return false;
}
this.optionsFingerprint = fingerprint;
return false;
}
shouldRender() {
if (typeof paypal === 'undefined' || typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
return false;
}
if (! document.querySelector(this.config.wrapper)) {
return false;
}
return true;
}
shouldRender() {
if (
typeof paypal === 'undefined' ||
typeof paypal.Messages === 'undefined' ||
typeof this.config.wrapper === 'undefined'
) {
return false;
}
if ( ! document.querySelector( this.config.wrapper ) ) {
return false;
}
return true;
}
}
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.
*/
class PreviewButton {
/**
* @param {string} selector - CSS ID of the wrapper, including the `#`
* @param {object} apiConfig - PayPal configuration object; retrieved via a
* widgetBuilder API method
*/
constructor({
selector,
apiConfig,
}) {
this.apiConfig = apiConfig;
this.defaultAttributes = {};
this.buttonConfig = {};
this.ppcpConfig = {};
this.isDynamic = true;
/**
* @param {string} selector - CSS ID of the wrapper, including the `#`
* @param {Object} apiConfig - PayPal configuration object; retrieved via a
* widgetBuilder API method
*/
constructor( { selector, apiConfig } ) {
this.apiConfig = apiConfig;
this.defaultAttributes = {};
this.buttonConfig = {};
this.ppcpConfig = {};
this.isDynamic = true;
// The selector is usually overwritten in constructor of derived class.
this.selector = selector;
this.wrapper = selector;
// The selector is usually overwritten in constructor of derived class.
this.selector = selector;
this.wrapper = selector;
this.domWrapper = null;
}
this.domWrapper = null;
}
/**
* Creates a new DOM node to contain the preview button.
*
* @return {jQuery} Always a single jQuery element with the new DOM node.
*/
createNewWrapper() {
const previewId = this.selector.replace('#', '');
const previewClass = 'ppcp-button-apm';
/**
* Creates a new DOM node to contain the preview button.
*
* @return {jQuery} Always a single jQuery element with the new DOM node.
*/
createNewWrapper() {
const previewId = this.selector.replace( '#', '' );
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.
* When the button is dynamic, it will reflect current form values. A static button always
* uses the settings that were provided via PHP.
*
* @return {this} Reference to self, for chaining.
*/
setDynamic(state) {
this.isDynamic = state;
return this;
}
/**
* Toggle the "dynamic" nature of the preview.
* When the button is dynamic, it will reflect current form values. A static button always
* uses the settings that were provided via PHP.
*
* @param state
* @return {this} Reference to self, for chaining.
*/
setDynamic( state ) {
this.isDynamic = state;
return this;
}
/**
* Sets server-side configuration for the button.
*
* @return {this} Reference to self, for chaining.
*/
setButtonConfig(config) {
this.buttonConfig = merge(this.defaultAttributes, config);
this.buttonConfig.button.wrapper = this.selector;
/**
* Sets server-side configuration for the button.
*
* @param config
* @return {this} Reference to self, for chaining.
*/
setButtonConfig( config ) {
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.
*
* @return {this} Reference to self, for chaining.
*/
setPpcpConfig(config) {
this.ppcpConfig = merge({}, config);
/**
* Updates the button configuration with current details from the form.
*
* @param config
* @return {this} Reference to self, for chaining.
*/
setPpcpConfig( config ) {
this.ppcpConfig = merge( {}, config );
return this;
}
return this;
}
/**
* Merge form details into the config object for preview.
* Mutates the previewConfig object; no return value.
*/
dynamicPreviewConfig(previewConfig, formConfig) {
// Implement in derived class.
}
/**
* Merge form details into the config object for preview.
* Mutates the previewConfig object; no return value.
* @param previewConfig
* @param formConfig
*/
dynamicPreviewConfig( previewConfig, formConfig ) {
// Implement in derived class.
}
/**
* Responsible for creating the actual payment button preview.
* Called by the `render()` method, after the wrapper DOM element is ready.
*/
createButton(previewConfig) {
throw new Error('The "createButton" method must be implemented by the derived class');
}
/**
* Responsible for creating the actual payment button preview.
* 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'
);
}
/**
* Refreshes the button in the DOM.
* Will always create a new button in the DOM.
*/
render() {
// The APM button is disabled and cannot be enabled on the current page: Do not render it.
if (!this.isDynamic && !this.buttonConfig.is_enabled) {
return;
}
/**
* Refreshes the button in the DOM.
* Will always create a new button in the DOM.
*/
render() {
// The APM button is disabled and cannot be enabled on the current page: Do not render it.
if ( ! this.isDynamic && ! this.buttonConfig.is_enabled ) {
return;
}
if (!this.domWrapper) {
if (!this.wrapper) {
console.error('Skip render, button is not configured yet');
return;
}
this.domWrapper = this.createNewWrapper();
this.domWrapper.insertAfter(this.wrapper);
} else {
this.domWrapper.empty().show();
}
if ( ! this.domWrapper ) {
if ( ! this.wrapper ) {
console.error( 'Skip render, button is not configured yet' );
return;
}
this.domWrapper = this.createNewWrapper();
this.domWrapper.insertAfter( this.wrapper );
} else {
this.domWrapper.empty().show();
}
this.isVisible = true;
const previewButtonConfig = merge({}, this.buttonConfig);
const previewPpcpConfig = this.isDynamic ? merge({}, this.ppcpConfig) : {};
previewButtonConfig.button.wrapper = this.selector;
this.isVisible = true;
const previewButtonConfig = merge( {}, this.buttonConfig );
const previewPpcpConfig = this.isDynamic
? 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!
* If both selectors point to the same element, an infinite loop is triggered.
*/
const buttonWrapper = previewButtonConfig.button.wrapper.replace(/^#/, '');
const ppcpWrapper = this.ppcpConfig.button.wrapper.replace(/^#/, '');
/*
* previewButtonConfig.button.wrapper must be different from this.ppcpConfig.button.wrapper!
* If both selectors point to the same element, an infinite loop is triggered.
*/
const buttonWrapper = previewButtonConfig.button.wrapper.replace(
/^#/,
''
);
const ppcpWrapper = this.ppcpConfig.button.wrapper.replace( /^#/, '' );
if (buttonWrapper === ppcpWrapper) {
throw new Error(`[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${buttonWrapper}"`);
}
if ( buttonWrapper === ppcpWrapper ) {
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
* 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
* problems with preview buttons not being displayed instantly.
*
* Using a timeout here will make the button visible again at the end of the current
* event queue.
*/
setTimeout(() => this.domWrapper.show());
}
/*
* 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
* certain JS events (like `ppcp-hidden`) do not fire in the expected order. This causes
* problems with preview buttons not being displayed instantly.
*
* Using a timeout here will make the button visible again at the end of the current
* event queue.
*/
setTimeout( () => this.domWrapper.show() );
}
remove() {
this.isVisible = false;
remove() {
this.isVisible = false;
if (this.domWrapper) {
this.domWrapper.hide().empty();
}
}
if ( this.domWrapper ) {
this.domWrapper.hide().empty();
}
}
}
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.
*/
class PreviewButtonManager {
/**
* Resolves the promise.
* Used by `this.boostrap()` to process enqueued initialization logic.
*/
#onInitResolver;
/**
* Resolves the promise.
* Used by `this.boostrap()` to process enqueued initialization logic.
*/
#onInitResolver;
/**
* A deferred Promise that is resolved once the page is ready.
* Deferred init logic can be added by using `this.#onInit.then(...)`
*
* @param {Promise<void>|null}
*/
#onInit;
/**
* A deferred Promise that is resolved once the page is ready.
* Deferred init logic can be added by using `this.#onInit.then(...)`
*
* @param {Promise<void>|null}
*/
#onInit;
constructor({
methodName,
buttonConfig,
defaultAttributes,
}) {
// Define the payment method name in the derived class.
this.methodName = methodName;
constructor( { methodName, buttonConfig, defaultAttributes } ) {
// Define the payment method name in the derived class.
this.methodName = methodName;
this.buttonConfig = buttonConfig;
this.defaultAttributes = defaultAttributes;
this.buttonConfig = buttonConfig;
this.defaultAttributes = defaultAttributes;
this.isEnabled = true;
this.buttons = {};
this.apiConfig = null;
this.apiError = '';
this.isEnabled = true;
this.buttons = {};
this.apiConfig = null;
this.apiError = '';
this.#onInit = new Promise(resolve => {
this.#onInitResolver = resolve;
});
this.#onInit = new Promise( ( resolve ) => {
this.#onInitResolver = resolve;
} );
this.bootstrap = this.bootstrap.bind(this);
this.renderPreview = this.renderPreview.bind(this);
this.bootstrap = this.bootstrap.bind( this );
this.renderPreview = this.renderPreview.bind( this );
/**
* The "configureAllButtons" method applies ppcpConfig to all buttons that were created
* by this PreviewButtonManager instance. We debounce this method, as it should invoke
* 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
* 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.
*/
this._configureAllButtons = debounce(this._configureAllButtons.bind(this), 100);
/**
* The "configureAllButtons" method applies ppcpConfig to all buttons that were created
* by this PreviewButtonManager instance. We debounce this method, as it should invoke
* 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
* 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.
*/
this._configureAllButtons = debounce(
this._configureAllButtons.bind( this ),
100
);
this.registerEventListeners();
}
this.registerEventListeners();
}
/**
* Protected method that needs to be implemented by the derived class.
* Responsible for fetching and returning the PayPal configuration object for this payment
* method.
*
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
* @return {Promise<{}>}
*/
async fetchConfig(payPal) {
throw new Error('The "fetchConfig" method must 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
* method.
*
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
* @return {Promise<{}>}
*/
async fetchConfig( payPal ) {
throw new Error(
'The "fetchConfig" method must 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.
*
* @param {string} wrapperId - CSS ID of the wrapper element.
* @return {PreviewButton}
*/
createButtonInstance(wrapperId) {
throw new Error('The "createButtonInstance" method must 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.
*
* @param {string} wrapperId - CSS ID of the wrapper element.
* @return {PreviewButton}
*/
createButtonInstance( wrapperId ) {
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
* instead of keeping the preview box empty.
*
* 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.
* @param wrapperId
* @return {any}
*/
createDummy(wrapperId) {
const elButton = document.createElement('div');
elButton.classList.add('ppcp-button-apm', 'ppcp-button-dummy');
elButton.innerHTML = `<span>${this.apiError ?? 'Not Available'}</span>`;
/**
* In case the button SDK could not be loaded from PayPal, we display this dummy button
* instead of keeping the preview box empty.
*
* 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.
* @param wrapperId
* @return {any}
*/
createDummy( wrapperId ) {
const elButton = document.createElement( 'div' );
elButton.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' );
elButton.innerHTML = `<span>${
this.apiError ?? 'Not Available'
}</span>`;
document.querySelector(wrapperId).appendChild(elButton);
document.querySelector( wrapperId ).appendChild( elButton );
const instDummy = {
setDynamic: () => instDummy,
setPpcpConfig: () => instDummy,
render: () => {},
remove: () => {},
};
const instDummy = {
setDynamic: () => instDummy,
setPpcpConfig: () => instDummy,
render: () => {},
remove: () => {},
};
return instDummy;
}
return instDummy;
}
registerEventListeners() {
jQuery(document).one('DOMContentLoaded', this.bootstrap);
registerEventListeners() {
jQuery( document ).one( 'DOMContentLoaded', this.bootstrap );
// General event that all APM buttons react to.
jQuery(document).on('ppcp_paypal_render_preview', this.renderPreview);
// General event that all APM buttons react to.
jQuery( document ).on(
'ppcp_paypal_render_preview',
this.renderPreview
);
// Specific event to only (re)render the current APM button type.
jQuery(document).on(`ppcp_paypal_render_preview_${this.methodName}`, this.renderPreview);
}
// Specific event to only (re)render the current APM button type.
jQuery( document ).on(
`ppcp_paypal_render_preview_${ this.methodName }`,
this.renderPreview
);
}
/**
* Output an error message to the console, with a module-specific prefix.
*/
error(message, ...args) {
console.error(`${this.methodName} ${message}`, ...args);
}
/**
* Output an error message to the console, with a module-specific prefix.
* @param message
* @param {...any} args
*/
error( message, ...args ) {
console.error( `${ this.methodName } ${ message }`, ...args );
}
/**
* 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
* style settings that were provided from server-side.
*/
isDynamic() {
return !!document.querySelector(`[data-ppcp-apm-name="${this.methodName}"]`);
}
/**
* 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
* style settings that were provided from server-side.
*/
isDynamic() {
return !! document.querySelector(
`[data-ppcp-apm-name="${ this.methodName }"]`
);
}
/**
* Load dependencies and bootstrap the module.
* Returns a Promise that resolves once all dependencies were loaded and the module can be
* used without limitation.
*
* @return {Promise<void>}
*/
async bootstrap() {
const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds.
const RESOLVE_INTERVAL = 200;
/**
* Load dependencies and bootstrap the module.
* Returns a Promise that resolves once all dependencies were loaded and the module can be
* used without limitation.
*
* @return {Promise<void>}
*/
async bootstrap() {
const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds.
const RESOLVE_INTERVAL = 200;
if (!this.buttonConfig?.sdk_url || !widgetBuilder) {
this.error('Button could not be configured.');
return;
}
if ( ! this.buttonConfig?.sdk_url || ! widgetBuilder ) {
this.error( 'Button could not be configured.' );
return;
}
// This is a localization object of "gateway-settings.js". If it's missing, the script was
// not loaded.
if (!window.PayPalCommerceGatewaySettings) {
this.error(
'PayPal settings are not fully loaded. Please clear the cache and reload the page.');
return;
}
// This is a localization object of "gateway-settings.js". If it's missing, the script was
// not loaded.
if ( ! window.PayPalCommerceGatewaySettings ) {
this.error(
'PayPal settings are not fully loaded. Please clear the cache and reload the page.'
);
return;
}
// A helper function that clears the interval and resolves/rejects the promise.
const resolveOrReject = (resolve, reject, id, success = true) => {
clearInterval(id);
success ? resolve() : reject('Timeout while waiting for widgetBuilder.paypal');
};
// A helper function that clears the interval and resolves/rejects the promise.
const resolveOrReject = ( resolve, reject, id, success = true ) => {
clearInterval( id );
success
? resolve()
: reject( 'Timeout while waiting for widgetBuilder.paypal' );
};
// Wait for the PayPal SDK to be ready.
const paypalPromise = new Promise((resolve, reject) => {
let elapsedTime = 0;
// Wait for the PayPal SDK to be ready.
const paypalPromise = new Promise( ( resolve, reject ) => {
let elapsedTime = 0;
const id = setInterval(() => {
if (widgetBuilder.paypal) {
resolveOrReject(resolve, reject, id);
} else if (elapsedTime >= MAX_WAIT_TIME) {
resolveOrReject(resolve, reject, id, false);
}
elapsedTime += RESOLVE_INTERVAL;
}, RESOLVE_INTERVAL);
});
const id = setInterval( () => {
if ( widgetBuilder.paypal ) {
resolveOrReject( resolve, reject, id );
} else if ( elapsedTime >= MAX_WAIT_TIME ) {
resolveOrReject( resolve, reject, id, false );
}
elapsedTime += RESOLVE_INTERVAL;
}, RESOLVE_INTERVAL );
} );
// Load the custom SDK script.
const customScriptPromise = loadCustomScript({ url: this.buttonConfig.sdk_url });
// Load the custom SDK script.
const customScriptPromise = loadCustomScript( {
url: this.buttonConfig.sdk_url,
} );
// Wait for both promises to resolve before continuing.
await Promise
.all([customScriptPromise, paypalPromise])
.catch(err => {
console.log(`Failed to load ${this.methodName} dependencies:`, err);
});
// Wait for both promises to resolve before continuing.
await Promise.all( [ customScriptPromise, paypalPromise ] ).catch(
( err ) => {
console.log(
`Failed to load ${ this.methodName } dependencies:`,
err
);
}
);
/*
/*
The fetchConfig method requires two objects to succeed:
(a) the SDK custom-script
(b) the `widgetBuilder.paypal` object
*/
try {
this.apiConfig = await this.fetchConfig(widgetBuilder.paypal);
} catch (error) {
this.apiConfig = null;
}
try {
this.apiConfig = await this.fetchConfig( widgetBuilder.paypal );
} catch ( error ) {
this.apiConfig = null;
}
// Avoid errors when there was a problem with loading the SDK.
await this.#onInitResolver();
// Avoid errors when there was a problem with loading the SDK.
await this.#onInitResolver();
this.#onInit = null;
}
this.#onInit = null;
}
/**
* Event handler, fires on `ppcp_paypal_render_preview`
*
* @param ev - Ignored
* @param ppcpConfig - The button settings for the preview.
*/
renderPreview(ev, ppcpConfig) {
const id = ppcpConfig.button.wrapper;
/**
* Event handler, fires on `ppcp_paypal_render_preview`
*
* @param ev - Ignored
* @param ppcpConfig - The button settings for the preview.
*/
renderPreview( ev, ppcpConfig ) {
const id = ppcpConfig.button.wrapper;
if (!id) {
this.error('Button did not provide a wrapper ID', ppcpConfig);
return;
}
if ( ! id ) {
this.error( 'Button did not provide a wrapper ID', ppcpConfig );
return;
}
if (!this.shouldInsertPreviewButton(id)) {
return;
}
if ( ! this.shouldInsertPreviewButton( id ) ) {
return;
}
if (!this.buttons[id]) {
this._addButton(id, ppcpConfig);
} else {
// This is a debounced method, that fires after 100ms.
this._configureAllButtons(ppcpConfig);
}
}
if ( ! this.buttons[ id ] ) {
this._addButton( id, ppcpConfig );
} else {
// This is a debounced method, that fires after 100ms.
this._configureAllButtons( ppcpConfig );
}
}
/**
* Determines if the preview box supports the current 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.
* @return {boolean} True if the box is eligible for the preview button, false otherwise.
*/
shouldInsertPreviewButton(previewId) {
const container = document.querySelector(previewId);
const box = container.closest('.ppcp-preview');
const limit = box.dataset.ppcpPreviewBlock ?? 'all';
/**
* Determines if the preview box supports the current 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.
* @return {boolean} True if the box is eligible for the preview button, false otherwise.
*/
shouldInsertPreviewButton( previewId ) {
const container = document.querySelector( previewId );
const box = container.closest( '.ppcp-preview' );
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.
*/
_configureButton(id, ppcpConfig) {
this.buttons[id]
.setDynamic(this.isDynamic())
.setPpcpConfig(ppcpConfig)
.render();
}
/**
* Applies a new configuration to an existing preview button.
* @param id
* @param ppcpConfig
*/
_configureButton( id, ppcpConfig ) {
this.buttons[ id ]
.setDynamic( this.isDynamic() )
.setPpcpConfig( ppcpConfig )
.render();
}
/**
* Apples the provided configuration to all existing preview buttons.
*/
_configureAllButtons(ppcpConfig) {
Object.entries(this.buttons).forEach(([id, button]) => {
this._configureButton(id, {
...ppcpConfig,
button: {
...ppcpConfig.button,
/**
* Apples the provided configuration to all existing preview buttons.
* @param ppcpConfig
*/
_configureAllButtons( ppcpConfig ) {
Object.entries( this.buttons ).forEach( ( [ id, button ] ) => {
this._configureButton( id, {
...ppcpConfig,
button: {
...ppcpConfig.button,
// The ppcpConfig object might refer to a different wrapper.
// Fix the selector, to avoid unintentionally hidden preview buttons.
wrapper: button.wrapper,
},
});
});
}
// The ppcpConfig object might refer to a different wrapper.
// Fix the selector, to avoid unintentionally hidden preview buttons.
wrapper: button.wrapper,
},
} );
} );
}
/**
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
*/
_addButton(id, ppcpConfig) {
const createButton = () => {
if (!this.buttons[id]) {
let newInst;
if (this.apiConfig && 'object' === typeof this.apiConfig) {
newInst = this.createButtonInstance(id).setButtonConfig(this.buttonConfig);
} else {
newInst = this.createDummy(id);
}
/**
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
* @param id
* @param ppcpConfig
*/
_addButton( id, ppcpConfig ) {
const createButton = () => {
if ( ! this.buttons[ id ] ) {
let newInst;
if ( this.apiConfig && 'object' === typeof this.apiConfig ) {
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) {
this.#onInit.then(createButton);
} else {
createButton();
}
}
if ( this.#onInit ) {
this.#onInit.then( createButton );
} else {
createButton();
}
}
/**
* Refreshes all buttons using the latest buttonConfig.
*
* @return {this} Reference to self, for chaining.
*/
renderButtons() {
if (this.isEnabled) {
Object.values(this.buttons).forEach(button => button.render());
} else {
Object.values(this.buttons).forEach(button => button.remove());
}
/**
* Refreshes all buttons using the latest buttonConfig.
*
* @return {this} Reference to self, for chaining.
*/
renderButtons() {
if ( this.isEnabled ) {
Object.values( this.buttons ).forEach( ( button ) =>
button.render()
);
} else {
Object.values( this.buttons ).forEach( ( button ) =>
button.remove()
);
}
return this;
}
return this;
}
/**
* Enables this payment method, which re-creates or refreshes all buttons.
*
* @return {this} Reference to self, for chaining.
*/
enable() {
if (!this.isEnabled) {
this.isEnabled = true;
this.renderButtons();
}
/**
* Enables this payment method, which re-creates or refreshes all buttons.
*
* @return {this} Reference to self, for chaining.
*/
enable() {
if ( ! this.isEnabled ) {
this.isEnabled = true;
this.renderButtons();
}
return this;
}
return this;
}
/**
* Disables this payment method, effectively removing all preview buttons.
*
* @return {this} Reference to self, for chaining.
*/
disable() {
if (!this.isEnabled) {
this.isEnabled = false;
this.renderButtons();
}
/**
* Disables this payment method, effectively removing all preview buttons.
*
* @return {this} Reference to self, for chaining.
*/
disable() {
if ( ! this.isEnabled ) {
this.isEnabled = false;
this.renderButtons();
}
return this;
}
return this;
}
}
export default PreviewButtonManager;

View file

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

View file

@ -3,179 +3,178 @@
* To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource].
*/
class WidgetBuilder {
constructor() {
this.paypal = null;
this.buttons = new Map();
this.messages = new Map();
constructor() {
this.paypal = null;
this.buttons = new Map();
this.messages = new Map();
this.renderEventName = 'ppcp-render';
this.renderEventName = 'ppcp-render';
document.ppcpWidgetBuilderStatus = () => {
console.log( {
buttons: this.buttons,
messages: this.messages,
} );
};
document.ppcpWidgetBuilderStatus = () => {
console.log({
buttons: this.buttons,
messages: this.messages,
});
}
jQuery( document )
.off( this.renderEventName )
.on( this.renderEventName, () => {
this.renderAll();
} );
}
jQuery(document)
.off(this.renderEventName)
.on(this.renderEventName, () => {
this.renderAll();
});
}
setPaypal( paypal ) {
this.paypal = paypal;
jQuery( document ).trigger( 'ppcp-paypal-loaded', paypal );
}
setPaypal(paypal) {
this.paypal = paypal;
jQuery(document).trigger('ppcp-paypal-loaded', paypal);
}
registerButtons( wrapper, options ) {
wrapper = this.sanitizeWrapper( wrapper );
registerButtons(wrapper, options) {
wrapper = this.sanitizeWrapper(wrapper);
this.buttons.set( this.toKey( wrapper ), {
wrapper,
options,
} );
}
this.buttons.set(this.toKey(wrapper), {
wrapper: wrapper,
options: options,
});
}
renderButtons( wrapper ) {
wrapper = this.sanitizeWrapper( wrapper );
renderButtons(wrapper) {
wrapper = this.sanitizeWrapper(wrapper);
if ( ! this.buttons.has( this.toKey( wrapper ) ) ) {
return;
}
if (!this.buttons.has(this.toKey(wrapper))) {
return;
}
if ( this.hasRendered( wrapper ) ) {
return;
}
if (this.hasRendered(wrapper)) {
return;
}
const entry = this.buttons.get( this.toKey( wrapper ) );
const btn = this.paypal.Buttons( entry.options );
const entry = this.buttons.get(this.toKey(wrapper));
const btn = this.paypal.Buttons(entry.options);
if ( ! btn.isEligible() ) {
this.buttons.delete( this.toKey( wrapper ) );
return;
}
if (!btn.isEligible()) {
this.buttons.delete(this.toKey(wrapper));
return;
}
const target = this.buildWrapperTarget( wrapper );
let target = this.buildWrapperTarget(wrapper);
if ( ! target ) {
return;
}
if (!target) {
return;
}
btn.render( target );
}
btn.render(target);
}
renderAllButtons() {
for ( const [ wrapper, entry ] of this.buttons ) {
this.renderButtons( wrapper );
}
}
renderAllButtons() {
for (const [wrapper, entry] of this.buttons) {
this.renderButtons(wrapper);
}
}
registerMessages( wrapper, options ) {
this.messages.set( wrapper, {
wrapper,
options,
} );
}
registerMessages(wrapper, options) {
this.messages.set(wrapper, {
wrapper: wrapper,
options: options
});
}
renderMessages( wrapper ) {
if ( ! this.messages.has( wrapper ) ) {
return;
}
renderMessages(wrapper) {
if (!this.messages.has(wrapper)) {
return;
}
const entry = this.messages.get( wrapper );
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 element = document.querySelector(wrapper);
element.setAttribute('data-pp-amount', entry.options.amount);
return;
}
const btn = this.paypal.Messages( entry.options );
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
setTimeout(() => {
if (!this.hasRendered(wrapper)) {
btn.render(entry.wrapper);
}
}, 100);
}
renderAllMessages() {
for ( const [ wrapper, entry ] of this.messages ) {
this.renderMessages( wrapper );
}
}
renderAllMessages() {
for (const [wrapper, entry] of this.messages) {
this.renderMessages(wrapper);
}
}
renderAll() {
this.renderAllButtons();
this.renderAllMessages();
}
renderAll() {
this.renderAllButtons();
this.renderAllMessages();
}
hasRendered( wrapper ) {
let selector = wrapper;
hasRendered(wrapper) {
let selector = wrapper;
if ( Array.isArray( wrapper ) ) {
selector = wrapper[ 0 ];
for ( const item of wrapper.slice( 1 ) ) {
selector += ' .item-' + item;
}
}
if (Array.isArray(wrapper)) {
selector = wrapper[0];
for (const item of wrapper.slice(1)) {
selector += ' .item-' + item;
}
}
const element = document.querySelector( selector );
return element && element.hasChildNodes();
}
const element = document.querySelector(selector);
return element && element.hasChildNodes();
}
sanitizeWrapper( wrapper ) {
if ( Array.isArray( wrapper ) ) {
wrapper = wrapper.filter( ( item ) => !! item );
if ( wrapper.length === 1 ) {
wrapper = wrapper[ 0 ];
}
}
return wrapper;
}
sanitizeWrapper(wrapper) {
if (Array.isArray(wrapper)) {
wrapper = wrapper.filter(item => !!item);
if (wrapper.length === 1) {
wrapper = wrapper[0];
}
}
return wrapper;
}
buildWrapperTarget( wrapper ) {
let target = wrapper;
buildWrapperTarget(wrapper) {
let target = wrapper;
if ( Array.isArray( wrapper ) ) {
const $wrapper = jQuery( wrapper[ 0 ] );
if (Array.isArray(wrapper)) {
const $wrapper = jQuery(wrapper[0]);
if ( ! $wrapper.length ) {
return;
}
if (!$wrapper.length) {
return;
}
const itemClass = 'item-' + wrapper[ 1 ];
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
let $item = $wrapper.find('.' + itemClass);
if ( ! $item.length ) {
$item = jQuery( `<div class="${ itemClass }"></div>` );
$wrapper.append( $item );
}
if (!$item.length) {
$item = jQuery(`<div class="${itemClass}"></div>`);
$wrapper.append($item);
}
target = $item.get( 0 );
}
target = $item.get(0);
}
if ( ! jQuery( target ).length ) {
return null;
}
if (!jQuery(target).length) {
return null;
}
return target;
}
return target;
}
toKey(wrapper) {
if (Array.isArray(wrapper)) {
return JSON.stringify(wrapper);
}
return wrapper;
}
toKey( wrapper ) {
if ( Array.isArray( wrapper ) ) {
return JSON.stringify( wrapper );
}
return wrapper;
}
}
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" );
$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 ) ) {
$this->render_after_block(
@ -1163,11 +1165,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
),
'update_customer_shipping' => 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(
'cart_endpoint' => '/wp-json/wc/store/cart/',
'update_customer_endpoint' => '/wp-json/wc/store/v1/cart/update-customer/',
'cart_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT ),
'update_customer_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'update-customer' ),
),
'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ),
'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 {
$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
*/
private function bn_codes(): array {
private function bn_codes() : array {
$bn_code = PPCP_PAYPAL_BN_CODE;
return array(
'checkout' => 'Woo_PPCP',
'cart' => 'Woo_PPCP',
'mini-cart' => 'Woo_PPCP',
'product' => 'Woo_PPCP',
'checkout' => $bn_code,
'cart' => $bn_code,
'mini-cart' => $bn_code,
'product' => $bn_code,
);
}

View file

@ -14,8 +14,10 @@ use WC_Cart;
use WC_Order;
use WC_Order_Item_Product;
use WC_Order_Item_Shipping;
use WC_Product;
use WC_Subscription;
use WC_Subscriptions_Product;
use WC_Tax;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
@ -106,6 +108,7 @@ class WooCommerceOrderCreator {
* @param Payer|null $payer The payer.
* @param Shipping|null $shipping The shipping.
* @return void
* @psalm-suppress InvalidScalarArgument
*/
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void {
$cart_contents = $wc_cart->get_cart();
@ -130,18 +133,21 @@ class WooCommerceOrderCreator {
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_subtotal( $total );
$item->set_total( $total );
$item->set_subtotal( $subtotal );
$item->set_total( $subtotal );
$this->configure_taxes( $product, $item, $subtotal );
$product_id = $product->get_id();
if ( $this->is_subscription( $product_id ) ) {
$subscription = $this->create_subscription( $wc_order, $product_id );
$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_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.
*

View file

@ -150,6 +150,30 @@ return array(
'TWD',
'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(
'AUD',
'BRL',

View file

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

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