Merge branch 'trunk' into PCP-3342-shipping-callback-compatibility-problem-with-name-your-price-plugin

# Conflicts:
#	modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php
This commit is contained in:
Narek Zakarian 2024-07-23 16:15:20 +04:00
commit d20b1b982f
No known key found for this signature in database
GPG key ID: 07AFD7E7A9C164A7
174 changed files with 15007 additions and 11994 deletions

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,17 +1,16 @@
import ApplepayButton from "./ApplepayButton"; import ApplepayButton from './ApplepayButton';
class ApplepayManagerBlockEditor { class ApplepayManagerBlockEditor {
constructor( buttonConfig, ppcpConfig ) {
constructor(buttonConfig, ppcpConfig) {
this.buttonConfig = buttonConfig; this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig; this.ppcpConfig = ppcpConfig;
this.applePayConfig = null; this.applePayConfig = null;
} }
init() { init() {
(async () => { ( async () => {
await this.config(); await this.config();
})(); } )();
} }
async config() { async config() {
@ -22,13 +21,12 @@ class ApplepayManagerBlockEditor {
this.ppcpConfig.context, this.ppcpConfig.context,
null, null,
this.buttonConfig, this.buttonConfig,
this.ppcpConfig, this.ppcpConfig
); );
button.init(this.applePayConfig); button.init( this.applePayConfig );
} catch ( error ) {
} catch (error) { console.error( 'Failed to initialize Apple Pay:', error );
console.error('Failed to initialize Apple Pay:', error);
} }
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,4 +5,4 @@ export const endpoints = {
createOrderProduct: '_apple_pay_create_order_product', createOrderProduct: '_apple_pay_create_order_product',
updateShippingMethod: '_apple_pay_update_shipping_contact', updateShippingMethod: '_apple_pay_update_shipping_contact',
updateShippingContact: '_apple_pay_update_billing_contact', updateShippingContact: '_apple_pay_update_billing_contact',
} };

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
class DomElement { class DomElement {
constructor( config ) {
constructor(config) {
this.$ = jQuery; this.$ = jQuery;
this.config = config; this.config = config;
this.selector = this.config.selector; this.selector = this.config.selector;
@ -10,20 +9,20 @@ class DomElement {
this.anchorSelector = this.config.anchorSelector || null; this.anchorSelector = this.config.anchorSelector || null;
} }
trigger(action) { trigger( action ) {
this.$(this.selector).trigger(action); this.$( this.selector ).trigger( action );
} }
on(action, callable) { on( action, callable ) {
this.$(document).on(action, this.selector, callable); this.$( document ).on( action, this.selector, callable );
} }
hide() { hide() {
this.$(this.selector).hide(); this.$( this.selector ).hide();
} }
show() { show() {
this.$(this.selector).show(); this.$( this.selector ).show();
} }
click() { click() {
@ -31,9 +30,8 @@ class DomElement {
} }
get() { get() {
return document.querySelector(this.selector); return document.querySelector( this.selector );
} }
} }
export default DomElement; export default DomElement;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/** /**
* Class AxoModule * Class AxoModule
*/ */
@ -107,12 +107,9 @@ class AxoModule implements ModuleInterface {
return $methods; return $methods;
} }
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if ( apply_filters( if ( apply_filters(
'woocommerce_paypal_payments_axo_hide_credit_card_gateway', 'woocommerce_paypal_payments_axo_hide_credit_card_gateway',
$this->hide_credit_card_when_using_fastlane( $methods, $settings ) $this->hide_credit_card_when_using_fastlane( $methods, $c )
) ) { ) ) {
unset( $methods[ CreditCardGateway::ID ] ); unset( $methods[ CreditCardGateway::ID ] );
} }
@ -143,12 +140,17 @@ class AxoModule implements ModuleInterface {
); );
add_action( add_action(
'init', 'wp_loaded',
function () use ( $c ) { function () use ( $c ) {
$module = $this; $module = $this;
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
// Check if the module is applicable, correct country, currency, ... etc. // Check if the module is applicable, correct country, currency, ... etc.
if ( ! $c->get( 'axo.eligible' ) || 'continuation' === $c->get( 'button.context' ) ) { if ( ! $c->get( 'axo.eligible' )
|| 'continuation' === $c->get( 'button.context' )
|| $subscription_helper->cart_contains_subscription() ) {
return; return;
} }
@ -160,13 +162,10 @@ class AxoModule implements ModuleInterface {
'wp_enqueue_scripts', 'wp_enqueue_scripts',
static function () use ( $c, $manager, $module ) { static function () use ( $c, $manager, $module ) {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$smart_button = $c->get( 'button.smart-button' ); $smart_button = $c->get( 'button.smart-button' );
assert( $smart_button instanceof SmartButtonInterface ); assert( $smart_button instanceof SmartButtonInterface );
if ( $module->should_render_fastlane( $settings ) && $smart_button->should_load_ppcp_script() ) { if ( $module->should_render_fastlane( $c ) && $smart_button->should_load_ppcp_script() ) {
$manager->enqueue(); $manager->enqueue();
} }
} }
@ -243,10 +242,8 @@ class AxoModule implements ModuleInterface {
add_action( add_action(
'template_redirect', 'template_redirect',
function () use ( $c ) { function () use ( $c ) {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if ( $this->should_render_fastlane( $settings ) ) { if ( $this->should_render_fastlane( $c ) ) {
WC()->session->set( 'chosen_payment_method', AxoGateway::ID ); WC()->session->set( 'chosen_payment_method', AxoGateway::ID );
} }
} }
@ -320,11 +317,11 @@ class AxoModule implements ModuleInterface {
* Condition to evaluate if Credit Card gateway should be hidden. * Condition to evaluate if Credit Card gateway should be hidden.
* *
* @param array $methods WC payment methods. * @param array $methods WC payment methods.
* @param Settings $settings The settings. * @param ContainerInterface $c The container.
* @return bool * @return bool
*/ */
private function hide_credit_card_when_using_fastlane( array $methods, Settings $settings ): bool { private function hide_credit_card_when_using_fastlane( array $methods, ContainerInterface $c ): bool {
return $this->should_render_fastlane( $settings ) && isset( $methods[ CreditCardGateway::ID ] ); return $this->should_render_fastlane( $c ) && isset( $methods[ CreditCardGateway::ID ] );
} }
/** /**
@ -332,10 +329,13 @@ class AxoModule implements ModuleInterface {
* *
* Fastlane should only render on the classic checkout, when Fastlane is enabled in the settings and also only for guest customers. * Fastlane should only render on the classic checkout, when Fastlane is enabled in the settings and also only for guest customers.
* *
* @param Settings $settings The settings. * @param ContainerInterface $c The container.
* @return bool * @return bool
*/ */
private function should_render_fastlane( Settings $settings ): bool { private function should_render_fastlane( ContainerInterface $c ): bool {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$is_axo_enabled = $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ?? false; $is_axo_enabled = $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ?? false;
$is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false; $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false;
@ -353,10 +353,8 @@ class AxoModule implements ModuleInterface {
* @return void * @return void
*/ */
private function add_checkout_loader_markup( ContainerInterface $c ): void { private function add_checkout_loader_markup( ContainerInterface $c ): void {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if ( $this->should_render_fastlane( $settings ) ) { if ( $this->should_render_fastlane( $c ) ) {
add_action( add_action(
'woocommerce_checkout_before_customer_details', 'woocommerce_checkout_before_customer_details',
function () { function () {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,15 @@
import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
import {setVisible, setVisibleByClass} from '../Helper/Hiding'; import { setVisible, setVisibleByClass } from '../Helper/Hiding';
import { import {
getCurrentPaymentMethod, getCurrentPaymentMethod,
isSavedCardSelected, ORDER_BUTTON_SELECTOR, isSavedCardSelected,
PaymentMethods ORDER_BUTTON_SELECTOR,
} from "../Helper/CheckoutMethodState"; PaymentMethods,
import BootstrapHelper from "../Helper/BootstrapHelper"; } from '../Helper/CheckoutMethodState';
import BootstrapHelper from '../Helper/BootstrapHelper';
class CheckoutBootstap { class CheckoutBootstap {
constructor(gateway, renderer, spinner, errorHandler) { constructor( gateway, renderer, spinner, errorHandler ) {
this.gateway = gateway; this.gateway = gateway;
this.renderer = renderer; this.renderer = renderer;
this.spinner = spinner; this.spinner = spinner;
@ -16,9 +17,13 @@ class CheckoutBootstap {
this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR; this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR;
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { this.renderer.onButtonsInit(
this.gateway.button.wrapper,
() => {
this.handleButtonStatus(); this.handleButtonStatus();
}, true); },
true
);
} }
init() { init() {
@ -29,72 +34,91 @@ class CheckoutBootstap {
// WC saves form values, so with our current UI it would be a bit weird // WC saves form values, so with our current UI it would be a bit weird
// if the user paid with saved, then after some time tries to pay again, // if the user paid with saved, then after some time tries to pay again,
// but wants to enter a new card, and to do that they have to choose “Select payment” in the list. // but wants to enter a new card, and to do that they have to choose “Select payment” in the list.
jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val()); jQuery( '#saved-credit-card' ).val(
jQuery( '#saved-credit-card option:first' ).val()
);
jQuery(document.body).on('updated_checkout', () => { jQuery( document.body ).on( 'updated_checkout', () => {
this.render() this.render();
this.handleButtonStatus(); this.handleButtonStatus();
if (this.shouldShowMessages() && document.querySelector(this.gateway.messages.wrapper)) { // currently we need amount only for Pay Later if (
fetch( this.shouldShowMessages() &&
this.gateway.ajax.cart_script_params.endpoint, document.querySelector( this.gateway.messages.wrapper )
{ ) {
// currently we need amount only for Pay Later
fetch( this.gateway.ajax.cart_script_params.endpoint, {
method: 'GET', method: 'GET',
credentials: 'same-origin', credentials: 'same-origin',
} } )
) .then( ( result ) => result.json() )
.then(result => result.json()) .then( ( result ) => {
.then(result => { if ( ! result.success ) {
if (! result.success) {
return; return;
} }
jQuery(document.body).trigger('ppcp_checkout_total_updated', [result.data.amount]); jQuery( document.body ).trigger(
}); 'ppcp_checkout_total_updated',
[ result.data.amount ]
);
} );
} }
}); } );
jQuery(document.body).on('updated_checkout payment_method_selected', () => { jQuery( document.body ).on(
'updated_checkout payment_method_selected',
() => {
this.updateUi(); this.updateUi();
}); }
);
jQuery(document).on('hosted_fields_loaded', () => { jQuery( document ).on( 'hosted_fields_loaded', () => {
jQuery('#saved-credit-card').on('change', () => { jQuery( '#saved-credit-card' ).on( 'change', () => {
this.updateUi(); this.updateUi();
}) } );
}); } );
jQuery(document).on('ppcp_should_show_messages', (e, data) => { jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => {
if (!this.shouldShowMessages()) { if ( ! this.shouldShowMessages() ) {
data.result = false; data.result = false;
} }
}); } );
this.updateUi(); this.updateUi();
} }
handleButtonStatus() { handleButtonStatus() {
BootstrapHelper.handleButtonStatus(this); BootstrapHelper.handleButtonStatus( this );
} }
shouldRender() { shouldRender() {
if (document.querySelector(this.gateway.button.cancel_wrapper)) { if ( document.querySelector( this.gateway.button.cancel_wrapper ) ) {
return false; return false;
} }
return document.querySelector(this.gateway.button.wrapper) !== null || document.querySelector(this.gateway.hosted_fields.wrapper) !== null; return (
document.querySelector( this.gateway.button.wrapper ) !== null ||
document.querySelector( this.gateway.hosted_fields.wrapper ) !==
null
);
} }
shouldEnable() { shouldEnable() {
return BootstrapHelper.shouldEnable(this); return BootstrapHelper.shouldEnable( this );
} }
render() { render() {
if (!this.shouldRender()) { if ( ! this.shouldRender() ) {
return; return;
} }
if (document.querySelector(this.gateway.hosted_fields.wrapper + '>div')) { if (
document.querySelector(this.gateway.hosted_fields.wrapper + '>div').setAttribute('style', ''); document.querySelector(
this.gateway.hosted_fields.wrapper + '>div'
)
) {
document
.querySelector( this.gateway.hosted_fields.wrapper + '>div' )
.setAttribute( 'style', '' );
} }
const actionHandler = new CheckoutActionHandler( const actionHandler = new CheckoutActionHandler(
PayPalCommerceGateway, PayPalCommerceGateway,
@ -102,17 +126,28 @@ class CheckoutBootstap {
this.spinner this.spinner
); );
if( if (
PayPalCommerceGateway.data_client_id.has_subscriptions PayPalCommerceGateway.data_client_id.has_subscriptions &&
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) { ) {
let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id let subscription_plan_id =
if(PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== '') { PayPalCommerceGateway.subscription_plan_id;
subscription_plan_id = PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart if (
PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !==
''
) {
subscription_plan_id =
PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart;
} }
this.renderer.render(actionHandler.subscriptionsConfiguration(subscription_plan_id), {}, actionHandler.configuration()); this.renderer.render(
actionHandler.subscriptionsConfiguration(
subscription_plan_id
),
{},
actionHandler.configuration()
);
if(!PayPalCommerceGateway.subscription_product_allowed) { if ( ! PayPalCommerceGateway.subscription_product_allowed ) {
this.gateway.button.is_disabled = true; this.gateway.button.is_disabled = true;
this.handleButtonStatus(); this.handleButtonStatus();
} }
@ -120,90 +155,161 @@ class CheckoutBootstap {
return; return;
} }
if( if (
PayPalCommerceGateway.is_free_trial_cart PayPalCommerceGateway.is_free_trial_cart &&
&& PayPalCommerceGateway.vault_v3_enabled PayPalCommerceGateway.vault_v3_enabled
) { ) {
this.renderer.render(actionHandler.addPaymentMethodConfiguration(), {}, actionHandler.configuration()); this.renderer.render(
actionHandler.addPaymentMethodConfiguration(),
{},
actionHandler.configuration()
);
return; return;
} }
this.renderer.render(actionHandler.configuration(), {}, actionHandler.configuration()); this.renderer.render(
actionHandler.configuration(),
{},
actionHandler.configuration()
);
} }
updateUi() { updateUi() {
const currentPaymentMethod = getCurrentPaymentMethod(); const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
const isCard = currentPaymentMethod === PaymentMethods.CARDS; const isCard = currentPaymentMethod === PaymentMethods.CARDS;
const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod); const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes(
currentPaymentMethod
);
const isGooglePayMethod =
currentPaymentMethod === PaymentMethods.GOOGLEPAY;
const isSavedCard = isCard && isSavedCardSelected(); const isSavedCard = isCard && isSavedCardSelected();
const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway; const isNotOurGateway =
! isPaypal &&
! isCard &&
! isSeparateButtonGateway &&
! isGooglePayMethod;
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; const hasVaultedPaypal =
PayPalCommerceGateway.vaulted_paypal_email !== '';
const paypalButtonWrappers = { const paypalButtonWrappers = {
...Object.entries(PayPalCommerceGateway.separate_buttons) ...Object.entries( PayPalCommerceGateway.separate_buttons ).reduce(
.reduce((result, [k, data]) => { ( result, [ k, data ] ) => {
return {...result, [data.id]: data.wrapper} return { ...result, [ data.id ]: data.wrapper };
}, {}), },
{}
),
}; };
setVisibleByClass(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, 'ppcp-hidden'); setVisibleByClass(
setVisible('.ppcp-vaulted-paypal-details', isPaypal); this.standardOrderButtonSelector,
setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); ( isPaypal && isFreeTrial && hasVaultedPaypal ) ||
setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); isNotOurGateway ||
for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) { isSavedCard,
setVisible(wrapper, gatewayId === currentPaymentMethod); 'ppcp-hidden'
);
setVisible( '.ppcp-vaulted-paypal-details', isPaypal );
setVisible(
this.gateway.button.wrapper,
isPaypal && ! ( isFreeTrial && hasVaultedPaypal )
);
setVisible(
this.gateway.hosted_fields.wrapper,
isCard && ! isSavedCard
);
for ( const [ gatewayId, wrapper ] of Object.entries(
paypalButtonWrappers
) ) {
setVisible( wrapper, gatewayId === currentPaymentMethod );
} }
if (isCard) { if ( isCard ) {
if (isSavedCard) { if ( isSavedCard ) {
this.disableCreditCardFields(); this.disableCreditCardFields();
} else { } else {
this.enableCreditCardFields(); this.enableCreditCardFields();
} }
} }
jQuery(document.body).trigger('ppcp_checkout_rendered'); setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod );
jQuery( document.body ).trigger( 'ppcp_checkout_rendered' );
} }
shouldShowMessages() { shouldShowMessages() {
// hide when another method selected only if messages are near buttons // hide when another method selected only if messages are near buttons
const messagesWrapper = document.querySelector(this.gateway.messages.wrapper); const messagesWrapper = document.querySelector(
if (getCurrentPaymentMethod() !== PaymentMethods.PAYPAL && this.gateway.messages.wrapper
messagesWrapper && jQuery(messagesWrapper).closest('.ppc-button-wrapper').length );
if (
getCurrentPaymentMethod() !== PaymentMethods.PAYPAL &&
messagesWrapper &&
jQuery( messagesWrapper ).closest( '.ppc-button-wrapper' ).length
) { ) {
return false; return false;
} }
return !PayPalCommerceGateway.is_free_trial_cart; return ! PayPalCommerceGateway.is_free_trial_cart;
} }
disableCreditCardFields() { disableCreditCardFields() {
jQuery('label[for="ppcp-credit-card-gateway-card-number"]').addClass('ppcp-credit-card-gateway-form-field-disabled') jQuery( 'label[for="ppcp-credit-card-gateway-card-number"]' ).addClass(
jQuery('#ppcp-credit-card-gateway-card-number').addClass('ppcp-credit-card-gateway-form-field-disabled') '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( '#ppcp-credit-card-gateway-card-number' ).addClass(
jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').addClass('ppcp-credit-card-gateway-form-field-disabled') '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( 'label[for="ppcp-credit-card-gateway-card-expiry"]' ).addClass(
jQuery('#ppcp-credit-card-vault').addClass('ppcp-credit-card-gateway-form-field-disabled') 'ppcp-credit-card-gateway-form-field-disabled'
jQuery('#ppcp-credit-card-vault').attr("disabled", true) );
this.renderer.disableCreditCardFields() 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() { enableCreditCardFields() {
jQuery('label[for="ppcp-credit-card-gateway-card-number"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') jQuery(
jQuery('#ppcp-credit-card-gateway-card-number').removeClass('ppcp-credit-card-gateway-form-field-disabled') 'label[for="ppcp-credit-card-gateway-card-number"]'
jQuery('label[for="ppcp-credit-card-gateway-card-expiry"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') ).removeClass( 'ppcp-credit-card-gateway-form-field-disabled' );
jQuery('#ppcp-credit-card-gateway-card-expiry').removeClass('ppcp-credit-card-gateway-form-field-disabled') jQuery( '#ppcp-credit-card-gateway-card-number' ).removeClass(
jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') 'ppcp-credit-card-gateway-form-field-disabled'
jQuery('#ppcp-credit-card-gateway-card-cvc').removeClass('ppcp-credit-card-gateway-form-field-disabled') );
jQuery('label[for="vault"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') jQuery(
jQuery('#ppcp-credit-card-vault').removeClass('ppcp-credit-card-gateway-form-field-disabled') 'label[for="ppcp-credit-card-gateway-card-expiry"]'
jQuery('#ppcp-credit-card-vault').attr("disabled", false) ).removeClass( 'ppcp-credit-card-gateway-form-field-disabled' );
this.renderer.enableCreditCardFields() 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,97 +1,110 @@
import {setVisible} from "../Helper/Hiding"; import { setVisible } from '../Helper/Hiding';
import MessageRenderer from "../Renderer/MessageRenderer"; import MessageRenderer from '../Renderer/MessageRenderer';
class MessagesBootstrap { class MessagesBootstrap {
constructor(gateway, messageRenderer) { constructor( gateway, messageRenderer ) {
this.gateway = gateway; this.gateway = gateway;
this.renderers = []; this.renderers = [];
this.lastAmount = this.gateway.messages.amount; this.lastAmount = this.gateway.messages.amount;
if (messageRenderer) { if ( messageRenderer ) {
this.renderers.push(messageRenderer); this.renderers.push( messageRenderer );
} }
} }
async init() { async init() {
if (this.gateway.messages?.block?.enabled) { if ( this.gateway.messages?.block?.enabled ) {
await this.attemptDiscoverBlocks(3); // Try up to 3 times await this.attemptDiscoverBlocks( 3 ); // Try up to 3 times
} }
jQuery(document.body).on('ppcp_cart_rendered ppcp_checkout_rendered', () => { jQuery( document.body ).on(
'ppcp_cart_rendered ppcp_checkout_rendered',
() => {
this.render(); this.render();
}); }
jQuery(document.body).on('ppcp_script_data_changed', (e, data) => { );
jQuery( document.body ).on( 'ppcp_script_data_changed', ( e, data ) => {
this.gateway = data; this.gateway = data;
this.render(); 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) => { jQuery( document.body ).on(
if (this.lastAmount !== amount) { '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.lastAmount = amount;
this.render(); this.render();
} }
}); }
);
this.render(); this.render();
} }
attemptDiscoverBlocks(retries) { attemptDiscoverBlocks( retries ) {
return new Promise((resolve, reject) => { return new Promise( ( resolve, reject ) => {
this.discoverBlocks().then(found => { this.discoverBlocks().then( ( found ) => {
if (!found && retries > 0) { if ( ! found && retries > 0 ) {
setTimeout(() => { setTimeout( () => {
this.attemptDiscoverBlocks(retries - 1).then(resolve); this.attemptDiscoverBlocks( retries - 1 ).then(
}, 2000); // Wait 2 seconds before retrying resolve
);
}, 2000 ); // Wait 2 seconds before retrying
} else { } else {
resolve(); resolve();
} }
}); } );
}); } );
} }
discoverBlocks() { discoverBlocks() {
return new Promise((resolve) => { return new Promise( ( resolve ) => {
const elements = document.querySelectorAll('.ppcp-messages'); const elements = document.querySelectorAll( '.ppcp-messages' );
if (elements.length === 0) { if ( elements.length === 0 ) {
resolve(false); resolve( false );
return; return;
} }
Array.from(elements).forEach(blockElement => { Array.from( elements ).forEach( ( blockElement ) => {
if (!blockElement.id) { if ( ! blockElement.id ) {
blockElement.id = `ppcp-message-${Math.random().toString(36).substr(2, 9)}`; // Ensure each block has a unique ID blockElement.id = `ppcp-message-${ Math.random()
.toString( 36 )
.substr( 2, 9 ) }`; // Ensure each block has a unique ID
} }
const config = {wrapper: '#' + blockElement.id}; const config = { wrapper: '#' + blockElement.id };
if (!blockElement.getAttribute('data-pp-placement')) { if ( ! blockElement.getAttribute( 'data-pp-placement' ) ) {
config.placement = this.gateway.messages.placement; config.placement = this.gateway.messages.placement;
} }
this.renderers.push(new MessageRenderer(config)); this.renderers.push( new MessageRenderer( config ) );
}); } );
resolve(true); resolve( true );
}); } );
} }
shouldShow(renderer) { shouldShow( renderer ) {
if (this.gateway.messages.is_hidden === true) { if ( this.gateway.messages.is_hidden === true ) {
return false; return false;
} }
const eventData = {result: true} const eventData = { result: true };
jQuery(document.body).trigger('ppcp_should_show_messages', [eventData, renderer.config.wrapper]); jQuery( document.body ).trigger( 'ppcp_should_show_messages', [
eventData,
renderer.config.wrapper,
] );
return eventData.result; return eventData.result;
} }
render() { render() {
this.renderers.forEach(renderer => { this.renderers.forEach( ( renderer ) => {
const shouldShow = this.shouldShow(renderer); const shouldShow = this.shouldShow( renderer );
setVisible(renderer.config.wrapper, shouldShow); setVisible( renderer.config.wrapper, shouldShow );
if (!shouldShow) { if ( ! shouldShow ) {
return; return;
} }
if (!renderer.shouldRender()) { if ( ! renderer.shouldRender() ) {
return; return;
} }
renderer.renderWithAmount(this.lastAmount); renderer.renderWithAmount( this.lastAmount );
}); } );
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
class Product { class Product {
constructor( id, quantity, variations, extra ) {
constructor(id, quantity, variations, extra) {
this.id = id; this.id = id;
this.quantity = quantity; this.quantity = quantity;
this.variations = variations; this.variations = variations;
@ -8,11 +7,11 @@ class Product {
} }
data() { data() {
return { return {
id:this.id, id: this.id,
quantity: this.quantity, quantity: this.quantity,
variations: this.variations, variations: this.variations,
extra: this.extra, extra: this.extra,
} };
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
export const cardFieldStyles = (field) => { export const cardFieldStyles = ( field ) => {
const allowedProperties = [ const allowedProperties = [
'appearance', 'appearance',
'color', 'color',
@ -37,14 +37,14 @@ export const cardFieldStyles = (field) => {
'-webkit-transition', '-webkit-transition',
]; ];
const stylesRaw = window.getComputedStyle(field); const stylesRaw = window.getComputedStyle( field );
const styles = {}; const styles = {};
Object.values(stylesRaw).forEach((prop) => { Object.values( stylesRaw ).forEach( ( prop ) => {
if (!stylesRaw[prop] || !allowedProperties.includes(prop)) { if ( ! stylesRaw[ prop ] || ! allowedProperties.includes( prop ) ) {
return; return;
} }
styles[prop] = '' + stylesRaw[prop]; styles[ prop ] = '' + stylesRaw[ prop ];
}); } );
return styles; return styles;
} };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,6 @@ const DEFAULT_TRIGGER_ELEMENT_SELECTOR = '.woocommerce-checkout-payment';
* @property {boolean} isVisible - Whether the triggerElement is visible. * @property {boolean} isVisible - Whether the triggerElement is visible.
*/ */
class MultistepCheckoutHelper { class MultistepCheckoutHelper {
/** /**
* Selector that defines the HTML element we are waiting to become visible. * Selector that defines the HTML element we are waiting to become visible.
* @type {string} * @type {string}
@ -43,46 +42,47 @@ class MultistepCheckoutHelper {
* @param {string} formSelector - Selector of the checkout form * @param {string} formSelector - Selector of the checkout form
* @param {string} triggerElementSelector - Optional. Selector of the dependant element. * @param {string} triggerElementSelector - Optional. Selector of the dependant element.
*/ */
constructor(formSelector, triggerElementSelector = '') { constructor( formSelector, triggerElementSelector = '' ) {
this.#formSelector = formSelector; this.#formSelector = formSelector;
this.#triggerElementSelector = triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR; this.#triggerElementSelector =
triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR;
this.#intervalId = false; this.#intervalId = false;
/* /*
Start the visibility checker after a brief delay. This allows eventual multistep plugins to Start the visibility checker after a brief delay. This allows eventual multistep plugins to
dynamically prepare the checkout page, so we can decide whether this helper is needed. dynamically prepare the checkout page, so we can decide whether this helper is needed.
*/ */
setTimeout(() => { setTimeout( () => {
if (this.form && !this.isVisible) { if ( this.form && ! this.isVisible ) {
this.start(); this.start();
} }
}, 250); }, 250 );
} }
/** /**
* The checkout form element. * The checkout form element.
* @returns {Element|null} - Form element or null. * @return {Element|null} - Form element or null.
*/ */
get form() { get form() {
return document.querySelector(this.#formSelector); return document.querySelector( this.#formSelector );
} }
/** /**
* The element which must be visible before payment buttons should be initialized. * The element which must be visible before payment buttons should be initialized.
* @returns {Element|null} - Trigger element or null. * @return {Element|null} - Trigger element or null.
*/ */
get triggerElement() { get triggerElement() {
return this.form?.querySelector(this.#triggerElementSelector); return this.form?.querySelector( this.#triggerElementSelector );
} }
/** /**
* Checks the visibility of the payment button wrapper. * Checks the visibility of the payment button wrapper.
* @returns {boolean} - returns boolean value on the basis of visibility of element. * @return {boolean} - returns boolean value on the basis of visibility of element.
*/ */
get isVisible() { get isVisible() {
const box = this.triggerElement?.getBoundingClientRect(); const box = this.triggerElement?.getBoundingClientRect();
return !!(box && box.width && box.height); return !! ( box && box.width && box.height );
} }
/** /**
@ -91,7 +91,10 @@ class MultistepCheckoutHelper {
*/ */
start() { start() {
this.stop(); this.stop();
this.#intervalId = setInterval(() => this.checkElement(), this.#intervalTime); this.#intervalId = setInterval(
() => this.checkElement(),
this.#intervalTime
);
} }
/** /**
@ -99,8 +102,8 @@ class MultistepCheckoutHelper {
* Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist. * Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist.
*/ */
stop() { stop() {
if (this.#intervalId) { if ( this.#intervalId ) {
clearInterval(this.#intervalId); clearInterval( this.#intervalId );
this.#intervalId = false; this.#intervalId = false;
} }
} }
@ -110,7 +113,7 @@ class MultistepCheckoutHelper {
* If visible, it initialises the payment buttons and stops the observation. * If visible, it initialises the payment buttons and stops the observation.
*/ */
checkElement() { checkElement() {
if (this.isVisible) { if ( this.isVisible ) {
refreshButtons(); refreshButtons();
this.stop(); this.stop();
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,13 +6,10 @@ import merge from 'deepmerge';
class PreviewButton { class PreviewButton {
/** /**
* @param {string} selector - CSS ID of the wrapper, including the `#` * @param {string} selector - CSS ID of the wrapper, including the `#`
* @param {object} apiConfig - PayPal configuration object; retrieved via a * @param {Object} apiConfig - PayPal configuration object; retrieved via a
* widgetBuilder API method * widgetBuilder API method
*/ */
constructor({ constructor( { selector, apiConfig } ) {
selector,
apiConfig,
}) {
this.apiConfig = apiConfig; this.apiConfig = apiConfig;
this.defaultAttributes = {}; this.defaultAttributes = {};
this.buttonConfig = {}; this.buttonConfig = {};
@ -32,10 +29,10 @@ class PreviewButton {
* @return {jQuery} Always a single jQuery element with the new DOM node. * @return {jQuery} Always a single jQuery element with the new DOM node.
*/ */
createNewWrapper() { createNewWrapper() {
const previewId = this.selector.replace('#', ''); const previewId = this.selector.replace( '#', '' );
const previewClass = 'ppcp-button-apm'; const previewClass = 'ppcp-button-apm';
return jQuery(`<div id='${previewId}' class='${previewClass}'>`); return jQuery( `<div id='${ previewId }' class='${ previewClass }'>` );
} }
/** /**
@ -43,9 +40,10 @@ class PreviewButton {
* When the button is dynamic, it will reflect current form values. A static button always * When the button is dynamic, it will reflect current form values. A static button always
* uses the settings that were provided via PHP. * uses the settings that were provided via PHP.
* *
* @param state
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
setDynamic(state) { setDynamic( state ) {
this.isDynamic = state; this.isDynamic = state;
return this; return this;
} }
@ -53,10 +51,11 @@ class PreviewButton {
/** /**
* Sets server-side configuration for the button. * Sets server-side configuration for the button.
* *
* @param config
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
setButtonConfig(config) { setButtonConfig( config ) {
this.buttonConfig = merge(this.defaultAttributes, config); this.buttonConfig = merge( this.defaultAttributes, config );
this.buttonConfig.button.wrapper = this.selector; this.buttonConfig.button.wrapper = this.selector;
return this; return this;
@ -65,10 +64,11 @@ class PreviewButton {
/** /**
* Updates the button configuration with current details from the form. * Updates the button configuration with current details from the form.
* *
* @param config
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
setPpcpConfig(config) { setPpcpConfig( config ) {
this.ppcpConfig = merge({}, config); this.ppcpConfig = merge( {}, config );
return this; return this;
} }
@ -76,17 +76,22 @@ class PreviewButton {
/** /**
* Merge form details into the config object for preview. * Merge form details into the config object for preview.
* Mutates the previewConfig object; no return value. * Mutates the previewConfig object; no return value.
* @param previewConfig
* @param formConfig
*/ */
dynamicPreviewConfig(previewConfig, formConfig) { dynamicPreviewConfig( previewConfig, formConfig ) {
// Implement in derived class. // Implement in derived class.
} }
/** /**
* Responsible for creating the actual payment button preview. * Responsible for creating the actual payment button preview.
* Called by the `render()` method, after the wrapper DOM element is ready. * Called by the `render()` method, after the wrapper DOM element is ready.
* @param previewConfig
*/ */
createButton(previewConfig) { createButton( previewConfig ) {
throw new Error('The "createButton" method must be implemented by the derived class'); throw new Error(
'The "createButton" method must be implemented by the derived class'
);
} }
/** /**
@ -95,40 +100,47 @@ class PreviewButton {
*/ */
render() { render() {
// The APM button is disabled and cannot be enabled on the current page: Do not render it. // The APM button is disabled and cannot be enabled on the current page: Do not render it.
if (!this.isDynamic && !this.buttonConfig.is_enabled) { if ( ! this.isDynamic && ! this.buttonConfig.is_enabled ) {
return; return;
} }
if (!this.domWrapper) { if ( ! this.domWrapper ) {
if (!this.wrapper) { if ( ! this.wrapper ) {
console.error('Skip render, button is not configured yet'); console.error( 'Skip render, button is not configured yet' );
return; return;
} }
this.domWrapper = this.createNewWrapper(); this.domWrapper = this.createNewWrapper();
this.domWrapper.insertAfter(this.wrapper); this.domWrapper.insertAfter( this.wrapper );
} else { } else {
this.domWrapper.empty().show(); this.domWrapper.empty().show();
} }
this.isVisible = true; this.isVisible = true;
const previewButtonConfig = merge({}, this.buttonConfig); const previewButtonConfig = merge( {}, this.buttonConfig );
const previewPpcpConfig = this.isDynamic ? merge({}, this.ppcpConfig) : {}; const previewPpcpConfig = this.isDynamic
? merge( {}, this.ppcpConfig )
: {};
previewButtonConfig.button.wrapper = this.selector; previewButtonConfig.button.wrapper = this.selector;
this.dynamicPreviewConfig(previewButtonConfig, previewPpcpConfig); this.dynamicPreviewConfig( previewButtonConfig, previewPpcpConfig );
/* /*
* previewButtonConfig.button.wrapper must be different from this.ppcpConfig.button.wrapper! * previewButtonConfig.button.wrapper must be different from this.ppcpConfig.button.wrapper!
* If both selectors point to the same element, an infinite loop is triggered. * If both selectors point to the same element, an infinite loop is triggered.
*/ */
const buttonWrapper = previewButtonConfig.button.wrapper.replace(/^#/, ''); const buttonWrapper = previewButtonConfig.button.wrapper.replace(
const ppcpWrapper = this.ppcpConfig.button.wrapper.replace(/^#/, ''); /^#/,
''
);
const ppcpWrapper = this.ppcpConfig.button.wrapper.replace( /^#/, '' );
if (buttonWrapper === ppcpWrapper) { if ( buttonWrapper === ppcpWrapper ) {
throw new Error(`[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${buttonWrapper}"`); throw new Error(
`[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${ buttonWrapper }"`
);
} }
this.createButton(previewButtonConfig); this.createButton( previewButtonConfig );
/* /*
* Unfortunately, a hacky way that is required to guarantee that this preview button is * Unfortunately, a hacky way that is required to guarantee that this preview button is
@ -139,13 +151,13 @@ class PreviewButton {
* Using a timeout here will make the button visible again at the end of the current * Using a timeout here will make the button visible again at the end of the current
* event queue. * event queue.
*/ */
setTimeout(() => this.domWrapper.show()); setTimeout( () => this.domWrapper.show() );
} }
remove() { remove() {
this.isVisible = false; this.isVisible = false;
if (this.domWrapper) { if ( this.domWrapper ) {
this.domWrapper.hide().empty(); this.domWrapper.hide().empty();
} }
} }

View file

@ -20,11 +20,7 @@ class PreviewButtonManager {
*/ */
#onInit; #onInit;
constructor({ constructor( { methodName, buttonConfig, defaultAttributes } ) {
methodName,
buttonConfig,
defaultAttributes,
}) {
// Define the payment method name in the derived class. // Define the payment method name in the derived class.
this.methodName = methodName; this.methodName = methodName;
@ -36,12 +32,12 @@ class PreviewButtonManager {
this.apiConfig = null; this.apiConfig = null;
this.apiError = ''; this.apiError = '';
this.#onInit = new Promise(resolve => { this.#onInit = new Promise( ( resolve ) => {
this.#onInitResolver = resolve; this.#onInitResolver = resolve;
}); } );
this.bootstrap = this.bootstrap.bind(this); this.bootstrap = this.bootstrap.bind( this );
this.renderPreview = this.renderPreview.bind(this); this.renderPreview = this.renderPreview.bind( this );
/** /**
* The "configureAllButtons" method applies ppcpConfig to all buttons that were created * The "configureAllButtons" method applies ppcpConfig to all buttons that were created
@ -52,7 +48,10 @@ class PreviewButtonManager {
* buttons, but only a single time, passing in a random button's wrapper-ID; however, * buttons, but only a single time, passing in a random button's wrapper-ID; however,
* that event should always refresh all preview buttons, not only that single button. * that event should always refresh all preview buttons, not only that single button.
*/ */
this._configureAllButtons = debounce(this._configureAllButtons.bind(this), 100); this._configureAllButtons = debounce(
this._configureAllButtons.bind( this ),
100
);
this.registerEventListeners(); this.registerEventListeners();
} }
@ -65,8 +64,10 @@ class PreviewButtonManager {
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
* @return {Promise<{}>} * @return {Promise<{}>}
*/ */
async fetchConfig(payPal) { async fetchConfig( payPal ) {
throw new Error('The "fetchConfig" method must be implemented by the derived class'); throw new Error(
'The "fetchConfig" method must be implemented by the derived class'
);
} }
/** /**
@ -76,8 +77,10 @@ class PreviewButtonManager {
* @param {string} wrapperId - CSS ID of the wrapper element. * @param {string} wrapperId - CSS ID of the wrapper element.
* @return {PreviewButton} * @return {PreviewButton}
*/ */
createButtonInstance(wrapperId) { createButtonInstance( wrapperId ) {
throw new Error('The "createButtonInstance" method must be implemented by the derived class'); throw new Error(
'The "createButtonInstance" method must be implemented by the derived class'
);
} }
/** /**
@ -90,12 +93,14 @@ class PreviewButtonManager {
* @param wrapperId * @param wrapperId
* @return {any} * @return {any}
*/ */
createDummy(wrapperId) { createDummy( wrapperId ) {
const elButton = document.createElement('div'); const elButton = document.createElement( 'div' );
elButton.classList.add('ppcp-button-apm', 'ppcp-button-dummy'); elButton.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' );
elButton.innerHTML = `<span>${this.apiError ?? 'Not Available'}</span>`; elButton.innerHTML = `<span>${
this.apiError ?? 'Not Available'
}</span>`;
document.querySelector(wrapperId).appendChild(elButton); document.querySelector( wrapperId ).appendChild( elButton );
const instDummy = { const instDummy = {
setDynamic: () => instDummy, setDynamic: () => instDummy,
@ -108,20 +113,28 @@ class PreviewButtonManager {
} }
registerEventListeners() { registerEventListeners() {
jQuery(document).one('DOMContentLoaded', this.bootstrap); jQuery( document ).one( 'DOMContentLoaded', this.bootstrap );
// General event that all APM buttons react to. // General event that all APM buttons react to.
jQuery(document).on('ppcp_paypal_render_preview', this.renderPreview); jQuery( document ).on(
'ppcp_paypal_render_preview',
this.renderPreview
);
// Specific event to only (re)render the current APM button type. // Specific event to only (re)render the current APM button type.
jQuery(document).on(`ppcp_paypal_render_preview_${this.methodName}`, this.renderPreview); jQuery( document ).on(
`ppcp_paypal_render_preview_${ this.methodName }`,
this.renderPreview
);
} }
/** /**
* Output an error message to the console, with a module-specific prefix. * Output an error message to the console, with a module-specific prefix.
* @param message
* @param {...any} args
*/ */
error(message, ...args) { error( message, ...args ) {
console.error(`${this.methodName} ${message}`, ...args); console.error( `${ this.methodName } ${ message }`, ...args );
} }
/** /**
@ -130,7 +143,9 @@ class PreviewButtonManager {
* style settings that were provided from server-side. * style settings that were provided from server-side.
*/ */
isDynamic() { isDynamic() {
return !!document.querySelector(`[data-ppcp-apm-name="${this.methodName}"]`); return !! document.querySelector(
`[data-ppcp-apm-name="${ this.methodName }"]`
);
} }
/** /**
@ -144,48 +159,56 @@ class PreviewButtonManager {
const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds. const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds.
const RESOLVE_INTERVAL = 200; const RESOLVE_INTERVAL = 200;
if (!this.buttonConfig?.sdk_url || !widgetBuilder) { if ( ! this.buttonConfig?.sdk_url || ! widgetBuilder ) {
this.error('Button could not be configured.'); this.error( 'Button could not be configured.' );
return; return;
} }
// This is a localization object of "gateway-settings.js". If it's missing, the script was // This is a localization object of "gateway-settings.js". If it's missing, the script was
// not loaded. // not loaded.
if (!window.PayPalCommerceGatewaySettings) { if ( ! window.PayPalCommerceGatewaySettings ) {
this.error( this.error(
'PayPal settings are not fully loaded. Please clear the cache and reload the page.'); 'PayPal settings are not fully loaded. Please clear the cache and reload the page.'
);
return; return;
} }
// A helper function that clears the interval and resolves/rejects the promise. // A helper function that clears the interval and resolves/rejects the promise.
const resolveOrReject = (resolve, reject, id, success = true) => { const resolveOrReject = ( resolve, reject, id, success = true ) => {
clearInterval(id); clearInterval( id );
success ? resolve() : reject('Timeout while waiting for widgetBuilder.paypal'); success
? resolve()
: reject( 'Timeout while waiting for widgetBuilder.paypal' );
}; };
// Wait for the PayPal SDK to be ready. // Wait for the PayPal SDK to be ready.
const paypalPromise = new Promise((resolve, reject) => { const paypalPromise = new Promise( ( resolve, reject ) => {
let elapsedTime = 0; let elapsedTime = 0;
const id = setInterval(() => { const id = setInterval( () => {
if (widgetBuilder.paypal) { if ( widgetBuilder.paypal ) {
resolveOrReject(resolve, reject, id); resolveOrReject( resolve, reject, id );
} else if (elapsedTime >= MAX_WAIT_TIME) { } else if ( elapsedTime >= MAX_WAIT_TIME ) {
resolveOrReject(resolve, reject, id, false); resolveOrReject( resolve, reject, id, false );
} }
elapsedTime += RESOLVE_INTERVAL; elapsedTime += RESOLVE_INTERVAL;
}, RESOLVE_INTERVAL); }, RESOLVE_INTERVAL );
}); } );
// Load the custom SDK script. // Load the custom SDK script.
const customScriptPromise = loadCustomScript({ url: this.buttonConfig.sdk_url }); const customScriptPromise = loadCustomScript( {
url: this.buttonConfig.sdk_url,
} );
// Wait for both promises to resolve before continuing. // Wait for both promises to resolve before continuing.
await Promise await Promise.all( [ customScriptPromise, paypalPromise ] ).catch(
.all([customScriptPromise, paypalPromise]) ( err ) => {
.catch(err => { console.log(
console.log(`Failed to load ${this.methodName} dependencies:`, err); `Failed to load ${ this.methodName } dependencies:`,
}); err
);
}
);
/* /*
The fetchConfig method requires two objects to succeed: The fetchConfig method requires two objects to succeed:
@ -193,8 +216,8 @@ class PreviewButtonManager {
(b) the `widgetBuilder.paypal` object (b) the `widgetBuilder.paypal` object
*/ */
try { try {
this.apiConfig = await this.fetchConfig(widgetBuilder.paypal); this.apiConfig = await this.fetchConfig( widgetBuilder.paypal );
} catch (error) { } catch ( error ) {
this.apiConfig = null; this.apiConfig = null;
} }
@ -210,23 +233,23 @@ class PreviewButtonManager {
* @param ev - Ignored * @param ev - Ignored
* @param ppcpConfig - The button settings for the preview. * @param ppcpConfig - The button settings for the preview.
*/ */
renderPreview(ev, ppcpConfig) { renderPreview( ev, ppcpConfig ) {
const id = ppcpConfig.button.wrapper; const id = ppcpConfig.button.wrapper;
if (!id) { if ( ! id ) {
this.error('Button did not provide a wrapper ID', ppcpConfig); this.error( 'Button did not provide a wrapper ID', ppcpConfig );
return; return;
} }
if (!this.shouldInsertPreviewButton(id)) { if ( ! this.shouldInsertPreviewButton( id ) ) {
return; return;
} }
if (!this.buttons[id]) { if ( ! this.buttons[ id ] ) {
this._addButton(id, ppcpConfig); this._addButton( id, ppcpConfig );
} else { } else {
// This is a debounced method, that fires after 100ms. // This is a debounced method, that fires after 100ms.
this._configureAllButtons(ppcpConfig); this._configureAllButtons( ppcpConfig );
} }
} }
@ -238,30 +261,33 @@ class PreviewButtonManager {
* @param {string} previewId - ID of the inner preview box container. * @param {string} previewId - ID of the inner preview box container.
* @return {boolean} True if the box is eligible for the preview button, false otherwise. * @return {boolean} True if the box is eligible for the preview button, false otherwise.
*/ */
shouldInsertPreviewButton(previewId) { shouldInsertPreviewButton( previewId ) {
const container = document.querySelector(previewId); const container = document.querySelector( previewId );
const box = container.closest('.ppcp-preview'); const box = container.closest( '.ppcp-preview' );
const limit = box.dataset.ppcpPreviewBlock ?? 'all'; const limit = box.dataset.ppcpPreviewBlock ?? 'all';
return ('all' === limit) || (this.methodName === limit); return 'all' === limit || this.methodName === limit;
} }
/** /**
* Applies a new configuration to an existing preview button. * Applies a new configuration to an existing preview button.
* @param id
* @param ppcpConfig
*/ */
_configureButton(id, ppcpConfig) { _configureButton( id, ppcpConfig ) {
this.buttons[id] this.buttons[ id ]
.setDynamic(this.isDynamic()) .setDynamic( this.isDynamic() )
.setPpcpConfig(ppcpConfig) .setPpcpConfig( ppcpConfig )
.render(); .render();
} }
/** /**
* Apples the provided configuration to all existing preview buttons. * Apples the provided configuration to all existing preview buttons.
* @param ppcpConfig
*/ */
_configureAllButtons(ppcpConfig) { _configureAllButtons( ppcpConfig ) {
Object.entries(this.buttons).forEach(([id, button]) => { Object.entries( this.buttons ).forEach( ( [ id, button ] ) => {
this._configureButton(id, { this._configureButton( id, {
...ppcpConfig, ...ppcpConfig,
button: { button: {
...ppcpConfig.button, ...ppcpConfig.button,
@ -270,31 +296,35 @@ class PreviewButtonManager {
// Fix the selector, to avoid unintentionally hidden preview buttons. // Fix the selector, to avoid unintentionally hidden preview buttons.
wrapper: button.wrapper, wrapper: button.wrapper,
}, },
}); } );
}); } );
} }
/** /**
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves. * Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
* @param id
* @param ppcpConfig
*/ */
_addButton(id, ppcpConfig) { _addButton( id, ppcpConfig ) {
const createButton = () => { const createButton = () => {
if (!this.buttons[id]) { if ( ! this.buttons[ id ] ) {
let newInst; let newInst;
if (this.apiConfig && 'object' === typeof this.apiConfig) { if ( this.apiConfig && 'object' === typeof this.apiConfig ) {
newInst = this.createButtonInstance(id).setButtonConfig(this.buttonConfig); newInst = this.createButtonInstance( id ).setButtonConfig(
this.buttonConfig
);
} else { } else {
newInst = this.createDummy(id); newInst = this.createDummy( id );
} }
this.buttons[id] = newInst; this.buttons[ id ] = newInst;
} }
this._configureButton(id, ppcpConfig); this._configureButton( id, ppcpConfig );
}; };
if (this.#onInit) { if ( this.#onInit ) {
this.#onInit.then(createButton); this.#onInit.then( createButton );
} else { } else {
createButton(); createButton();
} }
@ -306,10 +336,14 @@ class PreviewButtonManager {
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
renderButtons() { renderButtons() {
if (this.isEnabled) { if ( this.isEnabled ) {
Object.values(this.buttons).forEach(button => button.render()); Object.values( this.buttons ).forEach( ( button ) =>
button.render()
);
} else { } else {
Object.values(this.buttons).forEach(button => button.remove()); Object.values( this.buttons ).forEach( ( button ) =>
button.remove()
);
} }
return this; return this;
@ -321,7 +355,7 @@ class PreviewButtonManager {
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
enable() { enable() {
if (!this.isEnabled) { if ( ! this.isEnabled ) {
this.isEnabled = true; this.isEnabled = true;
this.renderButtons(); this.renderButtons();
} }
@ -335,7 +369,7 @@ class PreviewButtonManager {
* @return {this} Reference to self, for chaining. * @return {this} Reference to self, for chaining.
*/ */
disable() { disable() {
if (!this.isEnabled) { if ( ! this.isEnabled ) {
this.isEnabled = false; this.isEnabled = false;
this.renderButtons(); this.renderButtons();
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -373,7 +373,16 @@ class CompatModule implements ModuleInterface {
// W3 Total Cache. // W3 Total Cache.
add_filter( add_filter(
'w3tc_minify_js_do_tag_minification', 'w3tc_minify_js_do_tag_minification',
function( bool $do_tag_minification, string $script_tag, string $file ) { /**
* Filter callback for 'w3tc_minify_js_do_tag_minification'.
*
* @param bool $do_tag_minification Whether to do tag minification.
* @param string $script_tag The script tag.
* @param string|null $file The file path.
* @return bool Whether to do tag minification.
* @psalm-suppress MissingClosureParamType
*/
function( bool $do_tag_minification, string $script_tag, $file ) {
if ( $file && strpos( $file, 'ppcp' ) !== false ) { if ( $file && strpos( $file, 'ppcp' ) !== false ) {
return false; return false;
} }

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