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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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