Run autofix code style

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,13 @@
import ContextHandlerFactory from "./Context/ContextHandlerFactory";
import {createAppleErrors} from "./Helper/applePayError";
import ContextHandlerFactory from './Context/ContextHandlerFactory';
import { createAppleErrors } from './Helper/applePayError';
import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
import FormValidator from "../../../ppcp-button/resources/js/modules/Helper/FormValidator";
import FormValidator from '../../../ppcp-button/resources/js/modules/Helper/FormValidator';
import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/ApmButtons";
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons';
class ApplepayButton {
constructor( context, externalHandler, buttonConfig, ppcpConfig ) {
apmButtonsInit( ppcpConfig );
@ -27,9 +26,11 @@ class ApplepayButton {
this.ppcpConfig
);
this.updatedContactInfo = []
this.selectedShippingMethod = []
this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value || buttonConfig.nonce
this.updatedContactInfo = [];
this.selectedShippingMethod = [];
this.nonce =
document.getElementById( 'woocommerce-process-checkout-nonce' )
?.value || buttonConfig.nonce;
// Stores initialization data sent to the button.
this.initialPaymentRequest = null;
@ -41,7 +42,7 @@ class ApplepayButton {
if ( this.buttonConfig.is_debug ) {
//console.log('[ApplePayButton]', ...arguments);
}
}
};
this.refreshContextData();
@ -66,21 +67,28 @@ class ApplepayButton {
this.initEventHandlers();
this.isInitialized = true;
this.applePayConfig = config;
this.isEligible = (this.applePayConfig.isEligible && window.ApplePaySession) || this.buttonConfig.is_admin;
this.isEligible =
( this.applePayConfig.isEligible && window.ApplePaySession ) ||
this.buttonConfig.is_admin;
if ( this.isEligible ) {
this.fetchTransactionInfo().then( () => {
this.addButton();
const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper;
const id = "#apple-" + this.buttonConfig.button.wrapper;
const id_minicart =
'#apple-' + this.buttonConfig.button.mini_cart_wrapper;
const id = '#apple-' + this.buttonConfig.button.wrapper;
if ( this.context === 'mini-cart' ) {
document.querySelector(id_minicart)?.addEventListener('click', (evt) => {
document
.querySelector( id_minicart )
?.addEventListener( 'click', ( evt ) => {
evt.preventDefault();
this.onButtonClick();
} );
} else {
document.querySelector(id)?.addEventListener('click', (evt) => {
document
.querySelector( id )
?.addEventListener( 'click', ( evt ) => {
evt.preventDefault();
this.onButtonClick();
} );
@ -110,12 +118,12 @@ class ApplepayButton {
* Returns configurations relative to this button context.
*/
contextConfig() {
let config = {
const config = {
wrapper: this.buttonConfig.button.wrapper,
ppcpStyle: this.ppcpConfig.button.style,
buttonStyle: this.buttonConfig.button.style,
ppcpButtonWrapper: this.ppcpConfig.button.wrapper
}
ppcpButtonWrapper: this.ppcpConfig.button.wrapper,
};
if ( this.context === 'mini-cart' ) {
config.wrapper = this.buttonConfig.button.mini_cart_wrapper;
@ -124,8 +132,11 @@ class ApplepayButton {
config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper;
}
if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) {
config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway-paypal';
if (
[ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1
) {
config.ppcpButtonWrapper =
'#express-payment-method-ppcp-gateway-paypal';
}
return config;
@ -136,7 +147,9 @@ class ApplepayButton {
const wrapper_id = '#' + wrapper;
if ( wrapper_id === ppcpButtonWrapper ) {
throw new Error(`[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${wrapper_id}"`);
throw new Error(
`[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper_id }"`
);
}
const syncButtonVisibility = () => {
@ -146,20 +159,27 @@ class ApplepayButton {
const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper );
setVisible( wrapper_id, $ppcpButtonWrapper.is( ':visible' ) );
setEnabled(wrapper_id, !$ppcpButtonWrapper.hasClass('ppcp-disabled'));
}
setEnabled(
wrapper_id,
! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' )
);
};
jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => {
jQuery( document ).on(
'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
( ev, data ) => {
if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) {
syncButtonVisibility();
}
});
}
);
syncButtonVisibility();
}
/**
* Starts an ApplePay session.
* @param paymentRequest
*/
applePaySession( paymentRequest ) {
this.log( 'applePaySession', paymentRequest );
@ -167,8 +187,10 @@ class ApplepayButton {
session.begin();
if ( this.shouldRequireShippingInButton() ) {
session.onshippingmethodselected = this.onShippingMethodSelected(session);
session.onshippingcontactselected = this.onShippingContactSelected(session);
session.onshippingmethodselected =
this.onShippingMethodSelected( session );
session.onshippingcontactselected =
this.onShippingContactSelected( session );
}
session.onvalidatemerchant = this.onValidateMerchant( session );
session.onpaymentauthorized = this.onPaymentAuthorized( session );
@ -187,7 +209,7 @@ class ApplepayButton {
const type = this.buttonConfig.button.type;
const language = this.buttonConfig.button.lang;
const color = this.buttonConfig.button.color;
const id = "apple-" + wrapper;
const id = 'apple-' + wrapper;
if ( appleContainer ) {
appleContainer.innerHTML = `<apple-pay-button id="${ id }" buttonstyle="${ color }" type="${ type }" locale="${ language }">`;
@ -197,8 +219,11 @@ class ApplepayButton {
$wrapper.addClass( 'ppcp-button-' + ppcpStyle.shape );
if ( ppcpStyle.height ) {
$wrapper.css('--apple-pay-button-height', `${ppcpStyle.height}px`)
$wrapper.css('height', `${ppcpStyle.height}px`)
$wrapper.css(
'--apple-pay-button-height',
`${ ppcpStyle.height }px`
);
$wrapper.css( 'height', `${ ppcpStyle.height }px` );
}
}
@ -224,7 +249,9 @@ class ApplepayButton {
document.querySelector( '.woocommerce-notices-wrapper' )
);
try {
const formData = new FormData(document.querySelector(checkoutFormSelector));
const formData = new FormData(
document.querySelector( checkoutFormSelector )
);
this.formData = Object.fromEntries( formData.entries() );
this.updateRequestDataWithForm( paymentRequest );
@ -235,17 +262,23 @@ class ApplepayButton {
this.log( '=== paymentRequest', paymentRequest );
const session = this.applePaySession( paymentRequest );
const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ?
new FormValidator(
const formValidator =
PayPalCommerceGateway.early_checkout_validation_enabled
? new FormValidator(
PayPalCommerceGateway.ajax.validate_checkout.endpoint,
PayPalCommerceGateway.ajax.validate_checkout.nonce,
) : null;
PayPalCommerceGateway.ajax.validate_checkout.nonce
)
: null;
if ( formValidator ) {
try {
const errors = await formValidator.validate(document.querySelector(checkoutFormSelector));
const errors = await formValidator.validate(
document.querySelector( checkoutFormSelector )
);
if ( errors.length > 0 ) {
errorHandler.messages( errors );
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
jQuery( document.body ).trigger( 'checkout_error', [
errorHandler.currentHtml(),
] );
session.abort();
return;
}
@ -263,31 +296,37 @@ class ApplepayButton {
/**
* If the button should show the shipping fields.
*
* @returns {false|*}
* @return {false|*}
*/
shouldRequireShippingInButton() {
return this.contextHandler.shippingAllowed()
&& this.buttonConfig.product.needShipping
&& (this.context !== 'checkout' || this.shouldUpdateButtonWithFormData());
return (
this.contextHandler.shippingAllowed() &&
this.buttonConfig.product.needShipping &&
( this.context !== 'checkout' ||
this.shouldUpdateButtonWithFormData() )
);
}
/**
* If the button should be updated with the form addresses.
*
* @returns {boolean}
* @return {boolean}
*/
shouldUpdateButtonWithFormData() {
if ( this.context !== 'checkout' ) {
return false;
}
return this.buttonConfig?.preferences?.checkout_data_mode === 'use_applepay';
return (
this.buttonConfig?.preferences?.checkout_data_mode ===
'use_applepay'
);
}
/**
* Indicates how payment completion should be handled if with the context handler default actions.
* Or with ApplePay module specific completion.
*
* @returns {boolean}
* @return {boolean}
*/
shouldCompletePaymentWithContextHandler() {
// Data already handled, ex: PayNow
@ -295,7 +334,10 @@ class ApplepayButton {
return true;
}
// Use WC form data mode in Checkout.
if (this.context === 'checkout' && !this.shouldUpdateButtonWithFormData()) {
if (
this.context === 'checkout' &&
! this.shouldUpdateButtonWithFormData()
) {
return true;
}
return false;
@ -303,6 +345,7 @@ class ApplepayButton {
/**
* Updates ApplePay paymentRequest with form data.
* @param paymentRequest
*/
updateRequestDataWithForm( paymentRequest ) {
if ( ! this.shouldUpdateButtonWithFormData() ) {
@ -310,7 +353,9 @@ class ApplepayButton {
}
// Add billing address.
paymentRequest.billingContact = this.fillBillingContact(this.formData);
paymentRequest.billingContact = this.fillBillingContact(
this.formData
);
// Add custom data.
// "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder().
@ -321,7 +366,9 @@ class ApplepayButton {
}
// Add shipping address.
paymentRequest.shippingContact = this.fillShippingContact(this.formData);
paymentRequest.shippingContact = this.fillShippingContact(
this.formData
);
// Get shipping methods.
const rate = this.transactionInfo.chosenShippingMethods[ 0 ];
@ -331,10 +378,10 @@ class ApplepayButton {
for ( const shippingPackage of this.transactionInfo.shippingPackages ) {
if ( rate === shippingPackage.id ) {
const shippingMethod = {
'label' : shippingPackage.label,
'detail' : '',
'amount' : shippingPackage.cost_str,
'identifier' : shippingPackage.id,
label: shippingPackage.label,
detail: '',
amount: shippingPackage.cost_str,
identifier: shippingPackage.id,
};
// Remember this shipping method as the selected one.
@ -349,10 +396,10 @@ class ApplepayButton {
for ( const shippingPackage of this.transactionInfo.shippingPackages ) {
if ( rate !== shippingPackage.id ) {
paymentRequest.shippingMethods.push( {
'label' : shippingPackage.label,
'detail' : '',
'amount' : shippingPackage.cost_str,
'identifier' : shippingPackage.id,
label: shippingPackage.label,
detail: '',
amount: shippingPackage.cost_str,
identifier: shippingPackage.id,
} );
}
}
@ -360,19 +407,26 @@ class ApplepayButton {
// Store for reuse in case this data is not provided by ApplePay on authorization.
this.initialPaymentRequest = paymentRequest;
this.log('=== paymentRequest.shippingMethods', paymentRequest.shippingMethods);
this.log(
'=== paymentRequest.shippingMethods',
paymentRequest.shippingMethods
);
}
paymentRequest() {
const applepayConfig = this.applePayConfig
const buttonConfig = this.buttonConfig
let baseRequest = {
const applepayConfig = this.applePayConfig;
const buttonConfig = this.buttonConfig;
const baseRequest = {
countryCode: applepayConfig.countryCode,
merchantCapabilities: applepayConfig.merchantCapabilities,
supportedNetworks: applepayConfig.supportedNetworks,
requiredShippingContactFields: ["postalAddress", "email", "phone"],
requiredBillingContactFields: ["postalAddress"], // ApplePay does not implement billing email and phone fields.
}
requiredShippingContactFields: [
'postalAddress',
'email',
'phone',
],
requiredBillingContactFields: [ 'postalAddress' ], // ApplePay does not implement billing email and phone fields.
};
if ( ! this.shouldRequireShippingInButton() ) {
if ( this.shouldCompletePaymentWithContextHandler() ) {
@ -380,7 +434,10 @@ class ApplepayButton {
baseRequest.requiredShippingContactFields = [];
} else {
// Minimum data required for order creation.
baseRequest.requiredShippingContactFields = ["email", "phone"];
baseRequest.requiredShippingContactFields = [
'email',
'phone',
];
}
}
@ -388,9 +445,9 @@ class ApplepayButton {
paymentRequest.currencyCode = buttonConfig.shop.currencyCode;
paymentRequest.total = {
label: buttonConfig.shop.totalLabel,
type: "final",
type: 'final',
amount: this.transactionInfo.totalPrice,
}
};
return paymentRequest;
}
@ -399,7 +456,8 @@ class ApplepayButton {
switch ( this.context ) {
case 'product':
// Refresh product data that makes the price change.
this.productQuantity = document.querySelector('input.qty')?.value;
this.productQuantity =
document.querySelector( 'input.qty' )?.value;
this.products = this.contextHandler.products();
this.log( 'Products updated', this.products );
break;
@ -415,12 +473,16 @@ class ApplepayButton {
return ( applePayValidateMerchantEvent ) => {
this.log( 'onvalidatemerchant call' );
widgetBuilder.paypal.Applepay().validateMerchant({
validationUrl: applePayValidateMerchantEvent.validationURL
widgetBuilder.paypal
.Applepay()
.validateMerchant( {
validationUrl: applePayValidateMerchantEvent.validationURL,
} )
.then(validateResult => {
.then( ( validateResult ) => {
this.log( 'onvalidatemerchant ok' );
session.completeMerchantValidation(validateResult.merchantSession);
session.completeMerchantValidation(
validateResult.merchantSession
);
//call backend to update validation to true
jQuery.ajax( {
url: this.buttonConfig.ajax_url,
@ -429,10 +491,10 @@ class ApplepayButton {
action: 'ppcp_validate',
validation: true,
'woocommerce-process-checkout-nonce': this.nonce,
}
},
} );
} )
})
.catch(validateError => {
.catch( ( validateError ) => {
this.log( 'onvalidatemerchant error', validateError );
console.error( validateError );
//call backend to update validation to false
@ -443,7 +505,7 @@ class ApplepayButton {
action: 'ppcp_validate',
validation: false,
'woocommerce-process-checkout-nonce': this.nonce,
}
},
} );
this.log( 'onvalidatemerchant session abort' );
session.abort();
@ -462,18 +524,25 @@ class ApplepayButton {
jQuery.ajax( {
url: ajax_url,
method: 'POST',
data: data,
success: (applePayShippingMethodUpdate, textStatus, jqXHR) => {
data,
success: (
applePayShippingMethodUpdate,
textStatus,
jqXHR
) => {
this.log( 'onshippingmethodselected ok' );
let response = applePayShippingMethodUpdate.data;
const response = applePayShippingMethodUpdate.data;
if ( applePayShippingMethodUpdate.success === false ) {
response.errors = createAppleErrors( response.errors );
}
this.selectedShippingMethod = event.shippingMethod;
// Sort the response shipping methods, so that the selected shipping method is the first one.
response.newShippingMethods = response.newShippingMethods.sort((a, b) => {
if (a.label === this.selectedShippingMethod.label) {
response.newShippingMethods =
response.newShippingMethods.sort( ( a, b ) => {
if (
a.label === this.selectedShippingMethod.label
) {
return -1;
}
return 1;
@ -506,16 +575,21 @@ class ApplepayButton {
jQuery.ajax( {
url: ajax_url,
method: 'POST',
data: data,
success: (applePayShippingContactUpdate, textStatus, jqXHR) => {
data,
success: (
applePayShippingContactUpdate,
textStatus,
jqXHR
) => {
this.log( 'onshippingcontactselected ok' );
let response = applePayShippingContactUpdate.data;
const response = applePayShippingContactUpdate.data;
this.updatedContactInfo = event.shippingContact;
if ( applePayShippingContactUpdate.success === false ) {
response.errors = createAppleErrors( response.errors );
}
if ( response.newShippingMethods ) {
this.selectedShippingMethod = response.newShippingMethods[0];
this.selectedShippingMethod =
response.newShippingMethods[ 0 ];
}
session.completeShippingContactSelection( response );
},
@ -537,7 +611,7 @@ class ApplepayButton {
case 'product':
return {
action: 'ppcp_update_shipping_contact',
product_id: product_id,
product_id,
products: JSON.stringify( this.products ),
caller_page: 'productDetail',
product_quantity: this.productQuantity,
@ -566,16 +640,20 @@ class ApplepayButton {
this.refreshContextData();
switch ( this.context ) {
case 'product': return {
case 'product':
return {
action: 'ppcp_update_shipping_method',
shipping_method: event.shippingMethod,
simplified_contact: this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact,
product_id: product_id,
simplified_contact:
this.updatedContactInfo ||
this.initialPaymentRequest.shippingContact ||
this.initialPaymentRequest.billingContact,
product_id,
products: JSON.stringify( this.products ),
caller_page: 'productDetail',
product_quantity: this.productQuantity,
'woocommerce-process-checkout-nonce': this.nonce,
}
};
case 'cart':
case 'checkout':
case 'cart-block':
@ -584,10 +662,13 @@ class ApplepayButton {
return {
action: 'ppcp_update_shipping_method',
shipping_method: event.shippingMethod,
simplified_contact: this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact,
simplified_contact:
this.updatedContactInfo ||
this.initialPaymentRequest.shippingContact ||
this.initialPaymentRequest.billingContact,
caller_page: 'cart',
'woocommerce-process-checkout-nonce': this.nonce,
}
};
}
}
@ -602,27 +683,38 @@ class ApplepayButton {
const processInWooAndCapture = async ( data ) => {
return new Promise( ( resolve, reject ) => {
try {
const billingContact = data.billing_contact || this.initialPaymentRequest.billingContact;
const shippingContact = data.shipping_contact || this.initialPaymentRequest.shippingContact;
const shippingMethod = this.selectedShippingMethod || (this.initialPaymentRequest.shippingMethods || [])[0];
const billingContact =
data.billing_contact ||
this.initialPaymentRequest.billingContact;
const shippingContact =
data.shipping_contact ||
this.initialPaymentRequest.shippingContact;
const shippingMethod =
this.selectedShippingMethod ||
( this.initialPaymentRequest.shippingMethods ||
[] )[ 0 ];
let request_data = {
const request_data = {
action: 'ppcp_create_order',
'caller_page': this.context,
'product_id': this.buttonConfig.product.id ?? null,
'products': JSON.stringify(this.products),
'product_quantity': this.productQuantity ?? null,
'shipping_contact': shippingContact,
'billing_contact': billingContact,
'token': event.payment.token,
'shipping_method': shippingMethod,
caller_page: this.context,
product_id: this.buttonConfig.product.id ?? null,
products: JSON.stringify( this.products ),
product_quantity: this.productQuantity ?? null,
shipping_contact: shippingContact,
billing_contact: billingContact,
token: event.payment.token,
shipping_method: shippingMethod,
'woocommerce-process-checkout-nonce': this.nonce,
'funding_source': 'applepay',
'_wp_http_referer': '/?wc-ajax=update_order_review',
'paypal_order_id': data.paypal_order_id,
funding_source: 'applepay',
_wp_http_referer: '/?wc-ajax=update_order_review',
paypal_order_id: data.paypal_order_id,
};
this.log('onpaymentauthorized request', this.buttonConfig.ajax_url, data);
this.log(
'onpaymentauthorized request',
this.buttonConfig.ajax_url,
data
);
jQuery.ajax( {
url: this.buttonConfig.ajax_url,
@ -631,12 +723,19 @@ class ApplepayButton {
complete: ( jqXHR, textStatus ) => {
this.log( 'onpaymentauthorized complete' );
},
success: (authorizationResult, textStatus, jqXHR) => {
success: (
authorizationResult,
textStatus,
jqXHR
) => {
this.log( 'onpaymentauthorized ok' );
resolve( authorizationResult );
},
error: ( jqXHR, textStatus, errorThrown ) => {
this.log('onpaymentauthorized error', textStatus);
this.log(
'onpaymentauthorized error',
textStatus
);
reject( new Error( errorThrown ) );
},
} );
@ -645,86 +744,135 @@ class ApplepayButton {
console.log( error ); // handle error
}
} );
}
};
let id = await this.contextHandler.createOrder();
const id = await this.contextHandler.createOrder();
this.log('onpaymentauthorized paypal order ID', id, event.payment.token, event.payment.billingContact);
this.log(
'onpaymentauthorized paypal order ID',
id,
event.payment.token,
event.payment.billingContact
);
try {
const confirmOrderResponse = await widgetBuilder.paypal.Applepay().confirmOrder({
const confirmOrderResponse = await widgetBuilder.paypal
.Applepay()
.confirmOrder( {
orderId: id,
token: event.payment.token,
billingContact: event.payment.billingContact,
} );
this.log('onpaymentauthorized confirmOrderResponse', confirmOrderResponse);
this.log(
'onpaymentauthorized confirmOrderResponse',
confirmOrderResponse
);
if (confirmOrderResponse && confirmOrderResponse.approveApplePayPayment) {
if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") {
if (
confirmOrderResponse &&
confirmOrderResponse.approveApplePayPayment
) {
if (
confirmOrderResponse.approveApplePayPayment.status ===
'APPROVED'
) {
try {
if (this.shouldCompletePaymentWithContextHandler()) {
if (
this.shouldCompletePaymentWithContextHandler()
) {
// No shipping, expect immediate capture, ex: PayNow, Checkout with form data.
let approveFailed = false;
await this.contextHandler.approveOrder({
orderID: id
}, { // actions mock object.
restart: () => new Promise((resolve, reject) => {
await this.contextHandler.approveOrder(
{
orderID: id,
},
{
// actions mock object.
restart: () =>
new Promise(
( resolve, reject ) => {
approveFailed = true;
resolve();
}),
order: {
get: () => new Promise((resolve, reject) => {
resolve(null);
})
}
});
),
order: {
get: () =>
new Promise(
( resolve, reject ) => {
resolve( null );
}
),
},
}
);
if ( ! approveFailed ) {
this.log('onpaymentauthorized approveOrder OK');
session.completePayment(ApplePaySession.STATUS_SUCCESS);
this.log(
'onpaymentauthorized approveOrder OK'
);
session.completePayment(
ApplePaySession.STATUS_SUCCESS
);
} else {
this.log('onpaymentauthorized approveOrder FAIL');
session.completePayment(ApplePaySession.STATUS_FAILURE);
this.log(
'onpaymentauthorized approveOrder FAIL'
);
session.completePayment(
ApplePaySession.STATUS_FAILURE
);
session.abort();
console.error( error );
}
} else {
// Default payment.
let data = {
billing_contact: event.payment.billingContact,
shipping_contact: event.payment.shippingContact,
const data = {
billing_contact:
event.payment.billingContact,
shipping_contact:
event.payment.shippingContact,
paypal_order_id: id,
};
let authorizationResult = await processInWooAndCapture(data);
if (authorizationResult.result === "success") {
session.completePayment(ApplePaySession.STATUS_SUCCESS);
window.location.href = authorizationResult.redirect;
const authorizationResult =
await processInWooAndCapture( data );
if (
authorizationResult.result === 'success'
) {
session.completePayment(
ApplePaySession.STATUS_SUCCESS
);
window.location.href =
authorizationResult.redirect;
} else {
session.completePayment(ApplePaySession.STATUS_FAILURE);
session.completePayment(
ApplePaySession.STATUS_FAILURE
);
}
}
} catch ( error ) {
session.completePayment(ApplePaySession.STATUS_FAILURE);
session.completePayment(
ApplePaySession.STATUS_FAILURE
);
session.abort();
console.error( error );
}
} else {
console.error( 'Error status is not APPROVED' );
session.completePayment(ApplePaySession.STATUS_FAILURE);
session.completePayment(
ApplePaySession.STATUS_FAILURE
);
}
} else {
console.error( 'Invalid confirmOrderResponse' );
session.completePayment( ApplePaySession.STATUS_FAILURE );
}
} catch ( error ) {
console.error('Error confirming order with applepay token', error);
console.error(
'Error confirming order with applepay token',
error
);
session.completePayment( ApplePaySession.STATUS_FAILURE );
session.abort();
}
@ -742,31 +890,61 @@ class ApplepayButton {
postalCode: data.billing_postcode ?? '',
countryCode: data.billing_country ?? '',
administrativeArea: data.billing_state ?? '',
}
};
}
fillShippingContact( data ) {
if (data.shipping_first_name === "") {
if ( data.shipping_first_name === '' ) {
return this.fillBillingContact( data );
}
return {
givenName: (data?.shipping_first_name && data.shipping_first_name !== "") ? data.shipping_first_name : data?.billing_first_name,
familyName: (data?.shipping_last_name && data.shipping_last_name !== "") ? data.shipping_last_name : data?.billing_last_name,
emailAddress: (data?.shipping_email && data.shipping_email !== "") ? data.shipping_email : data?.billing_email,
phoneNumber: (data?.shipping_phone && data.shipping_phone !== "") ? data.shipping_phone : data?.billing_phone,
addressLines: [data.shipping_address_1 ?? '', data.shipping_address_2 ?? ''],
locality: (data?.shipping_city && data.shipping_city !== "") ? data.shipping_city : data?.billing_city,
postalCode: (data?.shipping_postcode && data.shipping_postcode !== "") ? data.shipping_postcode : data?.billing_postcode,
countryCode: (data?.shipping_country && data.shipping_country !== "") ? data.shipping_country : data?.billing_country,
administrativeArea: (data?.shipping_state && data.shipping_state !== "") ? data.shipping_state : data?.billing_state,
}
givenName:
data?.shipping_first_name && data.shipping_first_name !== ''
? data.shipping_first_name
: data?.billing_first_name,
familyName:
data?.shipping_last_name && data.shipping_last_name !== ''
? data.shipping_last_name
: data?.billing_last_name,
emailAddress:
data?.shipping_email && data.shipping_email !== ''
? data.shipping_email
: data?.billing_email,
phoneNumber:
data?.shipping_phone && data.shipping_phone !== ''
? data.shipping_phone
: data?.billing_phone,
addressLines: [
data.shipping_address_1 ?? '',
data.shipping_address_2 ?? '',
],
locality:
data?.shipping_city && data.shipping_city !== ''
? data.shipping_city
: data?.billing_city,
postalCode:
data?.shipping_postcode && data.shipping_postcode !== ''
? data.shipping_postcode
: data?.billing_postcode,
countryCode:
data?.shipping_country && data.shipping_country !== ''
? data.shipping_country
: data?.billing_country,
administrativeArea:
data?.shipping_state && data.shipping_state !== ''
? data.shipping_state
: data?.billing_state,
};
}
fillApplicationData( data ) {
const jsonString = JSON.stringify( data );
let utf8Str = encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (match, p1) => {
const utf8Str = encodeURIComponent( jsonString ).replace(
/%([0-9A-F]{2})/g,
( match, p1 ) => {
return String.fromCharCode( '0x' + p1 );
});
}
);
return btoa( utf8Str );
}

View file

@ -1,10 +1,8 @@
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
import ApplepayButton from "./ApplepayButton";
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
import ApplepayButton from './ApplepayButton';
class ApplepayManager {
constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.ApplePayConfig = null;
@ -15,7 +13,7 @@ class ApplepayManager {
bootstrap.context,
bootstrap.handler,
buttonConfig,
ppcpConfig,
ppcpConfig
);
this.buttons.push( button );
@ -43,13 +41,12 @@ class ApplepayManager {
/**
* Gets ApplePay configuration of the PayPal merchant.
* @returns {Promise<null>}
* @return {Promise<null>}
*/
async config() {
this.ApplePayConfig = await paypal.Applepay().config();
return this.ApplePayConfig;
}
}
export default ApplepayManager;

View file

@ -1,7 +1,6 @@
import ApplepayButton from "./ApplepayButton";
import ApplepayButton from './ApplepayButton';
class ApplepayManagerBlockEditor {
constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
@ -22,11 +21,10 @@ class ApplepayManagerBlockEditor {
this.ppcpConfig.context,
null,
this.buttonConfig,
this.ppcpConfig,
this.ppcpConfig
);
button.init( this.applePayConfig );
} catch ( error ) {
console.error( 'Failed to initialize Apple Pay:', error );
}

View file

@ -1,19 +1,19 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import CartActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
import {isPayPalSubscription} from "../../../../ppcp-blocks/resources/js/Helper/Subscription";
import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler';
import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription';
class BaseHandler {
constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
}
isVaultV3Mode() {
return this.ppcpConfig?.save_payment_methods?.id_token // vault v3
&& ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled // not PayPal Subscriptions mode
&& this.ppcpConfig?.can_save_vault_token; // vault is enabled
return (
this.ppcpConfig?.save_payment_methods?.id_token && // vault v3
! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled && // not PayPal Subscriptions mode
this.ppcpConfig?.can_save_vault_token
); // vault is enabled
}
validateContext() {
@ -30,17 +30,14 @@ class BaseHandler {
transactionInfo() {
return new Promise( ( resolve, reject ) => {
const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint;
const separator = (endpoint.indexOf('?') !== -1) ? '&' : '?';
const separator = endpoint.indexOf( '?' ) !== -1 ? '&' : '?';
fetch(
endpoint + separator + 'shipping=1',
{
fetch( endpoint + separator + 'shipping=1', {
method: 'GET',
credentials: 'same-origin'
}
)
.then(result => result.json())
.then(result => {
credentials: 'same-origin',
} )
.then( ( result ) => result.json() )
.then( ( result ) => {
if ( ! result.success ) {
return;
}
@ -53,10 +50,10 @@ class BaseHandler {
currencyCode: data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.total_str,
chosenShippingMethods: data.chosen_shipping_methods || null,
chosenShippingMethods:
data.chosen_shipping_methods || null,
shippingPackages: data.shipping_packages || null,
} );
} );
} );
}
@ -70,10 +67,7 @@ class BaseHandler {
}
actionHandler() {
return new CartActionHandler(
this.ppcpConfig,
this.errorHandler(),
);
return new CartActionHandler( this.ppcpConfig, this.errorHandler() );
}
errorHandler() {
@ -89,7 +83,6 @@ class BaseHandler {
document.querySelector( '.woocommerce-notices-wrapper' )
);
}
}
export default BaseHandler;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,13 @@
import SingleProductHandler from "./SingleProductHandler";
import CartHandler from "./CartHandler";
import CheckoutHandler from "./CheckoutHandler";
import CartBlockHandler from "./CartBlockHandler";
import CheckoutBlockHandler from "./CheckoutBlockHandler";
import MiniCartHandler from "./MiniCartHandler";
import PreviewHandler from "./PreviewHandler";
import PayNowHandler from "./PayNowHandler";
import SingleProductHandler from './SingleProductHandler';
import CartHandler from './CartHandler';
import CheckoutHandler from './CheckoutHandler';
import CartBlockHandler from './CartBlockHandler';
import CheckoutBlockHandler from './CheckoutBlockHandler';
import MiniCartHandler from './MiniCartHandler';
import PreviewHandler from './PreviewHandler';
import PayNowHandler from './PayNowHandler';
class ContextHandlerFactory {
static create( context, buttonConfig, ppcpConfig ) {
switch ( context ) {
case 'product':

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import BaseHandler from "./BaseHandler";
import BaseHandler from './BaseHandler';
class PreviewHandler extends BaseHandler {
constructor( buttonConfig, ppcpConfig, externalHandler ) {
super( buttonConfig, ppcpConfig, externalHandler );
}
@ -9,11 +8,11 @@ class PreviewHandler extends BaseHandler {
transactionInfo() {
// We need to return something as ApplePay button initialization expects valid data.
return {
countryCode: "US",
currencyCode: "USD",
totalPrice: "10.00",
totalPriceStatus: "FINAL"
}
countryCode: 'US',
currencyCode: 'USD',
totalPrice: '10.00',
totalPriceStatus: 'FINAL',
};
}
createOrder() {
@ -31,7 +30,6 @@ class PreviewHandler extends BaseHandler {
errorHandler() {
throw new Error( 'Error handler fail. This is just a preview.' );
}
}
export default PreviewHandler;

View file

@ -1,12 +1,10 @@
import SingleProductActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler";
import SimulateCart from "../../../../ppcp-button/resources/js/modules/Helper/SimulateCart";
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import UpdateCart from "../../../../ppcp-button/resources/js/modules/Helper/UpdateCart";
import BaseHandler from "./BaseHandler";
import SingleProductActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler';
import SimulateCart from '../../../../ppcp-button/resources/js/modules/Helper/SimulateCart';
import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
import UpdateCart from '../../../../ppcp-button/resources/js/modules/Helper/UpdateCart';
import BaseHandler from './BaseHandler';
class SingleProductHandler extends BaseHandler {
validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.product ) {
return this.isVaultV3Mode();
@ -28,38 +26,39 @@ class SingleProductHandler extends BaseHandler {
null,
null,
form(),
errorHandler,
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();
return new Promise( ( resolve, reject ) => {
(new SimulateCart(
new SimulateCart(
this.ppcpConfig.ajax.simulate_cart.endpoint,
this.ppcpConfig.ajax.simulate_cart.nonce,
)).simulate((data) => {
this.ppcpConfig.ajax.simulate_cart.nonce
).simulate( ( data ) => {
resolve( {
countryCode: data.country_code,
currencyCode: data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.total_str
totalPrice: data.total_str,
} );
}, products );
} );
}
createOrder() {
return this.actionHandler().configuration().createOrder(null, null, {
'updateCartOptions': {
'keepShipping': true
}
return this.actionHandler()
.configuration()
.createOrder( null, null, {
updateCartOptions: {
keepShipping: true,
},
} );
}
@ -68,10 +67,10 @@ class SingleProductHandler extends BaseHandler {
this.ppcpConfig,
new UpdateCart(
this.ppcpConfig.ajax.change_cart.endpoint,
this.ppcpConfig.ajax.change_cart.nonce,
this.ppcpConfig.ajax.change_cart.nonce
),
document.querySelector( 'form.cart' ),
this.errorHandler(),
this.errorHandler()
);
}

View file

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

View file

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

View file

@ -7,13 +7,13 @@ import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Rend
*/
const buttonManager = () => {
if ( ! ApplePayPreviewButtonManager.instance ) {
ApplePayPreviewButtonManager.instance = new ApplePayPreviewButtonManager();
ApplePayPreviewButtonManager.instance =
new ApplePayPreviewButtonManager();
}
return ApplePayPreviewButtonManager.instance;
};
/**
* Manages all Apple Pay preview buttons on this page.
*/
@ -38,7 +38,9 @@ class ApplePayPreviewButtonManager extends PreviewButtonManager {
const apiMethod = payPal?.Applepay()?.config;
if ( ! apiMethod ) {
this.error('configuration object cannot be retrieved from PayPal');
this.error(
'configuration object cannot be retrieved from PayPal'
);
return {};
}
@ -59,7 +61,6 @@ class ApplePayPreviewButtonManager extends PreviewButtonManager {
}
}
/**
* A single Apple Pay preview button instance.
*/
@ -85,7 +86,12 @@ class ApplePayPreviewButton extends PreviewButton {
}
createButton( buttonConfig ) {
const button = new ApplepayButton('preview', null, buttonConfig, this.ppcpConfig);
const button = new ApplepayButton(
'preview',
null,
buttonConfig,
this.ppcpConfig
);
button.init( this.apiConfig );
}
@ -93,17 +99,23 @@ class ApplePayPreviewButton extends PreviewButton {
/**
* Merge form details into the config object for preview.
* Mutates the previewConfig object; no return value.
* @param buttonConfig
* @param ppcpConfig
*/
dynamicPreviewConfig( buttonConfig, ppcpConfig ) {
// The Apple Pay button expects the "wrapper" to be an ID without `#` prefix!
buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(/^#/, '');
buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(
/^#/,
''
);
// Merge the current form-values into the preview-button configuration.
if ( ppcpConfig.button ) {
buttonConfig.button.type = ppcpConfig.button.style.type;
buttonConfig.button.color = ppcpConfig.button.style.color;
buttonConfig.button.lang =
ppcpConfig.button.style?.lang || ppcpConfig.button.style.language;
ppcpConfig.button.style?.lang ||
ppcpConfig.button.style.language;
}
}
}

View file

@ -1,11 +1,11 @@
import { useEffect, useState } from '@wordpress/element';
import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry';
import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription'
import {loadCustomScript} from "@paypal/paypal-js";
import CheckoutHandler from "./Context/CheckoutHandler";
import ApplepayManager from "./ApplepayManager";
import ApplepayManagerBlockEditor from "./ApplepayManagerBlockEditor";
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
import { loadCustomScript } from '@paypal/paypal-js';
import CheckoutHandler from './Context/CheckoutHandler';
import ApplepayManager from './ApplepayManager';
import ApplepayManagerBlockEditor from './ApplepayManagerBlockEditor';
const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
const ppcpConfig = ppcpData.scriptData;
@ -23,7 +23,9 @@ const ApplePayComponent = ( props ) => {
const [ applePayLoaded, setApplePayLoaded ] = useState( false );
const bootstrap = function () {
const ManagerClass = props.isEditing ? ApplepayManagerBlockEditor : ApplepayManager;
const ManagerClass = props.isEditing
? ApplepayManagerBlockEditor
: ApplepayManager;
const manager = new ManagerClass( buttonConfig, ppcpConfig );
manager.init();
};
@ -52,16 +54,16 @@ const ApplePayComponent = ( props ) => {
return (
<div
id={ buttonConfig.button.wrapper.replace( '#', '' ) }
className="ppcp-button-apm ppcp-button-applepay">
</div>
className="ppcp-button-apm ppcp-button-applepay"
></div>
);
}
};
const features = [ 'products' ];
if (
cartHasSubscriptionProducts(ppcpConfig)
&& (new CheckoutHandler(buttonConfig, ppcpConfig)).isVaultV3Mode()
cartHasSubscriptionProducts( ppcpConfig ) &&
new CheckoutHandler( buttonConfig, ppcpConfig ).isVaultV3Mode()
) {
features.push( 'subscriptions' );
}
@ -74,6 +76,6 @@ registerExpressPaymentMethod({
ariaLabel: buttonData.title,
canMakePayment: () => buttonData.enabled,
supports: {
features: features,
features,
},
} );

View file

@ -1,14 +1,9 @@
import {loadCustomScript} from "@paypal/paypal-js";
import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
import ApplepayManager from "./ApplepayManager";
import { loadCustomScript } from '@paypal/paypal-js';
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
import ApplepayManager from './ApplepayManager';
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
(function ({
buttonConfig,
ppcpConfig,
jQuery
}) {
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
let manager;
const bootstrap = function () {
@ -22,12 +17,10 @@ import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helpe
}
} );
document.addEventListener(
'DOMContentLoaded',
() => {
document.addEventListener( 'DOMContentLoaded', () => {
if (
(typeof (buttonConfig) === 'undefined') ||
(typeof (ppcpConfig) === 'undefined')
typeof buttonConfig === 'undefined' ||
typeof ppcpConfig === 'undefined'
) {
return;
}
@ -48,7 +41,7 @@ import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helpe
bootstrapped = true;
bootstrap();
}
}
};
// Load ApplePay SDK
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
@ -61,11 +54,9 @@ import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helpe
paypalLoaded = true;
tryToBoot();
} );
},
);
} );
} )( {
buttonConfig: window.wc_ppcp_applepay,
ppcpConfig: window.PayPalCommerceGateway,
jQuery: window.jQuery
jQuery: window.jQuery,
} );

View file

@ -1,15 +1,17 @@
import Fastlane from "./Connection/Fastlane";
import {log} from "./Helper/Debug";
import DomElementCollection from "./Components/DomElementCollection";
import ShippingView from "./Views/ShippingView";
import BillingView from "./Views/BillingView";
import CardView from "./Views/CardView";
import PayPalInsights from "./Insights/PayPalInsights";
import {disable,enable} from "../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler";
import {getCurrentPaymentMethod} from "../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState";
import Fastlane from './Connection/Fastlane';
import { log } from './Helper/Debug';
import DomElementCollection from './Components/DomElementCollection';
import ShippingView from './Views/ShippingView';
import BillingView from './Views/BillingView';
import CardView from './Views/CardView';
import PayPalInsights from './Insights/PayPalInsights';
import {
disable,
enable,
} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
import { getCurrentPaymentMethod } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
class AxoManager {
constructor( axoConfig, ppcpConfig ) {
this.axoConfig = axoConfig;
this.ppcpConfig = ppcpConfig;
@ -39,36 +41,51 @@ class AxoManager {
this.el = new DomElementCollection();
this.emailInput = document.querySelector(this.el.fieldBillingEmail.selector + ' input');
this.emailInput = document.querySelector(
this.el.fieldBillingEmail.selector + ' input'
);
this.styles = {
root: {
backgroundColorPrimary: '#ffffff'
}
backgroundColorPrimary: '#ffffff',
},
};
this.locale = 'en_us';
this.registerEventHandlers();
this.shippingView = new ShippingView(this.el.shippingAddressContainer.selector, this.el, this.states );
this.billingView = new BillingView(this.el.billingAddressContainer.selector, this.el);
this.cardView = new CardView(this.el.paymentContainer.selector + '-details', this.el, this);
this.shippingView = new ShippingView(
this.el.shippingAddressContainer.selector,
this.el,
this.states
);
this.billingView = new BillingView(
this.el.billingAddressContainer.selector,
this.el
);
this.cardView = new CardView(
this.el.paymentContainer.selector + '-details',
this.el,
this
);
document.axoDebugSetStatus = ( key, value ) => {
this.setStatus( key, value );
}
};
document.axoDebugObject = () => {
return this;
}
};
if (
this.axoConfig?.insights?.enabled
&& this.axoConfig?.insights?.client_id
&& this.axoConfig?.insights?.session_id
this.axoConfig?.insights?.enabled &&
this.axoConfig?.insights?.client_id &&
this.axoConfig?.insights?.session_id
) {
PayPalInsights.config(this.axoConfig?.insights?.client_id, { debug: true });
PayPalInsights.config( this.axoConfig?.insights?.client_id, {
debug: true,
} );
PayPalInsights.setSessionId( this.axoConfig?.insights?.session_id );
PayPalInsights.trackJsLoad();
@ -79,7 +96,7 @@ class AxoManager {
user_data: {
country: 'US',
is_store_member: false,
}
},
} );
}
}
@ -88,19 +105,21 @@ class AxoManager {
}
registerEventHandlers() {
this.$(document).on('change', 'input[name=payment_method]', (ev) => {
this.$( document ).on(
'change',
'input[name=payment_method]',
( ev ) => {
const map = {
'ppcp-axo-gateway': 'card',
'ppcp-gateway': 'paypal',
}
};
PayPalInsights.trackSelectPaymentMethod( {
payment_method_selected: map[ ev.target.value ] || 'other',
page_type: 'checkout'
page_type: 'checkout',
} );
});
}
);
// Listen to Gateway Radio button changes.
this.el.gatewayRadioButton.on( 'change', ( ev ) => {
@ -111,20 +130,24 @@ class AxoManager {
}
} );
this.$(document).on('updated_checkout payment_method_selected', () => {
this.$( document ).on(
'updated_checkout payment_method_selected',
() => {
this.triggerGatewayChange();
});
}
);
// On checkout form submitted.
this.el.submitButton.on( 'click', () => {
this.onClickSubmitButton();
return false;
})
} );
// Click change shipping address link.
this.el.changeShippingAddressLink.on( 'click', async () => {
if ( this.status.hasProfile ) {
const { selectionChanged, selectedAddress } = await this.fastlane.profile.showShippingAddressSelector();
const { selectionChanged, selectedAddress } =
await this.fastlane.profile.showShippingAddressSelector();
if ( selectionChanged ) {
this.setShipping( selectedAddress );
@ -147,7 +170,8 @@ class AxoManager {
if ( response.selectionChanged ) {
this.setCard( response.selectedCard );
this.setBilling( {
address: response.selectedCard.paymentSource.card.billingAddress
address:
response.selectedCard.paymentSource.card.billingAddress,
} );
}
} );
@ -162,23 +186,38 @@ class AxoManager {
// Prevents sending checkout form when pressing Enter key on input field
// and triggers customer lookup
this.$('form.woocommerce-checkout input').on('keydown', async (ev) => {
if(ev.key === 'Enter' && getCurrentPaymentMethod() === 'ppcp-axo-gateway' ) {
this.$( 'form.woocommerce-checkout input' ).on(
'keydown',
async ( ev ) => {
if (
ev.key === 'Enter' &&
getCurrentPaymentMethod() === 'ppcp-axo-gateway'
) {
ev.preventDefault();
log(`Enter key attempt - emailInput: ${this.emailInput.value}`);
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
log(
`Enter key attempt - emailInput: ${ this.emailInput.value }`
);
log(
`this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }`
);
this.validateEmail( this.el.fieldBillingEmail.selector );
if (this.emailInput && this.lastEmailCheckedIdentity !== this.emailInput.value) {
if (
this.emailInput &&
this.lastEmailCheckedIdentity !== this.emailInput.value
) {
await this.onChangeEmail();
}
}
});
}
);
this.reEnableEmailInput();
// Clear last email checked identity when email field is focused.
this.$( '#billing_email_field input' ).on( 'focus', ( ev ) => {
log(`Clear the last email checked: ${this.lastEmailCheckedIdentity}`);
log(
`Clear the last email checked: ${ this.lastEmailCheckedIdentity }`
);
this.lastEmailCheckedIdentity = '';
} );
@ -187,9 +226,14 @@ class AxoManager {
const termsField = document.querySelector( "[name='terms-field']" );
if ( termsField ) {
const status = ev.detail;
const shouldHide = status.active && status.validEmail === false && status.hasProfile === false;
const shouldHide =
status.active &&
status.validEmail === false &&
status.hasProfile === false;
termsField.parentElement.style.display = shouldHide ? 'none' : 'block';
termsField.parentElement.style.display = shouldHide
? 'none'
: 'block';
}
} );
}
@ -236,7 +280,11 @@ class AxoManager {
if ( scenario.defaultFormFields ) {
this.el.customerDetails.show();
this.toggleLoaderAndOverlay(this.el.customerDetails, 'loader', 'ppcp-axo-overlay');
this.toggleLoaderAndOverlay(
this.el.customerDetails,
'loader',
'ppcp-axo-overlay'
);
} else {
this.el.customerDetails.hide();
}
@ -251,8 +299,14 @@ class AxoManager {
this.el.watermarkContainer.show();
// Move watermark to after email.
document.querySelector('#billing_email_field .woocommerce-input-wrapper').append(
document.querySelector(this.el.watermarkContainer.selector)
document
.querySelector(
'#billing_email_field .woocommerce-input-wrapper'
)
.append(
document.querySelector(
this.el.watermarkContainer.selector
)
);
} else {
this.el.emailWidgetContainer.hide();
@ -262,7 +316,6 @@ class AxoManager {
}
if ( scenario.axoProfileViews ) {
this.shippingView.activate();
this.cardView.activate();
@ -283,13 +336,17 @@ class AxoManager {
this.shippingView.deactivate();
this.billingView.deactivate();
this.cardView.deactivate();
this.$(this.el.axoCustomerDetails.selector).removeClass('col-1');
this.$( this.el.axoCustomerDetails.selector ).removeClass(
'col-1'
);
}
if ( scenario.axoPaymentContainer ) {
this.el.paymentContainer.show();
this.el.gatewayDescription.hide();
document.querySelector(this.el.billingEmailSubmitButton.selector).setAttribute('disabled', 'disabled');
document
.querySelector( this.el.billingEmailSubmitButton.selector )
.setAttribute( 'disabled', 'disabled' );
} else {
this.el.paymentContainer.hide();
}
@ -305,7 +362,7 @@ class AxoManager {
}
identifyScenario( active, validEmail, hasProfile ) {
let response = {
const response = {
defaultSubmitButton: false,
defaultEmailField: false,
defaultFormFields: false,
@ -314,7 +371,7 @@ class AxoManager {
axoProfileViews: false,
axoPaymentContainer: false,
axoSubmitButton: false,
}
};
if ( active && validEmail && hasProfile ) {
response.extraFormFields = true;
@ -344,7 +401,9 @@ class AxoManager {
}
ensureBillingFieldsConsistency() {
const $billingFields = this.$('.woocommerce-billing-fields .form-row:visible');
const $billingFields = this.$(
'.woocommerce-billing-fields .form-row:visible'
);
const $billingHeaders = this.$( '.woocommerce-billing-fields h3' );
if ( this.billingView.isActive() ) {
if ( $billingFields.length ) {
@ -358,7 +417,9 @@ class AxoManager {
}
ensureShippingFieldsConsistency() {
const $shippingFields = this.$('.woocommerce-shipping-fields .form-row:visible');
const $shippingFields = this.$(
'.woocommerce-shipping-fields .form-row:visible'
);
const $shippingHeaders = this.$( '.woocommerce-shipping-fields h3' );
if ( this.shippingView.isActive() ) {
if ( $shippingFields.length ) {
@ -386,7 +447,9 @@ class AxoManager {
log( `Status updated: ${ JSON.stringify( this.status ) }` );
document.dispatchEvent(new CustomEvent("axo_status_updated", {detail: this.status}));
document.dispatchEvent(
new CustomEvent( 'axo_status_updated', { detail: this.status } )
);
this.rerender();
}
@ -397,8 +460,13 @@ class AxoManager {
this.setStatus( 'active', true );
log( `Attempt on activation - emailInput: ${ this.emailInput.value }` );
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
if (this.emailInput && this.lastEmailCheckedIdentity !== this.emailInput.value) {
log(
`this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }`
);
if (
this.emailInput &&
this.lastEmailCheckedIdentity !== this.emailInput.value
) {
this.onChangeEmail();
}
}
@ -412,9 +480,12 @@ class AxoManager {
// Customer details container.
if ( ! document.querySelector( wrapper.selector ) ) {
document.querySelector(wrapper.anchorSelector).insertAdjacentHTML('afterbegin', `
document.querySelector( wrapper.anchorSelector ).insertAdjacentHTML(
'afterbegin',
`
<div id="${ wrapper.id }" class="${ wrapper.className }"></div>
`);
`
);
}
const wrapperElement = document.querySelector( wrapper.selector );
@ -422,64 +493,94 @@ class AxoManager {
// Billing view container.
const bc = this.el.billingAddressContainer;
if ( ! document.querySelector( bc.selector ) ) {
wrapperElement.insertAdjacentHTML('beforeend', `
wrapperElement.insertAdjacentHTML(
'beforeend',
`
<div id="${ bc.id }" class="${ bc.className }"></div>
`);
`
);
}
// Shipping view container.
const sc = this.el.shippingAddressContainer;
if ( ! document.querySelector( sc.selector ) ) {
wrapperElement.insertAdjacentHTML('beforeend', `
wrapperElement.insertAdjacentHTML(
'beforeend',
`
<div id="${ sc.id }" class="${ sc.className }"></div>
`);
`
);
}
// billingEmailFieldWrapper
const befw = this.el.billingEmailFieldWrapper;
if ( ! document.querySelector( befw.selector ) ) {
document.querySelector('#billing_email_field .woocommerce-input-wrapper').insertAdjacentHTML('afterend', `
document
.querySelector(
'#billing_email_field .woocommerce-input-wrapper'
)
.insertAdjacentHTML(
'afterend',
`
<div id="${ befw.id }"></div>
`);
`
);
}
// Watermark container
const wc = this.el.watermarkContainer;
if ( ! document.querySelector( wc.selector ) ) {
document.querySelector(befw.selector).insertAdjacentHTML('beforeend', `
document.querySelector( befw.selector ).insertAdjacentHTML(
'beforeend',
`
<div class="${ wc.className }" id="${ wc.id }"></div>
`);
`
);
}
// Payment container
const pc = this.el.paymentContainer;
if ( ! document.querySelector( pc.selector ) ) {
const gatewayPaymentContainer = document.querySelector('.payment_method_ppcp-axo-gateway');
gatewayPaymentContainer.insertAdjacentHTML('beforeend', `
const gatewayPaymentContainer = document.querySelector(
'.payment_method_ppcp-axo-gateway'
);
gatewayPaymentContainer.insertAdjacentHTML(
'beforeend',
`
<div id="${ pc.id }" class="${ pc.className } axo-hidden">
<div id="${ pc.id }-form" class="${ pc.className }-form"></div>
<div id="${ pc.id }-details" class="${ pc.className }-details"></div>
</div>
`);
`
);
}
if ( this.useEmailWidget() ) {
// Display email widget.
const ec = this.el.emailWidgetContainer;
if ( ! document.querySelector( ec.selector ) ) {
wrapperElement.insertAdjacentHTML('afterbegin', `
wrapperElement.insertAdjacentHTML(
'afterbegin',
`
<div id="${ ec.id }" class="${ ec.className }">
--- EMAIL WIDGET PLACEHOLDER ---
</div>
`);
`
);
}
} else {
// Move email to the AXO container.
let emailRow = document.querySelector(this.el.fieldBillingEmail.selector);
const emailRow = document.querySelector(
this.el.fieldBillingEmail.selector
);
wrapperElement.prepend( emailRow );
document.querySelector(this.el.billingEmailFieldWrapper.selector).prepend(document.querySelector('#billing_email_field .woocommerce-input-wrapper'));
document
.querySelector( this.el.billingEmailFieldWrapper.selector )
.prepend(
document.querySelector(
'#billing_email_field .woocommerce-input-wrapper'
)
);
}
}
@ -502,7 +603,7 @@ class AxoManager {
await this.fastlane.connect( {
locale: this.locale,
styles: this.styles
styles: this.styles,
} );
this.fastlane.setLocale( 'en_us' );
@ -513,49 +614,76 @@ class AxoManager {
}
async renderWatermark( includeAdditionalInfo = true ) {
(await this.fastlane.FastlaneWatermarkComponent({
includeAdditionalInfo
})).render(this.el.watermarkContainer.selector);
(
await this.fastlane.FastlaneWatermarkComponent( {
includeAdditionalInfo,
} )
).render( this.el.watermarkContainer.selector );
this.toggleWatermarkLoading(this.el.watermarkContainer, 'ppcp-axo-watermark-loading', 'loader');
this.toggleWatermarkLoading(
this.el.watermarkContainer,
'ppcp-axo-watermark-loading',
'loader'
);
}
renderEmailSubmitButton() {
const billingEmailSubmitButton = this.el.billingEmailSubmitButton;
const billingEmailSubmitButtonSpinner = this.el.billingEmailSubmitButtonSpinner;
const billingEmailSubmitButtonSpinner =
this.el.billingEmailSubmitButtonSpinner;
if ( ! document.querySelector( billingEmailSubmitButton.selector ) ) {
document.querySelector(this.el.billingEmailFieldWrapper.selector).insertAdjacentHTML('beforeend', `
document
.querySelector( this.el.billingEmailFieldWrapper.selector )
.insertAdjacentHTML(
'beforeend',
`
<button type="button" id="${ billingEmailSubmitButton.id }" class="${ billingEmailSubmitButton.className }">
${ this.axoConfig.billing_email_button_text }
<span id="${ billingEmailSubmitButtonSpinner.id }"></span>
</button>
`);
`
);
document.querySelector(this.el.billingEmailSubmitButton.selector).offsetHeight;
document.querySelector(this.el.billingEmailSubmitButton.selector).classList.remove('ppcp-axo-billing-email-submit-button-hidden');
document.querySelector(this.el.billingEmailSubmitButton.selector).classList.add('ppcp-axo-billing-email-submit-button-loaded');
document.querySelector( this.el.billingEmailSubmitButton.selector )
.offsetHeight;
document
.querySelector( this.el.billingEmailSubmitButton.selector )
.classList.remove(
'ppcp-axo-billing-email-submit-button-hidden'
);
document
.querySelector( this.el.billingEmailSubmitButton.selector )
.classList.add( 'ppcp-axo-billing-email-submit-button-loaded' );
}
}
watchEmail() {
if ( this.useEmailWidget() ) {
// TODO
} else {
this.emailInput.addEventListener( 'change', async () => {
log(`Change event attempt - emailInput: ${this.emailInput.value}`);
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
if (this.emailInput && this.lastEmailCheckedIdentity !== this.emailInput.value) {
log(
`Change event attempt - emailInput: ${ this.emailInput.value }`
);
log(
`this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }`
);
if (
this.emailInput &&
this.lastEmailCheckedIdentity !== this.emailInput.value
) {
this.validateEmail( this.el.fieldBillingEmail.selector );
this.onChangeEmail();
}
} );
log(`Last, this.emailInput.value attempt - emailInput: ${this.emailInput.value}`);
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
log(
`Last, this.emailInput.value attempt - emailInput: ${ this.emailInput.value }`
);
log(
`this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }`
);
if ( this.emailInput.value ) {
this.onChangeEmail();
}
@ -575,7 +703,11 @@ class AxoManager {
return;
}
log(`Email changed: ${this.emailInput ? this.emailInput.value : '<empty>'}`);
log(
`Email changed: ${
this.emailInput ? this.emailInput.value : '<empty>'
}`
);
this.$( this.el.paymentContainer.selector + '-detail' ).html( '' );
this.$( this.el.paymentContainer.selector + '-form' ).html( '' );
@ -587,7 +719,11 @@ class AxoManager {
this.lastEmailCheckedIdentity = this.emailInput.value;
if (!this.emailInput.value || !this.emailInput.checkValidity() || !this.validateEmailFormat(this.emailInput.value)) {
if (
! this.emailInput.value ||
! this.emailInput.checkValidity() ||
! this.validateEmailFormat( this.emailInput.value )
) {
log( 'The email address is not valid.' );
return;
}
@ -601,30 +737,49 @@ class AxoManager {
}
PayPalInsights.trackSubmitCheckoutEmail( {
page_type: 'checkout'
page_type: 'checkout',
} );
this.disableGatewaySelection();
this.spinnerToggleLoaderAndOverlay(this.el.billingEmailSubmitButtonSpinner, 'loader', 'ppcp-axo-overlay');
this.spinnerToggleLoaderAndOverlay(
this.el.billingEmailSubmitButtonSpinner,
'loader',
'ppcp-axo-overlay'
);
await this.lookupCustomerByEmail();
this.spinnerToggleLoaderAndOverlay(this.el.billingEmailSubmitButtonSpinner, 'loader', 'ppcp-axo-overlay');
this.spinnerToggleLoaderAndOverlay(
this.el.billingEmailSubmitButtonSpinner,
'loader',
'ppcp-axo-overlay'
);
this.enableGatewaySelection();
}
async lookupCustomerByEmail() {
const lookupResponse = await this.fastlane.identity.lookupCustomerByEmail(this.emailInput.value);
const lookupResponse =
await this.fastlane.identity.lookupCustomerByEmail(
this.emailInput.value
);
log( `lookupCustomerByEmail: ${ JSON.stringify( lookupResponse ) }` );
if ( lookupResponse.customerContextId ) {
// Email is associated with a Connect profile or a PayPal member.
// Authenticate the customer to get access to their profile.
log('Email is associated with a Connect profile or a PayPal member');
log(
'Email is associated with a Connect profile or a PayPal member'
);
const authResponse = await this.fastlane.identity.triggerAuthenticationFlow(lookupResponse.customerContextId);
const authResponse =
await this.fastlane.identity.triggerAuthenticationFlow(
lookupResponse.customerContextId
);
log(`AuthResponse - triggerAuthenticationFlow: ${JSON.stringify(authResponse)}`);
log(
`AuthResponse - triggerAuthenticationFlow: ${ JSON.stringify(
authResponse
) }`
);
if ( authResponse.authenticationState === 'succeeded' ) {
const shippingData = authResponse.profileData.shippingAddress;
@ -635,21 +790,27 @@ class AxoManager {
if ( authResponse.profileData.card ) {
this.setStatus( 'hasCard', true );
} else {
this.cardComponent = (await this.fastlane.FastlaneCardComponent(
this.cardComponent = (
await this.fastlane.FastlaneCardComponent(
this.cardComponentData()
)).render(this.el.paymentContainer.selector + '-form');
)
).render( this.el.paymentContainer.selector + '-form' );
}
const cardBillingAddress = authResponse.profileData?.card?.paymentSource?.card?.billingAddress;
const cardBillingAddress =
authResponse.profileData?.card?.paymentSource?.card
?.billingAddress;
if ( cardBillingAddress ) {
this.setCard( authResponse.profileData.card );
const billingData = {
address: cardBillingAddress,
};
const phoneNumber = authResponse.profileData?.shippingAddress?.phoneNumber?.nationalNumber ?? '';
const phoneNumber =
authResponse.profileData?.shippingAddress?.phoneNumber
?.nationalNumber ?? '';
if ( phoneNumber ) {
billingData.phoneNumber = phoneNumber
billingData.phoneNumber = phoneNumber;
}
this.setBilling( billingData );
@ -665,22 +826,22 @@ class AxoManager {
await this.renderWatermark( false );
this.rerender();
} else {
// authentication failed or canceled by the customer
// set status as guest customer
log("Authentication Failed")
log( 'Authentication Failed' );
this.setStatus( 'validEmail', true );
this.setStatus( 'hasProfile', false );
await this.renderWatermark( true );
this.cardComponent = (await this.fastlane.FastlaneCardComponent(
this.cardComponent = (
await this.fastlane.FastlaneCardComponent(
this.cardComponentData()
)).render(this.el.paymentContainer.selector + '-form');
)
).render( this.el.paymentContainer.selector + '-form' );
}
} else {
// No profile found with this email address.
// This is a guest customer.
@ -691,9 +852,11 @@ class AxoManager {
await this.renderWatermark( true );
this.cardComponent = (await this.fastlane.FastlaneCardComponent(
this.cardComponent = (
await this.fastlane.FastlaneCardComponent(
this.cardComponentData()
)).render(this.el.paymentContainer.selector + '-form');
)
).render( this.el.paymentContainer.selector + '-form' );
}
}
@ -732,30 +895,34 @@ class AxoManager {
onClickSubmitButton() {
// TODO: validate data.
if (this.data.card) { // Ryan flow
if ( this.data.card ) {
// Ryan flow
log( 'Starting Ryan flow.' );
this.$('#ship-to-different-address-checkbox').prop('checked', 'checked');
this.$( '#ship-to-different-address-checkbox' ).prop(
'checked',
'checked'
);
let data = {};
const data = {};
this.billingView.toSubmitData( data );
this.shippingView.toSubmitData( data );
this.cardView.toSubmitData( data );
this.ensureBillingPhoneNumber( data );
log(`Ryan flow - submitted nonce: ${this.data.card.id}` )
log( `Ryan flow - submitted nonce: ${ this.data.card.id }` );
this.submit( this.data.card.id, data );
} else { // Gary flow
} else {
// Gary flow
log( 'Starting Gary flow.' );
try {
this.cardComponent.getPaymentToken(
this.tokenizeData()
).then((response) => {
log(`Gary flow - submitted nonce: ${response.id}` )
this.cardComponent
.getPaymentToken( this.tokenizeData() )
.then( ( response ) => {
log( `Gary flow - submitted nonce: ${ response.id }` );
this.submit( response.id );
} );
} catch ( e ) {
@ -769,17 +936,19 @@ class AxoManager {
return {
fields: {
cardholderName: {
enabled: this.axoConfig.name_on_card === '1'
}
enabled: this.axoConfig.name_on_card === '1',
},
styles: this.deleteKeysWithEmptyString(this.axoConfig.style_options)
}
},
styles: this.deleteKeysWithEmptyString(
this.axoConfig.style_options
),
};
}
tokenizeData() {
return {
cardholderName: {
fullName: this.billingView.fullName()
fullName: this.billingView.fullName(),
},
billingAddress: {
addressLine1: this.billingView.inputValue( 'street1' ),
@ -788,14 +957,16 @@ class AxoManager {
adminArea2: this.billingView.inputValue( 'city' ),
postalCode: this.billingView.inputValue( 'postCode' ),
countryCode: this.billingView.inputValue( 'countryCode' ),
}
}
},
};
}
submit( nonce, data ) {
// Send the nonce and previously captured device data to server to complete checkout
if ( ! this.el.axoNonceInput.get() ) {
this.$('form.woocommerce-checkout').append(`<input type="hidden" id="${this.el.axoNonceInput.id}" name="axo_nonce" value="" />`);
this.$( 'form.woocommerce-checkout' ).append(
`<input type="hidden" id="${ this.el.axoNonceInput.id }" name="axo_nonce" value="" />`
);
}
this.el.axoNonceInput.get().value = nonce;
@ -807,11 +978,10 @@ class AxoManager {
user_data: {
country: 'US',
is_store_member: false,
}
},
} );
if ( data ) {
// Ryan flow.
const form = document.querySelector( 'form.woocommerce-checkout' );
const formData = new FormData( form );
@ -826,22 +996,31 @@ class AxoManager {
// Set type of user (Ryan) to be received on WC gateway process payment request.
formData.set( 'fastlane_member', true );
fetch(wc_checkout_params.checkout_url, { // TODO: maybe create a new endpoint to process_payment.
method: "POST",
body: formData
fetch( wc_checkout_params.checkout_url, {
// TODO: maybe create a new endpoint to process_payment.
method: 'POST',
body: formData,
} )
.then(response => response.json())
.then(responseData => {
.then( ( response ) => response.json() )
.then( ( responseData ) => {
if ( responseData.result === 'failure' ) {
if ( responseData.messages ) {
const $notices = this.$('.woocommerce-notices-wrapper').eq(0);
const $notices = this.$(
'.woocommerce-notices-wrapper'
).eq( 0 );
$notices.html( responseData.messages );
this.$('html, body').animate({
scrollTop: $notices.offset().top
}, 500);
this.$( 'html, body' ).animate(
{
scrollTop: $notices.offset().top,
},
500
);
}
log(`Error sending checkout form. ${responseData}`, 'error');
log(
`Error sending checkout form. ${ responseData }`,
'error'
);
this.hideLoading();
return;
@ -850,22 +1029,25 @@ class AxoManager {
window.location.href = responseData.redirect;
}
} )
.catch(error => {
log(`Error sending checkout form. ${error.message}`, 'error');
.catch( ( error ) => {
log(
`Error sending checkout form. ${ error.message }`,
'error'
);
this.hideLoading();
} );
} else {
// Gary flow.
this.el.defaultSubmitButton.click();
}
}
showLoading() {
const submitContainerSelector = '.woocommerce-checkout-payment';
jQuery('form.woocommerce-checkout').append('<div class="blockUI blockOverlay" style="z-index: 1000; border: medium; margin: 0px; padding: 0px; width: 100%; height: 100%; top: 0px; left: 0px; background: rgb(255, 255, 255); opacity: 0.6; cursor: default; position: absolute;"></div>');
jQuery( 'form.woocommerce-checkout' ).append(
'<div class="blockUI blockOverlay" style="z-index: 1000; border: medium; margin: 0px; padding: 0px; width: 100%; height: 100%; top: 0px; left: 0px; background: rgb(255, 255, 255); opacity: 0.6; cursor: default; position: absolute;"></div>'
);
disable( submitContainerSelector );
}
@ -880,18 +1062,19 @@ class AxoManager {
}
deleteKeysWithEmptyString = ( obj ) => {
for(let key of Object.keys(obj)){
for ( const key of Object.keys( obj ) ) {
if ( obj[ key ] === '' ) {
delete obj[ key ];
}
else if (typeof obj[key] === 'object'){
} else if ( typeof obj[ key ] === 'object' ) {
obj[ key ] = this.deleteKeysWithEmptyString( obj[ key ] );
if (Object.keys(obj[key]).length === 0 ) delete obj[key];
if ( Object.keys( obj[ key ] ).length === 0 ) {
delete obj[ key ];
}
}
}
return Array.isArray(obj) ? obj.filter(val => val) : obj;
}
return Array.isArray( obj ) ? obj.filter( ( val ) => val ) : obj;
};
ensureBillingPhoneNumber( data ) {
if ( data.billing_phone === '' ) {
@ -909,8 +1092,12 @@ class AxoManager {
}
toggleLoaderAndOverlay( element, loaderClass, overlayClass ) {
const loader = document.querySelector(`${element.selector} .${loaderClass}`);
const overlay = document.querySelector(`${element.selector} .${overlayClass}`);
const loader = document.querySelector(
`${ element.selector } .${ loaderClass }`
);
const overlay = document.querySelector(
`${ element.selector } .${ overlayClass }`
);
if ( loader ) {
loader.classList.toggle( loaderClass );
}
@ -928,8 +1115,12 @@ class AxoManager {
}
toggleWatermarkLoading( container, loadingClass, loaderClass ) {
const watermarkLoading = document.querySelector(`${container.selector}.${loadingClass}`);
const watermarkLoader = document.querySelector(`${container.selector}.${loaderClass}`);
const watermarkLoading = document.querySelector(
`${ container.selector }.${ loadingClass }`
);
const watermarkLoader = document.querySelector(
`${ container.selector }.${ loaderClass }`
);
if ( watermarkLoading ) {
watermarkLoading.classList.toggle( loadingClass );
}
@ -960,7 +1151,9 @@ class AxoManager {
reEnableEmailInput() {
const reEnableInput = ( ev ) => {
const submitButton = document.querySelector(this.el.billingEmailSubmitButton.selector);
const submitButton = document.querySelector(
this.el.billingEmailSubmitButton.selector
);
if ( submitButton.hasAttribute( 'disabled' ) ) {
submitButton.removeAttribute( 'disabled' );
}

View file

@ -1,5 +1,4 @@
class DomElement {
constructor( config ) {
this.$ = jQuery;
this.config = config;
@ -33,7 +32,6 @@ class DomElement {
get() {
return document.querySelector( this.selector );
}
}
export default DomElement;

View file

@ -1,7 +1,6 @@
import DomElement from "./DomElement";
import DomElement from './DomElement';
class DomElementCollection {
constructor() {
this.gatewayRadioButton = new DomElement( {
selector: '#payment_method_ppcp-axo-gateway',
@ -18,46 +17,47 @@ class DomElementCollection {
this.paymentContainer = new DomElement( {
id: 'ppcp-axo-payment-container',
selector: '#ppcp-axo-payment-container',
className: 'ppcp-axo-payment-container'
className: 'ppcp-axo-payment-container',
} );
this.watermarkContainer = new DomElement( {
id: 'ppcp-axo-watermark-container',
selector: '#ppcp-axo-watermark-container',
className: 'ppcp-axo-watermark-container ppcp-axo-watermark-loading loader'
className:
'ppcp-axo-watermark-container ppcp-axo-watermark-loading loader',
} );
this.customerDetails = new DomElement( {
selector: '#customer_details > *:not(#ppcp-axo-customer-details)'
selector: '#customer_details > *:not(#ppcp-axo-customer-details)',
} );
this.axoCustomerDetails = new DomElement( {
id: 'ppcp-axo-customer-details',
selector: '#ppcp-axo-customer-details',
className: 'ppcp-axo-customer-details',
anchorSelector: '#customer_details'
anchorSelector: '#customer_details',
} );
this.emailWidgetContainer = new DomElement( {
id: 'ppcp-axo-email-widget',
selector: '#ppcp-axo-email-widget',
className: 'ppcp-axo-email-widget'
className: 'ppcp-axo-email-widget',
} );
this.shippingAddressContainer = new DomElement( {
id: 'ppcp-axo-shipping-address-container',
selector: '#ppcp-axo-shipping-address-container',
className: 'ppcp-axo-shipping-address-container'
className: 'ppcp-axo-shipping-address-container',
} );
this.billingAddressContainer = new DomElement( {
id: 'ppcp-axo-billing-address-container',
selector: '#ppcp-axo-billing-address-container',
className: 'ppcp-axo-billing-address-container'
className: 'ppcp-axo-billing-address-container',
} );
this.fieldBillingEmail = new DomElement( {
selector: '#billing_email_field'
selector: '#billing_email_field',
} );
this.billingEmailFieldWrapper = new DomElement( {
@ -68,13 +68,14 @@ class DomElementCollection {
this.billingEmailSubmitButton = new DomElement( {
id: 'ppcp-axo-billing-email-submit-button',
selector: '#ppcp-axo-billing-email-submit-button',
className: 'ppcp-axo-billing-email-submit-button-hidden button alt wp-element-button wc-block-components-button'
className:
'ppcp-axo-billing-email-submit-button-hidden button alt wp-element-button wc-block-components-button',
} );
this.billingEmailSubmitButtonSpinner = new DomElement( {
id: 'ppcp-axo-billing-email-submit-button-spinner',
selector: '#ppcp-axo-billing-email-submit-button-spinner',
className: 'loader ppcp-axo-overlay'
className: 'loader ppcp-axo-overlay',
} );
this.submitButtonContainer = new DomElement( {
@ -82,7 +83,7 @@ class DomElementCollection {
} );
this.submitButton = new DomElement( {
selector: '#ppcp-axo-submit-button-container button'
selector: '#ppcp-axo-submit-button-container button',
} );
this.changeShippingAddressLink = new DomElement( {
@ -109,7 +110,6 @@ class DomElementCollection {
id: 'ppcp-axo-nonce',
selector: '#ppcp-axo-nonce',
} );
}
}

View file

@ -1,6 +1,4 @@
class FormFieldGroup {
constructor( config ) {
this.data = {};
@ -32,7 +30,13 @@ class FormFieldGroup {
return '';
}
const value = path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, this.data);
const value = path
.split( '.' )
.reduce(
( acc, key ) =>
acc && acc[ key ] !== undefined ? acc[ key ] : undefined,
this.data
);
return value ? value : '';
}
@ -51,7 +55,7 @@ class FormFieldGroup {
}
refresh() {
let content = document.querySelector(this.contentSelector);
const content = document.querySelector( this.contentSelector );
if ( ! content ) {
return;
@ -89,21 +93,24 @@ class FormFieldGroup {
}
} );
return isEmpty;
}
},
} );
}
}
showField( selector ) {
const field = document.querySelector(this.baseSelector + ' ' + selector);
const field = document.querySelector(
this.baseSelector + ' ' + selector
);
if ( field ) {
field.classList.remove( 'ppcp-axo-field-hidden' );
}
}
hideField( selector ) {
const field = document.querySelector(this.baseSelector + ' ' + selector);
const field = document.querySelector(
this.baseSelector + ' ' + selector
);
if ( field ) {
field.classList.add( 'ppcp-axo-field-hidden' );
}
@ -147,7 +154,6 @@ class FormFieldGroup {
data[ inputElement.name ] = this.dataValue( fieldKey );
} );
}
}
export default FormFieldGroup;

View file

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

View file

@ -13,8 +13,8 @@ export function log(message, level = 'info') {
log: {
message,
level,
}
})
},
} ),
} ).then( () => {
if ( wpDebug ) {
switch ( level ) {

View file

@ -1,15 +1,13 @@
class PayPalInsights {
constructor() {
window.paypalInsightDataLayer = window.paypalInsightDataLayer || [];
document.paypalInsight = () => {
paypalInsightDataLayer.push( arguments );
}
};
}
/**
* @returns {PayPalInsights}
* @return {PayPalInsights}
*/
static init() {
if ( ! PayPalInsights.instance ) {
@ -52,7 +50,6 @@ class PayPalInsights {
static trackEndCheckout( data ) {
PayPalInsights.track( 'end_checkout', data );
}
}
export default PayPalInsights;

View file

@ -1,7 +1,6 @@
import FormFieldGroup from "../Components/FormFieldGroup";
import FormFieldGroup from '../Components/FormFieldGroup';
class BillingView {
constructor( selector, elements ) {
this.el = elements;
@ -13,15 +12,18 @@ class BillingView {
if ( ! key ) {
return '';
}
const selectElement = document.querySelector(selectSelector);
const selectElement =
document.querySelector( selectSelector );
if ( ! selectElement ) {
return key;
}
const option = selectElement.querySelector(`option[value="${key}"]`);
const option = selectElement.querySelector(
`option[value="${ key }"]`
);
return option ? option.textContent : key;
}
};
if ( data.isEmpty() ) {
return `
@ -38,49 +40,49 @@ class BillingView {
},
fields: {
email: {
'valuePath': 'email',
valuePath: 'email',
},
firstName: {
'selector': '#billing_first_name_field',
'valuePath': null
selector: '#billing_first_name_field',
valuePath: null,
},
lastName: {
'selector': '#billing_last_name_field',
'valuePath': null
selector: '#billing_last_name_field',
valuePath: null,
},
street1: {
'selector': '#billing_address_1_field',
'valuePath': 'billing.address.addressLine1',
selector: '#billing_address_1_field',
valuePath: 'billing.address.addressLine1',
},
street2: {
'selector': '#billing_address_2_field',
'valuePath': null
selector: '#billing_address_2_field',
valuePath: null,
},
postCode: {
'selector': '#billing_postcode_field',
'valuePath': 'billing.address.postalCode',
selector: '#billing_postcode_field',
valuePath: 'billing.address.postalCode',
},
city: {
'selector': '#billing_city_field',
'valuePath': 'billing.address.adminArea2',
selector: '#billing_city_field',
valuePath: 'billing.address.adminArea2',
},
stateCode: {
'selector': '#billing_state_field',
'valuePath': 'billing.address.adminArea1',
selector: '#billing_state_field',
valuePath: 'billing.address.adminArea1',
},
countryCode: {
'selector': '#billing_country_field',
'valuePath': 'billing.address.countryCode',
selector: '#billing_country_field',
valuePath: 'billing.address.countryCode',
},
company: {
'selector': '#billing_company_field',
'valuePath': null,
selector: '#billing_company_field',
valuePath: null,
},
phone: {
'selector': '#billing_phone_field',
'valuePath': 'billing.phoneNumber'
}
}
selector: '#billing_phone_field',
valuePath: 'billing.phoneNumber',
},
},
} );
}
@ -109,13 +111,14 @@ class BillingView {
}
fullName() {
return `${this.inputValue('firstName')} ${this.inputValue('lastName')}`.trim();
return `${ this.inputValue( 'firstName' ) } ${ this.inputValue(
'lastName'
) }`.trim();
}
toSubmitData( data ) {
return this.group.toSubmitData( data );
}
}
export default BillingView;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import MessagesBootstrap from "../../../../ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap";
import {debounce} from "../Helper/debounce";
import MessagesBootstrap from '../../../../ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap';
import { debounce } from '../Helper/debounce';
class BlockCheckoutMessagesBootstrap {
constructor( scriptData ) {
@ -13,9 +13,11 @@ class BlockCheckoutMessagesBootstrap {
this._updateCartTotal();
if ( wp.data?.subscribe ) {
wp.data.subscribe(debounce(() => {
wp.data.subscribe(
debounce( () => {
this._updateCartTotal();
}, 300));
}, 300 )
);
}
}
@ -27,13 +29,16 @@ class BlockCheckoutMessagesBootstrap {
return null;
}
const cart = wp.data.select('wc/store/cart')
const cart = wp.data.select( 'wc/store/cart' );
if ( ! cart ) {
return null;
}
const totals = cart.getCartTotals();
return parseInt(totals.total_price, 10) / 10 ** totals.currency_minor_unit;
return (
parseInt( totals.total_price, 10 ) /
10 ** totals.currency_minor_unit
);
}
/**
@ -47,7 +52,9 @@ class BlockCheckoutMessagesBootstrap {
if ( currentTotal !== this.lastCartTotal ) {
this.lastCartTotal = currentTotal;
jQuery(document.body).trigger('ppcp_block_cart_total_updated', [currentTotal]);
jQuery( document.body ).trigger( 'ppcp_block_cart_total_updated', [
currentTotal,
] );
}
}
}

View file

@ -4,50 +4,55 @@ import {
PayPalScriptProvider,
PayPalCardFieldsProvider,
PayPalCardFieldsForm,
} from "@paypal/react-paypal-js";
} from '@paypal/react-paypal-js';
import {CheckoutHandler} from "./checkout-handler";
import {createOrder, onApprove} from "../card-fields-config";
import {cartHasSubscriptionProducts} from "../Helper/Subscription";
import { CheckoutHandler } from './checkout-handler';
import { createOrder, onApprove } from '../card-fields-config';
import { cartHasSubscriptionProducts } from '../Helper/Subscription';
export function CardFields({config, eventRegistration, emitResponse, components}) {
export function CardFields( {
config,
eventRegistration,
emitResponse,
components,
} ) {
const { onPaymentSetup } = eventRegistration;
const { responseTypes } = emitResponse;
const { PaymentMethodIcons } = components;
const [ cardFieldsForm, setCardFieldsForm ] = useState();
const getCardFieldsForm = ( cardFieldsForm ) => {
setCardFieldsForm(cardFieldsForm)
}
setCardFieldsForm( cardFieldsForm );
};
const getSavePayment = ( savePayment ) => {
localStorage.setItem( 'ppcp-save-card-payment', savePayment );
}
};
const hasSubscriptionProducts = cartHasSubscriptionProducts(config.scriptData);
const hasSubscriptionProducts = cartHasSubscriptionProducts(
config.scriptData
);
useEffect( () => {
localStorage.removeItem( 'ppcp-save-card-payment' );
if ( hasSubscriptionProducts ) {
localStorage.setItem( 'ppcp-save-card-payment', 'true' );
}
}, [hasSubscriptionProducts])
}, [ hasSubscriptionProducts ] );
useEffect(
() =>
onPaymentSetup( () => {
async function handlePaymentProcessing() {
await cardFieldsForm.submit()
.catch((error) => {
await cardFieldsForm.submit().catch( ( error ) => {
return {
type: responseTypes.ERROR,
}
};
} );
return {
type: responseTypes.SUCCESS,
}
};
}
return handlePaymentProcessing();
@ -60,7 +65,7 @@ export function CardFields({config, eventRegistration, emitResponse, components}
<PayPalScriptProvider
options={ {
clientId: config.scriptData.client_id,
components: "card-fields",
components: 'card-fields',
dataNamespace: 'ppcp-block-card-fields',
} }
>
@ -72,7 +77,10 @@ export function CardFields({config, eventRegistration, emitResponse, components}
} }
>
<PayPalCardFieldsForm />
<PaymentMethodIcons icons={config.card_icons} align="left" />
<PaymentMethodIcons
icons={ config.card_icons }
align="left"
/>
<CheckoutHandler
getCardFieldsForm={ getCardFieldsForm }
getSavePayment={ getSavePayment }
@ -83,5 +91,5 @@ export function CardFields({config, eventRegistration, emitResponse, components}
</PayPalCardFieldsProvider>
</PayPalScriptProvider>
</>
)
);
}

View file

@ -1,11 +1,17 @@
import { useEffect } from '@wordpress/element';
import {usePayPalCardFields} from "@paypal/react-paypal-js";
import { usePayPalCardFields } from '@paypal/react-paypal-js';
export const CheckoutHandler = ({getCardFieldsForm, getSavePayment, hasSubscriptionProducts, saveCardText, is_vaulting_enabled}) => {
export const CheckoutHandler = ( {
getCardFieldsForm,
getSavePayment,
hasSubscriptionProducts,
saveCardText,
is_vaulting_enabled,
} ) => {
const { cardFieldsForm } = usePayPalCardFields();
useEffect( () => {
getCardFieldsForm(cardFieldsForm)
getCardFieldsForm( cardFieldsForm );
}, [] );
if ( ! is_vaulting_enabled ) {
@ -24,5 +30,5 @@ export const CheckoutHandler = ({getCardFieldsForm, getSavePayment, hasSubscript
/>
<label htmlFor="save">{ saveCardText }</label>
</>
)
}
);
};

View file

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

View file

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

View file

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

View file

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

View file

@ -2,15 +2,16 @@ export async function createOrder() {
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
return fetch( config.scriptData.ajax.create_order.endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify( {
nonce: config.scriptData.ajax.create_order.nonce,
context: config.scriptData.context,
payment_method: 'ppcp-credit-card-gateway',
save_payment_method: localStorage.getItem('ppcp-save-card-payment') === 'true',
save_payment_method:
localStorage.getItem( 'ppcp-save-card-payment' ) === 'true',
} ),
} )
.then( ( response ) => response.json() )
@ -26,9 +27,9 @@ export async function onApprove(data) {
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
return fetch( config.scriptData.ajax.approve_order.endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify( {
order_id: data.orderID,

View file

@ -1,31 +1,26 @@
import { useEffect, useState } from '@wordpress/element';
import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
import {
registerExpressPaymentMethod,
registerPaymentMethod,
} from '@woocommerce/blocks-registry';
import {
mergeWcAddress,
paypalAddressToWc,
paypalOrderToWcAddresses,
paypalSubscriptionToWcAddresses
} from "./Helper/Address";
import {
convertKeysToSnakeCase
} from "./Helper/Helper";
paypalSubscriptionToWcAddresses,
} from './Helper/Address';
import { convertKeysToSnakeCase } from './Helper/Helper';
import {
cartHasSubscriptionProducts,
isPayPalSubscription
} from "./Helper/Subscription";
import {
loadPaypalScriptPromise
} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";
import {
normalizeStyleForFundingSource
} from '../../../ppcp-button/resources/js/modules/Helper/Style'
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
import BlockCheckoutMessagesBootstrap from "./Bootstrap/BlockCheckoutMessagesBootstrap";
import {keysToCamelCase} from "../../../ppcp-button/resources/js/modules/Helper/Utils";
import {
handleShippingOptionsChange
} from "../../../ppcp-button/resources/js/modules/Helper/ShippingHandler";
isPayPalSubscription,
} from './Helper/Subscription';
import { loadPaypalScriptPromise } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js';
import { normalizeStyleForFundingSource } from '../../../ppcp-button/resources/js/modules/Helper/Style';
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
import BlockCheckoutMessagesBootstrap from './Bootstrap/BlockCheckoutMessagesBootstrap';
import { keysToCamelCase } from '../../../ppcp-button/resources/js/modules/Helper/Utils';
import { handleShippingOptionsChange } from '../../../ppcp-button/resources/js/modules/Helper/ShippingHandler';
const config = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
window.ppcpFundingSource = config.fundingSource;
@ -46,36 +41,52 @@ const PayPalComponent = ({
isEditing,
fundingSource,
} ) => {
const {onPaymentSetup, onCheckoutFail, onCheckoutValidation} = eventRegistration;
const { onPaymentSetup, onCheckoutFail, onCheckoutValidation } =
eventRegistration;
const { responseTypes } = emitResponse;
const [ paypalOrder, setPaypalOrder ] = useState( null );
const [gotoContinuationOnError, setGotoContinuationOnError] = useState(false);
const [ gotoContinuationOnError, setGotoContinuationOnError ] =
useState( false );
const [ paypalScriptLoaded, setPaypalScriptLoaded ] = useState( false );
if ( ! paypalScriptLoaded ) {
if ( ! paypalScriptPromise ) {
// for editor, since canMakePayment was not called
paypalScriptPromise = loadPaypalScriptPromise(config.scriptData)
paypalScriptPromise = loadPaypalScriptPromise( config.scriptData );
}
paypalScriptPromise.then( () => setPaypalScriptLoaded( true ) );
}
const methodId = fundingSource ? `${config.id}-${fundingSource}` : config.id;
const methodId = fundingSource
? `${ config.id }-${ fundingSource }`
: config.id;
useEffect( () => {
// fill the form if in continuation (for product or mini-cart buttons)
if (!config.scriptData.continuation || !config.scriptData.continuation.order || window.ppcpContinuationFilled) {
if (
! config.scriptData.continuation ||
! config.scriptData.continuation.order ||
window.ppcpContinuationFilled
) {
return;
}
try {
const paypalAddresses = paypalOrderToWcAddresses(config.scriptData.continuation.order);
const wcAddresses = wp.data.select('wc/store/cart').getCustomerData();
const paypalAddresses = paypalOrderToWcAddresses(
config.scriptData.continuation.order
);
const wcAddresses = wp.data
.select( 'wc/store/cart' )
.getCustomerData();
const addresses = mergeWcAddress( wcAddresses, paypalAddresses );
wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress);
wp.data
.dispatch( 'wc/store/cart' )
.setBillingAddress( addresses.billingAddress );
if ( shippingData.needsShipping ) {
wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress);
wp.data
.dispatch( 'wc/store/cart' )
.setShippingAddress( addresses.shippingAddress );
}
} catch ( err ) {
// sometimes the PayPal address is missing, skip in this case.
@ -83,7 +94,7 @@ const PayPalComponent = ({
}
// this useEffect should run only once, but adding this in case of some kind of full re-rendering
window.ppcpContinuationFilled = true;
}, [])
}, [] );
const createOrder = async ( data, actions ) => {
try {
@ -94,20 +105,29 @@ const PayPalComponent = ({
payment_method: 'ppcp-gateway',
funding_source: window.ppcpFundingSource ?? 'paypal',
createaccount: false,
...(data?.paymentSource && { payment_source: data.paymentSource })
...( data?.paymentSource && {
payment_source: data.paymentSource,
} ),
};
const res = await fetch(config.scriptData.ajax.create_order.endpoint, {
const res = await fetch(
config.scriptData.ajax.create_order.endpoint,
{
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify( requestBody ),
});
}
);
const json = await res.json();
if ( ! json.success ) {
if ( json.data?.details?.length > 0 ) {
throw new Error(json.data.details.map(d => `${d.issue} ${d.description}`).join('<br/>'));
throw new Error(
json.data.details
.map( ( d ) => `${ d.issue } ${ d.description }` )
.join( '<br/>' )
);
} else if ( json.data?.message ) {
throw new Error( json.data.message );
}
@ -128,12 +148,17 @@ const PayPalComponent = ({
const createSubscription = async ( data, actions ) => {
let planId = config.scriptData.subscription_plan_id;
if (config.scriptData.variable_paypal_subscription_variation_from_cart !== '') {
planId = config.scriptData.variable_paypal_subscription_variation_from_cart;
if (
config.scriptData
.variable_paypal_subscription_variation_from_cart !== ''
) {
planId =
config.scriptData
.variable_paypal_subscription_variation_from_cart;
}
return actions.subscription.create( {
'plan_id': planId
plan_id: planId,
} );
};
@ -142,9 +167,10 @@ const PayPalComponent = ({
const subscription = await actions.subscription.get();
if ( subscription ) {
const addresses = paypalSubscriptionToWcAddresses(subscription);
const addresses =
paypalSubscriptionToWcAddresses( subscription );
let promises = [
const promises = [
// save address on server
wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
billing_address: addresses.billingAddress,
@ -153,9 +179,17 @@ const PayPalComponent = ({
];
if ( shouldHandleShippingInPayPal() ) {
// set address in UI
promises.push(wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress));
promises.push(
wp.data
.dispatch( 'wc/store/cart' )
.setBillingAddress( addresses.billingAddress )
);
if ( shippingData.needsShipping ) {
promises.push(wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress))
promises.push(
wp.data
.dispatch( 'wc/store/cart' )
.setShippingAddress( addresses.shippingAddress )
);
}
}
await Promise.all( promises );
@ -163,27 +197,34 @@ const PayPalComponent = ({
setPaypalOrder( subscription );
const res = await fetch(config.scriptData.ajax.approve_subscription.endpoint, {
const res = await fetch(
config.scriptData.ajax.approve_subscription.endpoint,
{
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify( {
nonce: config.scriptData.ajax.approve_subscription.nonce,
nonce: config.scriptData.ajax.approve_subscription
.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID
})
});
subscription_id: data.subscriptionID,
} ),
}
);
const json = await res.json();
if ( ! json.success ) {
if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') {
if (
typeof actions !== 'undefined' &&
typeof actions.restart !== 'undefined'
) {
return actions.restart();
}
if ( json.data?.message ) {
throw new Error( json.data.message );
}
throw new Error(config.scriptData.labels.error.generic)
throw new Error( config.scriptData.labels.error.generic );
}
if ( ! shouldHandleShippingInPayPal() ) {
@ -207,9 +248,12 @@ const PayPalComponent = ({
const checkoutUrl = new URL( config.scriptData.redirect );
// sometimes some browsers may load some kind of cached version of the page,
// so adding a parameter to avoid that
checkoutUrl.searchParams.append('ppcp-continuation-redirect', (new Date()).getTime().toString());
checkoutUrl.searchParams.append(
'ppcp-continuation-redirect',
new Date().getTime().toString()
);
return checkoutUrl.toString();
}
};
const handleApprove = async ( data, actions ) => {
try {
@ -218,7 +262,7 @@ const PayPalComponent = ({
if ( order ) {
const addresses = paypalOrderToWcAddresses( order );
let promises = [
const promises = [
// save address on server
wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
billing_address: addresses.billingAddress,
@ -227,9 +271,17 @@ const PayPalComponent = ({
];
if ( shouldHandleShippingInPayPal() ) {
// set address in UI
promises.push(wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress));
promises.push(
wp.data
.dispatch( 'wc/store/cart' )
.setBillingAddress( addresses.billingAddress )
);
if ( shippingData.needsShipping ) {
promises.push(wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress))
promises.push(
wp.data
.dispatch( 'wc/store/cart' )
.setShippingAddress( addresses.shippingAddress )
);
}
}
await Promise.all( promises );
@ -237,27 +289,33 @@ const PayPalComponent = ({
setPaypalOrder( order );
const res = await fetch(config.scriptData.ajax.approve_order.endpoint, {
const res = await fetch(
config.scriptData.ajax.approve_order.endpoint,
{
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify( {
nonce: config.scriptData.ajax.approve_order.nonce,
order_id: data.orderID,
funding_source: window.ppcpFundingSource ?? 'paypal',
})
});
} ),
}
);
const json = await res.json();
if ( ! json.success ) {
if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') {
if (
typeof actions !== 'undefined' &&
typeof actions.restart !== 'undefined'
) {
return actions.restart();
}
if ( json.data?.message ) {
throw new Error( json.data.message );
}
throw new Error(config.scriptData.labels.error.generic)
throw new Error( config.scriptData.labels.error.generic );
}
if ( ! shouldHandleShippingInPayPal() ) {
@ -282,7 +340,10 @@ const PayPalComponent = ({
if ( config.scriptData.continuation ) {
return true;
}
if (gotoContinuationOnError && wp.data.select('wc/store/validation').hasValidationErrors()) {
if (
gotoContinuationOnError &&
wp.data.select( 'wc/store/validation' ).hasValidationErrors()
) {
location.href = getCheckoutRedirectUrl();
return { type: responseTypes.ERROR };
}
@ -307,8 +368,11 @@ const PayPalComponent = ({
return false;
}
return window.ppcpFundingSource !== 'venmo' || !config.scriptData.vaultingEnabled;
}
return (
window.ppcpFundingSource !== 'venmo' ||
! config.scriptData.vaultingEnabled
);
};
let handleShippingOptionsChange = null;
let handleShippingAddressChange = null;
@ -320,7 +384,9 @@ const PayPalComponent = ({
try {
const shippingOptionId = data.selectedShippingOption?.id;
if ( shippingOptionId ) {
await wp.data.dispatch('wc/store/cart').selectShippingRate(shippingOptionId);
await wp.data
.dispatch( 'wc/store/cart' )
.selectShippingRate( shippingOptionId );
await shippingData.setSelectedRates( shippingOptionId );
}
@ -330,7 +396,7 @@ const PayPalComponent = ({
body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
} ),
} );
const json = await res.json();
@ -347,7 +413,9 @@ const PayPalComponent = ({
handleShippingAddressChange = async ( data, actions ) => {
try {
const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress));
const address = paypalAddressToWc(
convertKeysToSnakeCase( data.shippingAddress )
);
await wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
shipping_address: address,
@ -361,7 +429,7 @@ const PayPalComponent = ({
body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
} ),
} );
const json = await res.json();
@ -380,7 +448,9 @@ const PayPalComponent = ({
try {
const shippingOptionId = data.selectedShippingOption?.id;
if ( shippingOptionId ) {
await wp.data.dispatch('wc/store/cart').selectShippingRate(shippingOptionId);
await wp.data
.dispatch( 'wc/store/cart' )
.selectShippingRate( shippingOptionId );
await shippingData.setSelectedRates( shippingOptionId );
}
} catch ( e ) {
@ -392,7 +462,9 @@ const PayPalComponent = ({
handleSubscriptionShippingAddressChange = async ( data, actions ) => {
try {
const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress));
const address = paypalAddressToWc(
convertKeysToSnakeCase( data.shippingAddress )
);
await wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
shipping_address: address,
@ -406,7 +478,7 @@ const PayPalComponent = ({
body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
} ),
} );
const json = await res.json();
@ -414,7 +486,6 @@ const PayPalComponent = ({
if ( ! json.success ) {
throw new Error( json.data.message );
}
} catch ( e ) {
console.error( e );
@ -434,9 +505,11 @@ const PayPalComponent = ({
type: responseTypes.SUCCESS,
meta: {
paymentMethodData: {
'paypal_order_id': config.scriptData.continuation.order_id,
'funding_source': window.ppcpFundingSource ?? 'paypal',
}
paypal_order_id:
config.scriptData.continuation.order_id,
funding_source:
window.ppcpFundingSource ?? 'paypal',
},
},
};
}
@ -447,8 +520,8 @@ const PayPalComponent = ({
type: responseTypes.SUCCESS,
meta: {
paymentMethodData: {
'paypal_order_id': paypalOrder.id,
'funding_source': window.ppcpFundingSource ?? 'paypal',
paypal_order_id: paypalOrder.id,
funding_source: window.ppcpFundingSource ?? 'paypal',
},
...addresses,
},
@ -464,7 +537,7 @@ const PayPalComponent = ({
return;
}
const unsubscribe = onCheckoutFail( ( { processingResponse } ) => {
console.error(processingResponse)
console.error( processingResponse );
if ( onClose ) {
onClose();
}
@ -481,31 +554,39 @@ const PayPalComponent = ({
if ( config.scriptData.continuation ) {
return (
<div dangerouslySetInnerHTML={{__html: config.scriptData.continuation.cancel.html}}>
</div>
)
<div
dangerouslySetInnerHTML={ {
__html: config.scriptData.continuation.cancel.html,
} }
></div>
);
}
if ( ! registeredContext ) {
buttonModuleWatcher.registerContextBootstrap(config.scriptData.context, {
buttonModuleWatcher.registerContextBootstrap(
config.scriptData.context,
{
createOrder: () => {
return createOrder();
},
onApprove: ( data, actions ) => {
return handleApprove( data, actions );
},
});
}
);
registeredContext = true;
}
const style = normalizeStyleForFundingSource(config.scriptData.button.style, fundingSource);
const style = normalizeStyleForFundingSource(
config.scriptData.button.style,
fundingSource
);
if ( ! paypalScriptLoaded ) {
return null;
}
const PayPalButton = paypal.Buttons.driver("react", { React, ReactDOM });
const PayPalButton = paypal.Buttons.driver( 'react', { React, ReactDOM } );
const getOnShippingOptionsChange = ( fundingSource ) => {
if ( fundingSource === 'venmo' ) {
@ -517,7 +598,7 @@ const PayPalComponent = ({
? handleShippingOptionsChange( data, actions )
: null;
};
}
};
const getOnShippingAddressChange = ( fundingSource ) => {
if ( fundingSource === 'venmo' ) {
@ -525,11 +606,13 @@ const PayPalComponent = ({
}
return ( data, actions ) => {
shouldHandleShippingInPayPal()
let shippingAddressChange = shouldHandleShippingInPayPal()
? handleShippingAddressChange( data, actions )
: null;
return shippingAddressChange;
};
};
}
if ( isPayPalSubscription( config.scriptData ) ) {
return (
@ -541,8 +624,12 @@ const PayPalComponent = ({
onError={ onClose }
createSubscription={ createSubscription }
onApprove={ handleApproveSubscription }
onShippingOptionsChange={getOnShippingOptionsChange(fundingSource)}
onShippingAddressChange={getOnShippingAddressChange(fundingSource)}
onShippingOptionsChange={ getOnShippingOptionsChange(
fundingSource
) }
onShippingAddressChange={ getOnShippingAddressChange(
fundingSource
) }
/>
);
}
@ -556,32 +643,33 @@ const PayPalComponent = ({
onError={ onClose }
createOrder={ createOrder }
onApprove={ handleApprove }
onShippingOptionsChange={getOnShippingOptionsChange(fundingSource)}
onShippingAddressChange={getOnShippingAddressChange(fundingSource)}
onShippingOptionsChange={ getOnShippingOptionsChange(
fundingSource
) }
onShippingAddressChange={ getOnShippingAddressChange(
fundingSource
) }
/>
);
}
};
const BlockEditorPayPalComponent = () => {
const urlParams = {
clientId: 'test',
...config.scriptData.url_params,
dataNamespace: 'ppcp-blocks-editor-paypal-buttons',
components: 'buttons',
}
};
return (
<PayPalScriptProvider
options={urlParams}
>
<PayPalScriptProvider options={ urlParams }>
<PayPalButtons
onClick={ ( data, actions ) => {
return false;
} }
/>
</PayPalScriptProvider>
)
}
);
};
const features = [ 'products' ];
let block_enabled = true;
@ -589,26 +677,26 @@ let block_enabled = true;
if ( cartHasSubscriptionProducts( config.scriptData ) ) {
// Don't show buttons on block cart page if using vault v2 and user is not logged in
if (
! config.scriptData.user.is_logged
&& config.scriptData.context === "cart-block"
&& ! isPayPalSubscription(config.scriptData) // using vaulting
&& ! config.scriptData?.save_payment_methods?.id_token // not vault v3
! config.scriptData.user.is_logged &&
config.scriptData.context === 'cart-block' &&
! isPayPalSubscription( config.scriptData ) && // using vaulting
! config.scriptData?.save_payment_methods?.id_token // not vault v3
) {
block_enabled = false;
}
// Don't render if vaulting disabled and is in vault subscription mode
if (
! isPayPalSubscription(config.scriptData)
&& ! config.scriptData.can_save_vault_token
! isPayPalSubscription( config.scriptData ) &&
! config.scriptData.can_save_vault_token
) {
block_enabled = false;
}
// Don't render buttons if in subscription mode and product not associated with a PayPal subscription
if (
isPayPalSubscription(config.scriptData)
&& !config.scriptData.subscription_product_allowed
isPayPalSubscription( config.scriptData ) &&
! config.scriptData.subscription_product_allowed
) {
block_enabled = false;
}
@ -617,13 +705,32 @@ if(cartHasSubscriptionProducts(config.scriptData)) {
}
if ( block_enabled && config.enabled ) {
if ((config.addPlaceOrderMethod || config.usePlaceOrder) && !config.scriptData.continuation) {
let descriptionElement = <div dangerouslySetInnerHTML={{__html: config.description}}></div>;
if (
( config.addPlaceOrderMethod || config.usePlaceOrder ) &&
! config.scriptData.continuation
) {
let descriptionElement = (
<div
dangerouslySetInnerHTML={ { __html: config.description } }
></div>
);
if ( config.placeOrderButtonDescription ) {
descriptionElement = <div>
<p dangerouslySetInnerHTML={{__html: config.description}}></p>
<p style={{textAlign: 'center'}} className={'ppcp-place-order-description'} dangerouslySetInnerHTML={{__html: config.placeOrderButtonDescription}}></p>
</div>;
descriptionElement = (
<div>
<p
dangerouslySetInnerHTML={ {
__html: config.description,
} }
></p>
<p
style={ { textAlign: 'center' } }
className={ 'ppcp-place-order-description' }
dangerouslySetInnerHTML={ {
__html: config.placeOrderButtonDescription,
} }
></p>
</div>
);
}
registerPaymentMethod( {
@ -637,7 +744,7 @@ if (block_enabled && config.enabled) {
return config.enabled;
},
supports: {
features: features,
features,
},
} );
}
@ -657,19 +764,34 @@ if (block_enabled && config.enabled) {
},
} );
} else if ( ! config.usePlaceOrder ) {
for (const fundingSource of ['paypal', ...config.enabledFundingSources]) {
for ( const fundingSource of [
'paypal',
...config.enabledFundingSources,
] ) {
registerExpressPaymentMethod( {
name: `${ config.id }-${ fundingSource }`,
paymentMethodId: config.id,
label: <div dangerouslySetInnerHTML={{__html: config.title}}/>,
content: <PayPalComponent isEditing={false} fundingSource={fundingSource}/>,
label: (
<div dangerouslySetInnerHTML={ { __html: config.title } } />
),
content: (
<PayPalComponent
isEditing={ false }
fundingSource={ fundingSource }
/>
),
edit: <BlockEditorPayPalComponent />,
ariaLabel: config.title,
canMakePayment: async () => {
if ( ! paypalScriptPromise ) {
paypalScriptPromise = loadPaypalScriptPromise(config.scriptData);
paypalScriptPromise = loadPaypalScriptPromise(
config.scriptData
);
paypalScriptPromise.then( () => {
const messagesBootstrap = new BlockCheckoutMessagesBootstrap(config.scriptData);
const messagesBootstrap =
new BlockCheckoutMessagesBootstrap(
config.scriptData
);
messagesBootstrap.init();
} );
}
@ -678,7 +800,7 @@ if (block_enabled && config.enabled) {
return paypal.Buttons( { fundingSource } ).isEligible();
},
supports: {
features: features,
features,
},
} );
}

View file

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import {CheckoutHandler} from "../Components/checkout-handler";
import { CheckoutHandler } from '../Components/checkout-handler';
test( 'checkbox label displays the given text', async () => {
render(

View file

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

View file

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

View file

@ -2,32 +2,34 @@ 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";
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 buttonsSpinner = new Spinner(
document.querySelector( '.ppc-button-wrapper' )
);
const cardsSpinner = new Spinner( '#ppcp-hosted-fields' );
const bootstrap = () => {
@ -37,39 +39,54 @@ const bootstrap = () => {
const errorHandler = new ErrorHandler(
PayPalCommerceGateway.labels.error.generic,
document.querySelector(checkoutFormSelector) ?? document.querySelector('.woocommerce-notices-wrapper')
document.querySelector( checkoutFormSelector ) ??
document.querySelector( '.woocommerce-notices-wrapper' )
);
const spinner = new Spinner();
const formSaver = new FormSaver(
PayPalCommerceGateway.ajax.save_checkout_form.endpoint,
PayPalCommerceGateway.ajax.save_checkout_form.nonce,
PayPalCommerceGateway.ajax.save_checkout_form.nonce
);
const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ?
new FormValidator(
const formValidator =
PayPalCommerceGateway.early_checkout_validation_enabled
? new FormValidator(
PayPalCommerceGateway.ajax.validate_checkout.endpoint,
PayPalCommerceGateway.ajax.validate_checkout.nonce,
) : null;
PayPalCommerceGateway.ajax.validate_checkout.nonce
)
: null;
const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, formValidator, spinner, errorHandler);
const freeTrialHandler = new FreeTrialHandler(
PayPalCommerceGateway,
checkoutFormSelector,
formSaver,
formValidator,
spinner,
errorHandler
);
new MultistepCheckoutHelper( checkoutFormSelector );
jQuery('form.woocommerce-checkout input').on('keydown', e => {
if (e.key === 'Enter' && [
jQuery( 'form.woocommerce-checkout input' ).on( 'keydown', ( e ) => {
if (
e.key === 'Enter' &&
[
PaymentMethods.PAYPAL,
PaymentMethods.CARDS,
PaymentMethods.CARD_BUTTON,
].includes(getCurrentPaymentMethod())) {
].includes( getCurrentPaymentMethod() )
) {
e.preventDefault();
}
} );
const hasMessages = () => {
return PayPalCommerceGateway.messages.is_hidden === false
&& document.querySelector(PayPalCommerceGateway.messages.wrapper);
}
return (
PayPalCommerceGateway.messages.is_hidden === false &&
document.querySelector( PayPalCommerceGateway.messages.wrapper )
);
};
const doBasicCheckoutValidation = () => {
if ( PayPalCommerceGateway.basic_checkout_validation_enabled ) {
@ -78,35 +95,61 @@ const bootstrap = () => {
// 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'));
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 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');
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('*', '')
let label = el
.querySelector( 'label' )
.textContent.replaceAll( '*', '' )
.trim();
if ( billingFieldsContainer?.contains( el ) ) {
label = PayPalCommerceGateway.labels.billing_field.replace('%s', label);
label =
PayPalCommerceGateway.labels.billing_field.replace(
'%s',
label
);
}
if ( shippingFieldsContainer?.contains( el ) ) {
label = PayPalCommerceGateway.labels.shipping_field.replace('%s', label);
label =
PayPalCommerceGateway.labels.shipping_field.replace(
'%s',
label
);
}
return PayPalCommerceGateway.labels.error.required.field
.replace('%s', `<strong>${label}</strong>`)
}).filter(s => s.length > 2);
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.message(
PayPalCommerceGateway.labels.error.required.generic
);
}
return false;
@ -121,7 +164,9 @@ const bootstrap = () => {
const onSmartButtonClick = async ( data, actions ) => {
window.ppcpFundingSource = data.fundingSource;
const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input');
const requiredFields = jQuery(
'form.woocommerce-checkout .validate-required:visible :input'
);
requiredFields.each( ( i, input ) => {
jQuery( input ).trigger( 'validate' );
} );
@ -136,15 +181,15 @@ const bootstrap = () => {
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
isFreeTrial &&
data.fundingSource !== 'card' &&
! PayPalCommerceGateway.subscription_plan_id &&
! PayPalCommerceGateway.vault_v3_enabled
) {
freeTrialHandler.handle();
return actions.reject();
@ -164,47 +209,67 @@ const bootstrap = () => {
buttonsSpinner.unblock();
};
let creditCardRenderer = new HostedFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner);
let creditCardRenderer = new HostedFieldsRenderer(
PayPalCommerceGateway,
errorHandler,
spinner
);
if ( typeof paypal.CardFields !== 'undefined' ) {
creditCardRenderer = new CardFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner, onCardFieldsBeforeSubmit);
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,
errorHandler
);
miniCartBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('mini-cart', miniCartBootstrap);
buttonModuleWatcher.registerContextBootstrap(
'mini-cart',
miniCartBootstrap
);
}
if (
context === 'product'
&& (
PayPalCommerceGateway.single_product_buttons_enabled === '1'
|| hasMessages()
)
context === 'product' &&
( PayPalCommerceGateway.single_product_buttons_enabled === '1' ||
hasMessages() )
) {
const singleProductBootstrap = new SingleProductBootstap(
PayPalCommerceGateway,
renderer,
errorHandler,
errorHandler
);
singleProductBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('product', singleProductBootstrap);
buttonModuleWatcher.registerContextBootstrap(
'product',
singleProductBootstrap
);
}
if ( context === 'cart' ) {
const cartBootstrap = new CartBootstrap(
PayPalCommerceGateway,
renderer,
errorHandler,
errorHandler
);
cartBootstrap.init();
@ -216,11 +281,14 @@ const bootstrap = () => {
PayPalCommerceGateway,
renderer,
spinner,
errorHandler,
errorHandler
);
checkoutBootstap.init();
buttonModuleWatcher.registerContextBootstrap('checkout', checkoutBootstap);
buttonModuleWatcher.registerContextBootstrap(
'checkout',
checkoutBootstap
);
}
if ( context === 'pay-now' ) {
@ -228,41 +296,44 @@ const bootstrap = () => {
PayPalCommerceGateway,
renderer,
spinner,
errorHandler,
errorHandler
);
payNowBootstrap.init();
buttonModuleWatcher.registerContextBootstrap('pay-now', payNowBootstrap);
buttonModuleWatcher.registerContextBootstrap(
'pay-now',
payNowBootstrap
);
}
const messagesBootstrap = new MessagesBootstrap(
PayPalCommerceGateway,
messageRenderer,
messageRenderer
);
messagesBootstrap.init();
apmButtonsInit( PayPalCommerceGateway );
};
document.addEventListener(
'DOMContentLoaded',
() => {
if (!typeof (PayPalCommerceGateway)) {
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
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),
]
...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.
@ -271,18 +342,26 @@ document.addEventListener(
// 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 !== '')
! [ '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 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
@ -296,7 +375,7 @@ document.addEventListener(
} else {
cardsSpinner.unblock();
}
}
};
jQuery( document ).on( 'hosted_fields_loaded', () => {
cardsSpinner.unblock();
@ -307,24 +386,30 @@ document.addEventListener(
hideOrderButtonIfPpcpGateway();
jQuery(document.body).on('updated_checkout payment_method_selected', () => {
jQuery( document.body ).on(
'updated_checkout payment_method_selected',
() => {
if ( bootstrapped || failed ) {
return;
}
hideOrderButtonIfPpcpGateway();
});
}
);
loadPaypalScript(PayPalCommerceGateway, () => {
loadPaypalScript(
PayPalCommerceGateway,
() => {
bootstrapped = true;
bootstrap();
}, () => {
},
() => {
failed = true;
setVisibleByClass( ORDER_BUTTON_SELECTOR, true, 'ppcp-hidden' );
buttonsSpinner.unblock();
cardsSpinner.unblock();
});
},
}
);
} );

View file

@ -1,9 +1,8 @@
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;
@ -13,7 +12,7 @@ class CartActionHandler {
return {
createSubscription: ( data, actions ) => {
return actions.subscription.create( {
'plan_id': subscription_plan_id
plan_id: subscription_plan_id,
} );
},
onApprove: ( data, actions ) => {
@ -24,36 +23,45 @@ class CartActionHandler {
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'
should_create_wc_order:
! context.config.vaultingEnabled ||
data.paymentSource !== 'venmo',
} ),
} )
}).then((res)=>{
.then( ( res ) => {
return res.json();
}).then((data) => {
} )
.then( ( data ) => {
if ( ! data.success ) {
console.log(data)
console.log( data );
throw Error( data.data.message );
}
let orderReceivedUrl = data.data?.order_received_url
const orderReceivedUrl = data.data?.order_received_url;
location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect;
location.href = orderReceivedUrl
? orderReceivedUrl
: context.config.redirect;
} );
},
onError: ( err ) => {
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] : '';
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'
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
@ -63,11 +71,13 @@ class CartActionHandler {
funding_source: window.ppcpFundingSource,
bn_code: bnCode,
payer,
context:this.config.context
context: this.config.context,
} ),
}).then(function(res) {
} )
.then( function ( res ) {
return res.json();
}).then(function(data) {
} )
.then( function ( data ) {
if ( ! data.success ) {
console.error( data );
throw Error( data.data.message );
@ -81,7 +91,7 @@ class CartActionHandler {
onApprove: onApprove( this, this.errorHandler ),
onError: ( error ) => {
this.errorHandler.genericError();
}
},
};
}
}

View file

@ -1,11 +1,10 @@
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;
@ -22,7 +21,7 @@ class CheckoutActionHandler {
}
return actions.subscription.create( {
'plan_id': subscription_plan_id
plan_id: subscription_plan_id,
} );
},
onApprove: ( data, actions ) => {
@ -32,43 +31,57 @@ class CheckoutActionHandler {
body: JSON.stringify( {
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID
subscription_id: data.subscriptionID,
} ),
} )
}).then((res)=>{
.then( ( res ) => {
return res.json();
}).then((data) => {
} )
.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] : '';
const bnCode =
typeof this.config.bn_codes[ this.config.context ] !==
'undefined'
? this.config.bn_codes[ this.config.context ]
: '';
const errorHandler = this.errorHandler;
const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review';
const formData = new FormData(document.querySelector(formSelector));
const formSelector =
this.config.context === 'checkout'
? 'form.checkout'
: 'form#order_review';
const 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 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'
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
@ -81,39 +94,55 @@ class CheckoutActionHandler {
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
createaccount,
save_payment_method: savePaymentMethod,
} ),
} )
}).then(function (res) {
.then( function ( res ) {
return res.json();
}).then(function (data) {
} )
.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' )
{
if ( typeof data.messages !== 'undefined' ) {
const domParser = new DOMParser();
errorHandler.appendPreparedErrorMessageElement(
domParser.parseFromString(data.messages, 'text/html')
domParser
.parseFromString(
data.messages,
'text/html'
)
.querySelector( 'ul' )
);
} else {
errorHandler.clear();
if ( data.data.refresh ) {
jQuery( document.body ).trigger( 'update_checkout' );
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/>'));
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() ] );
jQuery( document.body ).trigger( 'checkout_error', [
errorHandler.currentHtml(),
] );
}
throw { type: 'create-order-error', data: data.data };
@ -125,7 +154,7 @@ class CheckoutActionHandler {
document.querySelector( formSelector ).appendChild( input );
return data.data.id;
} );
}
};
return {
createOrder,
onApprove: onApprove( this, this.errorHandler, this.spinner ),
@ -141,56 +170,63 @@ class CheckoutActionHandler {
}
this.errorHandler.genericError();
}
}
},
};
}
addPaymentMethodConfiguration() {
return {
createVaultSetupToken: async () => {
const response = await fetch(this.config.ajax.create_setup_token.endpoint, {
method: "POST",
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()
const result = await response.json();
if ( result.data.id ) {
return result.data.id
return result.data.id;
}
console.error(result)
console.error( result );
},
onApprove: async ( { vaultSetupToken } ) => {
const response = await fetch(this.config.ajax.create_payment_token_for_guest.endpoint, {
method: "POST",
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,
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()
document.querySelector( '#place_order' ).click();
return;
}
console.error(result)
console.error( result );
},
onError: ( error ) => {
console.error(error)
}
}
console.error( error );
},
};
}
}

View file

@ -23,12 +23,13 @@ class FreeTrialHandler {
this.errorHandler = errorHandler;
}
async handle()
{
async handle() {
this.spinner.block();
try {
await this.formSaver.save(document.querySelector(this.formSelector));
await this.formSaver.save(
document.querySelector( this.formSelector )
);
} catch ( error ) {
console.error( error );
}
@ -36,13 +37,17 @@ class FreeTrialHandler {
try {
if ( this.formValidator ) {
try {
const errors = await this.formValidator.validate(document.querySelector(this.formSelector));
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() ] );
jQuery( document.body ).trigger( 'checkout_error', [
this.errorHandler.currentHtml(),
] );
return;
}
@ -54,7 +59,7 @@ class FreeTrialHandler {
const res = await fetch( this.config.ajax.vault_paypal.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {

View file

@ -1,19 +1,13 @@
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
) {
constructor( config, updateCart, formElement, errorHandler ) {
this.config = config;
this.updateCart = updateCart;
this.formElement = formElement;
@ -25,7 +19,7 @@ class SingleProductActionHandler {
return {
createSubscription: ( data, actions ) => {
return actions.subscription.create( {
'plan_id': subscription_plan
plan_id: subscription_plan,
} );
},
onApprove: ( data, actions ) => {
@ -35,49 +29,51 @@ class SingleProductActionHandler {
body: JSON.stringify( {
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID
subscription_id: data.subscriptionID,
} ),
} )
}).then((res)=>{
.then( ( res ) => {
return res.json();
}).then(() => {
} )
.then( () => {
const products = this.getSubscriptionProducts();
fetch( this.config.ajax.change_cart.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.config.ajax.change_cart.nonce,
products,
} ),
} )
}).then((result) => {
.then( ( result ) => {
return result.json();
}).then((result) => {
} )
.then( ( result ) => {
if ( ! result.success ) {
console.log(result)
console.log( result );
throw Error( result.data.message );
}
location.href = this.config.redirect;
})
} );
} );
},
onError: ( err ) => {
console.error( err );
}
}
},
};
}
getSubscriptionProducts()
{
getSubscriptionProducts() {
const id = document.querySelector( '[name="add-to-cart"]' ).value;
return [ new Product( id, 1, this.variations(), this.extraFields() ) ];
}
configuration()
{
configuration() {
return {
createOrder: this.createOrder(),
onApprove: onApprove( this, this.errorHandler ),
@ -99,37 +95,51 @@ class SingleProductActionHandler {
} else {
this.refreshMiniCart();
}
}
}
},
};
}
getProducts()
{
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())];
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) => {
this.formElement
.querySelectorAll( 'input[type="number"]' )
.forEach( ( element ) => {
if ( ! element.value ) {
return;
}
const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
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()));
})
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(
@ -139,23 +149,27 @@ class SingleProductActionHandler {
);
}
createOrder()
{
createOrder() {
this.cartHelper = null;
return ( data, actions, options = {} ) => {
this.errorHandler.clear();
const onResolve = ( purchase_units ) => {
this.cartHelper = (new CartHelper()).addFromPurchaseUnits(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] : '';
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'
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
@ -165,11 +179,13 @@ class SingleProductActionHandler {
bn_code: bnCode,
payment_method: PaymentMethods.PAYPAL,
funding_source: window.ppcpFundingSource,
context:this.config.context
context: this.config.context,
} ),
} )
}).then(function (res) {
.then( function ( res ) {
return res.json();
}).then(function (data) {
} )
.then( function ( data ) {
if ( ! data.success ) {
console.error( data );
throw Error( data.data.message );
@ -178,45 +194,48 @@ class SingleProductActionHandler {
} );
};
return this.updateCart.update(onResolve, this.getProducts(), options.updateCartOptions || {});
return this.updateCart.update(
onResolve,
this.getProducts(),
options.updateCartOptions || {}
);
};
}
variations()
{
variations() {
if ( ! this.hasVariations() ) {
return null;
}
return [...this.formElement.querySelectorAll("[name^='attribute_']")].map(
(element) => {
return [
...this.formElement.querySelectorAll( "[name^='attribute_']" ),
].map( ( element ) => {
return {
value: element.value,
name:element.name
}
}
);
name: element.name,
};
} );
}
hasVariations()
{
hasVariations() {
return this.formElement.classList.contains( 'variations_form' );
}
isGroupedProduct()
{
isGroupedProduct() {
return this.formElement.classList.contains( 'grouped_form' );
}
isBookingProduct()
{
isBookingProduct() {
// detection for "woocommerce-bookings" plugin
return !! this.formElement.querySelector( '.wc-booking-product-id' );
}
cleanCart() {
this.cartHelper.removeFromCart().then(() => {
this.cartHelper
.removeFromCart()
.then( () => {
this.refreshMiniCart();
}).catch(error => {
} )
.catch( ( error ) => {
this.refreshMiniCart();
} );
}
@ -224,6 +243,5 @@ class SingleProductActionHandler {
refreshMiniCart() {
jQuery( document.body ).trigger( 'wc_fragment_refresh' );
}
}
export default SingleProductActionHandler;

View file

@ -1,6 +1,4 @@
class ButtonModuleWatcher {
constructor() {
this.contextBootstrapRegistry = {};
this.contextBootstrapWatchers = [];
@ -13,19 +11,19 @@ class ButtonModuleWatcher {
registerContextBootstrap( context, handler ) {
this.contextBootstrapRegistry[ context ] = {
context: context,
handler: handler
}
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,5 +1,5 @@
import CartActionHandler from '../ActionHandler/CartActionHandler';
import BootstrapHelper from "../Helper/BootstrapHelper";
import BootstrapHelper from '../Helper/BootstrapHelper';
class CartBootstrap {
constructor( gateway, renderer, errorHandler ) {
@ -7,9 +7,13 @@ class CartBootstrap {
this.renderer = renderer;
this.errorHandler = errorHandler;
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
this.renderer.onButtonsInit(
this.gateway.button.wrapper,
() => {
this.handleButtonStatus();
}, true);
},
true
);
}
init() {
@ -18,32 +22,35 @@ class CartBootstrap {
this.handleButtonStatus();
}
jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
jQuery( document.body ).on(
'updated_cart_totals updated_checkout',
() => {
if ( this.shouldRender() ) {
this.render();
this.handleButtonStatus();
}
fetch(
this.gateway.ajax.cart_script_params.endpoint,
{
fetch( this.gateway.ajax.cart_script_params.endpoint, {
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
} )
.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);
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');
jQuery( this.gateway.button.wrapper ).trigger(
'ppcp-reload-buttons'
);
}
// handle button status
@ -59,10 +66,14 @@ class CartBootstrap {
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 );
@ -83,19 +94,26 @@ class CartBootstrap {
const actionHandler = new CartActionHandler(
PayPalCommerceGateway,
this.errorHandler,
this.errorHandler
);
if (
PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
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
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;
@ -105,9 +123,7 @@ class CartBootstrap {
return;
}
this.renderer.render(
actionHandler.configuration()
);
this.renderer.render( actionHandler.configuration() );
jQuery( document.body ).trigger( 'ppcp_cart_rendered' );
}

View file

@ -2,10 +2,11 @@ import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
import { setVisible, setVisibleByClass } from '../Helper/Hiding';
import {
getCurrentPaymentMethod,
isSavedCardSelected, ORDER_BUTTON_SELECTOR,
PaymentMethods
} from "../Helper/CheckoutMethodState";
import BootstrapHelper from "../Helper/BootstrapHelper";
isSavedCardSelected,
ORDER_BUTTON_SELECTOR,
PaymentMethods,
} from '../Helper/CheckoutMethodState';
import BootstrapHelper from '../Helper/BootstrapHelper';
class CheckoutBootstap {
constructor( gateway, renderer, spinner, errorHandler ) {
@ -16,9 +17,13 @@ class CheckoutBootstap {
this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR;
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
this.renderer.onButtonsInit(
this.gateway.button.wrapper,
() => {
this.handleButtonStatus();
}, true);
},
true
);
}
init() {
@ -29,39 +34,48 @@ class CheckoutBootstap {
// 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( '#saved-credit-card' ).val(
jQuery( '#saved-credit-card option:first' ).val()
);
jQuery( document.body ).on( 'updated_checkout', () => {
this.render()
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,
{
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 => {
} )
.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', () => {
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( 'ppcp_should_show_messages', ( e, data ) => {
@ -82,7 +96,11 @@ class CheckoutBootstap {
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() {
@ -93,8 +111,14 @@ class CheckoutBootstap {
if ( ! this.shouldRender() ) {
return;
}
if (document.querySelector(this.gateway.hosted_fields.wrapper + '>div')) {
document.querySelector(this.gateway.hosted_fields.wrapper + '>div').setAttribute('style', '');
if (
document.querySelector(
this.gateway.hosted_fields.wrapper + '>div'
)
) {
document
.querySelector( this.gateway.hosted_fields.wrapper + '>div' )
.setAttribute( 'style', '' );
}
const actionHandler = new CheckoutActionHandler(
PayPalCommerceGateway,
@ -103,14 +127,25 @@ class CheckoutBootstap {
);
if (
PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
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
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());
this.renderer.render(
actionHandler.subscriptionsConfiguration(
subscription_plan_id
),
{},
actionHandler.configuration()
);
if ( ! PayPalCommerceGateway.subscription_product_allowed ) {
this.gateway.button.is_disabled = true;
@ -121,38 +156,71 @@ class CheckoutBootstap {
}
if (
PayPalCommerceGateway.is_free_trial_cart
&& PayPalCommerceGateway.vault_v3_enabled
PayPalCommerceGateway.is_free_trial_cart &&
PayPalCommerceGateway.vault_v3_enabled
) {
this.renderer.render(actionHandler.addPaymentMethodConfiguration(), {}, actionHandler.configuration());
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 isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes(
currentPaymentMethod
);
const isGooglePayMethod =
currentPaymentMethod === PaymentMethods.GOOGLEPAY;
const isSavedCard = isCard && isSavedCardSelected();
const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway;
const isNotOurGateway =
! isPaypal &&
! isCard &&
! isSeparateButtonGateway &&
! isGooglePayMethod;
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== '';
const hasVaultedPaypal =
PayPalCommerceGateway.vaulted_paypal_email !== '';
const paypalButtonWrappers = {
...Object.entries(PayPalCommerceGateway.separate_buttons)
.reduce((result, [k, data]) => {
return {...result, [data.id]: data.wrapper}
}, {}),
...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');
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(
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 );
}
@ -164,14 +232,20 @@ class CheckoutBootstap {
}
}
setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod );
jQuery( document.body ).trigger( 'ppcp_checkout_rendered' );
}
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
const messagesWrapper = document.querySelector(
this.gateway.messages.wrapper
);
if (
getCurrentPaymentMethod() !== PaymentMethods.PAYPAL &&
messagesWrapper &&
jQuery( messagesWrapper ).closest( '.ppc-button-wrapper' ).length
) {
return false;
}
@ -180,30 +254,62 @@ class CheckoutBootstap {
}
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()
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()
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,5 +1,5 @@
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 ) {
@ -15,29 +15,37 @@ class MessagesBootstrap {
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', () => {
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) => {
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();
}
attemptDiscoverBlocks( retries ) {
return new Promise( ( resolve, reject ) => {
this.discoverBlocks().then(found => {
this.discoverBlocks().then( ( found ) => {
if ( ! found && retries > 0 ) {
setTimeout( () => {
this.attemptDiscoverBlocks(retries - 1).then(resolve);
this.attemptDiscoverBlocks( retries - 1 ).then(
resolve
);
}, 2000 ); // Wait 2 seconds before retrying
} else {
resolve();
@ -54,9 +62,11 @@ class MessagesBootstrap {
return;
}
Array.from(elements).forEach(blockElement => {
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
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' ) ) {
@ -73,13 +83,16 @@ class MessagesBootstrap {
return false;
}
const eventData = {result: true}
jQuery(document.body).trigger('ppcp_should_show_messages', [eventData, renderer.config.wrapper]);
const eventData = { result: true };
jQuery( document.body ).trigger( 'ppcp_should_show_messages', [
eventData,
renderer.config.wrapper,
] );
return eventData.result;
}
render() {
this.renderers.forEach(renderer => {
this.renderers.forEach( ( renderer ) => {
const shouldShow = this.shouldShow( renderer );
setVisible( renderer.config.wrapper, shouldShow );
if ( ! shouldShow ) {

View file

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

View file

@ -1,14 +1,14 @@
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)
super( gateway, renderer, spinner, errorHandler );
}
updateUi() {
if ( isChangePaymentPage() ) {
return
return;
}
super.updateUi();

View file

@ -1,29 +1,43 @@
import UpdateCart from "../Helper/UpdateCart";
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
import {hide, show} from "../Helper/Hiding";
import BootstrapHelper from "../Helper/BootstrapHelper";
import {loadPaypalJsScript} from "../Helper/ScriptLoading";
import {getPlanIdFromVariation} from "../Helper/Subscriptions"
import SimulateCart from "../Helper/SimulateCart";
import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils";
import merge from "deepmerge";
import UpdateCart from '../Helper/UpdateCart';
import SingleProductActionHandler from '../ActionHandler/SingleProductActionHandler';
import { hide, show } from '../Helper/Hiding';
import BootstrapHelper from '../Helper/BootstrapHelper';
import { loadPaypalJsScript } from '../Helper/ScriptLoading';
import { getPlanIdFromVariation } from '../Helper/Subscriptions';
import SimulateCart from '../Helper/SimulateCart';
import { strRemoveWord, strAddWord, throttle } from '../Helper/Utils';
import merge from 'deepmerge';
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
class SingleProductBootstap {
constructor( gateway, renderer, errorHandler ) {
this.gateway = gateway;
this.renderer = renderer;
this.errorHandler = errorHandler;
this.mutationObserver = new MutationObserver(this.handleChange.bind(this));
this.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);
this.simulateCartThrottled = throttle(
this.simulateCart.bind( this ),
this.gateway.simulate_cart.throttling || 5000
);
this.debouncedHandleChange = debounce(
this.handleChange.bind( this ),
100
);
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
this.renderer.onButtonsInit(
this.gateway.button.wrapper,
() => {
this.handleChange();
}, true);
},
true
);
this.subscriptionButtonsLoaded = false
this.subscriptionButtonsLoaded = false;
}
form() {
@ -31,7 +45,7 @@ class SingleProductBootstap {
}
handleChange() {
this.subscriptionButtonsLoaded = false
this.subscriptionButtonsLoaded = false;
if ( ! this.shouldRender() ) {
this.renderer.disableSmartButtons( this.gateway.button.wrapper );
@ -49,7 +63,7 @@ class SingleProductBootstap {
handleButtonStatus( simulateCart = true ) {
BootstrapHelper.handleButtonStatus( this, {
formSelector: this.formSelector
formSelector: this.formSelector,
} );
if ( simulateCart ) {
@ -65,15 +79,21 @@ class SingleProductBootstap {
}
jQuery( document ).on( 'change', this.formSelector, () => {
this.handleChange();
this.debouncedHandleChange();
} );
this.mutationObserver.observe( form, {
childList: true,
subtree: true,
} );
this.mutationObserver.observe(form, { childList: true, subtree: true });
const addToCartButton = form.querySelector('.single_add_to_cart_button');
const addToCartButton = form.querySelector(
'.single_add_to_cart_button'
);
if ( addToCartButton ) {
(new MutationObserver(this.handleButtonStatus.bind(this)))
.observe(addToCartButton, { attributes : true });
new MutationObserver(
this.handleButtonStatus.bind( this )
).observe( addToCartButton, { attributes: true } );
}
jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => {
@ -91,37 +111,58 @@ class SingleProductBootstap {
}
shouldRender() {
return this.form() !== null
&& !this.isWcsattSubscriptionMode();
return this.form() !== null && ! this.isWcsattSubscriptionMode();
}
shouldEnable() {
const form = this.form();
const addToCartButton = form ? form.querySelector('.single_add_to_cart_button') : null;
const addToCartButton = form
? form.querySelector( '.single_add_to_cart_button' )
: null;
return BootstrapHelper.shouldEnable(this)
&& !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,
() =>
document.querySelector(
'form.cart ins .woocommerce-Price-amount'
)?.innerText,
() =>
document.querySelector( 'form.cart .woocommerce-Price-amount' )
?.innerText,
() => {
const priceEl = document.querySelector('.product .woocommerce-Price-amount');
const priceEl = document.querySelector(
'.product .woocommerce-Price-amount'
);
// variable products show price like 10.00 - 20.00 here
// 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'));
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);
]
.map( ( f ) => f() )
.find( ( val ) => val );
if ( typeof priceText === 'undefined' ) {
return returnOnUndefined;
@ -131,7 +172,9 @@ class SingleProductBootstap {
return 0;
}
return parseFloat(priceText.replace(/,/g, '.').replace(/([^\d,\.\s]*)/g, ''));
return parseFloat(
priceText.replace( /,/g, '.' ).replace( /([^\d,\.\s]*)/g, '' )
);
}
priceAmountIsZero() {
@ -147,8 +190,14 @@ class SingleProductBootstap {
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
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() {
@ -156,18 +205,22 @@ class SingleProductBootstap {
return null;
}
return [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map(
(element) => {
return [
...document
.querySelector( 'form.cart' )
?.querySelectorAll( "[name^='attribute_']" ),
].map( ( element ) => {
return {
value: element.value,
name: element.name
}
}
);
name: element.name,
};
} );
}
hasVariations() {
return document.querySelector('form.cart')?.classList.contains('variations_form');
return document
.querySelector( 'form.cart' )
?.classList.contains( 'variations_form' );
}
render() {
@ -175,45 +228,48 @@ class SingleProductBootstap {
this.gateway,
new UpdateCart(
this.gateway.ajax.change_cart.endpoint,
this.gateway.ajax.change_cart.nonce,
this.gateway.ajax.change_cart.nonce
),
this.form(),
this.errorHandler,
this.errorHandler
);
if (
PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
PayPalCommerceGateway.data_client_id.has_subscriptions &&
PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) {
const buttonWrapper = document.getElementById('ppc-button-ppcp-gateway');
const buttonWrapper = document.getElementById(
'ppc-button-ppcp-gateway'
);
buttonWrapper.innerHTML = '';
const subscription_plan = this.variations() !== null
const subscription_plan =
this.variations() !== null
? getPlanIdFromVariation( this.variations() )
: PayPalCommerceGateway.subscription_plan_id
: PayPalCommerceGateway.subscription_plan_id;
if ( ! subscription_plan ) {
return;
}
if(this.subscriptionButtonsLoaded) return
if ( this.subscriptionButtonsLoaded ) {
return;
}
loadPaypalJsScript(
{
clientId: PayPalCommerceGateway.client_id,
currency: PayPalCommerceGateway.currency,
intent: 'subscription',
vault: true
vault: true,
},
actionHandler.subscriptionsConfiguration( subscription_plan ),
this.gateway.button.wrapper
);
this.subscriptionButtonsLoaded = true
this.subscriptionButtonsLoaded = true;
return;
}
this.renderer.render(
actionHandler.configuration()
);
this.renderer.render( actionHandler.configuration() );
}
simulateCart() {
@ -225,29 +281,35 @@ class SingleProductBootstap {
null,
null,
this.form(),
this.errorHandler,
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();
(new SimulateCart(
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]);
this.gateway.ajax.simulate_cart.nonce
).simulate( ( data ) => {
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}});
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}});
newData = merge( newData, {
messages: { is_hidden: data.messages.is_hidden },
} );
}
if ( newData ) {
BootstrapHelper.updateScriptData( this, newData );
@ -260,27 +322,39 @@ class SingleProductBootstap {
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)) {
for ( const [ fundingSource, funding ] of Object.entries(
data.funding
) ) {
if ( funding.enabled === true ) {
enableFunding = strAddWord( enableFunding, fundingSource );
disableFunding = strRemoveWord(disableFunding, fundingSource);
disableFunding = strRemoveWord(
disableFunding,
fundingSource
);
} else if ( funding.enabled === false ) {
enableFunding = strRemoveWord(enableFunding, fundingSource);
disableFunding = strAddWord(disableFunding, fundingSource);
enableFunding = strRemoveWord(
enableFunding,
fundingSource
);
disableFunding = strAddWord(
disableFunding,
fundingSource
);
}
}
if (
(enableFunding !== this.gateway.url_params['enable-funding']) ||
(disableFunding !== this.gateway.url_params['disable-funding'])
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');
jQuery( this.gateway.button.wrapper ).trigger(
'ppcp-reload-buttons'
);
}
this.handleButtonStatus( false );
}, products );
}
}

View file

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

View file

@ -1,7 +1,6 @@
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;
@ -10,8 +9,8 @@ class BookingProduct extends Product {
data() {
return {
...super.data(),
booking: this.booking
}
booking: this.booking,
};
}
}

View file

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

View file

@ -1,30 +1,26 @@
class ErrorHandler {
/**
* @param {String} genericErrorText
* @param {string} genericErrorText
* @param {Element} wrapper
*/
constructor(genericErrorText, wrapper)
{
constructor( genericErrorText, wrapper ) {
this.genericErrorText = genericErrorText;
this.wrapper = wrapper;
}
genericError() {
this.clear();
this.message(this.genericErrorText)
this.message( this.genericErrorText );
}
appendPreparedErrorMessageElement(errorMessageElement)
{
appendPreparedErrorMessageElement( errorMessageElement ) {
this._getMessageContainer().replaceWith( errorMessageElement );
}
/**
* @param {String} text
* @param {string} text
*/
message(text)
{
message( text ) {
this._addMessage( text );
this._scrollToMessages();
@ -33,51 +29,46 @@ class ErrorHandler {
/**
* @param {Array} texts
*/
messages(texts)
{
texts.forEach(t => this._addMessage(t));
messages( texts ) {
texts.forEach( ( t ) => this._addMessage( t ) );
this._scrollToMessages();
}
/**
* @returns {String}
* @return {string}
*/
currentHtml()
{
currentHtml() {
const messageContainer = this._getMessageContainer();
return messageContainer.outerHTML;
}
/**
* @private
* @param {String} text
* @param {string} text
*/
_addMessage(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();
let messageNode = this._prepareMessageElement(text);
const messageNode = this._prepareMessageElement( text );
messageContainer.appendChild( messageNode );
}
/**
* @private
*/
_scrollToMessages()
{
_scrollToMessages() {
jQuery.scroll_to_notices( jQuery( '.woocommerce-error' ) );
}
/**
* @private
*/
_getMessageContainer()
{
_getMessageContainer() {
let messageContainer = document.querySelector( 'ul.woocommerce-error' );
if ( messageContainer === null ) {
messageContainer = document.createElement( 'ul' );
@ -89,18 +80,17 @@ class ErrorHandler {
}
/**
* @param message
* @private
*/
_prepareMessageElement(message)
{
_prepareMessageElement( message ) {
const li = document.createElement( 'li' );
li.innerHTML = message;
return li;
}
clear()
{
clear() {
jQuery( '.woocommerce-error, .woocommerce-message' ).remove();
}
}

View file

@ -1,4 +1,3 @@
export const apmButtonsInit = ( config, selector = '.ppcp-button-apm' ) => {
let selectorInContainer = selector;
@ -7,10 +6,10 @@ export const apmButtonsInit = (config, selector = '.ppcp-button-apm') => {
}
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;
const isSeparateGateways =
jQuery( wrapper ).children( 'div[class^="item-"]' ).length > 0;
if ( isSeparateGateways ) {
selector += `, ${ wrapper } div[class^="item-"]`;
@ -19,10 +18,9 @@ export const apmButtonsInit = (config, selector = '.ppcp-button-apm') => {
}
window.ppcpApmButtons = new ApmButtons( selector, selectorInContainer );
}
};
export class ApmButtons {
constructor( selector, selectorInContainer ) {
this.selector = selector;
this.selectorInContainer = selectorInContainer;
@ -32,31 +30,39 @@ export class ApmButtons {
this.reloadContainers();
// Refresh button layout.
jQuery(window).resize(() => {
jQuery( window )
.resize( () => {
this.refresh();
}).resize();
} )
.resize();
jQuery( document ).on( 'ppcp-smart-buttons-init', () => {
this.refresh();
} );
jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => {
jQuery( document ).on(
'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
( ev, data ) => {
this.refresh();
setTimeout( this.refresh.bind( this ), 200 );
});
}
);
// Observes for new buttons.
(new MutationObserver(this.observeElementsCallback.bind(this)))
.observe(document.body, { childList: true, subtree: true });
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';
const observeSelector =
this.selector +
', .widget_shopping_cart, .widget_shopping_cart_content';
let shouldReload = false;
for (let mutation of mutationsList) {
for ( const mutation of mutationsList ) {
if ( mutation.type === 'childList' ) {
mutation.addedNodes.forEach(node => {
mutation.addedNodes.forEach( ( node ) => {
if ( node.matches && node.matches( observeSelector ) ) {
shouldReload = true;
}
@ -68,12 +74,12 @@ export class ApmButtons {
this.reloadContainers();
this.refresh();
}
};
}
reloadContainers() {
jQuery( this.selector ).each( ( index, el ) => {
const parent = jQuery( el ).parent();
if (!this.containers.some($el => $el.is(parent))) {
if ( ! this.containers.some( ( $el ) => $el.is( parent ) ) ) {
this.containers.push( parent );
}
} );
@ -86,7 +92,9 @@ export class ApmButtons {
// 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' );
@ -110,11 +118,12 @@ export class ApmButtons {
const minMargin = 11; // Minimum margin.
const height = $el.height();
const margin = Math.max(minMargin, Math.round(height * 0.3));
const margin = Math.max(
minMargin,
Math.round( height * 0.3 )
);
$el.css( 'margin-top', `${ margin }px` );
} );
}
}
}

View file

@ -1,11 +1,10 @@
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;
@ -23,7 +22,9 @@ export default class BootstrapHelper {
}
if ( wasDisabled !== ! shouldEnable ) {
jQuery(options.wrapper).trigger('ppcp_buttons_enabled_changed', [shouldEnable]);
jQuery( options.wrapper ).trigger( 'ppcp_buttons_enabled_changed', [
shouldEnable,
] );
}
}
@ -33,19 +34,21 @@ export default class BootstrapHelper {
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 );
const isChanged = JSON.stringify(bs.gateway) !== JSON.stringify(newObj);
const isChanged =
JSON.stringify( bs.gateway ) !== JSON.stringify( newObj );
bs.gateway = newObj;
if ( isChanged ) {
jQuery(document.body).trigger('ppcp_script_data_changed', [newObj]);
jQuery( document.body ).trigger( 'ppcp_script_data_changed', [
newObj,
] );
}
}
}

View file

@ -1,31 +1,31 @@
/**
* @param selectorOrElement
* @returns {Element}
* @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
handler: 'ButtonsDisabler.setEnabled',
action: 'enable',
selector: selectorOrElement,
element,
} );
}
};
const triggerDisabled = ( selectorOrElement, element ) => {
jQuery( document ).trigger( 'ppcp-disabled', {
'handler': 'ButtonsDisabler.setEnabled',
'action': 'disable',
'selector': selectorOrElement,
'element': element
handler: 'ButtonsDisabler.setEnabled',
action: 'disable',
selector: selectorOrElement,
element,
} );
}
};
export const setEnabled = ( selectorOrElement, enable, form = null ) => {
const element = getElement( selectorOrElement );
@ -42,7 +42,6 @@ export const setEnabled = (selectorOrElement, enable, form = null) => {
.css( 'pointer-events', '' );
triggerEnabled( selectorOrElement, element );
} else {
jQuery( element )
.addClass( 'ppcp-disabled' )
@ -51,8 +50,12 @@ export const setEnabled = (selectorOrElement, enable, form = null) => {
if ( form ) {
// Trigger form submit to show the error message
let $form = jQuery(form);
if ($form.find('.single_add_to_cart_button').hasClass('disabled')) {
const $form = jQuery( form );
if (
$form
.find( '.single_add_to_cart_button' )
.hasClass( 'disabled' )
) {
$form.find( ':submit' ).trigger( 'click' );
}
}

View file

@ -31,7 +31,13 @@ export function setupButtonEvents(refresh) {
// 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);
document.body.addEventListener(
'wc_fragments_loaded',
debouncedRefresh
);
document.body.addEventListener(
'wc_fragments_refreshed',
debouncedRefresh
);
}, miniCartInitDelay );
}

View file

@ -47,4 +47,4 @@ export const cardFieldStyles = (field) => {
} );
return styles;
}
};

View file

@ -1,14 +1,15 @@
class CartHelper {
constructor(cartItemKeys = [])
{
constructor( cartItemKeys = [] ) {
this.cartItemKeys = cartItemKeys;
}
getEndpoint() {
let ajaxUrl = "/?wc-ajax=%%endpoint%%";
let ajaxUrl = '/?wc-ajax=%%endpoint%%';
if ((typeof wc_cart_fragments_params !== 'undefined') && wc_cart_fragments_params.wc_ajax_url) {
if (
typeof wc_cart_fragments_params !== 'undefined' &&
wc_cart_fragments_params.wc_ajax_url
) {
ajaxUrl = wc_cart_fragments_params.wc_ajax_url;
}
@ -28,8 +29,7 @@ class CartHelper {
return this;
}
removeFromCart()
{
removeFromCart() {
return new Promise( ( resolve, reject ) => {
if ( ! this.cartItemKeys || ! this.cartItemKeys.length ) {
resolve();
@ -44,7 +44,7 @@ class CartHelper {
if ( numResponses >= numRequests ) {
resolve();
}
}
};
for ( const cartItemKey of this.cartItemKeys ) {
const params = new URLSearchParams();
@ -58,12 +58,15 @@ class CartHelper {
fetch( this.getEndpoint(), {
method: 'POST',
credentials: 'same-origin',
body: params
}).then(function (res) {
body: params,
} )
.then( function ( res ) {
return res.json();
}).then(() => {
} )
.then( () => {
tryToResolve();
}).catch(() => {
} )
.catch( () => {
tryToResolve();
} );
}

View file

@ -1,6 +1,6 @@
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 ) => {
@ -11,38 +11,45 @@ const validateCheckoutForm = function (config) {
document.querySelector( '.woocommerce-notices-wrapper' )
);
const formSelector = config.context === 'checkout' ? 'form.checkout' : 'form#order_review';
const formValidator = config.early_checkout_validation_enabled ?
new FormValidator(
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;
config.ajax.validate_checkout.nonce
)
: null;
if ( ! formValidator ) {
resolve();
return;
}
formValidator.validate(document.querySelector(formSelector)).then((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() ] );
jQuery( document.body ).trigger( 'checkout_error', [
errorHandler.currentHtml(),
] );
reject();
} else {
resolve();
}
} );
} catch ( error ) {
console.error( error );
reject();
}
} );
}
};
export default validateCheckoutForm;

View file

@ -3,6 +3,7 @@ export const PaymentMethods = {
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';

View file

@ -6,12 +6,16 @@ const dccInputFactory = (original) => {
newElement.setAttribute( 'class', original.className );
Object.values( styles ).forEach( ( prop ) => {
if (! styles[prop] || ! isNaN(prop) || prop === 'background-image' ) {
if (
! styles[ prop ] ||
! isNaN( prop ) ||
prop === 'background-image'
) {
return;
}
newElement.style.setProperty( prop, '' + styles[ prop ] );
} );
return newElement;
}
};
export default dccInputFactory;

View file

@ -1,12 +1,10 @@
/**
* Common Form utility methods
*/
export default class FormHelper {
static getPrefixedFields( formElement, prefix ) {
const formData = new FormData( formElement );
let fields = {};
const fields = {};
for ( const [ name, value ] of formData.entries() ) {
if ( ! prefix || name.startsWith( prefix ) ) {
@ -19,11 +17,10 @@ export default class FormHelper {
static getFilteredFields( formElement, exactFilters, prefixFilters ) {
const formData = new FormData( formElement );
let fields = {};
let counters = {};
const fields = {};
const counters = {};
for ( let [ name, value ] of formData.entries() ) {
// Handle array format
if ( name.indexOf( '[]' ) !== -1 ) {
const k = name;
@ -35,10 +32,15 @@ export default class FormHelper {
if ( ! name ) {
continue;
}
if (exactFilters && (exactFilters.indexOf(name) !== -1)) {
if ( exactFilters && exactFilters.indexOf( name ) !== -1 ) {
continue;
}
if (prefixFilters && prefixFilters.some(prefixFilter => name.startsWith(prefixFilter))) {
if (
prefixFilters &&
prefixFilters.some( ( prefixFilter ) =>
name.startsWith( prefixFilter )
)
) {
continue;
}

View file

@ -10,7 +10,7 @@ export default class FormSaver {
const res = await fetch( this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {

View file

@ -10,7 +10,7 @@ export default class FormValidator {
const res = await fetch( this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {

View file

@ -1,35 +1,39 @@
/**
* @param selectorOrElement
* @returns {Element}
* @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
handler,
action: 'hide',
selector: selectorOrElement,
element,
} );
}
};
const triggerShown = ( handler, selectorOrElement, element ) => {
jQuery( document ).trigger( 'ppcp-shown', {
'handler': handler,
'action': 'show',
'selector': selectorOrElement,
'element': element
handler,
action: 'show',
selector: selectorOrElement,
element,
} );
}
};
export const isVisible = ( element ) => {
return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}
return !! (
element.offsetWidth ||
element.offsetHeight ||
element.getClientRects().length
);
};
export const setVisible = ( selectorOrElement, show, important = false ) => {
const element = getElement( selectorOrElement );
@ -44,9 +48,12 @@ export const setVisible = (selectorOrElement, show, important = false) => {
return;
}
element.style.setProperty('display', 'none', important ? 'important' : '');
element.style.setProperty(
'display',
'none',
important ? 'important' : ''
);
triggerHidden( 'Hiding.setVisible', selectorOrElement, element );
} else {
if ( currentValue === 'none' ) {
element.style.removeProperty( 'display' );

View file

@ -14,7 +14,6 @@ const DEFAULT_TRIGGER_ELEMENT_SELECTOR = '.woocommerce-checkout-payment';
* @property {boolean} isVisible - Whether the triggerElement is visible.
*/
class MultistepCheckoutHelper {
/**
* Selector that defines the HTML element we are waiting to become visible.
* @type {string}
@ -45,7 +44,8 @@ class MultistepCheckoutHelper {
*/
constructor( formSelector, triggerElementSelector = '' ) {
this.#formSelector = formSelector;
this.#triggerElementSelector = triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR;
this.#triggerElementSelector =
triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR;
this.#intervalId = false;
/*
@ -61,7 +61,7 @@ class MultistepCheckoutHelper {
/**
* The checkout form element.
* @returns {Element|null} - Form element or null.
* @return {Element|null} - Form element or null.
*/
get form() {
return document.querySelector( this.#formSelector );
@ -69,7 +69,7 @@ class MultistepCheckoutHelper {
/**
* The element which must be visible before payment buttons should be initialized.
* @returns {Element|null} - Trigger element or null.
* @return {Element|null} - Trigger element or null.
*/
get triggerElement() {
return this.form?.querySelector( this.#triggerElementSelector );
@ -77,7 +77,7 @@ class MultistepCheckoutHelper {
/**
* Checks the visibility of the payment button wrapper.
* @returns {boolean} - returns boolean value on the basis of visibility of element.
* @return {boolean} - returns boolean value on the basis of visibility of element.
*/
get isVisible() {
const box = this.triggerElement?.getBoundingClientRect();
@ -91,7 +91,10 @@ class MultistepCheckoutHelper {
*/
start() {
this.stop();
this.#intervalId = setInterval(() => this.checkElement(), this.#intervalTime);
this.#intervalId = setInterval(
() => this.checkElement(),
this.#intervalTime
);
}
/**

View file

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

View file

@ -1,17 +1,17 @@
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 || {
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.
@ -36,7 +36,7 @@ export const loadPaypalScript = (config, onLoaded, onError = null) => {
options.isLoading = false;
options.onLoadedCallbacks = [];
options.onErrorCallbacks = [];
}
};
// Callback to be called once the PayPal script is loaded.
const callback = ( paypal ) => {
@ -47,14 +47,14 @@ export const loadPaypalScript = (config, onLoaded, onError = null) => {
}
resetState();
}
};
const errorCallback = ( err ) => {
for ( const onErrorCallback of options.onErrorCallbacks ) {
onErrorCallback( err );
}
resetState();
}
};
// Build the PayPal script options.
let scriptOptions = keysToCamelCase( config.url_params );
@ -72,7 +72,12 @@ export const loadPaypalScript = (config, onLoaded, onError = null) => {
// 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);
dataClientIdAttributeHandler(
scriptOptions,
config.data_client_id,
callback,
errorCallback
);
return;
}
@ -83,27 +88,23 @@ export const loadPaypalScript = (config, onLoaded, onError = null) => {
}
// Load PayPal script
loadScript(scriptOptions)
.then(callback)
.catch(errorCallback);
}
loadScript( scriptOptions ).then( callback ).catch( errorCallback );
};
export const loadPaypalScriptPromise = ( config ) => {
return new Promise( ( resolve, reject ) => {
loadPaypalScript(config, 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);
loadScript( options ).then( resolve ).catch( reject );
} );
}
};

View file

@ -1,5 +1,5 @@
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.
@ -7,36 +7,41 @@ import {convertKeysToSnakeCase} from "../../../../../ppcp-blocks/resources/js/He
* @param data
* @param actions
* @param config
* @returns {Promise<void>}
* @return {Promise<void>}
*/
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, {
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,
'X-WC-Store-API-Nonce':
config.ajax.update_customer_shipping.wp_rest_nonce,
},
body: JSON.stringify( {
rate_id: shippingOptionId,
})
})
.then(response => {
} ),
}
)
.then( ( response ) => {
return response.json();
} )
.then(cardData => {
const shippingMethods = document.querySelectorAll('.shipping_method');
.then( ( cardData ) => {
const shippingMethods =
document.querySelectorAll( '.shipping_method' );
shippingMethods.forEach( function ( method ) {
if ( method.value === shippingOptionId ) {
method.checked = true;
}
} );
})
} );
}
if ( ! config.data_client_id.has_subscriptions ) {
@ -46,7 +51,7 @@ export const handleShippingOptionsChange = async (data, actions, config) => {
body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
} ),
} );
const json = await res.json();
@ -68,18 +73,22 @@ export const handleShippingOptionsChange = async (data, actions, config) => {
* @param data
* @param actions
* @param config
* @returns {Promise<void>}
* @return {Promise<void>}
*/
export const handleShippingAddressChange = async ( data, actions, config ) => {
try {
const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress));
const address = paypalAddressToWc(
convertKeysToSnakeCase( data.shippingAddress )
);
// Retrieve current cart contents
await fetch(config.ajax.update_customer_shipping.shipping_address.cart_endpoint)
.then(response => {
await fetch(
config.ajax.update_customer_shipping.shipping_address.cart_endpoint
)
.then( ( response ) => {
return response.json();
} )
.then(cartData => {
.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;
@ -89,22 +98,36 @@ export const handleShippingAddressChange = async (data, actions, config) => {
cartData.shipping_address.country = address.country;
// Send update request
return fetch(config.ajax.update_customer_shipping.shipping_address.update_customer_endpoint, {
return fetch(
config.ajax.update_customer_shipping.shipping_address
.update_customer_endpoint,
{
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce,
'X-WC-Store-API-Nonce':
config.ajax.update_customer_shipping
.wp_rest_nonce,
},
body: JSON.stringify( {
shipping_address: cartData.shipping_address,
})
}).then(function (res) {
} ),
}
)
.then( function ( res ) {
return res.json();
}).then(function (customerData) {
jQuery(".cart_totals .shop_table").load(location.href + " " + ".cart_totals .shop_table" + ">*", "");
})
} )
.then( function ( customerData ) {
jQuery( '.cart_totals .shop_table' ).load(
location.href +
' ' +
'.cart_totals .shop_table' +
'>*',
''
);
} );
} );
const res = await fetch( config.ajax.update_shipping.endpoint, {
method: 'POST',
@ -112,7 +135,7 @@ export const handleShippingAddressChange = async (data, actions, config) => {
body: JSON.stringify( {
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
} ),
} );
const json = await res.json();

View file

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

View file

@ -1,5 +1,4 @@
class Spinner {
constructor( target = 'form.woocommerce-checkout' ) {
this.target = target;
}
@ -9,18 +8,16 @@ class Spinner {
}
block() {
jQuery( this.target ).block( {
message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6
}
opacity: 0.6,
},
} );
}
unblock() {
jQuery( this.target ).unblock();
}
}

View file

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

View file

@ -1,20 +1,28 @@
export const isChangePaymentPage = () => {
const urlParams = new URLSearchParams(window.location.search)
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 = {};
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;
}
Object.assign( obj, {
[ name.replace( 'attribute_', '' ) ]: value,
} );
} );
return subscription_plan;
if (
JSON.stringify( obj ) ===
JSON.stringify( element.attributes ) &&
element.subscription_plan !== ''
) {
subscription_plan = element.subscription_plan;
}
}
);
return subscription_plan;
};

View file

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

View file

@ -2,20 +2,20 @@ export const toCamelCase = (str) => {
return str.replace( /([-_]\w)/g, function ( match ) {
return match[ 1 ].toUpperCase();
} );
}
};
export const keysToCamelCase = ( obj ) => {
let output = {};
const 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);
const arr = str.split( separator );
if ( ! arr.includes( word ) ) {
arr.push( word );
}
@ -23,8 +23,8 @@ export const strAddWord = (str, word, separator = ',') => {
};
export const strRemoveWord = ( str, word, separator = ',' ) => {
let arr = str.split(separator);
let index = arr.indexOf(word);
const arr = str.split( separator );
const index = arr.indexOf( word );
if ( index !== -1 ) {
arr.splice( index, 1 );
}
@ -56,14 +56,14 @@ export const throttle = (func, limit) => {
lastContext = this;
}
};
}
};
const Utils = {
toCamelCase,
keysToCamelCase,
strAddWord,
strRemoveWord,
throttle
throttle,
};
export default Utils;

View file

@ -3,32 +3,36 @@ const onApprove = (context, errorHandler) => {
return fetch( context.config.ajax.approve_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'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'
should_create_wc_order:
! context.config.vaultingEnabled ||
data.paymentSource !== 'venmo',
} ),
} )
}).then((res)=>{
.then( ( res ) => {
return res.json();
}).then((data)=>{
} )
.then( ( data ) => {
if ( ! data.success ) {
errorHandler.genericError();
return actions.restart().catch(err => {
return actions.restart().catch( ( err ) => {
errorHandler.genericError();
} );
}
let orderReceivedUrl = data.data?.order_received_url
location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect;
const orderReceivedUrl = data.data?.order_received_url;
location.href = orderReceivedUrl
? orderReceivedUrl
: context.config.redirect;
} );
}
}
};
};
export default onApprove;

View file

@ -6,17 +6,19 @@ const onApprove = (context, errorHandler, spinner) => {
return fetch( context.config.ajax.approve_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'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)=>{
.then( ( res ) => {
return res.json();
}).then((data)=>{
} )
.then( ( data ) => {
spinner.unblock();
if ( ! data.success ) {
if ( data.data.code === 100 ) {
@ -24,15 +26,17 @@ const onApprove = (context, errorHandler, spinner) => {
} else {
errorHandler.genericError();
}
if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') {
if (
typeof actions !== 'undefined' &&
typeof actions.restart !== 'undefined'
) {
return actions.restart();
}
throw new Error( data.data.message );
}
document.querySelector('#place_order').click()
document.querySelector( '#place_order' ).click();
} );
}
}
};
};
export default onApprove;

View file

@ -1,9 +1,13 @@
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) {
constructor(
defaultConfig,
errorHandler,
spinner,
onCardFieldsBeforeSubmit
) {
this.defaultConfig = defaultConfig;
this.errorHandler = errorHandler;
this.spinner = spinner;
@ -16,21 +20,21 @@ class CardFieldsRenderer {
render( wrapper, contextConfig ) {
if (
(
this.defaultConfig.context !== 'checkout'
&& this.defaultConfig.context !== 'pay-now'
)
|| wrapper === null
|| document.querySelector(wrapper) === null
( this.defaultConfig.context !== 'checkout' &&
this.defaultConfig.context !== 'pay-now' ) ||
wrapper === null ||
document.querySelector( wrapper ) === null
) {
return;
}
const buttonSelector = wrapper + ' button';
const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway');
const gateWayBox = document.querySelector(
'.payment_box.payment_method_ppcp-credit-card-gateway'
);
if ( ! gateWayBox ) {
return
return;
}
const oldDisplayStyle = gateWayBox.style.display;
@ -43,69 +47,89 @@ class CardFieldsRenderer {
const cardField = paypal.CardFields( {
createOrder: contextConfig.createOrder,
onApprove: function (data) {
onApprove( data ) {
return contextConfig.onApprove( data );
},
onError: function (error) {
console.error(error)
onError( error ) {
console.error( error );
this.spinner.unblock();
}
},
} );
if ( cardField.isEligible() ) {
const nameField = document.getElementById('ppcp-credit-card-gateway-card-name');
const nameField = document.getElementById(
'ppcp-credit-card-gateway-card-name'
);
if ( nameField ) {
let styles = cardFieldStyles(nameField);
let fieldOptions = {
style: { 'input': styles }
}
const styles = cardFieldStyles( nameField );
const fieldOptions = {
style: { input: styles },
};
if ( nameField.getAttribute( 'placeholder' ) ) {
fieldOptions.placeholder = nameField.getAttribute('placeholder');
fieldOptions.placeholder =
nameField.getAttribute( 'placeholder' );
}
cardField.NameField(fieldOptions).render(nameField.parentNode);
cardField
.NameField( fieldOptions )
.render( nameField.parentNode );
nameField.remove();
}
const numberField = document.getElementById('ppcp-credit-card-gateway-card-number');
const numberField = document.getElementById(
'ppcp-credit-card-gateway-card-number'
);
if ( numberField ) {
let styles = cardFieldStyles(numberField);
let fieldOptions = {
style: { 'input': styles }
}
const styles = cardFieldStyles( numberField );
const fieldOptions = {
style: { input: styles },
};
if ( numberField.getAttribute( 'placeholder' ) ) {
fieldOptions.placeholder = numberField.getAttribute('placeholder');
fieldOptions.placeholder =
numberField.getAttribute( 'placeholder' );
}
cardField.NumberField(fieldOptions).render(numberField.parentNode);
cardField
.NumberField( fieldOptions )
.render( numberField.parentNode );
numberField.remove();
}
const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry');
const expiryField = document.getElementById(
'ppcp-credit-card-gateway-card-expiry'
);
if ( expiryField ) {
let styles = cardFieldStyles(expiryField);
let fieldOptions = {
style: { 'input': styles }
}
const styles = cardFieldStyles( expiryField );
const fieldOptions = {
style: { input: styles },
};
if ( expiryField.getAttribute( 'placeholder' ) ) {
fieldOptions.placeholder = expiryField.getAttribute('placeholder');
fieldOptions.placeholder =
expiryField.getAttribute( 'placeholder' );
}
cardField.ExpiryField(fieldOptions).render(expiryField.parentNode);
cardField
.ExpiryField( fieldOptions )
.render( expiryField.parentNode );
expiryField.remove();
}
const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc');
const cvvField = document.getElementById(
'ppcp-credit-card-gateway-card-cvc'
);
if ( cvvField ) {
let styles = cardFieldStyles(cvvField);
let fieldOptions = {
style: { 'input': styles }
}
const styles = cardFieldStyles( cvvField );
const fieldOptions = {
style: { input: styles },
};
if ( cvvField.getAttribute( 'placeholder' ) ) {
fieldOptions.placeholder = cvvField.getAttribute('placeholder');
fieldOptions.placeholder =
cvvField.getAttribute( 'placeholder' );
}
cardField.CVVField(fieldOptions).render(cvvField.parentNode);
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;
@ -113,34 +137,44 @@ class CardFieldsRenderer {
show( buttonSelector );
if ( this.defaultConfig.cart_contains_subscription ) {
const saveToAccount = document.querySelector('#wc-ppcp-credit-card-gateway-new-payment-method');
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) => {
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
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()) {
if (
typeof this.onCardFieldsBeforeSubmit === 'function' &&
! this.onCardFieldsBeforeSubmit()
) {
this.spinner.unblock();
return;
}
cardField.submit()
.catch((error) => {
cardField.submit().catch( ( error ) => {
this.spinner.unblock();
console.error(error)
this.errorHandler.message(this.defaultConfig.hosted_fields.labels.fields_not_valid);
console.error( error );
this.errorHandler.message(
this.defaultConfig.hosted_fields.labels.fields_not_valid
);
} );
} );
}

View file

@ -1,8 +1,7 @@
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;
@ -15,28 +14,36 @@ class HostedFieldsRenderer {
render( wrapper, contextConfig ) {
if (
(
this.defaultConfig.context !== 'checkout'
&& this.defaultConfig.context !== 'pay-now'
)
|| wrapper === null
|| document.querySelector(wrapper) === null
( this.defaultConfig.context !== 'checkout' &&
this.defaultConfig.context !== 'pay-now' ) ||
wrapper === null ||
document.querySelector( wrapper ) === null
) {
return;
}
if (typeof paypal.HostedFields !== 'undefined' && paypal.HostedFields.isEligible()) {
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
.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');
const gateWayBox = document.querySelector(
'.payment_box.payment_method_ppcp-credit-card-gateway'
);
if ( ! gateWayBox ) {
return
return;
}
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
@ -46,10 +53,12 @@ class HostedFieldsRenderer {
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 );
let styles = {};
const styles = {};
Object.values( stylesRaw ).forEach( ( prop ) => {
if ( ! stylesRaw[ prop ] ) {
return;
@ -58,47 +67,70 @@ class HostedFieldsRenderer {
} );
const cardNumber = dccInputFactory( cardNumberField );
cardNumberField.parentNode.replaceChild(cardNumber, cardNumberField);
cardNumberField.parentNode.replaceChild(
cardNumber,
cardNumberField
);
const cardExpiryField = document.querySelector('#ppcp-credit-card-gateway-card-expiry');
const cardExpiryField = document.querySelector(
'#ppcp-credit-card-gateway-card-expiry'
);
const cardExpiry = dccInputFactory( cardExpiryField );
cardExpiryField.parentNode.replaceChild(cardExpiry, cardExpiryField);
cardExpiryField.parentNode.replaceChild(
cardExpiry,
cardExpiryField
);
const cardCodeField = document.querySelector('#ppcp-credit-card-gateway-card-cvc');
const cardCodeField = document.querySelector(
'#ppcp-credit-card-gateway-card-cvc'
);
const cardCode = dccInputFactory( cardCodeField );
cardCodeField.parentNode.replaceChild( cardCode, cardCodeField );
gateWayBox.style.display = oldDisplayStyle;
const formWrapper = '.payment_box payment_method_ppcp-credit-card-gateway';
const formWrapper =
'.payment_box payment_method_ppcp-credit-card-gateway';
if (
this.defaultConfig.enforce_vault
&& document.querySelector(formWrapper + ' .ppcp-credit-card-vault')
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);
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
input: styles,
},
fields: {
number: {
selector: '#ppcp-credit-card-gateway-card-number',
placeholder: this.defaultConfig.hosted_fields.labels.credit_card_number,
placeholder:
this.defaultConfig.hosted_fields.labels
.credit_card_number,
},
cvv: {
selector: '#ppcp-credit-card-gateway-card-cvc',
placeholder: this.defaultConfig.hosted_fields.labels.cvv,
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"));
placeholder:
this.defaultConfig.hosted_fields.labels.mm_yy,
},
},
} ).then( ( hostedFields ) => {
document.dispatchEvent(
new CustomEvent( 'hosted_fields_loaded' )
);
this.currentHostedFieldsInstance = hostedFields;
hostedFields.on( 'inputSubmitRequest', () => {
@ -109,22 +141,34 @@ class HostedFieldsRenderer {
this.cardValid = false;
return;
}
const validCards = this.defaultConfig.hosted_fields.valid_cards;
this.cardValid = validCards.indexOf(event.cards[0].type) !== -1;
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);
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( '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._recreateElementClassAttribute(
cardNumber,
cardNumberField.className
);
this.emptyFields.add( event.emittedBy );
} );
hostedFields.on( 'notEmpty', ( event ) => {
@ -133,25 +177,33 @@ class HostedFieldsRenderer {
show( buttonSelector );
if (document.querySelector(wrapper).getAttribute('data-ppcp-subscribed') !== true) {
document.querySelector(buttonSelector).addEventListener(
'click',
event => {
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;
}
@ -164,16 +216,16 @@ class HostedFieldsRenderer {
if ( this.currentHostedFieldsInstance ) {
this.currentHostedFieldsInstance.setAttribute( {
field: 'number',
attribute: 'disabled'
})
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.setAttribute( {
field: 'cvv',
attribute: 'disabled'
})
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.setAttribute( {
field: 'expirationDate',
attribute: 'disabled'
})
attribute: 'disabled',
} );
}
}
@ -181,16 +233,16 @@ class HostedFieldsRenderer {
if ( this.currentHostedFieldsInstance ) {
this.currentHostedFieldsInstance.removeAttribute( {
field: 'number',
attribute: 'disabled'
})
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.removeAttribute( {
field: 'cvv',
attribute: 'disabled'
})
attribute: 'disabled',
} );
this.currentHostedFieldsInstance.removeAttribute( {
field: 'expirationDate',
attribute: 'disabled'
})
attribute: 'disabled',
} );
}
}
@ -199,42 +251,69 @@ class HostedFieldsRenderer {
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;
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
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;
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 : '';
const firstName = document.getElementById(
'billing_first_name'
)
? document.getElementById( 'billing_first_name' ).value
: '';
const lastName = document.getElementById( 'billing_last_name' )
? document.getElementById( 'billing_last_name' ).value
: '';
hostedFieldsData.cardholderName = firstName + ' ' + lastName;
}
this.currentHostedFieldsInstance.submit(hostedFieldsData).then((payload) => {
this.currentHostedFieldsInstance
.submit( hostedFieldsData )
.then( ( payload ) => {
payload.orderID = payload.orderId;
this.spinner.unblock();
return contextConfig.onApprove( payload );
}).catch(err => {
} )
.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/>'));
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/>'));
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 ) {
@ -252,9 +331,11 @@ class HostedFieldsRenderer {
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;
message =
this.defaultConfig.hosted_fields.labels.card_not_supported;
} else if ( ! this.formValid ) {
message = this.defaultConfig.hosted_fields.labels.fields_not_valid;
message =
this.defaultConfig.hosted_fields.labels.fields_not_valid;
}
this.errorHandler.message( message );
@ -262,11 +343,13 @@ class HostedFieldsRenderer {
}
_cardNumberFiledCLassNameByCardType( cardType ) {
return cardType === 'american-express' ? 'amex' : cardType.replace('-', '');
return cardType === 'american-express'
? 'amex'
: cardType.replace( '-', '' );
}
_recreateElementClassAttribute( element, newClassName ) {
element.removeAttribute('class')
element.removeAttribute( 'class' );
element.setAttribute( 'class', newClassName );
}
}

View file

@ -1,7 +1,6 @@
import widgetBuilder from "./WidgetBuilder";
import widgetBuilder from './WidgetBuilder';
class MessageRenderer {
constructor( config ) {
this.config = config;
this.optionsFingerprint = null;
@ -24,7 +23,12 @@ class MessageRenderer {
}
// sometimes the element is destroyed while the options stay the same
if (document.querySelector(this.config.wrapper).getAttribute('data-render-number') !== this.currentNumber.toString()) {
if (
document
.querySelector( this.config.wrapper )
.getAttribute( 'data-render-number' ) !==
this.currentNumber.toString()
) {
this.optionsFingerprint = null;
}
@ -52,8 +56,11 @@ class MessageRenderer {
}
shouldRender() {
if (typeof paypal === 'undefined' || typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
if (
typeof paypal === 'undefined' ||
typeof paypal.Messages === 'undefined' ||
typeof this.config.wrapper === 'undefined'
) {
return false;
}
if ( ! document.querySelector( this.config.wrapper ) ) {

View file

@ -6,13 +6,10 @@ import merge from 'deepmerge';
class PreviewButton {
/**
* @param {string} selector - CSS ID of the wrapper, including the `#`
* @param {object} apiConfig - PayPal configuration object; retrieved via a
* @param {Object} apiConfig - PayPal configuration object; retrieved via a
* widgetBuilder API method
*/
constructor({
selector,
apiConfig,
}) {
constructor( { selector, apiConfig } ) {
this.apiConfig = apiConfig;
this.defaultAttributes = {};
this.buttonConfig = {};
@ -43,6 +40,7 @@ class PreviewButton {
* 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 ) {
@ -53,6 +51,7 @@ class PreviewButton {
/**
* Sets server-side configuration for the button.
*
* @param config
* @return {this} Reference to self, for chaining.
*/
setButtonConfig( config ) {
@ -65,6 +64,7 @@ class PreviewButton {
/**
* Updates the button configuration with current details from the form.
*
* @param config
* @return {this} Reference to self, for chaining.
*/
setPpcpConfig( config ) {
@ -76,6 +76,8 @@ class PreviewButton {
/**
* 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.
@ -84,9 +86,12 @@ class PreviewButton {
/**
* 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');
throw new Error(
'The "createButton" method must be implemented by the derived class'
);
}
/**
@ -112,7 +117,9 @@ class PreviewButton {
this.isVisible = true;
const previewButtonConfig = merge( {}, this.buttonConfig );
const previewPpcpConfig = this.isDynamic ? merge({}, this.ppcpConfig) : {};
const previewPpcpConfig = this.isDynamic
? merge( {}, this.ppcpConfig )
: {};
previewButtonConfig.button.wrapper = this.selector;
this.dynamicPreviewConfig( previewButtonConfig, previewPpcpConfig );
@ -121,11 +128,16 @@ class PreviewButton {
* 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 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}"`);
throw new Error(
`[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${ buttonWrapper }"`
);
}
this.createButton( previewButtonConfig );

View file

@ -20,11 +20,7 @@ class PreviewButtonManager {
*/
#onInit;
constructor({
methodName,
buttonConfig,
defaultAttributes,
}) {
constructor( { methodName, buttonConfig, defaultAttributes } ) {
// Define the payment method name in the derived class.
this.methodName = methodName;
@ -36,7 +32,7 @@ class PreviewButtonManager {
this.apiConfig = null;
this.apiError = '';
this.#onInit = new Promise(resolve => {
this.#onInit = new Promise( ( resolve ) => {
this.#onInitResolver = resolve;
} );
@ -52,7 +48,10 @@ class PreviewButtonManager {
* 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._configureAllButtons = debounce(
this._configureAllButtons.bind( this ),
100
);
this.registerEventListeners();
}
@ -66,7 +65,9 @@ class PreviewButtonManager {
* @return {Promise<{}>}
*/
async fetchConfig( payPal ) {
throw new Error('The "fetchConfig" method must be implemented by the derived class');
throw new Error(
'The "fetchConfig" method must be implemented by the derived class'
);
}
/**
@ -77,7 +78,9 @@ class PreviewButtonManager {
* @return {PreviewButton}
*/
createButtonInstance( wrapperId ) {
throw new Error('The "createButtonInstance" method must be implemented by the derived class');
throw new Error(
'The "createButtonInstance" method must be implemented by the derived class'
);
}
/**
@ -93,7 +96,9 @@ class PreviewButtonManager {
createDummy( wrapperId ) {
const elButton = document.createElement( 'div' );
elButton.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' );
elButton.innerHTML = `<span>${this.apiError ?? 'Not Available'}</span>`;
elButton.innerHTML = `<span>${
this.apiError ?? 'Not Available'
}</span>`;
document.querySelector( wrapperId ).appendChild( elButton );
@ -111,14 +116,22 @@ class PreviewButtonManager {
jQuery( document ).one( 'DOMContentLoaded', this.bootstrap );
// General event that all APM buttons react to.
jQuery(document).on('ppcp_paypal_render_preview', this.renderPreview);
jQuery( document ).on(
'ppcp_paypal_render_preview',
this.renderPreview
);
// Specific event to only (re)render the current APM button type.
jQuery(document).on(`ppcp_paypal_render_preview_${this.methodName}`, this.renderPreview);
jQuery( document ).on(
`ppcp_paypal_render_preview_${ this.methodName }`,
this.renderPreview
);
}
/**
* Output an error message to the console, with a module-specific prefix.
* @param message
* @param {...any} args
*/
error( message, ...args ) {
console.error( `${ this.methodName } ${ message }`, ...args );
@ -130,7 +143,9 @@ class PreviewButtonManager {
* style settings that were provided from server-side.
*/
isDynamic() {
return !!document.querySelector(`[data-ppcp-apm-name="${this.methodName}"]`);
return !! document.querySelector(
`[data-ppcp-apm-name="${ this.methodName }"]`
);
}
/**
@ -153,14 +168,17 @@ class PreviewButtonManager {
// not loaded.
if ( ! window.PayPalCommerceGatewaySettings ) {
this.error(
'PayPal settings are not fully loaded. Please clear the cache and reload the page.');
'PayPal settings are not fully loaded. Please clear the cache and reload the page.'
);
return;
}
// 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');
success
? resolve()
: reject( 'Timeout while waiting for widgetBuilder.paypal' );
};
// Wait for the PayPal SDK to be ready.
@ -178,14 +196,19 @@ class PreviewButtonManager {
} );
// Load the custom SDK script.
const customScriptPromise = loadCustomScript({ url: this.buttonConfig.sdk_url });
const customScriptPromise = loadCustomScript( {
url: this.buttonConfig.sdk_url,
} );
// Wait for both promises to resolve before continuing.
await Promise
.all([customScriptPromise, paypalPromise])
.catch(err => {
console.log(`Failed to load ${this.methodName} dependencies:`, err);
});
await Promise.all( [ customScriptPromise, paypalPromise ] ).catch(
( err ) => {
console.log(
`Failed to load ${ this.methodName } dependencies:`,
err
);
}
);
/*
The fetchConfig method requires two objects to succeed:
@ -243,11 +266,13 @@ class PreviewButtonManager {
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.
* @param id
* @param ppcpConfig
*/
_configureButton( id, ppcpConfig ) {
this.buttons[ id ]
@ -258,6 +283,7 @@ class PreviewButtonManager {
/**
* Apples the provided configuration to all existing preview buttons.
* @param ppcpConfig
*/
_configureAllButtons( ppcpConfig ) {
Object.entries( this.buttons ).forEach( ( [ id, button ] ) => {
@ -276,13 +302,17 @@ class PreviewButtonManager {
/**
* 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);
newInst = this.createButtonInstance( id ).setButtonConfig(
this.buttonConfig
);
} else {
newInst = this.createDummy( id );
}
@ -307,9 +337,13 @@ class PreviewButtonManager {
*/
renderButtons() {
if ( this.isEnabled ) {
Object.values(this.buttons).forEach(button => button.render());
Object.values( this.buttons ).forEach( ( button ) =>
button.render()
);
} else {
Object.values(this.buttons).forEach(button => button.remove());
Object.values( this.buttons ).forEach( ( button ) =>
button.remove()
);
}
return this;

View file

@ -1,15 +1,20 @@
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";
} from '../Helper/ShippingHandler.js';
class Renderer {
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
constructor(
creditCardRenderer,
defaultSettings,
onSmartButtonClick,
onSmartButtonsInit
) {
this.defaultSettings = defaultSettings;
this.creditCardRenderer = creditCardRenderer;
this.onSmartButtonClick = onSmartButtonClick;
@ -23,13 +28,20 @@ class Renderer {
this.reloadEventName = 'ppcp-reload-buttons';
}
render(contextConfig, settingsOverride = {}, contextConfigOverride = () => {}) {
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(
@ -40,8 +52,13 @@ class Renderer {
);
} else {
// render each button separately
for (const fundingSource of paypal.getFundingSources().filter(s => !(s in enabledSeparateGateways))) {
const style = normalizeStyleForFundingSource(settings.button.style, fundingSource);
for ( const fundingSource of paypal
.getFundingSources()
.filter( ( s ) => ! ( s in enabledSeparateGateways ) ) ) {
const style = normalizeStyleForFundingSource(
settings.button.style,
fundingSource
);
this.renderButtons(
settings.button.wrapper,
@ -54,10 +71,15 @@ class Renderer {
}
if ( this.creditCardRenderer ) {
this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfigOverride);
this.creditCardRenderer.render(
settings.hosted_fields.wrapper,
contextConfigOverride
);
}
for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) {
for ( const [ fundingSource, data ] of Object.entries(
enabledSeparateGateways
) ) {
this.renderButtons(
data.wrapper,
data.style,
@ -68,8 +90,21 @@ class Renderer {
}
}
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) {
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;
@ -106,15 +141,33 @@ class Renderer {
// 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)
let shippingOptionsChange =
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
venmoButtonClicked
)
? handleShippingOptionsChange(
data,
actions,
this.defaultSettings
)
: null;
}
return shippingOptionsChange
};
options.onShippingAddressChange = ( data, actions ) => {
!this.isVenmoButtonClickedWhenVaultingIsEnabled(venmoButtonClicked)
? handleShippingAddressChange(data, actions, this.defaultSettings)
let shippingAddressChange =
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
venmoButtonClicked
)
? handleShippingAddressChange(
data,
actions,
this.defaultSettings
)
: null;
}
return shippingAddressChange
};
}
return options;
@ -122,35 +175,57 @@ class Renderer {
jQuery( document )
.off( this.reloadEventName, wrapper )
.on(this.reloadEventName, wrapper, (event, settingsOverride = {}, triggeredFundingSource) => {
.on(
this.reloadEventName,
wrapper,
( event, settingsOverride = {}, triggeredFundingSource ) => {
// Only accept events from the matching funding source
if (fundingSource && triggeredFundingSource && (triggeredFundingSource !== fundingSource)) {
if (
fundingSource &&
triggeredFundingSource &&
triggeredFundingSource !== fundingSource
) {
return;
}
const settings = merge(this.defaultSettings, settingsOverride);
const settings = merge(
this.defaultSettings,
settingsOverride
);
let scriptOptions = keysToCamelCase( settings.url_params );
scriptOptions = merge(scriptOptions, settings.script_attributes);
scriptOptions = merge(
scriptOptions,
settings.script_attributes
);
loadScript( scriptOptions ).then( ( paypal ) => {
widgetBuilder.setPaypal( paypal );
widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions());
widgetBuilder.registerButtons(
[ wrapper, fundingSource ],
buttonsOptions()
);
widgetBuilder.renderAll();
} );
});
}
);
this.renderedSources.add( wrapper + ( fundingSource ?? '' ) );
if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') {
widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions());
if (
typeof paypal !== 'undefined' &&
typeof paypal.Buttons !== 'undefined'
) {
widgetBuilder.registerButtons(
[ wrapper, fundingSource ],
buttonsOptions()
);
widgetBuilder.renderButtons( [ wrapper, fundingSource ] );
}
}
isVenmoButtonClickedWhenVaultingIsEnabled = ( venmoButtonClicked ) => {
return venmoButtonClicked && this.defaultSettings.vaultingEnabled;
}
};
isAlreadyRendered( wrapper, fundingSource ) {
return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) );
@ -165,23 +240,24 @@ class Renderer {
}
onButtonsInit( wrapper, handler, reset ) {
this.onButtonsInitListeners[wrapper] = reset ? [] : (this.onButtonsInitListeners[wrapper] || []);
this.onButtonsInitListeners[ wrapper ] = reset
? []
: this.onButtonsInitListeners[ wrapper ] || [];
this.onButtonsInitListeners[ wrapper ].push( handler );
}
handleOnButtonsInit( wrapper, data, actions ) {
this.buttonsOptions[ wrapper ] = {
data: data,
actions: actions
}
data,
actions,
};
if ( this.onButtonsInitListeners[ wrapper ] ) {
for (let handler of this.onButtonsInitListeners[wrapper]) {
for ( const handler of this.onButtonsInitListeners[ wrapper ] ) {
if ( typeof handler === 'function' ) {
handler( {
wrapper: wrapper,
...this.buttonsOptions[wrapper]
wrapper,
...this.buttonsOptions[ wrapper ],
} );
}
}

View file

@ -3,7 +3,6 @@
* To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource].
*/
class WidgetBuilder {
constructor() {
this.paypal = null;
this.buttons = new Map();
@ -16,7 +15,7 @@ class WidgetBuilder {
buttons: this.buttons,
messages: this.messages,
} );
}
};
jQuery( document )
.off( this.renderEventName )
@ -34,8 +33,8 @@ class WidgetBuilder {
wrapper = this.sanitizeWrapper( wrapper );
this.buttons.set( this.toKey( wrapper ), {
wrapper: wrapper,
options: options,
wrapper,
options,
} );
}
@ -58,7 +57,7 @@ class WidgetBuilder {
return;
}
let target = this.buildWrapperTarget(wrapper);
const target = this.buildWrapperTarget( wrapper );
if ( ! target ) {
return;
@ -75,8 +74,8 @@ class WidgetBuilder {
registerMessages( wrapper, options ) {
this.messages.set( wrapper, {
wrapper: wrapper,
options: options
wrapper,
options,
} );
}
@ -132,7 +131,7 @@ class WidgetBuilder {
sanitizeWrapper( wrapper ) {
if ( Array.isArray( wrapper ) ) {
wrapper = wrapper.filter(item => !!item);
wrapper = wrapper.filter( ( item ) => !! item );
if ( wrapper.length === 1 ) {
wrapper = wrapper[ 0 ];
}

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;
}
/**
@ -1521,11 +1526,13 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
*/
private function bn_codes() : array {
$bn_code = PPCP_PAYPAL_BN_CODE;
return array(
'checkout' => 'Woo_PPCP',
'cart' => 'Woo_PPCP',
'mini-cart' => 'Woo_PPCP',
'product' => 'Woo_PPCP',
'checkout' => $bn_code,
'cart' => $bn_code,
'mini-cart' => $bn_code,
'product' => $bn_code,
);
}

View file

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

View file

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

View file

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

View file

@ -83,6 +83,9 @@ return array(
'compat.wc_shipping_tax.is_supported_plugin_version_active' => function (): bool {
return class_exists( 'WC_Connect_Loader' );
},
'compat.nyp.is_supported_plugin_version_active' => function (): bool {
return function_exists( 'wc_nyp_init' );
},
'compat.module.url' => static function ( ContainerInterface $container ): string {
/**

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