diff --git a/.psalm/stubs.php b/.psalm/stubs.php index 8b9348b1b..59be5215c 100644 --- a/.psalm/stubs.php +++ b/.psalm/stubs.php @@ -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. * diff --git a/changelog.txt b/changelog.txt index 55745879f..aaf167948 100644 --- a/changelog.txt +++ b/changelog.txt @@ -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 diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index f91e24119..dbdf2efe3 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -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(), diff --git a/modules/ppcp-api-client/src/Endpoint/RequestTrait.php b/modules/ppcp-api-client/src/Endpoint/RequestTrait.php index 369dc9c1e..cb5f3c543 100644 --- a/modules/ppcp-api-client/src/Endpoint/RequestTrait.php +++ b/modules/ppcp-api-client/src/Endpoint/RequestTrait.php @@ -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 ); diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 49ccdaf88..2bcac16fc 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,775 +1,953 @@ -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 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 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); - - this.isInitialized = false; - - this.context = context; - this.externalHandler = externalHandler; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.paymentsClient = null; - this.formData = null; - - this.contextHandler = ContextHandlerFactory.create( - this.context, - this.buttonConfig, - this.ppcpConfig - ); - - 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; - - // Default eligibility status. - this.isEligible = true; - - this.log = function() { - if ( this.buttonConfig.is_debug ) { - //console.log('[ApplePayButton]', ...arguments); - } - } - - this.refreshContextData(); - - // Debug helpers - jQuery(document).on('ppcp-applepay-debug', () => { - console.log('ApplePayButton', this.context, this); - }); - document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; - document.ppcpApplepayButtons[this.context] = this; - } - - init(config) { - if (this.isInitialized) { - return; - } - - if (!this.contextHandler.validateContext()) { - return; - } - - this.log('Init', this.context); - this.initEventHandlers(); - this.isInitialized = true; - this.applePayConfig = config; - 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; - - if (this.context === 'mini-cart') { - document.querySelector(id_minicart)?.addEventListener('click', (evt) => { - evt.preventDefault(); - this.onButtonClick(); - }); - } else { - document.querySelector(id)?.addEventListener('click', (evt) => { - evt.preventDefault(); - this.onButtonClick(); - }); - } - }); - } else { - jQuery('#' + this.buttonConfig.button.wrapper).hide(); - jQuery('#' + this.buttonConfig.button.mini_cart_wrapper).hide(); - jQuery('#express-payment-method-ppcp-applepay').hide(); - } - } - - reinit() { - if (!this.applePayConfig) { - return; - } - - this.isInitialized = false; - this.init(this.applePayConfig); - } - - async fetchTransactionInfo() { - this.transactionInfo = await this.contextHandler.transactionInfo(); - } - - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - let config = { - wrapper: this.buttonConfig.button.wrapper, - ppcpStyle: this.ppcpConfig.button.style, - buttonStyle: this.buttonConfig.button.style, - ppcpButtonWrapper: this.ppcpConfig.button.wrapper - } - - if (this.context === 'mini-cart') { - config.wrapper = this.buttonConfig.button.mini_cart_wrapper; - config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; - config.buttonStyle = this.buttonConfig.button.mini_cart_style; - 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'; - } - - return config; - } - - initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - 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}"`); - } - - const syncButtonVisibility = () => { - if (!this.isEligible) { - return; - } - - const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper); - setVisible(wrapper_id, $ppcpButtonWrapper.is(':visible')); - setEnabled(wrapper_id, !$ppcpButtonWrapper.hasClass('ppcp-disabled')); - } - - 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. - */ - applePaySession(paymentRequest) { - this.log('applePaySession', paymentRequest); - const session = new ApplePaySession(4, paymentRequest); - session.begin(); - - if (this.shouldRequireShippingInButton()) { - session.onshippingmethodselected = this.onShippingMethodSelected(session); - session.onshippingcontactselected = this.onShippingContactSelected(session); - } - session.onvalidatemerchant = this.onValidateMerchant(session); - session.onpaymentauthorized = this.onPaymentAuthorized(session); - return session; - } - - /** - * Adds an Apple Pay purchase button. - */ - addButton() { - this.log('addButton', this.context); - - const { wrapper, ppcpStyle } = this.contextConfig(); - - const appleContainer = document.getElementById(wrapper); - const type = this.buttonConfig.button.type; - const language = this.buttonConfig.button.lang; - const color = this.buttonConfig.button.color; - const id = "apple-" + wrapper; - - if (appleContainer) { - appleContainer.innerHTML = ``; - } - - const $wrapper = jQuery('#' + wrapper); - $wrapper.addClass('ppcp-button-' + ppcpStyle.shape); - - if (ppcpStyle.height) { - $wrapper.css('--apple-pay-button-height', `${ppcpStyle.height}px`) - $wrapper.css('height', `${ppcpStyle.height}px`) - } - } - - //------------------------ - // Button click - //------------------------ - - /** - * Show Apple Pay payment sheet when Apple Pay payment button is clicked - */ - async onButtonClick() { - this.log('onButtonClick', this.context); - - const paymentRequest = this.paymentRequest(); - - window.ppcpFundingSource = 'apple_pay'; // Do this on another place like on create order endpoint handler. - - // Trigger woocommerce validation if we are in the checkout page. - if (this.context === 'checkout') { - const checkoutFormSelector = 'form.woocommerce-checkout'; - const errorHandler = new ErrorHandler( - PayPalCommerceGateway.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); - try { - const formData = new FormData(document.querySelector(checkoutFormSelector)); - this.formData = Object.fromEntries(formData.entries()); - - this.updateRequestDataWithForm(paymentRequest); - } catch (error) { - console.error(error); - } - - this.log('=== paymentRequest', paymentRequest); - - const session = this.applePaySession(paymentRequest); - const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ? - new FormValidator( - PayPalCommerceGateway.ajax.validate_checkout.endpoint, - PayPalCommerceGateway.ajax.validate_checkout.nonce, - ) : null; - if (formValidator) { - try { - const errors = await formValidator.validate(document.querySelector(checkoutFormSelector)); - if (errors.length > 0) { - errorHandler.messages(errors); - jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] ); - session.abort(); - return; - } - } catch (error) { - console.error(error); - } - } - return; - } - - // Default session initialization. - this.applePaySession(paymentRequest); - } - - /** - * If the button should show the shipping fields. - * - * @returns {false|*} - */ - shouldRequireShippingInButton() { - 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} - */ - shouldUpdateButtonWithFormData() { - if (this.context !== 'checkout') { - return false; - } - 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} - */ - shouldCompletePaymentWithContextHandler() { - // Data already handled, ex: PayNow - if (!this.contextHandler.shippingAllowed()) { - return true; - } - // Use WC form data mode in Checkout. - if (this.context === 'checkout' && !this.shouldUpdateButtonWithFormData()) { - return true; - } - return false; - } - - /** - * Updates ApplePay paymentRequest with form data. - */ - updateRequestDataWithForm(paymentRequest) { - if (!this.shouldUpdateButtonWithFormData()) { - return; - } - - // Add billing address. - paymentRequest.billingContact = this.fillBillingContact(this.formData); - - // Add custom data. - // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). - // paymentRequest.applicationData = this.fillApplicationData(this.formData); - - if (!this.shouldRequireShippingInButton()) { - return; - } - - // Add shipping address. - paymentRequest.shippingContact = this.fillShippingContact(this.formData); - - // Get shipping methods. - const rate = this.transactionInfo.chosenShippingMethods[0]; - paymentRequest.shippingMethods = []; - - // Add selected shipping method. - for (const shippingPackage of this.transactionInfo.shippingPackages) { - if (rate === shippingPackage.id) { - const shippingMethod = { - 'label' : shippingPackage.label, - 'detail' : '', - 'amount' : shippingPackage.cost_str, - 'identifier' : shippingPackage.id, - }; - - // Remember this shipping method as the selected one. - this.selectedShippingMethod = shippingMethod; - - paymentRequest.shippingMethods.push(shippingMethod); - break; - } - } - - // Add other shipping methods. - 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, - }); - } - } - - // Store for reuse in case this data is not provided by ApplePay on authorization. - this.initialPaymentRequest = paymentRequest; - - this.log('=== paymentRequest.shippingMethods', paymentRequest.shippingMethods); - } - - paymentRequest() { - const applepayConfig = this.applePayConfig - const buttonConfig = this.buttonConfig - let 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. - } - - if (!this.shouldRequireShippingInButton()) { - if (this.shouldCompletePaymentWithContextHandler()) { - // Data needs handled externally. - baseRequest.requiredShippingContactFields = []; - } else { - // Minimum data required for order creation. - baseRequest.requiredShippingContactFields = ["email", "phone"]; - } - } - - const paymentRequest = Object.assign({}, baseRequest); - paymentRequest.currencyCode = buttonConfig.shop.currencyCode; - paymentRequest.total = { - label: buttonConfig.shop.totalLabel, - type: "final", - amount: this.transactionInfo.totalPrice, - } - - return paymentRequest; - } - - refreshContextData() { - switch (this.context) { - case 'product': - // Refresh product data that makes the price change. - this.productQuantity = document.querySelector('input.qty')?.value; - this.products = this.contextHandler.products(); - this.log('Products updated', this.products); - break; - } - } - - //------------------------ - // Payment process - //------------------------ - - onValidateMerchant(session) { - this.log('onvalidatemerchant', this.buttonConfig.ajax_url); - return (applePayValidateMerchantEvent) => { - this.log('onvalidatemerchant call'); - - widgetBuilder.paypal.Applepay().validateMerchant({ - validationUrl: applePayValidateMerchantEvent.validationURL - }) - .then(validateResult => { - this.log('onvalidatemerchant ok'); - session.completeMerchantValidation(validateResult.merchantSession); - //call backend to update validation to true - jQuery.ajax({ - url: this.buttonConfig.ajax_url, - type: 'POST', - data: { - action: 'ppcp_validate', - validation: true, - 'woocommerce-process-checkout-nonce': this.nonce, - } - }) - }) - .catch(validateError => { - this.log('onvalidatemerchant error', validateError); - console.error(validateError); - //call backend to update validation to false - jQuery.ajax({ - url: this.buttonConfig.ajax_url, - type: 'POST', - data: { - action: 'ppcp_validate', - validation: false, - 'woocommerce-process-checkout-nonce': this.nonce, - } - }); - this.log('onvalidatemerchant session abort'); - session.abort(); - }); - }; - } - - onShippingMethodSelected(session) { - this.log('onshippingmethodselected', this.buttonConfig.ajax_url); - const ajax_url = this.buttonConfig.ajax_url; - return (event) => { - this.log('onshippingmethodselected call'); - - const data = this.getShippingMethodData(event); - - jQuery.ajax({ - url: ajax_url, - method: 'POST', - data: data, - success: (applePayShippingMethodUpdate, textStatus, jqXHR) => { - this.log('onshippingmethodselected ok'); - let 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) { - return -1; - } - return 1; - }); - - if (applePayShippingMethodUpdate.success === false) { - response.errors = createAppleErrors(response.errors); - } - session.completeShippingMethodSelection(response); - }, - error: (jqXHR, textStatus, errorThrown) => { - this.log('onshippingmethodselected error', textStatus); - console.warn(textStatus, errorThrown); - session.abort(); - }, - }); - }; - } - - onShippingContactSelected(session) { - this.log('onshippingcontactselected', this.buttonConfig.ajax_url); - - const ajax_url = this.buttonConfig.ajax_url; - - return (event) => { - this.log('onshippingcontactselected call'); - - const data = this.getShippingContactData(event); - - jQuery.ajax({ - url: ajax_url, - method: 'POST', - data: data, - success: (applePayShippingContactUpdate, textStatus, jqXHR) => { - this.log('onshippingcontactselected ok'); - let response = applePayShippingContactUpdate.data; - this.updatedContactInfo = event.shippingContact; - if (applePayShippingContactUpdate.success === false) { - response.errors = createAppleErrors(response.errors); - } - if (response.newShippingMethods) { - this.selectedShippingMethod = response.newShippingMethods[0]; - } - session.completeShippingContactSelection(response); - }, - error: (jqXHR, textStatus, errorThrown) => { - this.log('onshippingcontactselected error', textStatus); - console.warn(textStatus, errorThrown); - session.abort(); - }, - }); - }; - } - - getShippingContactData(event) { - const product_id = this.buttonConfig.product.id; - - this.refreshContextData(); - - switch (this.context) { - case 'product': - return { - action: 'ppcp_update_shipping_contact', - product_id: product_id, - products: JSON.stringify(this.products), - caller_page: 'productDetail', - product_quantity: this.productQuantity, - simplified_contact: event.shippingContact, - need_shipping: this.shouldRequireShippingInButton(), - 'woocommerce-process-checkout-nonce': this.nonce, - }; - case 'cart': - case 'checkout': - case 'cart-block': - case 'checkout-block': - case 'mini-cart': - return { - action: 'ppcp_update_shipping_contact', - simplified_contact: event.shippingContact, - caller_page: 'cart', - need_shipping: this.shouldRequireShippingInButton(), - 'woocommerce-process-checkout-nonce': this.nonce, - }; - } - } - - getShippingMethodData(event) { - const product_id = this.buttonConfig.product.id; - - this.refreshContextData(); - - switch (this.context) { - 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, - 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': - case 'checkout-block': - case 'mini-cart': - return { - action: 'ppcp_update_shipping_method', - shipping_method: event.shippingMethod, - simplified_contact: this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact, - caller_page: 'cart', - 'woocommerce-process-checkout-nonce': this.nonce, - } - } - } - - onPaymentAuthorized(session) { - this.log('onpaymentauthorized'); - return async (event) => { - this.log('onpaymentauthorized call'); - - function form() { - return document.querySelector('form.cart'); - } - 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]; - - let 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, - 'woocommerce-process-checkout-nonce': this.nonce, - '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); - - jQuery.ajax({ - url: this.buttonConfig.ajax_url, - method: 'POST', - data: request_data, - complete: (jqXHR, textStatus) => { - this.log('onpaymentauthorized complete'); - }, - success: (authorizationResult, textStatus, jqXHR) => { - this.log('onpaymentauthorized ok'); - resolve(authorizationResult); - }, - error: (jqXHR, textStatus, errorThrown) => { - this.log('onpaymentauthorized error', textStatus); - reject(new Error(errorThrown)); - }, - }); - } catch (error) { - this.log('onpaymentauthorized catch', error); - console.log(error); // handle error - } - }); - } - - let id = await this.contextHandler.createOrder(); - - this.log('onpaymentauthorized paypal order ID', id, event.payment.token, event.payment.billingContact); - - try { - const confirmOrderResponse = await widgetBuilder.paypal.Applepay().confirmOrder({ - orderId: id, - token: event.payment.token, - billingContact: event.payment.billingContact, - }); - - this.log('onpaymentauthorized confirmOrderResponse', confirmOrderResponse); - - if (confirmOrderResponse && confirmOrderResponse.approveApplePayPayment) { - if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") { - try { - - 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) => { - approveFailed = true; - resolve(); - }), - order: { - get: () => new Promise((resolve, reject) => { - resolve(null); - }) - } - }); - - if (!approveFailed) { - this.log('onpaymentauthorized approveOrder OK'); - session.completePayment(ApplePaySession.STATUS_SUCCESS); - } else { - 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, - paypal_order_id: id, - }; - let authorizationResult = await processInWooAndCapture(data); - if (authorizationResult.result === "success") { - session.completePayment(ApplePaySession.STATUS_SUCCESS); - window.location.href = authorizationResult.redirect; - } else { - session.completePayment(ApplePaySession.STATUS_FAILURE); - } - - } - - } catch (error) { - session.completePayment(ApplePaySession.STATUS_FAILURE); - session.abort(); - console.error(error); - } - } else { - console.error('Error status is not APPROVED'); - 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); - session.completePayment(ApplePaySession.STATUS_FAILURE); - session.abort(); - } - }; - } - - fillBillingContact(data) { - return { - givenName: data.billing_first_name ?? '', - familyName: data.billing_last_name ?? '', - emailAddress: data.billing_email ?? '', - phoneNumber: data.billing_phone ?? '', - addressLines: [data.billing_address_1, data.billing_address_2], - locality: data.billing_city ?? '', - postalCode: data.billing_postcode ?? '', - countryCode: data.billing_country ?? '', - administrativeArea: data.billing_state ?? '', - } - } - - fillShippingContact(data) { - 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, - } - } - - fillApplicationData(data) { - const jsonString = JSON.stringify(data); - let utf8Str = encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (match, p1) => { - return String.fromCharCode('0x' + p1); - }); - - return btoa(utf8Str); - } + constructor( context, externalHandler, buttonConfig, ppcpConfig ) { + apmButtonsInit( ppcpConfig ); + + this.isInitialized = false; + + this.context = context; + this.externalHandler = externalHandler; + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.paymentsClient = null; + this.formData = null; + + this.contextHandler = ContextHandlerFactory.create( + this.context, + this.buttonConfig, + this.ppcpConfig + ); + + 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; + + // Default eligibility status. + this.isEligible = true; + + this.log = function () { + if ( this.buttonConfig.is_debug ) { + //console.log('[ApplePayButton]', ...arguments); + } + }; + + this.refreshContextData(); + + // Debug helpers + jQuery( document ).on( 'ppcp-applepay-debug', () => { + console.log( 'ApplePayButton', this.context, this ); + } ); + document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; + document.ppcpApplepayButtons[ this.context ] = this; + } + + init( config ) { + if ( this.isInitialized ) { + return; + } + + if ( ! this.contextHandler.validateContext() ) { + return; + } + + this.log( 'Init', this.context ); + this.initEventHandlers(); + this.isInitialized = true; + this.applePayConfig = config; + 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; + + if ( this.context === 'mini-cart' ) { + document + .querySelector( id_minicart ) + ?.addEventListener( 'click', ( evt ) => { + evt.preventDefault(); + this.onButtonClick(); + } ); + } else { + document + .querySelector( id ) + ?.addEventListener( 'click', ( evt ) => { + evt.preventDefault(); + this.onButtonClick(); + } ); + } + } ); + } else { + jQuery( '#' + this.buttonConfig.button.wrapper ).hide(); + jQuery( '#' + this.buttonConfig.button.mini_cart_wrapper ).hide(); + jQuery( '#express-payment-method-ppcp-applepay' ).hide(); + } + } + + reinit() { + if ( ! this.applePayConfig ) { + return; + } + + this.isInitialized = false; + this.init( this.applePayConfig ); + } + + async fetchTransactionInfo() { + this.transactionInfo = await this.contextHandler.transactionInfo(); + } + + /** + * Returns configurations relative to this button context. + */ + contextConfig() { + const config = { + wrapper: this.buttonConfig.button.wrapper, + ppcpStyle: this.ppcpConfig.button.style, + buttonStyle: this.buttonConfig.button.style, + ppcpButtonWrapper: this.ppcpConfig.button.wrapper, + }; + + if ( this.context === 'mini-cart' ) { + config.wrapper = this.buttonConfig.button.mini_cart_wrapper; + config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; + config.buttonStyle = this.buttonConfig.button.mini_cart_style; + 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'; + } + + return config; + } + + initEventHandlers() { + const { wrapper, ppcpButtonWrapper } = this.contextConfig(); + 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 }"` + ); + } + + const syncButtonVisibility = () => { + if ( ! this.isEligible ) { + return; + } + + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper_id, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper_id, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + + 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 ); + const session = new ApplePaySession( 4, paymentRequest ); + session.begin(); + + if ( this.shouldRequireShippingInButton() ) { + session.onshippingmethodselected = + this.onShippingMethodSelected( session ); + session.onshippingcontactselected = + this.onShippingContactSelected( session ); + } + session.onvalidatemerchant = this.onValidateMerchant( session ); + session.onpaymentauthorized = this.onPaymentAuthorized( session ); + return session; + } + + /** + * Adds an Apple Pay purchase button. + */ + addButton() { + this.log( 'addButton', this.context ); + + const { wrapper, ppcpStyle } = this.contextConfig(); + + const appleContainer = document.getElementById( wrapper ); + const type = this.buttonConfig.button.type; + const language = this.buttonConfig.button.lang; + const color = this.buttonConfig.button.color; + const id = 'apple-' + wrapper; + + if ( appleContainer ) { + appleContainer.innerHTML = ``; + } + + const $wrapper = jQuery( '#' + wrapper ); + $wrapper.addClass( 'ppcp-button-' + ppcpStyle.shape ); + + if ( ppcpStyle.height ) { + $wrapper.css( + '--apple-pay-button-height', + `${ ppcpStyle.height }px` + ); + $wrapper.css( 'height', `${ ppcpStyle.height }px` ); + } + } + + //------------------------ + // Button click + //------------------------ + + /** + * Show Apple Pay payment sheet when Apple Pay payment button is clicked + */ + async onButtonClick() { + this.log( 'onButtonClick', this.context ); + + const paymentRequest = this.paymentRequest(); + + window.ppcpFundingSource = 'apple_pay'; // Do this on another place like on create order endpoint handler. + + // Trigger woocommerce validation if we are in the checkout page. + if ( this.context === 'checkout' ) { + const checkoutFormSelector = 'form.woocommerce-checkout'; + const errorHandler = new ErrorHandler( + PayPalCommerceGateway.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + try { + const formData = new FormData( + document.querySelector( checkoutFormSelector ) + ); + this.formData = Object.fromEntries( formData.entries() ); + + this.updateRequestDataWithForm( paymentRequest ); + } catch ( error ) { + console.error( error ); + } + + this.log( '=== paymentRequest', paymentRequest ); + + const session = this.applePaySession( paymentRequest ); + const formValidator = + PayPalCommerceGateway.early_checkout_validation_enabled + ? new FormValidator( + PayPalCommerceGateway.ajax.validate_checkout.endpoint, + PayPalCommerceGateway.ajax.validate_checkout.nonce + ) + : null; + if ( formValidator ) { + try { + const errors = await formValidator.validate( + document.querySelector( checkoutFormSelector ) + ); + if ( errors.length > 0 ) { + errorHandler.messages( errors ); + jQuery( document.body ).trigger( 'checkout_error', [ + errorHandler.currentHtml(), + ] ); + session.abort(); + return; + } + } catch ( error ) { + console.error( error ); + } + } + return; + } + + // Default session initialization. + this.applePaySession( paymentRequest ); + } + + /** + * If the button should show the shipping fields. + * + * @return {false|*} + */ + shouldRequireShippingInButton() { + return ( + this.contextHandler.shippingAllowed() && + this.buttonConfig.product.needShipping && + ( this.context !== 'checkout' || + this.shouldUpdateButtonWithFormData() ) + ); + } + + /** + * If the button should be updated with the form addresses. + * + * @return {boolean} + */ + shouldUpdateButtonWithFormData() { + if ( this.context !== 'checkout' ) { + return false; + } + 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. + * + * @return {boolean} + */ + shouldCompletePaymentWithContextHandler() { + // Data already handled, ex: PayNow + if ( ! this.contextHandler.shippingAllowed() ) { + return true; + } + // Use WC form data mode in Checkout. + if ( + this.context === 'checkout' && + ! this.shouldUpdateButtonWithFormData() + ) { + return true; + } + return false; + } + + /** + * Updates ApplePay paymentRequest with form data. + * @param paymentRequest + */ + updateRequestDataWithForm( paymentRequest ) { + if ( ! this.shouldUpdateButtonWithFormData() ) { + return; + } + + // Add billing address. + paymentRequest.billingContact = this.fillBillingContact( + this.formData + ); + + // Add custom data. + // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). + // paymentRequest.applicationData = this.fillApplicationData(this.formData); + + if ( ! this.shouldRequireShippingInButton() ) { + return; + } + + // Add shipping address. + paymentRequest.shippingContact = this.fillShippingContact( + this.formData + ); + + // Get shipping methods. + const rate = this.transactionInfo.chosenShippingMethods[ 0 ]; + paymentRequest.shippingMethods = []; + + // Add selected shipping method. + for ( const shippingPackage of this.transactionInfo.shippingPackages ) { + if ( rate === shippingPackage.id ) { + const shippingMethod = { + label: shippingPackage.label, + detail: '', + amount: shippingPackage.cost_str, + identifier: shippingPackage.id, + }; + + // Remember this shipping method as the selected one. + this.selectedShippingMethod = shippingMethod; + + paymentRequest.shippingMethods.push( shippingMethod ); + break; + } + } + + // Add other shipping methods. + 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, + } ); + } + } + + // Store for reuse in case this data is not provided by ApplePay on authorization. + this.initialPaymentRequest = paymentRequest; + + this.log( + '=== paymentRequest.shippingMethods', + paymentRequest.shippingMethods + ); + } + + paymentRequest() { + 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. + }; + + if ( ! this.shouldRequireShippingInButton() ) { + if ( this.shouldCompletePaymentWithContextHandler() ) { + // Data needs handled externally. + baseRequest.requiredShippingContactFields = []; + } else { + // Minimum data required for order creation. + baseRequest.requiredShippingContactFields = [ + 'email', + 'phone', + ]; + } + } + + const paymentRequest = Object.assign( {}, baseRequest ); + paymentRequest.currencyCode = buttonConfig.shop.currencyCode; + paymentRequest.total = { + label: buttonConfig.shop.totalLabel, + type: 'final', + amount: this.transactionInfo.totalPrice, + }; + + return paymentRequest; + } + + refreshContextData() { + switch ( this.context ) { + case 'product': + // Refresh product data that makes the price change. + this.productQuantity = + document.querySelector( 'input.qty' )?.value; + this.products = this.contextHandler.products(); + this.log( 'Products updated', this.products ); + break; + } + } + + //------------------------ + // Payment process + //------------------------ + + onValidateMerchant( session ) { + this.log( 'onvalidatemerchant', this.buttonConfig.ajax_url ); + return ( applePayValidateMerchantEvent ) => { + this.log( 'onvalidatemerchant call' ); + + widgetBuilder.paypal + .Applepay() + .validateMerchant( { + validationUrl: applePayValidateMerchantEvent.validationURL, + } ) + .then( ( validateResult ) => { + this.log( 'onvalidatemerchant ok' ); + session.completeMerchantValidation( + validateResult.merchantSession + ); + //call backend to update validation to true + jQuery.ajax( { + url: this.buttonConfig.ajax_url, + type: 'POST', + data: { + action: 'ppcp_validate', + validation: true, + 'woocommerce-process-checkout-nonce': this.nonce, + }, + } ); + } ) + .catch( ( validateError ) => { + this.log( 'onvalidatemerchant error', validateError ); + console.error( validateError ); + //call backend to update validation to false + jQuery.ajax( { + url: this.buttonConfig.ajax_url, + type: 'POST', + data: { + action: 'ppcp_validate', + validation: false, + 'woocommerce-process-checkout-nonce': this.nonce, + }, + } ); + this.log( 'onvalidatemerchant session abort' ); + session.abort(); + } ); + }; + } + + onShippingMethodSelected( session ) { + this.log( 'onshippingmethodselected', this.buttonConfig.ajax_url ); + const ajax_url = this.buttonConfig.ajax_url; + return ( event ) => { + this.log( 'onshippingmethodselected call' ); + + const data = this.getShippingMethodData( event ); + + jQuery.ajax( { + url: ajax_url, + method: 'POST', + data, + success: ( + applePayShippingMethodUpdate, + textStatus, + jqXHR + ) => { + this.log( 'onshippingmethodselected ok' ); + 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 + ) { + return -1; + } + return 1; + } ); + + if ( applePayShippingMethodUpdate.success === false ) { + response.errors = createAppleErrors( response.errors ); + } + session.completeShippingMethodSelection( response ); + }, + error: ( jqXHR, textStatus, errorThrown ) => { + this.log( 'onshippingmethodselected error', textStatus ); + console.warn( textStatus, errorThrown ); + session.abort(); + }, + } ); + }; + } + + onShippingContactSelected( session ) { + this.log( 'onshippingcontactselected', this.buttonConfig.ajax_url ); + + const ajax_url = this.buttonConfig.ajax_url; + + return ( event ) => { + this.log( 'onshippingcontactselected call' ); + + const data = this.getShippingContactData( event ); + + jQuery.ajax( { + url: ajax_url, + method: 'POST', + data, + success: ( + applePayShippingContactUpdate, + textStatus, + jqXHR + ) => { + this.log( 'onshippingcontactselected ok' ); + 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 ]; + } + session.completeShippingContactSelection( response ); + }, + error: ( jqXHR, textStatus, errorThrown ) => { + this.log( 'onshippingcontactselected error', textStatus ); + console.warn( textStatus, errorThrown ); + session.abort(); + }, + } ); + }; + } + + getShippingContactData( event ) { + const product_id = this.buttonConfig.product.id; + + this.refreshContextData(); + + switch ( this.context ) { + case 'product': + return { + action: 'ppcp_update_shipping_contact', + product_id, + products: JSON.stringify( this.products ), + caller_page: 'productDetail', + product_quantity: this.productQuantity, + simplified_contact: event.shippingContact, + need_shipping: this.shouldRequireShippingInButton(), + 'woocommerce-process-checkout-nonce': this.nonce, + }; + case 'cart': + case 'checkout': + case 'cart-block': + case 'checkout-block': + case 'mini-cart': + return { + action: 'ppcp_update_shipping_contact', + simplified_contact: event.shippingContact, + caller_page: 'cart', + need_shipping: this.shouldRequireShippingInButton(), + 'woocommerce-process-checkout-nonce': this.nonce, + }; + } + } + + getShippingMethodData( event ) { + const product_id = this.buttonConfig.product.id; + + this.refreshContextData(); + + switch ( this.context ) { + case 'product': + return { + action: 'ppcp_update_shipping_method', + shipping_method: event.shippingMethod, + 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': + case 'checkout-block': + case 'mini-cart': + return { + action: 'ppcp_update_shipping_method', + shipping_method: event.shippingMethod, + simplified_contact: + this.updatedContactInfo || + this.initialPaymentRequest.shippingContact || + this.initialPaymentRequest.billingContact, + caller_page: 'cart', + 'woocommerce-process-checkout-nonce': this.nonce, + }; + } + } + + onPaymentAuthorized( session ) { + this.log( 'onpaymentauthorized' ); + return async ( event ) => { + this.log( 'onpaymentauthorized call' ); + + function form() { + return document.querySelector( 'form.cart' ); + } + 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 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, + 'woocommerce-process-checkout-nonce': this.nonce, + 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 + ); + + jQuery.ajax( { + url: this.buttonConfig.ajax_url, + method: 'POST', + data: request_data, + complete: ( jqXHR, textStatus ) => { + this.log( 'onpaymentauthorized complete' ); + }, + success: ( + authorizationResult, + textStatus, + jqXHR + ) => { + this.log( 'onpaymentauthorized ok' ); + resolve( authorizationResult ); + }, + error: ( jqXHR, textStatus, errorThrown ) => { + this.log( + 'onpaymentauthorized error', + textStatus + ); + reject( new Error( errorThrown ) ); + }, + } ); + } catch ( error ) { + this.log( 'onpaymentauthorized catch', error ); + console.log( error ); // handle error + } + } ); + }; + + const id = await this.contextHandler.createOrder(); + + this.log( + 'onpaymentauthorized paypal order ID', + id, + event.payment.token, + event.payment.billingContact + ); + + try { + const confirmOrderResponse = await widgetBuilder.paypal + .Applepay() + .confirmOrder( { + orderId: id, + token: event.payment.token, + billingContact: event.payment.billingContact, + } ); + + this.log( + 'onpaymentauthorized confirmOrderResponse', + confirmOrderResponse + ); + + if ( + confirmOrderResponse && + confirmOrderResponse.approveApplePayPayment + ) { + if ( + confirmOrderResponse.approveApplePayPayment.status === + 'APPROVED' + ) { + try { + 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 ) => { + approveFailed = true; + resolve(); + } + ), + order: { + get: () => + new Promise( + ( resolve, reject ) => { + resolve( null ); + } + ), + }, + } + ); + + if ( ! approveFailed ) { + this.log( + 'onpaymentauthorized approveOrder OK' + ); + session.completePayment( + ApplePaySession.STATUS_SUCCESS + ); + } else { + this.log( + 'onpaymentauthorized approveOrder FAIL' + ); + session.completePayment( + ApplePaySession.STATUS_FAILURE + ); + session.abort(); + console.error( error ); + } + } else { + // Default payment. + + const data = { + billing_contact: + event.payment.billingContact, + shipping_contact: + event.payment.shippingContact, + paypal_order_id: id, + }; + 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 + ); + } + } + } catch ( error ) { + session.completePayment( + ApplePaySession.STATUS_FAILURE + ); + session.abort(); + console.error( error ); + } + } else { + console.error( 'Error status is not APPROVED' ); + 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 + ); + session.completePayment( ApplePaySession.STATUS_FAILURE ); + session.abort(); + } + }; + } + + fillBillingContact( data ) { + return { + givenName: data.billing_first_name ?? '', + familyName: data.billing_last_name ?? '', + emailAddress: data.billing_email ?? '', + phoneNumber: data.billing_phone ?? '', + addressLines: [ data.billing_address_1, data.billing_address_2 ], + locality: data.billing_city ?? '', + postalCode: data.billing_postcode ?? '', + countryCode: data.billing_country ?? '', + administrativeArea: data.billing_state ?? '', + }; + } + + fillShippingContact( data ) { + 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, + }; + } + + fillApplicationData( data ) { + const jsonString = JSON.stringify( data ); + const utf8Str = encodeURIComponent( jsonString ).replace( + /%([0-9A-F]{2})/g, + ( match, p1 ) => { + return String.fromCharCode( '0x' + p1 ); + } + ); + + return btoa( utf8Str ); + } } export default ApplepayButton; diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index fa9622143..0992d9e7d 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -1,55 +1,52 @@ -import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; -import ApplepayButton from "./ApplepayButton"; +import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; +import ApplepayButton from './ApplepayButton'; class ApplepayManager { + constructor( buttonConfig, ppcpConfig ) { + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.ApplePayConfig = null; + this.buttons = []; - constructor(buttonConfig, ppcpConfig) { + buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => { + const button = new ApplepayButton( + bootstrap.context, + bootstrap.handler, + buttonConfig, + ppcpConfig + ); - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.ApplePayConfig = null; - this.buttons = []; + this.buttons.push( button ); - buttonModuleWatcher.watchContextBootstrap((bootstrap) => { - const button = new ApplepayButton( - bootstrap.context, - bootstrap.handler, - buttonConfig, - ppcpConfig, - ); + if ( this.ApplePayConfig ) { + button.init( this.ApplePayConfig ); + } + } ); + } - this.buttons.push(button); + init() { + ( async () => { + await this.config(); + for ( const button of this.buttons ) { + button.init( this.ApplePayConfig ); + } + } )(); + } - if (this.ApplePayConfig) { - button.init(this.ApplePayConfig); - } - }); - } - - init() { - (async () => { - await this.config(); - for (const button of this.buttons) { - button.init(this.ApplePayConfig); - } - })(); - } - - reinit() { - for (const button of this.buttons) { - button.reinit(); - } - } - - /** - * Gets ApplePay configuration of the PayPal merchant. - * @returns {Promise} - */ - async config() { - this.ApplePayConfig = await paypal.Applepay().config(); - return this.ApplePayConfig; - } + reinit() { + for ( const button of this.buttons ) { + button.reinit(); + } + } + /** + * Gets ApplePay configuration of the PayPal merchant. + * @return {Promise} + */ + async config() { + this.ApplePayConfig = await paypal.Applepay().config(); + return this.ApplePayConfig; + } } export default ApplepayManager; diff --git a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js index 75ff956b5..2f4db9d41 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js @@ -1,36 +1,34 @@ -import ApplepayButton from "./ApplepayButton"; +import ApplepayButton from './ApplepayButton'; class ApplepayManagerBlockEditor { + constructor( buttonConfig, ppcpConfig ) { + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.applePayConfig = null; + } - constructor(buttonConfig, ppcpConfig) { - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.applePayConfig = null; - } + init() { + ( async () => { + await this.config(); + } )(); + } - init() { - (async () => { - await this.config(); - })(); - } + async config() { + try { + this.applePayConfig = await paypal.Applepay().config(); - async config() { - try { - this.applePayConfig = await paypal.Applepay().config(); + const button = new ApplepayButton( + this.ppcpConfig.context, + null, + this.buttonConfig, + this.ppcpConfig + ); - const button = new ApplepayButton( - this.ppcpConfig.context, - null, - this.buttonConfig, - this.ppcpConfig, - ); - - button.init(this.applePayConfig); - - } catch (error) { - console.error('Failed to initialize Apple Pay:', error); - } - } + button.init( this.applePayConfig ); + } catch ( error ) { + console.error( 'Failed to initialize Apple Pay:', error ); + } + } } export default ApplepayManagerBlockEditor; diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index 8f5926c70..f763ac5d9 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -1,95 +1,88 @@ -import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; -import CartActionHandler - from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler"; -import {isPayPalSubscription} from "../../../../ppcp-blocks/resources/js/Helper/Subscription"; +import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; +import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler'; +import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription'; class BaseHandler { + constructor( buttonConfig, ppcpConfig ) { + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + } - constructor(buttonConfig, ppcpConfig) { - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - } + isVaultV3Mode() { + return ( + this.ppcpConfig?.save_payment_methods?.id_token && // vault v3 + ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled && // not PayPal Subscriptions mode + this.ppcpConfig?.can_save_vault_token + ); // vault is enabled + } - isVaultV3Mode() { - return this.ppcpConfig?.save_payment_methods?.id_token // vault v3 - && ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled // not PayPal Subscriptions mode - && this.ppcpConfig?.can_save_vault_token; // vault is enabled - } + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { + return this.isVaultV3Mode(); + } + return true; + } - validateContext() { - if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { - return this.isVaultV3Mode(); - } - return true; - } + shippingAllowed() { + return this.buttonConfig.product.needsShipping; + } - shippingAllowed() { - return this.buttonConfig.product.needsShipping; - } + transactionInfo() { + return new Promise( ( resolve, reject ) => { + const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint; + const separator = endpoint.indexOf( '?' ) !== -1 ? '&' : '?'; - transactionInfo() { - return new Promise((resolve, reject) => { - const endpoint = this.ppcpConfig.ajax.cart_script_params.endpoint; - const separator = (endpoint.indexOf('?') !== -1) ? '&' : '?'; + fetch( endpoint + separator + 'shipping=1', { + method: 'GET', + credentials: 'same-origin', + } ) + .then( ( result ) => result.json() ) + .then( ( result ) => { + if ( ! result.success ) { + return; + } - fetch( - endpoint + separator + 'shipping=1', - { - method: 'GET', - credentials: 'same-origin' - } - ) - .then(result => result.json()) - .then(result => { - if (! result.success) { - return; - } + // handle script reload + const data = result.data; - // handle script reload - const data = result.data; + resolve( { + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str, + chosenShippingMethods: + data.chosen_shipping_methods || null, + shippingPackages: data.shipping_packages || null, + } ); + } ); + } ); + } - resolve({ - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str, - chosenShippingMethods: data.chosen_shipping_methods || null, - shippingPackages: data.shipping_packages || null, - }); + createOrder() { + return this.actionHandler().configuration().createOrder( null, null ); + } - }); - }); - } + approveOrder( data, actions ) { + return this.actionHandler().configuration().onApprove( data, actions ); + } - createOrder() { - return this.actionHandler().configuration().createOrder(null, null); - } + actionHandler() { + return new CartActionHandler( this.ppcpConfig, this.errorHandler() ); + } - approveOrder(data, actions) { - return this.actionHandler().configuration().onApprove(data, actions); - } - - actionHandler() { - return new CartActionHandler( - this.ppcpConfig, - this.errorHandler(), - ); - } - - errorHandler() { - return new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); - } - - errorHandler() { - return new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); - } + errorHandler() { + return new ErrorHandler( + this.ppcpConfig.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + } + errorHandler() { + return new ErrorHandler( + this.ppcpConfig.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + } } export default BaseHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/CartBlockHandler.js b/modules/ppcp-applepay/resources/js/Context/CartBlockHandler.js index 5f62080ee..5b5499fda 100644 --- a/modules/ppcp-applepay/resources/js/Context/CartBlockHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/CartBlockHandler.js @@ -1,7 +1,5 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; -class CartBlockHandler extends BaseHandler { - -} +class CartBlockHandler extends BaseHandler {} export default CartBlockHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/CartHandler.js b/modules/ppcp-applepay/resources/js/Context/CartHandler.js index 295bec718..7f70164e7 100644 --- a/modules/ppcp-applepay/resources/js/Context/CartHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/CartHandler.js @@ -1,7 +1,5 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; -class CartHandler extends BaseHandler { - -} +class CartHandler extends BaseHandler {} export default CartHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/CheckoutBlockHandler.js b/modules/ppcp-applepay/resources/js/Context/CheckoutBlockHandler.js index 8bbe9dff6..277f08110 100644 --- a/modules/ppcp-applepay/resources/js/Context/CheckoutBlockHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/CheckoutBlockHandler.js @@ -1,7 +1,5 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; -class CheckoutBlockHandler extends BaseHandler{ - -} +class CheckoutBlockHandler extends BaseHandler {} export default CheckoutBlockHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js index 67c88162a..246dd0697 100644 --- a/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js @@ -1,17 +1,15 @@ -import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; -import CheckoutActionHandler - from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler"; -import BaseHandler from "./BaseHandler"; +import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner'; +import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler'; +import BaseHandler from './BaseHandler'; class CheckoutHandler extends BaseHandler { - - actionHandler() { - return new CheckoutActionHandler( - this.ppcpConfig, - this.errorHandler(), - new Spinner() - ); - } + actionHandler() { + return new CheckoutActionHandler( + this.ppcpConfig, + this.errorHandler(), + new Spinner() + ); + } } export default CheckoutHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js index bea8e837b..d95dbe713 100644 --- a/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js +++ b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js @@ -1,34 +1,33 @@ -import SingleProductHandler from "./SingleProductHandler"; -import CartHandler from "./CartHandler"; -import CheckoutHandler from "./CheckoutHandler"; -import CartBlockHandler from "./CartBlockHandler"; -import CheckoutBlockHandler from "./CheckoutBlockHandler"; -import MiniCartHandler from "./MiniCartHandler"; -import PreviewHandler from "./PreviewHandler"; -import PayNowHandler from "./PayNowHandler"; +import SingleProductHandler from './SingleProductHandler'; +import CartHandler from './CartHandler'; +import CheckoutHandler from './CheckoutHandler'; +import CartBlockHandler from './CartBlockHandler'; +import CheckoutBlockHandler from './CheckoutBlockHandler'; +import MiniCartHandler from './MiniCartHandler'; +import PreviewHandler from './PreviewHandler'; +import PayNowHandler from './PayNowHandler'; class ContextHandlerFactory { - - static create(context, buttonConfig, ppcpConfig) { - switch (context) { - case 'product': - return new SingleProductHandler(buttonConfig, ppcpConfig); - case 'cart': - return new CartHandler(buttonConfig, ppcpConfig); - case 'checkout': - return new CheckoutHandler(buttonConfig, ppcpConfig); - case 'pay-now': - return new PayNowHandler(buttonConfig, ppcpConfig); - case 'mini-cart': - return new MiniCartHandler(buttonConfig, ppcpConfig); - case 'cart-block': - return new CartBlockHandler(buttonConfig, ppcpConfig); - case 'checkout-block': - return new CheckoutBlockHandler(buttonConfig, ppcpConfig); - case 'preview': - return new PreviewHandler(buttonConfig, ppcpConfig); - } - } + static create( context, buttonConfig, ppcpConfig ) { + switch ( context ) { + case 'product': + return new SingleProductHandler( buttonConfig, ppcpConfig ); + case 'cart': + return new CartHandler( buttonConfig, ppcpConfig ); + case 'checkout': + return new CheckoutHandler( buttonConfig, ppcpConfig ); + case 'pay-now': + return new PayNowHandler( buttonConfig, ppcpConfig ); + case 'mini-cart': + return new MiniCartHandler( buttonConfig, ppcpConfig ); + case 'cart-block': + return new CartBlockHandler( buttonConfig, ppcpConfig ); + case 'checkout-block': + return new CheckoutBlockHandler( buttonConfig, ppcpConfig ); + case 'preview': + return new PreviewHandler( buttonConfig, ppcpConfig ); + } + } } export default ContextHandlerFactory; diff --git a/modules/ppcp-applepay/resources/js/Context/MiniCartHandler.js b/modules/ppcp-applepay/resources/js/Context/MiniCartHandler.js index 1884203b7..0ca0253cf 100644 --- a/modules/ppcp-applepay/resources/js/Context/MiniCartHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/MiniCartHandler.js @@ -1,7 +1,5 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; -class MiniCartHandler extends BaseHandler { - -} +class MiniCartHandler extends BaseHandler {} export default MiniCartHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js index e5f382970..d56f5c39f 100644 --- a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js @@ -1,38 +1,35 @@ -import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; -import BaseHandler from "./BaseHandler"; -import CheckoutActionHandler - from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler"; +import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner'; +import BaseHandler from './BaseHandler'; +import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler'; class PayNowHandler extends BaseHandler { + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { + return this.isVaultV3Mode(); + } + return true; + } - validateContext() { - if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { - return this.isVaultV3Mode(); - } - return true; - } + transactionInfo() { + return new Promise( async ( resolve, reject ) => { + const data = this.ppcpConfig.pay_now; - transactionInfo() { - return new Promise(async (resolve, reject) => { - const data = this.ppcpConfig['pay_now']; - - resolve({ - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str - }); - }); - } - - actionHandler() { - return new CheckoutActionHandler( - this.ppcpConfig, - this.errorHandler(), - new Spinner() - ); - } + resolve( { + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str, + } ); + } ); + } + actionHandler() { + return new CheckoutActionHandler( + this.ppcpConfig, + this.errorHandler(), + new Spinner() + ); + } } export default PayNowHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js index 6e0da8852..8740705e9 100644 --- a/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js @@ -1,37 +1,35 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; class PreviewHandler extends BaseHandler { + constructor( buttonConfig, ppcpConfig, externalHandler ) { + super( buttonConfig, ppcpConfig, externalHandler ); + } - constructor(buttonConfig, ppcpConfig, externalHandler) { - super(buttonConfig, ppcpConfig, externalHandler); - } + transactionInfo() { + // We need to return something as ApplePay button initialization expects valid data. + return { + countryCode: 'US', + currencyCode: 'USD', + totalPrice: '10.00', + totalPriceStatus: 'FINAL', + }; + } - transactionInfo() { - // We need to return something as ApplePay button initialization expects valid data. - return { - countryCode: "US", - currencyCode: "USD", - totalPrice: "10.00", - totalPriceStatus: "FINAL" - } - } + createOrder() { + throw new Error( 'Create order fail. This is just a preview.' ); + } - createOrder() { - throw new Error('Create order fail. This is just a preview.'); - } + approveOrder( data, actions ) { + throw new Error( 'Approve order fail. This is just a preview.' ); + } - approveOrder(data, actions) { - throw new Error('Approve order fail. This is just a preview.'); - } - - actionHandler() { - throw new Error('Action handler fail. This is just a preview.'); - } - - errorHandler() { - throw new Error('Error handler fail. This is just a preview.'); - } + actionHandler() { + throw new Error( 'Action handler fail. This is just a preview.' ); + } + errorHandler() { + throw new Error( 'Error handler fail. This is just a preview.' ); + } } export default PreviewHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js index 5ad5857be..b49e412da 100644 --- a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js @@ -1,83 +1,82 @@ -import SingleProductActionHandler - from "../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler"; -import SimulateCart from "../../../../ppcp-button/resources/js/modules/Helper/SimulateCart"; -import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; -import UpdateCart from "../../../../ppcp-button/resources/js/modules/Helper/UpdateCart"; -import BaseHandler from "./BaseHandler"; +import SingleProductActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler'; +import SimulateCart from '../../../../ppcp-button/resources/js/modules/Helper/SimulateCart'; +import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; +import UpdateCart from '../../../../ppcp-button/resources/js/modules/Helper/UpdateCart'; +import BaseHandler from './BaseHandler'; class SingleProductHandler extends BaseHandler { + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.product ) { + return this.isVaultV3Mode(); + } + return true; + } - validateContext() { - if ( this.ppcpConfig?.locations_with_subscription_product?.product ) { - return this.isVaultV3Mode(); - } - return true; - } + transactionInfo() { + const errorHandler = new ErrorHandler( + this.ppcpConfig.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); - transactionInfo() { - const errorHandler = new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); + function form() { + return document.querySelector( 'form.cart' ); + } - function form() { - return document.querySelector('form.cart'); - } + const actionHandler = new SingleProductActionHandler( + null, + null, + form(), + errorHandler + ); - const actionHandler = new SingleProductActionHandler( - null, - null, - form(), - errorHandler, - ); + const hasSubscriptions = + PayPalCommerceGateway.data_client_id.has_subscriptions && + PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; - const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions - && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; + const products = hasSubscriptions + ? actionHandler.getSubscriptionProducts() + : actionHandler.getProducts(); - const products = hasSubscriptions - ? actionHandler.getSubscriptionProducts() - : actionHandler.getProducts(); + return new Promise( ( resolve, reject ) => { + new SimulateCart( + this.ppcpConfig.ajax.simulate_cart.endpoint, + this.ppcpConfig.ajax.simulate_cart.nonce + ).simulate( ( data ) => { + resolve( { + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str, + } ); + }, products ); + } ); + } - return new Promise((resolve, reject) => { - (new SimulateCart( - this.ppcpConfig.ajax.simulate_cart.endpoint, - this.ppcpConfig.ajax.simulate_cart.nonce, - )).simulate((data) => { + createOrder() { + return this.actionHandler() + .configuration() + .createOrder( null, null, { + updateCartOptions: { + keepShipping: true, + }, + } ); + } - resolve({ - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str - }); + actionHandler() { + return new SingleProductActionHandler( + this.ppcpConfig, + new UpdateCart( + this.ppcpConfig.ajax.change_cart.endpoint, + this.ppcpConfig.ajax.change_cart.nonce + ), + document.querySelector( 'form.cart' ), + this.errorHandler() + ); + } - }, products); - }); - } - - createOrder() { - return this.actionHandler().configuration().createOrder(null, null, { - 'updateCartOptions': { - 'keepShipping': true - } - }); - } - - actionHandler() { - return new SingleProductActionHandler( - this.ppcpConfig, - new UpdateCart( - this.ppcpConfig.ajax.change_cart.endpoint, - this.ppcpConfig.ajax.change_cart.nonce, - ), - document.querySelector('form.cart'), - this.errorHandler(), - ); - } - - products() { - return this.actionHandler().getProducts(); - } + products() { + return this.actionHandler().getProducts(); + } } export default SingleProductHandler; diff --git a/modules/ppcp-applepay/resources/js/Helper/applePayError.js b/modules/ppcp-applepay/resources/js/Helper/applePayError.js index 8775eea4e..33efe63d7 100644 --- a/modules/ppcp-applepay/resources/js/Helper/applePayError.js +++ b/modules/ppcp-applepay/resources/js/Helper/applePayError.js @@ -1,10 +1,12 @@ -export function createAppleErrors(errors) { - const errorList = [] - for (const error of errors) { - const {contactField = null, code = null, message = null} = error - const appleError = contactField ? new ApplePayError(code, contactField, message) : new ApplePayError(code) - errorList.push(appleError) - } +export function createAppleErrors( errors ) { + const errorList = []; + for ( const error of errors ) { + const { contactField = null, code = null, message = null } = error; + const appleError = contactField + ? new ApplePayError( code, contactField, message ) + : new ApplePayError( code ); + errorList.push( appleError ); + } - return errorList + return errorList; } diff --git a/modules/ppcp-applepay/resources/js/Helper/utils.js b/modules/ppcp-applepay/resources/js/Helper/utils.js index b5cfee8f7..3b8edc2c8 100644 --- a/modules/ppcp-applepay/resources/js/Helper/utils.js +++ b/modules/ppcp-applepay/resources/js/Helper/utils.js @@ -1,8 +1,8 @@ export const buttonID = 'applepay-container'; export const endpoints = { - validation: '_apple_pay_validation', - createOrderCart: '_apple_pay_create_order_cart', - createOrderProduct: '_apple_pay_create_order_product', - updateShippingMethod: '_apple_pay_update_shipping_contact', - updateShippingContact: '_apple_pay_update_billing_contact', -} + validation: '_apple_pay_validation', + createOrderCart: '_apple_pay_create_order_cart', + createOrderProduct: '_apple_pay_create_order_product', + updateShippingMethod: '_apple_pay_update_shipping_contact', + updateShippingContact: '_apple_pay_update_billing_contact', +}; diff --git a/modules/ppcp-applepay/resources/js/boot-admin.js b/modules/ppcp-applepay/resources/js/boot-admin.js index dffd0429b..080d7c8aa 100644 --- a/modules/ppcp-applepay/resources/js/boot-admin.js +++ b/modules/ppcp-applepay/resources/js/boot-admin.js @@ -6,106 +6,118 @@ import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Rend * Accessor that creates and returns a single PreviewButtonManager instance. */ const buttonManager = () => { - if (!ApplePayPreviewButtonManager.instance) { - ApplePayPreviewButtonManager.instance = new ApplePayPreviewButtonManager(); - } + if ( ! ApplePayPreviewButtonManager.instance ) { + ApplePayPreviewButtonManager.instance = + new ApplePayPreviewButtonManager(); + } - return ApplePayPreviewButtonManager.instance; + return ApplePayPreviewButtonManager.instance; }; - /** * Manages all Apple Pay preview buttons on this page. */ class ApplePayPreviewButtonManager extends PreviewButtonManager { - constructor() { - const args = { - methodName: 'ApplePay', - buttonConfig: window.wc_ppcp_applepay_admin, - }; + constructor() { + const args = { + methodName: 'ApplePay', + buttonConfig: window.wc_ppcp_applepay_admin, + }; - super(args); - } + super( args ); + } - /** - * Responsible for fetching and returning the PayPal configuration object for this payment - * method. - * - * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. - * @return {Promise<{}>} - */ - async fetchConfig(payPal) { - const apiMethod = payPal?.Applepay()?.config; + /** + * Responsible for fetching and returning the PayPal configuration object for this payment + * method. + * + * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. + * @return {Promise<{}>} + */ + async fetchConfig( payPal ) { + const apiMethod = payPal?.Applepay()?.config; - if (!apiMethod) { - this.error('configuration object cannot be retrieved from PayPal'); - return {}; - } + if ( ! apiMethod ) { + this.error( + 'configuration object cannot be retrieved from PayPal' + ); + return {}; + } - return await apiMethod(); - } + return await apiMethod(); + } - /** - * This method is responsible for creating a new PreviewButton instance and returning it. - * - * @param {string} wrapperId - CSS ID of the wrapper element. - * @return {ApplePayPreviewButton} - */ - createButtonInstance(wrapperId) { - return new ApplePayPreviewButton({ - selector: wrapperId, - apiConfig: this.apiConfig, - }); - } + /** + * This method is responsible for creating a new PreviewButton instance and returning it. + * + * @param {string} wrapperId - CSS ID of the wrapper element. + * @return {ApplePayPreviewButton} + */ + createButtonInstance( wrapperId ) { + return new ApplePayPreviewButton( { + selector: wrapperId, + apiConfig: this.apiConfig, + } ); + } } - /** * A single Apple Pay preview button instance. */ class ApplePayPreviewButton extends PreviewButton { - constructor(args) { - super(args); + constructor( args ) { + super( args ); - this.selector = `${args.selector}ApplePay`; - this.defaultAttributes = { - button: { - type: 'pay', - color: 'black', - lang: 'en', - }, - }; - } + this.selector = `${ args.selector }ApplePay`; + this.defaultAttributes = { + button: { + type: 'pay', + color: 'black', + lang: 'en', + }, + }; + } - createNewWrapper() { - const element = super.createNewWrapper(); - element.addClass('ppcp-button-applepay'); + createNewWrapper() { + const element = super.createNewWrapper(); + element.addClass( 'ppcp-button-applepay' ); - return element; - } + return element; + } - createButton(buttonConfig) { - const button = new ApplepayButton('preview', null, buttonConfig, this.ppcpConfig); + createButton( buttonConfig ) { + const button = new ApplepayButton( + 'preview', + null, + buttonConfig, + this.ppcpConfig + ); - button.init(this.apiConfig); - } + button.init( this.apiConfig ); + } - /** - * Merge form details into the config object for preview. - * Mutates the previewConfig object; no return value. - */ - dynamicPreviewConfig(buttonConfig, ppcpConfig) { - // The Apple Pay button expects the "wrapper" to be an ID without `#` prefix! - buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(/^#/, ''); + /** + * Merge form details into the config object for preview. + * Mutates the previewConfig object; no return value. + * @param buttonConfig + * @param ppcpConfig + */ + dynamicPreviewConfig( buttonConfig, ppcpConfig ) { + // The Apple Pay button expects the "wrapper" to be an ID without `#` prefix! + buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace( + /^#/, + '' + ); - // Merge the current form-values into the preview-button configuration. - if (ppcpConfig.button) { - buttonConfig.button.type = ppcpConfig.button.style.type; - buttonConfig.button.color = ppcpConfig.button.style.color; - buttonConfig.button.lang = - ppcpConfig.button.style?.lang || ppcpConfig.button.style.language; - } - } + // Merge the current form-values into the preview-button configuration. + if ( ppcpConfig.button ) { + buttonConfig.button.type = ppcpConfig.button.style.type; + buttonConfig.button.color = ppcpConfig.button.style.color; + buttonConfig.button.lang = + ppcpConfig.button.style?.lang || + ppcpConfig.button.style.language; + } + } } // Initialize the preview button manager. diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index d8e92ef24..8445466eb 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -1,79 +1,81 @@ -import {useEffect, useState} from '@wordpress/element'; -import {registerExpressPaymentMethod} from '@woocommerce/blocks-registry'; -import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' -import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription' -import {loadCustomScript} from "@paypal/paypal-js"; -import CheckoutHandler from "./Context/CheckoutHandler"; -import ApplepayManager from "./ApplepayManager"; -import ApplepayManagerBlockEditor from "./ApplepayManagerBlockEditor"; +import { useEffect, useState } from '@wordpress/element'; +import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry'; +import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; +import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription'; +import { loadCustomScript } from '@paypal/paypal-js'; +import CheckoutHandler from './Context/CheckoutHandler'; +import ApplepayManager from './ApplepayManager'; +import ApplepayManagerBlockEditor from './ApplepayManagerBlockEditor'; -const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data'); +const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' ); const ppcpConfig = ppcpData.scriptData; -const buttonData = wc.wcSettings.getSetting('ppcp-applepay_data'); +const buttonData = wc.wcSettings.getSetting( 'ppcp-applepay_data' ); const buttonConfig = buttonData.scriptData; -if (typeof window.PayPalCommerceGateway === 'undefined') { - window.PayPalCommerceGateway = ppcpConfig; +if ( typeof window.PayPalCommerceGateway === 'undefined' ) { + window.PayPalCommerceGateway = ppcpConfig; } const ApplePayComponent = ( props ) => { - const [bootstrapped, setBootstrapped] = useState(false); - const [paypalLoaded, setPaypalLoaded] = useState(false); - const [applePayLoaded, setApplePayLoaded] = useState(false); + const [ bootstrapped, setBootstrapped ] = useState( false ); + const [ paypalLoaded, setPaypalLoaded ] = useState( false ); + const [ applePayLoaded, setApplePayLoaded ] = useState( false ); - const bootstrap = function () { - const ManagerClass = props.isEditing ? ApplepayManagerBlockEditor : ApplepayManager; - const manager = new ManagerClass(buttonConfig, ppcpConfig); - manager.init(); - }; + const bootstrap = function () { + const ManagerClass = props.isEditing + ? ApplepayManagerBlockEditor + : ApplepayManager; + const manager = new ManagerClass( buttonConfig, ppcpConfig ); + manager.init(); + }; - useEffect(() => { - // Load ApplePay SDK - loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { - setApplePayLoaded(true); - }); + useEffect( () => { + // Load ApplePay SDK + loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => { + setApplePayLoaded( true ); + } ); - ppcpConfig.url_params.components += ',applepay'; + ppcpConfig.url_params.components += ',applepay'; - // Load PayPal - loadPaypalScript(ppcpConfig, () => { - setPaypalLoaded(true); - }); - }, []); + // Load PayPal + loadPaypalScript( ppcpConfig, () => { + setPaypalLoaded( true ); + } ); + }, [] ); - useEffect(() => { - if (!bootstrapped && paypalLoaded && applePayLoaded) { - setBootstrapped(true); - bootstrap(); - } - }, [paypalLoaded, applePayLoaded]); + useEffect( () => { + if ( ! bootstrapped && paypalLoaded && applePayLoaded ) { + setBootstrapped( true ); + bootstrap(); + } + }, [ paypalLoaded, applePayLoaded ] ); - return ( -
-
- ); -} + return ( +
+ ); +}; -const features = ['products']; +const features = [ 'products' ]; if ( - cartHasSubscriptionProducts(ppcpConfig) - && (new CheckoutHandler(buttonConfig, ppcpConfig)).isVaultV3Mode() + cartHasSubscriptionProducts( ppcpConfig ) && + new CheckoutHandler( buttonConfig, ppcpConfig ).isVaultV3Mode() ) { - features.push('subscriptions'); + features.push( 'subscriptions' ); } -registerExpressPaymentMethod({ - name: buttonData.id, - label:
, - content: , - edit: , - ariaLabel: buttonData.title, - canMakePayment: () => buttonData.enabled, - supports: { - features: features, - }, -}); +registerExpressPaymentMethod( { + name: buttonData.id, + label:
, + content: , + edit: , + ariaLabel: buttonData.title, + canMakePayment: () => buttonData.enabled, + supports: { + features, + }, +} ); diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index a32b72107..aee735231 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -1,71 +1,62 @@ -import {loadCustomScript} from "@paypal/paypal-js"; -import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; -import ApplepayManager from "./ApplepayManager"; -import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; +import { loadCustomScript } from '@paypal/paypal-js'; +import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; +import ApplepayManager from './ApplepayManager'; +import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; -(function ({ - buttonConfig, - ppcpConfig, - jQuery - }) { +( function ( { buttonConfig, ppcpConfig, jQuery } ) { + let manager; - let manager; + const bootstrap = function () { + manager = new ApplepayManager( buttonConfig, ppcpConfig ); + manager.init(); + }; - const bootstrap = function () { - manager = new ApplepayManager(buttonConfig, ppcpConfig); - manager.init(); - }; + setupButtonEvents( function () { + if ( manager ) { + manager.reinit(); + } + } ); - setupButtonEvents(function() { - if (manager) { - manager.reinit(); - } - }); + document.addEventListener( 'DOMContentLoaded', () => { + if ( + typeof buttonConfig === 'undefined' || + typeof ppcpConfig === 'undefined' + ) { + return; + } + const isMiniCart = ppcpConfig.mini_cart_buttons_enabled; + const isButton = jQuery( '#' + buttonConfig.button.wrapper ).length > 0; + // If button wrapper is not present then there is no need to load the scripts. + // minicart loads later? + if ( ! isMiniCart && ! isButton ) { + return; + } - document.addEventListener( - 'DOMContentLoaded', - () => { - if ( - (typeof (buttonConfig) === 'undefined') || - (typeof (ppcpConfig) === 'undefined') - ) { - return; - } - const isMiniCart = ppcpConfig.mini_cart_buttons_enabled; - const isButton = jQuery('#' + buttonConfig.button.wrapper).length > 0; - // If button wrapper is not present then there is no need to load the scripts. - // minicart loads later? - if (!isMiniCart && !isButton) { - return; - } + let bootstrapped = false; + let paypalLoaded = false; + let applePayLoaded = false; - let bootstrapped = false; - let paypalLoaded = false; - let applePayLoaded = false; + const tryToBoot = () => { + if ( ! bootstrapped && paypalLoaded && applePayLoaded ) { + bootstrapped = true; + bootstrap(); + } + }; - const tryToBoot = () => { - if (!bootstrapped && paypalLoaded && applePayLoaded) { - bootstrapped = true; - bootstrap(); - } - } + // Load ApplePay SDK + loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => { + applePayLoaded = true; + tryToBoot(); + } ); - // Load ApplePay SDK - loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { - applePayLoaded = true; - tryToBoot(); - }); - - // Load PayPal - loadPaypalScript(ppcpConfig, () => { - paypalLoaded = true; - tryToBoot(); - }); - }, - ); - -})({ - buttonConfig: window.wc_ppcp_applepay, - ppcpConfig: window.PayPalCommerceGateway, - jQuery: window.jQuery -}); + // Load PayPal + loadPaypalScript( ppcpConfig, () => { + paypalLoaded = true; + tryToBoot(); + } ); + } ); +} )( { + buttonConfig: window.wc_ppcp_applepay, + ppcpConfig: window.PayPalCommerceGateway, + jQuery: window.jQuery, +} ); diff --git a/modules/ppcp-axo/resources/js/AxoManager.js b/modules/ppcp-axo/resources/js/AxoManager.js index be2176585..b0837bcd6 100644 --- a/modules/ppcp-axo/resources/js/AxoManager.js +++ b/modules/ppcp-axo/resources/js/AxoManager.js @@ -1,975 +1,1168 @@ -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; - - this.initialized = false; - this.fastlane = new Fastlane(); - this.$ = jQuery; - - this.hideGatewaySelection = false; - - this.status = { - active: false, - validEmail: false, - hasProfile: false, - useEmailWidget: this.useEmailWidget(), - hasCard: false, - }; - - this.data = { - email: null, - billing: null, - shipping: null, - card: null, - }; - - this.states = this.axoConfig.woocommerce.states; - - this.el = new DomElementCollection(); - - this.emailInput = document.querySelector(this.el.fieldBillingEmail.selector + ' input'); - - this.styles = { - root: { - 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); - - 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 - ) { - PayPalInsights.config(this.axoConfig?.insights?.client_id, { debug: true }); - PayPalInsights.setSessionId(this.axoConfig?.insights?.session_id); - PayPalInsights.trackJsLoad(); - - if (document.querySelector('.woocommerce-checkout')) { - PayPalInsights.trackBeginCheckout({ - amount: this.axoConfig?.insights?.amount, - page_type: 'checkout', - user_data: { - country: 'US', - is_store_member: false, - } - }); - } - } - - this.triggerGatewayChange(); - } - - registerEventHandlers() { - - 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' - }); - }); - - - // Listen to Gateway Radio button changes. - this.el.gatewayRadioButton.on('change', (ev) => { - if (ev.target.checked) { - this.activateAxo(); - } else { - this.deactivateAxo(); - } - }); - - 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(); - - if (selectionChanged) { - this.setShipping(selectedAddress); - this.shippingView.refresh(); - } - } - }); - - // Click change billing address link. - this.el.changeBillingAddressLink.on('click', async () => { - if (this.status.hasProfile) { - this.el.changeCardLink.trigger('click'); - } - }); - - // Click change card link. - this.el.changeCardLink.on('click', async () => { - const response = await this.fastlane.profile.showCardSelector(); - - if (response.selectionChanged) { - this.setCard(response.selectedCard); - this.setBilling({ - address: response.selectedCard.paymentSource.card.billingAddress - }); - } - }); - - // Cancel "continuation" mode. - this.el.showGatewaySelectionLink.on('click', async () => { - this.hideGatewaySelection = false; - this.$('.wc_payment_methods label').show(); - this.$('.wc_payment_methods input').show(); - this.cardView.refresh(); - }); - - // 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' ) { - ev.preventDefault(); - 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) { - 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}`); - this.lastEmailCheckedIdentity = ''; - }); - - // Listening to status update event - document.addEventListener('axo_status_updated', (ev) => { - const termsField = document.querySelector("[name='terms-field']"); - if(termsField) { - const status = ev.detail; - const shouldHide = status.active && status.validEmail === false && status.hasProfile === false; - - termsField.parentElement.style.display = shouldHide ? 'none' : 'block'; - } - }); - } - - rerender() { - /** - * active | 0 1 1 1 - * validEmail | * 0 1 1 - * hasProfile | * * 0 1 - * -------------------------------- - * defaultSubmitButton | 1 0 0 0 - * defaultEmailField | 1 0 0 0 - * defaultFormFields | 1 0 1 0 - * extraFormFields | 0 0 0 1 - * axoEmailField | 0 1 0 0 - * axoProfileViews | 0 0 0 1 - * axoPaymentContainer | 0 0 1 1 - * axoSubmitButton | 0 0 1 1 - */ - const scenario = this.identifyScenario( - this.status.active, - this.status.validEmail, - this.status.hasProfile - ); - - log(`Scenario: ${JSON.stringify(scenario)}`); - - // Reset some elements to a default status. - this.el.watermarkContainer.hide(); - - if (scenario.defaultSubmitButton) { - this.el.defaultSubmitButton.show(); - this.el.billingEmailSubmitButton.hide(); - } else { - this.el.defaultSubmitButton.hide(); - this.el.billingEmailSubmitButton.show(); - } - - if (scenario.defaultEmailField) { - this.el.fieldBillingEmail.show(); - } else { - this.el.fieldBillingEmail.hide(); - } - - if (scenario.defaultFormFields) { - this.el.customerDetails.show(); - this.toggleLoaderAndOverlay(this.el.customerDetails, 'loader', 'ppcp-axo-overlay'); - } else { - this.el.customerDetails.hide(); - } - - if (scenario.extraFormFields) { - this.el.customerDetails.show(); - // Hiding of unwanted will be handled by the axoProfileViews handler. - } - - if (scenario.axoEmailField) { - this.showAxoEmailField(); - this.el.watermarkContainer.show(); - - // Move watermark to after email. - document.querySelector('#billing_email_field .woocommerce-input-wrapper').append( - document.querySelector(this.el.watermarkContainer.selector) - ); - } else { - this.el.emailWidgetContainer.hide(); - if (!scenario.defaultEmailField) { - this.el.fieldBillingEmail.hide(); - } - } - - if (scenario.axoProfileViews) { - - this.shippingView.activate(); - this.cardView.activate(); - - if (this.status.hasCard) { - this.billingView.activate(); - } - - // Move watermark to after shipping. - this.$(this.el.shippingAddressContainer.selector).after( - this.$(this.el.watermarkContainer.selector) - ); - - this.el.watermarkContainer.show(); - - // Add class to customer details container. - this.$(this.el.axoCustomerDetails.selector).addClass('col-1'); - } else { - this.shippingView.deactivate(); - this.billingView.deactivate(); - this.cardView.deactivate(); - 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'); - } else { - this.el.paymentContainer.hide(); - } - - if (scenario.axoSubmitButton) { - this.el.submitButtonContainer.show(); - } else { - this.el.submitButtonContainer.hide(); - } - - this.ensureBillingFieldsConsistency(); - this.ensureShippingFieldsConsistency(); - } - - identifyScenario(active, validEmail, hasProfile) { - let response = { - defaultSubmitButton: false, - defaultEmailField: false, - defaultFormFields: false, - extraFormFields: false, - axoEmailField: false, - axoProfileViews: false, - axoPaymentContainer: false, - axoSubmitButton: false, - } - - if (active && validEmail && hasProfile) { - response.extraFormFields = true; - response.axoProfileViews = true; - response.axoPaymentContainer = true; - response.axoSubmitButton = true; - return response; - } - if (active && validEmail && !hasProfile) { - response.defaultFormFields = true; - response.axoEmailField = true; - response.axoPaymentContainer = true; - response.axoSubmitButton = true; - return response; - } - if (active && !validEmail) { - response.axoEmailField = true; - return response; - } - if (!active) { - response.defaultSubmitButton = true; - response.defaultEmailField = true; - response.defaultFormFields = true; - return response; - } - throw new Error('Invalid scenario.'); - } - - ensureBillingFieldsConsistency() { - const $billingFields = this.$('.woocommerce-billing-fields .form-row:visible'); - const $billingHeaders = this.$('.woocommerce-billing-fields h3'); - if (this.billingView.isActive()) { - if ($billingFields.length) { - $billingHeaders.show(); - } else { - $billingHeaders.hide(); - } - } else { - $billingHeaders.show(); - } - } - - ensureShippingFieldsConsistency() { - const $shippingFields = this.$('.woocommerce-shipping-fields .form-row:visible'); - const $shippingHeaders = this.$('.woocommerce-shipping-fields h3'); - if (this.shippingView.isActive()) { - if ($shippingFields.length) { - $shippingHeaders.show(); - } else { - $shippingHeaders.hide(); - } - } else { - $shippingHeaders.show(); - } - } - - showAxoEmailField() { - if (this.status.useEmailWidget) { - this.el.emailWidgetContainer.show(); - this.el.fieldBillingEmail.hide(); - } else { - this.el.emailWidgetContainer.hide(); - this.el.fieldBillingEmail.show(); - } - } - - setStatus(key, value) { - this.status[key] = value; - - log(`Status updated: ${JSON.stringify(this.status)}`); - - document.dispatchEvent(new CustomEvent("axo_status_updated", {detail: this.status})); - - this.rerender(); - } - - activateAxo() { - this.initPlacements(); - this.initFastlane(); - 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) { - this.onChangeEmail(); - } - } - - deactivateAxo() { - this.setStatus('active', false); - } - - initPlacements() { - const wrapper = this.el.axoCustomerDetails; - - // Customer details container. - if (!document.querySelector(wrapper.selector)) { - document.querySelector(wrapper.anchorSelector).insertAdjacentHTML('afterbegin', ` -
- `); - } - - const wrapperElement = document.querySelector(wrapper.selector); - - // Billing view container. - const bc = this.el.billingAddressContainer; - if (!document.querySelector(bc.selector)) { - wrapperElement.insertAdjacentHTML('beforeend', ` -
- `); - } - - // Shipping view container. - const sc = this.el.shippingAddressContainer; - if (!document.querySelector(sc.selector)) { - wrapperElement.insertAdjacentHTML('beforeend', ` -
- `); - } - - // billingEmailFieldWrapper - const befw = this.el.billingEmailFieldWrapper; - if (!document.querySelector(befw.selector)) { - document.querySelector('#billing_email_field .woocommerce-input-wrapper').insertAdjacentHTML('afterend', ` -
- `); - } - - // Watermark container - const wc = this.el.watermarkContainer; - if (!document.querySelector(wc.selector)) { - document.querySelector(befw.selector).insertAdjacentHTML('beforeend', ` -
- `); - } - - // Payment container - const pc = this.el.paymentContainer; - if (!document.querySelector(pc.selector)) { - const gatewayPaymentContainer = document.querySelector('.payment_method_ppcp-axo-gateway'); - gatewayPaymentContainer.insertAdjacentHTML('beforeend', ` -
-
-
+ constructor( axoConfig, ppcpConfig ) { + this.axoConfig = axoConfig; + this.ppcpConfig = ppcpConfig; + + this.initialized = false; + this.fastlane = new Fastlane(); + this.$ = jQuery; + + this.hideGatewaySelection = false; + + this.status = { + active: false, + validEmail: false, + hasProfile: false, + useEmailWidget: this.useEmailWidget(), + hasCard: false, + }; + + this.data = { + email: null, + billing: null, + shipping: null, + card: null, + }; + + this.states = this.axoConfig.woocommerce.states; + + this.el = new DomElementCollection(); + + this.emailInput = document.querySelector( + this.el.fieldBillingEmail.selector + ' input' + ); + + this.styles = { + root: { + 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 + ); + + 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 + ) { + PayPalInsights.config( this.axoConfig?.insights?.client_id, { + debug: true, + } ); + PayPalInsights.setSessionId( this.axoConfig?.insights?.session_id ); + PayPalInsights.trackJsLoad(); + + if ( document.querySelector( '.woocommerce-checkout' ) ) { + PayPalInsights.trackBeginCheckout( { + amount: this.axoConfig?.insights?.amount, + page_type: 'checkout', + user_data: { + country: 'US', + is_store_member: false, + }, + } ); + } + } + + this.triggerGatewayChange(); + } + + registerEventHandlers() { + 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', + } ); + } + ); + + // Listen to Gateway Radio button changes. + this.el.gatewayRadioButton.on( 'change', ( ev ) => { + if ( ev.target.checked ) { + this.activateAxo(); + } else { + this.deactivateAxo(); + } + } ); + + 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(); + + if ( selectionChanged ) { + this.setShipping( selectedAddress ); + this.shippingView.refresh(); + } + } + } ); + + // Click change billing address link. + this.el.changeBillingAddressLink.on( 'click', async () => { + if ( this.status.hasProfile ) { + this.el.changeCardLink.trigger( 'click' ); + } + } ); + + // Click change card link. + this.el.changeCardLink.on( 'click', async () => { + const response = await this.fastlane.profile.showCardSelector(); + + if ( response.selectionChanged ) { + this.setCard( response.selectedCard ); + this.setBilling( { + address: + response.selectedCard.paymentSource.card.billingAddress, + } ); + } + } ); + + // Cancel "continuation" mode. + this.el.showGatewaySelectionLink.on( 'click', async () => { + this.hideGatewaySelection = false; + this.$( '.wc_payment_methods label' ).show(); + this.$( '.wc_payment_methods input' ).show(); + this.cardView.refresh(); + } ); + + // 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' + ) { + ev.preventDefault(); + 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 + ) { + 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 }` + ); + this.lastEmailCheckedIdentity = ''; + } ); + + // Listening to status update event + document.addEventListener( 'axo_status_updated', ( ev ) => { + const termsField = document.querySelector( "[name='terms-field']" ); + if ( termsField ) { + const status = ev.detail; + const shouldHide = + status.active && + status.validEmail === false && + status.hasProfile === false; + + termsField.parentElement.style.display = shouldHide + ? 'none' + : 'block'; + } + } ); + } + + rerender() { + /** + * active | 0 1 1 1 + * validEmail | * 0 1 1 + * hasProfile | * * 0 1 + * -------------------------------- + * defaultSubmitButton | 1 0 0 0 + * defaultEmailField | 1 0 0 0 + * defaultFormFields | 1 0 1 0 + * extraFormFields | 0 0 0 1 + * axoEmailField | 0 1 0 0 + * axoProfileViews | 0 0 0 1 + * axoPaymentContainer | 0 0 1 1 + * axoSubmitButton | 0 0 1 1 + */ + const scenario = this.identifyScenario( + this.status.active, + this.status.validEmail, + this.status.hasProfile + ); + + log( `Scenario: ${ JSON.stringify( scenario ) }` ); + + // Reset some elements to a default status. + this.el.watermarkContainer.hide(); + + if ( scenario.defaultSubmitButton ) { + this.el.defaultSubmitButton.show(); + this.el.billingEmailSubmitButton.hide(); + } else { + this.el.defaultSubmitButton.hide(); + this.el.billingEmailSubmitButton.show(); + } + + if ( scenario.defaultEmailField ) { + this.el.fieldBillingEmail.show(); + } else { + this.el.fieldBillingEmail.hide(); + } + + if ( scenario.defaultFormFields ) { + this.el.customerDetails.show(); + this.toggleLoaderAndOverlay( + this.el.customerDetails, + 'loader', + 'ppcp-axo-overlay' + ); + } else { + this.el.customerDetails.hide(); + } + + if ( scenario.extraFormFields ) { + this.el.customerDetails.show(); + // Hiding of unwanted will be handled by the axoProfileViews handler. + } + + if ( scenario.axoEmailField ) { + this.showAxoEmailField(); + this.el.watermarkContainer.show(); + + // Move watermark to after email. + document + .querySelector( + '#billing_email_field .woocommerce-input-wrapper' + ) + .append( + document.querySelector( + this.el.watermarkContainer.selector + ) + ); + } else { + this.el.emailWidgetContainer.hide(); + if ( ! scenario.defaultEmailField ) { + this.el.fieldBillingEmail.hide(); + } + } + + if ( scenario.axoProfileViews ) { + this.shippingView.activate(); + this.cardView.activate(); + + if ( this.status.hasCard ) { + this.billingView.activate(); + } + + // Move watermark to after shipping. + this.$( this.el.shippingAddressContainer.selector ).after( + this.$( this.el.watermarkContainer.selector ) + ); + + this.el.watermarkContainer.show(); + + // Add class to customer details container. + this.$( this.el.axoCustomerDetails.selector ).addClass( 'col-1' ); + } else { + this.shippingView.deactivate(); + this.billingView.deactivate(); + this.cardView.deactivate(); + 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' ); + } else { + this.el.paymentContainer.hide(); + } + + if ( scenario.axoSubmitButton ) { + this.el.submitButtonContainer.show(); + } else { + this.el.submitButtonContainer.hide(); + } + + this.ensureBillingFieldsConsistency(); + this.ensureShippingFieldsConsistency(); + } + + identifyScenario( active, validEmail, hasProfile ) { + const response = { + defaultSubmitButton: false, + defaultEmailField: false, + defaultFormFields: false, + extraFormFields: false, + axoEmailField: false, + axoProfileViews: false, + axoPaymentContainer: false, + axoSubmitButton: false, + }; + + if ( active && validEmail && hasProfile ) { + response.extraFormFields = true; + response.axoProfileViews = true; + response.axoPaymentContainer = true; + response.axoSubmitButton = true; + return response; + } + if ( active && validEmail && ! hasProfile ) { + response.defaultFormFields = true; + response.axoEmailField = true; + response.axoPaymentContainer = true; + response.axoSubmitButton = true; + return response; + } + if ( active && ! validEmail ) { + response.axoEmailField = true; + return response; + } + if ( ! active ) { + response.defaultSubmitButton = true; + response.defaultEmailField = true; + response.defaultFormFields = true; + return response; + } + throw new Error( 'Invalid scenario.' ); + } + + ensureBillingFieldsConsistency() { + const $billingFields = this.$( + '.woocommerce-billing-fields .form-row:visible' + ); + const $billingHeaders = this.$( '.woocommerce-billing-fields h3' ); + if ( this.billingView.isActive() ) { + if ( $billingFields.length ) { + $billingHeaders.show(); + } else { + $billingHeaders.hide(); + } + } else { + $billingHeaders.show(); + } + } + + ensureShippingFieldsConsistency() { + const $shippingFields = this.$( + '.woocommerce-shipping-fields .form-row:visible' + ); + const $shippingHeaders = this.$( '.woocommerce-shipping-fields h3' ); + if ( this.shippingView.isActive() ) { + if ( $shippingFields.length ) { + $shippingHeaders.show(); + } else { + $shippingHeaders.hide(); + } + } else { + $shippingHeaders.show(); + } + } + + showAxoEmailField() { + if ( this.status.useEmailWidget ) { + this.el.emailWidgetContainer.show(); + this.el.fieldBillingEmail.hide(); + } else { + this.el.emailWidgetContainer.hide(); + this.el.fieldBillingEmail.show(); + } + } + + setStatus( key, value ) { + this.status[ key ] = value; + + log( `Status updated: ${ JSON.stringify( this.status ) }` ); + + document.dispatchEvent( + new CustomEvent( 'axo_status_updated', { detail: this.status } ) + ); + + this.rerender(); + } + + activateAxo() { + this.initPlacements(); + this.initFastlane(); + 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 + ) { + this.onChangeEmail(); + } + } + + deactivateAxo() { + this.setStatus( 'active', false ); + } + + initPlacements() { + const wrapper = this.el.axoCustomerDetails; + + // Customer details container. + if ( ! document.querySelector( wrapper.selector ) ) { + document.querySelector( wrapper.anchorSelector ).insertAdjacentHTML( + 'afterbegin', + ` +
+ ` + ); + } + + const wrapperElement = document.querySelector( wrapper.selector ); + + // Billing view container. + const bc = this.el.billingAddressContainer; + if ( ! document.querySelector( bc.selector ) ) { + wrapperElement.insertAdjacentHTML( + 'beforeend', + ` +
+ ` + ); + } + + // Shipping view container. + const sc = this.el.shippingAddressContainer; + if ( ! document.querySelector( sc.selector ) ) { + wrapperElement.insertAdjacentHTML( + 'beforeend', + ` +
+ ` + ); + } + + // billingEmailFieldWrapper + const befw = this.el.billingEmailFieldWrapper; + if ( ! document.querySelector( befw.selector ) ) { + document + .querySelector( + '#billing_email_field .woocommerce-input-wrapper' + ) + .insertAdjacentHTML( + 'afterend', + ` +
+ ` + ); + } + + // Watermark container + const wc = this.el.watermarkContainer; + if ( ! document.querySelector( wc.selector ) ) { + document.querySelector( befw.selector ).insertAdjacentHTML( + 'beforeend', + ` +
+ ` + ); + } + + // Payment container + const pc = this.el.paymentContainer; + if ( ! document.querySelector( pc.selector ) ) { + const gatewayPaymentContainer = document.querySelector( + '.payment_method_ppcp-axo-gateway' + ); + gatewayPaymentContainer.insertAdjacentHTML( + 'beforeend', + ` +
+
+
- `); - } + ` + ); + } - if (this.useEmailWidget()) { - - // Display email widget. - const ec = this.el.emailWidgetContainer; - if (!document.querySelector(ec.selector)) { - wrapperElement.insertAdjacentHTML('afterbegin', ` -
+ if ( this.useEmailWidget() ) { + // Display email widget. + const ec = this.el.emailWidgetContainer; + if ( ! document.querySelector( ec.selector ) ) { + wrapperElement.insertAdjacentHTML( + 'afterbegin', + ` +
--- EMAIL WIDGET PLACEHOLDER ---
- `); - } + ` + ); + } + } else { + // Move email to the AXO container. + 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' + ) + ); + } + } - } else { - // Move email to the AXO container. - let 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')); - } - } + async initFastlane() { + if ( this.initialized ) { + return; + } + this.initialized = true; - async initFastlane() { - if (this.initialized) { - return; - } - this.initialized = true; + await this.connect(); + await this.renderWatermark(); + this.renderEmailSubmitButton(); + this.watchEmail(); + } - await this.connect(); - await this.renderWatermark(); - this.renderEmailSubmitButton(); - this.watchEmail(); - } + async connect() { + if ( this.axoConfig.environment.is_sandbox ) { + window.localStorage.setItem( 'axoEnv', 'sandbox' ); + } - async connect() { - if (this.axoConfig.environment.is_sandbox) { - window.localStorage.setItem('axoEnv', 'sandbox'); - } + await this.fastlane.connect( { + locale: this.locale, + styles: this.styles, + } ); - await this.fastlane.connect({ - locale: this.locale, - styles: this.styles - }); + this.fastlane.setLocale( 'en_us' ); + } - this.fastlane.setLocale('en_us'); - } + triggerGatewayChange() { + this.el.gatewayRadioButton.trigger( 'change' ); + } - triggerGatewayChange() { - this.el.gatewayRadioButton.trigger('change'); - } + async renderWatermark( includeAdditionalInfo = true ) { + ( + await this.fastlane.FastlaneWatermarkComponent( { + includeAdditionalInfo, + } ) + ).render( this.el.watermarkContainer.selector ); - async renderWatermark(includeAdditionalInfo = true) { - (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; - renderEmailSubmitButton() { - const billingEmailSubmitButton = this.el.billingEmailSubmitButton; - const billingEmailSubmitButtonSpinner = this.el.billingEmailSubmitButtonSpinner; - - if (!document.querySelector(billingEmailSubmitButton.selector)) { - document.querySelector(this.el.billingEmailFieldWrapper.selector).insertAdjacentHTML('beforeend', ` - - `); - - 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) { - this.validateEmail(this.el.fieldBillingEmail.selector); - this.onChangeEmail(); - } - }); - - log(`Last, this.emailInput.value attempt - emailInput: ${this.emailInput.value}`); - log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`); - if (this.emailInput.value) { - this.onChangeEmail(); - } - } - } - - async onChangeEmail () { - this.clearData(); - - if (!this.status.active) { - log('Email checking skipped, AXO not active.'); - return; - } - - if (!this.emailInput) { - log('Email field not initialized.'); - return; - } - - log(`Email changed: ${this.emailInput ? this.emailInput.value : ''}`); - - this.$(this.el.paymentContainer.selector + '-detail').html(''); - this.$(this.el.paymentContainer.selector + '-form').html(''); - - this.setStatus('validEmail', false); - this.setStatus('hasProfile', false); - - this.hideGatewaySelection = false; - - this.lastEmailCheckedIdentity = this.emailInput.value; - - if (!this.emailInput.value || !this.emailInput.checkValidity() || !this.validateEmailFormat(this.emailInput.value)) { - log('The email address is not valid.'); - return; - } - - this.data.email = this.emailInput.value; - this.billingView.setData(this.data); - - if (!this.fastlane.identity) { - log('Not initialized.'); - return; - } - - PayPalInsights.trackSubmitCheckoutEmail({ - page_type: 'checkout' - }); - - - this.disableGatewaySelection(); - this.spinnerToggleLoaderAndOverlay(this.el.billingEmailSubmitButtonSpinner, 'loader', 'ppcp-axo-overlay'); - await this.lookupCustomerByEmail(); - this.spinnerToggleLoaderAndOverlay(this.el.billingEmailSubmitButtonSpinner, 'loader', 'ppcp-axo-overlay'); - this.enableGatewaySelection(); - } - - async lookupCustomerByEmail() { - 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'); - - const authResponse = await this.fastlane.identity.triggerAuthenticationFlow(lookupResponse.customerContextId); - - log(`AuthResponse - triggerAuthenticationFlow: ${JSON.stringify(authResponse)}`); - - if (authResponse.authenticationState === 'succeeded') { - const shippingData = authResponse.profileData.shippingAddress; - if (shippingData) { - this.setShipping(shippingData); - } - - if (authResponse.profileData.card) { - this.setStatus('hasCard', true); - } else { - this.cardComponent = (await this.fastlane.FastlaneCardComponent( - this.cardComponentData() - )).render(this.el.paymentContainer.selector + '-form'); - } - - 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 ?? ''; - if(phoneNumber) { - billingData.phoneNumber = phoneNumber - } - - this.setBilling(billingData); - } - - this.setStatus('validEmail', true); - this.setStatus('hasProfile', true); - - this.hideGatewaySelection = true; - this.$('.wc_payment_methods label').hide(); - this.$('.wc_payment_methods input').hide(); - - await this.renderWatermark(false); - - this.rerender(); - - } else { - // authentication failed or canceled by the customer - // set status as guest customer - log("Authentication Failed") - - this.setStatus('validEmail', true); - this.setStatus('hasProfile', false); - - await this.renderWatermark(true); - - this.cardComponent = (await this.fastlane.FastlaneCardComponent( - this.cardComponentData() - )).render(this.el.paymentContainer.selector + '-form'); - } - - } else { - // No profile found with this email address. - // This is a guest customer. - log('No profile found with this email address.'); - - this.setStatus('validEmail', true); - this.setStatus('hasProfile', false); - - await this.renderWatermark(true); - - this.cardComponent = (await this.fastlane.FastlaneCardComponent( - this.cardComponentData() - )).render(this.el.paymentContainer.selector + '-form'); - } - } - - disableGatewaySelection() { - this.$('.wc_payment_methods input').prop('disabled', true); - } - - enableGatewaySelection() { - this.$('.wc_payment_methods input').prop('disabled', false); - } - - clearData() { - this.data = { - email: null, - billing: null, - shipping: null, - card: null, - }; - } - - setShipping(shipping) { - this.data.shipping = shipping; - this.shippingView.setData(this.data); - } - - setBilling(billing) { - this.data.billing = billing; - this.billingView.setData(this.data); - } - - setCard(card) { - this.data.card = card; - this.cardView.setData(this.data); - } - - onClickSubmitButton() { - // TODO: validate data. - - if (this.data.card) { // Ryan flow - log('Starting Ryan flow.'); - - this.$('#ship-to-different-address-checkbox').prop('checked', 'checked'); - - let 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}` ) - - this.submit(this.data.card.id, data); - - } else { // Gary flow - log('Starting Gary flow.'); - - try { - this.cardComponent.getPaymentToken( - this.tokenizeData() - ).then((response) => { - log(`Gary flow - submitted nonce: ${response.id}` ) - this.submit(response.id); - }); - } catch (e) { - alert('Error tokenizing data.'); - log(`Error tokenizing data. ${e.message}`, 'error'); - } - } - } - - cardComponentData() { - return { - fields: { - cardholderName: { - enabled: this.axoConfig.name_on_card === '1' - } - }, - styles: this.deleteKeysWithEmptyString(this.axoConfig.style_options) - } - } - - tokenizeData() { - return { - cardholderName: { - fullName: this.billingView.fullName() - }, - billingAddress: { - addressLine1: this.billingView.inputValue('street1'), - addressLine2: this.billingView.inputValue('street2'), - adminArea1: this.billingView.inputValue('stateCode'), - 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(``); - } - - this.el.axoNonceInput.get().value = nonce; - - PayPalInsights.trackEndCheckout({ - amount: this.axoConfig?.insights?.amount, - page_type: 'checkout', - payment_method_selected: 'card', - user_data: { - country: 'US', - is_store_member: false, - } - }); - - if (data) { - - // Ryan flow. - const form = document.querySelector('form.woocommerce-checkout'); - const formData = new FormData(form); - - this.showLoading(); - - // Fill in form data. - Object.keys(data).forEach((key) => { - formData.set(key, data[key]); - }); - - // 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 - }) - .then(response => response.json()) - .then(responseData => { - if (responseData.result === 'failure') { - if (responseData.messages) { - const $notices = this.$('.woocommerce-notices-wrapper').eq(0); - $notices.html(responseData.messages); - this.$('html, body').animate({ - scrollTop: $notices.offset().top - }, 500); - } - - log(`Error sending checkout form. ${responseData}`, 'error'); - - this.hideLoading(); - return; - } - if (responseData.redirect) { - window.location.href = responseData.redirect; - } - }) - .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('
'); - disable(submitContainerSelector); - } - - hideLoading() { - const submitContainerSelector = '.woocommerce-checkout-payment'; - jQuery('form.woocommerce-checkout .blockOverlay').remove(); - enable(submitContainerSelector); - } - - useEmailWidget() { - return this.axoConfig?.widgets?.email === 'use_widget'; - } - - deleteKeysWithEmptyString = (obj) => { - for(let key of Object.keys(obj)){ - if (obj[key] === ''){ - delete obj[key]; - } - else if (typeof obj[key] === 'object'){ - obj[key] = this.deleteKeysWithEmptyString(obj[key]); - if (Object.keys(obj[key]).length === 0 ) delete obj[key]; - } - } - - return Array.isArray(obj) ? obj.filter(val => val) : obj; - } - - ensureBillingPhoneNumber(data) { - if (data.billing_phone === '') { - let phone = ''; - const cc = this.data?.shipping?.phoneNumber?.countryCode; - const number = this.data?.shipping?.phoneNumber?.nationalNumber; - - if (cc) { - phone = `+${cc} `; - } - phone += number; - - data.billing_phone = phone; - } - } - - toggleLoaderAndOverlay(element, loaderClass, overlayClass) { - const loader = document.querySelector(`${element.selector} .${loaderClass}`); - const overlay = document.querySelector(`${element.selector} .${overlayClass}`); - if (loader) { - loader.classList.toggle(loaderClass); - } - if (overlay) { - overlay.classList.toggle(overlayClass); - } - } - - spinnerToggleLoaderAndOverlay(element, loaderClass, overlayClass) { - const spinner = document.querySelector(`${element.selector}`); - if (spinner) { - spinner.classList.toggle(loaderClass); - spinner.classList.toggle(overlayClass); - } - } - - toggleWatermarkLoading(container, loadingClass, loaderClass) { - const watermarkLoading = document.querySelector(`${container.selector}.${loadingClass}`); - const watermarkLoader = document.querySelector(`${container.selector}.${loaderClass}`); - if (watermarkLoading) { - watermarkLoading.classList.toggle(loadingClass); - } - if (watermarkLoader) { - watermarkLoader.classList.toggle(loaderClass); - } - } - - validateEmailFormat(value) { - const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailPattern.test(value); - } - - validateEmail(billingEmail) { - const billingEmailSelector = document.querySelector(billingEmail); - const value = document.querySelector(billingEmail + ' input').value; - - if (this.validateEmailFormat(value)) { - billingEmailSelector.classList.remove('woocommerce-invalid'); - billingEmailSelector.classList.add('woocommerce-validated'); - this.setStatus('validEmail', true); - } else { - billingEmailSelector.classList.remove('woocommerce-validated'); - billingEmailSelector.classList.add('woocommerce-invalid'); - this.setStatus('validEmail', false); - } - } - - reEnableEmailInput() { - const reEnableInput = (ev) => { - const submitButton = document.querySelector(this.el.billingEmailSubmitButton.selector); - if (submitButton.hasAttribute('disabled')) { - submitButton.removeAttribute('disabled'); - } - }; - - this.$('#billing_email_field input').on('focus', reEnableInput); - this.$('#billing_email_field input').on('input', reEnableInput); - this.$('#billing_email_field input').on('click', reEnableInput); - } + ` + ); + + 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 + ) { + this.validateEmail( this.el.fieldBillingEmail.selector ); + this.onChangeEmail(); + } + } ); + + log( + `Last, this.emailInput.value attempt - emailInput: ${ this.emailInput.value }` + ); + log( + `this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }` + ); + if ( this.emailInput.value ) { + this.onChangeEmail(); + } + } + } + + async onChangeEmail() { + this.clearData(); + + if ( ! this.status.active ) { + log( 'Email checking skipped, AXO not active.' ); + return; + } + + if ( ! this.emailInput ) { + log( 'Email field not initialized.' ); + return; + } + + log( + `Email changed: ${ + this.emailInput ? this.emailInput.value : '' + }` + ); + + this.$( this.el.paymentContainer.selector + '-detail' ).html( '' ); + this.$( this.el.paymentContainer.selector + '-form' ).html( '' ); + + this.setStatus( 'validEmail', false ); + this.setStatus( 'hasProfile', false ); + + this.hideGatewaySelection = false; + + this.lastEmailCheckedIdentity = this.emailInput.value; + + if ( + ! this.emailInput.value || + ! this.emailInput.checkValidity() || + ! this.validateEmailFormat( this.emailInput.value ) + ) { + log( 'The email address is not valid.' ); + return; + } + + this.data.email = this.emailInput.value; + this.billingView.setData( this.data ); + + if ( ! this.fastlane.identity ) { + log( 'Not initialized.' ); + return; + } + + PayPalInsights.trackSubmitCheckoutEmail( { + page_type: 'checkout', + } ); + + this.disableGatewaySelection(); + this.spinnerToggleLoaderAndOverlay( + this.el.billingEmailSubmitButtonSpinner, + 'loader', + 'ppcp-axo-overlay' + ); + await this.lookupCustomerByEmail(); + this.spinnerToggleLoaderAndOverlay( + this.el.billingEmailSubmitButtonSpinner, + 'loader', + 'ppcp-axo-overlay' + ); + this.enableGatewaySelection(); + } + + async lookupCustomerByEmail() { + 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' + ); + + const authResponse = + await this.fastlane.identity.triggerAuthenticationFlow( + lookupResponse.customerContextId + ); + + log( + `AuthResponse - triggerAuthenticationFlow: ${ JSON.stringify( + authResponse + ) }` + ); + + if ( authResponse.authenticationState === 'succeeded' ) { + const shippingData = authResponse.profileData.shippingAddress; + if ( shippingData ) { + this.setShipping( shippingData ); + } + + if ( authResponse.profileData.card ) { + this.setStatus( 'hasCard', true ); + } else { + this.cardComponent = ( + await this.fastlane.FastlaneCardComponent( + this.cardComponentData() + ) + ).render( this.el.paymentContainer.selector + '-form' ); + } + + 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 ?? ''; + if ( phoneNumber ) { + billingData.phoneNumber = phoneNumber; + } + + this.setBilling( billingData ); + } + + this.setStatus( 'validEmail', true ); + this.setStatus( 'hasProfile', true ); + + this.hideGatewaySelection = true; + this.$( '.wc_payment_methods label' ).hide(); + this.$( '.wc_payment_methods input' ).hide(); + + await this.renderWatermark( false ); + + this.rerender(); + } else { + // authentication failed or canceled by the customer + // set status as guest customer + log( 'Authentication Failed' ); + + this.setStatus( 'validEmail', true ); + this.setStatus( 'hasProfile', false ); + + await this.renderWatermark( true ); + + this.cardComponent = ( + await this.fastlane.FastlaneCardComponent( + this.cardComponentData() + ) + ).render( this.el.paymentContainer.selector + '-form' ); + } + } else { + // No profile found with this email address. + // This is a guest customer. + log( 'No profile found with this email address.' ); + + this.setStatus( 'validEmail', true ); + this.setStatus( 'hasProfile', false ); + + await this.renderWatermark( true ); + + this.cardComponent = ( + await this.fastlane.FastlaneCardComponent( + this.cardComponentData() + ) + ).render( this.el.paymentContainer.selector + '-form' ); + } + } + + disableGatewaySelection() { + this.$( '.wc_payment_methods input' ).prop( 'disabled', true ); + } + + enableGatewaySelection() { + this.$( '.wc_payment_methods input' ).prop( 'disabled', false ); + } + + clearData() { + this.data = { + email: null, + billing: null, + shipping: null, + card: null, + }; + } + + setShipping( shipping ) { + this.data.shipping = shipping; + this.shippingView.setData( this.data ); + } + + setBilling( billing ) { + this.data.billing = billing; + this.billingView.setData( this.data ); + } + + setCard( card ) { + this.data.card = card; + this.cardView.setData( this.data ); + } + + onClickSubmitButton() { + // TODO: validate data. + + if ( this.data.card ) { + // Ryan flow + log( 'Starting Ryan flow.' ); + + this.$( '#ship-to-different-address-checkbox' ).prop( + 'checked', + 'checked' + ); + + 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 }` ); + + this.submit( this.data.card.id, data ); + } else { + // Gary flow + log( 'Starting Gary flow.' ); + + try { + this.cardComponent + .getPaymentToken( this.tokenizeData() ) + .then( ( response ) => { + log( `Gary flow - submitted nonce: ${ response.id }` ); + this.submit( response.id ); + } ); + } catch ( e ) { + alert( 'Error tokenizing data.' ); + log( `Error tokenizing data. ${ e.message }`, 'error' ); + } + } + } + + cardComponentData() { + return { + fields: { + cardholderName: { + enabled: this.axoConfig.name_on_card === '1', + }, + }, + styles: this.deleteKeysWithEmptyString( + this.axoConfig.style_options + ), + }; + } + + tokenizeData() { + return { + cardholderName: { + fullName: this.billingView.fullName(), + }, + billingAddress: { + addressLine1: this.billingView.inputValue( 'street1' ), + addressLine2: this.billingView.inputValue( 'street2' ), + adminArea1: this.billingView.inputValue( 'stateCode' ), + 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( + `` + ); + } + + this.el.axoNonceInput.get().value = nonce; + + PayPalInsights.trackEndCheckout( { + amount: this.axoConfig?.insights?.amount, + page_type: 'checkout', + payment_method_selected: 'card', + user_data: { + country: 'US', + is_store_member: false, + }, + } ); + + if ( data ) { + // Ryan flow. + const form = document.querySelector( 'form.woocommerce-checkout' ); + const formData = new FormData( form ); + + this.showLoading(); + + // Fill in form data. + Object.keys( data ).forEach( ( key ) => { + formData.set( key, data[ key ] ); + } ); + + // 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, + } ) + .then( ( response ) => response.json() ) + .then( ( responseData ) => { + if ( responseData.result === 'failure' ) { + if ( responseData.messages ) { + const $notices = this.$( + '.woocommerce-notices-wrapper' + ).eq( 0 ); + $notices.html( responseData.messages ); + this.$( 'html, body' ).animate( + { + scrollTop: $notices.offset().top, + }, + 500 + ); + } + + log( + `Error sending checkout form. ${ responseData }`, + 'error' + ); + + this.hideLoading(); + return; + } + if ( responseData.redirect ) { + window.location.href = responseData.redirect; + } + } ) + .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( + '
' + ); + disable( submitContainerSelector ); + } + + hideLoading() { + const submitContainerSelector = '.woocommerce-checkout-payment'; + jQuery( 'form.woocommerce-checkout .blockOverlay' ).remove(); + enable( submitContainerSelector ); + } + + useEmailWidget() { + return this.axoConfig?.widgets?.email === 'use_widget'; + } + + deleteKeysWithEmptyString = ( obj ) => { + for ( const key of Object.keys( obj ) ) { + if ( obj[ key ] === '' ) { + delete obj[ key ]; + } else if ( typeof obj[ key ] === 'object' ) { + obj[ key ] = this.deleteKeysWithEmptyString( obj[ key ] ); + if ( Object.keys( obj[ key ] ).length === 0 ) { + delete obj[ key ]; + } + } + } + + return Array.isArray( obj ) ? obj.filter( ( val ) => val ) : obj; + }; + + ensureBillingPhoneNumber( data ) { + if ( data.billing_phone === '' ) { + let phone = ''; + const cc = this.data?.shipping?.phoneNumber?.countryCode; + const number = this.data?.shipping?.phoneNumber?.nationalNumber; + + if ( cc ) { + phone = `+${ cc } `; + } + phone += number; + + data.billing_phone = phone; + } + } + + toggleLoaderAndOverlay( element, loaderClass, overlayClass ) { + const loader = document.querySelector( + `${ element.selector } .${ loaderClass }` + ); + const overlay = document.querySelector( + `${ element.selector } .${ overlayClass }` + ); + if ( loader ) { + loader.classList.toggle( loaderClass ); + } + if ( overlay ) { + overlay.classList.toggle( overlayClass ); + } + } + + spinnerToggleLoaderAndOverlay( element, loaderClass, overlayClass ) { + const spinner = document.querySelector( `${ element.selector }` ); + if ( spinner ) { + spinner.classList.toggle( loaderClass ); + spinner.classList.toggle( overlayClass ); + } + } + + toggleWatermarkLoading( container, loadingClass, loaderClass ) { + const watermarkLoading = document.querySelector( + `${ container.selector }.${ loadingClass }` + ); + const watermarkLoader = document.querySelector( + `${ container.selector }.${ loaderClass }` + ); + if ( watermarkLoading ) { + watermarkLoading.classList.toggle( loadingClass ); + } + if ( watermarkLoader ) { + watermarkLoader.classList.toggle( loaderClass ); + } + } + + validateEmailFormat( value ) { + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailPattern.test( value ); + } + + validateEmail( billingEmail ) { + const billingEmailSelector = document.querySelector( billingEmail ); + const value = document.querySelector( billingEmail + ' input' ).value; + + if ( this.validateEmailFormat( value ) ) { + billingEmailSelector.classList.remove( 'woocommerce-invalid' ); + billingEmailSelector.classList.add( 'woocommerce-validated' ); + this.setStatus( 'validEmail', true ); + } else { + billingEmailSelector.classList.remove( 'woocommerce-validated' ); + billingEmailSelector.classList.add( 'woocommerce-invalid' ); + this.setStatus( 'validEmail', false ); + } + } + + reEnableEmailInput() { + const reEnableInput = ( ev ) => { + const submitButton = document.querySelector( + this.el.billingEmailSubmitButton.selector + ); + if ( submitButton.hasAttribute( 'disabled' ) ) { + submitButton.removeAttribute( 'disabled' ); + } + }; + + this.$( '#billing_email_field input' ).on( 'focus', reEnableInput ); + this.$( '#billing_email_field input' ).on( 'input', reEnableInput ); + this.$( '#billing_email_field input' ).on( 'click', reEnableInput ); + } } export default AxoManager; diff --git a/modules/ppcp-axo/resources/js/Components/DomElement.js b/modules/ppcp-axo/resources/js/Components/DomElement.js index 30cadcb2a..31f908712 100644 --- a/modules/ppcp-axo/resources/js/Components/DomElement.js +++ b/modules/ppcp-axo/resources/js/Components/DomElement.js @@ -1,39 +1,37 @@ class DomElement { + constructor( config ) { + this.$ = jQuery; + this.config = config; + this.selector = this.config.selector; + this.id = this.config.id || null; + this.className = this.config.className || null; + this.attributes = this.config.attributes || null; + this.anchorSelector = this.config.anchorSelector || null; + } - constructor(config) { - this.$ = jQuery; - this.config = config; - this.selector = this.config.selector; - this.id = this.config.id || null; - this.className = this.config.className || null; - this.attributes = this.config.attributes || null; - this.anchorSelector = this.config.anchorSelector || null; - } + trigger( action ) { + this.$( this.selector ).trigger( action ); + } - trigger(action) { - this.$(this.selector).trigger(action); - } + on( action, callable ) { + this.$( document ).on( action, this.selector, callable ); + } - on(action, callable) { - this.$(document).on(action, this.selector, callable); - } + hide() { + this.$( this.selector ).hide(); + } - hide() { - this.$(this.selector).hide(); - } + show() { + this.$( this.selector ).show(); + } - show() { - this.$(this.selector).show(); - } - - click() { - this.get().click(); - } - - get() { - return document.querySelector(this.selector); - } + click() { + this.get().click(); + } + get() { + return document.querySelector( this.selector ); + } } export default DomElement; diff --git a/modules/ppcp-axo/resources/js/Components/DomElementCollection.js b/modules/ppcp-axo/resources/js/Components/DomElementCollection.js index b6ab77177..0fef70882 100644 --- a/modules/ppcp-axo/resources/js/Components/DomElementCollection.js +++ b/modules/ppcp-axo/resources/js/Components/DomElementCollection.js @@ -1,116 +1,116 @@ -import DomElement from "./DomElement"; +import DomElement from './DomElement'; class DomElementCollection { + constructor() { + this.gatewayRadioButton = new DomElement( { + selector: '#payment_method_ppcp-axo-gateway', + } ); - constructor() { - this.gatewayRadioButton = new DomElement({ - selector: '#payment_method_ppcp-axo-gateway', - }); + this.gatewayDescription = new DomElement( { + selector: '.payment_box.payment_method_ppcp-axo-gateway', + } ); - this.gatewayDescription = new DomElement({ - selector: '.payment_box.payment_method_ppcp-axo-gateway', - }); + this.defaultSubmitButton = new DomElement( { + selector: '#place_order', + } ); - this.defaultSubmitButton = new DomElement({ - selector: '#place_order', - }); + this.paymentContainer = new DomElement( { + id: 'ppcp-axo-payment-container', + selector: '#ppcp-axo-payment-container', + className: 'ppcp-axo-payment-container', + } ); - this.paymentContainer = new DomElement({ - id: 'ppcp-axo-payment-container', - selector: '#ppcp-axo-payment-container', - className: 'ppcp-axo-payment-container' - }); + this.watermarkContainer = new DomElement( { + id: 'ppcp-axo-watermark-container', + selector: '#ppcp-axo-watermark-container', + className: + 'ppcp-axo-watermark-container ppcp-axo-watermark-loading loader', + } ); - this.watermarkContainer = new DomElement({ - id: 'ppcp-axo-watermark-container', - selector: '#ppcp-axo-watermark-container', - className: 'ppcp-axo-watermark-container ppcp-axo-watermark-loading loader' - }); + this.customerDetails = new DomElement( { + selector: '#customer_details > *:not(#ppcp-axo-customer-details)', + } ); - this.customerDetails = new DomElement({ - selector: '#customer_details > *:not(#ppcp-axo-customer-details)' - }); + this.axoCustomerDetails = new DomElement( { + id: 'ppcp-axo-customer-details', + selector: '#ppcp-axo-customer-details', + className: 'ppcp-axo-customer-details', + anchorSelector: '#customer_details', + } ); - this.axoCustomerDetails = new DomElement({ - id: 'ppcp-axo-customer-details', - selector: '#ppcp-axo-customer-details', - className: 'ppcp-axo-customer-details', - anchorSelector: '#customer_details' - }); + this.emailWidgetContainer = new DomElement( { + id: 'ppcp-axo-email-widget', + selector: '#ppcp-axo-email-widget', + className: 'ppcp-axo-email-widget', + } ); - this.emailWidgetContainer = new DomElement({ - id: 'ppcp-axo-email-widget', - selector: '#ppcp-axo-email-widget', - className: 'ppcp-axo-email-widget' - }); + this.shippingAddressContainer = new DomElement( { + id: 'ppcp-axo-shipping-address-container', + selector: '#ppcp-axo-shipping-address-container', + className: 'ppcp-axo-shipping-address-container', + } ); - this.shippingAddressContainer = new DomElement({ - id: 'ppcp-axo-shipping-address-container', - selector: '#ppcp-axo-shipping-address-container', - className: 'ppcp-axo-shipping-address-container' - }); + this.billingAddressContainer = new DomElement( { + id: 'ppcp-axo-billing-address-container', + selector: '#ppcp-axo-billing-address-container', + className: 'ppcp-axo-billing-address-container', + } ); - this.billingAddressContainer = new DomElement({ - id: 'ppcp-axo-billing-address-container', - selector: '#ppcp-axo-billing-address-container', - className: 'ppcp-axo-billing-address-container' - }); + this.fieldBillingEmail = new DomElement( { + selector: '#billing_email_field', + } ); - this.fieldBillingEmail = new DomElement({ - selector: '#billing_email_field' - }); + this.billingEmailFieldWrapper = new DomElement( { + id: 'ppcp-axo-billing-email-field-wrapper', + selector: '#ppcp-axo-billing-email-field-wrapper', + } ); - this.billingEmailFieldWrapper = new DomElement({ - id: 'ppcp-axo-billing-email-field-wrapper', - selector: '#ppcp-axo-billing-email-field-wrapper', - }); + this.billingEmailSubmitButton = new DomElement( { + id: 'ppcp-axo-billing-email-submit-button', + selector: '#ppcp-axo-billing-email-submit-button', + className: + 'ppcp-axo-billing-email-submit-button-hidden button alt wp-element-button wc-block-components-button', + } ); - this.billingEmailSubmitButton = new DomElement({ - id: 'ppcp-axo-billing-email-submit-button', - selector: '#ppcp-axo-billing-email-submit-button', - className: 'ppcp-axo-billing-email-submit-button-hidden button alt wp-element-button wc-block-components-button' - }); + this.billingEmailSubmitButtonSpinner = new DomElement( { + id: 'ppcp-axo-billing-email-submit-button-spinner', + selector: '#ppcp-axo-billing-email-submit-button-spinner', + className: 'loader ppcp-axo-overlay', + } ); - this.billingEmailSubmitButtonSpinner = new DomElement({ - id: 'ppcp-axo-billing-email-submit-button-spinner', - selector: '#ppcp-axo-billing-email-submit-button-spinner', - className: 'loader ppcp-axo-overlay' - }); + this.submitButtonContainer = new DomElement( { + selector: '#ppcp-axo-submit-button-container', + } ); - this.submitButtonContainer = new DomElement({ - selector: '#ppcp-axo-submit-button-container', - }); + this.submitButton = new DomElement( { + selector: '#ppcp-axo-submit-button-container button', + } ); - this.submitButton = new DomElement({ - selector: '#ppcp-axo-submit-button-container button' - }); + this.changeShippingAddressLink = new DomElement( { + selector: '*[data-ppcp-axo-change-shipping-address]', + attributes: 'data-ppcp-axo-change-shipping-address', + } ); - this.changeShippingAddressLink = new DomElement({ - selector: '*[data-ppcp-axo-change-shipping-address]', - attributes: 'data-ppcp-axo-change-shipping-address', - }); + this.changeBillingAddressLink = new DomElement( { + selector: '*[data-ppcp-axo-change-billing-address]', + attributes: 'data-ppcp-axo-change-billing-address', + } ); - this.changeBillingAddressLink = new DomElement({ - selector: '*[data-ppcp-axo-change-billing-address]', - attributes: 'data-ppcp-axo-change-billing-address', - }); + this.changeCardLink = new DomElement( { + selector: '*[data-ppcp-axo-change-card]', + attributes: 'data-ppcp-axo-change-card', + } ); - this.changeCardLink = new DomElement({ - selector: '*[data-ppcp-axo-change-card]', - attributes: 'data-ppcp-axo-change-card', - }); + this.showGatewaySelectionLink = new DomElement( { + selector: '*[data-ppcp-axo-show-gateway-selection]', + attributes: 'data-ppcp-axo-show-gateway-selection', + } ); - this.showGatewaySelectionLink = new DomElement({ - selector: '*[data-ppcp-axo-show-gateway-selection]', - attributes: 'data-ppcp-axo-show-gateway-selection', - }); - - this.axoNonceInput = new DomElement({ - id: 'ppcp-axo-nonce', - selector: '#ppcp-axo-nonce', - }); - - } + this.axoNonceInput = new DomElement( { + id: 'ppcp-axo-nonce', + selector: '#ppcp-axo-nonce', + } ); + } } export default DomElementCollection; diff --git a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js index 272df1288..b4318a43d 100644 --- a/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js +++ b/modules/ppcp-axo/resources/js/Components/FormFieldGroup.js @@ -1,153 +1,159 @@ - class FormFieldGroup { + constructor( config ) { + this.data = {}; - constructor(config) { - this.data = {}; + this.baseSelector = config.baseSelector; + this.contentSelector = config.contentSelector; + this.fields = config.fields || {}; + this.template = config.template; - this.baseSelector = config.baseSelector; - this.contentSelector = config.contentSelector; - this.fields = config.fields || {}; - this.template = config.template; + this.active = false; + } - this.active = false; - } + setData( data ) { + this.data = data; + this.refresh(); + } - setData(data) { - this.data = data; - this.refresh(); - } + dataValue( fieldKey ) { + if ( ! fieldKey || ! this.fields[ fieldKey ] ) { + return ''; + } - dataValue(fieldKey) { - if (!fieldKey || !this.fields[fieldKey]) { - return ''; - } + if ( typeof this.fields[ fieldKey ].valueCallback === 'function' ) { + return this.fields[ fieldKey ].valueCallback( this.data ); + } - if (typeof this.fields[fieldKey].valueCallback === 'function') { - return this.fields[fieldKey].valueCallback(this.data); - } + const path = this.fields[ fieldKey ].valuePath; - const path = this.fields[fieldKey].valuePath; + if ( ! path ) { + return ''; + } - if (!path) { - return ''; - } + const value = path + .split( '.' ) + .reduce( + ( acc, key ) => + acc && acc[ key ] !== undefined ? acc[ key ] : undefined, + this.data + ); + return value ? value : ''; + } - const value = path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, this.data); - return value ? value : ''; - } + activate() { + this.active = true; + this.refresh(); + } - activate() { - this.active = true; - this.refresh(); - } + deactivate() { + this.active = false; + this.refresh(); + } - deactivate() { - this.active = false; - this.refresh(); - } + toggle() { + this.active ? this.deactivate() : this.activate(); + } - toggle() { - this.active ? this.deactivate() : this.activate(); - } + refresh() { + const content = document.querySelector( this.contentSelector ); - refresh() { - let content = document.querySelector(this.contentSelector); + if ( ! content ) { + return; + } - if (!content) { - return; - } + content.innerHTML = ''; - content.innerHTML = ''; + if ( ! this.active ) { + this.hideField( this.contentSelector ); + } else { + this.showField( this.contentSelector ); + } - if (!this.active) { - this.hideField(this.contentSelector); - } else { - this.showField(this.contentSelector); - } + Object.keys( this.fields ).forEach( ( key ) => { + const field = this.fields[ key ]; - Object.keys(this.fields).forEach((key) => { - const field = this.fields[key]; + if ( this.active && ! field.showInput ) { + this.hideField( field.selector ); + } else { + this.showField( field.selector ); + } + } ); - if (this.active && !field.showInput) { - this.hideField(field.selector); - } else { - this.showField(field.selector); - } - }); + if ( typeof this.template === 'function' ) { + content.innerHTML = this.template( { + value: ( fieldKey ) => { + return this.dataValue( fieldKey ); + }, + isEmpty: () => { + let isEmpty = true; + Object.keys( this.fields ).forEach( ( fieldKey ) => { + if ( this.dataValue( fieldKey ) ) { + isEmpty = false; + return false; + } + } ); + return isEmpty; + }, + } ); + } + } - if (typeof this.template === 'function') { - content.innerHTML = this.template({ - value: (fieldKey) => { - return this.dataValue(fieldKey); - }, - isEmpty: () => { - let isEmpty = true; - Object.keys(this.fields).forEach((fieldKey) => { - if (this.dataValue(fieldKey)) { - isEmpty = false; - return false; - } - }); - return isEmpty; - } - }); - } + showField( selector ) { + const field = document.querySelector( + this.baseSelector + ' ' + selector + ); + if ( field ) { + field.classList.remove( 'ppcp-axo-field-hidden' ); + } + } - } + hideField( selector ) { + const field = document.querySelector( + this.baseSelector + ' ' + selector + ); + if ( field ) { + field.classList.add( 'ppcp-axo-field-hidden' ); + } + } - showField(selector) { - const field = document.querySelector(this.baseSelector + ' ' + selector); - if (field) { - field.classList.remove('ppcp-axo-field-hidden'); - } - } + inputElement( name ) { + const baseSelector = this.fields[ name ].selector; - hideField(selector) { - const field = document.querySelector(this.baseSelector + ' ' + selector); - if (field) { - field.classList.add('ppcp-axo-field-hidden'); - } - } + const select = document.querySelector( baseSelector + ' select' ); + if ( select ) { + return select; + } - inputElement(name) { - const baseSelector = this.fields[name].selector; + const input = document.querySelector( baseSelector + ' input' ); + if ( input ) { + return input; + } - const select = document.querySelector(baseSelector + ' select'); - if (select) { - return select; - } + return null; + } - const input = document.querySelector(baseSelector + ' input'); - if (input) { - return input; - } + inputValue( name ) { + const el = this.inputElement( name ); + return el ? el.value : ''; + } - return null; - } + toSubmitData( data ) { + Object.keys( this.fields ).forEach( ( fieldKey ) => { + const field = this.fields[ fieldKey ]; - inputValue(name) { - const el = this.inputElement(name); - return el ? el.value : ''; - } + if ( ! field.valuePath || ! field.selector ) { + return true; + } - toSubmitData(data) { - Object.keys(this.fields).forEach((fieldKey) => { - const field = this.fields[fieldKey]; + const inputElement = this.inputElement( fieldKey ); - if (!field.valuePath || !field.selector) { - return true; - } - - const inputElement = this.inputElement(fieldKey); - - if (!inputElement) { - return true; - } - - data[inputElement.name] = this.dataValue(fieldKey); - }); - } + if ( ! inputElement ) { + return true; + } + data[ inputElement.name ] = this.dataValue( fieldKey ); + } ); + } } export default FormFieldGroup; diff --git a/modules/ppcp-axo/resources/js/Connection/Fastlane.js b/modules/ppcp-axo/resources/js/Connection/Fastlane.js index 8e1c76949..d01ae8524 100644 --- a/modules/ppcp-axo/resources/js/Connection/Fastlane.js +++ b/modules/ppcp-axo/resources/js/Connection/Fastlane.js @@ -1,42 +1,42 @@ - class Fastlane { + construct() { + this.connection = null; + this.identity = null; + this.profile = null; + this.FastlaneCardComponent = null; + this.FastlanePaymentComponent = null; + this.FastlaneWatermarkComponent = null; + } - construct() { - this.connection = null; - this.identity = null; - this.profile = null; - this.FastlaneCardComponent = null; - this.FastlanePaymentComponent = null; - this.FastlaneWatermarkComponent = null; - } + connect( config ) { + return new Promise( ( resolve, reject ) => { + window.paypal + .Fastlane( config ) + .then( ( result ) => { + this.init( result ); + resolve(); + } ) + .catch( ( error ) => { + console.error( error ); + reject(); + } ); + } ); + } - connect(config) { - return new Promise((resolve, reject) => { - window.paypal.Fastlane(config) - .then((result) => { - this.init(result); - resolve(); - }) - .catch((error) => { - console.error(error) - reject(); - }); - }); - } - - init(connection) { - this.connection = connection; - this.identity = this.connection.identity; - this.profile = this.connection.profile; - this.FastlaneCardComponent = this.connection.FastlaneCardComponent; - this.FastlanePaymentComponent = this.connection.FastlanePaymentComponent; - this.FastlaneWatermarkComponent = this.connection.FastlaneWatermarkComponent - } - - setLocale(locale) { - this.connection.setLocale(locale); - } + init( connection ) { + this.connection = connection; + this.identity = this.connection.identity; + this.profile = this.connection.profile; + this.FastlaneCardComponent = this.connection.FastlaneCardComponent; + this.FastlanePaymentComponent = + this.connection.FastlanePaymentComponent; + this.FastlaneWatermarkComponent = + this.connection.FastlaneWatermarkComponent; + } + setLocale( locale ) { + this.connection.setLocale( locale ); + } } export default Fastlane; diff --git a/modules/ppcp-axo/resources/js/Helper/Debug.js b/modules/ppcp-axo/resources/js/Helper/Debug.js index 7ace5db33..84cda012c 100644 --- a/modules/ppcp-axo/resources/js/Helper/Debug.js +++ b/modules/ppcp-axo/resources/js/Helper/Debug.js @@ -1,29 +1,29 @@ -export function log(message, level = 'info') { - const wpDebug = window.wc_ppcp_axo?.wp_debug; - const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint; - if (!endpoint) { - return; - } +export function log( message, level = 'info' ) { + const wpDebug = window.wc_ppcp_axo?.wp_debug; + const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint; + if ( ! endpoint ) { + return; + } - fetch(endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: window.wc_ppcp_axo.ajax.frontend_logger.nonce, - log: { - message, - level, - } - }) - }).then(() => { - if (wpDebug) { - switch (level) { - case 'error': - console.error(`[AXO] ${message}`); - break; - default: - console.log(`[AXO] ${message}`); - } - } - }); + fetch( endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: window.wc_ppcp_axo.ajax.frontend_logger.nonce, + log: { + message, + level, + }, + } ), + } ).then( () => { + if ( wpDebug ) { + switch ( level ) { + case 'error': + console.error( `[AXO] ${ message }` ); + break; + default: + console.log( `[AXO] ${ message }` ); + } + } + } ); } diff --git a/modules/ppcp-axo/resources/js/Insights/PayPalInsights.js b/modules/ppcp-axo/resources/js/Insights/PayPalInsights.js index 926ca9355..7856f1f6c 100644 --- a/modules/ppcp-axo/resources/js/Insights/PayPalInsights.js +++ b/modules/ppcp-axo/resources/js/Insights/PayPalInsights.js @@ -1,58 +1,55 @@ - class PayPalInsights { + constructor() { + window.paypalInsightDataLayer = window.paypalInsightDataLayer || []; + document.paypalInsight = () => { + paypalInsightDataLayer.push( arguments ); + }; + } - constructor() { - window.paypalInsightDataLayer = window.paypalInsightDataLayer || []; - document.paypalInsight = () => { - paypalInsightDataLayer.push(arguments); - } - } + /** + * @return {PayPalInsights} + */ + static init() { + if ( ! PayPalInsights.instance ) { + PayPalInsights.instance = new PayPalInsights(); + } + return PayPalInsights.instance; + } - /** - * @returns {PayPalInsights} - */ - static init() { - if (!PayPalInsights.instance) { - PayPalInsights.instance = new PayPalInsights(); - } - return PayPalInsights.instance; - } + static track( eventName, data ) { + PayPalInsights.init(); + paypalInsight( 'event', eventName, data ); + } - static track(eventName, data) { - PayPalInsights.init(); - paypalInsight('event', eventName, data); - } + static config( clientId, data ) { + PayPalInsights.init(); + paypalInsight( 'config', clientId, data ); + } - static config (clientId, data) { - PayPalInsights.init(); - paypalInsight('config', clientId, data); - } + static setSessionId( sessionId ) { + PayPalInsights.init(); + paypalInsight( 'set', { session_id: sessionId } ); + } - static setSessionId (sessionId) { - PayPalInsights.init(); - paypalInsight('set', { session_id: sessionId }); - } + static trackJsLoad() { + PayPalInsights.track( 'js_load', { timestamp: Date.now() } ); + } - static trackJsLoad () { - PayPalInsights.track('js_load', { timestamp: Date.now() }); - } + static trackBeginCheckout( data ) { + PayPalInsights.track( 'begin_checkout', data ); + } - static trackBeginCheckout (data) { - PayPalInsights.track('begin_checkout', data); - } + static trackSubmitCheckoutEmail( data ) { + PayPalInsights.track( 'submit_checkout_email', data ); + } - static trackSubmitCheckoutEmail (data) { - PayPalInsights.track('submit_checkout_email', data); - } - - static trackSelectPaymentMethod (data) { - PayPalInsights.track('select_payment_method', data); - } - - static trackEndCheckout (data) { - PayPalInsights.track('end_checkout', data); - } + static trackSelectPaymentMethod( data ) { + PayPalInsights.track( 'select_payment_method', data ); + } + static trackEndCheckout( data ) { + PayPalInsights.track( 'end_checkout', data ); + } } export default PayPalInsights; diff --git a/modules/ppcp-axo/resources/js/Views/BillingView.js b/modules/ppcp-axo/resources/js/Views/BillingView.js index f2903d4ef..c9047f417 100644 --- a/modules/ppcp-axo/resources/js/Views/BillingView.js +++ b/modules/ppcp-axo/resources/js/Views/BillingView.js @@ -1,121 +1,124 @@ -import FormFieldGroup from "../Components/FormFieldGroup"; +import FormFieldGroup from '../Components/FormFieldGroup'; class BillingView { + constructor( selector, elements ) { + this.el = elements; - constructor(selector, elements) { - this.el = elements; + this.group = new FormFieldGroup( { + baseSelector: '.woocommerce-checkout', + contentSelector: selector, + template: ( data ) => { + const valueOfSelect = ( selectSelector, key ) => { + if ( ! key ) { + return ''; + } + const selectElement = + document.querySelector( selectSelector ); - this.group = new FormFieldGroup({ - baseSelector: '.woocommerce-checkout', - contentSelector: selector, - template: (data) => { - const valueOfSelect = (selectSelector, key) => { - if (!key) { - return ''; - } - const selectElement = document.querySelector(selectSelector); + if ( ! selectElement ) { + return key; + } - if (!selectElement) { - return key; - } + const option = selectElement.querySelector( + `option[value="${ key }"]` + ); + return option ? option.textContent : key; + }; - const option = selectElement.querySelector(`option[value="${key}"]`); - return option ? option.textContent : key; - } - - if (data.isEmpty()) { - return ` + if ( data.isEmpty() ) { + return `

Billing

- Edit + Edit
Please fill in your billing details.
`; - } - return ''; - }, - fields: { - email: { - 'valuePath': 'email', - }, - firstName: { - 'selector': '#billing_first_name_field', - 'valuePath': null - }, - lastName: { - 'selector': '#billing_last_name_field', - 'valuePath': null - }, - street1: { - 'selector': '#billing_address_1_field', - 'valuePath': 'billing.address.addressLine1', - }, - street2: { - 'selector': '#billing_address_2_field', - 'valuePath': null - }, - postCode: { - 'selector': '#billing_postcode_field', - 'valuePath': 'billing.address.postalCode', - }, - city: { - 'selector': '#billing_city_field', - 'valuePath': 'billing.address.adminArea2', - }, - stateCode: { - 'selector': '#billing_state_field', - 'valuePath': 'billing.address.adminArea1', - }, - countryCode: { - 'selector': '#billing_country_field', - 'valuePath': 'billing.address.countryCode', - }, - company: { - 'selector': '#billing_company_field', - 'valuePath': null, - }, - phone: { - 'selector': '#billing_phone_field', - 'valuePath': 'billing.phoneNumber' - } - } - }); - } + } + return ''; + }, + fields: { + email: { + valuePath: 'email', + }, + firstName: { + selector: '#billing_first_name_field', + valuePath: null, + }, + lastName: { + selector: '#billing_last_name_field', + valuePath: null, + }, + street1: { + selector: '#billing_address_1_field', + valuePath: 'billing.address.addressLine1', + }, + street2: { + selector: '#billing_address_2_field', + valuePath: null, + }, + postCode: { + selector: '#billing_postcode_field', + valuePath: 'billing.address.postalCode', + }, + city: { + selector: '#billing_city_field', + valuePath: 'billing.address.adminArea2', + }, + stateCode: { + selector: '#billing_state_field', + valuePath: 'billing.address.adminArea1', + }, + countryCode: { + selector: '#billing_country_field', + valuePath: 'billing.address.countryCode', + }, + company: { + selector: '#billing_company_field', + valuePath: null, + }, + phone: { + selector: '#billing_phone_field', + valuePath: 'billing.phoneNumber', + }, + }, + } ); + } - isActive() { - return this.group.active; - } + isActive() { + return this.group.active; + } - activate() { - this.group.activate(); - } + activate() { + this.group.activate(); + } - deactivate() { - this.group.deactivate(); - } + deactivate() { + this.group.deactivate(); + } - refresh() { - this.group.refresh(); - } + refresh() { + this.group.refresh(); + } - setData(data) { - this.group.setData(data); - } + setData( data ) { + this.group.setData( data ); + } - inputValue(name) { - return this.group.inputValue(name); - } + inputValue( name ) { + return this.group.inputValue( name ); + } - fullName() { - return `${this.inputValue('firstName')} ${this.inputValue('lastName')}`.trim(); - } - - toSubmitData(data) { - return this.group.toSubmitData(data); - } + fullName() { + return `${ this.inputValue( 'firstName' ) } ${ this.inputValue( + 'lastName' + ) }`.trim(); + } + toSubmitData( data ) { + return this.group.toSubmitData( data ); + } } export default BillingView; diff --git a/modules/ppcp-axo/resources/js/Views/CardView.js b/modules/ppcp-axo/resources/js/Views/CardView.js index 82c15079c..856e70e4c 100644 --- a/modules/ppcp-axo/resources/js/Views/CardView.js +++ b/modules/ppcp-axo/resources/js/Views/CardView.js @@ -1,116 +1,126 @@ -import FormFieldGroup from "../Components/FormFieldGroup"; +import FormFieldGroup from '../Components/FormFieldGroup'; class CardView { + constructor( selector, elements, manager ) { + this.el = elements; + this.manager = manager; - constructor(selector, elements, manager) { - this.el = elements; - this.manager = manager; + this.group = new FormFieldGroup( { + baseSelector: '.ppcp-axo-payment-container', + contentSelector: selector, + template: ( data ) => { + const selectOtherPaymentMethod = () => { + if ( ! this.manager.hideGatewaySelection ) { + return ''; + } + return `

Select other payment method

`; + }; - this.group = new FormFieldGroup({ - baseSelector: '.ppcp-axo-payment-container', - contentSelector: selector, - template: (data) => { - const selectOtherPaymentMethod = () => { - if (!this.manager.hideGatewaySelection) { - return ''; - } - return `

Select other payment method

`; - }; - - if (data.isEmpty()) { - return ` + if ( data.isEmpty() ) { + return `
- ${selectOtherPaymentMethod()} + ${ selectOtherPaymentMethod() }
`; - } + } - const expiry = data.value('expiry').split('-'); + const expiry = data.value( 'expiry' ).split( '-' ); - const cardIcons = { - 'VISA': 'visa-light.svg', - 'MASTER_CARD': 'mastercard-light.svg', - 'AMEX': 'amex-light.svg', - 'DISCOVER': 'discover-light.svg', - 'DINERS': 'dinersclub-light.svg', - 'JCB': 'jcb-light.svg', - 'UNIONPAY': 'unionpay-light.svg', - }; + const cardIcons = { + VISA: 'visa-light.svg', + MASTER_CARD: 'mastercard-light.svg', + AMEX: 'amex-light.svg', + DISCOVER: 'discover-light.svg', + DINERS: 'dinersclub-light.svg', + JCB: 'jcb-light.svg', + UNIONPAY: 'unionpay-light.svg', + }; - return ` + return `

Card Details

- Edit + Edit
${data.value('brand')}
-
${data.value('lastDigits') ? '**** **** **** ' + data.value('lastDigits'): ''}
-
${expiry[1]}/${expiry[0]}
-
${data.value('name')}
+
${ + data.value( 'lastDigits' ) + ? '**** **** **** ' + + data.value( 'lastDigits' ) + : '' + }
+
${ expiry[ 1 ] }/${ expiry[ 0 ] }
+
${ data.value( + 'name' + ) }
- ${selectOtherPaymentMethod()} + ${ selectOtherPaymentMethod() }
`; - }, - fields: { - brand: { - 'valuePath': 'card.paymentSource.card.brand', - }, - expiry: { - 'valuePath': 'card.paymentSource.card.expiry', - }, - lastDigits: { - 'valuePath': 'card.paymentSource.card.lastDigits', - }, - name: { - 'valuePath': 'card.paymentSource.card.name', - }, - } - }); - } + }, + fields: { + brand: { + valuePath: 'card.paymentSource.card.brand', + }, + expiry: { + valuePath: 'card.paymentSource.card.expiry', + }, + lastDigits: { + valuePath: 'card.paymentSource.card.lastDigits', + }, + name: { + valuePath: 'card.paymentSource.card.name', + }, + }, + } ); + } - activate() { - this.group.activate(); - } + activate() { + this.group.activate(); + } - deactivate() { - this.group.deactivate(); - } + deactivate() { + this.group.deactivate(); + } - refresh() { - this.group.refresh(); - } + refresh() { + this.group.refresh(); + } - setData(data) { - this.group.setData(data); - } + setData( data ) { + this.group.setData( data ); + } - toSubmitData(data) { - const name = this.group.dataValue('name'); - const { firstName, lastName } = this.splitName(name); + toSubmitData( data ) { + const name = this.group.dataValue( 'name' ); + const { firstName, lastName } = this.splitName( name ); - data['billing_first_name'] = firstName; - data['billing_last_name'] = lastName ? lastName : firstName; + data.billing_first_name = firstName; + data.billing_last_name = lastName ? lastName : firstName; - return this.group.toSubmitData(data); - } + return this.group.toSubmitData( data ); + } - splitName(fullName) { - let nameParts = fullName.trim().split(' '); - let firstName = nameParts[0]; - let lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; - - return { firstName, lastName }; - } + splitName( fullName ) { + const nameParts = fullName.trim().split( ' ' ); + const firstName = nameParts[ 0 ]; + const lastName = + nameParts.length > 1 ? nameParts[ nameParts.length - 1 ] : ''; + return { firstName, lastName }; + } } export default CardView; diff --git a/modules/ppcp-axo/resources/js/Views/ShippingView.js b/modules/ppcp-axo/resources/js/Views/ShippingView.js index ec8cfb149..ba7ffb408 100644 --- a/modules/ppcp-axo/resources/js/Views/ShippingView.js +++ b/modules/ppcp-axo/resources/js/Views/ShippingView.js @@ -1,170 +1,185 @@ -import FormFieldGroup from "../Components/FormFieldGroup"; +import FormFieldGroup from '../Components/FormFieldGroup'; class ShippingView { + constructor( selector, elements, states ) { + this.el = elements; + this.states = states; + this.group = new FormFieldGroup( { + baseSelector: '.woocommerce-checkout', + contentSelector: selector, + template: ( data ) => { + const valueOfSelect = ( selectSelector, key ) => { + if ( ! key ) { + return ''; + } + const selectElement = + document.querySelector( selectSelector ); - constructor(selector, elements, states) { - this.el = elements; - this.states = states; - this.group = new FormFieldGroup({ - baseSelector: '.woocommerce-checkout', - contentSelector: selector, - template: (data) => { - const valueOfSelect = (selectSelector, key) => { - if (!key) { - return ''; - } - const selectElement = document.querySelector(selectSelector); + if ( ! selectElement ) { + return key; + } - if (!selectElement) { - return key; - } + const option = selectElement.querySelector( + `option[value="${ key }"]` + ); + return option ? option.textContent : key; + }; - const option = selectElement.querySelector(`option[value="${key}"]`); - return option ? option.textContent : key; - } - - if (data.isEmpty()) { - return ` + if ( data.isEmpty() ) { + return `

Shipping

- Edit + Edit
Please fill in your shipping details.
`; - } - const countryCode = data.value('countryCode'); - const stateCode = data.value('stateCode'); - const stateName = (this.states[countryCode] && this.states[countryCode][stateCode]) ? this.states[countryCode][stateCode] : stateCode; + } + const countryCode = data.value( 'countryCode' ); + const stateCode = data.value( 'stateCode' ); + const stateName = + this.states[ countryCode ] && + this.states[ countryCode ][ stateCode ] + ? this.states[ countryCode ][ stateCode ] + : stateCode; - if( - this.hasEmptyValues(data, stateName) - ) { - return ` + if ( this.hasEmptyValues( data, stateName ) ) { + return `

Shipping

- Edit + Edit
Please fill in your shipping details.
`; - } + } - return ` + return `

Shipping

- Edit + Edit
-
${data.value('email')}
-
${data.value('company')}
-
${data.value('firstName')} ${data.value('lastName')}
-
${data.value('street1')}
-
${data.value('street2')}
-
${data.value('city')}, ${stateName} ${data.value('postCode')}
-
${valueOfSelect('#billing_country', countryCode)}
-
${data.value('phone')}
+
${ data.value( 'email' ) }
+
${ data.value( 'company' ) }
+
${ data.value( 'firstName' ) } ${ data.value( + 'lastName' + ) }
+
${ data.value( 'street1' ) }
+
${ data.value( 'street2' ) }
+
${ data.value( + 'city' + ) }, ${ stateName } ${ data.value( 'postCode' ) }
+
${ valueOfSelect( + '#billing_country', + countryCode + ) }
+
${ data.value( 'phone' ) }
`; - }, - fields: { - email: { - 'valuePath': 'email', - }, - firstName: { - 'key': 'firstName', - 'selector': '#shipping_first_name_field', - 'valuePath': 'shipping.name.firstName', - }, - lastName: { - 'selector': '#shipping_last_name_field', - 'valuePath': 'shipping.name.lastName', - }, - street1: { - 'selector': '#shipping_address_1_field', - 'valuePath': 'shipping.address.addressLine1', - }, - street2: { - 'selector': '#shipping_address_2_field', - 'valuePath': null - }, - postCode: { - 'selector': '#shipping_postcode_field', - 'valuePath': 'shipping.address.postalCode', - }, - city: { - 'selector': '#shipping_city_field', - 'valuePath': 'shipping.address.adminArea2', - }, - stateCode: { - 'selector': '#shipping_state_field', - 'valuePath': 'shipping.address.adminArea1', - }, - countryCode: { - 'selector': '#shipping_country_field', - 'valuePath': 'shipping.address.countryCode', - }, - company: { - 'selector': '#shipping_company_field', - 'valuePath': null, - }, - shipDifferentAddress: { - 'selector': '#ship-to-different-address', - 'valuePath': null, - }, - phone: { - //'selector': '#billing_phone_field', // There is no shipping phone field. - 'valueCallback': function (data) { - let phone = ''; - const cc = data?.shipping?.phoneNumber?.countryCode; - const number = data?.shipping?.phoneNumber?.nationalNumber; + }, + fields: { + email: { + valuePath: 'email', + }, + firstName: { + key: 'firstName', + selector: '#shipping_first_name_field', + valuePath: 'shipping.name.firstName', + }, + lastName: { + selector: '#shipping_last_name_field', + valuePath: 'shipping.name.lastName', + }, + street1: { + selector: '#shipping_address_1_field', + valuePath: 'shipping.address.addressLine1', + }, + street2: { + selector: '#shipping_address_2_field', + valuePath: null, + }, + postCode: { + selector: '#shipping_postcode_field', + valuePath: 'shipping.address.postalCode', + }, + city: { + selector: '#shipping_city_field', + valuePath: 'shipping.address.adminArea2', + }, + stateCode: { + selector: '#shipping_state_field', + valuePath: 'shipping.address.adminArea1', + }, + countryCode: { + selector: '#shipping_country_field', + valuePath: 'shipping.address.countryCode', + }, + company: { + selector: '#shipping_company_field', + valuePath: null, + }, + shipDifferentAddress: { + selector: '#ship-to-different-address', + valuePath: null, + }, + phone: { + //'selector': '#billing_phone_field', // There is no shipping phone field. + valueCallback( data ) { + let phone = ''; + const cc = data?.shipping?.phoneNumber?.countryCode; + const number = + data?.shipping?.phoneNumber?.nationalNumber; - if (cc) { - phone = `+${cc} `; - } - phone += number; - return phone; - } - } - } - }); - } + if ( cc ) { + phone = `+${ cc } `; + } + phone += number; + return phone; + }, + }, + }, + } ); + } - hasEmptyValues(data, stateName) { - return !data.value('email') - || !data.value('firstName') - || !data.value('lastName') - || !data.value('street1') - || !data.value('city') - || !stateName; - } + hasEmptyValues( data, stateName ) { + return ( + ! data.value( 'email' ) || + ! data.value( 'firstName' ) || + ! data.value( 'lastName' ) || + ! data.value( 'street1' ) || + ! data.value( 'city' ) || + ! stateName + ); + } - isActive() { - return this.group.active; - } + isActive() { + return this.group.active; + } - activate() { - this.group.activate(); - } + activate() { + this.group.activate(); + } - deactivate() { - this.group.deactivate(); - } + deactivate() { + this.group.deactivate(); + } - refresh() { - this.group.refresh(); - } + refresh() { + this.group.refresh(); + } - setData(data) { - this.group.setData(data); - } - - toSubmitData(data) { - return this.group.toSubmitData(data); - } + setData( data ) { + this.group.setData( data ); + } + toSubmitData( data ) { + return this.group.toSubmitData( data ); + } } export default ShippingView; diff --git a/modules/ppcp-axo/resources/js/boot.js b/modules/ppcp-axo/resources/js/boot.js index 6b8b35893..75bc9e636 100644 --- a/modules/ppcp-axo/resources/js/boot.js +++ b/modules/ppcp-axo/resources/js/boot.js @@ -1,33 +1,24 @@ -import AxoManager from "./AxoManager"; -import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; +import AxoManager from './AxoManager'; +import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; -(function ({ - axoConfig, - ppcpConfig, - jQuery -}) { +( function ( { axoConfig, ppcpConfig, jQuery } ) { + const bootstrap = () => { + new AxoManager( axoConfig, ppcpConfig ); + }; - const bootstrap = () => { - new AxoManager(axoConfig, ppcpConfig); - } + document.addEventListener( 'DOMContentLoaded', () => { + if ( ! typeof PayPalCommerceGateway ) { + console.error( 'AXO could not be configured.' ); + return; + } - document.addEventListener( - 'DOMContentLoaded', - () => { - if (!typeof (PayPalCommerceGateway)) { - console.error('AXO could not be configured.'); - return; - } - - // Load PayPal - loadPaypalScript(ppcpConfig, () => { - bootstrap(); - }); - }, - ); - -})({ - axoConfig: window.wc_ppcp_axo, - ppcpConfig: window.PayPalCommerceGateway, - jQuery: window.jQuery -}); + // Load PayPal + loadPaypalScript( ppcpConfig, () => { + bootstrap(); + } ); + } ); +} )( { + axoConfig: window.wc_ppcp_axo, + ppcpConfig: window.PayPalCommerceGateway, + jQuery: window.jQuery, +} ); diff --git a/modules/ppcp-axo/resources/js/test/components.test.js b/modules/ppcp-axo/resources/js/test/components.test.js index d4bbbcba8..e48a50c0c 100644 --- a/modules/ppcp-axo/resources/js/test/components.test.js +++ b/modules/ppcp-axo/resources/js/test/components.test.js @@ -1,20 +1,20 @@ import * as $ from 'jquery'; -import DomElement from "../Components/DomElement"; -import FormFieldGroup from "../Components/FormFieldGroup"; +import DomElement from '../Components/DomElement'; +import FormFieldGroup from '../Components/FormFieldGroup'; -global['$'] = global['jQuery'] = $; +global.$ = global.jQuery = $; -test('get dom element selector', () => { - const element = new DomElement({selector: '.foo'}); +test( 'get dom element selector', () => { + const element = new DomElement( { selector: '.foo' } ); - expect(element.selector).toBe('.foo') -}); + expect( element.selector ).toBe( '.foo' ); +} ); -test('form field group activate', () => { - const formFieldGroup = new FormFieldGroup({}); +test( 'form field group activate', () => { + const formFieldGroup = new FormFieldGroup( {} ); - expect(formFieldGroup.active).toBe(false) + expect( formFieldGroup.active ).toBe( false ); - formFieldGroup.activate() - expect(formFieldGroup.active).toBe(true) -}); + formFieldGroup.activate(); + expect( formFieldGroup.active ).toBe( true ); +} ); diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 629b56421..3505ff555 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -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(); } /** diff --git a/modules/ppcp-blocks/resources/js/Bootstrap/BlockCheckoutMessagesBootstrap.js b/modules/ppcp-blocks/resources/js/Bootstrap/BlockCheckoutMessagesBootstrap.js index 6d301451d..485f7a765 100644 --- a/modules/ppcp-blocks/resources/js/Bootstrap/BlockCheckoutMessagesBootstrap.js +++ b/modules/ppcp-blocks/resources/js/Bootstrap/BlockCheckoutMessagesBootstrap.js @@ -1,55 +1,62 @@ -import MessagesBootstrap from "../../../../ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap"; -import {debounce} from "../Helper/debounce"; +import MessagesBootstrap from '../../../../ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap'; +import { debounce } from '../Helper/debounce'; class BlockCheckoutMessagesBootstrap { - constructor(scriptData) { - this.messagesBootstrap = new MessagesBootstrap(scriptData, null); - this.lastCartTotal = null; - } + constructor( scriptData ) { + this.messagesBootstrap = new MessagesBootstrap( scriptData, null ); + this.lastCartTotal = null; + } - init() { - this.messagesBootstrap.init(); + init() { + this.messagesBootstrap.init(); - this._updateCartTotal(); + this._updateCartTotal(); - if (wp.data?.subscribe) { - wp.data.subscribe(debounce(() => { - this._updateCartTotal(); - }, 300)); - } - } + if ( wp.data?.subscribe ) { + wp.data.subscribe( + debounce( () => { + this._updateCartTotal(); + }, 300 ) + ); + } + } - /** - * @private - */ - _getCartTotal() { - if (!wp.data.select) { - return null; - } + /** + * @private + */ + _getCartTotal() { + if ( ! wp.data.select ) { + return null; + } - const cart = wp.data.select('wc/store/cart') - if (!cart) { - return null; - } + const cart = wp.data.select( 'wc/store/cart' ); + if ( ! cart ) { + return null; + } - const totals = cart.getCartTotals(); - return parseInt(totals.total_price, 10) / 10 ** totals.currency_minor_unit; - } + const totals = cart.getCartTotals(); + return ( + parseInt( totals.total_price, 10 ) / + 10 ** totals.currency_minor_unit + ); + } - /** - * @private - */ - _updateCartTotal() { - const currentTotal = this._getCartTotal(); - if (currentTotal === null) { - return; - } + /** + * @private + */ + _updateCartTotal() { + const currentTotal = this._getCartTotal(); + if ( currentTotal === null ) { + return; + } - if (currentTotal !== this.lastCartTotal) { - this.lastCartTotal = currentTotal; - jQuery(document.body).trigger('ppcp_block_cart_total_updated', [currentTotal]); - } - } + if ( currentTotal !== this.lastCartTotal ) { + this.lastCartTotal = currentTotal; + jQuery( document.body ).trigger( 'ppcp_block_cart_total_updated', [ + currentTotal, + ] ); + } + } } export default BlockCheckoutMessagesBootstrap; diff --git a/modules/ppcp-blocks/resources/js/Components/card-fields.js b/modules/ppcp-blocks/resources/js/Components/card-fields.js index 542d40bd3..4a1f25785 100644 --- a/modules/ppcp-blocks/resources/js/Components/card-fields.js +++ b/modules/ppcp-blocks/resources/js/Components/card-fields.js @@ -1,87 +1,95 @@ -import {useEffect, useState} from '@wordpress/element'; +import { useEffect, useState } from '@wordpress/element'; import { - PayPalScriptProvider, - PayPalCardFieldsProvider, - PayPalCardFieldsForm, -} from "@paypal/react-paypal-js"; + PayPalScriptProvider, + PayPalCardFieldsProvider, + PayPalCardFieldsForm, +} from '@paypal/react-paypal-js'; -import {CheckoutHandler} from "./checkout-handler"; -import {createOrder, onApprove} from "../card-fields-config"; -import {cartHasSubscriptionProducts} from "../Helper/Subscription"; +import { CheckoutHandler } from './checkout-handler'; +import { createOrder, onApprove } from '../card-fields-config'; +import { cartHasSubscriptionProducts } from '../Helper/Subscription'; -export function CardFields({config, eventRegistration, emitResponse, components}) { - const {onPaymentSetup} = eventRegistration; - const {responseTypes} = emitResponse; - const { PaymentMethodIcons } = components; +export function CardFields( { + config, + eventRegistration, + emitResponse, + components, +} ) { + const { onPaymentSetup } = eventRegistration; + const { responseTypes } = emitResponse; + const { PaymentMethodIcons } = components; - const [cardFieldsForm, setCardFieldsForm] = useState(); - const getCardFieldsForm = (cardFieldsForm) => { - setCardFieldsForm(cardFieldsForm) - } + const [ cardFieldsForm, setCardFieldsForm ] = useState(); + const getCardFieldsForm = ( cardFieldsForm ) => { + setCardFieldsForm( cardFieldsForm ); + }; - const getSavePayment = (savePayment) => { - localStorage.setItem('ppcp-save-card-payment', savePayment); - } + const getSavePayment = ( savePayment ) => { + localStorage.setItem( 'ppcp-save-card-payment', savePayment ); + }; - const hasSubscriptionProducts = cartHasSubscriptionProducts(config.scriptData); - useEffect(() => { - localStorage.removeItem('ppcp-save-card-payment'); + const hasSubscriptionProducts = cartHasSubscriptionProducts( + config.scriptData + ); + useEffect( () => { + localStorage.removeItem( 'ppcp-save-card-payment' ); - if(hasSubscriptionProducts) { - localStorage.setItem('ppcp-save-card-payment', 'true'); - } + if ( hasSubscriptionProducts ) { + localStorage.setItem( 'ppcp-save-card-payment', 'true' ); + } + }, [ hasSubscriptionProducts ] ); - }, [hasSubscriptionProducts]) + useEffect( + () => + onPaymentSetup( () => { + async function handlePaymentProcessing() { + await cardFieldsForm.submit().catch( ( error ) => { + return { + type: responseTypes.ERROR, + }; + } ); - useEffect( - () => - onPaymentSetup(() => { - async function handlePaymentProcessing() { - await cardFieldsForm.submit() - .catch((error) => { - return { - type: responseTypes.ERROR, - } - }); + return { + type: responseTypes.SUCCESS, + }; + } - return { - type: responseTypes.SUCCESS, - } - } + return handlePaymentProcessing(); + } ), + [ onPaymentSetup, cardFieldsForm ] + ); - return handlePaymentProcessing(); - }), - [onPaymentSetup, cardFieldsForm] - ); - - return ( - <> - - { - console.error(err); - }} - > - - - - - - - ) + return ( + <> + + { + console.error( err ); + } } + > + + + + + + + ); } diff --git a/modules/ppcp-blocks/resources/js/Components/checkout-handler.js b/modules/ppcp-blocks/resources/js/Components/checkout-handler.js index 48dd0602d..b378e7c45 100644 --- a/modules/ppcp-blocks/resources/js/Components/checkout-handler.js +++ b/modules/ppcp-blocks/resources/js/Components/checkout-handler.js @@ -1,28 +1,34 @@ -import {useEffect} from '@wordpress/element'; -import {usePayPalCardFields} from "@paypal/react-paypal-js"; +import { useEffect } from '@wordpress/element'; +import { usePayPalCardFields } from '@paypal/react-paypal-js'; -export const CheckoutHandler = ({getCardFieldsForm, getSavePayment, hasSubscriptionProducts, saveCardText, is_vaulting_enabled}) => { - const {cardFieldsForm} = usePayPalCardFields(); +export const CheckoutHandler = ( { + getCardFieldsForm, + getSavePayment, + hasSubscriptionProducts, + saveCardText, + is_vaulting_enabled, +} ) => { + const { cardFieldsForm } = usePayPalCardFields(); - useEffect(() => { - getCardFieldsForm(cardFieldsForm) - }, []); + useEffect( () => { + getCardFieldsForm( cardFieldsForm ); + }, [] ); - if (!is_vaulting_enabled) { - return null; - } + if ( ! is_vaulting_enabled ) { + return null; + } - return ( - <> - getSavePayment(e.target.checked)} - defaultChecked={hasSubscriptionProducts} - disabled={hasSubscriptionProducts} - /> - - - ) -} + return ( + <> + getSavePayment( e.target.checked ) } + defaultChecked={ hasSubscriptionProducts } + disabled={ hasSubscriptionProducts } + /> + + + ); +}; diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index 6f24d700a..6d514d51a 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -1,171 +1,179 @@ /** - * @param {String} fullName - * @returns {Array} + * @param {string} fullName + * @return {Array} */ -export const splitFullName = (fullName) => { - fullName = fullName.trim() - if (!fullName.includes(' ')) { - return [fullName, '']; - } - const parts = fullName.split(' '); - const firstName = parts[0]; - parts.shift(); - const lastName = parts.join(' '); - return [firstName, lastName]; -} +export const splitFullName = ( fullName ) => { + fullName = fullName.trim(); + if ( ! fullName.includes( ' ' ) ) { + return [ fullName, '' ]; + } + const parts = fullName.split( ' ' ); + const firstName = parts[ 0 ]; + parts.shift(); + const lastName = parts.join( ' ' ); + return [ firstName, lastName ]; +}; /** * @param {Object} address - * @returns {Object} + * @return {Object} */ -export const paypalAddressToWc = (address) => { - let map = { - country_code: 'country', - address_line_1: 'address_1', - address_line_2: 'address_2', - admin_area_1: 'state', - admin_area_2: 'city', - postal_code: 'postcode', - }; - if (address.city) { // address not from API, such as onShippingChange - map = { - country_code: 'country', - state: 'state', - city: 'city', - postal_code: 'postcode', - }; - } - const result = {}; - Object.entries(map).forEach(([paypalKey, wcKey]) => { - if (address[paypalKey]) { - result[wcKey] = address[paypalKey]; - } - }); +export const paypalAddressToWc = ( address ) => { + let map = { + country_code: 'country', + address_line_1: 'address_1', + address_line_2: 'address_2', + admin_area_1: 'state', + admin_area_2: 'city', + postal_code: 'postcode', + }; + if ( address.city ) { + // address not from API, such as onShippingChange + map = { + country_code: 'country', + state: 'state', + city: 'city', + postal_code: 'postcode', + }; + } + const result = {}; + Object.entries( map ).forEach( ( [ paypalKey, wcKey ] ) => { + if ( address[ paypalKey ] ) { + result[ wcKey ] = address[ paypalKey ]; + } + } ); - const defaultAddress = { - first_name: '', - last_name: '', - company: '', - address_1: '', - address_2: '', - city: '', - state: '', - postcode: '', - country: '', - phone: '', - }; + const defaultAddress = { + first_name: '', + last_name: '', + company: '', + address_1: '', + address_2: '', + city: '', + state: '', + postcode: '', + country: '', + phone: '', + }; - return {...defaultAddress, ...result}; -} + return { ...defaultAddress, ...result }; +}; /** * @param {Object} shipping - * @returns {Object} + * @return {Object} */ -export const paypalShippingToWc = (shipping) => { - const [firstName, lastName] = (shipping.name ? splitFullName(shipping.name.full_name) : ['','']); - return { - ...paypalAddressToWc(shipping.address), - first_name: firstName, - last_name: lastName, - } -} +export const paypalShippingToWc = ( shipping ) => { + const [ firstName, lastName ] = shipping.name + ? splitFullName( shipping.name.full_name ) + : [ '', '' ]; + return { + ...paypalAddressToWc( shipping.address ), + first_name: firstName, + last_name: lastName, + }; +}; /** * @param {Object} payer - * @returns {Object} + * @return {Object} */ -export const paypalPayerToWc = (payer) => { - const firstName = payer?.name?.given_name ?? ''; - const lastName = payer?.name?.surname ?? ''; - const address = payer.address ? paypalAddressToWc(payer.address) : {}; - return { - ...address, - first_name: firstName, - last_name: lastName, - email: payer.email_address, - } -} +export const paypalPayerToWc = ( payer ) => { + const firstName = payer?.name?.given_name ?? ''; + const lastName = payer?.name?.surname ?? ''; + const address = payer.address ? paypalAddressToWc( payer.address ) : {}; + return { + ...address, + first_name: firstName, + last_name: lastName, + email: payer.email_address, + }; +}; /** * @param {Object} subscriber - * @returns {Object} + * @return {Object} */ -export const paypalSubscriberToWc = (subscriber) => { - const firstName = subscriber?.name?.given_name ?? ''; - const lastName = subscriber?.name?.surname ?? ''; - const address = subscriber.address ? paypalAddressToWc(subscriber.shipping_address.address) : {}; - return { - ...address, - first_name: firstName, - last_name: lastName, - email: subscriber.email_address, - } -} +export const paypalSubscriberToWc = ( subscriber ) => { + const firstName = subscriber?.name?.given_name ?? ''; + const lastName = subscriber?.name?.surname ?? ''; + const address = subscriber.address + ? paypalAddressToWc( subscriber.shipping_address.address ) + : {}; + return { + ...address, + first_name: firstName, + last_name: lastName, + email: subscriber.email_address, + }; +}; /** * @param {Object} order - * @returns {Object} + * @return {Object} */ -export const paypalOrderToWcShippingAddress = (order) => { - const shipping = order.purchase_units[0].shipping; - if (!shipping) { - return {}; - } +export const paypalOrderToWcShippingAddress = ( order ) => { + const shipping = order.purchase_units[ 0 ].shipping; + if ( ! shipping ) { + return {}; + } - const res = paypalShippingToWc(shipping); + const res = paypalShippingToWc( shipping ); - // use the name from billing if the same, to avoid possible mistakes when splitting full_name - if (order.payer) { - const billingAddress = paypalPayerToWc(order.payer); - if (`${res.first_name} ${res.last_name}` === `${billingAddress.first_name} ${billingAddress.last_name}`) { - res.first_name = billingAddress.first_name; - res.last_name = billingAddress.last_name; - } - } + // use the name from billing if the same, to avoid possible mistakes when splitting full_name + if ( order.payer ) { + const billingAddress = paypalPayerToWc( order.payer ); + if ( + `${ res.first_name } ${ res.last_name }` === + `${ billingAddress.first_name } ${ billingAddress.last_name }` + ) { + res.first_name = billingAddress.first_name; + res.last_name = billingAddress.last_name; + } + } - return res; -} + return res; +}; /** * - * @param order - * @returns {{shippingAddress: Object, billingAddress: Object}} + * @param order + * @return {{shippingAddress: Object, billingAddress: Object}} */ -export const paypalOrderToWcAddresses = (order) => { - const shippingAddress = paypalOrderToWcShippingAddress(order); - let billingAddress = shippingAddress; - if (order.payer) { - billingAddress = paypalPayerToWc(order.payer); - // no billing address, such as if billing address retrieval is not allowed in the merchant account - if (!billingAddress.address_line_1) { - // use only non empty values from payer address, otherwise it will override shipping address - let payerAddress = Object.fromEntries( - Object.entries(billingAddress).filter( - ([key, value]) => value !== '' && key !== 'country' - ) - ); +export const paypalOrderToWcAddresses = ( order ) => { + const shippingAddress = paypalOrderToWcShippingAddress( order ); + let billingAddress = shippingAddress; + if ( order.payer ) { + billingAddress = paypalPayerToWc( order.payer ); + // no billing address, such as if billing address retrieval is not allowed in the merchant account + if ( ! billingAddress.address_line_1 ) { + // use only non empty values from payer address, otherwise it will override shipping address + const payerAddress = Object.fromEntries( + Object.entries( billingAddress ).filter( + ( [ key, value ] ) => value !== '' && key !== 'country' + ) + ); - billingAddress = { - ...shippingAddress, - ...payerAddress - }; - } - } + billingAddress = { + ...shippingAddress, + ...payerAddress, + }; + } + } - return {billingAddress, shippingAddress}; -} + return { billingAddress, shippingAddress }; +}; /** * - * @param subscription - * @returns {{shippingAddress: Object, billingAddress: Object}} + * @param subscription + * @return {{shippingAddress: Object, billingAddress: Object}} */ -export const paypalSubscriptionToWcAddresses = (subscription) => { - const shippingAddress = paypalSubscriberToWc(subscription.subscriber); - let billingAddress = shippingAddress; - return {billingAddress, shippingAddress}; -} +export const paypalSubscriptionToWcAddresses = ( subscription ) => { + const shippingAddress = paypalSubscriberToWc( subscription.subscriber ); + const billingAddress = shippingAddress; + return { billingAddress, shippingAddress }; +}; /** * Merges two WC addresses. @@ -173,22 +181,28 @@ export const paypalSubscriptionToWcAddresses = (subscription) => { * * @param {Object} address1 * @param {Object} address2 - * @returns {any} + * @return {any} */ -export const mergeWcAddress = (address1, address2) => { - if ('billingAddress' in address1) { - return { - billingAddress: mergeWcAddress(address1.billingAddress, address2.billingAddress), - shippingAddress: mergeWcAddress(address1.shippingAddress, address2.shippingAddress), - } - } +export const mergeWcAddress = ( address1, address2 ) => { + if ( 'billingAddress' in address1 ) { + return { + billingAddress: mergeWcAddress( + address1.billingAddress, + address2.billingAddress + ), + shippingAddress: mergeWcAddress( + address1.shippingAddress, + address2.shippingAddress + ), + }; + } - let address2WithoutEmpty = {...address2}; - Object.keys(address2).forEach(key => { - if (address2[key] === '') { - delete address2WithoutEmpty[key]; - } - }); + const address2WithoutEmpty = { ...address2 }; + Object.keys( address2 ).forEach( ( key ) => { + if ( address2[ key ] === '' ) { + delete address2WithoutEmpty[ key ]; + } + } ); - return {...address1, ...address2WithoutEmpty}; -} + return { ...address1, ...address2WithoutEmpty }; +}; diff --git a/modules/ppcp-blocks/resources/js/Helper/Helper.js b/modules/ppcp-blocks/resources/js/Helper/Helper.js index 379c88a49..8da0f9c1a 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Helper.js +++ b/modules/ppcp-blocks/resources/js/Helper/Helper.js @@ -1,22 +1,24 @@ /** - * @param str - * @returns {string} + * @param str + * @return {string} */ -export const toSnakeCase = (str) => { - return str.replace(/[\w]([A-Z])/g, function(m) { - return m[0] + "_" + m[1]; - }).toLowerCase(); -} +export const toSnakeCase = ( str ) => { + return str + .replace( /[\w]([A-Z])/g, function ( m ) { + return m[ 0 ] + '_' + m[ 1 ]; + } ) + .toLowerCase(); +}; /** - * @param obj - * @returns {{}} + * @param obj + * @return {{}} */ -export const convertKeysToSnakeCase = (obj) => { - const newObj = {}; - Object.keys(obj).forEach((key) => { - const newKey = toSnakeCase(key); - newObj[newKey] = obj[key]; - }); - return newObj; -} +export const convertKeysToSnakeCase = ( obj ) => { + const newObj = {}; + Object.keys( obj ).forEach( ( key ) => { + const newKey = toSnakeCase( key ); + newObj[ newKey ] = obj[ key ]; + } ); + return newObj; +}; diff --git a/modules/ppcp-blocks/resources/js/Helper/Subscription.js b/modules/ppcp-blocks/resources/js/Helper/Subscription.js index b274fea5e..a757a2ee8 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Subscription.js +++ b/modules/ppcp-blocks/resources/js/Helper/Subscription.js @@ -1,16 +1,18 @@ /** * @param {Object} scriptData - * @returns {Boolean} + * @return {boolean} */ -export const isPayPalSubscription = (scriptData) => { - return scriptData.data_client_id.has_subscriptions - && scriptData.data_client_id.paypal_subscriptions_enabled; -} +export const isPayPalSubscription = ( scriptData ) => { + return ( + scriptData.data_client_id.has_subscriptions && + scriptData.data_client_id.paypal_subscriptions_enabled + ); +}; /** * @param {Object} scriptData - * @returns {Boolean} + * @return {boolean} */ -export const cartHasSubscriptionProducts = (scriptData) => { - return !! scriptData?.locations_with_subscription_product?.cart; -} +export const cartHasSubscriptionProducts = ( scriptData ) => { + return !! scriptData?.locations_with_subscription_product?.cart; +}; diff --git a/modules/ppcp-blocks/resources/js/Helper/debounce.js b/modules/ppcp-blocks/resources/js/Helper/debounce.js index 68a34e771..0de89aaa1 100644 --- a/modules/ppcp-blocks/resources/js/Helper/debounce.js +++ b/modules/ppcp-blocks/resources/js/Helper/debounce.js @@ -1,9 +1,9 @@ -export const debounce = (callback, delayMs) => { - let timeoutId = null; - return (...args) => { - window.clearTimeout(timeoutId); - timeoutId = window.setTimeout(() => { - callback.apply(null, args); - }, delayMs); - }; +export const debounce = ( callback, delayMs ) => { + let timeoutId = null; + return ( ...args ) => { + window.clearTimeout( timeoutId ); + timeoutId = window.setTimeout( () => { + callback.apply( null, args ); + }, delayMs ); + }; }; diff --git a/modules/ppcp-blocks/resources/js/advanced-card-checkout-block.js b/modules/ppcp-blocks/resources/js/advanced-card-checkout-block.js index 6d268c122..48df41935 100644 --- a/modules/ppcp-blocks/resources/js/advanced-card-checkout-block.js +++ b/modules/ppcp-blocks/resources/js/advanced-card-checkout-block.js @@ -1,17 +1,19 @@ import { registerPaymentMethod } from '@woocommerce/blocks-registry'; -import {CardFields} from "./Components/card-fields"; +import { CardFields } from './Components/card-fields'; -const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); +const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); -registerPaymentMethod({ - name: config.id, - label:
, - content: , - edit:
, - ariaLabel: config.title, - canMakePayment: () => {return true}, - supports: { - showSavedCards: true, - features: config.supports - } -}) +registerPaymentMethod( { + name: config.id, + label:
, + content: , + edit:
, + ariaLabel: config.title, + canMakePayment: () => { + return true; + }, + supports: { + showSavedCards: true, + features: config.supports, + }, +} ); diff --git a/modules/ppcp-blocks/resources/js/card-fields-config.js b/modules/ppcp-blocks/resources/js/card-fields-config.js index b269e65be..5383729dc 100644 --- a/modules/ppcp-blocks/resources/js/card-fields-config.js +++ b/modules/ppcp-blocks/resources/js/card-fields-config.js @@ -1,45 +1,46 @@ export async function createOrder() { - const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); + const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); - return fetch(config.scriptData.ajax.create_order.endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - nonce: config.scriptData.ajax.create_order.nonce, - context: config.scriptData.context, - payment_method: 'ppcp-credit-card-gateway', - save_payment_method: localStorage.getItem('ppcp-save-card-payment') === 'true', - }), - }) - .then((response) => response.json()) - .then((order) => { - return order.data.id; - }) - .catch((err) => { - console.error(err); - }); + return fetch( config.scriptData.ajax.create_order.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: config.scriptData.ajax.create_order.nonce, + context: config.scriptData.context, + payment_method: 'ppcp-credit-card-gateway', + save_payment_method: + localStorage.getItem( 'ppcp-save-card-payment' ) === 'true', + } ), + } ) + .then( ( response ) => response.json() ) + .then( ( order ) => { + return order.data.id; + } ) + .catch( ( err ) => { + console.error( err ); + } ); } -export async function onApprove(data) { - const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); +export async function onApprove( data ) { + const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); - return fetch(config.scriptData.ajax.approve_order.endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - order_id: data.orderID, - nonce: config.scriptData.ajax.approve_order.nonce, - }), - }) - .then((response) => response.json()) - .then((data) => { - localStorage.removeItem('ppcp-save-card-payment'); - }) - .catch((err) => { - console.error(err); - }); + return fetch( config.scriptData.ajax.approve_order.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + order_id: data.orderID, + nonce: config.scriptData.ajax.approve_order.nonce, + } ), + } ) + .then( ( response ) => response.json() ) + .then( ( data ) => { + localStorage.removeItem( 'ppcp-save-card-payment' ); + } ) + .catch( ( err ) => { + console.error( err ); + } ); } diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 1434d1600..bf36dcb65 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,32 +1,27 @@ -import {useEffect, useState} from '@wordpress/element'; -import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; +import { useEffect, useState } from '@wordpress/element'; import { - mergeWcAddress, - paypalAddressToWc, - paypalOrderToWcAddresses, - paypalSubscriptionToWcAddresses -} from "./Helper/Address"; + registerExpressPaymentMethod, + registerPaymentMethod, +} from '@woocommerce/blocks-registry'; import { - convertKeysToSnakeCase -} from "./Helper/Helper"; + mergeWcAddress, + paypalAddressToWc, + paypalOrderToWcAddresses, + 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"; -const config = wc.wcSettings.getSetting('ppcp-gateway_data'); + 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'; +const config = wc.wcSettings.getSetting( 'ppcp-gateway_data' ); window.ppcpFundingSource = config.fundingSource; @@ -34,653 +29,780 @@ let registeredContext = false; let paypalScriptPromise = null; -const PayPalComponent = ({ - onClick, - onClose, - onSubmit, - onError, - eventRegistration, - emitResponse, - activePaymentMethod, - shippingData, - isEditing, - fundingSource, -}) => { - const {onPaymentSetup, onCheckoutFail, onCheckoutValidation} = eventRegistration; - const {responseTypes} = emitResponse; - - const [paypalOrder, setPaypalOrder] = useState(null); - 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.then(() => setPaypalScriptLoaded(true)); - } - - 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) { - return; - } - try { - 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); - if (shippingData.needsShipping) { - wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress); - } - } catch (err) { - // sometimes the PayPal address is missing, skip in this case. - console.log(err); - } - // 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 { - const requestBody = { - nonce: config.scriptData.ajax.create_order.nonce, - bn_code: '', - context: config.scriptData.context, - payment_method: 'ppcp-gateway', - funding_source: window.ppcpFundingSource ?? 'paypal', - createaccount: false, - ...(data?.paymentSource && { payment_source: data.paymentSource }) - }; - - 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('
')); - } else if (json.data?.message) { - throw new Error(json.data.message); - } - - throw new Error(config.scriptData.labels.error.generic); - } - return json.data.id; - } catch (err) { - console.error(err); - - onError(err.message); - - onClose(); - - throw err; - } - }; - - 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; - } - - return actions.subscription.create({ - 'plan_id': planId - }); - }; - - const handleApproveSubscription = async (data, actions) => { - try { - const subscription = await actions.subscription.get(); - - if (subscription) { - const addresses = paypalSubscriptionToWcAddresses(subscription); - - let promises = [ - // save address on server - wp.data.dispatch('wc/store/cart').updateCustomerData({ - billing_address: addresses.billingAddress, - shipping_address: addresses.shippingAddress, - }), - ]; - if (shouldHandleShippingInPayPal()) { - // set address in UI - 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)) - } - } - await Promise.all(promises); - } - - setPaypalOrder(subscription); - - 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, - order_id: data.orderID, - subscription_id: data.subscriptionID - }) - }); - - const json = await res.json(); - - if (!json.success) { - 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) - } - - if (!shouldHandleShippingInPayPal()) { - location.href = getCheckoutRedirectUrl(); - } else { - setGotoContinuationOnError(true); - onSubmit(); - } - } catch (err) { - console.error(err); - - onError(err.message); - - onClose(); - - throw err; - } - }; - - const getCheckoutRedirectUrl = () => { - 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()); - return checkoutUrl.toString(); - } - - const handleApprove = async (data, actions) => { - try { - const order = await actions.order.get(); - - if (order) { - const addresses = paypalOrderToWcAddresses(order); - - let promises = [ - // save address on server - wp.data.dispatch('wc/store/cart').updateCustomerData({ - billing_address: addresses.billingAddress, - shipping_address: addresses.shippingAddress, - }), - ]; - if (shouldHandleShippingInPayPal()) { - // set address in UI - 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)) - } - } - await Promise.all(promises); - } - - setPaypalOrder(order); - - 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') { - return actions.restart(); - } - if (json.data?.message) { - throw new Error(json.data.message); - } - - throw new Error(config.scriptData.labels.error.generic) - } - - if (!shouldHandleShippingInPayPal()) { - location.href = getCheckoutRedirectUrl(); - } else { - setGotoContinuationOnError(true); - onSubmit(); - } - } catch (err) { - console.error(err); - - onError(err.message); - - onClose(); - - throw err; - } - }; - - useEffect(() => { - const unsubscribe = onCheckoutValidation(() => { - if (config.scriptData.continuation) { - return true; - } - if (gotoContinuationOnError && wp.data.select('wc/store/validation').hasValidationErrors()) { - location.href = getCheckoutRedirectUrl(); - return { type: responseTypes.ERROR }; - } - - return true; - }); - return unsubscribe; - }, [onCheckoutValidation, gotoContinuationOnError] ); - - const handleClick = (data, actions) => { - if (isEditing) { - return actions.reject(); - } - - window.ppcpFundingSource = data.fundingSource; - - onClick(); - }; - - const shouldHandleShippingInPayPal = () => { - if (config.finalReviewEnabled) { - return false; - } - - return window.ppcpFundingSource !== 'venmo' || !config.scriptData.vaultingEnabled; - } - - let handleShippingOptionsChange = null; - let handleShippingAddressChange = null; - let handleSubscriptionShippingOptionsChange = null; - let handleSubscriptionShippingAddressChange = null; - - if (shippingData.needsShipping && shouldHandleShippingInPayPal()) { - handleShippingOptionsChange = async (data, actions) => { - try { - const shippingOptionId = data.selectedShippingOption?.id; - if (shippingOptionId) { - await wp.data.dispatch('wc/store/cart').selectShippingRate(shippingOptionId); - await shippingData.setSelectedRates(shippingOptionId); - } - - const res = await fetch(config.ajax.update_shipping.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.ajax.update_shipping.nonce, - order_id: data.orderID, - }) - }); - - const json = await res.json(); - - if (!json.success) { - throw new Error(json.data.message); - } - } catch (e) { - console.error(e); - - actions.reject(); - } - }; - - handleShippingAddressChange = async (data, actions) => { - try { - const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress)); - - await wp.data.dispatch('wc/store/cart').updateCustomerData({ - shipping_address: address, - }); - - await shippingData.setShippingAddress(address); - - const res = await fetch(config.ajax.update_shipping.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.ajax.update_shipping.nonce, - order_id: data.orderID, - }) - }); - - const json = await res.json(); - - if (!json.success) { - throw new Error(json.data.message); - } - } catch (e) { - console.error(e); - - actions.reject(); - } - }; - - handleSubscriptionShippingOptionsChange = async (data, actions) => { - try { - const shippingOptionId = data.selectedShippingOption?.id; - if (shippingOptionId) { - await wp.data.dispatch('wc/store/cart').selectShippingRate(shippingOptionId); - await shippingData.setSelectedRates(shippingOptionId); - } - } catch (e) { - console.error(e); - - actions.reject(); - } - }; - - handleSubscriptionShippingAddressChange = async (data, actions) => { - try { - const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress)); - - await wp.data.dispatch('wc/store/cart').updateCustomerData({ - shipping_address: address, - }); - - await shippingData.setShippingAddress(address); - - const res = await fetch(config.ajax.update_shipping.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.ajax.update_shipping.nonce, - order_id: data.orderID, - }) - }); - - const json = await res.json(); - - if (!json.success) { - throw new Error(json.data.message); - } - - } catch (e) { - console.error(e); - - actions.reject(); - } - }; - } - - useEffect(() => { - if (activePaymentMethod !== methodId) { - return; - } - - const unsubscribeProcessing = onPaymentSetup(() => { - if (config.scriptData.continuation) { - return { - type: responseTypes.SUCCESS, - meta: { - paymentMethodData: { - 'paypal_order_id': config.scriptData.continuation.order_id, - 'funding_source': window.ppcpFundingSource ?? 'paypal', - } - }, - }; - } - - const addresses = paypalOrderToWcAddresses(paypalOrder); - - return { - type: responseTypes.SUCCESS, - meta: { - paymentMethodData: { - 'paypal_order_id': paypalOrder.id, - 'funding_source': window.ppcpFundingSource ?? 'paypal', - }, - ...addresses, - }, - }; - }); - return () => { - unsubscribeProcessing(); - }; - }, [onPaymentSetup, paypalOrder, activePaymentMethod]); - - useEffect(() => { - if (activePaymentMethod !== methodId) { - return; - } - const unsubscribe = onCheckoutFail(({ processingResponse }) => { - console.error(processingResponse) - if (onClose) { - onClose(); - } - if (config.scriptData.continuation) { - return true; - } - if (shouldHandleShippingInPayPal()) { - location.href = getCheckoutRedirectUrl(); - } - return true; - }); - return unsubscribe; - }, [onCheckoutFail, onClose, activePaymentMethod]); - - if (config.scriptData.continuation) { - return ( -
- -
- ) - } - - if (!registeredContext) { - 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); - - if (!paypalScriptLoaded) { - return null; - } - - const PayPalButton = paypal.Buttons.driver("react", { React, ReactDOM }); - - const getOnShippingOptionsChange = (fundingSource) => { - if(fundingSource === 'venmo') { - return null; - } - - return (data, actions) => { - shouldHandleShippingInPayPal() - ? handleShippingOptionsChange(data, actions) - : null; - }; - } - - const getOnShippingAddressChange = (fundingSource) => { - if(fundingSource === 'venmo') { - return null; - } - - return (data, actions) => { - shouldHandleShippingInPayPal() - ? handleShippingAddressChange(data, actions) - : null; - }; - } - - if(isPayPalSubscription(config.scriptData)) { - return ( - - ); - } - - return ( - - ); -} +const PayPalComponent = ( { + onClick, + onClose, + onSubmit, + onError, + eventRegistration, + emitResponse, + activePaymentMethod, + shippingData, + isEditing, + fundingSource, +} ) => { + const { onPaymentSetup, onCheckoutFail, onCheckoutValidation } = + eventRegistration; + const { responseTypes } = emitResponse; + + const [ paypalOrder, setPaypalOrder ] = useState( null ); + 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.then( () => setPaypalScriptLoaded( true ) ); + } + + 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 + ) { + return; + } + try { + 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 ); + if ( shippingData.needsShipping ) { + wp.data + .dispatch( 'wc/store/cart' ) + .setShippingAddress( addresses.shippingAddress ); + } + } catch ( err ) { + // sometimes the PayPal address is missing, skip in this case. + console.log( err ); + } + // 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 { + const requestBody = { + nonce: config.scriptData.ajax.create_order.nonce, + bn_code: '', + context: config.scriptData.context, + payment_method: 'ppcp-gateway', + funding_source: window.ppcpFundingSource ?? 'paypal', + createaccount: false, + ...( data?.paymentSource && { + payment_source: data.paymentSource, + } ), + }; + + 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( '
' ) + ); + } else if ( json.data?.message ) { + throw new Error( json.data.message ); + } + + throw new Error( config.scriptData.labels.error.generic ); + } + return json.data.id; + } catch ( err ) { + console.error( err ); + + onError( err.message ); + + onClose(); + + throw err; + } + }; + + 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; + } + + return actions.subscription.create( { + plan_id: planId, + } ); + }; + + const handleApproveSubscription = async ( data, actions ) => { + try { + const subscription = await actions.subscription.get(); + + if ( subscription ) { + const addresses = + paypalSubscriptionToWcAddresses( subscription ); + + const promises = [ + // save address on server + wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( { + billing_address: addresses.billingAddress, + shipping_address: addresses.shippingAddress, + } ), + ]; + if ( shouldHandleShippingInPayPal() ) { + // set address in UI + 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 ) + ); + } + } + await Promise.all( promises ); + } + + setPaypalOrder( subscription ); + + 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, + order_id: data.orderID, + subscription_id: data.subscriptionID, + } ), + } + ); + + const json = await res.json(); + + if ( ! json.success ) { + 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 ); + } + + if ( ! shouldHandleShippingInPayPal() ) { + location.href = getCheckoutRedirectUrl(); + } else { + setGotoContinuationOnError( true ); + onSubmit(); + } + } catch ( err ) { + console.error( err ); + + onError( err.message ); + + onClose(); + + throw err; + } + }; + + const getCheckoutRedirectUrl = () => { + 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() + ); + return checkoutUrl.toString(); + }; + + const handleApprove = async ( data, actions ) => { + try { + const order = await actions.order.get(); + + if ( order ) { + const addresses = paypalOrderToWcAddresses( order ); + + const promises = [ + // save address on server + wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( { + billing_address: addresses.billingAddress, + shipping_address: addresses.shippingAddress, + } ), + ]; + if ( shouldHandleShippingInPayPal() ) { + // set address in UI + 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 ) + ); + } + } + await Promise.all( promises ); + } + + setPaypalOrder( order ); + + 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' + ) { + return actions.restart(); + } + if ( json.data?.message ) { + throw new Error( json.data.message ); + } + + throw new Error( config.scriptData.labels.error.generic ); + } + + if ( ! shouldHandleShippingInPayPal() ) { + location.href = getCheckoutRedirectUrl(); + } else { + setGotoContinuationOnError( true ); + onSubmit(); + } + } catch ( err ) { + console.error( err ); + + onError( err.message ); + + onClose(); + + throw err; + } + }; + + useEffect( () => { + const unsubscribe = onCheckoutValidation( () => { + if ( config.scriptData.continuation ) { + return true; + } + if ( + gotoContinuationOnError && + wp.data.select( 'wc/store/validation' ).hasValidationErrors() + ) { + location.href = getCheckoutRedirectUrl(); + return { type: responseTypes.ERROR }; + } + + return true; + } ); + return unsubscribe; + }, [ onCheckoutValidation, gotoContinuationOnError ] ); + + const handleClick = ( data, actions ) => { + if ( isEditing ) { + return actions.reject(); + } + + window.ppcpFundingSource = data.fundingSource; + + onClick(); + }; + + const shouldHandleShippingInPayPal = () => { + if ( config.finalReviewEnabled ) { + return false; + } + + return ( + window.ppcpFundingSource !== 'venmo' || + ! config.scriptData.vaultingEnabled + ); + }; + + let handleShippingOptionsChange = null; + let handleShippingAddressChange = null; + let handleSubscriptionShippingOptionsChange = null; + let handleSubscriptionShippingAddressChange = null; + + if ( shippingData.needsShipping && shouldHandleShippingInPayPal() ) { + handleShippingOptionsChange = async ( data, actions ) => { + try { + const shippingOptionId = data.selectedShippingOption?.id; + if ( shippingOptionId ) { + await wp.data + .dispatch( 'wc/store/cart' ) + .selectShippingRate( shippingOptionId ); + await shippingData.setSelectedRates( shippingOptionId ); + } + + const res = await fetch( config.ajax.update_shipping.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.ajax.update_shipping.nonce, + order_id: data.orderID, + } ), + } ); + + const json = await res.json(); + + if ( ! json.success ) { + throw new Error( json.data.message ); + } + } catch ( e ) { + console.error( e ); + + actions.reject(); + } + }; + + handleShippingAddressChange = async ( data, actions ) => { + try { + const address = paypalAddressToWc( + convertKeysToSnakeCase( data.shippingAddress ) + ); + + await wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( { + shipping_address: address, + } ); + + await shippingData.setShippingAddress( address ); + + const res = await fetch( config.ajax.update_shipping.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.ajax.update_shipping.nonce, + order_id: data.orderID, + } ), + } ); + + const json = await res.json(); + + if ( ! json.success ) { + throw new Error( json.data.message ); + } + } catch ( e ) { + console.error( e ); + + actions.reject(); + } + }; + + handleSubscriptionShippingOptionsChange = async ( data, actions ) => { + try { + const shippingOptionId = data.selectedShippingOption?.id; + if ( shippingOptionId ) { + await wp.data + .dispatch( 'wc/store/cart' ) + .selectShippingRate( shippingOptionId ); + await shippingData.setSelectedRates( shippingOptionId ); + } + } catch ( e ) { + console.error( e ); + + actions.reject(); + } + }; + + handleSubscriptionShippingAddressChange = async ( data, actions ) => { + try { + const address = paypalAddressToWc( + convertKeysToSnakeCase( data.shippingAddress ) + ); + + await wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( { + shipping_address: address, + } ); + + await shippingData.setShippingAddress( address ); + + const res = await fetch( config.ajax.update_shipping.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.ajax.update_shipping.nonce, + order_id: data.orderID, + } ), + } ); + + const json = await res.json(); + + if ( ! json.success ) { + throw new Error( json.data.message ); + } + } catch ( e ) { + console.error( e ); + + actions.reject(); + } + }; + } + + useEffect( () => { + if ( activePaymentMethod !== methodId ) { + return; + } + + const unsubscribeProcessing = onPaymentSetup( () => { + if ( config.scriptData.continuation ) { + return { + type: responseTypes.SUCCESS, + meta: { + paymentMethodData: { + paypal_order_id: + config.scriptData.continuation.order_id, + funding_source: + window.ppcpFundingSource ?? 'paypal', + }, + }, + }; + } + + const addresses = paypalOrderToWcAddresses( paypalOrder ); + + return { + type: responseTypes.SUCCESS, + meta: { + paymentMethodData: { + paypal_order_id: paypalOrder.id, + funding_source: window.ppcpFundingSource ?? 'paypal', + }, + ...addresses, + }, + }; + } ); + return () => { + unsubscribeProcessing(); + }; + }, [ onPaymentSetup, paypalOrder, activePaymentMethod ] ); + + useEffect( () => { + if ( activePaymentMethod !== methodId ) { + return; + } + const unsubscribe = onCheckoutFail( ( { processingResponse } ) => { + console.error( processingResponse ); + if ( onClose ) { + onClose(); + } + if ( config.scriptData.continuation ) { + return true; + } + if ( shouldHandleShippingInPayPal() ) { + location.href = getCheckoutRedirectUrl(); + } + return true; + } ); + return unsubscribe; + }, [ onCheckoutFail, onClose, activePaymentMethod ] ); + + if ( config.scriptData.continuation ) { + return ( +
+ ); + } + + if ( ! registeredContext ) { + 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 + ); + + if ( ! paypalScriptLoaded ) { + return null; + } + + const PayPalButton = paypal.Buttons.driver( 'react', { React, ReactDOM } ); + + const getOnShippingOptionsChange = ( fundingSource ) => { + if ( fundingSource === 'venmo' ) { + return null; + } + + return ( data, actions ) => { + shouldHandleShippingInPayPal() + ? handleShippingOptionsChange( data, actions ) + : null; + }; + }; + + const getOnShippingAddressChange = ( fundingSource ) => { + if ( fundingSource === 'venmo' ) { + return null; + } + + return ( data, actions ) => { + let shippingAddressChange = shouldHandleShippingInPayPal() + ? handleShippingAddressChange( data, actions ) + : null; + + return shippingAddressChange; + }; + }; + + if ( isPayPalSubscription( config.scriptData ) ) { + return ( + + ); + } + + return ( + + ); +}; const BlockEditorPayPalComponent = () => { + const urlParams = { + clientId: 'test', + ...config.scriptData.url_params, + dataNamespace: 'ppcp-blocks-editor-paypal-buttons', + components: 'buttons', + }; + return ( + + { + return false; + } } + /> + + ); +}; - const urlParams = { - clientId: 'test', - ...config.scriptData.url_params, - dataNamespace: 'ppcp-blocks-editor-paypal-buttons', - components: 'buttons', - } - return ( - - { - return false; - }} - /> - - ) -} - -const features = ['products']; +const features = [ 'products' ]; 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 - ) { - block_enabled = false; - } +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 + ) { + 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 - ) { - 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 + ) { + 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 - ) { - 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 + ) { + block_enabled = false; + } - features.push('subscriptions'); + features.push( 'subscriptions' ); } -if (block_enabled && config.enabled) { - if ((config.addPlaceOrderMethod || config.usePlaceOrder) && !config.scriptData.continuation) { - let descriptionElement =
; - if (config.placeOrderButtonDescription) { - descriptionElement =
-

-

-
; - } +if ( block_enabled && config.enabled ) { + if ( + ( config.addPlaceOrderMethod || config.usePlaceOrder ) && + ! config.scriptData.continuation + ) { + let descriptionElement = ( +
+ ); + if ( config.placeOrderButtonDescription ) { + descriptionElement = ( +
+

+

+
+ ); + } - registerPaymentMethod({ - name: config.id, - label:
, - content: descriptionElement, - edit: descriptionElement, - placeOrderButtonLabel: config.placeOrderButtonText, - ariaLabel: config.title, - canMakePayment: () => { - return config.enabled; - }, - supports: { - features: features, - }, - }); - } + registerPaymentMethod( { + name: config.id, + label:
, + content: descriptionElement, + edit: descriptionElement, + placeOrderButtonLabel: config.placeOrderButtonText, + ariaLabel: config.title, + canMakePayment: () => { + return config.enabled; + }, + supports: { + features, + }, + } ); + } - if (config.scriptData.continuation) { - registerPaymentMethod({ - name: config.id, - label:
, - content: , - edit: , - ariaLabel: config.title, - canMakePayment: () => { - return true; - }, - supports: { - features: [...features, 'ppcp_continuation'], - }, - }); - } else if (!config.usePlaceOrder) { - for (const fundingSource of ['paypal', ...config.enabledFundingSources]) { - registerExpressPaymentMethod({ - name: `${config.id}-${fundingSource}`, - paymentMethodId: config.id, - label:
, - content: , - edit: , - ariaLabel: config.title, - canMakePayment: async () => { - if (!paypalScriptPromise) { - paypalScriptPromise = loadPaypalScriptPromise(config.scriptData); - paypalScriptPromise.then(() => { - const messagesBootstrap = new BlockCheckoutMessagesBootstrap(config.scriptData); - messagesBootstrap.init(); - }); - } - await paypalScriptPromise; + if ( config.scriptData.continuation ) { + registerPaymentMethod( { + name: config.id, + label:
, + content: , + edit: , + ariaLabel: config.title, + canMakePayment: () => { + return true; + }, + supports: { + features: [ ...features, 'ppcp_continuation' ], + }, + } ); + } else if ( ! config.usePlaceOrder ) { + for ( const fundingSource of [ + 'paypal', + ...config.enabledFundingSources, + ] ) { + registerExpressPaymentMethod( { + name: `${ config.id }-${ fundingSource }`, + paymentMethodId: config.id, + label: ( +
+ ), + content: ( + + ), + edit: , + ariaLabel: config.title, + canMakePayment: async () => { + if ( ! paypalScriptPromise ) { + paypalScriptPromise = loadPaypalScriptPromise( + config.scriptData + ); + paypalScriptPromise.then( () => { + const messagesBootstrap = + new BlockCheckoutMessagesBootstrap( + config.scriptData + ); + messagesBootstrap.init(); + } ); + } + await paypalScriptPromise; - return paypal.Buttons({fundingSource}).isEligible(); - }, - supports: { - features: features, - }, - }); - } - } + return paypal.Buttons( { fundingSource } ).isEligible(); + }, + supports: { + features, + }, + } ); + } + } } diff --git a/modules/ppcp-blocks/resources/js/test/checkout-handler.test.js b/modules/ppcp-blocks/resources/js/test/checkout-handler.test.js index e7f870e11..eb5aa35dc 100644 --- a/modules/ppcp-blocks/resources/js/test/checkout-handler.test.js +++ b/modules/ppcp-blocks/resources/js/test/checkout-handler.test.js @@ -1,34 +1,34 @@ -import {render, screen} from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; -import {CheckoutHandler} from "../Components/checkout-handler"; +import { CheckoutHandler } from '../Components/checkout-handler'; -test('checkbox label displays the given text', async () => { - render( - {}} - saveCardText="Foo" - is_vaulting_enabled={true} - /> - ); +test( 'checkbox label displays the given text', async () => { + render( + {} } + saveCardText="Foo" + is_vaulting_enabled={ true } + /> + ); - await expect(screen.getByLabelText('Foo')).toBeInTheDocument(); -}); + await expect( screen.getByLabelText( 'Foo' ) ).toBeInTheDocument(); +} ); -test('click checkbox calls function passing checked value', async () => { - const getSavePayment = jest.fn(); +test( 'click checkbox calls function passing checked value', async () => { + const getSavePayment = jest.fn(); - render( - {}} - saveCardText="Foo" - is_vaulting_enabled={true} - /> - ); + render( + {} } + saveCardText="Foo" + is_vaulting_enabled={ true } + /> + ); - await userEvent.click(screen.getByLabelText('Foo')); + await userEvent.click( screen.getByLabelText( 'Foo' ) ); - await expect(getSavePayment.mock.calls).toHaveLength(1); - await expect(getSavePayment.mock.calls[0][0]).toBe(true); -}); + await expect( getSavePayment.mock.calls ).toHaveLength( 1 ); + await expect( getSavePayment.mock.calls[ 0 ][ 0 ] ).toBe( true ); +} ); diff --git a/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php b/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php index 2c6fd60f4..df113f8f0 100644 --- a/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php +++ b/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php @@ -52,7 +52,7 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType { * * @var Settings */ - protected $settings; + protected $plugin_settings; /** * AdvancedCardPaymentMethod constructor. @@ -70,12 +70,12 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType { $smart_button, Settings $settings ) { - $this->name = CreditCardGateway::ID; - $this->module_url = $module_url; - $this->version = $version; - $this->gateway = $gateway; - $this->smart_button = $smart_button; - $this->settings = $settings; + $this->name = CreditCardGateway::ID; + $this->module_url = $module_url; + $this->version = $version; + $this->gateway = $gateway; + $this->smart_button = $smart_button; + $this->plugin_settings = $settings; } /** @@ -118,8 +118,8 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType { 'scriptData' => $script_data, 'supports' => $this->gateway->supports, 'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ), - 'is_vaulting_enabled' => $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ), - 'card_icons' => $this->settings->has( 'card_icons' ) ? (array) $this->settings->get( 'card_icons' ) : array(), + 'is_vaulting_enabled' => $this->plugin_settings->has( 'vault_enabled_dcc' ) && $this->plugin_settings->get( 'vault_enabled_dcc' ), + 'card_icons' => $this->plugin_settings->has( 'card_icons' ) ? (array) $this->plugin_settings->get( 'card_icons' ) : array(), ); } diff --git a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php index f9322700d..be196fa03 100644 --- a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php +++ b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php @@ -22,7 +22,8 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; * Class UpdateShippingEndpoint */ class UpdateShippingEndpoint implements EndpointInterface { - const ENDPOINT = 'ppc-update-shipping'; + const ENDPOINT = 'ppc-update-shipping'; + const WC_STORE_API_ENDPOINT = '/wp-json/wc/store/cart/'; /** * The Request Data Helper. diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index e680115ba..9f5485b38 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -2,329 +2,414 @@ import MiniCartBootstap from './modules/ContextBootstrap/MiniCartBootstap'; import SingleProductBootstap from './modules/ContextBootstrap/SingleProductBootstap'; import CartBootstrap from './modules/ContextBootstrap/CartBootstap'; import CheckoutBootstap from './modules/ContextBootstrap/CheckoutBootstap'; -import PayNowBootstrap from "./modules/ContextBootstrap/PayNowBootstrap"; +import PayNowBootstrap from './modules/ContextBootstrap/PayNowBootstrap'; import Renderer from './modules/Renderer/Renderer'; import ErrorHandler from './modules/ErrorHandler'; -import HostedFieldsRenderer from "./modules/Renderer/HostedFieldsRenderer"; -import CardFieldsRenderer from "./modules/Renderer/CardFieldsRenderer"; -import MessageRenderer from "./modules/Renderer/MessageRenderer"; -import Spinner from "./modules/Helper/Spinner"; +import HostedFieldsRenderer from './modules/Renderer/HostedFieldsRenderer'; +import CardFieldsRenderer from './modules/Renderer/CardFieldsRenderer'; +import MessageRenderer from './modules/Renderer/MessageRenderer'; +import Spinner from './modules/Helper/Spinner'; import { - getCurrentPaymentMethod, - ORDER_BUTTON_SELECTOR, - PaymentMethods -} from "./modules/Helper/CheckoutMethodState"; -import {setVisibleByClass} from "./modules/Helper/Hiding"; -import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; -import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler"; -import MultistepCheckoutHelper from "./modules/Helper/MultistepCheckoutHelper"; + getCurrentPaymentMethod, + ORDER_BUTTON_SELECTOR, + PaymentMethods, +} from './modules/Helper/CheckoutMethodState'; +import { setVisibleByClass } from './modules/Helper/Hiding'; +import { isChangePaymentPage } from './modules/Helper/Subscriptions'; +import FreeTrialHandler from './modules/ActionHandler/FreeTrialHandler'; +import MultistepCheckoutHelper from './modules/Helper/MultistepCheckoutHelper'; import FormSaver from './modules/Helper/FormSaver'; -import FormValidator from "./modules/Helper/FormValidator"; -import {loadPaypalScript} from "./modules/Helper/ScriptLoading"; -import buttonModuleWatcher from "./modules/ButtonModuleWatcher"; -import MessagesBootstrap from "./modules/ContextBootstrap/MessagesBootstap"; -import {apmButtonsInit} from "./modules/Helper/ApmButtons"; +import FormValidator from './modules/Helper/FormValidator'; +import { loadPaypalScript } from './modules/Helper/ScriptLoading'; +import buttonModuleWatcher from './modules/ButtonModuleWatcher'; +import MessagesBootstrap from './modules/ContextBootstrap/MessagesBootstap'; +import { apmButtonsInit } from './modules/Helper/ApmButtons'; // TODO: could be a good idea to have a separate spinner for each gateway, // but I think we care mainly about the script loading, so one spinner should be enough. -const buttonsSpinner = new Spinner(document.querySelector('.ppc-button-wrapper')); -const cardsSpinner = new Spinner('#ppcp-hosted-fields'); +const buttonsSpinner = new Spinner( + document.querySelector( '.ppc-button-wrapper' ) +); +const cardsSpinner = new Spinner( '#ppcp-hosted-fields' ); const bootstrap = () => { - const checkoutFormSelector = 'form.woocommerce-checkout'; + const checkoutFormSelector = 'form.woocommerce-checkout'; - const context = PayPalCommerceGateway.context; + const context = PayPalCommerceGateway.context; - const errorHandler = new ErrorHandler( - PayPalCommerceGateway.labels.error.generic, - document.querySelector(checkoutFormSelector) ?? document.querySelector('.woocommerce-notices-wrapper') - ); - const spinner = new Spinner(); + const errorHandler = new ErrorHandler( + PayPalCommerceGateway.labels.error.generic, + document.querySelector( checkoutFormSelector ) ?? + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + const spinner = new Spinner(); - const formSaver = new FormSaver( - PayPalCommerceGateway.ajax.save_checkout_form.endpoint, - PayPalCommerceGateway.ajax.save_checkout_form.nonce, - ); + const formSaver = new FormSaver( + PayPalCommerceGateway.ajax.save_checkout_form.endpoint, + PayPalCommerceGateway.ajax.save_checkout_form.nonce + ); - const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ? - new FormValidator( - PayPalCommerceGateway.ajax.validate_checkout.endpoint, - PayPalCommerceGateway.ajax.validate_checkout.nonce, - ) : null; + const formValidator = + PayPalCommerceGateway.early_checkout_validation_enabled + ? new FormValidator( + PayPalCommerceGateway.ajax.validate_checkout.endpoint, + PayPalCommerceGateway.ajax.validate_checkout.nonce + ) + : null; - const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, formValidator, spinner, errorHandler); + const freeTrialHandler = new FreeTrialHandler( + PayPalCommerceGateway, + checkoutFormSelector, + formSaver, + formValidator, + spinner, + errorHandler + ); - new MultistepCheckoutHelper(checkoutFormSelector); + new MultistepCheckoutHelper( checkoutFormSelector ); - jQuery('form.woocommerce-checkout input').on('keydown', e => { - if (e.key === 'Enter' && [ - PaymentMethods.PAYPAL, - PaymentMethods.CARDS, - PaymentMethods.CARD_BUTTON, - ].includes(getCurrentPaymentMethod())) { - e.preventDefault(); - } - }); + jQuery( 'form.woocommerce-checkout input' ).on( 'keydown', ( e ) => { + if ( + e.key === 'Enter' && + [ + PaymentMethods.PAYPAL, + PaymentMethods.CARDS, + PaymentMethods.CARD_BUTTON, + ].includes( getCurrentPaymentMethod() ) + ) { + e.preventDefault(); + } + } ); - const hasMessages = () => { - return PayPalCommerceGateway.messages.is_hidden === false - && document.querySelector(PayPalCommerceGateway.messages.wrapper); - } + const hasMessages = () => { + return ( + PayPalCommerceGateway.messages.is_hidden === false && + document.querySelector( PayPalCommerceGateway.messages.wrapper ) + ); + }; - const doBasicCheckoutValidation = () => { - if (PayPalCommerceGateway.basic_checkout_validation_enabled) { - // A quick fix to get the errors about empty form fields before attempting PayPal order, - // it should solve #513 for most of the users, but it is not a proper solution. - // Currently it is disabled by default because a better solution is now implemented - // (see woocommerce_paypal_payments_basic_checkout_validation_enabled, - // woocommerce_paypal_payments_early_wc_checkout_validation_enabled filters). - const invalidFields = Array.from(jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible')); - if (invalidFields.length) { - const billingFieldsContainer = document.querySelector('.woocommerce-billing-fields'); - const shippingFieldsContainer = document.querySelector('.woocommerce-shipping-fields'); + const doBasicCheckoutValidation = () => { + if ( PayPalCommerceGateway.basic_checkout_validation_enabled ) { + // A quick fix to get the errors about empty form fields before attempting PayPal order, + // it should solve #513 for most of the users, but it is not a proper solution. + // Currently it is disabled by default because a better solution is now implemented + // (see woocommerce_paypal_payments_basic_checkout_validation_enabled, + // woocommerce_paypal_payments_early_wc_checkout_validation_enabled filters). + const invalidFields = Array.from( + jQuery( + 'form.woocommerce-checkout .validate-required.woocommerce-invalid:visible' + ) + ); + if ( invalidFields.length ) { + const billingFieldsContainer = document.querySelector( + '.woocommerce-billing-fields' + ); + const shippingFieldsContainer = document.querySelector( + '.woocommerce-shipping-fields' + ); - const nameMessageMap = PayPalCommerceGateway.labels.error.required.elements; - const messages = invalidFields.map(el => { - const name = el.querySelector('[name]')?.getAttribute('name'); - if (name && name in nameMessageMap) { - return nameMessageMap[name]; - } - let label = el.querySelector('label').textContent - .replaceAll('*', '') - .trim(); - if (billingFieldsContainer?.contains(el)) { - label = PayPalCommerceGateway.labels.billing_field.replace('%s', label); - } - if (shippingFieldsContainer?.contains(el)) { - label = PayPalCommerceGateway.labels.shipping_field.replace('%s', label); - } - return PayPalCommerceGateway.labels.error.required.field - .replace('%s', `${label}`) - }).filter(s => s.length > 2); + const nameMessageMap = + PayPalCommerceGateway.labels.error.required.elements; + const messages = invalidFields + .map( ( el ) => { + const name = el + .querySelector( '[name]' ) + ?.getAttribute( 'name' ); + if ( name && name in nameMessageMap ) { + return nameMessageMap[ name ]; + } + let label = el + .querySelector( 'label' ) + .textContent.replaceAll( '*', '' ) + .trim(); + if ( billingFieldsContainer?.contains( el ) ) { + label = + PayPalCommerceGateway.labels.billing_field.replace( + '%s', + label + ); + } + if ( shippingFieldsContainer?.contains( el ) ) { + label = + PayPalCommerceGateway.labels.shipping_field.replace( + '%s', + label + ); + } + return PayPalCommerceGateway.labels.error.required.field.replace( + '%s', + `${ label }` + ); + } ) + .filter( ( s ) => s.length > 2 ); - errorHandler.clear(); - if (messages.length) { - errorHandler.messages(messages); - } else { - errorHandler.message(PayPalCommerceGateway.labels.error.required.generic); - } + errorHandler.clear(); + if ( messages.length ) { + errorHandler.messages( messages ); + } else { + errorHandler.message( + PayPalCommerceGateway.labels.error.required.generic + ); + } - return false; - } - } - return true; - }; + return false; + } + } + return true; + }; - const onCardFieldsBeforeSubmit = () => { - return doBasicCheckoutValidation(); - }; + const onCardFieldsBeforeSubmit = () => { + return doBasicCheckoutValidation(); + }; - const onSmartButtonClick = async (data, actions) => { - window.ppcpFundingSource = data.fundingSource; - const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input'); - requiredFields.each((i, input) => { - jQuery(input).trigger('validate'); - }); + const onSmartButtonClick = async ( data, actions ) => { + window.ppcpFundingSource = data.fundingSource; + const requiredFields = jQuery( + 'form.woocommerce-checkout .validate-required:visible :input' + ); + requiredFields.each( ( i, input ) => { + jQuery( input ).trigger( 'validate' ); + } ); - if (!doBasicCheckoutValidation()) { - return actions.reject(); - } + if ( ! doBasicCheckoutValidation() ) { + return actions.reject(); + } - const form = document.querySelector(checkoutFormSelector); - if (form) { - jQuery('#ppcp-funding-source-form-input').remove(); - form.insertAdjacentHTML( - 'beforeend', - `` - ) - } + const form = document.querySelector( checkoutFormSelector ); + if ( form ) { + jQuery( '#ppcp-funding-source-form-input' ).remove(); + form.insertAdjacentHTML( + 'beforeend', + `` + ); + } - const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; - if ( - isFreeTrial - && data.fundingSource !== 'card' - && ! PayPalCommerceGateway.subscription_plan_id - && ! PayPalCommerceGateway.vault_v3_enabled - ) { - freeTrialHandler.handle(); - return actions.reject(); - } + const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; + if ( + isFreeTrial && + data.fundingSource !== 'card' && + ! PayPalCommerceGateway.subscription_plan_id && + ! PayPalCommerceGateway.vault_v3_enabled + ) { + freeTrialHandler.handle(); + return actions.reject(); + } - if (context === 'checkout') { - try { - await formSaver.save(form); - } catch (error) { - console.error(error); - } - } - }; + if ( context === 'checkout' ) { + try { + await formSaver.save( form ); + } catch ( error ) { + console.error( error ); + } + } + }; - const onSmartButtonsInit = () => { - jQuery(document).trigger('ppcp-smart-buttons-init', this); - buttonsSpinner.unblock(); - }; + const onSmartButtonsInit = () => { + jQuery( document ).trigger( 'ppcp-smart-buttons-init', this ); + buttonsSpinner.unblock(); + }; - let creditCardRenderer = new HostedFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner); - if (typeof paypal.CardFields !== 'undefined') { - creditCardRenderer = new CardFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner, onCardFieldsBeforeSubmit); - } + let creditCardRenderer = new HostedFieldsRenderer( + PayPalCommerceGateway, + errorHandler, + spinner + ); + if ( typeof paypal.CardFields !== 'undefined' ) { + creditCardRenderer = new CardFieldsRenderer( + PayPalCommerceGateway, + errorHandler, + spinner, + onCardFieldsBeforeSubmit + ); + } - const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit); - const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages); + const renderer = new Renderer( + creditCardRenderer, + PayPalCommerceGateway, + onSmartButtonClick, + onSmartButtonsInit + ); + const messageRenderer = new MessageRenderer( + PayPalCommerceGateway.messages + ); - if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') { - const miniCartBootstrap = new MiniCartBootstap( - PayPalCommerceGateway, - renderer, - errorHandler, - ); + if ( PayPalCommerceGateway.mini_cart_buttons_enabled === '1' ) { + const miniCartBootstrap = new MiniCartBootstap( + PayPalCommerceGateway, + renderer, + errorHandler + ); - miniCartBootstrap.init(); - buttonModuleWatcher.registerContextBootstrap('mini-cart', miniCartBootstrap); - } + miniCartBootstrap.init(); + buttonModuleWatcher.registerContextBootstrap( + 'mini-cart', + miniCartBootstrap + ); + } - if ( - context === 'product' - && ( - PayPalCommerceGateway.single_product_buttons_enabled === '1' - || hasMessages() - ) - ) { - const singleProductBootstrap = new SingleProductBootstap( - PayPalCommerceGateway, - renderer, - errorHandler, - ); + if ( + context === 'product' && + ( PayPalCommerceGateway.single_product_buttons_enabled === '1' || + hasMessages() ) + ) { + const singleProductBootstrap = new SingleProductBootstap( + PayPalCommerceGateway, + renderer, + errorHandler + ); - singleProductBootstrap.init(); - buttonModuleWatcher.registerContextBootstrap('product', singleProductBootstrap); - } + singleProductBootstrap.init(); + buttonModuleWatcher.registerContextBootstrap( + 'product', + singleProductBootstrap + ); + } - if (context === 'cart') { - const cartBootstrap = new CartBootstrap( - PayPalCommerceGateway, - renderer, - errorHandler, - ); + if ( context === 'cart' ) { + const cartBootstrap = new CartBootstrap( + PayPalCommerceGateway, + renderer, + errorHandler + ); - cartBootstrap.init(); - buttonModuleWatcher.registerContextBootstrap('cart', cartBootstrap); - } + cartBootstrap.init(); + buttonModuleWatcher.registerContextBootstrap( 'cart', cartBootstrap ); + } - if (context === 'checkout') { - const checkoutBootstap = new CheckoutBootstap( - PayPalCommerceGateway, - renderer, - spinner, - errorHandler, - ); + if ( context === 'checkout' ) { + const checkoutBootstap = new CheckoutBootstap( + PayPalCommerceGateway, + renderer, + spinner, + errorHandler + ); - checkoutBootstap.init(); - buttonModuleWatcher.registerContextBootstrap('checkout', checkoutBootstap); - } + checkoutBootstap.init(); + buttonModuleWatcher.registerContextBootstrap( + 'checkout', + checkoutBootstap + ); + } - if (context === 'pay-now' ) { - const payNowBootstrap = new PayNowBootstrap( - PayPalCommerceGateway, - renderer, - spinner, - errorHandler, - ); - payNowBootstrap.init(); - buttonModuleWatcher.registerContextBootstrap('pay-now', payNowBootstrap); - } + if ( context === 'pay-now' ) { + const payNowBootstrap = new PayNowBootstrap( + PayPalCommerceGateway, + renderer, + spinner, + errorHandler + ); + payNowBootstrap.init(); + buttonModuleWatcher.registerContextBootstrap( + 'pay-now', + payNowBootstrap + ); + } - const messagesBootstrap = new MessagesBootstrap( - PayPalCommerceGateway, - messageRenderer, - ); - messagesBootstrap.init(); + const messagesBootstrap = new MessagesBootstrap( + PayPalCommerceGateway, + messageRenderer + ); + messagesBootstrap.init(); - apmButtonsInit(PayPalCommerceGateway); + apmButtonsInit( PayPalCommerceGateway ); }; -document.addEventListener( - 'DOMContentLoaded', - () => { - if (!typeof (PayPalCommerceGateway)) { - console.error('PayPal button could not be configured.'); - return; - } +document.addEventListener( 'DOMContentLoaded', () => { + if ( ! typeof PayPalCommerceGateway ) { + console.error( 'PayPal button could not be configured.' ); + return; + } - if ( - PayPalCommerceGateway.context !== 'checkout' - && PayPalCommerceGateway.data_client_id.user === 0 - && PayPalCommerceGateway.data_client_id.has_subscriptions - ) { - return; - } + if ( + PayPalCommerceGateway.context !== 'checkout' && + PayPalCommerceGateway.data_client_id.user === 0 && + PayPalCommerceGateway.data_client_id.has_subscriptions + ) { + return; + } - const paypalButtonGatewayIds = [ - PaymentMethods.PAYPAL, - ...Object.entries(PayPalCommerceGateway.separate_buttons).map(([k, data]) => data.id), - ] + const paypalButtonGatewayIds = [ + PaymentMethods.PAYPAL, + ...Object.entries( PayPalCommerceGateway.separate_buttons ).map( + ( [ k, data ] ) => data.id + ), + ]; - // Sometimes PayPal script takes long time to load, - // so we additionally hide the standard order button here to avoid failed orders. - // Normally it is hidden later after the script load. - const hideOrderButtonIfPpcpGateway = () => { - // only in checkout and pay now page, otherwise it may break things (e.g. payment via product page), - // and also the loading spinner may look weird on other pages - if ( - !['checkout', 'pay-now'].includes(PayPalCommerceGateway.context) - || isChangePaymentPage() - || (PayPalCommerceGateway.is_free_trial_cart && PayPalCommerceGateway.vaulted_paypal_email !== '') - ) { - return; - } + // Sometimes PayPal script takes long time to load, + // so we additionally hide the standard order button here to avoid failed orders. + // Normally it is hidden later after the script load. + const hideOrderButtonIfPpcpGateway = () => { + // only in checkout and pay now page, otherwise it may break things (e.g. payment via product page), + // and also the loading spinner may look weird on other pages + if ( + ! [ 'checkout', 'pay-now' ].includes( + PayPalCommerceGateway.context + ) || + isChangePaymentPage() || + ( PayPalCommerceGateway.is_free_trial_cart && + PayPalCommerceGateway.vaulted_paypal_email !== '' ) + ) { + return; + } - const currentPaymentMethod = getCurrentPaymentMethod(); - const isPaypalButton = paypalButtonGatewayIds.includes(currentPaymentMethod); - const isCards = currentPaymentMethod === PaymentMethods.CARDS; + const currentPaymentMethod = getCurrentPaymentMethod(); + const isPaypalButton = + paypalButtonGatewayIds.includes( currentPaymentMethod ); + const isCards = currentPaymentMethod === PaymentMethods.CARDS; - setVisibleByClass(ORDER_BUTTON_SELECTOR, !isPaypalButton && !isCards, 'ppcp-hidden'); + setVisibleByClass( + ORDER_BUTTON_SELECTOR, + ! isPaypalButton && ! isCards, + 'ppcp-hidden' + ); - if (isPaypalButton) { - // stopped after the first rendering of the buttons, in onInit - buttonsSpinner.block(); - } else { - buttonsSpinner.unblock(); - } + if ( isPaypalButton ) { + // stopped after the first rendering of the buttons, in onInit + buttonsSpinner.block(); + } else { + buttonsSpinner.unblock(); + } - if (isCards) { - cardsSpinner.block(); - } else { - cardsSpinner.unblock(); - } - } + if ( isCards ) { + cardsSpinner.block(); + } else { + cardsSpinner.unblock(); + } + }; - jQuery(document).on('hosted_fields_loaded', () => { - cardsSpinner.unblock(); - }); + jQuery( document ).on( 'hosted_fields_loaded', () => { + cardsSpinner.unblock(); + } ); - let bootstrapped = false; - let failed = false; + let bootstrapped = false; + let failed = false; - hideOrderButtonIfPpcpGateway(); + hideOrderButtonIfPpcpGateway(); - jQuery(document.body).on('updated_checkout payment_method_selected', () => { - if (bootstrapped || failed) { - return; - } + jQuery( document.body ).on( + 'updated_checkout payment_method_selected', + () => { + if ( bootstrapped || failed ) { + return; + } - hideOrderButtonIfPpcpGateway(); - }); + hideOrderButtonIfPpcpGateway(); + } + ); - loadPaypalScript(PayPalCommerceGateway, () => { - bootstrapped = true; + loadPaypalScript( + PayPalCommerceGateway, + () => { + bootstrapped = true; - bootstrap(); - }, () => { - failed = true; + bootstrap(); + }, + () => { + failed = true; - setVisibleByClass(ORDER_BUTTON_SELECTOR, true, 'ppcp-hidden'); - buttonsSpinner.unblock(); - cardsSpinner.unblock(); - }); - }, -); + setVisibleByClass( ORDER_BUTTON_SELECTOR, true, 'ppcp-hidden' ); + buttonsSpinner.unblock(); + cardsSpinner.unblock(); + } + ); +} ); diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js index ce580fbb8..67fe573f8 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js @@ -1,89 +1,99 @@ import onApprove from '../OnApproveHandler/onApproveForContinue.js'; -import {payerData} from "../Helper/PayerData"; -import {PaymentMethods} from "../Helper/CheckoutMethodState"; +import { payerData } from '../Helper/PayerData'; +import { PaymentMethods } from '../Helper/CheckoutMethodState'; class CartActionHandler { + constructor( config, errorHandler ) { + this.config = config; + this.errorHandler = errorHandler; + } - constructor(config, errorHandler) { - this.config = config; - this.errorHandler = errorHandler; - } + subscriptionsConfiguration( subscription_plan_id ) { + return { + createSubscription: ( data, actions ) => { + return actions.subscription.create( { + plan_id: subscription_plan_id, + } ); + }, + onApprove: ( data, actions ) => { + fetch( this.config.ajax.approve_subscription.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.approve_subscription.nonce, + order_id: data.orderID, + subscription_id: data.subscriptionID, + should_create_wc_order: + ! context.config.vaultingEnabled || + data.paymentSource !== 'venmo', + } ), + } ) + .then( ( res ) => { + return res.json(); + } ) + .then( ( data ) => { + if ( ! data.success ) { + console.log( data ); + throw Error( data.data.message ); + } - subscriptionsConfiguration(subscription_plan_id) { - return { - createSubscription: (data, actions) => { - return actions.subscription.create({ - 'plan_id': subscription_plan_id - }); - }, - onApprove: (data, actions) => { - fetch(this.config.ajax.approve_subscription.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.approve_subscription.nonce, - order_id: data.orderID, - subscription_id: data.subscriptionID, - should_create_wc_order: !context.config.vaultingEnabled || data.paymentSource !== 'venmo' - }) - }).then((res)=>{ - return res.json(); - }).then((data) => { - if (!data.success) { - console.log(data) - throw Error(data.data.message); - } + const orderReceivedUrl = data.data?.order_received_url; - let orderReceivedUrl = data.data?.order_received_url + location.href = orderReceivedUrl + ? orderReceivedUrl + : context.config.redirect; + } ); + }, + onError: ( err ) => { + console.error( err ); + }, + }; + } - location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect; - }); - }, - onError: (err) => { - console.error(err); - } - } - } + configuration() { + const createOrder = ( data, actions ) => { + const payer = payerData(); + const bnCode = + typeof this.config.bn_codes[ this.config.context ] !== + 'undefined' + ? this.config.bn_codes[ this.config.context ] + : ''; + return fetch( this.config.ajax.create_order.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.create_order.nonce, + purchase_units: [], + payment_method: PaymentMethods.PAYPAL, + funding_source: window.ppcpFundingSource, + bn_code: bnCode, + payer, + context: this.config.context, + } ), + } ) + .then( function ( res ) { + return res.json(); + } ) + .then( function ( data ) { + if ( ! data.success ) { + console.error( data ); + throw Error( data.data.message ); + } + return data.data.id; + } ); + }; - configuration() { - const createOrder = (data, actions) => { - const payer = payerData(); - const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ? - this.config.bn_codes[this.config.context] : ''; - return fetch(this.config.ajax.create_order.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.create_order.nonce, - purchase_units: [], - payment_method: PaymentMethods.PAYPAL, - funding_source: window.ppcpFundingSource, - bn_code:bnCode, - payer, - context:this.config.context - }), - }).then(function(res) { - return res.json(); - }).then(function(data) { - if (!data.success) { - console.error(data); - throw Error(data.data.message); - } - return data.data.id; - }); - }; - - return { - createOrder, - onApprove: onApprove(this, this.errorHandler), - onError: (error) => { - this.errorHandler.genericError(); - } - }; - } + return { + createOrder, + onApprove: onApprove( this, this.errorHandler ), + onError: ( error ) => { + this.errorHandler.genericError(); + }, + }; + } } export default CartActionHandler; diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index 923c0b772..a93a24212 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -1,197 +1,233 @@ import 'formdata-polyfill'; import onApprove from '../OnApproveHandler/onApproveForPayNow.js'; -import {payerData} from "../Helper/PayerData"; -import {getCurrentPaymentMethod} from "../Helper/CheckoutMethodState"; -import validateCheckoutForm from "../Helper/CheckoutFormValidation"; +import { payerData } from '../Helper/PayerData'; +import { getCurrentPaymentMethod } from '../Helper/CheckoutMethodState'; +import validateCheckoutForm from '../Helper/CheckoutFormValidation'; class CheckoutActionHandler { + constructor( config, errorHandler, spinner ) { + this.config = config; + this.errorHandler = errorHandler; + this.spinner = spinner; + } - constructor(config, errorHandler, spinner) { - this.config = config; - this.errorHandler = errorHandler; - this.spinner = spinner; - } + subscriptionsConfiguration( subscription_plan_id ) { + return { + createSubscription: async ( data, actions ) => { + try { + await validateCheckoutForm( this.config ); + } catch ( error ) { + throw { type: 'form-validation-error' }; + } - subscriptionsConfiguration(subscription_plan_id) { - return { - createSubscription: async (data, actions) => { - try { - await validateCheckoutForm(this.config); - } catch (error) { - throw {type: 'form-validation-error'}; - } + return actions.subscription.create( { + plan_id: subscription_plan_id, + } ); + }, + onApprove: ( data, actions ) => { + fetch( this.config.ajax.approve_subscription.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.approve_subscription.nonce, + order_id: data.orderID, + subscription_id: data.subscriptionID, + } ), + } ) + .then( ( res ) => { + return res.json(); + } ) + .then( ( data ) => { + document.querySelector( '#place_order' ).click(); + } ); + }, + onError: ( err ) => { + console.error( err ); + }, + }; + } - return actions.subscription.create({ - 'plan_id': subscription_plan_id - }); - }, - onApprove: (data, actions) => { - fetch(this.config.ajax.approve_subscription.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.approve_subscription.nonce, - order_id: data.orderID, - subscription_id: data.subscriptionID - }) - }).then((res)=>{ - return res.json(); - }).then((data) => { - document.querySelector('#place_order').click(); - }); - }, - onError: (err) => { - console.error(err); - } - } - } + configuration() { + const spinner = this.spinner; + const createOrder = ( data, actions ) => { + const payer = payerData(); + const bnCode = + typeof this.config.bn_codes[ this.config.context ] !== + 'undefined' + ? this.config.bn_codes[ this.config.context ] + : ''; - configuration() { - const spinner = this.spinner; - const createOrder = (data, actions) => { - const payer = payerData(); - const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ? - this.config.bn_codes[this.config.context] : ''; + const errorHandler = this.errorHandler; - const errorHandler = this.errorHandler; + const formSelector = + this.config.context === 'checkout' + ? 'form.checkout' + : 'form#order_review'; + const formData = new FormData( + document.querySelector( formSelector ) + ); - const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review'; - const formData = new FormData(document.querySelector(formSelector)); + const createaccount = jQuery( '#createaccount' ).is( ':checked' ) + ? true + : false; - const createaccount = jQuery('#createaccount').is(":checked") ? true : false; + const paymentMethod = getCurrentPaymentMethod(); + const fundingSource = window.ppcpFundingSource; - const paymentMethod = getCurrentPaymentMethod(); - const fundingSource = window.ppcpFundingSource; + const savePaymentMethod = !! document.getElementById( + 'wc-ppcp-credit-card-gateway-new-payment-method' + )?.checked; - const savePaymentMethod = !!document.getElementById('wc-ppcp-credit-card-gateway-new-payment-method')?.checked; + return fetch( this.config.ajax.create_order.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.create_order.nonce, + payer, + bn_code: bnCode, + context: this.config.context, + order_id: this.config.order_id, + payment_method: paymentMethod, + funding_source: fundingSource, + // send as urlencoded string to handle complex fields via PHP functions the same as normal form submit + form_encoded: new URLSearchParams( formData ).toString(), + createaccount, + save_payment_method: savePaymentMethod, + } ), + } ) + .then( function ( res ) { + return res.json(); + } ) + .then( function ( data ) { + if ( ! data.success ) { + spinner.unblock(); + //handle both messages sent from Woocommerce (data.messages) and this plugin (data.data.message) + if ( typeof data.messages !== 'undefined' ) { + const domParser = new DOMParser(); + errorHandler.appendPreparedErrorMessageElement( + domParser + .parseFromString( + data.messages, + 'text/html' + ) + .querySelector( 'ul' ) + ); + } else { + errorHandler.clear(); - return fetch(this.config.ajax.create_order.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.create_order.nonce, - payer, - bn_code:bnCode, - context:this.config.context, - order_id:this.config.order_id, - payment_method: paymentMethod, - funding_source: fundingSource, - // send as urlencoded string to handle complex fields via PHP functions the same as normal form submit - form_encoded: new URLSearchParams(formData).toString(), - createaccount: createaccount, - save_payment_method: savePaymentMethod - }) - }).then(function (res) { - return res.json(); - }).then(function (data) { - if (!data.success) { - spinner.unblock(); - //handle both messages sent from Woocommerce (data.messages) and this plugin (data.data.message) - if (typeof(data.messages) !== 'undefined' ) - { - const domParser = new DOMParser(); - errorHandler.appendPreparedErrorMessageElement( - domParser.parseFromString(data.messages, 'text/html') - .querySelector('ul') - ); - } else { - errorHandler.clear(); + if ( data.data.refresh ) { + jQuery( document.body ).trigger( + 'update_checkout' + ); + } - if (data.data.refresh) { - jQuery( document.body ).trigger( 'update_checkout' ); - } + if ( data.data.errors?.length > 0 ) { + errorHandler.messages( data.data.errors ); + } else if ( data.data.details?.length > 0 ) { + errorHandler.message( + data.data.details + .map( + ( d ) => + `${ d.issue } ${ d.description }` + ) + .join( '
' ) + ); + } else { + errorHandler.message( data.data.message ); + } - if (data.data.errors?.length > 0) { - errorHandler.messages(data.data.errors); - } else if (data.data.details?.length > 0) { - errorHandler.message(data.data.details.map(d => `${d.issue} ${d.description}`).join('
')); - } else { - errorHandler.message(data.data.message); - } + // fire WC event for other plugins + jQuery( document.body ).trigger( 'checkout_error', [ + errorHandler.currentHtml(), + ] ); + } - // fire WC event for other plugins - jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] ); - } + throw { type: 'create-order-error', data: data.data }; + } + const input = document.createElement( 'input' ); + input.setAttribute( 'type', 'hidden' ); + input.setAttribute( 'name', 'ppcp-resume-order' ); + input.setAttribute( 'value', data.data.custom_id ); + document.querySelector( formSelector ).appendChild( input ); + return data.data.id; + } ); + }; + return { + createOrder, + onApprove: onApprove( this, this.errorHandler, this.spinner ), + onCancel: () => { + spinner.unblock(); + }, + onError: ( err ) => { + console.error( err ); + spinner.unblock(); - throw {type: 'create-order-error', data: data.data}; - } - const input = document.createElement('input'); - input.setAttribute('type', 'hidden'); - input.setAttribute('name', 'ppcp-resume-order'); - input.setAttribute('value', data.data.custom_id); - document.querySelector(formSelector).appendChild(input); - return data.data.id; - }); - } - return { - createOrder, - onApprove:onApprove(this, this.errorHandler, this.spinner), - onCancel: () => { - spinner.unblock(); - }, - onError: (err) => { - console.error(err); - spinner.unblock(); + if ( err && err.type === 'create-order-error' ) { + return; + } - if (err && err.type === 'create-order-error') { - return; - } + this.errorHandler.genericError(); + }, + }; + } - this.errorHandler.genericError(); - } - } - } + addPaymentMethodConfiguration() { + return { + createVaultSetupToken: async () => { + const response = await fetch( + this.config.ajax.create_setup_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.config.ajax.create_setup_token.nonce, + } ), + } + ); - addPaymentMethodConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch(this.config.ajax.create_setup_token.endpoint, { - method: "POST", - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nonce: this.config.ajax.create_setup_token.nonce, - }) - }); + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } - const result = await response.json() - if (result.data.id) { - return result.data.id - } + console.error( result ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + this.config.ajax.create_payment_token_for_guest.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.config.ajax + .create_payment_token_for_guest.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); - console.error(result) - }, - onApprove: async ({vaultSetupToken}) => { - const response = await fetch(this.config.ajax.create_payment_token_for_guest.endpoint, { - method: "POST", - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nonce: this.config.ajax.create_payment_token_for_guest.nonce, - vault_setup_token: vaultSetupToken, - }) - }) + const result = await response.json(); + if ( result.success === true ) { + document.querySelector( '#place_order' ).click(); + return; + } - const result = await response.json(); - if (result.success === true) { - document.querySelector('#place_order').click() - return; - } - - console.error(result) - }, - onError: (error) => { - console.error(error) - } - } - } + console.error( result ); + }, + onError: ( error ) => { + console.error( error ); + }, + }; + } } export default CheckoutActionHandler; diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js index 5486576ff..e2e4b7d3e 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js @@ -1,80 +1,85 @@ class FreeTrialHandler { - /** - * @param config - * @param formSelector - * @param {FormSaver} formSaver - * @param {FormValidator|null} formValidator - * @param {Spinner} spinner - * @param {ErrorHandler} errorHandler - */ - constructor( - config, - formSelector, - formSaver, - formValidator, - spinner, - errorHandler - ) { - this.config = config; - this.formSelector = formSelector; - this.formSaver = formSaver; - this.formValidator = formValidator; - this.spinner = spinner; - this.errorHandler = errorHandler; - } + /** + * @param config + * @param formSelector + * @param {FormSaver} formSaver + * @param {FormValidator|null} formValidator + * @param {Spinner} spinner + * @param {ErrorHandler} errorHandler + */ + constructor( + config, + formSelector, + formSaver, + formValidator, + spinner, + errorHandler + ) { + this.config = config; + this.formSelector = formSelector; + this.formSaver = formSaver; + this.formValidator = formValidator; + this.spinner = spinner; + this.errorHandler = errorHandler; + } - async handle() - { - this.spinner.block(); + async handle() { + this.spinner.block(); - try { - await this.formSaver.save(document.querySelector(this.formSelector)); - } catch (error) { - console.error(error); - } + try { + await this.formSaver.save( + document.querySelector( this.formSelector ) + ); + } catch ( error ) { + console.error( error ); + } - try { - if (this.formValidator) { - try { - const errors = await this.formValidator.validate(document.querySelector(this.formSelector)); - if (errors.length > 0) { - this.spinner.unblock(); - this.errorHandler.messages(errors); + try { + if ( this.formValidator ) { + try { + const errors = await this.formValidator.validate( + document.querySelector( this.formSelector ) + ); + if ( errors.length > 0 ) { + this.spinner.unblock(); + this.errorHandler.messages( errors ); - // fire WC event for other plugins - jQuery( document.body ).trigger( 'checkout_error' , [ this.errorHandler.currentHtml() ] ); + // fire WC event for other plugins + jQuery( document.body ).trigger( 'checkout_error', [ + this.errorHandler.currentHtml(), + ] ); - return; - } - } catch (error) { - console.error(error); - } - } + return; + } + } catch ( error ) { + console.error( error ); + } + } - const res = await fetch(this.config.ajax.vault_paypal.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.vault_paypal.nonce, - return_url: location.href, - }), - }); + const res = await fetch( this.config.ajax.vault_paypal.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.vault_paypal.nonce, + return_url: location.href, + } ), + } ); - const data = await res.json(); + const data = await res.json(); - if (!data.success) { - throw Error(data.data.message); - } + if ( ! data.success ) { + throw Error( data.data.message ); + } - location.href = data.data.approve_link; - } catch (error) { - this.spinner.unblock(); - console.error(error); - this.errorHandler.message(data.data.message); - } - } + location.href = data.data.approve_link; + } catch ( error ) { + this.spinner.unblock(); + console.error( error ); + this.errorHandler.message( data.data.message ); + } + } } export default FreeTrialHandler; diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js index 9590e9a57..8b1d7bf75 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js @@ -1,229 +1,247 @@ import Product from '../Entity/Product'; -import BookingProduct from "../Entity/BookingProduct"; +import BookingProduct from '../Entity/BookingProduct'; import onApprove from '../OnApproveHandler/onApproveForContinue'; -import {payerData} from "../Helper/PayerData"; -import {PaymentMethods} from "../Helper/CheckoutMethodState"; -import CartHelper from "../Helper/CartHelper"; -import FormHelper from "../Helper/FormHelper"; +import { payerData } from '../Helper/PayerData'; +import { PaymentMethods } from '../Helper/CheckoutMethodState'; +import CartHelper from '../Helper/CartHelper'; +import FormHelper from '../Helper/FormHelper'; class SingleProductActionHandler { + constructor( config, updateCart, formElement, errorHandler ) { + this.config = config; + this.updateCart = updateCart; + this.formElement = formElement; + this.errorHandler = errorHandler; + this.cartHelper = null; + } - constructor( - config, - updateCart, - formElement, - errorHandler - ) { - this.config = config; - this.updateCart = updateCart; - this.formElement = formElement; - this.errorHandler = errorHandler; - this.cartHelper = null; - } + subscriptionsConfiguration( subscription_plan ) { + return { + createSubscription: ( data, actions ) => { + return actions.subscription.create( { + plan_id: subscription_plan, + } ); + }, + onApprove: ( data, actions ) => { + fetch( this.config.ajax.approve_subscription.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.approve_subscription.nonce, + order_id: data.orderID, + subscription_id: data.subscriptionID, + } ), + } ) + .then( ( res ) => { + return res.json(); + } ) + .then( () => { + const products = this.getSubscriptionProducts(); - subscriptionsConfiguration(subscription_plan) { - return { - createSubscription: (data, actions) => { - return actions.subscription.create({ - 'plan_id': subscription_plan - }); - }, - onApprove: (data, actions) => { - fetch(this.config.ajax.approve_subscription.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.approve_subscription.nonce, - order_id: data.orderID, - subscription_id: data.subscriptionID - }) - }).then((res)=>{ - return res.json(); - }).then(() => { - const products = this.getSubscriptionProducts(); + fetch( this.config.ajax.change_cart.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.change_cart.nonce, + products, + } ), + } ) + .then( ( result ) => { + return result.json(); + } ) + .then( ( result ) => { + if ( ! result.success ) { + console.log( result ); + throw Error( result.data.message ); + } - fetch(this.config.ajax.change_cart.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.change_cart.nonce, - products, - }) - }).then((result) => { - return result.json(); - }).then((result) => { - if (!result.success) { - console.log(result) - throw Error(result.data.message); - } + location.href = this.config.redirect; + } ); + } ); + }, + onError: ( err ) => { + console.error( err ); + }, + }; + } - location.href = this.config.redirect; - }) - }); - }, - onError: (err) => { - console.error(err); - } - } - } + getSubscriptionProducts() { + const id = document.querySelector( '[name="add-to-cart"]' ).value; + return [ new Product( id, 1, this.variations(), this.extraFields() ) ]; + } - getSubscriptionProducts() - { - const id = document.querySelector('[name="add-to-cart"]').value; - return [new Product(id, 1, this.variations(), this.extraFields())]; - } + configuration() { + return { + createOrder: this.createOrder(), + onApprove: onApprove( this, this.errorHandler ), + onError: ( error ) => { + this.refreshMiniCart(); - configuration() - { - return { - createOrder: this.createOrder(), - onApprove: onApprove(this, this.errorHandler), - onError: (error) => { - this.refreshMiniCart(); + if ( this.isBookingProduct() && error.message ) { + this.errorHandler.clear(); + this.errorHandler.message( error.message ); + return; + } + this.errorHandler.genericError(); + }, + onCancel: () => { + // Could be used for every product type, + // but only clean the cart for Booking products for now. + if ( this.isBookingProduct() ) { + this.cleanCart(); + } else { + this.refreshMiniCart(); + } + }, + }; + } - if (this.isBookingProduct() && error.message) { - this.errorHandler.clear(); - this.errorHandler.message(error.message); - return; - } - this.errorHandler.genericError(); - }, - onCancel: () => { - // Could be used for every product type, - // but only clean the cart for Booking products for now. - if (this.isBookingProduct()) { - this.cleanCart(); - } else { - this.refreshMiniCart(); - } - } - } - } + getProducts() { + if ( this.isBookingProduct() ) { + const id = document.querySelector( '[name="add-to-cart"]' ).value; + return [ + new BookingProduct( + id, + 1, + FormHelper.getPrefixedFields( + this.formElement, + 'wc_bookings_field' + ), + this.extraFields() + ), + ]; + } else if ( this.isGroupedProduct() ) { + const products = []; + this.formElement + .querySelectorAll( 'input[type="number"]' ) + .forEach( ( element ) => { + if ( ! element.value ) { + return; + } + const elementName = element + .getAttribute( 'name' ) + .match( /quantity\[([\d]*)\]/ ); + if ( elementName.length !== 2 ) { + return; + } + const id = parseInt( elementName[ 1 ] ); + const quantity = parseInt( element.value ); + products.push( + new Product( id, quantity, null, this.extraFields() ) + ); + } ); + return products; + } + const id = document.querySelector( '[name="add-to-cart"]' ).value; + const qty = document.querySelector( '[name="quantity"]' ).value; + const variations = this.variations(); + return [ new Product( id, qty, variations, this.extraFields() ) ]; + } - getProducts() - { - if ( this.isBookingProduct() ) { - const id = document.querySelector('[name="add-to-cart"]').value; - return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"), this.extraFields())]; - } else if ( this.isGroupedProduct() ) { - const products = []; - this.formElement.querySelectorAll('input[type="number"]').forEach((element) => { - if (! element.value) { - return; - } - const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/); - if (elementName.length !== 2) { - return; - } - const id = parseInt(elementName[1]); - const quantity = parseInt(element.value); - products.push(new Product(id, quantity, null, this.extraFields())); - }) - return products; - } else { - const id = document.querySelector('[name="add-to-cart"]').value; - const qty = document.querySelector('[name="quantity"]').value; - const variations = this.variations(); - return [new Product(id, qty, variations, this.extraFields())]; - } - } + extraFields() { + return FormHelper.getFilteredFields( + this.formElement, + [ 'add-to-cart', 'quantity', 'product_id', 'variation_id' ], + [ 'attribute_', 'wc_bookings_field' ] + ); + } - extraFields() { - return FormHelper.getFilteredFields( - this.formElement, - ['add-to-cart', 'quantity', 'product_id', 'variation_id'], - ['attribute_', 'wc_bookings_field'] - ); - } + createOrder() { + this.cartHelper = null; - createOrder() - { - this.cartHelper = null; + return ( data, actions, options = {} ) => { + this.errorHandler.clear(); - return (data, actions, options = {}) => { - this.errorHandler.clear(); + const onResolve = ( purchase_units ) => { + this.cartHelper = new CartHelper().addFromPurchaseUnits( + purchase_units + ); - const onResolve = (purchase_units) => { - this.cartHelper = (new CartHelper()).addFromPurchaseUnits(purchase_units); + const payer = payerData(); + const bnCode = + typeof this.config.bn_codes[ this.config.context ] !== + 'undefined' + ? this.config.bn_codes[ this.config.context ] + : ''; + return fetch( this.config.ajax.create_order.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.ajax.create_order.nonce, + purchase_units, + payer, + bn_code: bnCode, + payment_method: PaymentMethods.PAYPAL, + funding_source: window.ppcpFundingSource, + context: this.config.context, + } ), + } ) + .then( function ( res ) { + return res.json(); + } ) + .then( function ( data ) { + if ( ! data.success ) { + console.error( data ); + throw Error( data.data.message ); + } + return data.data.id; + } ); + }; - const payer = payerData(); - const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ? - this.config.bn_codes[this.config.context] : ''; - return fetch(this.config.ajax.create_order.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.ajax.create_order.nonce, - purchase_units, - payer, - bn_code:bnCode, - payment_method: PaymentMethods.PAYPAL, - funding_source: window.ppcpFundingSource, - context:this.config.context - }) - }).then(function (res) { - return res.json(); - }).then(function (data) { - if (!data.success) { - console.error(data); - throw Error(data.data.message); - } - return data.data.id; - }); - }; + return this.updateCart.update( + onResolve, + this.getProducts(), + options.updateCartOptions || {} + ); + }; + } - return this.updateCart.update(onResolve, this.getProducts(), options.updateCartOptions || {}); - }; - } + variations() { + if ( ! this.hasVariations() ) { + return null; + } + return [ + ...this.formElement.querySelectorAll( "[name^='attribute_']" ), + ].map( ( element ) => { + return { + value: element.value, + name: element.name, + }; + } ); + } - variations() - { - if (! this.hasVariations()) { - return null; - } - return [...this.formElement.querySelectorAll("[name^='attribute_']")].map( - (element) => { - return { - value:element.value, - name:element.name - } - } - ); - } + hasVariations() { + return this.formElement.classList.contains( 'variations_form' ); + } - hasVariations() - { - return this.formElement.classList.contains('variations_form'); - } + isGroupedProduct() { + return this.formElement.classList.contains( 'grouped_form' ); + } - isGroupedProduct() - { - return this.formElement.classList.contains('grouped_form'); - } + isBookingProduct() { + // detection for "woocommerce-bookings" plugin + return !! this.formElement.querySelector( '.wc-booking-product-id' ); + } - isBookingProduct() - { - // detection for "woocommerce-bookings" plugin - return !!this.formElement.querySelector('.wc-booking-product-id'); - } - - cleanCart() { - this.cartHelper.removeFromCart().then(() => { - this.refreshMiniCart(); - }).catch(error => { - this.refreshMiniCart(); - }); - } - - refreshMiniCart() { - jQuery(document.body).trigger('wc_fragment_refresh'); - } + cleanCart() { + this.cartHelper + .removeFromCart() + .then( () => { + this.refreshMiniCart(); + } ) + .catch( ( error ) => { + this.refreshMiniCart(); + } ); + } + refreshMiniCart() { + jQuery( document.body ).trigger( 'wc_fragment_refresh' ); + } } export default SingleProductActionHandler; diff --git a/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js b/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js index 3fccca178..0df09b9cf 100644 --- a/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js +++ b/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js @@ -1,31 +1,29 @@ - class ButtonModuleWatcher { + constructor() { + this.contextBootstrapRegistry = {}; + this.contextBootstrapWatchers = []; + } - constructor() { - this.contextBootstrapRegistry = {}; - this.contextBootstrapWatchers = []; - } + watchContextBootstrap( callable ) { + this.contextBootstrapWatchers.push( callable ); + Object.values( this.contextBootstrapRegistry ).forEach( callable ); + } - watchContextBootstrap(callable) { - this.contextBootstrapWatchers.push(callable); - Object.values(this.contextBootstrapRegistry).forEach(callable); - } - - registerContextBootstrap(context, handler) { - this.contextBootstrapRegistry[context] = { - context: context, - handler: handler - } - - // Call registered watchers - for (const callable of this.contextBootstrapWatchers) { - callable(this.contextBootstrapRegistry[context]); - } - } + registerContextBootstrap( context, handler ) { + this.contextBootstrapRegistry[ context ] = { + context, + handler, + }; + // Call registered watchers + for ( const callable of this.contextBootstrapWatchers ) { + callable( this.contextBootstrapRegistry[ context ] ); + } + } } window.ppcpResources = window.ppcpResources || {}; -const buttonModuleWatcher = window.ppcpResources['ButtonModuleWatcher'] = window.ppcpResources['ButtonModuleWatcher'] || new ButtonModuleWatcher(); +const buttonModuleWatcher = ( window.ppcpResources.ButtonModuleWatcher = + window.ppcpResources.ButtonModuleWatcher || new ButtonModuleWatcher() ); export default buttonModuleWatcher; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js index bf8ad10ef..645a1306a 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js @@ -1,116 +1,132 @@ import CartActionHandler from '../ActionHandler/CartActionHandler'; -import BootstrapHelper from "../Helper/BootstrapHelper"; +import BootstrapHelper from '../Helper/BootstrapHelper'; class CartBootstrap { - constructor(gateway, renderer, errorHandler) { - this.gateway = gateway; - this.renderer = renderer; - this.errorHandler = errorHandler; + constructor( gateway, renderer, errorHandler ) { + this.gateway = gateway; + this.renderer = renderer; + this.errorHandler = errorHandler; - this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { - this.handleButtonStatus(); - }, true); - } + this.renderer.onButtonsInit( + this.gateway.button.wrapper, + () => { + this.handleButtonStatus(); + }, + true + ); + } - init() { - if (this.shouldRender()) { - this.render(); - this.handleButtonStatus(); - } + init() { + if ( this.shouldRender() ) { + this.render(); + this.handleButtonStatus(); + } - jQuery(document.body).on('updated_cart_totals updated_checkout', () => { - if (this.shouldRender()) { - this.render(); - this.handleButtonStatus(); - } + jQuery( document.body ).on( + 'updated_cart_totals updated_checkout', + () => { + if ( this.shouldRender() ) { + this.render(); + this.handleButtonStatus(); + } - fetch( - this.gateway.ajax.cart_script_params.endpoint, - { - method: 'GET', - credentials: 'same-origin', - } - ) - .then(result => result.json()) - .then(result => { - if (! result.success) { - return; - } + fetch( this.gateway.ajax.cart_script_params.endpoint, { + method: 'GET', + credentials: 'same-origin', + } ) + .then( ( result ) => result.json() ) + .then( ( result ) => { + if ( ! result.success ) { + return; + } - // handle script reload - const newParams = result.data.url_params; - const reloadRequired = JSON.stringify(this.gateway.url_params) !== JSON.stringify(newParams); + // handle script reload + const newParams = result.data.url_params; + const reloadRequired = + JSON.stringify( this.gateway.url_params ) !== + JSON.stringify( newParams ); - if (reloadRequired) { - this.gateway.url_params = newParams; - jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons'); - } + if ( reloadRequired ) { + this.gateway.url_params = newParams; + jQuery( this.gateway.button.wrapper ).trigger( + 'ppcp-reload-buttons' + ); + } - // handle button status - const newData = {}; - if (result.data.button) { - newData.button = result.data.button; - } - if (result.data.messages) { - newData.messages = result.data.messages; - } - if (newData) { - BootstrapHelper.updateScriptData(this, newData); - this.handleButtonStatus(); - } + // handle button status + const newData = {}; + if ( result.data.button ) { + newData.button = result.data.button; + } + if ( result.data.messages ) { + newData.messages = result.data.messages; + } + if ( newData ) { + BootstrapHelper.updateScriptData( this, newData ); + this.handleButtonStatus(); + } - jQuery(document.body).trigger('ppcp_cart_total_updated', [result.data.amount]); - }); - }); - } + jQuery( document.body ).trigger( + 'ppcp_cart_total_updated', + [ result.data.amount ] + ); + } ); + } + ); + } - handleButtonStatus() { - BootstrapHelper.handleButtonStatus(this); - } + handleButtonStatus() { + BootstrapHelper.handleButtonStatus( this ); + } - shouldRender() { - return document.querySelector(this.gateway.button.wrapper) !== null; - } + shouldRender() { + return document.querySelector( this.gateway.button.wrapper ) !== null; + } - shouldEnable() { - return BootstrapHelper.shouldEnable(this); - } + shouldEnable() { + return BootstrapHelper.shouldEnable( this ); + } - render() { - if (!this.shouldRender()) { - return; - } + render() { + if ( ! this.shouldRender() ) { + return; + } - const actionHandler = new CartActionHandler( - PayPalCommerceGateway, - this.errorHandler, - ); + const actionHandler = new CartActionHandler( + PayPalCommerceGateway, + this.errorHandler + ); - if( - PayPalCommerceGateway.data_client_id.has_subscriptions - && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled - ) { - let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id - if(PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== '') { - subscription_plan_id = PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart - } + if ( + PayPalCommerceGateway.data_client_id.has_subscriptions && + PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled + ) { + let subscription_plan_id = + PayPalCommerceGateway.subscription_plan_id; + if ( + PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== + '' + ) { + subscription_plan_id = + PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart; + } - this.renderer.render(actionHandler.subscriptionsConfiguration(subscription_plan_id)); + this.renderer.render( + actionHandler.subscriptionsConfiguration( subscription_plan_id ) + ); - if(!PayPalCommerceGateway.subscription_product_allowed) { - this.gateway.button.is_disabled = true; - this.handleButtonStatus(); - } + if ( ! PayPalCommerceGateway.subscription_product_allowed ) { + this.gateway.button.is_disabled = true; + this.handleButtonStatus(); + } - return; - } + return; + } - this.renderer.render( - actionHandler.configuration() - ); + this.renderer.render( actionHandler.configuration() ); - jQuery(document.body).trigger('ppcp_cart_rendered'); - } + jQuery( document.body ).trigger( 'ppcp_cart_rendered' ); + } } export default CartBootstrap; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index a14fa2f4b..33d1ecfd3 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -1,209 +1,315 @@ import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; -import {setVisible, setVisibleByClass} from '../Helper/Hiding'; +import { setVisible, setVisibleByClass } from '../Helper/Hiding'; import { - getCurrentPaymentMethod, - isSavedCardSelected, ORDER_BUTTON_SELECTOR, - PaymentMethods -} from "../Helper/CheckoutMethodState"; -import BootstrapHelper from "../Helper/BootstrapHelper"; + getCurrentPaymentMethod, + isSavedCardSelected, + ORDER_BUTTON_SELECTOR, + PaymentMethods, +} from '../Helper/CheckoutMethodState'; +import BootstrapHelper from '../Helper/BootstrapHelper'; class CheckoutBootstap { - constructor(gateway, renderer, spinner, errorHandler) { - this.gateway = gateway; - this.renderer = renderer; - this.spinner = spinner; - this.errorHandler = errorHandler; + constructor( gateway, renderer, spinner, errorHandler ) { + this.gateway = gateway; + this.renderer = renderer; + this.spinner = spinner; + this.errorHandler = errorHandler; - this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR; + this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR; - this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { - this.handleButtonStatus(); - }, true); - } + this.renderer.onButtonsInit( + this.gateway.button.wrapper, + () => { + this.handleButtonStatus(); + }, + true + ); + } - init() { - this.render(); - this.handleButtonStatus(); + init() { + this.render(); + this.handleButtonStatus(); - // Unselect saved card. - // WC saves form values, so with our current UI it would be a bit weird - // if the user paid with saved, then after some time tries to pay again, - // but wants to enter a new card, and to do that they have to choose “Select payment” in the list. - jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val()); + // Unselect saved card. + // WC saves form values, so with our current UI it would be a bit weird + // if the user paid with saved, then after some time tries to pay again, + // but wants to enter a new card, and to do that they have to choose “Select payment” in the list. + jQuery( '#saved-credit-card' ).val( + jQuery( '#saved-credit-card option:first' ).val() + ); - jQuery(document.body).on('updated_checkout', () => { - this.render() - this.handleButtonStatus(); + jQuery( document.body ).on( 'updated_checkout', () => { + this.render(); + this.handleButtonStatus(); - if (this.shouldShowMessages() && document.querySelector(this.gateway.messages.wrapper)) { // currently we need amount only for Pay Later - fetch( - this.gateway.ajax.cart_script_params.endpoint, - { - method: 'GET', - credentials: 'same-origin', - } - ) - .then(result => result.json()) - .then(result => { - if (! result.success) { - return; - } + if ( + this.shouldShowMessages() && + document.querySelector( this.gateway.messages.wrapper ) + ) { + // currently we need amount only for Pay Later + fetch( this.gateway.ajax.cart_script_params.endpoint, { + method: 'GET', + credentials: 'same-origin', + } ) + .then( ( result ) => result.json() ) + .then( ( result ) => { + if ( ! result.success ) { + return; + } - jQuery(document.body).trigger('ppcp_checkout_total_updated', [result.data.amount]); - }); - } - }); + jQuery( document.body ).trigger( + 'ppcp_checkout_total_updated', + [ result.data.amount ] + ); + } ); + } + } ); - jQuery(document.body).on('updated_checkout payment_method_selected', () => { - this.updateUi(); - }); + jQuery( document.body ).on( + 'updated_checkout payment_method_selected', + () => { + this.updateUi(); + } + ); - jQuery(document).on('hosted_fields_loaded', () => { - jQuery('#saved-credit-card').on('change', () => { - this.updateUi(); - }) - }); + jQuery( document ).on( 'hosted_fields_loaded', () => { + jQuery( '#saved-credit-card' ).on( 'change', () => { + this.updateUi(); + } ); + } ); - jQuery(document).on('ppcp_should_show_messages', (e, data) => { - if (!this.shouldShowMessages()) { - data.result = false; - } - }); + jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => { + if ( ! this.shouldShowMessages() ) { + data.result = false; + } + } ); - this.updateUi(); - } + this.updateUi(); + } - handleButtonStatus() { - BootstrapHelper.handleButtonStatus(this); - } + handleButtonStatus() { + BootstrapHelper.handleButtonStatus( this ); + } - shouldRender() { - if (document.querySelector(this.gateway.button.cancel_wrapper)) { - return false; - } + shouldRender() { + if ( document.querySelector( this.gateway.button.cancel_wrapper ) ) { + return false; + } - return document.querySelector(this.gateway.button.wrapper) !== null || document.querySelector(this.gateway.hosted_fields.wrapper) !== null; - } + return ( + document.querySelector( this.gateway.button.wrapper ) !== null || + document.querySelector( this.gateway.hosted_fields.wrapper ) !== + null + ); + } - shouldEnable() { - return BootstrapHelper.shouldEnable(this); - } + shouldEnable() { + return BootstrapHelper.shouldEnable( this ); + } - render() { - if (!this.shouldRender()) { - return; - } - if (document.querySelector(this.gateway.hosted_fields.wrapper + '>div')) { - document.querySelector(this.gateway.hosted_fields.wrapper + '>div').setAttribute('style', ''); - } - const actionHandler = new CheckoutActionHandler( - PayPalCommerceGateway, - this.errorHandler, - this.spinner - ); + render() { + if ( ! this.shouldRender() ) { + return; + } + if ( + document.querySelector( + this.gateway.hosted_fields.wrapper + '>div' + ) + ) { + document + .querySelector( this.gateway.hosted_fields.wrapper + '>div' ) + .setAttribute( 'style', '' ); + } + const actionHandler = new CheckoutActionHandler( + PayPalCommerceGateway, + this.errorHandler, + this.spinner + ); - if( - PayPalCommerceGateway.data_client_id.has_subscriptions - && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled - ) { - let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id - if(PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== '') { - subscription_plan_id = PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart - } - this.renderer.render(actionHandler.subscriptionsConfiguration(subscription_plan_id), {}, actionHandler.configuration()); + if ( + PayPalCommerceGateway.data_client_id.has_subscriptions && + PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled + ) { + let subscription_plan_id = + PayPalCommerceGateway.subscription_plan_id; + if ( + PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== + '' + ) { + subscription_plan_id = + PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart; + } + this.renderer.render( + actionHandler.subscriptionsConfiguration( + subscription_plan_id + ), + {}, + actionHandler.configuration() + ); - if(!PayPalCommerceGateway.subscription_product_allowed) { - this.gateway.button.is_disabled = true; - this.handleButtonStatus(); - } + if ( ! PayPalCommerceGateway.subscription_product_allowed ) { + this.gateway.button.is_disabled = true; + this.handleButtonStatus(); + } - return; - } + return; + } - if( - PayPalCommerceGateway.is_free_trial_cart - && PayPalCommerceGateway.vault_v3_enabled - ) { - this.renderer.render(actionHandler.addPaymentMethodConfiguration(), {}, actionHandler.configuration()); - return; - } + if ( + PayPalCommerceGateway.is_free_trial_cart && + PayPalCommerceGateway.vault_v3_enabled + ) { + this.renderer.render( + actionHandler.addPaymentMethodConfiguration(), + {}, + actionHandler.configuration() + ); + return; + } - this.renderer.render(actionHandler.configuration(), {}, actionHandler.configuration()); - } + this.renderer.render( + actionHandler.configuration(), + {}, + actionHandler.configuration() + ); + } - updateUi() { - const currentPaymentMethod = getCurrentPaymentMethod(); - const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; - const isCard = currentPaymentMethod === PaymentMethods.CARDS; - const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod); - const isSavedCard = isCard && isSavedCardSelected(); - const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway; - const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; - const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; + updateUi() { + const currentPaymentMethod = getCurrentPaymentMethod(); + const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; + const isCard = currentPaymentMethod === PaymentMethods.CARDS; + const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes( + currentPaymentMethod + ); + const isGooglePayMethod = + currentPaymentMethod === PaymentMethods.GOOGLEPAY; + const isSavedCard = isCard && isSavedCardSelected(); + const isNotOurGateway = + ! isPaypal && + ! isCard && + ! isSeparateButtonGateway && + ! isGooglePayMethod; + const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; + const hasVaultedPaypal = + PayPalCommerceGateway.vaulted_paypal_email !== ''; - const paypalButtonWrappers = { - ...Object.entries(PayPalCommerceGateway.separate_buttons) - .reduce((result, [k, data]) => { - return {...result, [data.id]: data.wrapper} - }, {}), - }; + const paypalButtonWrappers = { + ...Object.entries( PayPalCommerceGateway.separate_buttons ).reduce( + ( result, [ k, data ] ) => { + return { ...result, [ data.id ]: data.wrapper }; + }, + {} + ), + }; - setVisibleByClass(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, 'ppcp-hidden'); - setVisible('.ppcp-vaulted-paypal-details', isPaypal); - setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); - setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); - for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) { - setVisible(wrapper, gatewayId === currentPaymentMethod); - } + setVisibleByClass( + this.standardOrderButtonSelector, + ( isPaypal && isFreeTrial && hasVaultedPaypal ) || + isNotOurGateway || + isSavedCard, + 'ppcp-hidden' + ); + setVisible( '.ppcp-vaulted-paypal-details', isPaypal ); + setVisible( + this.gateway.button.wrapper, + isPaypal && ! ( isFreeTrial && hasVaultedPaypal ) + ); + setVisible( + this.gateway.hosted_fields.wrapper, + isCard && ! isSavedCard + ); + for ( const [ gatewayId, wrapper ] of Object.entries( + paypalButtonWrappers + ) ) { + setVisible( wrapper, gatewayId === currentPaymentMethod ); + } - if (isCard) { - if (isSavedCard) { - this.disableCreditCardFields(); - } else { - this.enableCreditCardFields(); - } - } + if ( isCard ) { + if ( isSavedCard ) { + this.disableCreditCardFields(); + } else { + this.enableCreditCardFields(); + } + } - jQuery(document.body).trigger('ppcp_checkout_rendered'); - } + setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod ); - shouldShowMessages() { - // hide when another method selected only if messages are near buttons - const messagesWrapper = document.querySelector(this.gateway.messages.wrapper); - if (getCurrentPaymentMethod() !== PaymentMethods.PAYPAL && - messagesWrapper && jQuery(messagesWrapper).closest('.ppc-button-wrapper').length - ) { - return false; - } + jQuery( document.body ).trigger( 'ppcp_checkout_rendered' ); + } - return !PayPalCommerceGateway.is_free_trial_cart; - } + shouldShowMessages() { + // hide when another method selected only if messages are near buttons + const messagesWrapper = document.querySelector( + this.gateway.messages.wrapper + ); + if ( + getCurrentPaymentMethod() !== PaymentMethods.PAYPAL && + messagesWrapper && + jQuery( messagesWrapper ).closest( '.ppc-button-wrapper' ).length + ) { + return false; + } - disableCreditCardFields() { - jQuery('label[for="ppcp-credit-card-gateway-card-number"]').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-gateway-card-number').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('label[for="ppcp-credit-card-gateway-card-expiry"]').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-gateway-card-expiry').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-gateway-card-cvc').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('label[for="vault"]').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-vault').addClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-vault').attr("disabled", true) - this.renderer.disableCreditCardFields() - } + return ! PayPalCommerceGateway.is_free_trial_cart; + } - enableCreditCardFields() { - jQuery('label[for="ppcp-credit-card-gateway-card-number"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-gateway-card-number').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('label[for="ppcp-credit-card-gateway-card-expiry"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-gateway-card-expiry').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-gateway-card-cvc').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('label[for="vault"]').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-vault').removeClass('ppcp-credit-card-gateway-form-field-disabled') - jQuery('#ppcp-credit-card-vault').attr("disabled", false) - this.renderer.enableCreditCardFields() - } + disableCreditCardFields() { + jQuery( 'label[for="ppcp-credit-card-gateway-card-number"]' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-gateway-card-number' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( 'label[for="ppcp-credit-card-gateway-card-expiry"]' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-gateway-card-expiry' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( 'label[for="ppcp-credit-card-gateway-card-cvc"]' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-gateway-card-cvc' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( 'label[for="vault"]' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-vault' ).addClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-vault' ).attr( 'disabled', true ); + this.renderer.disableCreditCardFields(); + } + + enableCreditCardFields() { + jQuery( + 'label[for="ppcp-credit-card-gateway-card-number"]' + ).removeClass( 'ppcp-credit-card-gateway-form-field-disabled' ); + jQuery( '#ppcp-credit-card-gateway-card-number' ).removeClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( + 'label[for="ppcp-credit-card-gateway-card-expiry"]' + ).removeClass( 'ppcp-credit-card-gateway-form-field-disabled' ); + jQuery( '#ppcp-credit-card-gateway-card-expiry' ).removeClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( 'label[for="ppcp-credit-card-gateway-card-cvc"]' ).removeClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-gateway-card-cvc' ).removeClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( 'label[for="vault"]' ).removeClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-vault' ).removeClass( + 'ppcp-credit-card-gateway-form-field-disabled' + ); + jQuery( '#ppcp-credit-card-vault' ).attr( 'disabled', false ); + this.renderer.enableCreditCardFields(); + } } -export default CheckoutBootstap +export default CheckoutBootstap; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js index 95774cfb6..be9b654bb 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js @@ -1,98 +1,111 @@ -import {setVisible} from "../Helper/Hiding"; -import MessageRenderer from "../Renderer/MessageRenderer"; +import { setVisible } from '../Helper/Hiding'; +import MessageRenderer from '../Renderer/MessageRenderer'; class MessagesBootstrap { - constructor(gateway, messageRenderer) { - this.gateway = gateway; - this.renderers = []; - this.lastAmount = this.gateway.messages.amount; - if (messageRenderer) { - this.renderers.push(messageRenderer); - } - } + constructor( gateway, messageRenderer ) { + this.gateway = gateway; + this.renderers = []; + this.lastAmount = this.gateway.messages.amount; + if ( messageRenderer ) { + this.renderers.push( messageRenderer ); + } + } - async init() { - if (this.gateway.messages?.block?.enabled) { - await this.attemptDiscoverBlocks(3); // Try up to 3 times - } - jQuery(document.body).on('ppcp_cart_rendered ppcp_checkout_rendered', () => { - this.render(); - }); - jQuery(document.body).on('ppcp_script_data_changed', (e, data) => { - this.gateway = data; - this.render(); - }); - jQuery(document.body).on('ppcp_cart_total_updated ppcp_checkout_total_updated ppcp_product_total_updated ppcp_block_cart_total_updated', (e, amount) => { - if (this.lastAmount !== amount) { - this.lastAmount = amount; - this.render(); - } - }); + async init() { + if ( this.gateway.messages?.block?.enabled ) { + await this.attemptDiscoverBlocks( 3 ); // Try up to 3 times + } + jQuery( document.body ).on( + 'ppcp_cart_rendered ppcp_checkout_rendered', + () => { + this.render(); + } + ); + jQuery( document.body ).on( 'ppcp_script_data_changed', ( e, data ) => { + this.gateway = data; + this.render(); + } ); + jQuery( document.body ).on( + 'ppcp_cart_total_updated ppcp_checkout_total_updated ppcp_product_total_updated ppcp_block_cart_total_updated', + ( e, amount ) => { + if ( this.lastAmount !== amount ) { + this.lastAmount = amount; + this.render(); + } + } + ); - this.render(); - } + this.render(); + } - attemptDiscoverBlocks(retries) { - return new Promise((resolve, reject) => { - this.discoverBlocks().then(found => { - if (!found && retries > 0) { - setTimeout(() => { - this.attemptDiscoverBlocks(retries - 1).then(resolve); - }, 2000); // Wait 2 seconds before retrying - } else { - resolve(); - } - }); - }); - } + attemptDiscoverBlocks( retries ) { + return new Promise( ( resolve, reject ) => { + this.discoverBlocks().then( ( found ) => { + if ( ! found && retries > 0 ) { + setTimeout( () => { + this.attemptDiscoverBlocks( retries - 1 ).then( + resolve + ); + }, 2000 ); // Wait 2 seconds before retrying + } else { + resolve(); + } + } ); + } ); + } - discoverBlocks() { - return new Promise((resolve) => { - const elements = document.querySelectorAll('.ppcp-messages'); - if (elements.length === 0) { - resolve(false); - return; - } + discoverBlocks() { + return new Promise( ( resolve ) => { + const elements = document.querySelectorAll( '.ppcp-messages' ); + if ( elements.length === 0 ) { + resolve( false ); + return; + } - Array.from(elements).forEach(blockElement => { - if (!blockElement.id) { - blockElement.id = `ppcp-message-${Math.random().toString(36).substr(2, 9)}`; // Ensure each block has a unique ID - } - const config = {wrapper: '#' + blockElement.id}; - if (!blockElement.getAttribute('data-pp-placement')) { - config.placement = this.gateway.messages.placement; - } - this.renderers.push(new MessageRenderer(config)); - }); - resolve(true); - }); - } + Array.from( elements ).forEach( ( blockElement ) => { + if ( ! blockElement.id ) { + blockElement.id = `ppcp-message-${ Math.random() + .toString( 36 ) + .substr( 2, 9 ) }`; // Ensure each block has a unique ID + } + const config = { wrapper: '#' + blockElement.id }; + if ( ! blockElement.getAttribute( 'data-pp-placement' ) ) { + config.placement = this.gateway.messages.placement; + } + this.renderers.push( new MessageRenderer( config ) ); + } ); + resolve( true ); + } ); + } - shouldShow(renderer) { - if (this.gateway.messages.is_hidden === true) { - return false; - } + shouldShow( renderer ) { + if ( this.gateway.messages.is_hidden === true ) { + return false; + } - const eventData = {result: true} - jQuery(document.body).trigger('ppcp_should_show_messages', [eventData, renderer.config.wrapper]); - return eventData.result; - } + const eventData = { result: true }; + jQuery( document.body ).trigger( 'ppcp_should_show_messages', [ + eventData, + renderer.config.wrapper, + ] ); + return eventData.result; + } - render() { - this.renderers.forEach(renderer => { - const shouldShow = this.shouldShow(renderer); - setVisible(renderer.config.wrapper, shouldShow); - if (!shouldShow) { - return; - } + render() { + this.renderers.forEach( ( renderer ) => { + const shouldShow = this.shouldShow( renderer ); + setVisible( renderer.config.wrapper, shouldShow ); + if ( ! shouldShow ) { + return; + } - if (!renderer.shouldRender()) { - return; - } + if ( ! renderer.shouldRender() ) { + return; + } - renderer.renderWithAmount(this.lastAmount); - }); - } + renderer.renderWithAmount( this.lastAmount ); + } ); + } } export default MessagesBootstrap; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js index 4b4e3efd6..1696031d8 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js @@ -1,66 +1,74 @@ import CartActionHandler from '../ActionHandler/CartActionHandler'; -import BootstrapHelper from "../Helper/BootstrapHelper"; +import BootstrapHelper from '../Helper/BootstrapHelper'; class MiniCartBootstap { - constructor(gateway, renderer, errorHandler) { - this.gateway = gateway; - this.renderer = renderer; - this.errorHandler = errorHandler; - this.actionHandler = null; - } + constructor( gateway, renderer, errorHandler ) { + this.gateway = gateway; + this.renderer = renderer; + this.errorHandler = errorHandler; + this.actionHandler = null; + } - init() { + init() { + this.actionHandler = new CartActionHandler( + PayPalCommerceGateway, + this.errorHandler + ); + this.render(); + this.handleButtonStatus(); - this.actionHandler = new CartActionHandler( - PayPalCommerceGateway, - this.errorHandler, - ); - this.render(); - this.handleButtonStatus(); + jQuery( document.body ).on( + 'wc_fragments_loaded wc_fragments_refreshed', + () => { + this.render(); + this.handleButtonStatus(); + } + ); - jQuery(document.body).on('wc_fragments_loaded wc_fragments_refreshed', () => { - this.render(); - this.handleButtonStatus(); - }); + this.renderer.onButtonsInit( + this.gateway.button.mini_cart_wrapper, + () => { + this.handleButtonStatus(); + }, + true + ); + } - this.renderer.onButtonsInit(this.gateway.button.mini_cart_wrapper, () => { - this.handleButtonStatus(); - }, true); - } + handleButtonStatus() { + BootstrapHelper.handleButtonStatus( this, { + wrapper: this.gateway.button.mini_cart_wrapper, + skipMessages: true, + } ); + } - handleButtonStatus() { - BootstrapHelper.handleButtonStatus(this, { - wrapper: this.gateway.button.mini_cart_wrapper, - skipMessages: true - }); - } + shouldRender() { + return ( + document.querySelector( this.gateway.button.mini_cart_wrapper ) !== + null || + document.querySelector( + this.gateway.hosted_fields.mini_cart_wrapper + ) !== null + ); + } - shouldRender() { - return document.querySelector(this.gateway.button.mini_cart_wrapper) !== null - || document.querySelector(this.gateway.hosted_fields.mini_cart_wrapper) !== null; - } + shouldEnable() { + return BootstrapHelper.shouldEnable( this, { + isDisabled: !! this.gateway.button.is_mini_cart_disabled, + } ); + } - shouldEnable() { - return BootstrapHelper.shouldEnable(this, { - isDisabled: !!this.gateway.button.is_mini_cart_disabled - }); - } + render() { + if ( ! this.shouldRender() ) { + return; + } - render() { - if (!this.shouldRender()) { - return; - } - - this.renderer.render( - this.actionHandler.configuration(), - { - button: { - wrapper: this.gateway.button.mini_cart_wrapper, - style: this.gateway.button.mini_cart_style, - }, - } - ); - } + this.renderer.render( this.actionHandler.configuration(), { + button: { + wrapper: this.gateway.button.mini_cart_wrapper, + style: this.gateway.button.mini_cart_style, + }, + } ); + } } export default MiniCartBootstap; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js index 678b60e63..dbc40e886 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js @@ -1,18 +1,18 @@ -import CheckoutBootstap from './CheckoutBootstap' -import {isChangePaymentPage} from "../Helper/Subscriptions"; +import CheckoutBootstap from './CheckoutBootstap'; +import { isChangePaymentPage } from '../Helper/Subscriptions'; class PayNowBootstrap extends CheckoutBootstap { - constructor(gateway, renderer, spinner, errorHandler) { - super(gateway, renderer, spinner, errorHandler) - } + constructor( gateway, renderer, spinner, errorHandler ) { + super( gateway, renderer, spinner, errorHandler ); + } - updateUi() { - if (isChangePaymentPage()) { - return - } + updateUi() { + if ( isChangePaymentPage() ) { + return; + } - super.updateUi(); - } + super.updateUi(); + } } export default PayNowBootstrap; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index c9978306e..59e3a0d2c 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -1,288 +1,362 @@ -import UpdateCart from "../Helper/UpdateCart"; -import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler"; -import {hide, show} from "../Helper/Hiding"; -import BootstrapHelper from "../Helper/BootstrapHelper"; -import {loadPaypalJsScript} from "../Helper/ScriptLoading"; -import {getPlanIdFromVariation} from "../Helper/Subscriptions" -import SimulateCart from "../Helper/SimulateCart"; -import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils"; -import merge from "deepmerge"; +import UpdateCart from '../Helper/UpdateCart'; +import SingleProductActionHandler from '../ActionHandler/SingleProductActionHandler'; +import { hide, show } from '../Helper/Hiding'; +import BootstrapHelper from '../Helper/BootstrapHelper'; +import { loadPaypalJsScript } from '../Helper/ScriptLoading'; +import { getPlanIdFromVariation } from '../Helper/Subscriptions'; +import SimulateCart from '../Helper/SimulateCart'; +import { strRemoveWord, strAddWord, throttle } from '../Helper/Utils'; +import merge from 'deepmerge'; +import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce'; class SingleProductBootstap { - constructor(gateway, renderer, errorHandler) { - this.gateway = gateway; - this.renderer = renderer; - this.errorHandler = errorHandler; - this.mutationObserver = new MutationObserver(this.handleChange.bind(this)); - this.formSelector = 'form.cart'; + constructor( gateway, renderer, errorHandler ) { + this.gateway = gateway; + this.renderer = renderer; + this.errorHandler = errorHandler; + this.mutationObserver = new MutationObserver( + this.handleChange.bind( this ) + ); + this.formSelector = 'form.cart'; - // Prevent simulate cart being called too many times in a burst. - this.simulateCartThrottled = throttle(this.simulateCart, this.gateway.simulate_cart.throttling || 5000); + // Prevent simulate cart being called too many times in a burst. + this.simulateCartThrottled = throttle( + this.simulateCart.bind( this ), + this.gateway.simulate_cart.throttling || 5000 + ); + this.debouncedHandleChange = debounce( + this.handleChange.bind( this ), + 100 + ); - this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { - this.handleChange(); - }, true); + this.renderer.onButtonsInit( + this.gateway.button.wrapper, + () => { + this.handleChange(); + }, + true + ); - this.subscriptionButtonsLoaded = false - } + this.subscriptionButtonsLoaded = false; + } - form() { - return document.querySelector(this.formSelector); - } + form() { + return document.querySelector( this.formSelector ); + } - handleChange() { - this.subscriptionButtonsLoaded = false + handleChange() { + this.subscriptionButtonsLoaded = false; - if (!this.shouldRender()) { - this.renderer.disableSmartButtons(this.gateway.button.wrapper); - hide(this.gateway.button.wrapper, this.formSelector); - return; - } + if ( ! this.shouldRender() ) { + this.renderer.disableSmartButtons( this.gateway.button.wrapper ); + hide( this.gateway.button.wrapper, this.formSelector ); + return; + } - this.render(); + this.render(); - this.renderer.enableSmartButtons(this.gateway.button.wrapper); - show(this.gateway.button.wrapper); + this.renderer.enableSmartButtons( this.gateway.button.wrapper ); + show( this.gateway.button.wrapper ); - this.handleButtonStatus(); - } + this.handleButtonStatus(); + } - handleButtonStatus(simulateCart = true) { - BootstrapHelper.handleButtonStatus(this, { - formSelector: this.formSelector - }); + handleButtonStatus( simulateCart = true ) { + BootstrapHelper.handleButtonStatus( this, { + formSelector: this.formSelector, + } ); - if (simulateCart) { - this.simulateCartThrottled(); - } - } + if ( simulateCart ) { + this.simulateCartThrottled(); + } + } - init() { - const form = this.form(); + init() { + const form = this.form(); - if (!form) { - return; - } + if ( ! form ) { + return; + } - jQuery(document).on('change', this.formSelector, () => { - this.handleChange(); - }); - this.mutationObserver.observe(form, { childList: true, subtree: true }); + jQuery( document ).on( 'change', this.formSelector, () => { + this.debouncedHandleChange(); + } ); + this.mutationObserver.observe( form, { + childList: true, + subtree: true, + } ); - const addToCartButton = form.querySelector('.single_add_to_cart_button'); + const addToCartButton = form.querySelector( + '.single_add_to_cart_button' + ); - if (addToCartButton) { - (new MutationObserver(this.handleButtonStatus.bind(this))) - .observe(addToCartButton, { attributes : true }); - } + if ( addToCartButton ) { + new MutationObserver( + this.handleButtonStatus.bind( this ) + ).observe( addToCartButton, { attributes: true } ); + } - jQuery(document).on('ppcp_should_show_messages', (e, data) => { - if (!this.shouldRender()) { - data.result = false; - } - }); + jQuery( document ).on( 'ppcp_should_show_messages', ( e, data ) => { + if ( ! this.shouldRender() ) { + data.result = false; + } + } ); - if (!this.shouldRender()) { - return; - } + if ( ! this.shouldRender() ) { + return; + } - this.render(); - this.handleChange(); - } + this.render(); + this.handleChange(); + } - shouldRender() { - return this.form() !== null - && !this.isWcsattSubscriptionMode(); - } + shouldRender() { + return this.form() !== null && ! this.isWcsattSubscriptionMode(); + } - shouldEnable() { - const form = this.form(); - const addToCartButton = form ? form.querySelector('.single_add_to_cart_button') : null; + shouldEnable() { + const form = this.form(); + const addToCartButton = form + ? form.querySelector( '.single_add_to_cart_button' ) + : null; - return BootstrapHelper.shouldEnable(this) - && !this.priceAmountIsZero() - && ((null === addToCartButton) || !addToCartButton.classList.contains('disabled')); - } + return ( + BootstrapHelper.shouldEnable( this ) && + ! this.priceAmountIsZero() && + ( null === addToCartButton || + ! addToCartButton.classList.contains( 'disabled' ) ) + ); + } - priceAmount(returnOnUndefined = 0) { - const priceText = [ - () => document.querySelector('form.cart ins .woocommerce-Price-amount')?.innerText, - () => document.querySelector('form.cart .woocommerce-Price-amount')?.innerText, - () => { - const priceEl = document.querySelector('.product .woocommerce-Price-amount'); - // variable products show price like 10.00 - 20.00 here - // but the second price also can be the suffix with the price incl/excl tax - if (priceEl) { - const allPriceElements = Array.from(priceEl.parentElement.querySelectorAll('.woocommerce-Price-amount')) - .filter(el => !el.parentElement.classList.contains('woocommerce-price-suffix')); - if (allPriceElements.length === 1) { - return priceEl.innerText; - } - } - return null; - }, - ].map(f => f()).find(val => val); + priceAmount( returnOnUndefined = 0 ) { + const priceText = [ + () => + document.querySelector( + 'form.cart ins .woocommerce-Price-amount' + )?.innerText, + () => + document.querySelector( 'form.cart .woocommerce-Price-amount' ) + ?.innerText, + () => { + const priceEl = document.querySelector( + '.product .woocommerce-Price-amount' + ); + // variable products show price like 10.00 - 20.00 here + // but the second price also can be the suffix with the price incl/excl tax + if ( priceEl ) { + const allPriceElements = Array.from( + priceEl.parentElement.querySelectorAll( + '.woocommerce-Price-amount' + ) + ).filter( + ( el ) => + ! el.parentElement.classList.contains( + 'woocommerce-price-suffix' + ) + ); + if ( allPriceElements.length === 1 ) { + return priceEl.innerText; + } + } + return null; + }, + ] + .map( ( f ) => f() ) + .find( ( val ) => val ); - if (typeof priceText === 'undefined') { - return returnOnUndefined; - } + if ( typeof priceText === 'undefined' ) { + return returnOnUndefined; + } - if (!priceText) { - return 0; - } + if ( ! priceText ) { + return 0; + } - return parseFloat(priceText.replace(/,/g, '.').replace(/([^\d,\.\s]*)/g, '')); - } + return parseFloat( + priceText.replace( /,/g, '.' ).replace( /([^\d,\.\s]*)/g, '' ) + ); + } - priceAmountIsZero() { - const price = this.priceAmount(-1); + priceAmountIsZero() { + const price = this.priceAmount( -1 ); - // if we can't find the price in the DOM we want to return true so the button is visible. - if (price === -1) { - return false; - } + // if we can't find the price in the DOM we want to return true so the button is visible. + if ( price === -1 ) { + return false; + } - return !price || price === 0; - } + return ! price || price === 0; + } - isWcsattSubscriptionMode() { - // Check "All products for subscriptions" plugin. - return document.querySelector('.wcsatt-options-product:not(.wcsatt-options-product--hidden) .subscription-option input[type="radio"]:checked') !== null - || document.querySelector('.wcsatt-options-prompt-label-subscription input[type="radio"]:checked') !== null; // grouped - } + isWcsattSubscriptionMode() { + // Check "All products for subscriptions" plugin. + return ( + document.querySelector( + '.wcsatt-options-product:not(.wcsatt-options-product--hidden) .subscription-option input[type="radio"]:checked' + ) !== null || + document.querySelector( + '.wcsatt-options-prompt-label-subscription input[type="radio"]:checked' + ) !== null + ); // grouped + } - variations() { - if (!this.hasVariations()) { - return null; - } + variations() { + if ( ! this.hasVariations() ) { + return null; + } - return [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map( - (element) => { - return { - value: element.value, - name: element.name - } - } - ); - } + return [ + ...document + .querySelector( 'form.cart' ) + ?.querySelectorAll( "[name^='attribute_']" ), + ].map( ( element ) => { + return { + value: element.value, + name: element.name, + }; + } ); + } - hasVariations() { - return document.querySelector('form.cart')?.classList.contains('variations_form'); - } + hasVariations() { + return document + .querySelector( 'form.cart' ) + ?.classList.contains( 'variations_form' ); + } - render() { - const actionHandler = new SingleProductActionHandler( - this.gateway, - new UpdateCart( - this.gateway.ajax.change_cart.endpoint, - this.gateway.ajax.change_cart.nonce, - ), - this.form(), - this.errorHandler, - ); + render() { + const actionHandler = new SingleProductActionHandler( + this.gateway, + new UpdateCart( + this.gateway.ajax.change_cart.endpoint, + this.gateway.ajax.change_cart.nonce + ), + this.form(), + this.errorHandler + ); - if( - PayPalCommerceGateway.data_client_id.has_subscriptions - && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled - ) { - const buttonWrapper = document.getElementById('ppc-button-ppcp-gateway'); - buttonWrapper.innerHTML = ''; + if ( + PayPalCommerceGateway.data_client_id.has_subscriptions && + PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled + ) { + const buttonWrapper = document.getElementById( + 'ppc-button-ppcp-gateway' + ); + buttonWrapper.innerHTML = ''; - const subscription_plan = this.variations() !== null - ? getPlanIdFromVariation(this.variations()) - : PayPalCommerceGateway.subscription_plan_id - if(!subscription_plan) { - return; - } + const subscription_plan = + this.variations() !== null + ? getPlanIdFromVariation( this.variations() ) + : PayPalCommerceGateway.subscription_plan_id; + if ( ! subscription_plan ) { + return; + } - if(this.subscriptionButtonsLoaded) return - loadPaypalJsScript( - { - clientId: PayPalCommerceGateway.client_id, - currency: PayPalCommerceGateway.currency, - intent: 'subscription', - vault: true - }, - actionHandler.subscriptionsConfiguration(subscription_plan), - this.gateway.button.wrapper - ); + if ( this.subscriptionButtonsLoaded ) { + return; + } + loadPaypalJsScript( + { + clientId: PayPalCommerceGateway.client_id, + currency: PayPalCommerceGateway.currency, + intent: 'subscription', + vault: true, + }, + actionHandler.subscriptionsConfiguration( subscription_plan ), + this.gateway.button.wrapper + ); - this.subscriptionButtonsLoaded = true - return; - } + this.subscriptionButtonsLoaded = true; + return; + } - this.renderer.render( - actionHandler.configuration() - ); - } + this.renderer.render( actionHandler.configuration() ); + } - simulateCart() { - if (!this.gateway.simulate_cart.enabled) { - return; - } + simulateCart() { + if ( ! this.gateway.simulate_cart.enabled ) { + return; + } - const actionHandler = new SingleProductActionHandler( - null, - null, - this.form(), - this.errorHandler, - ); + const actionHandler = new SingleProductActionHandler( + null, + null, + this.form(), + this.errorHandler + ); - const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions - && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; + const hasSubscriptions = + PayPalCommerceGateway.data_client_id.has_subscriptions && + PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; - const products = hasSubscriptions - ? actionHandler.getSubscriptionProducts() - : actionHandler.getProducts(); + const products = hasSubscriptions + ? actionHandler.getSubscriptionProducts() + : actionHandler.getProducts(); - (new SimulateCart( - this.gateway.ajax.simulate_cart.endpoint, - this.gateway.ajax.simulate_cart.nonce, - )).simulate((data) => { + new SimulateCart( + this.gateway.ajax.simulate_cart.endpoint, + this.gateway.ajax.simulate_cart.nonce + ).simulate( ( data ) => { + jQuery( document.body ).trigger( 'ppcp_product_total_updated', [ + data.total, + ] ); - jQuery(document.body).trigger('ppcp_product_total_updated', [data.total]); + let newData = {}; + if ( typeof data.button.is_disabled === 'boolean' ) { + newData = merge( newData, { + button: { is_disabled: data.button.is_disabled }, + } ); + } + if ( typeof data.messages.is_hidden === 'boolean' ) { + newData = merge( newData, { + messages: { is_hidden: data.messages.is_hidden }, + } ); + } + if ( newData ) { + BootstrapHelper.updateScriptData( this, newData ); + } - let newData = {}; - if (typeof data.button.is_disabled === 'boolean') { - newData = merge(newData, {button: {is_disabled: data.button.is_disabled}}); - } - if (typeof data.messages.is_hidden === 'boolean') { - newData = merge(newData, {messages: {is_hidden: data.messages.is_hidden}}); - } - if (newData) { - BootstrapHelper.updateScriptData(this, newData); - } + if ( this.gateway.single_product_buttons_enabled !== '1' ) { + return; + } - if ( this.gateway.single_product_buttons_enabled !== '1' ) { - return; - } + let enableFunding = this.gateway.url_params[ 'enable-funding' ]; + let disableFunding = this.gateway.url_params[ 'disable-funding' ]; - let enableFunding = this.gateway.url_params['enable-funding']; - let disableFunding = this.gateway.url_params['disable-funding']; + for ( const [ fundingSource, funding ] of Object.entries( + data.funding + ) ) { + if ( funding.enabled === true ) { + enableFunding = strAddWord( enableFunding, fundingSource ); + disableFunding = strRemoveWord( + disableFunding, + fundingSource + ); + } else if ( funding.enabled === false ) { + enableFunding = strRemoveWord( + enableFunding, + fundingSource + ); + disableFunding = strAddWord( + disableFunding, + fundingSource + ); + } + } - for (const [fundingSource, funding] of Object.entries(data.funding)) { - if (funding.enabled === true) { - enableFunding = strAddWord(enableFunding, fundingSource); - disableFunding = strRemoveWord(disableFunding, fundingSource); - } else if (funding.enabled === false) { - enableFunding = strRemoveWord(enableFunding, fundingSource); - disableFunding = strAddWord(disableFunding, fundingSource); - } - } + if ( + enableFunding !== this.gateway.url_params[ 'enable-funding' ] || + disableFunding !== this.gateway.url_params[ 'disable-funding' ] + ) { + this.gateway.url_params[ 'enable-funding' ] = enableFunding; + this.gateway.url_params[ 'disable-funding' ] = disableFunding; + jQuery( this.gateway.button.wrapper ).trigger( + 'ppcp-reload-buttons' + ); + } - if ( - (enableFunding !== this.gateway.url_params['enable-funding']) || - (disableFunding !== this.gateway.url_params['disable-funding']) - ) { - this.gateway.url_params['enable-funding'] = enableFunding; - this.gateway.url_params['disable-funding'] = disableFunding; - jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons'); - } - - this.handleButtonStatus(false); - - }, products); - } + this.handleButtonStatus( false ); + }, products ); + } } export default SingleProductBootstap; diff --git a/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js b/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js index 6ef7bd007..c527c8766 100644 --- a/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js +++ b/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js @@ -1,62 +1,71 @@ -import {loadScript} from "@paypal/paypal-js"; +import { loadScript } from '@paypal/paypal-js'; const storageKey = 'ppcp-data-client-id'; -const validateToken = (token, user) => { - if (! token) { - return false; - } - if (token.user !== user) { - return false; - } - const currentTime = new Date().getTime(); - const isExpired = currentTime >= token.expiration * 1000; - return ! isExpired; -} +const validateToken = ( token, user ) => { + if ( ! token ) { + return false; + } + if ( token.user !== user ) { + return false; + } + const currentTime = new Date().getTime(); + const isExpired = currentTime >= token.expiration * 1000; + return ! isExpired; +}; -const storedTokenForUser = (user) => { - const token = JSON.parse(sessionStorage.getItem(storageKey)); - if (validateToken(token, user)) { - return token.token; - } - return null; -} +const storedTokenForUser = ( user ) => { + const token = JSON.parse( sessionStorage.getItem( storageKey ) ); + if ( validateToken( token, user ) ) { + return token.token; + } + return null; +}; -const storeToken = (token) => { - sessionStorage.setItem(storageKey, JSON.stringify(token)); -} +const storeToken = ( token ) => { + sessionStorage.setItem( storageKey, JSON.stringify( token ) ); +}; -const dataClientIdAttributeHandler = (scriptOptions, config, callback, errorCallback = null) => { - fetch(config.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.nonce - }) - }).then((res)=>{ - return res.json(); - }).then((data)=>{ - const isValid = validateToken(data, config.user); - if (!isValid) { - return; - } - storeToken(data); +const dataClientIdAttributeHandler = ( + scriptOptions, + config, + callback, + errorCallback = null +) => { + fetch( config.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.nonce, + } ), + } ) + .then( ( res ) => { + return res.json(); + } ) + .then( ( data ) => { + const isValid = validateToken( data, config.user ); + if ( ! isValid ) { + return; + } + storeToken( data ); - scriptOptions['data-client-token'] = data.token; + scriptOptions[ 'data-client-token' ] = data.token; - loadScript(scriptOptions).then((paypal) => { - if (typeof callback === 'function') { - callback(paypal); - } - }).catch(err => { - if (typeof errorCallback === 'function') { - errorCallback(err); - } - }); - }); -} + loadScript( scriptOptions ) + .then( ( paypal ) => { + if ( typeof callback === 'function' ) { + callback( paypal ); + } + } ) + .catch( ( err ) => { + if ( typeof errorCallback === 'function' ) { + errorCallback( err ); + } + } ); + } ); +}; export default dataClientIdAttributeHandler; diff --git a/modules/ppcp-button/resources/js/modules/Entity/BookingProduct.js b/modules/ppcp-button/resources/js/modules/Entity/BookingProduct.js index 4245bcb6c..078a0cbd2 100644 --- a/modules/ppcp-button/resources/js/modules/Entity/BookingProduct.js +++ b/modules/ppcp-button/resources/js/modules/Entity/BookingProduct.js @@ -1,18 +1,17 @@ -import Product from "./Product"; +import Product from './Product'; class BookingProduct extends Product { + constructor( id, quantity, booking, extra ) { + super( id, quantity, null, extra ); + this.booking = booking; + } - constructor(id, quantity, booking, extra) { - super(id, quantity, null, extra); - this.booking = booking; - } - - data() { - return { - ...super.data(), - booking: this.booking - } - } + data() { + return { + ...super.data(), + booking: this.booking, + }; + } } export default BookingProduct; diff --git a/modules/ppcp-button/resources/js/modules/Entity/Product.js b/modules/ppcp-button/resources/js/modules/Entity/Product.js index b99f74b61..3c1d46076 100644 --- a/modules/ppcp-button/resources/js/modules/Entity/Product.js +++ b/modules/ppcp-button/resources/js/modules/Entity/Product.js @@ -1,19 +1,18 @@ class Product { - - constructor(id, quantity, variations, extra) { - this.id = id; - this.quantity = quantity; - this.variations = variations; - this.extra = extra; - } - data() { - return { - id:this.id, - quantity: this.quantity, - variations: this.variations, - extra: this.extra, - } - } + constructor( id, quantity, variations, extra ) { + this.id = id; + this.quantity = quantity; + this.variations = variations; + this.extra = extra; + } + data() { + return { + id: this.id, + quantity: this.quantity, + variations: this.variations, + extra: this.extra, + }; + } } export default Product; diff --git a/modules/ppcp-button/resources/js/modules/ErrorHandler.js b/modules/ppcp-button/resources/js/modules/ErrorHandler.js index 6048360b2..702e56557 100644 --- a/modules/ppcp-button/resources/js/modules/ErrorHandler.js +++ b/modules/ppcp-button/resources/js/modules/ErrorHandler.js @@ -1,108 +1,98 @@ class ErrorHandler { + /** + * @param {string} genericErrorText + * @param {Element} wrapper + */ + constructor( genericErrorText, wrapper ) { + this.genericErrorText = genericErrorText; + this.wrapper = wrapper; + } - /** - * @param {String} genericErrorText - * @param {Element} wrapper - */ - constructor(genericErrorText, wrapper) - { - this.genericErrorText = genericErrorText; - this.wrapper = wrapper; - } + genericError() { + this.clear(); + this.message( this.genericErrorText ); + } - genericError() { - this.clear(); - this.message(this.genericErrorText) - } + appendPreparedErrorMessageElement( errorMessageElement ) { + this._getMessageContainer().replaceWith( errorMessageElement ); + } - appendPreparedErrorMessageElement(errorMessageElement) - { - this._getMessageContainer().replaceWith(errorMessageElement); - } + /** + * @param {string} text + */ + message( text ) { + this._addMessage( text ); - /** - * @param {String} text - */ - message(text) - { - this._addMessage(text); + this._scrollToMessages(); + } - this._scrollToMessages(); - } + /** + * @param {Array} texts + */ + messages( texts ) { + texts.forEach( ( t ) => this._addMessage( t ) ); - /** - * @param {Array} texts - */ - messages(texts) - { - texts.forEach(t => this._addMessage(t)); + this._scrollToMessages(); + } - this._scrollToMessages(); - } + /** + * @return {string} + */ + currentHtml() { + const messageContainer = this._getMessageContainer(); + return messageContainer.outerHTML; + } - /** - * @returns {String} - */ - currentHtml() - { - const messageContainer = this._getMessageContainer(); - return messageContainer.outerHTML; - } + /** + * @private + * @param {string} text + */ + _addMessage( text ) { + if ( ! typeof String || text.length === 0 ) { + throw new Error( 'A new message text must be a non-empty string.' ); + } - /** - * @private - * @param {String} text - */ - _addMessage(text) - { - if(! typeof String || text.length === 0) { - throw new Error('A new message text must be a non-empty string.'); - } + const messageContainer = this._getMessageContainer(); - const messageContainer = this._getMessageContainer(); + const messageNode = this._prepareMessageElement( text ); + messageContainer.appendChild( messageNode ); + } - let messageNode = this._prepareMessageElement(text); - messageContainer.appendChild(messageNode); - } + /** + * @private + */ + _scrollToMessages() { + jQuery.scroll_to_notices( jQuery( '.woocommerce-error' ) ); + } - /** - * @private - */ - _scrollToMessages() - { - jQuery.scroll_to_notices(jQuery('.woocommerce-error')); - } + /** + * @private + */ + _getMessageContainer() { + let messageContainer = document.querySelector( 'ul.woocommerce-error' ); + if ( messageContainer === null ) { + messageContainer = document.createElement( 'ul' ); + messageContainer.setAttribute( 'class', 'woocommerce-error' ); + messageContainer.setAttribute( 'role', 'alert' ); + jQuery( this.wrapper ).prepend( messageContainer ); + } + return messageContainer; + } - /** - * @private - */ - _getMessageContainer() - { - let messageContainer = document.querySelector('ul.woocommerce-error'); - if (messageContainer === null) { - messageContainer = document.createElement('ul'); - messageContainer.setAttribute('class', 'woocommerce-error'); - messageContainer.setAttribute('role', 'alert'); - jQuery(this.wrapper).prepend(messageContainer); - } - return messageContainer; - } + /** + * @param message + * @private + */ + _prepareMessageElement( message ) { + const li = document.createElement( 'li' ); + li.innerHTML = message; - /** - * @private - */ - _prepareMessageElement(message) - { - const li = document.createElement('li'); - li.innerHTML = message; + return li; + } - return li; - } - - clear() - { - jQuery( '.woocommerce-error, .woocommerce-message' ).remove(); - } + clear() { + jQuery( '.woocommerce-error, .woocommerce-message' ).remove(); + } } export default ErrorHandler; diff --git a/modules/ppcp-button/resources/js/modules/Helper/ApmButtons.js b/modules/ppcp-button/resources/js/modules/Helper/ApmButtons.js index d6512723e..385432357 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ApmButtons.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ApmButtons.js @@ -1,120 +1,129 @@ +export const apmButtonsInit = ( config, selector = '.ppcp-button-apm' ) => { + let selectorInContainer = selector; -export const apmButtonsInit = (config, selector = '.ppcp-button-apm') => { - let selectorInContainer = selector; + if ( window.ppcpApmButtons ) { + return; + } - if (window.ppcpApmButtons) { - return; - } + if ( config && config.button ) { + // If it's separate gateways, modify wrapper to account for the individual buttons as individual APMs. + const wrapper = config.button.wrapper; + const isSeparateGateways = + jQuery( wrapper ).children( 'div[class^="item-"]' ).length > 0; - if (config && config.button) { + if ( isSeparateGateways ) { + selector += `, ${ wrapper } div[class^="item-"]`; + selectorInContainer += `, div[class^="item-"]`; + } + } - // If it's separate gateways, modify wrapper to account for the individual buttons as individual APMs. - const wrapper = config.button.wrapper; - const isSeparateGateways = jQuery(wrapper).children('div[class^="item-"]').length > 0; - - if (isSeparateGateways) { - selector += `, ${wrapper} div[class^="item-"]`; - selectorInContainer += `, div[class^="item-"]`; - } - } - - window.ppcpApmButtons = new ApmButtons(selector, selectorInContainer); -} + window.ppcpApmButtons = new ApmButtons( selector, selectorInContainer ); +}; export class ApmButtons { + constructor( selector, selectorInContainer ) { + this.selector = selector; + this.selectorInContainer = selectorInContainer; + this.containers = []; - constructor(selector, selectorInContainer) { - this.selector = selector; - this.selectorInContainer = selectorInContainer; - this.containers = []; + // Reloads button containers. + this.reloadContainers(); - // Reloads button containers. - this.reloadContainers(); + // Refresh button layout. + jQuery( window ) + .resize( () => { + this.refresh(); + } ) + .resize(); - // Refresh button layout. - jQuery(window).resize(() => { - this.refresh(); - }).resize(); + jQuery( document ).on( 'ppcp-smart-buttons-init', () => { + this.refresh(); + } ); - jQuery(document).on('ppcp-smart-buttons-init', () => { - this.refresh(); - }); + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + this.refresh(); + setTimeout( this.refresh.bind( this ), 200 ); + } + ); - jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => { - this.refresh(); - setTimeout(this.refresh.bind(this), 200); - }); + // Observes for new buttons. + new MutationObserver( + this.observeElementsCallback.bind( this ) + ).observe( document.body, { childList: true, subtree: true } ); + } - // Observes for new buttons. - (new MutationObserver(this.observeElementsCallback.bind(this))) - .observe(document.body, { childList: true, subtree: true }); - } + observeElementsCallback( mutationsList, observer ) { + const observeSelector = + this.selector + + ', .widget_shopping_cart, .widget_shopping_cart_content'; - observeElementsCallback(mutationsList, observer) { - const observeSelector = this.selector + ', .widget_shopping_cart, .widget_shopping_cart_content'; + let shouldReload = false; + for ( const mutation of mutationsList ) { + if ( mutation.type === 'childList' ) { + mutation.addedNodes.forEach( ( node ) => { + if ( node.matches && node.matches( observeSelector ) ) { + shouldReload = true; + } + } ); + } + } - let shouldReload = false; - for (let mutation of mutationsList) { - if (mutation.type === 'childList') { - mutation.addedNodes.forEach(node => { - if (node.matches && node.matches(observeSelector)) { - shouldReload = true; - } - }); - } - } + if ( shouldReload ) { + this.reloadContainers(); + this.refresh(); + } + } - if (shouldReload) { - this.reloadContainers(); - this.refresh(); - } - }; + reloadContainers() { + jQuery( this.selector ).each( ( index, el ) => { + const parent = jQuery( el ).parent(); + if ( ! this.containers.some( ( $el ) => $el.is( parent ) ) ) { + this.containers.push( parent ); + } + } ); + } - reloadContainers() { - jQuery(this.selector).each((index, el) => { - const parent = jQuery(el).parent(); - if (!this.containers.some($el => $el.is(parent))) { - this.containers.push(parent); - } - }); - } + refresh() { + for ( const container of this.containers ) { + const $container = jQuery( container ); - refresh() { - for (const container of this.containers) { - const $container = jQuery(container); + // Check width and add classes + const width = $container.width(); - // Check width and add classes - const width = $container.width(); + $container.removeClass( + 'ppcp-width-500 ppcp-width-300 ppcp-width-min' + ); - $container.removeClass('ppcp-width-500 ppcp-width-300 ppcp-width-min'); + if ( width >= 500 ) { + $container.addClass( 'ppcp-width-500' ); + } else if ( width >= 300 ) { + $container.addClass( 'ppcp-width-300' ); + } else { + $container.addClass( 'ppcp-width-min' ); + } - if (width >= 500) { - $container.addClass('ppcp-width-500'); - } else if (width >= 300) { - $container.addClass('ppcp-width-300'); - } else { - $container.addClass('ppcp-width-min'); - } + // Check first apm button + const $firstElement = $container.children( ':visible' ).first(); - // Check first apm button - const $firstElement = $container.children(':visible').first(); + // Assign margins to buttons + $container.find( this.selectorInContainer ).each( ( index, el ) => { + const $el = jQuery( el ); - // Assign margins to buttons - $container.find(this.selectorInContainer).each((index, el) => { - const $el = jQuery(el); - - if ($el.is($firstElement)) { - $el.css('margin-top', `0px`); - return true; - } - - const minMargin = 11; // Minimum margin. - const height = $el.height(); - const margin = Math.max(minMargin, Math.round(height * 0.3)); - $el.css('margin-top', `${margin}px`); - }); - - } - } + if ( $el.is( $firstElement ) ) { + $el.css( 'margin-top', `0px` ); + return true; + } + const minMargin = 11; // Minimum margin. + const height = $el.height(); + const margin = Math.max( + minMargin, + Math.round( height * 0.3 ) + ); + $el.css( 'margin-top', `${ margin }px` ); + } ); + } + } } diff --git a/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js b/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js index 41eb62e25..f30b2ade6 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js +++ b/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js @@ -1,51 +1,54 @@ -import {disable, enable, isDisabled} from "./ButtonDisabler"; -import merge from "deepmerge"; +import { disable, enable, isDisabled } from './ButtonDisabler'; +import merge from 'deepmerge'; /** * Common Bootstrap methods to avoid code repetition. */ export default class BootstrapHelper { + static handleButtonStatus( bs, options ) { + options = options || {}; + options.wrapper = options.wrapper || bs.gateway.button.wrapper; - static handleButtonStatus(bs, options) { - options = options || {}; - options.wrapper = options.wrapper || bs.gateway.button.wrapper; + const wasDisabled = isDisabled( options.wrapper ); + const shouldEnable = bs.shouldEnable(); - const wasDisabled = isDisabled(options.wrapper); - const shouldEnable = bs.shouldEnable(); + // Handle enable / disable + if ( shouldEnable && wasDisabled ) { + bs.renderer.enableSmartButtons( options.wrapper ); + enable( options.wrapper ); + } else if ( ! shouldEnable && ! wasDisabled ) { + bs.renderer.disableSmartButtons( options.wrapper ); + disable( options.wrapper, options.formSelector || null ); + } - // Handle enable / disable - if (shouldEnable && wasDisabled) { - bs.renderer.enableSmartButtons(options.wrapper); - enable(options.wrapper); - } else if (!shouldEnable && !wasDisabled) { - bs.renderer.disableSmartButtons(options.wrapper); - disable(options.wrapper, options.formSelector || null); - } + if ( wasDisabled !== ! shouldEnable ) { + jQuery( options.wrapper ).trigger( 'ppcp_buttons_enabled_changed', [ + shouldEnable, + ] ); + } + } - if (wasDisabled !== !shouldEnable) { - jQuery(options.wrapper).trigger('ppcp_buttons_enabled_changed', [shouldEnable]); - } - } + static shouldEnable( bs, options ) { + options = options || {}; + if ( typeof options.isDisabled === 'undefined' ) { + options.isDisabled = bs.gateway.button.is_disabled; + } - static shouldEnable(bs, options) { - options = options || {}; - if (typeof options.isDisabled === 'undefined') { - options.isDisabled = bs.gateway.button.is_disabled; - } + return bs.shouldRender() && options.isDisabled !== true; + } - return bs.shouldRender() - && options.isDisabled !== true; - } + static updateScriptData( bs, newData ) { + const newObj = merge( bs.gateway, newData ); - static updateScriptData(bs, newData) { - const newObj = merge(bs.gateway, newData); + const isChanged = + JSON.stringify( bs.gateway ) !== JSON.stringify( newObj ); - const isChanged = JSON.stringify(bs.gateway) !== JSON.stringify(newObj); + bs.gateway = newObj; - bs.gateway = newObj; - - if (isChanged) { - jQuery(document.body).trigger('ppcp_script_data_changed', [newObj]); - } - } + if ( isChanged ) { + jQuery( document.body ).trigger( 'ppcp_script_data_changed', [ + newObj, + ] ); + } + } } diff --git a/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js b/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js index c4042d079..30cc3f31b 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js @@ -1,83 +1,86 @@ /** - * @param selectorOrElement - * @returns {Element} + * @param selectorOrElement + * @return {Element} */ -const getElement = (selectorOrElement) => { - if (typeof selectorOrElement === 'string') { - return document.querySelector(selectorOrElement); - } - return selectorOrElement; -} - -const triggerEnabled = (selectorOrElement, element) => { - jQuery(document).trigger('ppcp-enabled', { - 'handler': 'ButtonsDisabler.setEnabled', - 'action': 'enable', - 'selector': selectorOrElement, - 'element': element - }); -} - -const triggerDisabled = (selectorOrElement, element) => { - jQuery(document).trigger('ppcp-disabled', { - 'handler': 'ButtonsDisabler.setEnabled', - 'action': 'disable', - 'selector': selectorOrElement, - 'element': element - }); -} - -export const setEnabled = (selectorOrElement, enable, form = null) => { - const element = getElement(selectorOrElement); - - if (!element) { - return; - } - - if (enable) { - jQuery(element) - .removeClass('ppcp-disabled') - .off('mouseup') - .find('> *') - .css('pointer-events', ''); - - triggerEnabled(selectorOrElement, element); - - } else { - jQuery(element) - .addClass('ppcp-disabled') - .on('mouseup', function(event) { - event.stopImmediatePropagation(); - - if (form) { - // Trigger form submit to show the error message - let $form = jQuery(form); - if ($form.find('.single_add_to_cart_button').hasClass('disabled')) { - $form.find(':submit').trigger('click'); - } - } - }) - .find('> *') - .css('pointer-events', 'none'); - - triggerDisabled(selectorOrElement, element); - } +const getElement = ( selectorOrElement ) => { + if ( typeof selectorOrElement === 'string' ) { + return document.querySelector( selectorOrElement ); + } + return selectorOrElement; }; -export const isDisabled = (selectorOrElement) => { - const element = getElement(selectorOrElement); - - if (!element) { - return false; - } - - return jQuery(element).hasClass('ppcp-disabled'); +const triggerEnabled = ( selectorOrElement, element ) => { + jQuery( document ).trigger( 'ppcp-enabled', { + handler: 'ButtonsDisabler.setEnabled', + action: 'enable', + selector: selectorOrElement, + element, + } ); }; -export const disable = (selectorOrElement, form = null) => { - setEnabled(selectorOrElement, false, form); +const triggerDisabled = ( selectorOrElement, element ) => { + jQuery( document ).trigger( 'ppcp-disabled', { + handler: 'ButtonsDisabler.setEnabled', + action: 'disable', + selector: selectorOrElement, + element, + } ); }; -export const enable = (selectorOrElement) => { - setEnabled(selectorOrElement, true); +export const setEnabled = ( selectorOrElement, enable, form = null ) => { + const element = getElement( selectorOrElement ); + + if ( ! element ) { + return; + } + + if ( enable ) { + jQuery( element ) + .removeClass( 'ppcp-disabled' ) + .off( 'mouseup' ) + .find( '> *' ) + .css( 'pointer-events', '' ); + + triggerEnabled( selectorOrElement, element ); + } else { + jQuery( element ) + .addClass( 'ppcp-disabled' ) + .on( 'mouseup', function ( event ) { + event.stopImmediatePropagation(); + + if ( form ) { + // Trigger form submit to show the error message + const $form = jQuery( form ); + if ( + $form + .find( '.single_add_to_cart_button' ) + .hasClass( 'disabled' ) + ) { + $form.find( ':submit' ).trigger( 'click' ); + } + } + } ) + .find( '> *' ) + .css( 'pointer-events', 'none' ); + + triggerDisabled( selectorOrElement, element ); + } +}; + +export const isDisabled = ( selectorOrElement ) => { + const element = getElement( selectorOrElement ); + + if ( ! element ) { + return false; + } + + return jQuery( element ).hasClass( 'ppcp-disabled' ); +}; + +export const disable = ( selectorOrElement, form = null ) => { + setEnabled( selectorOrElement, false, form ); +}; + +export const enable = ( selectorOrElement ) => { + setEnabled( selectorOrElement, true ); }; diff --git a/modules/ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper.js b/modules/ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper.js index 395bd9a8a..3492462e7 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper.js @@ -9,7 +9,7 @@ const REFRESH_BUTTON_EVENT = 'ppcp_refresh_payment_buttons'; * Use this function on the front-end to update payment buttons after the checkout form was updated. */ export function refreshButtons() { - document.dispatchEvent(new Event(REFRESH_BUTTON_EVENT)); + document.dispatchEvent( new Event( REFRESH_BUTTON_EVENT ) ); } /** @@ -18,20 +18,26 @@ export function refreshButtons() { * * @param {Function} refresh - Callback responsible to re-render the payment button. */ -export function setupButtonEvents(refresh) { - const miniCartInitDelay = 1000; - const debouncedRefresh = debounce(refresh, 50); +export function setupButtonEvents( refresh ) { + const miniCartInitDelay = 1000; + const debouncedRefresh = debounce( refresh, 50 ); - // Listen for our custom refresh event. - document.addEventListener(REFRESH_BUTTON_EVENT, debouncedRefresh); + // Listen for our custom refresh event. + document.addEventListener( REFRESH_BUTTON_EVENT, debouncedRefresh ); - // Listen for cart and checkout update events. - document.body.addEventListener('updated_cart_totals', debouncedRefresh); - document.body.addEventListener('updated_checkout', debouncedRefresh); + // Listen for cart and checkout update events. + document.body.addEventListener( 'updated_cart_totals', debouncedRefresh ); + document.body.addEventListener( 'updated_checkout', debouncedRefresh ); - // Use setTimeout for fragment events to avoid unnecessary refresh on initial render. - setTimeout(() => { - document.body.addEventListener('wc_fragments_loaded', debouncedRefresh); - document.body.addEventListener('wc_fragments_refreshed', debouncedRefresh); - }, miniCartInitDelay); + // Use setTimeout for fragment events to avoid unnecessary refresh on initial render. + setTimeout( () => { + document.body.addEventListener( + 'wc_fragments_loaded', + debouncedRefresh + ); + document.body.addEventListener( + 'wc_fragments_refreshed', + debouncedRefresh + ); + }, miniCartInitDelay ); } diff --git a/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js b/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js index 5dc02d3d6..60e7469b8 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js @@ -1,50 +1,50 @@ -export const cardFieldStyles = (field) => { - const allowedProperties = [ - 'appearance', - 'color', - 'direction', - 'font', - 'font-family', - 'font-size', - 'font-size-adjust', - 'font-stretch', - 'font-style', - 'font-variant', - 'font-variant-alternates', - 'font-variant-caps', - 'font-variant-east-asian', - 'font-variant-ligatures', - 'font-variant-numeric', - 'font-weight', - 'letter-spacing', - 'line-height', - 'opacity', - 'outline', - 'padding', - 'padding-bottom', - 'padding-left', - 'padding-right', - 'padding-top', - 'text-shadow', - 'transition', - '-moz-appearance', - '-moz-osx-font-smoothing', - '-moz-tap-highlight-color', - '-moz-transition', - '-webkit-appearance', - '-webkit-osx-font-smoothing', - '-webkit-tap-highlight-color', - '-webkit-transition', - ]; +export const cardFieldStyles = ( field ) => { + const allowedProperties = [ + 'appearance', + 'color', + 'direction', + 'font', + 'font-family', + 'font-size', + 'font-size-adjust', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-variant-alternates', + 'font-variant-caps', + 'font-variant-east-asian', + 'font-variant-ligatures', + 'font-variant-numeric', + 'font-weight', + 'letter-spacing', + 'line-height', + 'opacity', + 'outline', + 'padding', + 'padding-bottom', + 'padding-left', + 'padding-right', + 'padding-top', + 'text-shadow', + 'transition', + '-moz-appearance', + '-moz-osx-font-smoothing', + '-moz-tap-highlight-color', + '-moz-transition', + '-webkit-appearance', + '-webkit-osx-font-smoothing', + '-webkit-tap-highlight-color', + '-webkit-transition', + ]; - const stylesRaw = window.getComputedStyle(field); - const styles = {}; - Object.values(stylesRaw).forEach((prop) => { - if (!stylesRaw[prop] || !allowedProperties.includes(prop)) { - return; - } - styles[prop] = '' + stylesRaw[prop]; - }); + const stylesRaw = window.getComputedStyle( field ); + const styles = {}; + Object.values( stylesRaw ).forEach( ( prop ) => { + if ( ! stylesRaw[ prop ] || ! allowedProperties.includes( prop ) ) { + return; + } + styles[ prop ] = '' + stylesRaw[ prop ]; + } ); - return styles; -} + return styles; +}; diff --git a/modules/ppcp-button/resources/js/modules/Helper/CartHelper.js b/modules/ppcp-button/resources/js/modules/Helper/CartHelper.js index 1bc308413..746df7b40 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CartHelper.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CartHelper.js @@ -1,74 +1,77 @@ class CartHelper { + constructor( cartItemKeys = [] ) { + this.cartItemKeys = cartItemKeys; + } - constructor(cartItemKeys = []) - { - this.cartItemKeys = cartItemKeys; - } + getEndpoint() { + let ajaxUrl = '/?wc-ajax=%%endpoint%%'; - getEndpoint() { - let ajaxUrl = "/?wc-ajax=%%endpoint%%"; + if ( + typeof wc_cart_fragments_params !== 'undefined' && + wc_cart_fragments_params.wc_ajax_url + ) { + ajaxUrl = wc_cart_fragments_params.wc_ajax_url; + } - if ((typeof wc_cart_fragments_params !== 'undefined') && wc_cart_fragments_params.wc_ajax_url) { - ajaxUrl = wc_cart_fragments_params.wc_ajax_url; - } + return ajaxUrl.toString().replace( '%%endpoint%%', 'remove_from_cart' ); + } - return ajaxUrl.toString().replace('%%endpoint%%', 'remove_from_cart'); - } + addFromPurchaseUnits( purchaseUnits ) { + for ( const purchaseUnit of purchaseUnits || [] ) { + for ( const item of purchaseUnit.items || [] ) { + if ( ! item.cart_item_key ) { + continue; + } + this.cartItemKeys.push( item.cart_item_key ); + } + } - addFromPurchaseUnits(purchaseUnits) { - for (const purchaseUnit of purchaseUnits || []) { - for (const item of purchaseUnit.items || []) { - if (!item.cart_item_key) { - continue; - } - this.cartItemKeys.push(item.cart_item_key); - } - } + return this; + } - return this; - } + removeFromCart() { + return new Promise( ( resolve, reject ) => { + if ( ! this.cartItemKeys || ! this.cartItemKeys.length ) { + resolve(); + return; + } - removeFromCart() - { - return new Promise((resolve, reject) => { - if (!this.cartItemKeys || !this.cartItemKeys.length) { - resolve(); - return; - } + const numRequests = this.cartItemKeys.length; + let numResponses = 0; - const numRequests = this.cartItemKeys.length; - let numResponses = 0; + const tryToResolve = () => { + numResponses++; + if ( numResponses >= numRequests ) { + resolve(); + } + }; - const tryToResolve = () => { - numResponses++; - if (numResponses >= numRequests) { - resolve(); - } - } + for ( const cartItemKey of this.cartItemKeys ) { + const params = new URLSearchParams(); + params.append( 'cart_item_key', cartItemKey ); - for (const cartItemKey of this.cartItemKeys) { - const params = new URLSearchParams(); - params.append('cart_item_key', cartItemKey); + if ( ! cartItemKey ) { + tryToResolve(); + continue; + } - if (!cartItemKey) { - tryToResolve(); - continue; - } - - fetch(this.getEndpoint(), { - method: 'POST', - credentials: 'same-origin', - body: params - }).then(function (res) { - return res.json(); - }).then(() => { - tryToResolve(); - }).catch(() => { - tryToResolve(); - }); - } - }); - } + fetch( this.getEndpoint(), { + method: 'POST', + credentials: 'same-origin', + body: params, + } ) + .then( function ( res ) { + return res.json(); + } ) + .then( () => { + tryToResolve(); + } ) + .catch( () => { + tryToResolve(); + } ); + } + } ); + } } export default CartHelper; diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutFormValidation.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutFormValidation.js index 092d0f148..26070ecf2 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutFormValidation.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutFormValidation.js @@ -1,48 +1,55 @@ -import Spinner from "./Spinner"; -import FormValidator from "./FormValidator"; -import ErrorHandler from "../ErrorHandler"; +import Spinner from './Spinner'; +import FormValidator from './FormValidator'; +import ErrorHandler from '../ErrorHandler'; -const validateCheckoutForm = function (config) { - return new Promise(async (resolve, reject) => { - try { - const spinner = new Spinner(); - const errorHandler = new ErrorHandler( - config.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); +const validateCheckoutForm = function ( config ) { + return new Promise( async ( resolve, reject ) => { + try { + const spinner = new Spinner(); + const errorHandler = new ErrorHandler( + config.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); - const formSelector = config.context === 'checkout' ? 'form.checkout' : 'form#order_review'; - const formValidator = config.early_checkout_validation_enabled ? - new FormValidator( - config.ajax.validate_checkout.endpoint, - config.ajax.validate_checkout.nonce, - ) : null; + const formSelector = + config.context === 'checkout' + ? 'form.checkout' + : 'form#order_review'; + const formValidator = config.early_checkout_validation_enabled + ? new FormValidator( + config.ajax.validate_checkout.endpoint, + config.ajax.validate_checkout.nonce + ) + : null; - if (!formValidator) { - resolve(); - return; - } + if ( ! formValidator ) { + resolve(); + return; + } - formValidator.validate(document.querySelector(formSelector)).then((errors) => { - if (errors.length > 0) { - spinner.unblock(); - errorHandler.clear(); - errorHandler.messages(errors); + formValidator + .validate( document.querySelector( formSelector ) ) + .then( ( errors ) => { + if ( errors.length > 0 ) { + spinner.unblock(); + errorHandler.clear(); + errorHandler.messages( errors ); - // fire WC event for other plugins - jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] ); + // fire WC event for other plugins + jQuery( document.body ).trigger( 'checkout_error', [ + errorHandler.currentHtml(), + ] ); - reject(); - } else { - resolve(); - } - }); - - } catch (error) { - console.error(error); - reject(); - } - }); -} + reject(); + } else { + resolve(); + } + } ); + } catch ( error ) { + console.error( error ); + reject(); + } + } ); +}; export default validateCheckoutForm; diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 7d5292251..3e284c8ef 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -1,22 +1,23 @@ export const PaymentMethods = { - PAYPAL: 'ppcp-gateway', - CARDS: 'ppcp-credit-card-gateway', - OXXO: 'ppcp-oxxo-gateway', - CARD_BUTTON: 'ppcp-card-button-gateway', + PAYPAL: 'ppcp-gateway', + CARDS: 'ppcp-credit-card-gateway', + OXXO: 'ppcp-oxxo-gateway', + CARD_BUTTON: 'ppcp-card-button-gateway', + GOOGLEPAY: 'ppcp-googlepay', }; export const ORDER_BUTTON_SELECTOR = '#place_order'; export const getCurrentPaymentMethod = () => { - const el = document.querySelector('input[name="payment_method"]:checked'); - if (!el) { - return null; - } + const el = document.querySelector( 'input[name="payment_method"]:checked' ); + if ( ! el ) { + return null; + } - return el.value; + return el.value; }; export const isSavedCardSelected = () => { - const savedCardList = document.querySelector('#saved-credit-card'); - return savedCardList && savedCardList.value !== ''; + const savedCardList = document.querySelector( '#saved-credit-card' ); + return savedCardList && savedCardList.value !== ''; }; diff --git a/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js b/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js index c5a2034fc..114c9102e 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js +++ b/modules/ppcp-button/resources/js/modules/Helper/DccInputFactory.js @@ -1,17 +1,21 @@ -const dccInputFactory = (original) => { - const styles = window.getComputedStyle(original); - const newElement = document.createElement('span'); +const dccInputFactory = ( original ) => { + const styles = window.getComputedStyle( original ); + const newElement = document.createElement( 'span' ); - newElement.setAttribute('id', original.id); - newElement.setAttribute('class', original.className); + newElement.setAttribute( 'id', original.id ); + newElement.setAttribute( 'class', original.className ); - Object.values(styles).forEach( (prop) => { - if (! styles[prop] || ! isNaN(prop) || prop === 'background-image' ) { - return; - } - newElement.style.setProperty(prop,'' + styles[prop]); - }); - return newElement; -} + Object.values( styles ).forEach( ( prop ) => { + if ( + ! styles[ prop ] || + ! isNaN( prop ) || + prop === 'background-image' + ) { + return; + } + newElement.style.setProperty( prop, '' + styles[ prop ] ); + } ); + return newElement; +}; export default dccInputFactory; diff --git a/modules/ppcp-button/resources/js/modules/Helper/FormHelper.js b/modules/ppcp-button/resources/js/modules/Helper/FormHelper.js index 7f504e1c4..0fc5cdbe4 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/FormHelper.js +++ b/modules/ppcp-button/resources/js/modules/Helper/FormHelper.js @@ -1,50 +1,52 @@ - /** * Common Form utility methods */ export default class FormHelper { + static getPrefixedFields( formElement, prefix ) { + const formData = new FormData( formElement ); + const fields = {}; - static getPrefixedFields(formElement, prefix) { - const formData = new FormData(formElement); - let fields = {}; + for ( const [ name, value ] of formData.entries() ) { + if ( ! prefix || name.startsWith( prefix ) ) { + fields[ name ] = value; + } + } - for (const [name, value] of formData.entries()) { - if (!prefix || name.startsWith(prefix)) { - fields[name] = value; - } - } + return fields; + } - return fields; - } + static getFilteredFields( formElement, exactFilters, prefixFilters ) { + const formData = new FormData( formElement ); + const fields = {}; + const counters = {}; - static getFilteredFields(formElement, exactFilters, prefixFilters) { - const formData = new FormData(formElement); - let fields = {}; - let counters = {}; + for ( let [ name, value ] of formData.entries() ) { + // Handle array format + if ( name.indexOf( '[]' ) !== -1 ) { + const k = name; + counters[ k ] = counters[ k ] || 0; + name = name.replace( '[]', `[${ counters[ k ] }]` ); + counters[ k ]++; + } - for (let [name, value] of formData.entries()) { + if ( ! name ) { + continue; + } + if ( exactFilters && exactFilters.indexOf( name ) !== -1 ) { + continue; + } + if ( + prefixFilters && + prefixFilters.some( ( prefixFilter ) => + name.startsWith( prefixFilter ) + ) + ) { + continue; + } - // Handle array format - if (name.indexOf('[]') !== -1) { - const k = name; - counters[k] = counters[k] || 0; - name = name.replace('[]', `[${counters[k]}]`); - counters[k]++; - } + fields[ name ] = value; + } - if (!name) { - continue; - } - if (exactFilters && (exactFilters.indexOf(name) !== -1)) { - continue; - } - if (prefixFilters && prefixFilters.some(prefixFilter => name.startsWith(prefixFilter))) { - continue; - } - - fields[name] = value; - } - - return fields; - } + return fields; + } } diff --git a/modules/ppcp-button/resources/js/modules/Helper/FormSaver.js b/modules/ppcp-button/resources/js/modules/Helper/FormSaver.js index 0250b6b29..dfc27ceff 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/FormSaver.js +++ b/modules/ppcp-button/resources/js/modules/Helper/FormSaver.js @@ -1,28 +1,28 @@ export default class FormSaver { - constructor(url, nonce) { - this.url = url; - this.nonce = nonce; - } + constructor( url, nonce ) { + this.url = url; + this.nonce = nonce; + } - async save(form) { - const formData = new FormData(form); + async save( form ) { + const formData = new FormData( form ); - const res = await fetch(this.url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.nonce, - form_encoded: new URLSearchParams(formData).toString(), - }), - }); + const res = await fetch( this.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.nonce, + form_encoded: new URLSearchParams( formData ).toString(), + } ), + } ); - const data = await res.json(); + const data = await res.json(); - if (!data.success) { - throw Error(data.data.message); - } - } + if ( ! data.success ) { + throw Error( data.data.message ); + } + } } diff --git a/modules/ppcp-button/resources/js/modules/Helper/FormValidator.js b/modules/ppcp-button/resources/js/modules/Helper/FormValidator.js index 0c03ca4d4..01fdfa20b 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/FormValidator.js +++ b/modules/ppcp-button/resources/js/modules/Helper/FormValidator.js @@ -1,37 +1,37 @@ export default class FormValidator { - constructor(url, nonce) { - this.url = url; - this.nonce = nonce; - } + constructor( url, nonce ) { + this.url = url; + this.nonce = nonce; + } - async validate(form) { - const formData = new FormData(form); + async validate( form ) { + const formData = new FormData( form ); - const res = await fetch(this.url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.nonce, - form_encoded: new URLSearchParams(formData).toString(), - }), - }); + const res = await fetch( this.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.nonce, + form_encoded: new URLSearchParams( formData ).toString(), + } ), + } ); - const data = await res.json(); + const data = await res.json(); - if (!data.success) { - if (data.data.refresh) { - jQuery( document.body ).trigger( 'update_checkout' ); - } + if ( ! data.success ) { + if ( data.data.refresh ) { + jQuery( document.body ).trigger( 'update_checkout' ); + } - if (data.data.errors) { - return data.data.errors; - } - throw Error(data.data.message); - } + if ( data.data.errors ) { + return data.data.errors; + } + throw Error( data.data.message ); + } - return []; - } + return []; + } } diff --git a/modules/ppcp-button/resources/js/modules/Helper/Hiding.js b/modules/ppcp-button/resources/js/modules/Helper/Hiding.js index 9ffd5a031..e5b937a52 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Hiding.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Hiding.js @@ -1,85 +1,92 @@ /** - * @param selectorOrElement - * @returns {Element} + * @param selectorOrElement + * @return {Element} */ -const getElement = (selectorOrElement) => { - if (typeof selectorOrElement === 'string') { - return document.querySelector(selectorOrElement); - } - return selectorOrElement; -} - -const triggerHidden = (handler, selectorOrElement, element) => { - jQuery(document).trigger('ppcp-hidden', { - 'handler': handler, - 'action': 'hide', - 'selector': selectorOrElement, - 'element': element - }); -} - -const triggerShown = (handler, selectorOrElement, element) => { - jQuery(document).trigger('ppcp-shown', { - 'handler': handler, - 'action': 'show', - 'selector': selectorOrElement, - 'element': element - }); -} - -export const isVisible = (element) => { - return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length); -} - -export const setVisible = (selectorOrElement, show, important = false) => { - const element = getElement(selectorOrElement); - if (!element) { - return; - } - - const currentValue = element.style.getPropertyValue('display'); - - if (!show) { - if (currentValue === 'none') { - return; - } - - element.style.setProperty('display', 'none', important ? 'important' : ''); - triggerHidden('Hiding.setVisible', selectorOrElement, element); - - } else { - if (currentValue === 'none') { - element.style.removeProperty('display'); - triggerShown('Hiding.setVisible', selectorOrElement, element); - } - - // still not visible (if something else added display: none in CSS) - if (!isVisible(element)) { - element.style.setProperty('display', 'block'); - triggerShown('Hiding.setVisible', selectorOrElement, element); - } - } +const getElement = ( selectorOrElement ) => { + if ( typeof selectorOrElement === 'string' ) { + return document.querySelector( selectorOrElement ); + } + return selectorOrElement; }; -export const setVisibleByClass = (selectorOrElement, show, hiddenClass) => { - const element = getElement(selectorOrElement); - if (!element) { - return; - } - - if (show) { - element.classList.remove(hiddenClass); - triggerShown('Hiding.setVisibleByClass', selectorOrElement, element); - } else { - element.classList.add(hiddenClass); - triggerHidden('Hiding.setVisibleByClass', selectorOrElement, element); - } +const triggerHidden = ( handler, selectorOrElement, element ) => { + jQuery( document ).trigger( 'ppcp-hidden', { + handler, + action: 'hide', + selector: selectorOrElement, + element, + } ); }; -export const hide = (selectorOrElement, important = false) => { - setVisible(selectorOrElement, false, important); +const triggerShown = ( handler, selectorOrElement, element ) => { + jQuery( document ).trigger( 'ppcp-shown', { + handler, + action: 'show', + selector: selectorOrElement, + element, + } ); }; -export const show = (selectorOrElement) => { - setVisible(selectorOrElement, true); +export const isVisible = ( element ) => { + return !! ( + element.offsetWidth || + element.offsetHeight || + element.getClientRects().length + ); +}; + +export const setVisible = ( selectorOrElement, show, important = false ) => { + const element = getElement( selectorOrElement ); + if ( ! element ) { + return; + } + + const currentValue = element.style.getPropertyValue( 'display' ); + + if ( ! show ) { + if ( currentValue === 'none' ) { + return; + } + + element.style.setProperty( + 'display', + 'none', + important ? 'important' : '' + ); + triggerHidden( 'Hiding.setVisible', selectorOrElement, element ); + } else { + if ( currentValue === 'none' ) { + element.style.removeProperty( 'display' ); + triggerShown( 'Hiding.setVisible', selectorOrElement, element ); + } + + // still not visible (if something else added display: none in CSS) + if ( ! isVisible( element ) ) { + element.style.setProperty( 'display', 'block' ); + triggerShown( 'Hiding.setVisible', selectorOrElement, element ); + } + } +}; + +export const setVisibleByClass = ( selectorOrElement, show, hiddenClass ) => { + const element = getElement( selectorOrElement ); + if ( ! element ) { + return; + } + + if ( show ) { + element.classList.remove( hiddenClass ); + triggerShown( 'Hiding.setVisibleByClass', selectorOrElement, element ); + } else { + element.classList.add( hiddenClass ); + triggerHidden( 'Hiding.setVisibleByClass', selectorOrElement, element ); + } +}; + +export const hide = ( selectorOrElement, important = false ) => { + setVisible( selectorOrElement, false, important ); +}; + +export const show = ( selectorOrElement ) => { + setVisible( selectorOrElement, true ); }; diff --git a/modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js b/modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js index 5fa93244d..1e71d6bb7 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js +++ b/modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js @@ -9,112 +9,115 @@ const DEFAULT_TRIGGER_ELEMENT_SELECTOR = '.woocommerce-checkout-payment'; * invisibility period of wrappers, some payment buttons fail to initialize, * so we wait for the payment element to be visible. * - * @property {HTMLElement} form - Checkout form element. + * @property {HTMLElement} form - Checkout form element. * @property {HTMLElement} triggerElement - Element, which visibility we need to detect. - * @property {boolean} isVisible - Whether the triggerElement is visible. + * @property {boolean} isVisible - Whether the triggerElement is visible. */ class MultistepCheckoutHelper { + /** + * Selector that defines the HTML element we are waiting to become visible. + * @type {string} + */ + #triggerElementSelector; - /** - * Selector that defines the HTML element we are waiting to become visible. - * @type {string} - */ - #triggerElementSelector; + /** + * Interval (in milliseconds) in which the visibility of the trigger element is checked. + * @type {number} + */ + #intervalTime = 150; - /** - * Interval (in milliseconds) in which the visibility of the trigger element is checked. - * @type {number} - */ - #intervalTime = 150; + /** + * The interval ID returned by the setInterval() method. + * @type {number|false} + */ + #intervalId; - /** - * The interval ID returned by the setInterval() method. - * @type {number|false} - */ - #intervalId; + /** + * Selector passed to the constructor that identifies the checkout form + * @type {string} + */ + #formSelector; - /** - * Selector passed to the constructor that identifies the checkout form - * @type {string} - */ - #formSelector; + /** + * @param {string} formSelector - Selector of the checkout form + * @param {string} triggerElementSelector - Optional. Selector of the dependant element. + */ + constructor( formSelector, triggerElementSelector = '' ) { + this.#formSelector = formSelector; + this.#triggerElementSelector = + triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR; + this.#intervalId = false; - /** - * @param {string} formSelector - Selector of the checkout form - * @param {string} triggerElementSelector - Optional. Selector of the dependant element. - */ - constructor(formSelector, triggerElementSelector = '') { - this.#formSelector = formSelector; - this.#triggerElementSelector = triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR; - this.#intervalId = false; - - /* + /* Start the visibility checker after a brief delay. This allows eventual multistep plugins to dynamically prepare the checkout page, so we can decide whether this helper is needed. */ - setTimeout(() => { - if (this.form && !this.isVisible) { - this.start(); - } - }, 250); - } + setTimeout( () => { + if ( this.form && ! this.isVisible ) { + this.start(); + } + }, 250 ); + } - /** - * The checkout form element. - * @returns {Element|null} - Form element or null. - */ - get form() { - return document.querySelector(this.#formSelector); - } + /** + * The checkout form element. + * @return {Element|null} - Form element or null. + */ + get form() { + return document.querySelector( this.#formSelector ); + } - /** - * The element which must be visible before payment buttons should be initialized. - * @returns {Element|null} - Trigger element or null. - */ - get triggerElement() { - return this.form?.querySelector(this.#triggerElementSelector); - } + /** + * The element which must be visible before payment buttons should be initialized. + * @return {Element|null} - Trigger element or null. + */ + get triggerElement() { + return this.form?.querySelector( this.#triggerElementSelector ); + } - /** - * Checks the visibility of the payment button wrapper. - * @returns {boolean} - returns boolean value on the basis of visibility of element. - */ - get isVisible() { - const box = this.triggerElement?.getBoundingClientRect(); + /** + * Checks the visibility of the payment button wrapper. + * @return {boolean} - returns boolean value on the basis of visibility of element. + */ + get isVisible() { + const box = this.triggerElement?.getBoundingClientRect(); - return !!(box && box.width && box.height); - } + return !! ( box && box.width && box.height ); + } - /** - * Starts the observation of the DOM, initiates monitoring the checkout form. - * To ensure multiple calls to start don't create multiple intervals, we first call stop. - */ - start() { - this.stop(); - this.#intervalId = setInterval(() => this.checkElement(), this.#intervalTime); - } + /** + * Starts the observation of the DOM, initiates monitoring the checkout form. + * To ensure multiple calls to start don't create multiple intervals, we first call stop. + */ + start() { + this.stop(); + this.#intervalId = setInterval( + () => this.checkElement(), + this.#intervalTime + ); + } - /** - * Stops the observation of the checkout form. - * Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist. - */ - stop() { - if (this.#intervalId) { - clearInterval(this.#intervalId); - this.#intervalId = false; - } - } + /** + * Stops the observation of the checkout form. + * Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist. + */ + stop() { + if ( this.#intervalId ) { + clearInterval( this.#intervalId ); + this.#intervalId = false; + } + } - /** - * Checks if the trigger element is visible. - * If visible, it initialises the payment buttons and stops the observation. - */ - checkElement() { - if (this.isVisible) { - refreshButtons(); - this.stop(); - } - } + /** + * Checks if the trigger element is visible. + * If visible, it initialises the payment buttons and stops the observation. + */ + checkElement() { + if ( this.isVisible ) { + refreshButtons(); + this.stop(); + } + } } export default MultistepCheckoutHelper; diff --git a/modules/ppcp-button/resources/js/modules/Helper/PayerData.js b/modules/ppcp-button/resources/js/modules/Helper/PayerData.js index 2416e869e..b9b84d14f 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/PayerData.js +++ b/modules/ppcp-button/resources/js/modules/Helper/PayerData.js @@ -1,34 +1,59 @@ export const payerData = () => { - const payer = PayPalCommerceGateway.payer; - if (! payer) { - return null; - } + const payer = PayPalCommerceGateway.payer; + if ( ! payer ) { + return null; + } - const phone = (document.querySelector('#billing_phone') || typeof payer.phone !== 'undefined') ? - { - phone_type:"HOME", - phone_number:{ - national_number : (document.querySelector('#billing_phone')) ? document.querySelector('#billing_phone').value : payer.phone.phone_number.national_number - } - } : null; - const payerData = { - email_address:(document.querySelector('#billing_email')) ? document.querySelector('#billing_email').value : payer.email_address, - name : { - surname: (document.querySelector('#billing_last_name')) ? document.querySelector('#billing_last_name').value : payer.name.surname, - given_name: (document.querySelector('#billing_first_name')) ? document.querySelector('#billing_first_name').value : payer.name.given_name - }, - address : { - country_code : (document.querySelector('#billing_country')) ? document.querySelector('#billing_country').value : payer.address.country_code, - address_line_1 : (document.querySelector('#billing_address_1')) ? document.querySelector('#billing_address_1').value : payer.address.address_line_1, - address_line_2 : (document.querySelector('#billing_address_2')) ? document.querySelector('#billing_address_2').value : payer.address.address_line_2, - admin_area_1 : (document.querySelector('#billing_state')) ? document.querySelector('#billing_state').value : payer.address.admin_area_1, - admin_area_2 : (document.querySelector('#billing_city')) ? document.querySelector('#billing_city').value : payer.address.admin_area_2, - postal_code : (document.querySelector('#billing_postcode')) ? document.querySelector('#billing_postcode').value : payer.address.postal_code - } - }; + const phone = + document.querySelector( '#billing_phone' ) || + typeof payer.phone !== 'undefined' + ? { + phone_type: 'HOME', + phone_number: { + national_number: document.querySelector( + '#billing_phone' + ) + ? document.querySelector( '#billing_phone' ).value + : payer.phone.phone_number.national_number, + }, + } + : null; + const payerData = { + email_address: document.querySelector( '#billing_email' ) + ? document.querySelector( '#billing_email' ).value + : payer.email_address, + name: { + surname: document.querySelector( '#billing_last_name' ) + ? document.querySelector( '#billing_last_name' ).value + : payer.name.surname, + given_name: document.querySelector( '#billing_first_name' ) + ? document.querySelector( '#billing_first_name' ).value + : payer.name.given_name, + }, + address: { + country_code: document.querySelector( '#billing_country' ) + ? document.querySelector( '#billing_country' ).value + : payer.address.country_code, + address_line_1: document.querySelector( '#billing_address_1' ) + ? document.querySelector( '#billing_address_1' ).value + : payer.address.address_line_1, + address_line_2: document.querySelector( '#billing_address_2' ) + ? document.querySelector( '#billing_address_2' ).value + : payer.address.address_line_2, + admin_area_1: document.querySelector( '#billing_state' ) + ? document.querySelector( '#billing_state' ).value + : payer.address.admin_area_1, + admin_area_2: document.querySelector( '#billing_city' ) + ? document.querySelector( '#billing_city' ).value + : payer.address.admin_area_2, + postal_code: document.querySelector( '#billing_postcode' ) + ? document.querySelector( '#billing_postcode' ).value + : payer.address.postal_code, + }, + }; - if (phone) { - payerData.phone = phone; - } - return payerData; -} + if ( phone ) { + payerData.phone = phone; + } + return payerData; +}; diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js index 55866fe9d..0aa70f793 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js @@ -1,109 +1,110 @@ -import dataClientIdAttributeHandler from "../DataClientIdAttributeHandler"; -import {loadScript} from "@paypal/paypal-js"; -import widgetBuilder from "../Renderer/WidgetBuilder"; -import merge from "deepmerge"; -import {keysToCamelCase} from "./Utils"; -import {getCurrentPaymentMethod} from "./CheckoutMethodState"; +import dataClientIdAttributeHandler from '../DataClientIdAttributeHandler'; +import { loadScript } from '@paypal/paypal-js'; +import widgetBuilder from '../Renderer/WidgetBuilder'; +import merge from 'deepmerge'; +import { keysToCamelCase } from './Utils'; +import { getCurrentPaymentMethod } from './CheckoutMethodState'; import { v4 as uuidv4 } from 'uuid'; // This component may be used by multiple modules. This assures that options are shared between all instances. -let options = window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || { - isLoading: false, - onLoadedCallbacks: [], - onErrorCallbacks: [], +const options = ( window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || { + isLoading: false, + onLoadedCallbacks: [], + onErrorCallbacks: [], +} ); + +export const loadPaypalScript = ( config, onLoaded, onError = null ) => { + // If PayPal is already loaded call the onLoaded callback and return. + if ( typeof paypal !== 'undefined' ) { + onLoaded(); + return; + } + + // Add the onLoaded callback to the onLoadedCallbacks stack. + options.onLoadedCallbacks.push( onLoaded ); + if ( onError ) { + options.onErrorCallbacks.push( onError ); + } + + // Return if it's still loading. + if ( options.isLoading ) { + return; + } + options.isLoading = true; + + const resetState = () => { + options.isLoading = false; + options.onLoadedCallbacks = []; + options.onErrorCallbacks = []; + }; + + // Callback to be called once the PayPal script is loaded. + const callback = ( paypal ) => { + widgetBuilder.setPaypal( paypal ); + + for ( const onLoadedCallback of options.onLoadedCallbacks ) { + onLoadedCallback(); + } + + resetState(); + }; + const errorCallback = ( err ) => { + for ( const onErrorCallback of options.onErrorCallbacks ) { + onErrorCallback( err ); + } + + resetState(); + }; + + // Build the PayPal script options. + let scriptOptions = keysToCamelCase( config.url_params ); + if ( config.script_attributes ) { + scriptOptions = merge( scriptOptions, config.script_attributes ); + } + + // Axo SDK options + const sdkClientToken = config?.axo?.sdk_client_token; + const uuid = uuidv4().replace( /-/g, '' ); + if ( sdkClientToken ) { + scriptOptions[ 'data-sdk-client-token' ] = sdkClientToken; + scriptOptions[ 'data-client-metadata-id' ] = uuid; + } + + // Load PayPal script for special case with data-client-token + if ( config.data_client_id?.set_attribute ) { + dataClientIdAttributeHandler( + scriptOptions, + config.data_client_id, + callback, + errorCallback + ); + return; + } + + // Adds data-user-id-token to script options. + const userIdToken = config?.save_payment_methods?.id_token; + if ( userIdToken && ! sdkClientToken ) { + scriptOptions[ 'data-user-id-token' ] = userIdToken; + } + + // Load PayPal script + loadScript( scriptOptions ).then( callback ).catch( errorCallback ); }; -export const loadPaypalScript = (config, onLoaded, onError = null) => { - // If PayPal is already loaded call the onLoaded callback and return. - if (typeof paypal !== 'undefined') { - onLoaded(); - return; - } +export const loadPaypalScriptPromise = ( config ) => { + return new Promise( ( resolve, reject ) => { + loadPaypalScript( config, resolve, reject ); + } ); +}; - // Add the onLoaded callback to the onLoadedCallbacks stack. - options.onLoadedCallbacks.push(onLoaded); - if (onError) { - options.onErrorCallbacks.push(onError); - } +export const loadPaypalJsScript = ( options, buttons, container ) => { + loadScript( options ).then( ( paypal ) => { + paypal.Buttons( buttons ).render( container ); + } ); +}; - // Return if it's still loading. - if (options.isLoading) { - return; - } - options.isLoading = true; - - const resetState = () => { - options.isLoading = false; - options.onLoadedCallbacks = []; - options.onErrorCallbacks = []; - } - - // Callback to be called once the PayPal script is loaded. - const callback = (paypal) => { - widgetBuilder.setPaypal(paypal); - - for (const onLoadedCallback of options.onLoadedCallbacks) { - onLoadedCallback(); - } - - resetState(); - } - const errorCallback = (err) => { - for (const onErrorCallback of options.onErrorCallbacks) { - onErrorCallback(err); - } - - resetState(); - } - - // Build the PayPal script options. - let scriptOptions = keysToCamelCase(config.url_params); - if (config.script_attributes) { - scriptOptions = merge(scriptOptions, config.script_attributes); - } - - // Axo SDK options - const sdkClientToken = config?.axo?.sdk_client_token; - const uuid = uuidv4().replace(/-/g, ''); - if(sdkClientToken) { - scriptOptions['data-sdk-client-token'] = sdkClientToken; - scriptOptions['data-client-metadata-id'] = uuid; - } - - // Load PayPal script for special case with data-client-token - if (config.data_client_id?.set_attribute) { - dataClientIdAttributeHandler(scriptOptions, config.data_client_id, callback, errorCallback); - return; - } - - // Adds data-user-id-token to script options. - const userIdToken = config?.save_payment_methods?.id_token; - if(userIdToken && !sdkClientToken) { - scriptOptions['data-user-id-token'] = userIdToken; - } - - // Load PayPal script - loadScript(scriptOptions) - .then(callback) - .catch(errorCallback); -} - -export const loadPaypalScriptPromise = (config) => { - return new Promise((resolve, reject) => { - loadPaypalScript(config, resolve, reject) - }); -} - -export const loadPaypalJsScript = (options, buttons, container) => { - loadScript(options).then((paypal) => { - paypal.Buttons(buttons).render(container); - }); -} - -export const loadPaypalJsScriptPromise = (options) => { - return new Promise((resolve, reject) => { - loadScript(options) - .then(resolve) - .catch(reject); - }); -} +export const loadPaypalJsScriptPromise = ( options ) => { + return new Promise( ( resolve, reject ) => { + loadScript( options ).then( resolve ).catch( reject ); + } ); +}; diff --git a/modules/ppcp-button/resources/js/modules/Helper/ShippingHandler.js b/modules/ppcp-button/resources/js/modules/Helper/ShippingHandler.js index c67bee5bb..ae86ddea0 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ShippingHandler.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ShippingHandler.js @@ -1,128 +1,151 @@ -import {paypalAddressToWc} from "../../../../../ppcp-blocks/resources/js/Helper/Address.js"; -import {convertKeysToSnakeCase} from "../../../../../ppcp-blocks/resources/js/Helper/Helper.js"; +import { paypalAddressToWc } from '../../../../../ppcp-blocks/resources/js/Helper/Address.js'; +import { convertKeysToSnakeCase } from '../../../../../ppcp-blocks/resources/js/Helper/Helper.js'; /** * Handles the shipping option change in PayPal. * - * @param data - * @param actions - * @param config - * @returns {Promise} + * @param data + * @param actions + * @param config + * @return {Promise} */ -export const handleShippingOptionsChange = async (data, actions, config) => { - try { - const shippingOptionId = data.selectedShippingOption?.id; +export const handleShippingOptionsChange = async ( data, actions, config ) => { + try { + const shippingOptionId = data.selectedShippingOption?.id; - if (shippingOptionId) { - await fetch(config.ajax.update_customer_shipping.shipping_options.endpoint, { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - 'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce, - }, - body: JSON.stringify({ - rate_id: shippingOptionId, - }) - }) - .then(response => { - return response.json(); - }) - .then(cardData => { - const shippingMethods = document.querySelectorAll('.shipping_method'); + if ( shippingOptionId ) { + await fetch( + config.ajax.update_customer_shipping.shipping_options.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WC-Store-API-Nonce': + config.ajax.update_customer_shipping.wp_rest_nonce, + }, + body: JSON.stringify( { + rate_id: shippingOptionId, + } ), + } + ) + .then( ( response ) => { + return response.json(); + } ) + .then( ( cardData ) => { + const shippingMethods = + document.querySelectorAll( '.shipping_method' ); - shippingMethods.forEach(function(method) { - if (method.value === shippingOptionId) { - method.checked = true; - } - }); - }) - } + shippingMethods.forEach( function ( method ) { + if ( method.value === shippingOptionId ) { + method.checked = true; + } + } ); + } ); + } - if (!config.data_client_id.has_subscriptions) { - const res = await fetch(config.ajax.update_shipping.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.ajax.update_shipping.nonce, - order_id: data.orderID, - }) - }); + if ( ! config.data_client_id.has_subscriptions ) { + const res = await fetch( config.ajax.update_shipping.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.ajax.update_shipping.nonce, + order_id: data.orderID, + } ), + } ); - const json = await res.json(); + const json = await res.json(); - if (!json.success) { - throw new Error(json.data.message); - } - } - } catch (e) { - console.error(e); + if ( ! json.success ) { + throw new Error( json.data.message ); + } + } + } catch ( e ) { + console.error( e ); - actions.reject(); - } + actions.reject(); + } }; /** * Handles the shipping address change in PayPal. * - * @param data - * @param actions - * @param config - * @returns {Promise} + * @param data + * @param actions + * @param config + * @return {Promise} */ -export const handleShippingAddressChange = async (data, actions, config) => { - try { - const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress)); +export const handleShippingAddressChange = async ( data, actions, config ) => { + try { + const address = paypalAddressToWc( + convertKeysToSnakeCase( data.shippingAddress ) + ); - // Retrieve current cart contents - await fetch(config.ajax.update_customer_shipping.shipping_address.cart_endpoint) - .then(response => { - return response.json(); - }) - .then(cartData => { - // Update shipping address in the cart data - cartData.shipping_address.address_1 = address.address_1; - cartData.shipping_address.address_2 = address.address_2; - cartData.shipping_address.city = address.city; - cartData.shipping_address.state = address.state; - cartData.shipping_address.postcode = address.postcode; - cartData.shipping_address.country = address.country; + // Retrieve current cart contents + await fetch( + config.ajax.update_customer_shipping.shipping_address.cart_endpoint + ) + .then( ( response ) => { + return response.json(); + } ) + .then( ( cartData ) => { + // Update shipping address in the cart data + cartData.shipping_address.address_1 = address.address_1; + cartData.shipping_address.address_2 = address.address_2; + cartData.shipping_address.city = address.city; + cartData.shipping_address.state = address.state; + cartData.shipping_address.postcode = address.postcode; + cartData.shipping_address.country = address.country; - // Send update request - return fetch(config.ajax.update_customer_shipping.shipping_address.update_customer_endpoint, { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - 'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce, - }, - body: JSON.stringify({ - shipping_address: cartData.shipping_address, - }) - }).then(function (res) { - return res.json(); - }).then(function (customerData) { - jQuery(".cart_totals .shop_table").load(location.href + " " + ".cart_totals .shop_table" + ">*", ""); - }) - }) + // Send update request + return fetch( + config.ajax.update_customer_shipping.shipping_address + .update_customer_endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WC-Store-API-Nonce': + config.ajax.update_customer_shipping + .wp_rest_nonce, + }, + body: JSON.stringify( { + shipping_address: cartData.shipping_address, + } ), + } + ) + .then( function ( res ) { + return res.json(); + } ) + .then( function ( customerData ) { + jQuery( '.cart_totals .shop_table' ).load( + location.href + + ' ' + + '.cart_totals .shop_table' + + '>*', + '' + ); + } ); + } ); - const res = await fetch(config.ajax.update_shipping.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.ajax.update_shipping.nonce, - order_id: data.orderID, - }) - }); + const res = await fetch( config.ajax.update_shipping.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.ajax.update_shipping.nonce, + order_id: data.orderID, + } ), + } ); - const json = await res.json(); + const json = await res.json(); - if (!json.success) { - throw new Error(json.data.message); - } - } catch (e) { - console.error(e); + if ( ! json.success ) { + throw new Error( json.data.message ); + } + } catch ( e ) { + console.error( e ); - actions.reject(); - } + actions.reject(); + } }; diff --git a/modules/ppcp-button/resources/js/modules/Helper/SimulateCart.js b/modules/ppcp-button/resources/js/modules/Helper/SimulateCart.js index 106dfe989..4dd67be76 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/SimulateCart.js +++ b/modules/ppcp-button/resources/js/modules/Helper/SimulateCart.js @@ -1,48 +1,42 @@ class SimulateCart { + constructor( endpoint, nonce ) { + this.endpoint = endpoint; + this.nonce = nonce; + } - constructor(endpoint, nonce) - { - this.endpoint = endpoint; - this.nonce = nonce; - } + /** + * + * @param onResolve + * @param {Product[]} products + * @return {Promise} + */ + simulate( onResolve, products ) { + return new Promise( ( resolve, reject ) => { + fetch( this.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.nonce, + products, + } ), + } ) + .then( ( result ) => { + return result.json(); + } ) + .then( ( result ) => { + if ( ! result.success ) { + reject( result.data ); + return; + } - /** - * - * @param onResolve - * @param {Product[]} products - * @returns {Promise} - */ - simulate(onResolve, products) - { - return new Promise((resolve, reject) => { - fetch( - this.endpoint, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.nonce, - products, - }) - } - ).then( - (result) => { - return result.json(); - } - ).then((result) => { - if (! result.success) { - reject(result.data); - return; - } - - const resolved = onResolve(result.data); - resolve(resolved); - }) - }); - } + const resolved = onResolve( result.data ); + resolve( resolved ); + } ); + } ); + } } export default SimulateCart; diff --git a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js index 673e37a90..30d0830f9 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js @@ -1,28 +1,25 @@ class Spinner { + constructor( target = 'form.woocommerce-checkout' ) { + this.target = target; + } - constructor(target = 'form.woocommerce-checkout') { - this.target = target; - } + setTarget( target ) { + this.target = target; + } - setTarget(target) { - this.target = target; - } + block() { + jQuery( this.target ).block( { + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6, + }, + } ); + } - block() { - - jQuery( this.target ).block({ - message: null, - overlayCSS: { - background: '#fff', - opacity: 0.6 - } - }); - } - - unblock() { - - jQuery( this.target ).unblock(); - } + unblock() { + jQuery( this.target ).unblock(); + } } export default Spinner; diff --git a/modules/ppcp-button/resources/js/modules/Helper/Style.js b/modules/ppcp-button/resources/js/modules/Helper/Style.js index a7672cc78..3f3f64e04 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Style.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Style.js @@ -1,20 +1,20 @@ -export const normalizeStyleForFundingSource = (style, fundingSource) => { - const commonProps = {}; - ['shape', 'height'].forEach(prop => { - if (style[prop]) { - commonProps[prop] = style[prop]; - } - }); +export const normalizeStyleForFundingSource = ( style, fundingSource ) => { + const commonProps = {}; + [ 'shape', 'height' ].forEach( ( prop ) => { + if ( style[ prop ] ) { + commonProps[ prop ] = style[ prop ]; + } + } ); - switch (fundingSource) { - case 'paypal': - return style; - case 'paylater': - return { - color: style.color, - ...commonProps - }; - default: - return commonProps; - } -} + switch ( fundingSource ) { + case 'paypal': + return style; + case 'paylater': + return { + color: style.color, + ...commonProps, + }; + default: + return commonProps; + } +}; diff --git a/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js b/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js index 4366fd9d2..e4bf0f389 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js @@ -1,20 +1,28 @@ export const isChangePaymentPage = () => { - const urlParams = new URLSearchParams(window.location.search) - return urlParams.has('change_payment_method'); -} + const urlParams = new URLSearchParams( window.location.search ); + return urlParams.has( 'change_payment_method' ); +}; -export const getPlanIdFromVariation = (variation) => { - let subscription_plan = ''; - PayPalCommerceGateway.variable_paypal_subscription_variations.forEach((element) => { - let obj = {}; - variation.forEach(({name, value}) => { - Object.assign(obj, {[name.replace('attribute_', '')]: value}); - }) +export const getPlanIdFromVariation = ( variation ) => { + let subscription_plan = ''; + PayPalCommerceGateway.variable_paypal_subscription_variations.forEach( + ( element ) => { + const obj = {}; + variation.forEach( ( { name, value } ) => { + Object.assign( obj, { + [ name.replace( 'attribute_', '' ) ]: value, + } ); + } ); - if(JSON.stringify(obj) === JSON.stringify(element.attributes) && element.subscription_plan !== '') { - subscription_plan = element.subscription_plan; - } - }); + if ( + JSON.stringify( obj ) === + JSON.stringify( element.attributes ) && + element.subscription_plan !== '' + ) { + subscription_plan = element.subscription_plan; + } + } + ); - return subscription_plan; -} + return subscription_plan; +}; diff --git a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js index f79f8613b..454eacfc6 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js +++ b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js @@ -1,51 +1,45 @@ -import Product from "../Entity/Product"; +import Product from '../Entity/Product'; class UpdateCart { + constructor( endpoint, nonce ) { + this.endpoint = endpoint; + this.nonce = nonce; + } - constructor(endpoint, nonce) - { - this.endpoint = endpoint; - this.nonce = nonce; - } + /** + * + * @param onResolve + * @param {Product[]} products + * @param {Object} options + * @return {Promise} + */ + update( onResolve, products, options = {} ) { + return new Promise( ( resolve, reject ) => { + fetch( this.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.nonce, + products, + ...options, + } ), + } ) + .then( ( result ) => { + return result.json(); + } ) + .then( ( result ) => { + if ( ! result.success ) { + reject( result.data ); + return; + } - /** - * - * @param onResolve - * @param {Product[]} products - * @param {Object} options - * @returns {Promise} - */ - update(onResolve, products, options = {}) - { - return new Promise((resolve, reject) => { - fetch( - this.endpoint, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.nonce, - products, - ...options - }) - } - ).then( - (result) => { - return result.json(); - } - ).then((result) => { - if (! result.success) { - reject(result.data); - return; - } - - const resolved = onResolve(result.data); - resolve(resolved); - }) - }); - } + const resolved = onResolve( result.data ); + resolve( resolved ); + } ); + } ); + } } export default UpdateCart; diff --git a/modules/ppcp-button/resources/js/modules/Helper/Utils.js b/modules/ppcp-button/resources/js/modules/Helper/Utils.js index c1fbb8aec..cf4121ead 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Utils.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Utils.js @@ -1,69 +1,69 @@ -export const toCamelCase = (str) => { - return str.replace(/([-_]\w)/g, function(match) { - return match[1].toUpperCase(); - }); -} - -export const keysToCamelCase = (obj) => { - let output = {}; - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - output[toCamelCase(key)] = obj[key]; - } - } - return output; -} - -export const strAddWord = (str, word, separator = ',') => { - let arr = str.split(separator); - if (!arr.includes(word)) { - arr.push(word); - } - return arr.join(separator); +export const toCamelCase = ( str ) => { + return str.replace( /([-_]\w)/g, function ( match ) { + return match[ 1 ].toUpperCase(); + } ); }; -export const strRemoveWord = (str, word, separator = ',') => { - let arr = str.split(separator); - let index = arr.indexOf(word); - if (index !== -1) { - arr.splice(index, 1); - } - return arr.join(separator); +export const keysToCamelCase = ( obj ) => { + const output = {}; + for ( const key in obj ) { + if ( Object.prototype.hasOwnProperty.call( obj, key ) ) { + output[ toCamelCase( key ) ] = obj[ key ]; + } + } + return output; }; -export const throttle = (func, limit) => { - let inThrottle, lastArgs, lastContext; +export const strAddWord = ( str, word, separator = ',' ) => { + const arr = str.split( separator ); + if ( ! arr.includes( word ) ) { + arr.push( word ); + } + return arr.join( separator ); +}; - function execute() { - inThrottle = true; - func.apply(this, arguments); - setTimeout(() => { - inThrottle = false; - if (lastArgs) { - const nextArgs = lastArgs; - const nextContext = lastContext; - lastArgs = lastContext = null; - execute.apply(nextContext, nextArgs); - } - }, limit); - } +export const strRemoveWord = ( str, word, separator = ',' ) => { + const arr = str.split( separator ); + const index = arr.indexOf( word ); + if ( index !== -1 ) { + arr.splice( index, 1 ); + } + return arr.join( separator ); +}; - return function() { - if (!inThrottle) { - execute.apply(this, arguments); - } else { - lastArgs = arguments; - lastContext = this; - } - }; -} +export const throttle = ( func, limit ) => { + let inThrottle, lastArgs, lastContext; + + function execute() { + inThrottle = true; + func.apply( this, arguments ); + setTimeout( () => { + inThrottle = false; + if ( lastArgs ) { + const nextArgs = lastArgs; + const nextContext = lastContext; + lastArgs = lastContext = null; + execute.apply( nextContext, nextArgs ); + } + }, limit ); + } + + return function () { + if ( ! inThrottle ) { + execute.apply( this, arguments ); + } else { + lastArgs = arguments; + lastContext = this; + } + }; +}; const Utils = { - toCamelCase, - keysToCamelCase, - strAddWord, - strRemoveWord, - throttle + toCamelCase, + keysToCamelCase, + strAddWord, + strRemoveWord, + throttle, }; export default Utils; diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js index b983689ce..c60c163fd 100644 --- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js +++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js @@ -1,34 +1,38 @@ -const onApprove = (context, errorHandler) => { - return (data, actions) => { - return fetch(context.config.ajax.approve_order.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: context.config.ajax.approve_order.nonce, - order_id:data.orderID, - funding_source: window.ppcpFundingSource, - should_create_wc_order: !context.config.vaultingEnabled || data.paymentSource !== 'venmo' - }) - }).then((res)=>{ - return res.json(); - }).then((data)=>{ - if (!data.success) { - errorHandler.genericError(); - return actions.restart().catch(err => { - errorHandler.genericError(); - }); - } +const onApprove = ( context, errorHandler ) => { + return ( data, actions ) => { + return fetch( context.config.ajax.approve_order.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: context.config.ajax.approve_order.nonce, + order_id: data.orderID, + funding_source: window.ppcpFundingSource, + should_create_wc_order: + ! context.config.vaultingEnabled || + data.paymentSource !== 'venmo', + } ), + } ) + .then( ( res ) => { + return res.json(); + } ) + .then( ( data ) => { + if ( ! data.success ) { + errorHandler.genericError(); + return actions.restart().catch( ( err ) => { + errorHandler.genericError(); + } ); + } - let orderReceivedUrl = data.data?.order_received_url + const orderReceivedUrl = data.data?.order_received_url; - location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect; - - }); - - } -} + location.href = orderReceivedUrl + ? orderReceivedUrl + : context.config.redirect; + } ); + }; +}; export default onApprove; diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js index e8fcbde09..629e8871a 100644 --- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js +++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js @@ -1,38 +1,42 @@ -const onApprove = (context, errorHandler, spinner) => { - return (data, actions) => { - spinner.block(); - errorHandler.clear(); +const onApprove = ( context, errorHandler, spinner ) => { + return ( data, actions ) => { + spinner.block(); + errorHandler.clear(); - return fetch(context.config.ajax.approve_order.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: context.config.ajax.approve_order.nonce, - order_id:data.orderID, - funding_source: window.ppcpFundingSource, - }) - }).then((res)=>{ - return res.json(); - }).then((data)=>{ - spinner.unblock(); - if (!data.success) { - if (data.data.code === 100) { - errorHandler.message(data.data.message); - } else { - errorHandler.genericError(); - } - if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') { - return actions.restart(); - } - throw new Error(data.data.message); - } - document.querySelector('#place_order').click() - }); - - } -} + return fetch( context.config.ajax.approve_order.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: context.config.ajax.approve_order.nonce, + order_id: data.orderID, + funding_source: window.ppcpFundingSource, + } ), + } ) + .then( ( res ) => { + return res.json(); + } ) + .then( ( data ) => { + spinner.unblock(); + if ( ! data.success ) { + if ( data.data.code === 100 ) { + errorHandler.message( data.data.message ); + } else { + errorHandler.genericError(); + } + if ( + typeof actions !== 'undefined' && + typeof actions.restart !== 'undefined' + ) { + return actions.restart(); + } + throw new Error( data.data.message ); + } + document.querySelector( '#place_order' ).click(); + } ); + }; +}; export default onApprove; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js index 73532a727..b3e70011f 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js @@ -1,152 +1,186 @@ -import {show} from "../Helper/Hiding"; -import {cardFieldStyles} from "../Helper/CardFieldsHelper"; +import { show } from '../Helper/Hiding'; +import { cardFieldStyles } from '../Helper/CardFieldsHelper'; class CardFieldsRenderer { + constructor( + defaultConfig, + errorHandler, + spinner, + onCardFieldsBeforeSubmit + ) { + this.defaultConfig = defaultConfig; + this.errorHandler = errorHandler; + this.spinner = spinner; + this.cardValid = false; + this.formValid = false; + this.emptyFields = new Set( [ 'number', 'cvv', 'expirationDate' ] ); + this.currentHostedFieldsInstance = null; + this.onCardFieldsBeforeSubmit = onCardFieldsBeforeSubmit; + } - constructor(defaultConfig, errorHandler, spinner, onCardFieldsBeforeSubmit) { - this.defaultConfig = defaultConfig; - this.errorHandler = errorHandler; - this.spinner = spinner; - this.cardValid = false; - this.formValid = false; - this.emptyFields = new Set(['number', 'cvv', 'expirationDate']); - this.currentHostedFieldsInstance = null; - this.onCardFieldsBeforeSubmit = onCardFieldsBeforeSubmit; - } + render( wrapper, contextConfig ) { + if ( + ( this.defaultConfig.context !== 'checkout' && + this.defaultConfig.context !== 'pay-now' ) || + wrapper === null || + document.querySelector( wrapper ) === null + ) { + return; + } - render(wrapper, contextConfig) { - if ( - ( - this.defaultConfig.context !== 'checkout' - && this.defaultConfig.context !== 'pay-now' - ) - || wrapper === null - || document.querySelector(wrapper) === null - ) { - return; - } + const buttonSelector = wrapper + ' button'; - const buttonSelector = wrapper + ' button'; + const gateWayBox = document.querySelector( + '.payment_box.payment_method_ppcp-credit-card-gateway' + ); + if ( ! gateWayBox ) { + return; + } - const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway'); - if (!gateWayBox) { - return - } + const oldDisplayStyle = gateWayBox.style.display; + gateWayBox.style.display = 'block'; - const oldDisplayStyle = gateWayBox.style.display; - gateWayBox.style.display = 'block'; + const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' ); + if ( hideDccGateway ) { + hideDccGateway.parentNode.removeChild( hideDccGateway ); + } - const hideDccGateway = document.querySelector('#ppcp-hide-dcc'); - if (hideDccGateway) { - hideDccGateway.parentNode.removeChild(hideDccGateway); - } + const cardField = paypal.CardFields( { + createOrder: contextConfig.createOrder, + onApprove( data ) { + return contextConfig.onApprove( data ); + }, + onError( error ) { + console.error( error ); + this.spinner.unblock(); + }, + } ); - const cardField = paypal.CardFields({ - createOrder: contextConfig.createOrder, - onApprove: function (data) { - return contextConfig.onApprove(data); - }, - onError: function (error) { - console.error(error) - this.spinner.unblock(); - } - }); + if ( cardField.isEligible() ) { + const nameField = document.getElementById( + 'ppcp-credit-card-gateway-card-name' + ); + if ( nameField ) { + const styles = cardFieldStyles( nameField ); + const fieldOptions = { + style: { input: styles }, + }; + if ( nameField.getAttribute( 'placeholder' ) ) { + fieldOptions.placeholder = + nameField.getAttribute( 'placeholder' ); + } + cardField + .NameField( fieldOptions ) + .render( nameField.parentNode ); + nameField.remove(); + } - if (cardField.isEligible()) { - const nameField = document.getElementById('ppcp-credit-card-gateway-card-name'); - if (nameField) { - let styles = cardFieldStyles(nameField); - let fieldOptions = { - style: { 'input': styles } - } - if (nameField.getAttribute('placeholder')) { - fieldOptions.placeholder = nameField.getAttribute('placeholder'); - } - cardField.NameField(fieldOptions).render(nameField.parentNode); - nameField.remove(); - } + const numberField = document.getElementById( + 'ppcp-credit-card-gateway-card-number' + ); + if ( numberField ) { + const styles = cardFieldStyles( numberField ); + const fieldOptions = { + style: { input: styles }, + }; + if ( numberField.getAttribute( 'placeholder' ) ) { + fieldOptions.placeholder = + numberField.getAttribute( 'placeholder' ); + } + cardField + .NumberField( fieldOptions ) + .render( numberField.parentNode ); + numberField.remove(); + } - const numberField = document.getElementById('ppcp-credit-card-gateway-card-number'); - if (numberField) { - let styles = cardFieldStyles(numberField); - let fieldOptions = { - style: { 'input': styles } - } - if (numberField.getAttribute('placeholder')) { - fieldOptions.placeholder = numberField.getAttribute('placeholder'); - } - cardField.NumberField(fieldOptions).render(numberField.parentNode); - numberField.remove(); - } + const expiryField = document.getElementById( + 'ppcp-credit-card-gateway-card-expiry' + ); + if ( expiryField ) { + const styles = cardFieldStyles( expiryField ); + const fieldOptions = { + style: { input: styles }, + }; + if ( expiryField.getAttribute( 'placeholder' ) ) { + fieldOptions.placeholder = + expiryField.getAttribute( 'placeholder' ); + } + cardField + .ExpiryField( fieldOptions ) + .render( expiryField.parentNode ); + expiryField.remove(); + } - const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry'); - if (expiryField) { - let styles = cardFieldStyles(expiryField); - let fieldOptions = { - style: { 'input': styles } - } - if (expiryField.getAttribute('placeholder')) { - fieldOptions.placeholder = expiryField.getAttribute('placeholder'); - } - cardField.ExpiryField(fieldOptions).render(expiryField.parentNode); - expiryField.remove(); - } + const cvvField = document.getElementById( + 'ppcp-credit-card-gateway-card-cvc' + ); + if ( cvvField ) { + const styles = cardFieldStyles( cvvField ); + const fieldOptions = { + style: { input: styles }, + }; + if ( cvvField.getAttribute( 'placeholder' ) ) { + fieldOptions.placeholder = + cvvField.getAttribute( 'placeholder' ); + } + cardField + .CVVField( fieldOptions ) + .render( cvvField.parentNode ); + cvvField.remove(); + } - const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc'); - if (cvvField) { - let styles = cardFieldStyles(cvvField); - let fieldOptions = { - style: { 'input': styles } - } - if (cvvField.getAttribute('placeholder')) { - fieldOptions.placeholder = cvvField.getAttribute('placeholder'); - } - cardField.CVVField(fieldOptions).render(cvvField.parentNode); - cvvField.remove(); - } + document.dispatchEvent( new CustomEvent( 'hosted_fields_loaded' ) ); + } - document.dispatchEvent(new CustomEvent("hosted_fields_loaded")); - } + gateWayBox.style.display = oldDisplayStyle; - gateWayBox.style.display = oldDisplayStyle; + show( buttonSelector ); - show(buttonSelector); + if ( this.defaultConfig.cart_contains_subscription ) { + const saveToAccount = document.querySelector( + '#wc-ppcp-credit-card-gateway-new-payment-method' + ); + if ( saveToAccount ) { + saveToAccount.checked = true; + saveToAccount.disabled = true; + } + } - if(this.defaultConfig.cart_contains_subscription) { - const saveToAccount = document.querySelector('#wc-ppcp-credit-card-gateway-new-payment-method'); - if(saveToAccount) { - saveToAccount.checked = true; - saveToAccount.disabled = true; - } - } + document + .querySelector( buttonSelector ) + .addEventListener( 'click', ( event ) => { + event.preventDefault(); + this.spinner.block(); + this.errorHandler.clear(); - document.querySelector(buttonSelector).addEventListener("click", (event) => { - event.preventDefault(); - this.spinner.block(); - this.errorHandler.clear(); + const paymentToken = document.querySelector( + 'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked' + )?.value; + if ( paymentToken && paymentToken !== 'new' ) { + document.querySelector( '#place_order' ).click(); + return; + } - const paymentToken = document.querySelector('input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked')?.value - if(paymentToken && paymentToken !== 'new') { - document.querySelector('#place_order').click(); - return; - } + if ( + typeof this.onCardFieldsBeforeSubmit === 'function' && + ! this.onCardFieldsBeforeSubmit() + ) { + this.spinner.unblock(); + return; + } - if (typeof this.onCardFieldsBeforeSubmit === 'function' && !this.onCardFieldsBeforeSubmit()) { - this.spinner.unblock(); - return; - } + cardField.submit().catch( ( error ) => { + this.spinner.unblock(); + console.error( error ); + this.errorHandler.message( + this.defaultConfig.hosted_fields.labels.fields_not_valid + ); + } ); + } ); + } - cardField.submit() - .catch((error) => { - this.spinner.unblock(); - console.error(error) - this.errorHandler.message(this.defaultConfig.hosted_fields.labels.fields_not_valid); - }); - }); - } - - disableFields() {} - enableFields() {} + disableFields() {} + enableFields() {} } export default CardFieldsRenderer; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/HostedFieldsRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/HostedFieldsRenderer.js index 7a2c5043e..f49d9e9ed 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/HostedFieldsRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/HostedFieldsRenderer.js @@ -1,274 +1,357 @@ -import dccInputFactory from "../Helper/DccInputFactory"; -import {show} from "../Helper/Hiding"; +import dccInputFactory from '../Helper/DccInputFactory'; +import { show } from '../Helper/Hiding'; class HostedFieldsRenderer { + constructor( defaultConfig, errorHandler, spinner ) { + this.defaultConfig = defaultConfig; + this.errorHandler = errorHandler; + this.spinner = spinner; + this.cardValid = false; + this.formValid = false; + this.emptyFields = new Set( [ 'number', 'cvv', 'expirationDate' ] ); + this.currentHostedFieldsInstance = null; + } - constructor(defaultConfig, errorHandler, spinner) { - this.defaultConfig = defaultConfig; - this.errorHandler = errorHandler; - this.spinner = spinner; - this.cardValid = false; - this.formValid = false; - this.emptyFields = new Set(['number', 'cvv', 'expirationDate']); - this.currentHostedFieldsInstance = null; - } + render( wrapper, contextConfig ) { + if ( + ( this.defaultConfig.context !== 'checkout' && + this.defaultConfig.context !== 'pay-now' ) || + wrapper === null || + document.querySelector( wrapper ) === null + ) { + return; + } - render(wrapper, contextConfig) { - if ( - ( - this.defaultConfig.context !== 'checkout' - && this.defaultConfig.context !== 'pay-now' - ) - || wrapper === null - || document.querySelector(wrapper) === null - ) { - return; - } + if ( + typeof paypal.HostedFields !== 'undefined' && + paypal.HostedFields.isEligible() + ) { + const buttonSelector = wrapper + ' button'; - if (typeof paypal.HostedFields !== 'undefined' && paypal.HostedFields.isEligible()) { - const buttonSelector = wrapper + ' button'; + if ( this.currentHostedFieldsInstance ) { + this.currentHostedFieldsInstance + .teardown() + .catch( ( err ) => + console.error( + `Hosted fields teardown error: ${ err }` + ) + ); + this.currentHostedFieldsInstance = null; + } - if (this.currentHostedFieldsInstance) { - this.currentHostedFieldsInstance.teardown() - .catch(err => console.error(`Hosted fields teardown error: ${err}`)); - this.currentHostedFieldsInstance = null; - } + const gateWayBox = document.querySelector( + '.payment_box.payment_method_ppcp-credit-card-gateway' + ); + if ( ! gateWayBox ) { + return; + } + const oldDisplayStyle = gateWayBox.style.display; + gateWayBox.style.display = 'block'; - const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway'); - if (!gateWayBox) { - return - } - const oldDisplayStyle = gateWayBox.style.display; - gateWayBox.style.display = 'block'; + const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' ); + if ( hideDccGateway ) { + hideDccGateway.parentNode.removeChild( hideDccGateway ); + } - const hideDccGateway = document.querySelector('#ppcp-hide-dcc'); - if (hideDccGateway) { - hideDccGateway.parentNode.removeChild(hideDccGateway); - } + const cardNumberField = document.querySelector( + '#ppcp-credit-card-gateway-card-number' + ); - const cardNumberField = document.querySelector('#ppcp-credit-card-gateway-card-number'); + const stylesRaw = window.getComputedStyle( cardNumberField ); + const styles = {}; + Object.values( stylesRaw ).forEach( ( prop ) => { + if ( ! stylesRaw[ prop ] ) { + return; + } + styles[ prop ] = '' + stylesRaw[ prop ]; + } ); - const stylesRaw = window.getComputedStyle(cardNumberField); - let styles = {}; - Object.values(stylesRaw).forEach((prop) => { - if (!stylesRaw[prop]) { - return; - } - styles[prop] = '' + stylesRaw[prop]; - }); + const cardNumber = dccInputFactory( cardNumberField ); + cardNumberField.parentNode.replaceChild( + cardNumber, + cardNumberField + ); - const cardNumber = dccInputFactory(cardNumberField); - cardNumberField.parentNode.replaceChild(cardNumber, cardNumberField); + const cardExpiryField = document.querySelector( + '#ppcp-credit-card-gateway-card-expiry' + ); + const cardExpiry = dccInputFactory( cardExpiryField ); + cardExpiryField.parentNode.replaceChild( + cardExpiry, + cardExpiryField + ); - const cardExpiryField = document.querySelector('#ppcp-credit-card-gateway-card-expiry'); - const cardExpiry = dccInputFactory(cardExpiryField); - cardExpiryField.parentNode.replaceChild(cardExpiry, cardExpiryField); + const cardCodeField = document.querySelector( + '#ppcp-credit-card-gateway-card-cvc' + ); + const cardCode = dccInputFactory( cardCodeField ); + cardCodeField.parentNode.replaceChild( cardCode, cardCodeField ); - const cardCodeField = document.querySelector('#ppcp-credit-card-gateway-card-cvc'); - const cardCode = dccInputFactory(cardCodeField); - cardCodeField.parentNode.replaceChild(cardCode, cardCodeField); + gateWayBox.style.display = oldDisplayStyle; - gateWayBox.style.display = oldDisplayStyle; + const formWrapper = + '.payment_box payment_method_ppcp-credit-card-gateway'; + if ( + this.defaultConfig.enforce_vault && + document.querySelector( + formWrapper + ' .ppcp-credit-card-vault' + ) + ) { + document.querySelector( + formWrapper + ' .ppcp-credit-card-vault' + ).checked = true; + document + .querySelector( formWrapper + ' .ppcp-credit-card-vault' ) + .setAttribute( 'disabled', true ); + } + paypal.HostedFields.render( { + createOrder: contextConfig.createOrder, + styles: { + input: styles, + }, + fields: { + number: { + selector: '#ppcp-credit-card-gateway-card-number', + placeholder: + this.defaultConfig.hosted_fields.labels + .credit_card_number, + }, + cvv: { + selector: '#ppcp-credit-card-gateway-card-cvc', + placeholder: + this.defaultConfig.hosted_fields.labels.cvv, + }, + expirationDate: { + selector: '#ppcp-credit-card-gateway-card-expiry', + placeholder: + this.defaultConfig.hosted_fields.labels.mm_yy, + }, + }, + } ).then( ( hostedFields ) => { + document.dispatchEvent( + new CustomEvent( 'hosted_fields_loaded' ) + ); + this.currentHostedFieldsInstance = hostedFields; - const formWrapper = '.payment_box payment_method_ppcp-credit-card-gateway'; - if ( - this.defaultConfig.enforce_vault - && document.querySelector(formWrapper + ' .ppcp-credit-card-vault') - ) { - document.querySelector(formWrapper + ' .ppcp-credit-card-vault').checked = true; - document.querySelector(formWrapper + ' .ppcp-credit-card-vault').setAttribute('disabled', true); - } - paypal.HostedFields.render({ - createOrder: contextConfig.createOrder, - styles: { - 'input': styles - }, - fields: { - number: { - selector: '#ppcp-credit-card-gateway-card-number', - placeholder: this.defaultConfig.hosted_fields.labels.credit_card_number, - }, - cvv: { - selector: '#ppcp-credit-card-gateway-card-cvc', - placeholder: this.defaultConfig.hosted_fields.labels.cvv, - }, - expirationDate: { - selector: '#ppcp-credit-card-gateway-card-expiry', - placeholder: this.defaultConfig.hosted_fields.labels.mm_yy, - } - } - }).then(hostedFields => { - document.dispatchEvent(new CustomEvent("hosted_fields_loaded")); - this.currentHostedFieldsInstance = hostedFields; + hostedFields.on( 'inputSubmitRequest', () => { + this._submit( contextConfig ); + } ); + hostedFields.on( 'cardTypeChange', ( event ) => { + if ( ! event.cards.length ) { + this.cardValid = false; + return; + } + const validCards = + this.defaultConfig.hosted_fields.valid_cards; + this.cardValid = + validCards.indexOf( event.cards[ 0 ].type ) !== -1; - hostedFields.on('inputSubmitRequest', () => { - this._submit(contextConfig); - }); - hostedFields.on('cardTypeChange', (event) => { - if (!event.cards.length) { - this.cardValid = false; - return; - } - const validCards = this.defaultConfig.hosted_fields.valid_cards; - this.cardValid = validCards.indexOf(event.cards[0].type) !== -1; + const className = this._cardNumberFiledCLassNameByCardType( + event.cards[ 0 ].type + ); + this._recreateElementClassAttribute( + cardNumber, + cardNumberField.className + ); + if ( event.cards.length === 1 ) { + cardNumber.classList.add( className ); + } + } ); + hostedFields.on( 'validityChange', ( event ) => { + this.formValid = Object.keys( event.fields ).every( + function ( key ) { + return event.fields[ key ].isValid; + } + ); + } ); + hostedFields.on( 'empty', ( event ) => { + this._recreateElementClassAttribute( + cardNumber, + cardNumberField.className + ); + this.emptyFields.add( event.emittedBy ); + } ); + hostedFields.on( 'notEmpty', ( event ) => { + this.emptyFields.delete( event.emittedBy ); + } ); - const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type); - this._recreateElementClassAttribute(cardNumber, cardNumberField.className); - if (event.cards.length === 1) { - cardNumber.classList.add(className); - } - }) - hostedFields.on('validityChange', (event) => { - this.formValid = Object.keys(event.fields).every(function (key) { - return event.fields[key].isValid; - }); - }); - hostedFields.on('empty', (event) => { - this._recreateElementClassAttribute(cardNumber, cardNumberField.className); - this.emptyFields.add(event.emittedBy); - }); - hostedFields.on('notEmpty', (event) => { - this.emptyFields.delete(event.emittedBy); - }); + show( buttonSelector ); - show(buttonSelector); + if ( + document + .querySelector( wrapper ) + .getAttribute( 'data-ppcp-subscribed' ) !== true + ) { + document + .querySelector( buttonSelector ) + .addEventListener( 'click', ( event ) => { + event.preventDefault(); + this._submit( contextConfig ); + } ); - if (document.querySelector(wrapper).getAttribute('data-ppcp-subscribed') !== true) { - document.querySelector(buttonSelector).addEventListener( - 'click', - event => { - event.preventDefault(); - this._submit(contextConfig); - } - ); + document + .querySelector( wrapper ) + .setAttribute( 'data-ppcp-subscribed', true ); + } + } ); - document.querySelector(wrapper).setAttribute('data-ppcp-subscribed', true); - } - }); + document + .querySelector( '#payment_method_ppcp-credit-card-gateway' ) + .addEventListener( 'click', () => { + document + .querySelector( + 'label[for=ppcp-credit-card-gateway-card-number]' + ) + .click(); + } ); - document.querySelector('#payment_method_ppcp-credit-card-gateway').addEventListener( - 'click', - () => { - document.querySelector('label[for=ppcp-credit-card-gateway-card-number]').click(); - } - ) + return; + } - return; - } + const wrapperElement = document.querySelector( wrapper ); + wrapperElement.parentNode.removeChild( wrapperElement ); + } - const wrapperElement = document.querySelector(wrapper); - wrapperElement.parentNode.removeChild(wrapperElement); - } + disableFields() { + if ( this.currentHostedFieldsInstance ) { + this.currentHostedFieldsInstance.setAttribute( { + field: 'number', + attribute: 'disabled', + } ); + this.currentHostedFieldsInstance.setAttribute( { + field: 'cvv', + attribute: 'disabled', + } ); + this.currentHostedFieldsInstance.setAttribute( { + field: 'expirationDate', + attribute: 'disabled', + } ); + } + } - disableFields() { - if (this.currentHostedFieldsInstance) { - this.currentHostedFieldsInstance.setAttribute({ - field: 'number', - attribute: 'disabled' - }) - this.currentHostedFieldsInstance.setAttribute({ - field: 'cvv', - attribute: 'disabled' - }) - this.currentHostedFieldsInstance.setAttribute({ - field: 'expirationDate', - attribute: 'disabled' - }) - } - } + enableFields() { + if ( this.currentHostedFieldsInstance ) { + this.currentHostedFieldsInstance.removeAttribute( { + field: 'number', + attribute: 'disabled', + } ); + this.currentHostedFieldsInstance.removeAttribute( { + field: 'cvv', + attribute: 'disabled', + } ); + this.currentHostedFieldsInstance.removeAttribute( { + field: 'expirationDate', + attribute: 'disabled', + } ); + } + } - enableFields() { - if (this.currentHostedFieldsInstance) { - this.currentHostedFieldsInstance.removeAttribute({ - field: 'number', - attribute: 'disabled' - }) - this.currentHostedFieldsInstance.removeAttribute({ - field: 'cvv', - attribute: 'disabled' - }) - this.currentHostedFieldsInstance.removeAttribute({ - field: 'expirationDate', - attribute: 'disabled' - }) - } - } + _submit( contextConfig ) { + this.spinner.block(); + this.errorHandler.clear(); - _submit(contextConfig) { - this.spinner.block(); - this.errorHandler.clear(); + if ( this.formValid && this.cardValid ) { + const save_card = this.defaultConfig.can_save_vault_token + ? true + : false; + let vault = document.getElementById( 'ppcp-credit-card-vault' ) + ? document.getElementById( 'ppcp-credit-card-vault' ).checked + : save_card; + if ( this.defaultConfig.enforce_vault ) { + vault = true; + } + const contingency = this.defaultConfig.hosted_fields.contingency; + const hostedFieldsData = { + vault, + }; + if ( contingency !== 'NO_3D_SECURE' ) { + hostedFieldsData.contingencies = [ contingency ]; + } - if (this.formValid && this.cardValid) { - const save_card = this.defaultConfig.can_save_vault_token ? true : false; - let vault = document.getElementById('ppcp-credit-card-vault') ? - document.getElementById('ppcp-credit-card-vault').checked : save_card; - if (this.defaultConfig.enforce_vault) { - vault = true; - } - const contingency = this.defaultConfig.hosted_fields.contingency; - const hostedFieldsData = { - vault: vault - }; - if (contingency !== 'NO_3D_SECURE') { - hostedFieldsData.contingencies = [contingency]; - } + if ( this.defaultConfig.payer ) { + hostedFieldsData.cardholderName = + this.defaultConfig.payer.name.given_name + + ' ' + + this.defaultConfig.payer.name.surname; + } + if ( ! hostedFieldsData.cardholderName ) { + const firstName = document.getElementById( + 'billing_first_name' + ) + ? document.getElementById( 'billing_first_name' ).value + : ''; + const lastName = document.getElementById( 'billing_last_name' ) + ? document.getElementById( 'billing_last_name' ).value + : ''; - if (this.defaultConfig.payer) { - hostedFieldsData.cardholderName = this.defaultConfig.payer.name.given_name + ' ' + this.defaultConfig.payer.name.surname; - } - if (!hostedFieldsData.cardholderName) { - const firstName = document.getElementById('billing_first_name') ? document.getElementById('billing_first_name').value : ''; - const lastName = document.getElementById('billing_last_name') ? document.getElementById('billing_last_name').value : ''; + hostedFieldsData.cardholderName = firstName + ' ' + lastName; + } - hostedFieldsData.cardholderName = firstName + ' ' + lastName; - } + this.currentHostedFieldsInstance + .submit( hostedFieldsData ) + .then( ( payload ) => { + payload.orderID = payload.orderId; + this.spinner.unblock(); + return contextConfig.onApprove( payload ); + } ) + .catch( ( err ) => { + this.spinner.unblock(); + this.errorHandler.clear(); - this.currentHostedFieldsInstance.submit(hostedFieldsData).then((payload) => { - payload.orderID = payload.orderId; - this.spinner.unblock(); - return contextConfig.onApprove(payload); - }).catch(err => { - this.spinner.unblock(); - this.errorHandler.clear(); + if ( err.data?.details?.length ) { + this.errorHandler.message( + err.data.details + .map( + ( d ) => `${ d.issue } ${ d.description }` + ) + .join( '
' ) + ); + } else if ( err.details?.length ) { + this.errorHandler.message( + err.details + .map( + ( d ) => `${ d.issue } ${ d.description }` + ) + .join( '
' ) + ); + } else if ( err.data?.errors?.length > 0 ) { + this.errorHandler.messages( err.data.errors ); + } else if ( err.data?.message ) { + this.errorHandler.message( err.data.message ); + } else if ( err.message ) { + this.errorHandler.message( err.message ); + } else { + this.errorHandler.genericError(); + } + } ); + } else { + this.spinner.unblock(); - if (err.data?.details?.length) { - this.errorHandler.message(err.data.details.map(d => `${d.issue} ${d.description}`).join('
')); - } else if (err.details?.length) { - this.errorHandler.message(err.details.map(d => `${d.issue} ${d.description}`).join('
')); - } else if (err.data?.errors?.length > 0) { - this.errorHandler.messages(err.data.errors); - } else if (err.data?.message) { - this.errorHandler.message(err.data.message); - } else if (err.message) { - this.errorHandler.message(err.message); - } else { - this.errorHandler.genericError(); - } - }); - } else { - this.spinner.unblock(); + let message = this.defaultConfig.labels.error.generic; + if ( this.emptyFields.size > 0 ) { + message = this.defaultConfig.hosted_fields.labels.fields_empty; + } else if ( ! this.cardValid ) { + message = + this.defaultConfig.hosted_fields.labels.card_not_supported; + } else if ( ! this.formValid ) { + message = + this.defaultConfig.hosted_fields.labels.fields_not_valid; + } - let message = this.defaultConfig.labels.error.generic; - if (this.emptyFields.size > 0) { - message = this.defaultConfig.hosted_fields.labels.fields_empty; - } else if (!this.cardValid) { - message = this.defaultConfig.hosted_fields.labels.card_not_supported; - } else if (!this.formValid) { - message = this.defaultConfig.hosted_fields.labels.fields_not_valid; - } + this.errorHandler.message( message ); + } + } - this.errorHandler.message(message); - } - } + _cardNumberFiledCLassNameByCardType( cardType ) { + return cardType === 'american-express' + ? 'amex' + : cardType.replace( '-', '' ); + } - _cardNumberFiledCLassNameByCardType(cardType) { - return cardType === 'american-express' ? 'amex' : cardType.replace('-', ''); - } - - _recreateElementClassAttribute(element, newClassName) { - element.removeAttribute('class') - element.setAttribute('class', newClassName); - } + _recreateElementClassAttribute( element, newClassName ) { + element.removeAttribute( 'class' ); + element.setAttribute( 'class', newClassName ); + } } export default HostedFieldsRenderer; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js index 489202e56..bd46d7523 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js @@ -1,65 +1,72 @@ -import widgetBuilder from "./WidgetBuilder"; +import widgetBuilder from './WidgetBuilder'; class MessageRenderer { + constructor( config ) { + this.config = config; + this.optionsFingerprint = null; + this.currentNumber = 0; + } - constructor(config) { - this.config = config; - this.optionsFingerprint = null; - this.currentNumber = 0; - } + renderWithAmount( amount ) { + if ( ! this.shouldRender() ) { + return; + } - renderWithAmount(amount) { - if (! this.shouldRender()) { - return; - } + const options = { + amount, + }; + if ( this.config.placement ) { + options.placement = this.config.placement; + } + if ( this.config.style ) { + options.style = this.config.style; + } - const options = { - amount, - }; - if (this.config.placement) { - options.placement = this.config.placement; - } - if (this.config.style) { - options.style = this.config.style; - } + // sometimes the element is destroyed while the options stay the same + if ( + document + .querySelector( this.config.wrapper ) + .getAttribute( 'data-render-number' ) !== + this.currentNumber.toString() + ) { + this.optionsFingerprint = null; + } - // sometimes the element is destroyed while the options stay the same - if (document.querySelector(this.config.wrapper).getAttribute('data-render-number') !== this.currentNumber.toString()) { - this.optionsFingerprint = null; - } + if ( this.optionsEqual( options ) ) { + return; + } - if (this.optionsEqual(options)) { - return; - } + const wrapper = document.querySelector( this.config.wrapper ); + this.currentNumber++; + wrapper.setAttribute( 'data-render-number', this.currentNumber ); - const wrapper = document.querySelector(this.config.wrapper); - this.currentNumber++; - wrapper.setAttribute('data-render-number', this.currentNumber); + widgetBuilder.registerMessages( this.config.wrapper, options ); + widgetBuilder.renderMessages( this.config.wrapper ); + } - widgetBuilder.registerMessages(this.config.wrapper, options); - widgetBuilder.renderMessages(this.config.wrapper); - } + optionsEqual( options ) { + const fingerprint = JSON.stringify( options ); - optionsEqual(options) { - const fingerprint = JSON.stringify(options); + if ( this.optionsFingerprint === fingerprint ) { + return true; + } - if (this.optionsFingerprint === fingerprint) { - return true; - } + this.optionsFingerprint = fingerprint; + return false; + } - this.optionsFingerprint = fingerprint; - return false; - } - - shouldRender() { - - if (typeof paypal === 'undefined' || typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) { - return false; - } - if (! document.querySelector(this.config.wrapper)) { - return false; - } - return true; - } + shouldRender() { + if ( + typeof paypal === 'undefined' || + typeof paypal.Messages === 'undefined' || + typeof this.config.wrapper === 'undefined' + ) { + return false; + } + if ( ! document.querySelector( this.config.wrapper ) ) { + return false; + } + return true; + } } export default MessageRenderer; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js index f2b7f0b4c..cec951927 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js @@ -4,151 +4,163 @@ import merge from 'deepmerge'; * Base class for APM button previews, used on the plugin's settings page. */ class PreviewButton { - /** - * @param {string} selector - CSS ID of the wrapper, including the `#` - * @param {object} apiConfig - PayPal configuration object; retrieved via a - * widgetBuilder API method - */ - constructor({ - selector, - apiConfig, - }) { - this.apiConfig = apiConfig; - this.defaultAttributes = {}; - this.buttonConfig = {}; - this.ppcpConfig = {}; - this.isDynamic = true; + /** + * @param {string} selector - CSS ID of the wrapper, including the `#` + * @param {Object} apiConfig - PayPal configuration object; retrieved via a + * widgetBuilder API method + */ + constructor( { selector, apiConfig } ) { + this.apiConfig = apiConfig; + this.defaultAttributes = {}; + this.buttonConfig = {}; + this.ppcpConfig = {}; + this.isDynamic = true; - // The selector is usually overwritten in constructor of derived class. - this.selector = selector; - this.wrapper = selector; + // The selector is usually overwritten in constructor of derived class. + this.selector = selector; + this.wrapper = selector; - this.domWrapper = null; - } + this.domWrapper = null; + } - /** - * Creates a new DOM node to contain the preview button. - * - * @return {jQuery} Always a single jQuery element with the new DOM node. - */ - createNewWrapper() { - const previewId = this.selector.replace('#', ''); - const previewClass = 'ppcp-button-apm'; + /** + * Creates a new DOM node to contain the preview button. + * + * @return {jQuery} Always a single jQuery element with the new DOM node. + */ + createNewWrapper() { + const previewId = this.selector.replace( '#', '' ); + const previewClass = 'ppcp-button-apm'; - return jQuery(`
`); - } + return jQuery( `
` ); + } - /** - * Toggle the "dynamic" nature of the preview. - * When the button is dynamic, it will reflect current form values. A static button always - * uses the settings that were provided via PHP. - * - * @return {this} Reference to self, for chaining. - */ - setDynamic(state) { - this.isDynamic = state; - return this; - } + /** + * Toggle the "dynamic" nature of the preview. + * When the button is dynamic, it will reflect current form values. A static button always + * uses the settings that were provided via PHP. + * + * @param state + * @return {this} Reference to self, for chaining. + */ + setDynamic( state ) { + this.isDynamic = state; + return this; + } - /** - * Sets server-side configuration for the button. - * - * @return {this} Reference to self, for chaining. - */ - setButtonConfig(config) { - this.buttonConfig = merge(this.defaultAttributes, config); - this.buttonConfig.button.wrapper = this.selector; + /** + * Sets server-side configuration for the button. + * + * @param config + * @return {this} Reference to self, for chaining. + */ + setButtonConfig( config ) { + this.buttonConfig = merge( this.defaultAttributes, config ); + this.buttonConfig.button.wrapper = this.selector; - return this; - } + return this; + } - /** - * Updates the button configuration with current details from the form. - * - * @return {this} Reference to self, for chaining. - */ - setPpcpConfig(config) { - this.ppcpConfig = merge({}, config); + /** + * Updates the button configuration with current details from the form. + * + * @param config + * @return {this} Reference to self, for chaining. + */ + setPpcpConfig( config ) { + this.ppcpConfig = merge( {}, config ); - return this; - } + return this; + } - /** - * Merge form details into the config object for preview. - * Mutates the previewConfig object; no return value. - */ - dynamicPreviewConfig(previewConfig, formConfig) { - // Implement in derived class. - } + /** + * Merge form details into the config object for preview. + * Mutates the previewConfig object; no return value. + * @param previewConfig + * @param formConfig + */ + dynamicPreviewConfig( previewConfig, formConfig ) { + // Implement in derived class. + } - /** - * Responsible for creating the actual payment button preview. - * Called by the `render()` method, after the wrapper DOM element is ready. - */ - createButton(previewConfig) { - throw new Error('The "createButton" method must be implemented by the derived class'); - } + /** + * Responsible for creating the actual payment button preview. + * Called by the `render()` method, after the wrapper DOM element is ready. + * @param previewConfig + */ + createButton( previewConfig ) { + throw new Error( + 'The "createButton" method must be implemented by the derived class' + ); + } - /** - * Refreshes the button in the DOM. - * Will always create a new button in the DOM. - */ - render() { - // The APM button is disabled and cannot be enabled on the current page: Do not render it. - if (!this.isDynamic && !this.buttonConfig.is_enabled) { - return; - } + /** + * Refreshes the button in the DOM. + * Will always create a new button in the DOM. + */ + render() { + // The APM button is disabled and cannot be enabled on the current page: Do not render it. + if ( ! this.isDynamic && ! this.buttonConfig.is_enabled ) { + return; + } - if (!this.domWrapper) { - if (!this.wrapper) { - console.error('Skip render, button is not configured yet'); - return; - } - this.domWrapper = this.createNewWrapper(); - this.domWrapper.insertAfter(this.wrapper); - } else { - this.domWrapper.empty().show(); - } + if ( ! this.domWrapper ) { + if ( ! this.wrapper ) { + console.error( 'Skip render, button is not configured yet' ); + return; + } + this.domWrapper = this.createNewWrapper(); + this.domWrapper.insertAfter( this.wrapper ); + } else { + this.domWrapper.empty().show(); + } - this.isVisible = true; - const previewButtonConfig = merge({}, this.buttonConfig); - const previewPpcpConfig = this.isDynamic ? merge({}, this.ppcpConfig) : {}; - previewButtonConfig.button.wrapper = this.selector; + this.isVisible = true; + const previewButtonConfig = merge( {}, this.buttonConfig ); + const previewPpcpConfig = this.isDynamic + ? merge( {}, this.ppcpConfig ) + : {}; + previewButtonConfig.button.wrapper = this.selector; - this.dynamicPreviewConfig(previewButtonConfig, previewPpcpConfig); + this.dynamicPreviewConfig( previewButtonConfig, previewPpcpConfig ); - /* - * previewButtonConfig.button.wrapper must be different from this.ppcpConfig.button.wrapper! - * If both selectors point to the same element, an infinite loop is triggered. - */ - const buttonWrapper = previewButtonConfig.button.wrapper.replace(/^#/, ''); - const ppcpWrapper = this.ppcpConfig.button.wrapper.replace(/^#/, ''); + /* + * previewButtonConfig.button.wrapper must be different from this.ppcpConfig.button.wrapper! + * If both selectors point to the same element, an infinite loop is triggered. + */ + const buttonWrapper = previewButtonConfig.button.wrapper.replace( + /^#/, + '' + ); + const ppcpWrapper = this.ppcpConfig.button.wrapper.replace( /^#/, '' ); - if (buttonWrapper === ppcpWrapper) { - throw new Error(`[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${buttonWrapper}"`); - } + if ( buttonWrapper === ppcpWrapper ) { + throw new Error( + `[APM Preview Button] Infinite loop detected. Provide different selectors for the button/ppcp wrapper elements! Selector: "#${ buttonWrapper }"` + ); + } - this.createButton(previewButtonConfig); + this.createButton( previewButtonConfig ); - /* - * Unfortunately, a hacky way that is required to guarantee that this preview button is - * actually visible after calling the `render()` method. On some sites, we've noticed that - * certain JS events (like `ppcp-hidden`) do not fire in the expected order. This causes - * problems with preview buttons not being displayed instantly. - * - * Using a timeout here will make the button visible again at the end of the current - * event queue. - */ - setTimeout(() => this.domWrapper.show()); - } + /* + * Unfortunately, a hacky way that is required to guarantee that this preview button is + * actually visible after calling the `render()` method. On some sites, we've noticed that + * certain JS events (like `ppcp-hidden`) do not fire in the expected order. This causes + * problems with preview buttons not being displayed instantly. + * + * Using a timeout here will make the button visible again at the end of the current + * event queue. + */ + setTimeout( () => this.domWrapper.show() ); + } - remove() { - this.isVisible = false; + remove() { + this.isVisible = false; - if (this.domWrapper) { - this.domWrapper.hide().empty(); - } - } + if ( this.domWrapper ) { + this.domWrapper.hide().empty(); + } + } } export default PreviewButton; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js index 9d077af40..42e715904 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js @@ -6,342 +6,376 @@ import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounc * Manages all PreviewButton instances of a certain payment method on the page. */ class PreviewButtonManager { - /** - * Resolves the promise. - * Used by `this.boostrap()` to process enqueued initialization logic. - */ - #onInitResolver; + /** + * Resolves the promise. + * Used by `this.boostrap()` to process enqueued initialization logic. + */ + #onInitResolver; - /** - * A deferred Promise that is resolved once the page is ready. - * Deferred init logic can be added by using `this.#onInit.then(...)` - * - * @param {Promise|null} - */ - #onInit; + /** + * A deferred Promise that is resolved once the page is ready. + * Deferred init logic can be added by using `this.#onInit.then(...)` + * + * @param {Promise|null} + */ + #onInit; - constructor({ - methodName, - buttonConfig, - defaultAttributes, - }) { - // Define the payment method name in the derived class. - this.methodName = methodName; + constructor( { methodName, buttonConfig, defaultAttributes } ) { + // Define the payment method name in the derived class. + this.methodName = methodName; - this.buttonConfig = buttonConfig; - this.defaultAttributes = defaultAttributes; + this.buttonConfig = buttonConfig; + this.defaultAttributes = defaultAttributes; - this.isEnabled = true; - this.buttons = {}; - this.apiConfig = null; - this.apiError = ''; + this.isEnabled = true; + this.buttons = {}; + this.apiConfig = null; + this.apiError = ''; - this.#onInit = new Promise(resolve => { - this.#onInitResolver = resolve; - }); + this.#onInit = new Promise( ( resolve ) => { + this.#onInitResolver = resolve; + } ); - this.bootstrap = this.bootstrap.bind(this); - this.renderPreview = this.renderPreview.bind(this); + this.bootstrap = this.bootstrap.bind( this ); + this.renderPreview = this.renderPreview.bind( this ); - /** - * The "configureAllButtons" method applies ppcpConfig to all buttons that were created - * by this PreviewButtonManager instance. We debounce this method, as it should invoke - * only once, even if called multiple times in a row. - * - * This is required, as the `ppcp_paypal_render_preview` event does not fire for all - * buttons, but only a single time, passing in a random button's wrapper-ID; however, - * that event should always refresh all preview buttons, not only that single button. - */ - this._configureAllButtons = debounce(this._configureAllButtons.bind(this), 100); + /** + * The "configureAllButtons" method applies ppcpConfig to all buttons that were created + * by this PreviewButtonManager instance. We debounce this method, as it should invoke + * only once, even if called multiple times in a row. + * + * This is required, as the `ppcp_paypal_render_preview` event does not fire for all + * buttons, but only a single time, passing in a random button's wrapper-ID; however, + * that event should always refresh all preview buttons, not only that single button. + */ + this._configureAllButtons = debounce( + this._configureAllButtons.bind( this ), + 100 + ); - this.registerEventListeners(); - } + this.registerEventListeners(); + } - /** - * Protected method that needs to be implemented by the derived class. - * Responsible for fetching and returning the PayPal configuration object for this payment - * method. - * - * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. - * @return {Promise<{}>} - */ - async fetchConfig(payPal) { - throw new Error('The "fetchConfig" method must be implemented by the derived class'); - } + /** + * Protected method that needs to be implemented by the derived class. + * Responsible for fetching and returning the PayPal configuration object for this payment + * method. + * + * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. + * @return {Promise<{}>} + */ + async fetchConfig( payPal ) { + throw new Error( + 'The "fetchConfig" method must be implemented by the derived class' + ); + } - /** - * Protected method that needs to be implemented by the derived class. - * This method is responsible for creating a new PreviewButton instance and returning it. - * - * @param {string} wrapperId - CSS ID of the wrapper element. - * @return {PreviewButton} - */ - createButtonInstance(wrapperId) { - throw new Error('The "createButtonInstance" method must be implemented by the derived class'); - } + /** + * Protected method that needs to be implemented by the derived class. + * This method is responsible for creating a new PreviewButton instance and returning it. + * + * @param {string} wrapperId - CSS ID of the wrapper element. + * @return {PreviewButton} + */ + createButtonInstance( wrapperId ) { + throw new Error( + 'The "createButtonInstance" method must be implemented by the derived class' + ); + } - /** - * In case the button SDK could not be loaded from PayPal, we display this dummy button - * instead of keeping the preview box empty. - * - * This dummy is only visible on the admin side, and not rendered on the front-end. - * - * @todo Consider refactoring this into a new class that extends the PreviewButton class. - * @param wrapperId - * @return {any} - */ - createDummy(wrapperId) { - const elButton = document.createElement('div'); - elButton.classList.add('ppcp-button-apm', 'ppcp-button-dummy'); - elButton.innerHTML = `${this.apiError ?? 'Not Available'}`; + /** + * In case the button SDK could not be loaded from PayPal, we display this dummy button + * instead of keeping the preview box empty. + * + * This dummy is only visible on the admin side, and not rendered on the front-end. + * + * @todo Consider refactoring this into a new class that extends the PreviewButton class. + * @param wrapperId + * @return {any} + */ + createDummy( wrapperId ) { + const elButton = document.createElement( 'div' ); + elButton.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' ); + elButton.innerHTML = `${ + this.apiError ?? 'Not Available' + }`; - document.querySelector(wrapperId).appendChild(elButton); + document.querySelector( wrapperId ).appendChild( elButton ); - const instDummy = { - setDynamic: () => instDummy, - setPpcpConfig: () => instDummy, - render: () => {}, - remove: () => {}, - }; + const instDummy = { + setDynamic: () => instDummy, + setPpcpConfig: () => instDummy, + render: () => {}, + remove: () => {}, + }; - return instDummy; - } + return instDummy; + } - registerEventListeners() { - jQuery(document).one('DOMContentLoaded', this.bootstrap); + registerEventListeners() { + jQuery( document ).one( 'DOMContentLoaded', this.bootstrap ); - // General event that all APM buttons react to. - jQuery(document).on('ppcp_paypal_render_preview', this.renderPreview); + // General event that all APM buttons react to. + jQuery( document ).on( + 'ppcp_paypal_render_preview', + this.renderPreview + ); - // Specific event to only (re)render the current APM button type. - jQuery(document).on(`ppcp_paypal_render_preview_${this.methodName}`, this.renderPreview); - } + // Specific event to only (re)render the current APM button type. + jQuery( document ).on( + `ppcp_paypal_render_preview_${ this.methodName }`, + this.renderPreview + ); + } - /** - * Output an error message to the console, with a module-specific prefix. - */ - error(message, ...args) { - console.error(`${this.methodName} ${message}`, ...args); - } + /** + * Output an error message to the console, with a module-specific prefix. + * @param message + * @param {...any} args + */ + error( message, ...args ) { + console.error( `${ this.methodName } ${ message }`, ...args ); + } - /** - * Whether this is a dynamic preview of the APM button. - * A dynamic preview adjusts to the current form settings, while a static preview uses the - * style settings that were provided from server-side. - */ - isDynamic() { - return !!document.querySelector(`[data-ppcp-apm-name="${this.methodName}"]`); - } + /** + * Whether this is a dynamic preview of the APM button. + * A dynamic preview adjusts to the current form settings, while a static preview uses the + * style settings that were provided from server-side. + */ + isDynamic() { + return !! document.querySelector( + `[data-ppcp-apm-name="${ this.methodName }"]` + ); + } - /** - * Load dependencies and bootstrap the module. - * Returns a Promise that resolves once all dependencies were loaded and the module can be - * used without limitation. - * - * @return {Promise} - */ - async bootstrap() { - const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds. - const RESOLVE_INTERVAL = 200; + /** + * Load dependencies and bootstrap the module. + * Returns a Promise that resolves once all dependencies were loaded and the module can be + * used without limitation. + * + * @return {Promise} + */ + async bootstrap() { + const MAX_WAIT_TIME = 10000; // Fail, if PayPal SDK is unavailable after 10 seconds. + const RESOLVE_INTERVAL = 200; - if (!this.buttonConfig?.sdk_url || !widgetBuilder) { - this.error('Button could not be configured.'); - return; - } + if ( ! this.buttonConfig?.sdk_url || ! widgetBuilder ) { + this.error( 'Button could not be configured.' ); + return; + } - // This is a localization object of "gateway-settings.js". If it's missing, the script was - // not loaded. - if (!window.PayPalCommerceGatewaySettings) { - this.error( - 'PayPal settings are not fully loaded. Please clear the cache and reload the page.'); - return; - } + // This is a localization object of "gateway-settings.js". If it's missing, the script was + // not loaded. + if ( ! window.PayPalCommerceGatewaySettings ) { + this.error( + 'PayPal settings are not fully loaded. Please clear the cache and reload the page.' + ); + return; + } - // A helper function that clears the interval and resolves/rejects the promise. - const resolveOrReject = (resolve, reject, id, success = true) => { - clearInterval(id); - success ? resolve() : reject('Timeout while waiting for widgetBuilder.paypal'); - }; + // A helper function that clears the interval and resolves/rejects the promise. + const resolveOrReject = ( resolve, reject, id, success = true ) => { + clearInterval( id ); + success + ? resolve() + : reject( 'Timeout while waiting for widgetBuilder.paypal' ); + }; - // Wait for the PayPal SDK to be ready. - const paypalPromise = new Promise((resolve, reject) => { - let elapsedTime = 0; + // Wait for the PayPal SDK to be ready. + const paypalPromise = new Promise( ( resolve, reject ) => { + let elapsedTime = 0; - const id = setInterval(() => { - if (widgetBuilder.paypal) { - resolveOrReject(resolve, reject, id); - } else if (elapsedTime >= MAX_WAIT_TIME) { - resolveOrReject(resolve, reject, id, false); - } - elapsedTime += RESOLVE_INTERVAL; - }, RESOLVE_INTERVAL); - }); + const id = setInterval( () => { + if ( widgetBuilder.paypal ) { + resolveOrReject( resolve, reject, id ); + } else if ( elapsedTime >= MAX_WAIT_TIME ) { + resolveOrReject( resolve, reject, id, false ); + } + elapsedTime += RESOLVE_INTERVAL; + }, RESOLVE_INTERVAL ); + } ); - // Load the custom SDK script. - const customScriptPromise = loadCustomScript({ url: this.buttonConfig.sdk_url }); + // Load the custom SDK script. + const customScriptPromise = loadCustomScript( { + url: this.buttonConfig.sdk_url, + } ); - // Wait for both promises to resolve before continuing. - await Promise - .all([customScriptPromise, paypalPromise]) - .catch(err => { - console.log(`Failed to load ${this.methodName} dependencies:`, err); - }); + // Wait for both promises to resolve before continuing. + await Promise.all( [ customScriptPromise, paypalPromise ] ).catch( + ( err ) => { + console.log( + `Failed to load ${ this.methodName } dependencies:`, + err + ); + } + ); - /* + /* The fetchConfig method requires two objects to succeed: (a) the SDK custom-script (b) the `widgetBuilder.paypal` object */ - try { - this.apiConfig = await this.fetchConfig(widgetBuilder.paypal); - } catch (error) { - this.apiConfig = null; - } + try { + this.apiConfig = await this.fetchConfig( widgetBuilder.paypal ); + } catch ( error ) { + this.apiConfig = null; + } - // Avoid errors when there was a problem with loading the SDK. - await this.#onInitResolver(); + // Avoid errors when there was a problem with loading the SDK. + await this.#onInitResolver(); - this.#onInit = null; - } + this.#onInit = null; + } - /** - * Event handler, fires on `ppcp_paypal_render_preview` - * - * @param ev - Ignored - * @param ppcpConfig - The button settings for the preview. - */ - renderPreview(ev, ppcpConfig) { - const id = ppcpConfig.button.wrapper; + /** + * Event handler, fires on `ppcp_paypal_render_preview` + * + * @param ev - Ignored + * @param ppcpConfig - The button settings for the preview. + */ + renderPreview( ev, ppcpConfig ) { + const id = ppcpConfig.button.wrapper; - if (!id) { - this.error('Button did not provide a wrapper ID', ppcpConfig); - return; - } + if ( ! id ) { + this.error( 'Button did not provide a wrapper ID', ppcpConfig ); + return; + } - if (!this.shouldInsertPreviewButton(id)) { - return; - } + if ( ! this.shouldInsertPreviewButton( id ) ) { + return; + } - if (!this.buttons[id]) { - this._addButton(id, ppcpConfig); - } else { - // This is a debounced method, that fires after 100ms. - this._configureAllButtons(ppcpConfig); - } - } + if ( ! this.buttons[ id ] ) { + this._addButton( id, ppcpConfig ); + } else { + // This is a debounced method, that fires after 100ms. + this._configureAllButtons( ppcpConfig ); + } + } - /** - * Determines if the preview box supports the current button. - * - * When this function returns false, this manager instance does not create a new preview button. - * - * @param {string} previewId - ID of the inner preview box container. - * @return {boolean} True if the box is eligible for the preview button, false otherwise. - */ - shouldInsertPreviewButton(previewId) { - const container = document.querySelector(previewId); - const box = container.closest('.ppcp-preview'); - const limit = box.dataset.ppcpPreviewBlock ?? 'all'; + /** + * Determines if the preview box supports the current button. + * + * When this function returns false, this manager instance does not create a new preview button. + * + * @param {string} previewId - ID of the inner preview box container. + * @return {boolean} True if the box is eligible for the preview button, false otherwise. + */ + shouldInsertPreviewButton( previewId ) { + const container = document.querySelector( previewId ); + const box = container.closest( '.ppcp-preview' ); + const limit = box.dataset.ppcpPreviewBlock ?? 'all'; - return ('all' === limit) || (this.methodName === limit); - } + return 'all' === limit || this.methodName === limit; + } - /** - * Applies a new configuration to an existing preview button. - */ - _configureButton(id, ppcpConfig) { - this.buttons[id] - .setDynamic(this.isDynamic()) - .setPpcpConfig(ppcpConfig) - .render(); - } + /** + * Applies a new configuration to an existing preview button. + * @param id + * @param ppcpConfig + */ + _configureButton( id, ppcpConfig ) { + this.buttons[ id ] + .setDynamic( this.isDynamic() ) + .setPpcpConfig( ppcpConfig ) + .render(); + } - /** - * Apples the provided configuration to all existing preview buttons. - */ - _configureAllButtons(ppcpConfig) { - Object.entries(this.buttons).forEach(([id, button]) => { - this._configureButton(id, { - ...ppcpConfig, - button: { - ...ppcpConfig.button, + /** + * Apples the provided configuration to all existing preview buttons. + * @param ppcpConfig + */ + _configureAllButtons( ppcpConfig ) { + Object.entries( this.buttons ).forEach( ( [ id, button ] ) => { + this._configureButton( id, { + ...ppcpConfig, + button: { + ...ppcpConfig.button, - // The ppcpConfig object might refer to a different wrapper. - // Fix the selector, to avoid unintentionally hidden preview buttons. - wrapper: button.wrapper, - }, - }); - }); - } + // The ppcpConfig object might refer to a different wrapper. + // Fix the selector, to avoid unintentionally hidden preview buttons. + wrapper: button.wrapper, + }, + } ); + } ); + } - /** - * Creates a new preview button, that is rendered once the bootstrapping Promise resolves. - */ - _addButton(id, ppcpConfig) { - const createButton = () => { - if (!this.buttons[id]) { - let newInst; - if (this.apiConfig && 'object' === typeof this.apiConfig) { - newInst = this.createButtonInstance(id).setButtonConfig(this.buttonConfig); - } else { - newInst = this.createDummy(id); - } + /** + * Creates a new preview button, that is rendered once the bootstrapping Promise resolves. + * @param id + * @param ppcpConfig + */ + _addButton( id, ppcpConfig ) { + const createButton = () => { + if ( ! this.buttons[ id ] ) { + let newInst; + if ( this.apiConfig && 'object' === typeof this.apiConfig ) { + newInst = this.createButtonInstance( id ).setButtonConfig( + this.buttonConfig + ); + } else { + newInst = this.createDummy( id ); + } - this.buttons[id] = newInst; - } + this.buttons[ id ] = newInst; + } - this._configureButton(id, ppcpConfig); - }; + this._configureButton( id, ppcpConfig ); + }; - if (this.#onInit) { - this.#onInit.then(createButton); - } else { - createButton(); - } - } + if ( this.#onInit ) { + this.#onInit.then( createButton ); + } else { + createButton(); + } + } - /** - * Refreshes all buttons using the latest buttonConfig. - * - * @return {this} Reference to self, for chaining. - */ - renderButtons() { - if (this.isEnabled) { - Object.values(this.buttons).forEach(button => button.render()); - } else { - Object.values(this.buttons).forEach(button => button.remove()); - } + /** + * Refreshes all buttons using the latest buttonConfig. + * + * @return {this} Reference to self, for chaining. + */ + renderButtons() { + if ( this.isEnabled ) { + Object.values( this.buttons ).forEach( ( button ) => + button.render() + ); + } else { + Object.values( this.buttons ).forEach( ( button ) => + button.remove() + ); + } - return this; - } + return this; + } - /** - * Enables this payment method, which re-creates or refreshes all buttons. - * - * @return {this} Reference to self, for chaining. - */ - enable() { - if (!this.isEnabled) { - this.isEnabled = true; - this.renderButtons(); - } + /** + * Enables this payment method, which re-creates or refreshes all buttons. + * + * @return {this} Reference to self, for chaining. + */ + enable() { + if ( ! this.isEnabled ) { + this.isEnabled = true; + this.renderButtons(); + } - return this; - } + return this; + } - /** - * Disables this payment method, effectively removing all preview buttons. - * - * @return {this} Reference to self, for chaining. - */ - disable() { - if (!this.isEnabled) { - this.isEnabled = false; - this.renderButtons(); - } + /** + * Disables this payment method, effectively removing all preview buttons. + * + * @return {this} Reference to self, for chaining. + */ + disable() { + if ( ! this.isEnabled ) { + this.isEnabled = false; + this.renderButtons(); + } - return this; - } + return this; + } } export default PreviewButtonManager; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index a95621456..448432e3c 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -1,214 +1,290 @@ -import merge from "deepmerge"; -import {loadScript} from "@paypal/paypal-js"; -import {keysToCamelCase} from "../Helper/Utils"; -import widgetBuilder from "./WidgetBuilder"; -import {normalizeStyleForFundingSource} from "../Helper/Style"; +import merge from 'deepmerge'; +import { loadScript } from '@paypal/paypal-js'; +import { keysToCamelCase } from '../Helper/Utils'; +import widgetBuilder from './WidgetBuilder'; +import { normalizeStyleForFundingSource } from '../Helper/Style'; import { - handleShippingOptionsChange, - handleShippingAddressChange, -} from "../Helper/ShippingHandler.js"; + handleShippingOptionsChange, + handleShippingAddressChange, +} from '../Helper/ShippingHandler.js'; class Renderer { - constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) { - this.defaultSettings = defaultSettings; - this.creditCardRenderer = creditCardRenderer; - this.onSmartButtonClick = onSmartButtonClick; - this.onSmartButtonsInit = onSmartButtonsInit; + constructor( + creditCardRenderer, + defaultSettings, + onSmartButtonClick, + onSmartButtonsInit + ) { + this.defaultSettings = defaultSettings; + this.creditCardRenderer = creditCardRenderer; + this.onSmartButtonClick = onSmartButtonClick; + this.onSmartButtonsInit = onSmartButtonsInit; - this.buttonsOptions = {}; - this.onButtonsInitListeners = {}; + this.buttonsOptions = {}; + this.onButtonsInitListeners = {}; - this.renderedSources = new Set(); + this.renderedSources = new Set(); - this.reloadEventName = 'ppcp-reload-buttons'; - } + this.reloadEventName = 'ppcp-reload-buttons'; + } - render(contextConfig, settingsOverride = {}, contextConfigOverride = () => {}) { - const settings = merge(this.defaultSettings, settingsOverride); + render( + contextConfig, + settingsOverride = {}, + contextConfigOverride = () => {} + ) { + const settings = merge( this.defaultSettings, settingsOverride ); - const enabledSeparateGateways = Object.fromEntries(Object.entries( - settings.separate_buttons).filter(([s, data]) => document.querySelector(data.wrapper) - )); - const hasEnabledSeparateGateways = Object.keys(enabledSeparateGateways).length !== 0; + const enabledSeparateGateways = Object.fromEntries( + Object.entries( settings.separate_buttons ).filter( + ( [ s, data ] ) => document.querySelector( data.wrapper ) + ) + ); + const hasEnabledSeparateGateways = + Object.keys( enabledSeparateGateways ).length !== 0; - if (!hasEnabledSeparateGateways) { - this.renderButtons( - settings.button.wrapper, - settings.button.style, - contextConfig, - hasEnabledSeparateGateways - ); - } else { - // render each button separately - for (const fundingSource of paypal.getFundingSources().filter(s => !(s in enabledSeparateGateways))) { - const style = normalizeStyleForFundingSource(settings.button.style, fundingSource); + if ( ! hasEnabledSeparateGateways ) { + this.renderButtons( + settings.button.wrapper, + settings.button.style, + contextConfig, + hasEnabledSeparateGateways + ); + } else { + // render each button separately + for ( const fundingSource of paypal + .getFundingSources() + .filter( ( s ) => ! ( s in enabledSeparateGateways ) ) ) { + const style = normalizeStyleForFundingSource( + settings.button.style, + fundingSource + ); - this.renderButtons( - settings.button.wrapper, - style, - contextConfig, - hasEnabledSeparateGateways, - fundingSource - ); - } - } + this.renderButtons( + settings.button.wrapper, + style, + contextConfig, + hasEnabledSeparateGateways, + fundingSource + ); + } + } - if (this.creditCardRenderer) { - this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfigOverride); - } + if ( this.creditCardRenderer ) { + this.creditCardRenderer.render( + settings.hosted_fields.wrapper, + contextConfigOverride + ); + } - for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) { - this.renderButtons( - data.wrapper, - data.style, - contextConfig, - hasEnabledSeparateGateways, - fundingSource - ); - } - } + for ( const [ fundingSource, data ] of Object.entries( + enabledSeparateGateways + ) ) { + this.renderButtons( + data.wrapper, + data.style, + contextConfig, + hasEnabledSeparateGateways, + fundingSource + ); + } + } - renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) { - if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) { - // Try to render registered buttons again in case they were removed from the DOM by an external source. - widgetBuilder.renderButtons([wrapper, fundingSource]); - return; - } + renderButtons( + wrapper, + style, + contextConfig, + hasEnabledSeparateGateways, + fundingSource = null + ) { + if ( + ! document.querySelector( wrapper ) || + this.isAlreadyRendered( + wrapper, + fundingSource, + hasEnabledSeparateGateways + ) + ) { + // Try to render registered buttons again in case they were removed from the DOM by an external source. + widgetBuilder.renderButtons( [ wrapper, fundingSource ] ); + return; + } - if (fundingSource) { - contextConfig.fundingSource = fundingSource; - } + if ( fundingSource ) { + contextConfig.fundingSource = fundingSource; + } - let venmoButtonClicked = false; + let venmoButtonClicked = false; - const buttonsOptions = () => { - const options = { - style, - ...contextConfig, - onClick: (data, actions) => { - if (this.onSmartButtonClick) { - this.onSmartButtonClick(data, actions); - } + const buttonsOptions = () => { + const options = { + style, + ...contextConfig, + onClick: ( data, actions ) => { + if ( this.onSmartButtonClick ) { + this.onSmartButtonClick( data, actions ); + } - venmoButtonClicked = false; - if (data.fundingSource === 'venmo') { - venmoButtonClicked = true; - } - }, - onInit: (data, actions) => { - if (this.onSmartButtonsInit) { - this.onSmartButtonsInit(data, actions); - } - this.handleOnButtonsInit(wrapper, data, actions); - }, - }; + venmoButtonClicked = false; + if ( data.fundingSource === 'venmo' ) { + venmoButtonClicked = true; + } + }, + onInit: ( data, actions ) => { + if ( this.onSmartButtonsInit ) { + this.onSmartButtonsInit( data, actions ); + } + this.handleOnButtonsInit( wrapper, data, actions ); + }, + }; - // Check the condition and add the handler if needed - if (this.defaultSettings.should_handle_shipping_in_paypal) { - options.onShippingOptionsChange = (data, actions) => { - !this.isVenmoButtonClickedWhenVaultingIsEnabled(venmoButtonClicked) - ? handleShippingOptionsChange(data, actions, this.defaultSettings) - : null; - } - options.onShippingAddressChange = (data, actions) => { - !this.isVenmoButtonClickedWhenVaultingIsEnabled(venmoButtonClicked) - ? handleShippingAddressChange(data, actions, this.defaultSettings) - : null; - } - } + // Check the condition and add the handler if needed + if ( this.defaultSettings.should_handle_shipping_in_paypal ) { + options.onShippingOptionsChange = ( data, actions ) => { + let shippingOptionsChange = + ! this.isVenmoButtonClickedWhenVaultingIsEnabled( + venmoButtonClicked + ) + ? handleShippingOptionsChange( + data, + actions, + this.defaultSettings + ) + : null; - return options; - }; + return shippingOptionsChange + }; + options.onShippingAddressChange = ( data, actions ) => { + let shippingAddressChange = + ! this.isVenmoButtonClickedWhenVaultingIsEnabled( + venmoButtonClicked + ) + ? handleShippingAddressChange( + data, + actions, + this.defaultSettings + ) + : null; - jQuery(document) - .off(this.reloadEventName, wrapper) - .on(this.reloadEventName, wrapper, (event, settingsOverride = {}, triggeredFundingSource) => { + return shippingAddressChange + }; + } - // Only accept events from the matching funding source - if (fundingSource && triggeredFundingSource && (triggeredFundingSource !== fundingSource)) { - return; - } + return options; + }; - const settings = merge(this.defaultSettings, settingsOverride); - let scriptOptions = keysToCamelCase(settings.url_params); - scriptOptions = merge(scriptOptions, settings.script_attributes); + jQuery( document ) + .off( this.reloadEventName, wrapper ) + .on( + this.reloadEventName, + wrapper, + ( event, settingsOverride = {}, triggeredFundingSource ) => { + // Only accept events from the matching funding source + if ( + fundingSource && + triggeredFundingSource && + triggeredFundingSource !== fundingSource + ) { + return; + } - loadScript(scriptOptions).then((paypal) => { - widgetBuilder.setPaypal(paypal); - widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions()); - widgetBuilder.renderAll(); - }); - }); + const settings = merge( + this.defaultSettings, + settingsOverride + ); + let scriptOptions = keysToCamelCase( settings.url_params ); + scriptOptions = merge( + scriptOptions, + settings.script_attributes + ); - this.renderedSources.add(wrapper + (fundingSource ?? '')); + loadScript( scriptOptions ).then( ( paypal ) => { + widgetBuilder.setPaypal( paypal ); + widgetBuilder.registerButtons( + [ wrapper, fundingSource ], + buttonsOptions() + ); + widgetBuilder.renderAll(); + } ); + } + ); - if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') { - widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions()); - widgetBuilder.renderButtons([wrapper, fundingSource]); - } - } + this.renderedSources.add( wrapper + ( fundingSource ?? '' ) ); - isVenmoButtonClickedWhenVaultingIsEnabled = (venmoButtonClicked) => { - return venmoButtonClicked && this.defaultSettings.vaultingEnabled; - } + if ( + typeof paypal !== 'undefined' && + typeof paypal.Buttons !== 'undefined' + ) { + widgetBuilder.registerButtons( + [ wrapper, fundingSource ], + buttonsOptions() + ); + widgetBuilder.renderButtons( [ wrapper, fundingSource ] ); + } + } - isAlreadyRendered(wrapper, fundingSource) { - return this.renderedSources.has(wrapper + (fundingSource ?? '')); - } + isVenmoButtonClickedWhenVaultingIsEnabled = ( venmoButtonClicked ) => { + return venmoButtonClicked && this.defaultSettings.vaultingEnabled; + }; - disableCreditCardFields() { - this.creditCardRenderer.disableFields(); - } + isAlreadyRendered( wrapper, fundingSource ) { + return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) ); + } - enableCreditCardFields() { - this.creditCardRenderer.enableFields(); - } + disableCreditCardFields() { + this.creditCardRenderer.disableFields(); + } - onButtonsInit(wrapper, handler, reset) { - this.onButtonsInitListeners[wrapper] = reset ? [] : (this.onButtonsInitListeners[wrapper] || []); - this.onButtonsInitListeners[wrapper].push(handler); - } + enableCreditCardFields() { + this.creditCardRenderer.enableFields(); + } - handleOnButtonsInit(wrapper, data, actions) { + onButtonsInit( wrapper, handler, reset ) { + this.onButtonsInitListeners[ wrapper ] = reset + ? [] + : this.onButtonsInitListeners[ wrapper ] || []; + this.onButtonsInitListeners[ wrapper ].push( handler ); + } - this.buttonsOptions[wrapper] = { - data: data, - actions: actions - } + handleOnButtonsInit( wrapper, data, actions ) { + this.buttonsOptions[ wrapper ] = { + data, + actions, + }; - if (this.onButtonsInitListeners[wrapper]) { - for (let handler of this.onButtonsInitListeners[wrapper]) { - if (typeof handler === 'function') { - handler({ - wrapper: wrapper, - ...this.buttonsOptions[wrapper] - }); - } - } - } - } + if ( this.onButtonsInitListeners[ wrapper ] ) { + for ( const handler of this.onButtonsInitListeners[ wrapper ] ) { + if ( typeof handler === 'function' ) { + handler( { + wrapper, + ...this.buttonsOptions[ wrapper ], + } ); + } + } + } + } - disableSmartButtons(wrapper) { - if (!this.buttonsOptions[wrapper]) { - return; - } - try { - this.buttonsOptions[wrapper].actions.disable(); - } catch (err) { - console.log('Failed to disable buttons: ' + err); - } - } + disableSmartButtons( wrapper ) { + if ( ! this.buttonsOptions[ wrapper ] ) { + return; + } + try { + this.buttonsOptions[ wrapper ].actions.disable(); + } catch ( err ) { + console.log( 'Failed to disable buttons: ' + err ); + } + } - enableSmartButtons(wrapper) { - if (!this.buttonsOptions[wrapper]) { - return; - } - try { - this.buttonsOptions[wrapper].actions.enable(); - } catch (err) { - console.log('Failed to enable buttons: ' + err); - } - } + enableSmartButtons( wrapper ) { + if ( ! this.buttonsOptions[ wrapper ] ) { + return; + } + try { + this.buttonsOptions[ wrapper ].actions.enable(); + } catch ( err ) { + console.log( 'Failed to enable buttons: ' + err ); + } + } } export default Renderer; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js index b094f6bdf..baa031ca6 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js @@ -3,179 +3,178 @@ * To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource]. */ class WidgetBuilder { + constructor() { + this.paypal = null; + this.buttons = new Map(); + this.messages = new Map(); - constructor() { - this.paypal = null; - this.buttons = new Map(); - this.messages = new Map(); + this.renderEventName = 'ppcp-render'; - this.renderEventName = 'ppcp-render'; + document.ppcpWidgetBuilderStatus = () => { + console.log( { + buttons: this.buttons, + messages: this.messages, + } ); + }; - document.ppcpWidgetBuilderStatus = () => { - console.log({ - buttons: this.buttons, - messages: this.messages, - }); - } + jQuery( document ) + .off( this.renderEventName ) + .on( this.renderEventName, () => { + this.renderAll(); + } ); + } - jQuery(document) - .off(this.renderEventName) - .on(this.renderEventName, () => { - this.renderAll(); - }); - } + setPaypal( paypal ) { + this.paypal = paypal; + jQuery( document ).trigger( 'ppcp-paypal-loaded', paypal ); + } - setPaypal(paypal) { - this.paypal = paypal; - jQuery(document).trigger('ppcp-paypal-loaded', paypal); - } + registerButtons( wrapper, options ) { + wrapper = this.sanitizeWrapper( wrapper ); - registerButtons(wrapper, options) { - wrapper = this.sanitizeWrapper(wrapper); + this.buttons.set( this.toKey( wrapper ), { + wrapper, + options, + } ); + } - this.buttons.set(this.toKey(wrapper), { - wrapper: wrapper, - options: options, - }); - } + renderButtons( wrapper ) { + wrapper = this.sanitizeWrapper( wrapper ); - renderButtons(wrapper) { - wrapper = this.sanitizeWrapper(wrapper); + if ( ! this.buttons.has( this.toKey( wrapper ) ) ) { + return; + } - if (!this.buttons.has(this.toKey(wrapper))) { - return; - } + if ( this.hasRendered( wrapper ) ) { + return; + } - if (this.hasRendered(wrapper)) { - return; - } + const entry = this.buttons.get( this.toKey( wrapper ) ); + const btn = this.paypal.Buttons( entry.options ); - const entry = this.buttons.get(this.toKey(wrapper)); - const btn = this.paypal.Buttons(entry.options); + if ( ! btn.isEligible() ) { + this.buttons.delete( this.toKey( wrapper ) ); + return; + } - if (!btn.isEligible()) { - this.buttons.delete(this.toKey(wrapper)); - return; - } + const target = this.buildWrapperTarget( wrapper ); - let target = this.buildWrapperTarget(wrapper); + if ( ! target ) { + return; + } - if (!target) { - return; - } + btn.render( target ); + } - btn.render(target); - } + renderAllButtons() { + for ( const [ wrapper, entry ] of this.buttons ) { + this.renderButtons( wrapper ); + } + } - renderAllButtons() { - for (const [wrapper, entry] of this.buttons) { - this.renderButtons(wrapper); - } - } + registerMessages( wrapper, options ) { + this.messages.set( wrapper, { + wrapper, + options, + } ); + } - registerMessages(wrapper, options) { - this.messages.set(wrapper, { - wrapper: wrapper, - options: options - }); - } + renderMessages( wrapper ) { + if ( ! this.messages.has( wrapper ) ) { + return; + } - renderMessages(wrapper) { - if (!this.messages.has(wrapper)) { - return; - } + const entry = this.messages.get( wrapper ); - const entry = this.messages.get(wrapper); + if ( this.hasRendered( wrapper ) ) { + const element = document.querySelector( wrapper ); + element.setAttribute( 'data-pp-amount', entry.options.amount ); + return; + } - if (this.hasRendered(wrapper)) { - const element = document.querySelector(wrapper); - element.setAttribute('data-pp-amount', entry.options.amount); - return; - } + const btn = this.paypal.Messages( entry.options ); - const btn = this.paypal.Messages(entry.options); + btn.render( entry.wrapper ); - btn.render(entry.wrapper); + // watchdog to try to handle some strange cases where the wrapper may not be present + setTimeout( () => { + if ( ! this.hasRendered( wrapper ) ) { + btn.render( entry.wrapper ); + } + }, 100 ); + } - // watchdog to try to handle some strange cases where the wrapper may not be present - setTimeout(() => { - if (!this.hasRendered(wrapper)) { - btn.render(entry.wrapper); - } - }, 100); - } + renderAllMessages() { + for ( const [ wrapper, entry ] of this.messages ) { + this.renderMessages( wrapper ); + } + } - renderAllMessages() { - for (const [wrapper, entry] of this.messages) { - this.renderMessages(wrapper); - } - } + renderAll() { + this.renderAllButtons(); + this.renderAllMessages(); + } - renderAll() { - this.renderAllButtons(); - this.renderAllMessages(); - } + hasRendered( wrapper ) { + let selector = wrapper; - hasRendered(wrapper) { - let selector = wrapper; + if ( Array.isArray( wrapper ) ) { + selector = wrapper[ 0 ]; + for ( const item of wrapper.slice( 1 ) ) { + selector += ' .item-' + item; + } + } - if (Array.isArray(wrapper)) { - selector = wrapper[0]; - for (const item of wrapper.slice(1)) { - selector += ' .item-' + item; - } - } + const element = document.querySelector( selector ); + return element && element.hasChildNodes(); + } - const element = document.querySelector(selector); - return element && element.hasChildNodes(); - } + sanitizeWrapper( wrapper ) { + if ( Array.isArray( wrapper ) ) { + wrapper = wrapper.filter( ( item ) => !! item ); + if ( wrapper.length === 1 ) { + wrapper = wrapper[ 0 ]; + } + } + return wrapper; + } - sanitizeWrapper(wrapper) { - if (Array.isArray(wrapper)) { - wrapper = wrapper.filter(item => !!item); - if (wrapper.length === 1) { - wrapper = wrapper[0]; - } - } - return wrapper; - } + buildWrapperTarget( wrapper ) { + let target = wrapper; - buildWrapperTarget(wrapper) { - let target = wrapper; + if ( Array.isArray( wrapper ) ) { + const $wrapper = jQuery( wrapper[ 0 ] ); - if (Array.isArray(wrapper)) { - const $wrapper = jQuery(wrapper[0]); + if ( ! $wrapper.length ) { + return; + } - if (!$wrapper.length) { - return; - } + const itemClass = 'item-' + wrapper[ 1 ]; - const itemClass = 'item-' + wrapper[1]; + // Check if the parent element exists and it doesn't already have the div with the class + let $item = $wrapper.find( '.' + itemClass ); - // Check if the parent element exists and it doesn't already have the div with the class - let $item = $wrapper.find('.' + itemClass); + if ( ! $item.length ) { + $item = jQuery( `
` ); + $wrapper.append( $item ); + } - if (!$item.length) { - $item = jQuery(`
`); - $wrapper.append($item); - } + target = $item.get( 0 ); + } - target = $item.get(0); - } + if ( ! jQuery( target ).length ) { + return null; + } - if (!jQuery(target).length) { - return null; - } + return target; + } - return target; - } - - toKey(wrapper) { - if (Array.isArray(wrapper)) { - return JSON.stringify(wrapper); - } - return wrapper; - } + toKey( wrapper ) { + if ( Array.isArray( wrapper ) ) { + return JSON.stringify( wrapper ); + } + return wrapper; + } } window.widgetBuilder = window.widgetBuilder || new WidgetBuilder(); diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 726e06ea7..4b309818e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -829,7 +829,9 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages */ do_action( "ppcp_before_{$location_hook}_message_wrapper" ); - $messages_placeholder = '
'; + $bn_code = PPCP_PAYPAL_BN_CODE; + + $messages_placeholder = '
'; if ( is_array( $block_params ) && ( $block_params['blockName'] ?? false ) ) { $this->render_after_block( @@ -1163,11 +1165,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages ), 'update_customer_shipping' => array( 'shipping_options' => array( - 'endpoint' => '/wp-json/wc/store/cart/select-shipping-rate', + 'endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'select-shipping-rate' ), ), 'shipping_address' => array( - 'cart_endpoint' => '/wp-json/wc/store/cart/', - 'update_customer_endpoint' => '/wp-json/wc/store/v1/cart/update-customer/', + 'cart_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT ), + 'update_customer_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'update-customer' ), ), 'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ), 'update_shipping_method' => \WC_AJAX::get_endpoint( 'update_shipping_method' ), @@ -1511,7 +1513,10 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages private function bn_code_for_context( string $context ): string { $codes = $this->bn_codes(); - return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : 'Woo_PPCP'; + + $bn_code = PPCP_PAYPAL_BN_CODE; + + return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : $bn_code; } /** @@ -1519,13 +1524,15 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages * * @return array */ - private function bn_codes(): array { + private function bn_codes() : array { + + $bn_code = PPCP_PAYPAL_BN_CODE; return array( - 'checkout' => 'Woo_PPCP', - 'cart' => 'Woo_PPCP', - 'mini-cart' => 'Woo_PPCP', - 'product' => 'Woo_PPCP', + 'checkout' => $bn_code, + 'cart' => $bn_code, + 'mini-cart' => $bn_code, + 'product' => $bn_code, ); } diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 625157a05..5ff2b02e6 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -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. * diff --git a/modules/ppcp-card-fields/services.php b/modules/ppcp-card-fields/services.php index 5374a1c48..85f8004b6 100644 --- a/modules/ppcp-card-fields/services.php +++ b/modules/ppcp-card-fields/services.php @@ -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', diff --git a/modules/ppcp-compat/resources/js/tracking-compat.js b/modules/ppcp-compat/resources/js/tracking-compat.js index f6d2c80e4..589e747b5 100644 --- a/modules/ppcp-compat/resources/js/tracking-compat.js +++ b/modules/ppcp-compat/resources/js/tracking-compat.js @@ -1,66 +1,93 @@ -document.addEventListener( - 'DOMContentLoaded', - () => { - const config = PayPalCommerceGatewayOrderTrackingCompat; +document.addEventListener( 'DOMContentLoaded', () => { + const config = PayPalCommerceGatewayOrderTrackingCompat; - const orderTrackingContainerId = "ppcp_order-tracking"; - const orderTrackingContainerSelector = "#ppcp_order-tracking .ppcp-tracking-column.shipments"; - const gzdSaveButton = document.getElementById('order-shipments-save'); - const loadLocation = location.href + " " + orderTrackingContainerSelector + ">*"; - const gzdSyncEnabled = config.gzd_sync_enabled; - const wcShipmentSyncEnabled = config.wc_shipment_sync_enabled; - const wcShippingTaxSyncEnabled = config.wc_shipping_tax_sync_enabled; - const wcShipmentSaveButton = document.querySelector('#woocommerce-shipment-tracking .button-save-form'); - const wcShipmentTaxBuyLabelButtonSelector = '.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button'; + const orderTrackingContainerId = 'ppcp_order-tracking'; + const orderTrackingContainerSelector = + '#ppcp_order-tracking .ppcp-tracking-column.shipments'; + const gzdSaveButton = document.getElementById( 'order-shipments-save' ); + const loadLocation = + location.href + ' ' + orderTrackingContainerSelector + '>*'; + const gzdSyncEnabled = config.gzd_sync_enabled; + const wcShipmentSyncEnabled = config.wc_shipment_sync_enabled; + const wcShippingTaxSyncEnabled = config.wc_shipping_tax_sync_enabled; + const wcShipmentSaveButton = document.querySelector( + '#woocommerce-shipment-tracking .button-save-form' + ); + const wcShipmentTaxBuyLabelButtonSelector = + '.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button'; - const toggleLoaderVisibility = function() { - const loader = document.querySelector('.ppcp-tracking-loader'); - if (loader) { - if (loader.style.display === 'none' || loader.style.display === '') { - loader.style.display = 'block'; - } else { - loader.style.display = 'none'; - } - } - } + const toggleLoaderVisibility = function () { + const loader = document.querySelector( '.ppcp-tracking-loader' ); + if ( loader ) { + if ( + loader.style.display === 'none' || + loader.style.display === '' + ) { + loader.style.display = 'block'; + } else { + loader.style.display = 'none'; + } + } + }; - const waitForTrackingUpdate = function (elementToCheck) { - if (elementToCheck.css('display') !== 'none') { - setTimeout(() => waitForTrackingUpdate(elementToCheck), 100); - } else { - jQuery(orderTrackingContainerSelector).load(loadLocation, "", function(){ - toggleLoaderVisibility(); - }); - } - } + const waitForTrackingUpdate = function ( elementToCheck ) { + if ( elementToCheck.css( 'display' ) !== 'none' ) { + setTimeout( () => waitForTrackingUpdate( elementToCheck ), 100 ); + } else { + jQuery( orderTrackingContainerSelector ).load( + loadLocation, + '', + function () { + toggleLoaderVisibility(); + } + ); + } + }; - if (gzdSyncEnabled && typeof(gzdSaveButton) != 'undefined' && gzdSaveButton != null) { - gzdSaveButton.addEventListener('click', function (event) { - toggleLoaderVisibility(); - waitForTrackingUpdate(jQuery('#order-shipments-save')); - }) - } + if ( + gzdSyncEnabled && + typeof gzdSaveButton !== 'undefined' && + gzdSaveButton != null + ) { + gzdSaveButton.addEventListener( 'click', function ( event ) { + toggleLoaderVisibility(); + waitForTrackingUpdate( jQuery( '#order-shipments-save' ) ); + } ); + } - if (wcShipmentSyncEnabled && typeof(wcShipmentSaveButton) != 'undefined' && wcShipmentSaveButton != null) { - wcShipmentSaveButton.addEventListener('click', function (event) { - toggleLoaderVisibility(); - waitForTrackingUpdate(jQuery('#shipment-tracking-form')); - }) - } + if ( + wcShipmentSyncEnabled && + typeof wcShipmentSaveButton !== 'undefined' && + wcShipmentSaveButton != null + ) { + wcShipmentSaveButton.addEventListener( 'click', function ( event ) { + toggleLoaderVisibility(); + waitForTrackingUpdate( jQuery( '#shipment-tracking-form' ) ); + } ); + } - if (wcShippingTaxSyncEnabled && typeof(wcShippingTaxSyncEnabled) != 'undefined' && wcShippingTaxSyncEnabled != null) { - document.addEventListener('click', function(event) { - const wcShipmentTaxBuyLabelButton = event.target.closest(wcShipmentTaxBuyLabelButtonSelector); + if ( + wcShippingTaxSyncEnabled && + typeof wcShippingTaxSyncEnabled !== 'undefined' && + wcShippingTaxSyncEnabled != null + ) { + document.addEventListener( 'click', function ( event ) { + const wcShipmentTaxBuyLabelButton = event.target.closest( + wcShipmentTaxBuyLabelButtonSelector + ); - if (wcShipmentTaxBuyLabelButton) { - toggleLoaderVisibility(); - setTimeout(function () { - jQuery(orderTrackingContainerSelector).load(loadLocation, "", function(){ - toggleLoaderVisibility(); - }); - }, 10000); - } - }); - } - }, -); + if ( wcShipmentTaxBuyLabelButton ) { + toggleLoaderVisibility(); + setTimeout( function () { + jQuery( orderTrackingContainerSelector ).load( + loadLocation, + '', + function () { + toggleLoaderVisibility(); + } + ); + }, 10000 ); + } + } ); + } +} ); diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index 70ae6dac2..d27091162 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -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 { /** diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 09c35c24f..1749dcc0d 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -56,6 +56,11 @@ class CompatModule implements ModuleInterface { $this->fix_page_builders(); $this->exclude_cache_plugins_js_minification( $c ); $this->set_elementor_checkout_context(); + + $is_nyp_active = $c->get( 'compat.nyp.is_supported_plugin_version_active' ); + if ( $is_nyp_active ) { + $this->initialize_nyp_compat_layer(); + } } /** @@ -387,4 +392,24 @@ class CompatModule implements ModuleInterface { 3 ); } + + /** + * Sets up the compatibility layer for PayPal Shipping callback & WooCommerce Name Your Price plugin. + * + * @return void + */ + protected function initialize_nyp_compat_layer(): void { + add_filter( + 'woocommerce_paypal_payments_shipping_callback_cart_line_item_total', + static function( string $total, array $cart_item ) { + if ( ! isset( $cart_item['nyp'] ) ) { + return $total; + } + + return $cart_item['nyp']; + }, + 10, + 2 + ); + } } diff --git a/modules/ppcp-googlepay/resources/css/styles.scss b/modules/ppcp-googlepay/resources/css/styles.scss index c60212a2e..6cf2119a0 100644 --- a/modules/ppcp-googlepay/resources/css/styles.scss +++ b/modules/ppcp-googlepay/resources/css/styles.scss @@ -13,3 +13,7 @@ min-width: 0 !important; } } + +#ppc-button-ppcp-googlepay { + display: none; +} diff --git a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js index 82ac834e8..d61b674a7 100644 --- a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js @@ -1,79 +1,68 @@ -import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; -import CartActionHandler - from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler"; +import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; +import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler'; class BaseHandler { + constructor( buttonConfig, ppcpConfig, externalHandler ) { + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.externalHandler = externalHandler; + } - constructor(buttonConfig, ppcpConfig, externalHandler) { - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.externalHandler = externalHandler; - } + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { + return false; + } + return true; + } - validateContext() { - if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { - return false; - } - return true; - } + shippingAllowed() { + // Status of the shipping settings in WooCommerce. + return this.buttonConfig.shipping.configured; + } - shippingAllowed() { - // Status of the shipping settings in WooCommerce. - return this.buttonConfig.shipping.configured; - } + transactionInfo() { + return new Promise( ( resolve, reject ) => { + fetch( this.ppcpConfig.ajax.cart_script_params.endpoint, { + method: 'GET', + credentials: 'same-origin', + } ) + .then( ( result ) => result.json() ) + .then( ( result ) => { + if ( ! result.success ) { + return; + } - transactionInfo() { - return new Promise((resolve, reject) => { + // handle script reload + const data = result.data; - fetch( - this.ppcpConfig.ajax.cart_script_params.endpoint, - { - method: 'GET', - credentials: 'same-origin', - } - ) - .then(result => result.json()) - .then(result => { - if (! result.success) { - return; - } + resolve( { + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str, + } ); + } ); + } ); + } - // handle script reload - const data = result.data; + createOrder() { + return this.actionHandler().configuration().createOrder( null, null ); + } - resolve({ - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str - }); + approveOrder( data, actions ) { + return this.actionHandler().configuration().onApprove( data, actions ); + } - }); - }); - } - - createOrder() { - return this.actionHandler().configuration().createOrder(null, null); - } - - approveOrder(data, actions) { - return this.actionHandler().configuration().onApprove(data, actions); - } - - actionHandler() { - return new CartActionHandler( - this.ppcpConfig, - this.errorHandler(), - ); - } - - errorHandler() { - return new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); - } + actionHandler() { + return new CartActionHandler( this.ppcpConfig, this.errorHandler() ); + } + errorHandler() { + return new ErrorHandler( + this.ppcpConfig.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + } } export default BaseHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/CartBlockHandler.js b/modules/ppcp-googlepay/resources/js/Context/CartBlockHandler.js index 451167584..45633a64e 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CartBlockHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CartBlockHandler.js @@ -1,15 +1,13 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; class CartBlockHandler extends BaseHandler { + createOrder() { + return this.externalHandler.createOrder(); + } - createOrder() { - return this.externalHandler.createOrder(); - } - - approveOrder(data, actions) { - return this.externalHandler.onApprove(data, actions); - } - + approveOrder( data, actions ) { + return this.externalHandler.onApprove( data, actions ); + } } export default CartBlockHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/CartHandler.js b/modules/ppcp-googlepay/resources/js/Context/CartHandler.js index 295bec718..7f70164e7 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CartHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CartHandler.js @@ -1,7 +1,5 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; -class CartHandler extends BaseHandler { - -} +class CartHandler extends BaseHandler {} export default CartHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js index 9295c4302..3825b2a05 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js @@ -1,15 +1,13 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; -class CheckoutBlockHandler extends BaseHandler{ - - createOrder() { - return this.externalHandler.createOrder(); - } - - approveOrder(data, actions) { - return this.externalHandler.onApprove(data, actions); - } +class CheckoutBlockHandler extends BaseHandler { + createOrder() { + return this.externalHandler.createOrder(); + } + approveOrder( data, actions ) { + return this.externalHandler.onApprove( data, actions ); + } } export default CheckoutBlockHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js index ed8323a60..251b4f3c4 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js @@ -1,60 +1,64 @@ -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 FormValidator from "../../../../ppcp-button/resources/js/modules/Helper/FormValidator"; +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 FormValidator from '../../../../ppcp-button/resources/js/modules/Helper/FormValidator'; class CheckoutHandler extends BaseHandler { + transactionInfo() { + return new Promise( async ( resolve, reject ) => { + try { + const spinner = new Spinner(); + const errorHandler = this.errorHandler(); - transactionInfo() { - return new Promise(async (resolve, reject) => { + const formSelector = + this.ppcpConfig.context === 'checkout' + ? 'form.checkout' + : 'form#order_review'; + const formValidator = this.ppcpConfig + .early_checkout_validation_enabled + ? new FormValidator( + this.ppcpConfig.ajax.validate_checkout.endpoint, + this.ppcpConfig.ajax.validate_checkout.nonce + ) + : null; - try { - const spinner = new Spinner(); - const errorHandler = this.errorHandler(); + if ( ! formValidator ) { + resolve( super.transactionInfo() ); + return; + } - const formSelector = this.ppcpConfig.context === 'checkout' ? 'form.checkout' : 'form#order_review'; - const formValidator = this.ppcpConfig.early_checkout_validation_enabled ? - new FormValidator( - this.ppcpConfig.ajax.validate_checkout.endpoint, - this.ppcpConfig.ajax.validate_checkout.nonce, - ) : null; + formValidator + .validate( document.querySelector( formSelector ) ) + .then( ( errors ) => { + if ( errors.length > 0 ) { + spinner.unblock(); + errorHandler.clear(); + errorHandler.messages( errors ); - if (!formValidator) { - resolve(super.transactionInfo()); - return; - } + // fire WC event for other plugins + jQuery( document.body ).trigger( 'checkout_error', [ + errorHandler.currentHtml(), + ] ); - 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() ] ); - - reject(); - } else { - resolve(super.transactionInfo()); - } - }); - - } catch (error) { - console.error(error); - reject(); - } - }); - } - - actionHandler() { - return new CheckoutActionHandler( - this.ppcpConfig, - this.errorHandler(), - new Spinner() - ); - } + reject(); + } else { + resolve( super.transactionInfo() ); + } + } ); + } catch ( error ) { + console.error( error ); + reject(); + } + } ); + } + actionHandler() { + return new CheckoutActionHandler( + this.ppcpConfig, + this.errorHandler(), + new Spinner() + ); + } } export default CheckoutHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js index 8c6bc261d..358e248a3 100644 --- a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js +++ b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js @@ -1,34 +1,65 @@ -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 PayNowHandler from "./PayNowHandler"; -import PreviewHandler from "./PreviewHandler"; +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 PayNowHandler from './PayNowHandler'; +import PreviewHandler from './PreviewHandler'; class ContextHandlerFactory { - - static create(context, buttonConfig, ppcpConfig, externalActionHandler) { - switch (context) { - case 'product': - return new SingleProductHandler(buttonConfig, ppcpConfig, externalActionHandler); - case 'cart': - return new CartHandler(buttonConfig, ppcpConfig, externalActionHandler); - case 'checkout': - return new CheckoutHandler(buttonConfig, ppcpConfig, externalActionHandler); - case 'pay-now': - return new PayNowHandler(buttonConfig, ppcpConfig, externalActionHandler); - case 'mini-cart': - return new MiniCartHandler(buttonConfig, ppcpConfig, externalActionHandler); - case 'cart-block': - return new CartBlockHandler(buttonConfig, ppcpConfig, externalActionHandler); - case 'checkout-block': - return new CheckoutBlockHandler(buttonConfig, ppcpConfig, externalActionHandler); - case 'preview': - return new PreviewHandler(buttonConfig, ppcpConfig, externalActionHandler); - } - } + static create( context, buttonConfig, ppcpConfig, externalActionHandler ) { + switch ( context ) { + case 'product': + return new SingleProductHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + case 'cart': + return new CartHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + case 'checkout': + return new CheckoutHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + case 'pay-now': + return new PayNowHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + case 'mini-cart': + return new MiniCartHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + case 'cart-block': + return new CartBlockHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + case 'checkout-block': + return new CheckoutBlockHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + case 'preview': + return new PreviewHandler( + buttonConfig, + ppcpConfig, + externalActionHandler + ); + } + } } export default ContextHandlerFactory; diff --git a/modules/ppcp-googlepay/resources/js/Context/MiniCartHandler.js b/modules/ppcp-googlepay/resources/js/Context/MiniCartHandler.js index 1884203b7..0ca0253cf 100644 --- a/modules/ppcp-googlepay/resources/js/Context/MiniCartHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/MiniCartHandler.js @@ -1,7 +1,5 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; -class MiniCartHandler extends BaseHandler { - -} +class MiniCartHandler extends BaseHandler {} export default MiniCartHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js index 527b20339..79de6b39d 100644 --- a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js @@ -1,38 +1,35 @@ -import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; -import BaseHandler from "./BaseHandler"; -import CheckoutActionHandler - from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler"; +import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner'; +import BaseHandler from './BaseHandler'; +import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler'; class PayNowHandler extends BaseHandler { + validateContext() { + if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { + return false; + } + return true; + } - validateContext() { - if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { - return false; - } - return true; - } + transactionInfo() { + return new Promise( async ( resolve, reject ) => { + const data = this.ppcpConfig.pay_now; - transactionInfo() { - return new Promise(async (resolve, reject) => { - const data = this.ppcpConfig['pay_now']; - - resolve({ - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str - }); - }); - } - - actionHandler() { - return new CheckoutActionHandler( - this.ppcpConfig, - this.errorHandler(), - new Spinner() - ); - } + resolve( { + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str, + } ); + } ); + } + actionHandler() { + return new CheckoutActionHandler( + this.ppcpConfig, + this.errorHandler(), + new Spinner() + ); + } } export default PayNowHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js index f4eeee486..77f9f758a 100644 --- a/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js @@ -1,31 +1,29 @@ -import BaseHandler from "./BaseHandler"; +import BaseHandler from './BaseHandler'; class PreviewHandler extends BaseHandler { + constructor( buttonConfig, ppcpConfig, externalHandler ) { + super( buttonConfig, ppcpConfig, externalHandler ); + } - constructor(buttonConfig, ppcpConfig, externalHandler) { - super(buttonConfig, ppcpConfig, externalHandler); - } + transactionInfo() { + throw new Error( 'Transaction info fail. This is just a preview.' ); + } - transactionInfo() { - throw new Error('Transaction info fail. This is just a preview.'); - } + createOrder() { + throw new Error( 'Create order fail. This is just a preview.' ); + } - createOrder() { - throw new Error('Create order fail. This is just a preview.'); - } + approveOrder( data, actions ) { + throw new Error( 'Approve order fail. This is just a preview.' ); + } - approveOrder(data, actions) { - throw new Error('Approve order fail. This is just a preview.'); - } - - actionHandler() { - throw new Error('Action handler fail. This is just a preview.'); - } - - errorHandler() { - throw new Error('Error handler fail. This is just a preview.'); - } + actionHandler() { + throw new Error( 'Action handler fail. This is just a preview.' ); + } + errorHandler() { + throw new Error( 'Error handler fail. This is just a preview.' ); + } } export default PreviewHandler; diff --git a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js index 60563ded9..a8aa6e8bd 100644 --- a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js @@ -1,80 +1,78 @@ -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 false; + } + return true; + } - validateContext() { - if ( this.ppcpConfig?.locations_with_subscription_product?.product ) { - return false; - } - return true; - } + transactionInfo() { + const errorHandler = new ErrorHandler( + this.ppcpConfig.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); - transactionInfo() { - const errorHandler = new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); + function form() { + return document.querySelector( 'form.cart' ); + } - function form() { - return document.querySelector('form.cart'); - } + const actionHandler = new SingleProductActionHandler( + null, + null, + form(), + errorHandler + ); - const actionHandler = new SingleProductActionHandler( - null, - null, - form(), - errorHandler, - ); + const hasSubscriptions = + PayPalCommerceGateway.data_client_id.has_subscriptions && + PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; - const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions - && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; + const products = hasSubscriptions + ? actionHandler.getSubscriptionProducts() + : actionHandler.getProducts(); - const products = hasSubscriptions - ? actionHandler.getSubscriptionProducts() - : actionHandler.getProducts(); + return new Promise( ( resolve, reject ) => { + new SimulateCart( + this.ppcpConfig.ajax.simulate_cart.endpoint, + this.ppcpConfig.ajax.simulate_cart.nonce + ).simulate( ( data ) => { + resolve( { + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str, + } ); + }, products ); + } ); + } - return new Promise((resolve, reject) => { - (new SimulateCart( - this.ppcpConfig.ajax.simulate_cart.endpoint, - this.ppcpConfig.ajax.simulate_cart.nonce, - )).simulate((data) => { - - resolve({ - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str - }); - - }, products); - }); - } - - createOrder() { - return this.actionHandler().configuration().createOrder(null, null, { - 'updateCartOptions': { - 'keepShipping': true - } - }); - } - - actionHandler() { - return new SingleProductActionHandler( - this.ppcpConfig, - new UpdateCart( - this.ppcpConfig.ajax.change_cart.endpoint, - this.ppcpConfig.ajax.change_cart.nonce, - ), - document.querySelector('form.cart'), - this.errorHandler(), - ); - } + createOrder() { + return this.actionHandler() + .configuration() + .createOrder( null, null, { + updateCartOptions: { + keepShipping: true, + }, + } ); + } + actionHandler() { + return new SingleProductActionHandler( + this.ppcpConfig, + new UpdateCart( + this.ppcpConfig.ajax.change_cart.endpoint, + this.ppcpConfig.ajax.change_cart.nonce + ), + document.querySelector( 'form.cart' ), + this.errorHandler() + ); + } } export default SingleProductHandler; diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index b3ca0a935..87bc642f0 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,398 +1,526 @@ -import ContextHandlerFactory from "./Context/ContextHandlerFactory"; -import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; -import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; -import UpdatePaymentData from "./Helper/UpdatePaymentData"; -import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/ApmButtons"; +import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; +import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; +import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; +import UpdatePaymentData from './Helper/UpdatePaymentData'; +import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; class GooglepayButton { - - constructor(context, externalHandler, buttonConfig, ppcpConfig) { - apmButtonsInit(ppcpConfig); - - this.isInitialized = false; - - this.context = context; - this.externalHandler = externalHandler; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - - this.paymentsClient = null; - - this.contextHandler = ContextHandlerFactory.create( - this.context, - this.buttonConfig, - this.ppcpConfig, - this.externalHandler - ); - - this.log = function() { - if ( this.buttonConfig.is_debug ) { - //console.log('[GooglePayButton]', ...arguments); - } - } - } - - init(config) { - if (this.isInitialized) { - return; - } - this.isInitialized = true; - - if (!this.validateConfig()) { - return; - } - - if (!this.contextHandler.validateContext()) { - return; - } - - this.googlePayConfig = config; - this.allowedPaymentMethods = config.allowedPaymentMethods; - this.baseCardPaymentMethod = this.allowedPaymentMethods[0]; - - this.initClient(); - this.initEventHandlers(); - - this.paymentsClient.isReadyToPay( - this.buildReadyToPayRequest(this.allowedPaymentMethods, config) - ) - .then((response) => { - if (response.result) { - this.addButton(this.baseCardPaymentMethod); - } - }) - .catch(function(err) { - console.error(err); - }); - } - - reinit() { - if (!this.googlePayConfig) { - return; - } - - this.isInitialized = false; - this.init(this.googlePayConfig); - } - - validateConfig() { - if ( ['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { - console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment); - return false; - } - - if ( !this.contextHandler ) { - console.error('[GooglePayButton] Invalid context handler.', this.contextHandler); - return false; - } - - return true; - } - - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - let config = { - wrapper: this.buttonConfig.button.wrapper, - ppcpStyle: this.ppcpConfig.button.style, - buttonStyle: this.buttonConfig.button.style, - ppcpButtonWrapper: this.ppcpConfig.button.wrapper - } - - if (this.context === 'mini-cart') { - config.wrapper = this.buttonConfig.button.mini_cart_wrapper; - config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; - config.buttonStyle = this.buttonConfig.button.mini_cart_style; - config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; - - // Handle incompatible types. - if (config.buttonStyle.type === 'buy') { - config.buttonStyle.type = 'pay'; - } - } - - if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) { - config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway-paypal'; - } - - return config; - } - - initClient() { - const callbacks = { - onPaymentAuthorized: this.onPaymentAuthorized.bind(this) - } - - if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { - callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); - } - - this.paymentsClient = new google.payments.api.PaymentsClient({ - environment: this.buttonConfig.environment, - // add merchant info maybe - paymentDataCallbacks: callbacks - }); - } - - initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - - if (wrapper === ppcpButtonWrapper) { - throw new Error(`[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${wrapper}"`); - } - - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper); - setVisible(wrapper, $ppcpButtonWrapper.is(':visible')); - setEnabled(wrapper, !$ppcpButtonWrapper.hasClass('ppcp-disabled')); - } - - jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => { - if (jQuery(data.selector).is(ppcpButtonWrapper)) { - syncButtonVisibility(); - } - }); - - syncButtonVisibility(); - } - - buildReadyToPayRequest(allowedPaymentMethods, baseRequest) { - return Object.assign({}, baseRequest, { - allowedPaymentMethods: allowedPaymentMethods, - }); - } - - /** - * Add a Google Pay purchase button - */ - addButton(baseCardPaymentMethod) { - this.log('addButton', this.context); - - const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); - - this.waitForWrapper(wrapper, () => { - jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape); - - if (ppcpStyle.height) { - jQuery(wrapper).css('height', `${ppcpStyle.height}px`) - } - - const button = - this.paymentsClient.createButton({ - onClick: this.onButtonClick.bind(this), - allowedPaymentMethods: [baseCardPaymentMethod], - buttonColor: buttonStyle.color || 'black', - buttonType: buttonStyle.type || 'pay', - buttonLocale: buttonStyle.language || 'en', - buttonSizeMode: 'fill', - }); - - jQuery(wrapper).append(button); - }); - } - - waitForWrapper(selector, callback, delay = 100, timeout = 2000) { - const startTime = Date.now(); - const interval = setInterval(() => { - const el = document.querySelector(selector); - const timeElapsed = Date.now() - startTime; - - if (el) { - clearInterval(interval); - callback(el); - } else if (timeElapsed > timeout) { - clearInterval(interval); - } - }, delay); - } - - //------------------------ - // Button click - //------------------------ - - /** - * Show Google Pay payment sheet when Google Pay payment button is clicked - */ - async onButtonClick() { - this.log('onButtonClick', this.context); - - const paymentDataRequest = await this.paymentDataRequest(); - this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context); - - window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. - - this.paymentsClient.loadPaymentData(paymentDataRequest); - } - - async paymentDataRequest() { - let baseRequest = { - apiVersion: 2, - apiVersionMinor: 0 - } - - const googlePayConfig = this.googlePayConfig; - const paymentDataRequest = Object.assign({}, baseRequest); - paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods; - paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo(); - paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - - if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { - paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; - paymentDataRequest.shippingAddressRequired = true; - paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters(); - paymentDataRequest.shippingOptionRequired = true; - } else { - paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; - } - - return paymentDataRequest; - } - - //------------------------ - // Shipping processing - //------------------------ - - shippingAddressParameters() { - return { - allowedCountryCodes: this.buttonConfig.shipping.countries, - phoneNumberRequired: true - }; - } - - onPaymentDataChanged(paymentData) { - this.log('onPaymentDataChanged', this.context); - this.log('paymentData', paymentData); - - return new Promise(async (resolve, reject) => { - let paymentDataRequestUpdate = {}; - - const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); - const transactionInfo = await this.contextHandler.transactionInfo(); - - this.log('onPaymentDataChanged:updatedData', updatedData); - this.log('onPaymentDataChanged:transactionInfo', transactionInfo); - - updatedData.country_code = transactionInfo.countryCode; - updatedData.currency_code = transactionInfo.currencyCode; - updatedData.total_str = transactionInfo.totalPrice; - - // Handle unserviceable address. - if (!(updatedData.shipping_options?.shippingOptions?.length)) { - paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); - resolve(paymentDataRequestUpdate); - return; - } - - switch (paymentData.callbackTrigger) { - case 'INITIALIZE': - case 'SHIPPING_ADDRESS': - paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - case 'SHIPPING_OPTION': - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - } - - resolve(paymentDataRequestUpdate); - }); - } - - unserviceableShippingAddressError() { - return { - reason: "SHIPPING_ADDRESS_UNSERVICEABLE", - message: "Cannot ship to the selected address", - intent: "SHIPPING_ADDRESS" - }; - } - - calculateNewTransactionInfo(updatedData) { - return { - countryCode: updatedData.country_code, - currencyCode: updatedData.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: updatedData.total_str - }; - } - - - //------------------------ - // Payment process - //------------------------ - - onPaymentAuthorized(paymentData) { - this.log('onPaymentAuthorized', this.context); - return this.processPayment(paymentData); - } - - async processPayment(paymentData) { - this.log('processPayment', this.context); - - return new Promise(async (resolve, reject) => { - try { - let id = await this.contextHandler.createOrder(); - - this.log('processPayment: createOrder', id, this.context); - - const confirmOrderResponse = await widgetBuilder.paypal.Googlepay().confirmOrder({ - orderId: id, - paymentMethodData: paymentData.paymentMethodData - }); - - this.log('processPayment: confirmOrder', confirmOrderResponse, this.context); - - /** Capture the Order on the Server */ - if (confirmOrderResponse.status === "APPROVED") { - - let approveFailed = false; - 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); - }) - } - }); - - if (!approveFailed) { - resolve(this.processPaymentResponse('SUCCESS')); - } else { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'FAILED TO APPROVE')); - } - - } else { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'TRANSACTION FAILED')); - } - } catch(err) { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', err.message)); - } - }); - } - - processPaymentResponse(state, intent = null, message = null) { - let response = { - transactionState: state, - } - - if (intent || message) { - response.error = { - intent: intent, - message: message, - } - } - - this.log('processPaymentResponse', response, this.context); - - return response; - } - + constructor( + context, + externalHandler, + buttonConfig, + ppcpConfig, + contextHandler + ) { + apmButtonsInit( ppcpConfig ); + + this.isInitialized = false; + + this.context = context; + this.externalHandler = externalHandler; + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.contextHandler = contextHandler; + + this.paymentsClient = null; + + this.log = function () { + if ( this.buttonConfig.is_debug ) { + //console.log('[GooglePayButton]', ...arguments); + } + }; + } + + init( config, transactionInfo ) { + if ( this.isInitialized ) { + return; + } + this.isInitialized = true; + + if ( ! this.validateConfig() ) { + return; + } + + if ( ! this.contextHandler.validateContext() ) { + return; + } + + this.googlePayConfig = config; + this.transactionInfo = transactionInfo; + this.allowedPaymentMethods = config.allowedPaymentMethods; + this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; + + this.initClient(); + this.initEventHandlers(); + + this.paymentsClient + .isReadyToPay( + this.buildReadyToPayRequest( + this.allowedPaymentMethods, + config + ) + ) + .then( ( response ) => { + if ( response.result ) { + if ( + ( this.context === 'checkout' || + this.context === 'pay-now' ) && + this.buttonConfig.is_wc_gateway_enabled === '1' + ) { + const wrapper = document.getElementById( + 'ppc-button-ppcp-googlepay' + ); + + if ( wrapper ) { + const { ppcpStyle, buttonStyle } = + this.contextConfig(); + + wrapper.classList.add( + `ppcp-button-${ ppcpStyle.shape }`, + 'ppcp-button-apm', + 'ppcp-button-googlepay' + ); + + if ( ppcpStyle.height ) { + wrapper.style.height = `${ ppcpStyle.height }px`; + } + + this.addButtonCheckout( + this.baseCardPaymentMethod, + wrapper, + buttonStyle + ); + + return; + } + } + + this.addButton( this.baseCardPaymentMethod ); + } + } ) + .catch( function ( err ) { + console.error( err ); + } ); + } + + reinit() { + if ( ! this.googlePayConfig ) { + return; + } + + this.isInitialized = false; + this.init( this.googlePayConfig, this.transactionInfo ); + } + + validateConfig() { + if ( + [ 'PRODUCTION', 'TEST' ].indexOf( + this.buttonConfig.environment + ) === -1 + ) { + console.error( + '[GooglePayButton] Invalid environment.', + this.buttonConfig.environment + ); + return false; + } + + if ( ! this.contextHandler ) { + console.error( + '[GooglePayButton] Invalid context handler.', + this.contextHandler + ); + return false; + } + + return true; + } + + /** + * Returns configurations relative to this button context. + */ + contextConfig() { + const config = { + wrapper: this.buttonConfig.button.wrapper, + ppcpStyle: this.ppcpConfig.button.style, + buttonStyle: this.buttonConfig.button.style, + ppcpButtonWrapper: this.ppcpConfig.button.wrapper, + }; + + if ( this.context === 'mini-cart' ) { + config.wrapper = this.buttonConfig.button.mini_cart_wrapper; + config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; + config.buttonStyle = this.buttonConfig.button.mini_cart_style; + config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; + + // Handle incompatible types. + if ( config.buttonStyle.type === 'buy' ) { + config.buttonStyle.type = 'pay'; + } + } + + if ( + [ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1 + ) { + config.ppcpButtonWrapper = + '#express-payment-method-ppcp-gateway-paypal'; + } + + return config; + } + + initClient() { + const callbacks = { + onPaymentAuthorized: this.onPaymentAuthorized.bind( this ), + }; + + if ( + this.buttonConfig.shipping.enabled && + this.contextHandler.shippingAllowed() + ) { + callbacks.onPaymentDataChanged = + this.onPaymentDataChanged.bind( this ); + } + + this.paymentsClient = new google.payments.api.PaymentsClient( { + environment: this.buttonConfig.environment, + // add merchant info maybe + paymentDataCallbacks: callbacks, + } ); + } + + initEventHandlers() { + const { wrapper, ppcpButtonWrapper } = this.contextConfig(); + + if ( wrapper === ppcpButtonWrapper ) { + throw new Error( + `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + ); + } + + const syncButtonVisibility = () => { + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { + syncButtonVisibility(); + } + } + ); + + syncButtonVisibility(); + } + + buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { + return Object.assign( {}, baseRequest, { + allowedPaymentMethods, + } ); + } + + /** + * Add a Google Pay purchase button + * @param baseCardPaymentMethod + */ + addButton( baseCardPaymentMethod ) { + this.log( 'addButton', this.context ); + + const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); + + this.waitForWrapper( wrapper, () => { + jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape ); + + if ( ppcpStyle.height ) { + jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` ); + } + + const button = this.paymentsClient.createButton( { + onClick: this.onButtonClick.bind( this ), + allowedPaymentMethods: [ baseCardPaymentMethod ], + buttonColor: buttonStyle.color || 'black', + buttonType: buttonStyle.type || 'pay', + buttonLocale: buttonStyle.language || 'en', + buttonSizeMode: 'fill', + } ); + + jQuery( wrapper ).append( button ); + } ); + } + + addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) { + const button = this.paymentsClient.createButton( { + onClick: this.onButtonClick.bind( this ), + allowedPaymentMethods: [ baseCardPaymentMethod ], + buttonColor: buttonStyle.color || 'black', + buttonType: buttonStyle.type || 'pay', + buttonLocale: buttonStyle.language || 'en', + buttonSizeMode: 'fill', + } ); + + wrapper.appendChild( button ); + } + + waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) { + const startTime = Date.now(); + const interval = setInterval( () => { + const el = document.querySelector( selector ); + const timeElapsed = Date.now() - startTime; + + if ( el ) { + clearInterval( interval ); + callback( el ); + } else if ( timeElapsed > timeout ) { + clearInterval( interval ); + } + }, delay ); + } + + //------------------------ + // Button click + //------------------------ + + /** + * Show Google Pay payment sheet when Google Pay payment button is clicked + */ + onButtonClick() { + this.log( 'onButtonClick', this.context ); + + const paymentDataRequest = this.paymentDataRequest(); + + this.log( + 'onButtonClick: paymentDataRequest', + paymentDataRequest, + this.context + ); + + window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. + + this.paymentsClient.loadPaymentData( paymentDataRequest ); + } + + paymentDataRequest() { + const baseRequest = { + apiVersion: 2, + apiVersionMinor: 0, + }; + + const googlePayConfig = this.googlePayConfig; + const paymentDataRequest = Object.assign( {}, baseRequest ); + paymentDataRequest.allowedPaymentMethods = + googlePayConfig.allowedPaymentMethods; + paymentDataRequest.transactionInfo = this.transactionInfo; + paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; + + if ( + this.buttonConfig.shipping.enabled && + this.contextHandler.shippingAllowed() + ) { + paymentDataRequest.callbackIntents = [ + 'SHIPPING_ADDRESS', + 'SHIPPING_OPTION', + 'PAYMENT_AUTHORIZATION', + ]; + paymentDataRequest.shippingAddressRequired = true; + paymentDataRequest.shippingAddressParameters = + this.shippingAddressParameters(); + paymentDataRequest.shippingOptionRequired = true; + } else { + paymentDataRequest.callbackIntents = [ 'PAYMENT_AUTHORIZATION' ]; + } + + return paymentDataRequest; + } + + //------------------------ + // Shipping processing + //------------------------ + + shippingAddressParameters() { + return { + allowedCountryCodes: this.buttonConfig.shipping.countries, + phoneNumberRequired: true, + }; + } + + onPaymentDataChanged( paymentData ) { + this.log( 'onPaymentDataChanged', this.context ); + this.log( 'paymentData', paymentData ); + + return new Promise( async ( resolve, reject ) => { + try { + const paymentDataRequestUpdate = {}; + + const updatedData = await new UpdatePaymentData( + this.buttonConfig.ajax.update_payment_data + ).update( paymentData ); + const transactionInfo = this.transactionInfo; + + this.log( 'onPaymentDataChanged:updatedData', updatedData ); + this.log( + 'onPaymentDataChanged:transactionInfo', + transactionInfo + ); + + updatedData.country_code = transactionInfo.countryCode; + updatedData.currency_code = transactionInfo.currencyCode; + updatedData.total_str = transactionInfo.totalPrice; + + // Handle unserviceable address. + if ( ! updatedData.shipping_options?.shippingOptions?.length ) { + paymentDataRequestUpdate.error = + this.unserviceableShippingAddressError(); + resolve( paymentDataRequestUpdate ); + return; + } + + switch ( paymentData.callbackTrigger ) { + case 'INITIALIZE': + case 'SHIPPING_ADDRESS': + paymentDataRequestUpdate.newShippingOptionParameters = + updatedData.shipping_options; + paymentDataRequestUpdate.newTransactionInfo = + this.calculateNewTransactionInfo( updatedData ); + break; + case 'SHIPPING_OPTION': + paymentDataRequestUpdate.newTransactionInfo = + this.calculateNewTransactionInfo( updatedData ); + break; + } + + resolve( paymentDataRequestUpdate ); + } catch ( error ) { + console.error( 'Error during onPaymentDataChanged:', error ); + reject( error ); + } + } ); + } + + unserviceableShippingAddressError() { + return { + reason: 'SHIPPING_ADDRESS_UNSERVICEABLE', + message: 'Cannot ship to the selected address', + intent: 'SHIPPING_ADDRESS', + }; + } + + calculateNewTransactionInfo( updatedData ) { + return { + countryCode: updatedData.country_code, + currencyCode: updatedData.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: updatedData.total_str, + }; + } + + //------------------------ + // Payment process + //------------------------ + + onPaymentAuthorized( paymentData ) { + this.log( 'onPaymentAuthorized', this.context ); + return this.processPayment( paymentData ); + } + + async processPayment( paymentData ) { + this.log( 'processPayment', this.context ); + + return new Promise( async ( resolve, reject ) => { + try { + const id = await this.contextHandler.createOrder(); + + this.log( 'processPayment: createOrder', id, this.context ); + + const confirmOrderResponse = await widgetBuilder.paypal + .Googlepay() + .confirmOrder( { + orderId: id, + paymentMethodData: paymentData.paymentMethodData, + } ); + + this.log( + 'processPayment: confirmOrder', + confirmOrderResponse, + this.context + ); + + /** Capture the Order on the Server */ + if ( confirmOrderResponse.status === 'APPROVED' ) { + let approveFailed = false; + 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 ); + } ), + }, + } + ); + + if ( ! approveFailed ) { + resolve( this.processPaymentResponse( 'SUCCESS' ) ); + } else { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + 'FAILED TO APPROVE' + ) + ); + } + } else { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + 'TRANSACTION FAILED' + ) + ); + } + } catch ( err ) { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + err.message + ) + ); + } + } ); + } + + processPaymentResponse( state, intent = null, message = null ) { + const response = { + transactionState: state, + }; + + if ( intent || message ) { + response.error = { + intent, + message, + }; + } + + this.log( 'processPaymentResponse', response, this.context ); + + return response; + } } export default GooglepayButton; diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index 72475cfe5..e267f1b8a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -1,49 +1,83 @@ -import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; -import GooglepayButton from "./GooglepayButton"; +import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; +import GooglepayButton from './GooglepayButton'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; class GooglepayManager { + constructor( buttonConfig, ppcpConfig ) { + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.googlePayConfig = null; + this.transactionInfo = null; + this.contextHandler = null; - constructor(buttonConfig, ppcpConfig) { + this.buttons = []; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.googlePayConfig = null; + buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); - this.buttons = []; + const button = new GooglepayButton( + bootstrap.context, + bootstrap.handler, + buttonConfig, + ppcpConfig, + this.contextHandler + ); - buttonModuleWatcher.watchContextBootstrap((bootstrap) => { - const button = new GooglepayButton( - bootstrap.context, - bootstrap.handler, - buttonConfig, - ppcpConfig, - ); + this.buttons.push( button ); - this.buttons.push(button); + // Initialize button only if googlePayConfig and transactionInfo are already fetched. + if ( this.googlePayConfig && this.transactionInfo ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } else { + await this.init(); + if ( this.googlePayConfig && this.transactionInfo ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } + } + } ); + } - if (this.googlePayConfig) { - button.init(this.googlePayConfig); - } - }); - } + async init() { + try { + if ( ! this.googlePayConfig ) { + // Gets GooglePay configuration of the PayPal merchant. + this.googlePayConfig = await paypal.Googlepay().config(); + } - init() { - (async () => { - // Gets GooglePay configuration of the PayPal merchant. - this.googlePayConfig = await paypal.Googlepay().config(); + if ( ! this.transactionInfo ) { + this.transactionInfo = await this.fetchTransactionInfo(); + } - for (const button of this.buttons) { - button.init(this.googlePayConfig); - } - })(); - } + for ( const button of this.buttons ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } + } catch ( error ) { + console.error( 'Error during initialization:', error ); + } + } - reinit() { - for (const button of this.buttons) { - button.reinit(); - } - } + async fetchTransactionInfo() { + try { + if ( ! this.contextHandler ) { + throw new Error( 'ContextHandler is not initialized' ); + } + return await this.contextHandler.transactionInfo(); + } catch ( error ) { + console.error( 'Error fetching transaction info:', error ); + throw error; + } + } + reinit() { + for ( const button of this.buttons ) { + button.reinit(); + } + } } export default GooglepayManager; diff --git a/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js index 3d56d9316..433bd6b48 100644 --- a/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js +++ b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js @@ -1,38 +1,31 @@ - class UpdatePaymentData { + constructor( config ) { + this.config = config; + } - constructor(config) { - this.config = config; - } - - update(paymentData) { - return new Promise((resolve, reject) => { - fetch( - this.config.endpoint, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: this.config.nonce, - paymentData: paymentData, - }) - - } - ) - .then(result => result.json()) - .then(result => { - if (!result.success) { - return; - } - - resolve(result.data); - }); - }); - } + update( paymentData ) { + return new Promise( ( resolve, reject ) => { + fetch( this.config.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: this.config.nonce, + paymentData, + } ), + } ) + .then( ( result ) => result.json() ) + .then( ( result ) => { + if ( ! result.success ) { + return; + } + resolve( result.data ); + } ); + } ); + } } export default UpdatePaymentData; diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 3e561e6e4..b9f7b3c87 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -1,114 +1,131 @@ import GooglepayButton from './GooglepayButton'; import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton'; import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; /** * Accessor that creates and returns a single PreviewButtonManager instance. */ const buttonManager = () => { - if (!GooglePayPreviewButtonManager.instance) { - GooglePayPreviewButtonManager.instance = new GooglePayPreviewButtonManager(); - } + if ( ! GooglePayPreviewButtonManager.instance ) { + GooglePayPreviewButtonManager.instance = + new GooglePayPreviewButtonManager(); + } - return GooglePayPreviewButtonManager.instance; + return GooglePayPreviewButtonManager.instance; }; - /** * Manages all GooglePay preview buttons on this page. */ class GooglePayPreviewButtonManager extends PreviewButtonManager { - constructor() { - const args = { - methodName: 'GooglePay', - buttonConfig: window.wc_ppcp_googlepay_admin, - }; + constructor() { + const args = { + methodName: 'GooglePay', + buttonConfig: window.wc_ppcp_googlepay_admin, + }; - super(args); - } + super( args ); + } - /** - * Responsible for fetching and returning the PayPal configuration object for this payment - * method. - * - * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. - * @return {Promise<{}>} - */ - async fetchConfig(payPal) { - const apiMethod = payPal?.Googlepay()?.config; + /** + * Responsible for fetching and returning the PayPal configuration object for this payment + * method. + * + * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. + * @return {Promise<{}>} + */ + async fetchConfig( payPal ) { + const apiMethod = payPal?.Googlepay()?.config; - if (!apiMethod) { - this.error('configuration object cannot be retrieved from PayPal'); - return {}; - } + if ( ! apiMethod ) { + this.error( + 'configuration object cannot be retrieved from PayPal' + ); + return {}; + } - try { - return await apiMethod(); - } catch (error) { - if (error.message.includes('Not Eligible')) { - this.apiError = 'Not Eligible'; - } - return null; - } - } + try { + return await apiMethod(); + } catch ( error ) { + if ( error.message.includes( 'Not Eligible' ) ) { + this.apiError = 'Not Eligible'; + } + return null; + } + } - /** - * This method is responsible for creating a new PreviewButton instance and returning it. - * - * @param {string} wrapperId - CSS ID of the wrapper element. - * @return {GooglePayPreviewButton} - */ - createButtonInstance(wrapperId) { - return new GooglePayPreviewButton({ - selector: wrapperId, - apiConfig: this.apiConfig, - }); - } + /** + * This method is responsible for creating a new PreviewButton instance and returning it. + * + * @param {string} wrapperId - CSS ID of the wrapper element. + * @return {GooglePayPreviewButton} + */ + createButtonInstance( wrapperId ) { + return new GooglePayPreviewButton( { + selector: wrapperId, + apiConfig: this.apiConfig, + } ); + } } - /** * A single GooglePay preview button instance. */ class GooglePayPreviewButton extends PreviewButton { - constructor(args) { - super(args); + constructor( args ) { + super( args ); - this.selector = `${args.selector}GooglePay`; - this.defaultAttributes = { - button: { - style: { - type: 'pay', - color: 'black', - language: 'en', - }, - }, - }; - } + this.selector = `${ args.selector }GooglePay`; + this.defaultAttributes = { + button: { + style: { + type: 'pay', + color: 'black', + language: 'en', + }, + }, + }; + } - createNewWrapper() { - const element = super.createNewWrapper(); - element.addClass('ppcp-button-googlepay'); + createNewWrapper() { + const element = super.createNewWrapper(); + element.addClass( 'ppcp-button-googlepay' ); - return element; - } + return element; + } - createButton(buttonConfig) { - const button = new GooglepayButton('preview', null, buttonConfig, this.ppcpConfig); + createButton( buttonConfig ) { + const contextHandler = ContextHandlerFactory.create( + 'preview', + buttonConfig, + this.ppcpConfig, + null + ); - button.init(this.apiConfig); - } + const button = new GooglepayButton( + 'preview', + null, + buttonConfig, + this.ppcpConfig, + contextHandler + ); - /** - * Merge form details into the config object for preview. - * Mutates the previewConfig object; no return value. - */ - dynamicPreviewConfig(buttonConfig, ppcpConfig) { - // Merge the current form-values into the preview-button configuration. - if (ppcpConfig.button && buttonConfig.button) { - Object.assign(buttonConfig.button.style, ppcpConfig.button.style); - } - } + button.init( this.apiConfig, null ); + } + + /** + * Merge form details into the config object for preview. + * Mutates the previewConfig object; no return value. + * @param buttonConfig + * @param ppcpConfig + */ + dynamicPreviewConfig( buttonConfig, ppcpConfig ) { + // Merge the current form-values into the preview-button configuration. + if ( ppcpConfig.button && buttonConfig.button ) { + Object.assign( buttonConfig.button.style, ppcpConfig.button.style ); + } + } } // Initialize the preview button manager. diff --git a/modules/ppcp-googlepay/resources/js/boot-block.js b/modules/ppcp-googlepay/resources/js/boot-block.js index bc117357a..861fda98c 100644 --- a/modules/ppcp-googlepay/resources/js/boot-block.js +++ b/modules/ppcp-googlepay/resources/js/boot-block.js @@ -1,63 +1,69 @@ -import {useEffect, useState} from '@wordpress/element'; -import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; -import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' -import GooglepayManager from "./GooglepayManager"; -import {loadCustomScript} from "@paypal/paypal-js"; +import { useEffect, useState } from '@wordpress/element'; +import { + registerExpressPaymentMethod, + registerPaymentMethod, +} from '@woocommerce/blocks-registry'; +import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; +import GooglepayManager from './GooglepayManager'; +import { loadCustomScript } from '@paypal/paypal-js'; -const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data'); +const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' ); const ppcpConfig = ppcpData.scriptData; -const buttonData = wc.wcSettings.getSetting('ppcp-googlepay_data'); +const buttonData = wc.wcSettings.getSetting( 'ppcp-googlepay_data' ); const buttonConfig = buttonData.scriptData; -if (typeof window.PayPalCommerceGateway === 'undefined') { - window.PayPalCommerceGateway = ppcpConfig; +if ( typeof window.PayPalCommerceGateway === 'undefined' ) { + window.PayPalCommerceGateway = ppcpConfig; } const GooglePayComponent = () => { - const [bootstrapped, setBootstrapped] = useState(false); - const [paypalLoaded, setPaypalLoaded] = useState(false); - const [googlePayLoaded, setGooglePayLoaded] = useState(false); + const [ bootstrapped, setBootstrapped ] = useState( false ); + const [ paypalLoaded, setPaypalLoaded ] = useState( false ); + const [ googlePayLoaded, setGooglePayLoaded ] = useState( false ); - const bootstrap = function () { - const manager = new GooglepayManager(buttonConfig, ppcpConfig); - manager.init(); - }; + const bootstrap = function () { + const manager = new GooglepayManager( buttonConfig, ppcpConfig ); + manager.init(); + }; - useEffect(() => { - // Load GooglePay SDK - loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { - setGooglePayLoaded(true); - }); + useEffect( () => { + // Load GooglePay SDK + loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => { + setGooglePayLoaded( true ); + } ); - // Load PayPal - loadPaypalScript(ppcpConfig, () => { - setPaypalLoaded(true); - }); - }, []); + // Load PayPal + loadPaypalScript( ppcpConfig, () => { + setPaypalLoaded( true ); + } ); + }, [] ); - useEffect(() => { - if (!bootstrapped && paypalLoaded && googlePayLoaded) { - setBootstrapped(true); - bootstrap(); - } - }, [paypalLoaded, googlePayLoaded]); + useEffect( () => { + if ( ! bootstrapped && paypalLoaded && googlePayLoaded ) { + setBootstrapped( true ); + bootstrap(); + } + }, [ paypalLoaded, googlePayLoaded ] ); - return ( -
- ); -} + return ( +
+ ); +}; -const features = ['products']; +const features = [ 'products' ]; -registerExpressPaymentMethod({ - name: buttonData.id, - label:
, - content: , - edit: , - ariaLabel: buttonData.title, - canMakePayment: () => buttonData.enabled, - supports: { - features: features, - }, -}); +registerExpressPaymentMethod( { + name: buttonData.id, + label:
, + content: , + edit: , + ariaLabel: buttonData.title, + canMakePayment: () => buttonData.enabled, + supports: { + features, + }, +} ); diff --git a/modules/ppcp-googlepay/resources/js/boot.js b/modules/ppcp-googlepay/resources/js/boot.js index cb061ec01..99dd414f5 100644 --- a/modules/ppcp-googlepay/resources/js/boot.js +++ b/modules/ppcp-googlepay/resources/js/boot.js @@ -1,65 +1,56 @@ -import {loadCustomScript} from "@paypal/paypal-js"; -import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; -import GooglepayManager from "./GooglepayManager"; -import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; +import { loadCustomScript } from '@paypal/paypal-js'; +import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; +import GooglepayManager from './GooglepayManager'; +import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; -(function ({ - buttonConfig, - ppcpConfig, - jQuery -}) { +( function ( { buttonConfig, ppcpConfig, jQuery } ) { + let manager; - let manager; + const bootstrap = function () { + manager = new GooglepayManager( buttonConfig, ppcpConfig ); + manager.init(); + }; - const bootstrap = function () { - manager = new GooglepayManager(buttonConfig, ppcpConfig); - manager.init(); - }; + setupButtonEvents( function () { + if ( manager ) { + manager.reinit(); + } + } ); - setupButtonEvents(function() { - if (manager) { - manager.reinit(); - } - }); + document.addEventListener( 'DOMContentLoaded', () => { + if ( + typeof buttonConfig === 'undefined' || + typeof ppcpConfig === 'undefined' + ) { + // No PayPal buttons present on this page. + return; + } - document.addEventListener( - 'DOMContentLoaded', - () => { - if ( - (typeof (buttonConfig) === 'undefined') || - (typeof (ppcpConfig) === 'undefined') - ) { - // No PayPal buttons present on this page. - return; - } + let bootstrapped = false; + let paypalLoaded = false; + let googlePayLoaded = false; - let bootstrapped = false; - let paypalLoaded = false; - let googlePayLoaded = false; + const tryToBoot = () => { + if ( ! bootstrapped && paypalLoaded && googlePayLoaded ) { + bootstrapped = true; + bootstrap(); + } + }; - const tryToBoot = () => { - if (!bootstrapped && paypalLoaded && googlePayLoaded) { - bootstrapped = true; - bootstrap(); - } - } + // Load GooglePay SDK + loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => { + googlePayLoaded = true; + tryToBoot(); + } ); - // Load GooglePay SDK - loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { - googlePayLoaded = true; - tryToBoot(); - }); - - // Load PayPal - loadPaypalScript(ppcpConfig, () => { - paypalLoaded = true; - tryToBoot(); - }); - }, - ); - -})({ - buttonConfig: window.wc_ppcp_googlepay, - ppcpConfig: window.PayPalCommerceGateway, - jQuery: window.jQuery -}); + // Load PayPal + loadPaypalScript( ppcpConfig, () => { + paypalLoaded = true; + tryToBoot(); + } ); + } ); +} )( { + buttonConfig: window.wc_ppcp_googlepay, + ppcpConfig: window.PayPalCommerceGateway, + jQuery: window.jQuery, +} ); diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 6f41db792..11cbd740b 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -938,5 +938,14 @@ return array( esc_html( $button_text ) ); }, - + 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { + return new GooglePayGateway( + $container->get( 'wcgateway.order-processor' ), + $container->get( 'api.factory.paypal-checkout-url' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'wcgateway.transaction-url-provider' ), + $container->get( 'session.handler' ), + $container->get( 'googlepay.url' ) + ); + }, ); diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 6fab601b5..bd6faea79 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -13,7 +13,9 @@ use Exception; use Psr\Log\LoggerInterface; use WC_Countries; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; +use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; +use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; @@ -25,6 +27,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; */ class Button implements ButtonInterface { + use ContextTrait; + /** * The URL to the module. * @@ -409,7 +413,7 @@ class Button implements ButtonInterface { */ public function script_data(): array { $shipping = array( - 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' ) + 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' ) ? boolval( $this->settings->get( 'googlepay_button_shipping_enabled' ) ) : false, 'configured' => wc_shipping_enabled() && wc_get_shipping_method_count( false, true ) > 0, @@ -421,19 +425,23 @@ class Button implements ButtonInterface { $is_enabled = $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' ); + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + $is_wc_gateway_enabled = isset( $available_gateways[ GooglePayGateway::ID ] ); + return array( - 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, - 'is_enabled' => $is_enabled, - 'sdk_url' => $this->sdk_url, - 'button' => array( + 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, + 'is_enabled' => $is_enabled, + 'is_wc_gateway_enabled' => $is_wc_gateway_enabled, + 'sdk_url' => $this->sdk_url, + 'button' => array( 'wrapper' => '#ppc-button-googlepay-container', 'style' => $this->button_styles_for_context( 'cart' ), // For now use cart. Pass the context if necessary. 'mini_cart_wrapper' => '#ppc-button-googlepay-container-minicart', 'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ), ), - 'shipping' => $shipping, - 'ajax' => array( + 'shipping' => $shipping, + 'ajax' => array( 'update_payment_data' => array( 'endpoint' => \WC_AJAX::get_endpoint( UpdatePaymentDataEndpoint::ENDPOINT ), 'nonce' => wp_create_nonce( UpdatePaymentDataEndpoint::nonce() ), diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php new file mode 100644 index 000000000..26a84f326 --- /dev/null +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -0,0 +1,238 @@ +id = self::ID; + + $this->method_title = __( 'Google Pay (via PayPal) ', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'The separate payment gateway with the Google Pay button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); + + $this->title = $this->get_option( 'title', __( 'Google Pay', 'woocommerce-paypal-payments' ) ); + $this->description = $this->get_option( 'description', '' ); + + $this->module_url = $module_url; + $this->icon = esc_url( $this->module_url ) . 'assets/images/googlepay.png'; + + $this->init_form_fields(); + $this->init_settings(); + $this->order_processor = $order_processor; + $this->paypal_checkout_url_factory = $paypal_checkout_url_factory; + $this->refund_processor = $refund_processor; + $this->transaction_url_provider = $transaction_url_provider; + $this->session_handler = $session_handler; + + add_action( + 'woocommerce_update_options_payment_gateways_' . $this->id, + array( + $this, + 'process_admin_options', + ) + ); + } + + /** + * Initialize the form fields. + */ + public function init_form_fields() { + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'label' => __( 'Google Pay', 'woocommerce-paypal-payments' ), + 'default' => 'no', + 'desc_tip' => true, + 'description' => __( 'Enable/Disable Google Pay payment gateway.', 'woocommerce-paypal-payments' ), + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->title, + 'desc_tip' => true, + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->description, + 'desc_tip' => true, + 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + ); + } + + /** + * Process payment for a WooCommerce order. + * + * @param int $order_id The WooCommerce order id. + * + * @return array + */ + public function process_payment( $order_id ) { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + + /** + * If the WC_Order is paid through the approved webhook. + */ + //phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + return $this->handle_payment_success( $wc_order ); + } + //phpcs:enable WordPress.Security.NonceVerification.Recommended + + do_action( 'woocommerce_paypal_payments_before_process_order', $wc_order ); + + try { + try { + $this->order_processor->process( $wc_order ); + + do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order ); + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalOrderMissingException $exc ) { + $order = $this->order_processor->create_order( $wc_order ); + + return array( + 'result' => 'success', + 'redirect' => ( $this->paypal_checkout_url_factory )( $order->id() ), + ); + } + } catch ( PayPalApiException $error ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( Exception $error ) { + return $this->handle_payment_failure( $wc_order, $error ); + } + } + + /** + * Process refund. + * + * If the gateway declares 'refunds' support, this will allow it to refund. + * a passed in amount. + * + * @param int $order_id Order ID. + * @param float $amount Refund amount. + * @param string $reason Refund reason. + * @return boolean True or false based on success, or a WP_Error object. + */ + public function process_refund( $order_id, $amount = null, $reason = '' ) { + $order = wc_get_order( $order_id ); + if ( ! is_a( $order, \WC_Order::class ) ) { + return false; + } + return $this->refund_processor->process( $order, (float) $amount, (string) $reason ); + } + + /** + * Return transaction url for this gateway and given order. + * + * @param \WC_Order $order WC order to get transaction url by. + * + * @return string + */ + public function get_transaction_url( $order ): string { + $this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order ); + + return parent::get_transaction_url( $order ); + } +} diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index e2b0ea8c5..b7feedc07 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; +use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; @@ -159,6 +160,46 @@ class GooglepayModule implements ModuleInterface { }, 1 ); + + add_filter( + 'woocommerce_payment_gateways', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $methods ) use ( $c ): array { + if ( ! is_array( $methods ) ) { + return $methods; + } + + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + if ( $settings->has( 'googlepay_button_enabled' ) && $settings->get( 'googlepay_button_enabled' ) ) { + $googlepay_gateway = $c->get( 'googlepay.wc-gateway' ); + assert( $googlepay_gateway instanceof WC_Payment_Gateway ); + + $methods[] = $googlepay_gateway; + } + + return $methods; + } + ); + + add_action( + 'woocommerce_review_order_after_submit', + function () { + echo '
'; + } + ); + + add_action( + 'woocommerce_pay_order_after_submit', + function () { + echo '
'; + } + ); } /** diff --git a/modules/ppcp-onboarding/resources/js/onboarding.js b/modules/ppcp-onboarding/resources/js/onboarding.js index 57d30161b..905c66c65 100644 --- a/modules/ppcp-onboarding/resources/js/onboarding.js +++ b/modules/ppcp-onboarding/resources/js/onboarding.js @@ -3,413 +3,482 @@ const ppcp_onboarding = { PAYPAL_JS_ID: 'ppcp-onboarding-paypal-js', _timeout: false, - STATE_START: 'start', - STATE_ONBOARDED: 'onboarded', + STATE_START: 'start', + STATE_ONBOARDED: 'onboarded', - init: function() { - document.addEventListener('DOMContentLoaded', this.reload); + init() { + document.addEventListener( 'DOMContentLoaded', this.reload ); }, - reload: function() { - const buttons = document.querySelectorAll(ppcp_onboarding.BUTTON_SELECTOR); + reload() { + const buttons = document.querySelectorAll( + ppcp_onboarding.BUTTON_SELECTOR + ); - if (buttons.length > 0) { - // Add event listeners to buttons preventing link clicking if PayPal init failed. - buttons.forEach( - (element) => { - if (element.hasAttribute('data-ppcp-button-initialized')) { - return; - } + if ( buttons.length > 0 ) { + // Add event listeners to buttons preventing link clicking if PayPal init failed. + buttons.forEach( ( element ) => { + if ( element.hasAttribute( 'data-ppcp-button-initialized' ) ) { + return; + } - element.addEventListener( - 'click', - (e) => { - if (!element.hasAttribute('data-ppcp-button-initialized') || 'undefined' === typeof window.PAYPAL) { - e.preventDefault(); - } - } - ); - } - ); + element.addEventListener( 'click', ( e ) => { + if ( + ! element.hasAttribute( + 'data-ppcp-button-initialized' + ) || + 'undefined' === typeof window.PAYPAL + ) { + e.preventDefault(); + } + } ); + } ); - // Clear any previous PayPal scripts. - [ppcp_onboarding.PAYPAL_JS_ID, 'signup-js', 'biz-js'].forEach( - (scriptID) => { - const scriptTag = document.getElementById(scriptID); + // Clear any previous PayPal scripts. + [ ppcp_onboarding.PAYPAL_JS_ID, 'signup-js', 'biz-js' ].forEach( + ( scriptID ) => { + const scriptTag = document.getElementById( scriptID ); - if (scriptTag) { - scriptTag.parentNode.removeChild(scriptTag); - } + if ( scriptTag ) { + scriptTag.parentNode.removeChild( scriptTag ); + } - if ('undefined' !== typeof window.PAYPAL) { - delete window.PAYPAL; - } - } - ); + if ( 'undefined' !== typeof window.PAYPAL ) { + delete window.PAYPAL; + } + } + ); - // Load PayPal scripts. - const paypalScriptTag = document.createElement('script'); - paypalScriptTag.id = ppcp_onboarding.PAYPAL_JS_ID; - paypalScriptTag.src = PayPalCommerceGatewayOnboarding.paypal_js_url; - document.body.appendChild(paypalScriptTag); + // Load PayPal scripts. + const paypalScriptTag = document.createElement( 'script' ); + paypalScriptTag.id = ppcp_onboarding.PAYPAL_JS_ID; + paypalScriptTag.src = PayPalCommerceGatewayOnboarding.paypal_js_url; + document.body.appendChild( paypalScriptTag ); - if (ppcp_onboarding._timeout) { - clearTimeout(ppcp_onboarding._timeout); - } + if ( ppcp_onboarding._timeout ) { + clearTimeout( ppcp_onboarding._timeout ); + } - ppcp_onboarding._timeout = setTimeout( - () => { - buttons.forEach((element) => { element.setAttribute('data-ppcp-button-initialized', 'true'); }); + ppcp_onboarding._timeout = setTimeout( () => { + buttons.forEach( ( element ) => { + element.setAttribute( + 'data-ppcp-button-initialized', + 'true' + ); + } ); - if ('undefined' !== window.PAYPAL.apps.Signup) { - window.PAYPAL.apps.Signup.render(); - } - }, - 1000 - ); + if ( 'undefined' !== window.PAYPAL.apps.Signup ) { + window.PAYPAL.apps.Signup.render(); + } + }, 1000 ); } - const $onboarding_inputs = function () { - return jQuery('*[data-onboarding-option]'); - }; - const onboarding_options = function () { - let options = {}; - $onboarding_inputs().each((index, el) => { - const opt = jQuery(el).data('onboardingOption'); - options[opt] = el.checked; - }); - return options; - } - const disable_onboarding_options = function () { - $onboarding_inputs().each((index, el) => { - el.setAttribute('disabled', 'disabled'); - }); - } - const enable_onboarding_options = function () { - $onboarding_inputs().each((index, el) => { - el.removeAttribute('disabled'); - }); - } - const update_onboarding_options = function () { - const spinner = ''; + const $onboarding_inputs = function () { + return jQuery( '*[data-onboarding-option]' ); + }; + const onboarding_options = function () { + const options = {}; + $onboarding_inputs().each( ( index, el ) => { + const opt = jQuery( el ).data( 'onboardingOption' ); + options[ opt ] = el.checked; + } ); + return options; + }; + const disable_onboarding_options = function () { + $onboarding_inputs().each( ( index, el ) => { + el.setAttribute( 'disabled', 'disabled' ); + } ); + }; + const enable_onboarding_options = function () { + $onboarding_inputs().each( ( index, el ) => { + el.removeAttribute( 'disabled' ); + } ); + }; + const update_onboarding_options = function () { + const spinner = + ''; - disable_onboarding_options(); - buttons.forEach((element) => { - element.removeAttribute('href'); - element.setAttribute('disabled', 'disabled'); - jQuery(spinner).insertAfter(element); - }); + disable_onboarding_options(); + buttons.forEach( ( element ) => { + element.removeAttribute( 'href' ); + element.setAttribute( 'disabled', 'disabled' ); + jQuery( spinner ).insertAfter( element ); + } ); - fetch(PayPalCommerceGatewayOnboarding.update_signup_links_endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: PayPalCommerceGatewayOnboarding.update_signup_links_nonce, - settings: onboarding_options() - }) - }).then((res)=>{ - return res.json(); - }).then((data)=>{ - if (!data.success) { - alert('Could not update signup buttons: ' + JSON.stringify(data)); - return; - } - buttons.forEach((element) => { - for (let [key, value] of Object.entries(data.data.signup_links)) { - key = 'connect-to' + key.replace(/-/g, ''); - if(key === element.id) { - element.setAttribute('href', value); - element.removeAttribute('disabled') - document.querySelector('.spinner').remove() - } - } - }); - enable_onboarding_options(); - }); - } - $onboarding_inputs().on('click', (event) => { - event.preventDefault(); - update_onboarding_options(); - }); - }, - - loginSeller: function(env, authCode, sharedId) { - fetch( - PayPalCommerceGatewayOnboarding.endpoint, - { - method: 'POST', - headers: { - 'content-type': 'application/json' - }, - body: JSON.stringify( - { - authCode: authCode, - sharedId: sharedId, - nonce: PayPalCommerceGatewayOnboarding.nonce, - env: env, - acceptCards: document.querySelector('#ppcp-onboarding-accept-cards').checked, + fetch( + PayPalCommerceGatewayOnboarding.update_signup_links_endpoint, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: PayPalCommerceGatewayOnboarding.update_signup_links_nonce, + settings: onboarding_options(), + } ), + } + ) + .then( ( res ) => { + return res.json(); + } ) + .then( ( data ) => { + if ( ! data.success ) { + alert( + 'Could not update signup buttons: ' + + JSON.stringify( data ) + ); + return; } - ) - } - ); + buttons.forEach( ( element ) => { + for ( let [ key, value ] of Object.entries( + data.data.signup_links + ) ) { + key = 'connect-to' + key.replace( /-/g, '' ); + if ( key === element.id ) { + element.setAttribute( 'href', value ); + element.removeAttribute( 'disabled' ); + document.querySelector( '.spinner' ).remove(); + } + } + } ); + enable_onboarding_options(); + } ); + }; + $onboarding_inputs().on( 'click', ( event ) => { + event.preventDefault(); + update_onboarding_options(); + } ); + }, + + loginSeller( env, authCode, sharedId ) { + fetch( PayPalCommerceGatewayOnboarding.endpoint, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify( { + authCode, + sharedId, + nonce: PayPalCommerceGatewayOnboarding.nonce, + env, + acceptCards: document.querySelector( + '#ppcp-onboarding-accept-cards' + ).checked, + } ), + } ); }, }; - -window.ppcp_onboarding_sandboxCallback = function(...args) { - return ppcp_onboarding.loginSeller('sandbox', ...args); +window.ppcp_onboarding_sandboxCallback = function ( ...args ) { + return ppcp_onboarding.loginSeller( 'sandbox', ...args ); }; -window.ppcp_onboarding_productionCallback = function(...args) { - return ppcp_onboarding.loginSeller('production', ...args); +window.ppcp_onboarding_productionCallback = function ( ...args ) { + return ppcp_onboarding.loginSeller( 'production', ...args ); }; -(() => { - const productionCredentialElementsSelectors = [ - '#field-merchant_email_production', - '#field-merchant_id_production', - '#field-client_id_production', - '#field-client_secret_production', - ]; - const sandboxCredentialElementsSelectors = [ - '#field-merchant_email_sandbox', - '#field-merchant_id_sandbox', - '#field-client_id_sandbox', - '#field-client_secret_sandbox', - ]; +( () => { + const productionCredentialElementsSelectors = [ + '#field-merchant_email_production', + '#field-merchant_id_production', + '#field-client_id_production', + '#field-client_secret_production', + ]; + const sandboxCredentialElementsSelectors = [ + '#field-merchant_email_sandbox', + '#field-merchant_id_sandbox', + '#field-client_id_sandbox', + '#field-client_secret_sandbox', + ]; - const updateOptionsState = () => { - const cardsChk = document.querySelector('#ppcp-onboarding-accept-cards'); - if (!cardsChk) { - return; - } + const updateOptionsState = () => { + const cardsChk = document.querySelector( + '#ppcp-onboarding-accept-cards' + ); + if ( ! cardsChk ) { + return; + } - document.querySelectorAll('#ppcp-onboarding-dcc-options input').forEach(input => { - input.disabled = !cardsChk.checked; - }); + document + .querySelectorAll( '#ppcp-onboarding-dcc-options input' ) + .forEach( ( input ) => { + input.disabled = ! cardsChk.checked; + } ); - document.querySelector('.ppcp-onboarding-cards-options').style.display = !cardsChk.checked ? 'none' : ''; + document.querySelector( + '.ppcp-onboarding-cards-options' + ).style.display = ! cardsChk.checked ? 'none' : ''; - const basicRb = document.querySelector('#ppcp-onboarding-dcc-basic'); + const basicRb = document.querySelector( '#ppcp-onboarding-dcc-basic' ); - const isExpress = !cardsChk.checked || basicRb.checked; + const isExpress = ! cardsChk.checked || basicRb.checked; - const expressButtonSelectors = [ - '#field-ppcp_onboarding_production_express', - '#field-ppcp_onboarding_sandbox_express', - ]; - const ppcpButtonSelectors = [ - '#field-ppcp_onboarding_production_ppcp', - '#field-ppcp_onboarding_sandbox_ppcp', - ]; + const expressButtonSelectors = [ + '#field-ppcp_onboarding_production_express', + '#field-ppcp_onboarding_sandbox_express', + ]; + const ppcpButtonSelectors = [ + '#field-ppcp_onboarding_production_ppcp', + '#field-ppcp_onboarding_sandbox_ppcp', + ]; - document.querySelectorAll(expressButtonSelectors.join()).forEach( - element => element.style.display = isExpress ? '' : 'none' - ); - document.querySelectorAll(ppcpButtonSelectors.join()).forEach( - element => element.style.display = !isExpress ? '' : 'none' - ); + document + .querySelectorAll( expressButtonSelectors.join() ) + .forEach( + ( element ) => + ( element.style.display = isExpress ? '' : 'none' ) + ); + document + .querySelectorAll( ppcpButtonSelectors.join() ) + .forEach( + ( element ) => + ( element.style.display = ! isExpress ? '' : 'none' ) + ); - const screemImg = document.querySelector('#ppcp-onboarding-cards-screen-img'); - if (screemImg) { - const currentRb = Array.from(document.querySelectorAll('#ppcp-onboarding-dcc-options input[type="radio"]')) - .filter(rb => rb.checked)[0] ?? null; + const screemImg = document.querySelector( + '#ppcp-onboarding-cards-screen-img' + ); + if ( screemImg ) { + const currentRb = + Array.from( + document.querySelectorAll( + '#ppcp-onboarding-dcc-options input[type="radio"]' + ) + ).filter( ( rb ) => rb.checked )[ 0 ] ?? null; - const imgUrl = currentRb.getAttribute('data-screen-url'); - screemImg.src = imgUrl; - } - }; + const imgUrl = currentRb.getAttribute( 'data-screen-url' ); + screemImg.src = imgUrl; + } + }; - const updateManualInputControls = (shown, isSandbox, isAnyEnvOnboarded) => { - const productionElementsSelectors = productionCredentialElementsSelectors; - const sandboxElementsSelectors = sandboxCredentialElementsSelectors; - const otherElementsSelectors = [ - '.woocommerce-save-button', - ]; - if (!isAnyEnvOnboarded) { - otherElementsSelectors.push('#field-sandbox_on'); - } + const updateManualInputControls = ( + shown, + isSandbox, + isAnyEnvOnboarded + ) => { + const productionElementsSelectors = + productionCredentialElementsSelectors; + const sandboxElementsSelectors = sandboxCredentialElementsSelectors; + const otherElementsSelectors = [ '.woocommerce-save-button' ]; + if ( ! isAnyEnvOnboarded ) { + otherElementsSelectors.push( '#field-sandbox_on' ); + } - document.querySelectorAll(productionElementsSelectors.join()).forEach( - element => { - element.classList.remove('hide', 'show'); - element.classList.add((shown && !isSandbox) ? 'show' : 'hide'); - } - ); - document.querySelectorAll(sandboxElementsSelectors.join()).forEach( - element => { - element.classList.remove('hide', 'show'); - element.classList.add((shown && isSandbox) ? 'show' : 'hide'); - } - ); - document.querySelectorAll(otherElementsSelectors.join()).forEach( - element => element.style.display = shown ? '' : 'none' - ); - }; + document + .querySelectorAll( productionElementsSelectors.join() ) + .forEach( ( element ) => { + element.classList.remove( 'hide', 'show' ); + element.classList.add( shown && ! isSandbox ? 'show' : 'hide' ); + } ); + document + .querySelectorAll( sandboxElementsSelectors.join() ) + .forEach( ( element ) => { + element.classList.remove( 'hide', 'show' ); + element.classList.add( shown && isSandbox ? 'show' : 'hide' ); + } ); + document + .querySelectorAll( otherElementsSelectors.join() ) + .forEach( + ( element ) => ( element.style.display = shown ? '' : 'none' ) + ); + }; - const updateEnvironmentControls = (isSandbox) => { - const productionElementsSelectors = [ - '#field-ppcp_disconnect_production', - '#field-credentials_production_heading', - ]; - const sandboxElementsSelectors = [ - '#field-ppcp_disconnect_sandbox', - '#field-credentials_sandbox_heading', - ]; + const updateEnvironmentControls = ( isSandbox ) => { + const productionElementsSelectors = [ + '#field-ppcp_disconnect_production', + '#field-credentials_production_heading', + ]; + const sandboxElementsSelectors = [ + '#field-ppcp_disconnect_sandbox', + '#field-credentials_sandbox_heading', + ]; - document.querySelectorAll(productionElementsSelectors.join()).forEach( - element => element.style.display = !isSandbox ? '' : 'none' - ); - document.querySelectorAll(sandboxElementsSelectors.join()).forEach( - element => element.style.display = isSandbox ? '' : 'none' - ); - }; + document + .querySelectorAll( productionElementsSelectors.join() ) + .forEach( + ( element ) => + ( element.style.display = ! isSandbox ? '' : 'none' ) + ); + document + .querySelectorAll( sandboxElementsSelectors.join() ) + .forEach( + ( element ) => + ( element.style.display = isSandbox ? '' : 'none' ) + ); + }; - let isDisconnecting = false; + let isDisconnecting = false; - const disconnect = (event) => { - event.preventDefault(); - const fields = event.target.classList.contains('production') ? productionCredentialElementsSelectors : sandboxCredentialElementsSelectors; + const disconnect = ( event ) => { + event.preventDefault(); + const fields = event.target.classList.contains( 'production' ) + ? productionCredentialElementsSelectors + : sandboxCredentialElementsSelectors; - document.querySelectorAll(fields.map(f => f + ' input').join()).forEach( - (element) => { - element.value = ''; - } - ); + document + .querySelectorAll( fields.map( ( f ) => f + ' input' ).join() ) + .forEach( ( element ) => { + element.value = ''; + } ); - sandboxSwitchElement.checked = ! sandboxSwitchElement.checked; + sandboxSwitchElement.checked = ! sandboxSwitchElement.checked; - isDisconnecting = true; + isDisconnecting = true; - document.querySelector('.woocommerce-save-button').click(); - }; + document.querySelector( '.woocommerce-save-button' ).click(); + }; - // Prevent the message about unsaved checkbox/radiobutton when reloading the page. - // (WC listens for changes on all inputs and sets dirty flag until form submission) - const preventDirtyCheckboxPropagation = event => { - event.preventDefault(); - event.stopPropagation(); + // Prevent the message about unsaved checkbox/radiobutton when reloading the page. + // (WC listens for changes on all inputs and sets dirty flag until form submission) + const preventDirtyCheckboxPropagation = ( event ) => { + event.preventDefault(); + event.stopPropagation(); - const value = event.target.checked; - setTimeout( () => { - event.target.checked = value; - }, 1 - ); - }; + const value = event.target.checked; + setTimeout( () => { + event.target.checked = value; + }, 1 ); + }; - const sandboxSwitchElement = document.querySelector('#ppcp-sandbox_on'); + const sandboxSwitchElement = document.querySelector( '#ppcp-sandbox_on' ); - const validate = () => { - const selectors = sandboxSwitchElement.checked ? sandboxCredentialElementsSelectors : productionCredentialElementsSelectors; - const values = selectors.map(s => document.querySelector(s + ' input')).map(el => el.value); + const validate = () => { + const selectors = sandboxSwitchElement.checked + ? sandboxCredentialElementsSelectors + : productionCredentialElementsSelectors; + const values = selectors + .map( ( s ) => document.querySelector( s + ' input' ) ) + .map( ( el ) => el.value ); - const errors = []; - if (values.some(v => !v)) { - errors.push(PayPalCommerceGatewayOnboarding.error_messages.no_credentials); - } - - return errors; - }; - - const isAnyEnvOnboarded = PayPalCommerceGatewayOnboarding.sandbox_state === ppcp_onboarding.STATE_ONBOARDED || - PayPalCommerceGatewayOnboarding.production_state === ppcp_onboarding.STATE_ONBOARDED; - - document.querySelectorAll('.ppcp-disconnect').forEach( - (button) => { - button.addEventListener( - 'click', - disconnect + const errors = []; + if ( values.some( ( v ) => ! v ) ) { + errors.push( + PayPalCommerceGatewayOnboarding.error_messages.no_credentials ); } + + return errors; + }; + + const isAnyEnvOnboarded = + PayPalCommerceGatewayOnboarding.sandbox_state === + ppcp_onboarding.STATE_ONBOARDED || + PayPalCommerceGatewayOnboarding.production_state === + ppcp_onboarding.STATE_ONBOARDED; + + document.querySelectorAll( '.ppcp-disconnect' ).forEach( ( button ) => { + button.addEventListener( 'click', disconnect ); + } ); + + document + .querySelectorAll( '.ppcp-onboarding-options input' ) + .forEach( ( element ) => { + element.addEventListener( 'click', ( event ) => { + updateOptionsState(); + + preventDirtyCheckboxPropagation( event ); + } ); + } ); + + const isSandboxInBackend = + PayPalCommerceGatewayOnboarding.current_env === 'sandbox'; + if ( sandboxSwitchElement.checked !== isSandboxInBackend ) { + sandboxSwitchElement.checked = isSandboxInBackend; + } + + updateOptionsState(); + + const settingsContainer = document.querySelector( '#mainform .form-table' ); + + const markCurrentOnboardingState = ( isOnboarded ) => { + settingsContainer.classList.remove( + 'ppcp-onboarded', + 'ppcp-onboarding' + ); + settingsContainer.classList.add( + isOnboarded ? 'ppcp-onboarded' : 'ppcp-onboarding' + ); + }; + + markCurrentOnboardingState( + PayPalCommerceGatewayOnboarding.current_state === + ppcp_onboarding.STATE_ONBOARDED ); - document.querySelectorAll('.ppcp-onboarding-options input').forEach( - (element) => { - element.addEventListener('click', event => { - updateOptionsState(); + const manualInputToggleButton = document.querySelector( + '#field-toggle_manual_input button' + ); + let isManualInputShown = + PayPalCommerceGatewayOnboarding.current_state === + ppcp_onboarding.STATE_ONBOARDED; - preventDirtyCheckboxPropagation(event); - }); - } - ); + manualInputToggleButton.addEventListener( 'click', ( event ) => { + event.preventDefault(); - const isSandboxInBackend = PayPalCommerceGatewayOnboarding.current_env === 'sandbox'; - if (sandboxSwitchElement.checked !== isSandboxInBackend) { - sandboxSwitchElement.checked = isSandboxInBackend; - } + isManualInputShown = ! isManualInputShown; - updateOptionsState(); + updateManualInputControls( + isManualInputShown, + sandboxSwitchElement.checked, + isAnyEnvOnboarded + ); + } ); - const settingsContainer = document.querySelector('#mainform .form-table'); + sandboxSwitchElement.addEventListener( 'click', ( event ) => { + const isSandbox = sandboxSwitchElement.checked; - const markCurrentOnboardingState = (isOnboarded) => { - settingsContainer.classList.remove('ppcp-onboarded', 'ppcp-onboarding'); - settingsContainer.classList.add(isOnboarded ? 'ppcp-onboarded' : 'ppcp-onboarding'); - } + if ( isAnyEnvOnboarded ) { + const onboardingState = isSandbox + ? PayPalCommerceGatewayOnboarding.sandbox_state + : PayPalCommerceGatewayOnboarding.production_state; + const isOnboarded = + onboardingState === ppcp_onboarding.STATE_ONBOARDED; - markCurrentOnboardingState(PayPalCommerceGatewayOnboarding.current_state === ppcp_onboarding.STATE_ONBOARDED); + markCurrentOnboardingState( isOnboarded ); + isManualInputShown = isOnboarded; + } - const manualInputToggleButton = document.querySelector('#field-toggle_manual_input button'); - let isManualInputShown = PayPalCommerceGatewayOnboarding.current_state === ppcp_onboarding.STATE_ONBOARDED; + updateManualInputControls( + isManualInputShown, + isSandbox, + isAnyEnvOnboarded + ); - manualInputToggleButton.addEventListener( - 'click', - (event) => { - event.preventDefault(); + updateEnvironmentControls( isSandbox ); - isManualInputShown = !isManualInputShown; + preventDirtyCheckboxPropagation( event ); + } ); - updateManualInputControls(isManualInputShown, sandboxSwitchElement.checked, isAnyEnvOnboarded); - } - ); + updateManualInputControls( + isManualInputShown, + sandboxSwitchElement.checked, + isAnyEnvOnboarded + ); - sandboxSwitchElement.addEventListener( - 'click', - (event) => { - const isSandbox = sandboxSwitchElement.checked; + updateEnvironmentControls( sandboxSwitchElement.checked ); - if (isAnyEnvOnboarded) { - const onboardingState = isSandbox ? PayPalCommerceGatewayOnboarding.sandbox_state : PayPalCommerceGatewayOnboarding.production_state; - const isOnboarded = onboardingState === ppcp_onboarding.STATE_ONBOARDED; + document.querySelector( '#mainform' ).addEventListener( 'submit', ( e ) => { + if ( isDisconnecting ) { + return; + } - markCurrentOnboardingState(isOnboarded); - isManualInputShown = isOnboarded; - } + const errors = validate(); + if ( errors.length ) { + e.preventDefault(); - updateManualInputControls(isManualInputShown, isSandbox, isAnyEnvOnboarded); + const errorLabel = document.querySelector( + '#ppcp-form-errors-label' + ); + errorLabel.parentElement.parentElement.classList.remove( 'hide' ); - updateEnvironmentControls(isSandbox); + errorLabel.innerHTML = errors.join( '
' ); - preventDirtyCheckboxPropagation(event); - } - ); - - updateManualInputControls(isManualInputShown, sandboxSwitchElement.checked, isAnyEnvOnboarded); - - updateEnvironmentControls(sandboxSwitchElement.checked); - - document.querySelector('#mainform').addEventListener('submit', e => { - if (isDisconnecting) { - return; - } - - const errors = validate(); - if (errors.length) { - e.preventDefault(); - - const errorLabel = document.querySelector('#ppcp-form-errors-label'); - errorLabel.parentElement.parentElement.classList.remove('hide'); - - errorLabel.innerHTML = errors.join('
'); - - errorLabel.scrollIntoView(); - window.scrollBy(0, -120); // WP + WC floating header - } - }); + errorLabel.scrollIntoView(); + window.scrollBy( 0, -120 ); // WP + WC floating header + } + } ); // Onboarding buttons. ppcp_onboarding.init(); -})(); +} )(); diff --git a/modules/ppcp-onboarding/resources/js/settings.js b/modules/ppcp-onboarding/resources/js/settings.js index 0ef811e2d..1afc1890b 100644 --- a/modules/ppcp-onboarding/resources/js/settings.js +++ b/modules/ppcp-onboarding/resources/js/settings.js @@ -1,429 +1,510 @@ -document.addEventListener( - 'DOMContentLoaded', - () => { - const payLaterMessagingSelectableLocations = ['product', 'cart', 'checkout', 'shop', 'home']; - const payLaterMessagingAllLocations = payLaterMessagingSelectableLocations.concat('general'); - const payLaterMessagingLocationsSelector = '#field-pay_later_messaging_locations'; - const payLaterMessagingLocationsSelect = payLaterMessagingLocationsSelector + ' select'; - const payLaterMessagingEnabledSelector = '#ppcp-pay_later_messaging_enabled'; +document.addEventListener( 'DOMContentLoaded', () => { + const payLaterMessagingSelectableLocations = [ + 'product', + 'cart', + 'checkout', + 'shop', + 'home', + ]; + const payLaterMessagingAllLocations = + payLaterMessagingSelectableLocations.concat( 'general' ); + const payLaterMessagingLocationsSelector = + '#field-pay_later_messaging_locations'; + const payLaterMessagingLocationsSelect = + payLaterMessagingLocationsSelector + ' select'; + const payLaterMessagingEnabledSelector = + '#ppcp-pay_later_messaging_enabled'; - const smartButtonLocationsSelector = '#field-smart_button_locations'; - const smartButtonLocationsSelect = smartButtonLocationsSelector + ' select'; - const smartButtonSelectableLocations = ['product', 'cart', 'checkout', 'mini-cart', 'cart-block', 'checkout-block-express']; + const smartButtonLocationsSelector = '#field-smart_button_locations'; + const smartButtonLocationsSelect = smartButtonLocationsSelector + ' select'; + const smartButtonSelectableLocations = [ + 'product', + 'cart', + 'checkout', + 'mini-cart', + 'cart-block', + 'checkout-block-express', + ]; - const groupToggle = (selector, group) => { - const toggleElement = document.querySelector(selector); - if (! toggleElement) { - return; - } + const groupToggle = ( selector, group ) => { + const toggleElement = document.querySelector( selector ); + if ( ! toggleElement ) { + return; + } - if (! toggleElement.checked) { - group.forEach( (elementToHide) => { - const element = document.querySelector(elementToHide); - if (element) { - element.style.display = 'none'; - } - }) - } - toggleElement.addEventListener( - 'change', - (event) => { - if (! event.target.checked) { - group.forEach( (elementToHide) => { - const element = document.querySelector(elementToHide); - if (element) { - element.style.display = 'none'; - } - }); + if ( ! toggleElement.checked ) { + group.forEach( ( elementToHide ) => { + const element = document.querySelector( elementToHide ); + if ( element ) { + element.style.display = 'none'; + } + } ); + } + toggleElement.addEventListener( 'change', ( event ) => { + if ( ! event.target.checked ) { + group.forEach( ( elementToHide ) => { + const element = document.querySelector( elementToHide ); + if ( element ) { + element.style.display = 'none'; + } + } ); - return; - } + return; + } - group.forEach( (elementToShow) => { - document.querySelector(elementToShow).style.display = ''; - }) + group.forEach( ( elementToShow ) => { + document.querySelector( elementToShow ).style.display = ''; + } ); - togglePayLaterMessageFields(); - } - ); - }; + togglePayLaterMessageFields(); + } ); + }; - const groupToggleSelect = (selector, group) => { - const toggleElement = document.querySelector(selector); - if (! toggleElement) { - return; - } - const value = toggleElement.value; - group.forEach( (elementToToggle) => { - const domElement = document.querySelector(elementToToggle.selector); - if (! domElement) { - return; - } - if (value === elementToToggle.value && domElement.style.display !== 'none') { - domElement.style.display = ''; - return; - } - domElement.style.display = 'none'; - }); + const groupToggleSelect = ( selector, group ) => { + const toggleElement = document.querySelector( selector ); + if ( ! toggleElement ) { + return; + } + const value = toggleElement.value; + group.forEach( ( elementToToggle ) => { + const domElement = document.querySelector( + elementToToggle.selector + ); + if ( ! domElement ) { + return; + } + if ( + value === elementToToggle.value && + domElement.style.display !== 'none' + ) { + domElement.style.display = ''; + return; + } + domElement.style.display = 'none'; + } ); - // We need to use jQuery here as the select might be a select2 element, which doesn't use native events. - jQuery(toggleElement).on( - 'change', - (event) => { - const value = event.target.value; - group.forEach( (elementToToggle) => { - if (value === elementToToggle.value) { - document.querySelector(elementToToggle.selector).style.display = ''; - return; - } - document.querySelector(elementToToggle.selector).style.display = 'none'; - }) - } - ); - }; + // We need to use jQuery here as the select might be a select2 element, which doesn't use native events. + jQuery( toggleElement ).on( 'change', ( event ) => { + const value = event.target.value; + group.forEach( ( elementToToggle ) => { + if ( value === elementToToggle.value ) { + document.querySelector( + elementToToggle.selector + ).style.display = ''; + return; + } + document.querySelector( + elementToToggle.selector + ).style.display = 'none'; + } ); + } ); + }; - const togglePayLaterMessageFields = () => { - payLaterMessagingAllLocations.forEach( (location) => { - groupToggleSelect( - '#ppcp-pay_later_' + location + '_message_layout', - [ - { - value:'text', - selector:'#field-pay_later_' + location + '_message_logo' - }, - { - value:'text', - selector:'#field-pay_later_' + location + '_message_position' - }, - { - value:'text', - selector:'#field-pay_later_' + location + '_message_color' - }, - { - value:'flex', - selector:'#field-pay_later_' + location + '_message_flex_ratio' - }, - { - value:'flex', - selector:'#field-pay_later_' + location + '_message_flex_color' - } - ] - ); - }) - } + const togglePayLaterMessageFields = () => { + payLaterMessagingAllLocations.forEach( ( location ) => { + groupToggleSelect( + '#ppcp-pay_later_' + location + '_message_layout', + [ + { + value: 'text', + selector: + '#field-pay_later_' + location + '_message_logo', + }, + { + value: 'text', + selector: + '#field-pay_later_' + + location + + '_message_position', + }, + { + value: 'text', + selector: + '#field-pay_later_' + location + '_message_color', + }, + { + value: 'flex', + selector: + '#field-pay_later_' + + location + + '_message_flex_ratio', + }, + { + value: 'flex', + selector: + '#field-pay_later_' + + location + + '_message_flex_color', + }, + ] + ); + } ); + }; - const removeDisabledCardIcons = (disabledCardsSelectSelector, iconsSelectSelector) => { - const iconsSelect = document.querySelector(iconsSelectSelector); - if (! iconsSelect) { - return; - } - const allOptions = Array.from(document.querySelectorAll(disabledCardsSelectSelector + ' option')); - const iconVersions = { - 'visa': { - 'light': {'label': 'Visa (light)'}, - 'dark' : {'label': 'Visa (dark)', 'value': 'visa-dark'} - }, - 'mastercard': { - 'light': {'label': 'Mastercard (light)'}, - 'dark' : {'label': 'Mastercard (dark)', 'value': 'mastercard-dark'} - } - } - const replace = () => { - const validOptions = allOptions.filter(option => ! option.selected); - const selectedValidOptions = validOptions.map( - (option) => { - option = option.cloneNode(true); - let value = option.value; - option.selected = iconsSelect.querySelector('option[value="' + value + '"]') && iconsSelect.querySelector('option[value="' + value + '"]').selected; - if(value === 'visa' || value === 'mastercard') { - let darkOption = option.cloneNode(true); - let currentVersion = iconVersions[value]; - let darkValue = iconVersions[value].dark.value; + const removeDisabledCardIcons = ( + disabledCardsSelectSelector, + iconsSelectSelector + ) => { + const iconsSelect = document.querySelector( iconsSelectSelector ); + if ( ! iconsSelect ) { + return; + } + const allOptions = Array.from( + document.querySelectorAll( disabledCardsSelectSelector + ' option' ) + ); + const iconVersions = { + visa: { + light: { label: 'Visa (light)' }, + dark: { label: 'Visa (dark)', value: 'visa-dark' }, + }, + mastercard: { + light: { label: 'Mastercard (light)' }, + dark: { label: 'Mastercard (dark)', value: 'mastercard-dark' }, + }, + }; + const replace = () => { + const validOptions = allOptions.filter( + ( option ) => ! option.selected + ); + const selectedValidOptions = validOptions + .map( ( option ) => { + option = option.cloneNode( true ); + const value = option.value; + option.selected = + iconsSelect.querySelector( + 'option[value="' + value + '"]' + ) && + iconsSelect.querySelector( + 'option[value="' + value + '"]' + ).selected; + if ( value === 'visa' || value === 'mastercard' ) { + const darkOption = option.cloneNode( true ); + const currentVersion = iconVersions[ value ]; + const darkValue = iconVersions[ value ].dark.value; - option.text = currentVersion.light.label; - darkOption.text = currentVersion.dark.label; - darkOption.value = darkValue; - darkOption.selected = iconsSelect.querySelector('option[value="' + darkValue + '"]') && iconsSelect.querySelector('option[value="' + darkValue + '"]').selected; + option.text = currentVersion.light.label; + darkOption.text = currentVersion.dark.label; + darkOption.value = darkValue; + darkOption.selected = + iconsSelect.querySelector( + 'option[value="' + darkValue + '"]' + ) && + iconsSelect.querySelector( + 'option[value="' + darkValue + '"]' + ).selected; - return [option, darkOption]; - } - return option; - } - ).flat(); + return [ option, darkOption ]; + } + return option; + } ) + .flat(); - iconsSelect.innerHTML = ''; - selectedValidOptions.forEach( - (option) => { - if(Array.isArray(option)){ - option.forEach( - (option) => { - iconsSelect.appendChild(option); - } - ) - } + iconsSelect.innerHTML = ''; + selectedValidOptions.forEach( ( option ) => { + if ( Array.isArray( option ) ) { + option.forEach( ( option ) => { + iconsSelect.appendChild( option ); + } ); + } - iconsSelect.appendChild(option); - } - ); - }; + iconsSelect.appendChild( option ); + } ); + }; - const disabledCardsSelect = jQuery(disabledCardsSelectSelector); - disabledCardsSelect.on('change', replace); - replace(); - }; + const disabledCardsSelect = jQuery( disabledCardsSelectSelector ); + disabledCardsSelect.on( 'change', replace ); + replace(); + }; - const toggleInputsBySelectedLocations = ( - stylingPerSelector, - locationsSelector, - groupToShowOnChecked, - groupToHideOnChecked, - inputType - ) => { + const toggleInputsBySelectedLocations = ( + stylingPerSelector, + locationsSelector, + groupToShowOnChecked, + groupToHideOnChecked, + inputType + ) => { + const payLaterMessagingEnabled = document.querySelector( + payLaterMessagingEnabledSelector + ); + const stylingPerElement = document.querySelector( stylingPerSelector ); + const locationsElement = document.querySelector( locationsSelector ); + const stylingPerElementWrapper = stylingPerElement?.closest( 'tr' ); + const stylingPerElementWrapperSelector = + '#' + stylingPerElementWrapper?.getAttribute( 'id' ); - const payLaterMessagingEnabled = document.querySelector(payLaterMessagingEnabledSelector); - const stylingPerElement = document.querySelector(stylingPerSelector); - const locationsElement = document.querySelector(locationsSelector); - const stylingPerElementWrapper = stylingPerElement?.closest('tr'); - const stylingPerElementWrapperSelector = '#'+ stylingPerElementWrapper?.getAttribute('id'); + if ( ! stylingPerElement ) { + return; + } - if (! stylingPerElement) { - return; - } + const toggleElementsBySelectedLocations = () => { + stylingPerElementWrapper.style.display = ''; + const selectedLocations = getSelectedLocations( locationsSelector ); + const emptySmartButtonLocationMessage = jQuery( + '.ppcp-empty-smart-button-location' + ); - const toggleElementsBySelectedLocations = () => { - stylingPerElementWrapper.style.display = ''; - let selectedLocations = getSelectedLocations(locationsSelector); - let emptySmartButtonLocationMessage = jQuery('.ppcp-empty-smart-button-location'); + if ( selectedLocations.length === 0 ) { + hideElements( + groupToHideOnChecked.concat( + stylingPerElementWrapperSelector + ) + ); + if ( emptySmartButtonLocationMessage.length === 0 ) { + jQuery( + PayPalCommerceSettings.empty_smart_button_location_message + ).insertAfter( + jQuery( smartButtonLocationsSelector ).find( + '.description' + ) + ); + } + } - if(selectedLocations.length === 0) { - hideElements(groupToHideOnChecked.concat(stylingPerElementWrapperSelector)); - if (emptySmartButtonLocationMessage.length === 0) { - jQuery(PayPalCommerceSettings.empty_smart_button_location_message).insertAfter(jQuery(smartButtonLocationsSelector).find('.description')); - } - } + if ( ! stylingPerElement.checked ) { + return; + } - if (! stylingPerElement.checked) { - return; - } + if ( + inputType === 'messages' && + ! payLaterMessagingEnabled.checked + ) { + return; + } - if (inputType === 'messages' && ! payLaterMessagingEnabled.checked) { - return; - } + const inputSelectors = inputSelectorsByLocations( + selectedLocations, + inputType + ); - const inputSelectors = inputSelectorsByLocations(selectedLocations, inputType); + groupToShowOnChecked.forEach( ( element ) => { + if ( inputSelectors.includes( element ) ) { + document.querySelector( element ).style.display = ''; + return; + } + document.querySelector( element ).style.display = 'none'; + } ); - groupToShowOnChecked.forEach( (element) => { - if ( inputSelectors.includes(element) ) { - document.querySelector(element).style.display = ''; - return; - } - document.querySelector(element).style.display = 'none'; - }) + if ( inputType === 'messages' ) { + togglePayLaterMessageFields(); + } + }; - if (inputType === 'messages') { - togglePayLaterMessageFields(); - } - } + const hideElements = ( selectroGroup ) => { + selectroGroup.forEach( ( elementToHide ) => { + document.querySelector( elementToHide ).style.display = 'none'; + } ); + }; - const hideElements = (selectroGroup) => { - selectroGroup.forEach( (elementToHide) => { - document.querySelector(elementToHide).style.display = 'none'; - }) - } + const showElements = ( selectroGroup ) => { + selectroGroup.forEach( ( elementToShow ) => { + document.querySelector( elementToShow ).style.display = ''; + } ); + }; - const showElements = (selectroGroup) => { - selectroGroup.forEach( (elementToShow) => { - document.querySelector(elementToShow).style.display = ''; - }) - } + groupToggle( stylingPerSelector, groupToShowOnChecked ); + toggleElementsBySelectedLocations(); - groupToggle(stylingPerSelector, groupToShowOnChecked); - toggleElementsBySelectedLocations(); + if ( stylingPerElement.checked ) { + hideElements( groupToHideOnChecked ); + } - if (stylingPerElement.checked) { - hideElements(groupToHideOnChecked); - } + stylingPerElement.addEventListener( 'change', ( event ) => { + toggleElementsBySelectedLocations(); - stylingPerElement.addEventListener( - 'change', - (event) => { - toggleElementsBySelectedLocations(); + if ( event.target.checked ) { + hideElements( groupToHideOnChecked ); + return; + } - if (event.target.checked) { - hideElements(groupToHideOnChecked); - return; - } + const selectedLocations = getSelectedLocations( locationsSelector ); + if ( selectedLocations.length > 0 ) { + showElements( groupToHideOnChecked ); + } - let selectedLocations = getSelectedLocations(locationsSelector); - if(selectedLocations.length > 0) { - showElements(groupToHideOnChecked); - } + if ( inputType === 'messages' ) { + togglePayLaterMessageFields(); + } + } ); - if (inputType === 'messages') { - togglePayLaterMessageFields(); - } - } - ); + // We need to use jQuery here as the select might be a select2 element, which doesn't use native events. + jQuery( locationsElement ).on( 'change', function () { + const emptySmartButtonLocationMessage = jQuery( + '.ppcp-empty-smart-button-location' + ); + emptySmartButtonLocationMessage?.remove(); + toggleElementsBySelectedLocations(); + stylingPerElement.dispatchEvent( new Event( 'change' ) ); + } ); + }; - // We need to use jQuery here as the select might be a select2 element, which doesn't use native events. - jQuery(locationsElement).on('change', function (){ - let emptySmartButtonLocationMessage = jQuery('.ppcp-empty-smart-button-location'); - emptySmartButtonLocationMessage?.remove(); - toggleElementsBySelectedLocations() - stylingPerElement.dispatchEvent(new Event('change')) - }); - } + const getSelectedLocations = ( selector ) => { + const checkedLocations = document.querySelectorAll( + selector + ' :checked' + ); + return [ ...checkedLocations ].map( ( option ) => option.value ); + }; - const getSelectedLocations = (selector) => { - let checkedLocations = document.querySelectorAll(selector + ' :checked'); - return [...checkedLocations].map(option => option.value); - } + const inputSelectorsByLocations = ( locations, inputType = 'messages' ) => { + let inputSelectros = []; - const inputSelectorsByLocations = (locations, inputType = 'messages') => { - let inputSelectros = []; + locations.forEach( ( location ) => { + inputSelectros = + inputType === 'messages' + ? inputSelectros.concat( + payLaterMessagingInputSelectorByLocation( location ) + ) + : inputSelectros.concat( + butttonInputSelectorByLocation( location ) + ); + } ); - locations.forEach( (location) => { - inputSelectros = inputType === 'messages' - ? inputSelectros.concat(payLaterMessagingInputSelectorByLocation(location)) - : inputSelectros.concat(butttonInputSelectorByLocation(location)); - }) + return inputSelectros; + }; - return inputSelectros - } + const payLaterMessagingInputSelectorByLocation = ( location ) => { + const inputSelectors = [ + '#field-pay_later_' + location + '_message_layout', + '#field-pay_later_' + location + '_message_logo', + '#field-pay_later_' + location + '_message_position', + '#field-pay_later_' + location + '_message_color', + '#field-pay_later_' + location + '_message_flex_color', + '#field-pay_later_' + location + '_message_flex_ratio', + '#field-pay_later_' + location + '_message_preview', + ]; - const payLaterMessagingInputSelectorByLocation = (location) => { - const inputSelectors = [ - '#field-pay_later_' + location + '_message_layout', - '#field-pay_later_' + location + '_message_logo', - '#field-pay_later_' + location + '_message_position', - '#field-pay_later_' + location + '_message_color', - '#field-pay_later_' + location + '_message_flex_color', - '#field-pay_later_' + location + '_message_flex_ratio', - '#field-pay_later_' + location + '_message_preview', - ] + if ( location !== 'general' ) { + inputSelectors.push( + '#field-pay_later_' + location + '_messaging_heading' + ); + } - if (location !== 'general') { - inputSelectors.push('#field-pay_later_' + location + '_messaging_heading'); - } + return inputSelectors; + }; - return inputSelectors - } + const butttonInputSelectorByLocation = ( location ) => { + const locationPrefix = location === 'checkout' ? '' : '_' + location; + const inputSelectors = [ + '#field-button' + locationPrefix + '_layout', + '#field-button' + locationPrefix + '_tagline', + '#field-button' + locationPrefix + '_label', + '#field-button' + locationPrefix + '_color', + '#field-button' + locationPrefix + '_shape', + '#field-button' + locationPrefix + '_height', + '#field-button' + locationPrefix + '_preview', + ]; - const butttonInputSelectorByLocation = (location) => { - const locationPrefix = location === 'checkout' ? '' : '_' + location; - const inputSelectors = [ - '#field-button' + locationPrefix + '_layout', - '#field-button' + locationPrefix + '_tagline', - '#field-button' + locationPrefix + '_label', - '#field-button' + locationPrefix + '_color', - '#field-button' + locationPrefix + '_shape', - '#field-button' + locationPrefix + '_height', - '#field-button' + locationPrefix + '_preview', - ] + if ( location !== 'general' ) { + inputSelectors.push( '#field-button_' + location + '_heading' ); + } - if (location !== 'general') { - inputSelectors.push('#field-button_' + location + '_heading'); - } + return inputSelectors.filter( ( selector ) => + document.querySelector( selector ) + ); + }; - return inputSelectors.filter(selector => document.querySelector(selector)); - } + const allPayLaterMessaginginputSelectors = () => { + const stylingInputSelectors = inputSelectorsByLocations( + payLaterMessagingAllLocations + ); - const allPayLaterMessaginginputSelectors = () => { - let stylingInputSelectors = inputSelectorsByLocations(payLaterMessagingAllLocations); + return stylingInputSelectors.concat( + payLaterMessagingLocationsSelector, + '#field-pay_later_enable_styling_per_messaging_location' + ); + }; - return stylingInputSelectors.concat(payLaterMessagingLocationsSelector, '#field-pay_later_enable_styling_per_messaging_location'); - } + const toggleMessagingEnabled = () => { + const payLaterMessagingEnabled = document.querySelector( + payLaterMessagingEnabledSelector + ); + const stylingPerMessagingElement = document.querySelector( + '#ppcp-pay_later_enable_styling_per_messaging_location' + ); - const toggleMessagingEnabled = () => { - const payLaterMessagingEnabled = document.querySelector(payLaterMessagingEnabledSelector); - const stylingPerMessagingElement = document.querySelector('#ppcp-pay_later_enable_styling_per_messaging_location'); + groupToggle( + payLaterMessagingEnabledSelector, + allPayLaterMessaginginputSelectors() + ); - groupToggle( - payLaterMessagingEnabledSelector, - allPayLaterMessaginginputSelectors() - ); + if ( ! payLaterMessagingEnabled ) { + return; + } - if (! payLaterMessagingEnabled) { - return; + payLaterMessagingEnabled.addEventListener( 'change', ( event ) => { + if ( ! event.target.checked ) { + return; + } + stylingPerMessagingElement.dispatchEvent( new Event( 'change' ) ); + } ); + }; - } + const referenceTransactionsCheck = () => { + if ( + typeof PayPalCommerceGatewaySettings !== 'undefined' && + PayPalCommerceGatewaySettings.reference_transaction_enabled !== '1' + ) { + document + .getElementById( 'ppcp-vault_enabled' ) + ?.setAttribute( 'disabled', 'disabled' ); - payLaterMessagingEnabled.addEventListener( - 'change', - (event) => { - if (! event.target.checked) { - return; - } - stylingPerMessagingElement.dispatchEvent(new Event('change')) - } - ); - } + const description = document + .getElementById( 'field-vault_enabled' ) + ?.getElementsByClassName( 'description' )[ 0 ]; + if ( description ) { + description.innerHTML = + PayPalCommerceGatewaySettings.vaulting_must_enable_advanced_wallet_message; + } + } + }; - const referenceTransactionsCheck = () => { - if ( - typeof PayPalCommerceGatewaySettings !== 'undefined' - && PayPalCommerceGatewaySettings.reference_transaction_enabled !== '1' - ) { - document.getElementById('ppcp-vault_enabled')?.setAttribute('disabled', 'disabled'); + ( () => { + removeDisabledCardIcons( + 'select[name="ppcp[disable_cards][]"]', + 'select[name="ppcp[card_icons][]"]' + ); - const description = document.getElementById('field-vault_enabled')?.getElementsByClassName('description')[0]; - if (description) { - description.innerHTML = PayPalCommerceGatewaySettings.vaulting_must_enable_advanced_wallet_message; - } - } - } + groupToggle( '#ppcp-pay_later_button_enabled', [ + '#field-pay_later_button_locations', + ] ); - (() => { - removeDisabledCardIcons('select[name="ppcp[disable_cards][]"]', 'select[name="ppcp[card_icons][]"]'); + toggleInputsBySelectedLocations( + '#ppcp-pay_later_enable_styling_per_messaging_location', + payLaterMessagingLocationsSelect, + inputSelectorsByLocations( payLaterMessagingSelectableLocations ), + inputSelectorsByLocations( [ 'general' ] ), + 'messages' + ); - groupToggle( - '#ppcp-pay_later_button_enabled', - ['#field-pay_later_button_locations'] - ); + toggleInputsBySelectedLocations( + '#ppcp-smart_button_enable_styling_per_location', + smartButtonLocationsSelect, + inputSelectorsByLocations( + smartButtonSelectableLocations, + 'buttons' + ), + inputSelectorsByLocations( [ 'general' ], 'buttons' ), + 'buttons' + ); - toggleInputsBySelectedLocations( - '#ppcp-pay_later_enable_styling_per_messaging_location', - payLaterMessagingLocationsSelect, - inputSelectorsByLocations(payLaterMessagingSelectableLocations), - inputSelectorsByLocations(['general']), - 'messages' - ); + toggleMessagingEnabled(); - toggleInputsBySelectedLocations( - '#ppcp-smart_button_enable_styling_per_location', - smartButtonLocationsSelect, - inputSelectorsByLocations(smartButtonSelectableLocations, 'buttons'), - inputSelectorsByLocations(['general'], 'buttons'), - 'buttons' - ); + groupToggle( '#ppcp-vault_enabled', [ + '#field-subscription_behavior_when_vault_fails', + ] ); - toggleMessagingEnabled(); + groupToggleSelect( '#ppcp-intent', [ + { + value: 'authorize', + selector: '#field-capture_for_virtual_only', + }, + { + value: 'authorize', + selector: '#field-capture_on_status_change', + }, + ] ); + togglePayLaterMessageFields(); - groupToggle( - '#ppcp-vault_enabled', - [ - '#field-subscription_behavior_when_vault_fails', - ] - ); - - groupToggleSelect( - '#ppcp-intent', - [ - { - value:'authorize', - selector:'#field-capture_for_virtual_only' - }, - { - value:'authorize', - selector:'#field-capture_on_status_change' - } - ] - ); - - togglePayLaterMessageFields(); - - referenceTransactionsCheck() - })(); - } -) + referenceTransactionsCheck(); + } )(); +} ); diff --git a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php index 4b0c9c165..c5385216d 100644 --- a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php +++ b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php @@ -96,21 +96,21 @@ class OnboardingAssets { */ public function register(): bool { - $url = untrailingslashit( $this->module_url ) . '/assets/css/onboarding.css'; wp_register_style( 'ppcp-onboarding', - $url, + $this->module_url . '/assets/css/onboarding.css', array(), $this->version ); - $url = untrailingslashit( $this->module_url ) . '/assets/js/settings.js'; + wp_register_script( 'ppcp-settings', - $url, + $this->module_url . '/assets/js/settings.js', array(), $this->version, true ); + wp_localize_script( 'ppcp-settings', 'PayPalCommerceSettings', @@ -122,14 +122,14 @@ class OnboardingAssets { ) ); - $url = untrailingslashit( $this->module_url ) . '/assets/js/onboarding.js'; wp_register_script( 'ppcp-onboarding', - $url, + $this->module_url . '/assets/js/onboarding.js', array( 'jquery' ), $this->version, true ); + wp_localize_script( 'ppcp-onboarding', 'PayPalCommerceGatewayOnboarding', @@ -164,17 +164,22 @@ class OnboardingAssets { /** * Enqueues the necessary scripts. * - * @return bool + * @return void */ - public function enqueue(): bool { - wp_enqueue_style( 'ppcp-onboarding' ); - wp_enqueue_script( 'ppcp-settings' ); - if ( ! $this->should_render_onboarding_script() ) { - return false; + public function enqueue(): void { + // Do not enqueue anything when we are not on a PayPal Payments settings tab. + if ( ! $this->page_id ) { + return; } - wp_enqueue_script( 'ppcp-onboarding' ); - return true; + // Enqueue general assets for the plugin's settings page. + wp_enqueue_script( 'ppcp-settings' ); + wp_enqueue_style( 'ppcp-onboarding' ); // File also contains general settings styles. + + // Conditionally enqueue the onboarding script, when needed. + if ( $this->should_render_onboarding_script() ) { + wp_enqueue_script( 'ppcp-onboarding' ); + } } /** diff --git a/modules/ppcp-order-tracking/resources/js/order-edit-page.js b/modules/ppcp-order-tracking/resources/js/order-edit-page.js index bf1056102..fa5979d21 100644 --- a/modules/ppcp-order-tracking/resources/js/order-edit-page.js +++ b/modules/ppcp-order-tracking/resources/js/order-edit-page.js @@ -1,172 +1,251 @@ -document.addEventListener( - 'DOMContentLoaded', - () => { - const config = PayPalCommerceGatewayOrderTrackingInfo; - if (!typeof (PayPalCommerceGatewayOrderTrackingInfo)) { - console.error('tracking cannot be set.'); - return; - } +document.addEventListener( 'DOMContentLoaded', () => { + const config = PayPalCommerceGatewayOrderTrackingInfo; + if ( ! typeof PayPalCommerceGatewayOrderTrackingInfo ) { + console.error( 'tracking cannot be set.' ); + return; + } - const includeAllItemsCheckbox = document.getElementById('include-all-items'); - const shipmentsWrapper = '#ppcp_order-tracking .ppcp-tracking-column.shipments'; - const captureId = document.querySelector('.ppcp-tracking-capture_id'); - const orderId = document.querySelector('.ppcp-tracking-order_id'); - const carrier = document.querySelector('.ppcp-tracking-carrier'); - const carrierNameOther = document.querySelector('.ppcp-tracking-carrier_name_other'); + const includeAllItemsCheckbox = + document.getElementById( 'include-all-items' ); + const shipmentsWrapper = + '#ppcp_order-tracking .ppcp-tracking-column.shipments'; + const captureId = document.querySelector( '.ppcp-tracking-capture_id' ); + const orderId = document.querySelector( '.ppcp-tracking-order_id' ); + const carrier = document.querySelector( '.ppcp-tracking-carrier' ); + const carrierNameOther = document.querySelector( + '.ppcp-tracking-carrier_name_other' + ); - function toggleLineItemsSelectbox() { - const selectContainer = document.getElementById('items-select-container'); - includeAllItemsCheckbox?.addEventListener('change', function(){ - selectContainer.style.display = includeAllItemsCheckbox.checked ? 'none' : 'block'; - }) - } + function toggleLineItemsSelectbox() { + const selectContainer = document.getElementById( + 'items-select-container' + ); + includeAllItemsCheckbox?.addEventListener( 'change', function () { + selectContainer.style.display = includeAllItemsCheckbox.checked + ? 'none' + : 'block'; + } ); + } - function toggleShipment() { - jQuery(document).on('click', '.ppcp-shipment-header', function(event) { - const shipmentContainer = event.target.closest('.ppcp-shipment'); - const shipmentInfo = shipmentContainer.querySelector('.ppcp-shipment-info'); + function toggleShipment() { + jQuery( document ).on( + 'click', + '.ppcp-shipment-header', + function ( event ) { + const shipmentContainer = + event.target.closest( '.ppcp-shipment' ); + const shipmentInfo = shipmentContainer.querySelector( + '.ppcp-shipment-info' + ); - shipmentContainer.classList.toggle('active'); - shipmentContainer.classList.toggle('closed'); - shipmentInfo.classList.toggle('hidden'); - }); - } + shipmentContainer.classList.toggle( 'active' ); + shipmentContainer.classList.toggle( 'closed' ); + shipmentInfo.classList.toggle( 'hidden' ); + } + ); + } - function toggleShipmentUpdateButtonDisabled() { - jQuery(document).on('change', '.ppcp-shipment-status', function(event) { - const shipmentSelectbox = event.target; - const shipment = shipmentSelectbox.closest('.ppcp-shipment'); - const updateShipmentButton = shipment.querySelector('.update_shipment'); - const selectedValue = shipmentSelectbox.value; + function toggleShipmentUpdateButtonDisabled() { + jQuery( document ).on( + 'change', + '.ppcp-shipment-status', + function ( event ) { + const shipmentSelectbox = event.target; + const shipment = shipmentSelectbox.closest( '.ppcp-shipment' ); + const updateShipmentButton = + shipment.querySelector( '.update_shipment' ); + const selectedValue = shipmentSelectbox.value; - updateShipmentButton.classList.remove('button-disabled'); - }); - } + updateShipmentButton.classList.remove( 'button-disabled' ); + } + ); + } - function toggleLoaderVisibility() { - const loader = document.querySelector('.ppcp-tracking-loader'); - if (loader) { - if (loader.style.display === 'none' || loader.style.display === '') { - loader.style.display = 'block'; - } else { - loader.style.display = 'none'; - } - } - } + function toggleLoaderVisibility() { + const loader = document.querySelector( '.ppcp-tracking-loader' ); + if ( loader ) { + if ( + loader.style.display === 'none' || + loader.style.display === '' + ) { + loader.style.display = 'block'; + } else { + loader.style.display = 'none'; + } + } + } - function toggleOtherCarrierName() { - jQuery(carrier).on('change', function() { - const hiddenHtml = carrierNameOther.parentNode; - if (carrier.value === 'OTHER') { - hiddenHtml.classList.remove('hidden'); - } else { - if (!hiddenHtml.classList.contains('hidden')) { - hiddenHtml.classList.add('hidden'); - } - } - }) - } + function toggleOtherCarrierName() { + jQuery( carrier ).on( 'change', function () { + const hiddenHtml = carrierNameOther.parentNode; + if ( carrier.value === 'OTHER' ) { + hiddenHtml.classList.remove( 'hidden' ); + } else if ( ! hiddenHtml.classList.contains( 'hidden' ) ) { + hiddenHtml.classList.add( 'hidden' ); + } + } ); + } - function handleAddShipment() { - jQuery(document).on('click', '.submit_tracking_info', function () { - const trackingNumber = document.querySelector('.ppcp-tracking-tracking_number'); - const status = document.querySelector('.ppcp-tracking-status'); - const submitButton = document.querySelector('.submit_tracking_info'); - const items = document.querySelector('.ppcp-tracking-items'); - const noShipemntsContainer = document.querySelector('.ppcp-tracking-no-shipments'); + function handleAddShipment() { + jQuery( document ).on( 'click', '.submit_tracking_info', function () { + const trackingNumber = document.querySelector( + '.ppcp-tracking-tracking_number' + ); + const status = document.querySelector( '.ppcp-tracking-status' ); + const submitButton = document.querySelector( + '.submit_tracking_info' + ); + const items = document.querySelector( '.ppcp-tracking-items' ); + const noShipemntsContainer = document.querySelector( + '.ppcp-tracking-no-shipments' + ); - let checkedItems = includeAllItemsCheckbox?.checked || !items ? 0 : Array.from(items.selectedOptions).map(option => option.value) + const checkedItems = + includeAllItemsCheckbox?.checked || ! items + ? 0 + : Array.from( items.selectedOptions ).map( + ( option ) => option.value + ); - toggleLoaderVisibility() - fetch(config.ajax.tracking_info.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.ajax.tracking_info.nonce, - capture_id: captureId ? captureId.value : null, - tracking_number: trackingNumber ? trackingNumber.value : null, - status: status ? status.value : null, - carrier: carrier ? carrier.value : null, - carrier_name_other: carrierNameOther ? carrierNameOther.value : null, - order_id: orderId ? orderId.value : null, - items: checkedItems - }) - }).then(function (res) { - return res.json(); - }).then(function (data) { - toggleLoaderVisibility() + toggleLoaderVisibility(); + fetch( config.ajax.tracking_info.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.ajax.tracking_info.nonce, + capture_id: captureId ? captureId.value : null, + tracking_number: trackingNumber + ? trackingNumber.value + : null, + status: status ? status.value : null, + carrier: carrier ? carrier.value : null, + carrier_name_other: carrierNameOther + ? carrierNameOther.value + : null, + order_id: orderId ? orderId.value : null, + items: checkedItems, + } ), + } ) + .then( function ( res ) { + return res.json(); + } ) + .then( function ( data ) { + toggleLoaderVisibility(); - if (!data.success || ! data.data.shipment) { - jQuery( "

" + data.data.message + "

" ).insertAfter(submitButton); - setTimeout(()=> jQuery('.tracking-info-message').remove(),3000); - submitButton.removeAttribute('disabled'); - console.error(data); - throw Error(data.data.message); - } + if ( ! data.success || ! data.data.shipment ) { + jQuery( + "

" + + data.data.message + + '

' + ).insertAfter( submitButton ); + setTimeout( + () => jQuery( '.tracking-info-message' ).remove(), + 3000 + ); + submitButton.removeAttribute( 'disabled' ); + console.error( data ); + throw Error( data.data.message ); + } - jQuery( "

" + data.data.message + "

" ).insertAfter(submitButton); - setTimeout(()=> jQuery('.tracking-info-message').remove(),3000); - jQuery(data.data.shipment).appendTo(shipmentsWrapper); - if (noShipemntsContainer) { - noShipemntsContainer.parentNode.removeChild(noShipemntsContainer); - } - trackingNumber.value = '' - }); - }) - } + jQuery( + "

" + + data.data.message + + '

' + ).insertAfter( submitButton ); + setTimeout( + () => jQuery( '.tracking-info-message' ).remove(), + 3000 + ); + jQuery( data.data.shipment ).appendTo( shipmentsWrapper ); + if ( noShipemntsContainer ) { + noShipemntsContainer.parentNode.removeChild( + noShipemntsContainer + ); + } + trackingNumber.value = ''; + } ); + } ); + } - function handleUpdateShipment() { - jQuery(document).on('click', '.update_shipment', function (event) { - const updateShipment = event.target; - const parentElement = updateShipment.parentNode.parentNode; - const shipmentStatus = parentElement.querySelector('.ppcp-shipment-status'); - const shipmentTrackingNumber = parentElement.querySelector('.ppcp-shipment-tacking_number'); - const shipmentCarrier = parentElement.querySelector('.ppcp-shipment-carrier'); - const shipmentCarrierNameOther = parentElement.querySelector('.ppcp-shipment-carrier-other'); + function handleUpdateShipment() { + jQuery( document ).on( 'click', '.update_shipment', function ( event ) { + const updateShipment = event.target; + const parentElement = updateShipment.parentNode.parentNode; + const shipmentStatus = parentElement.querySelector( + '.ppcp-shipment-status' + ); + const shipmentTrackingNumber = parentElement.querySelector( + '.ppcp-shipment-tacking_number' + ); + const shipmentCarrier = parentElement.querySelector( + '.ppcp-shipment-carrier' + ); + const shipmentCarrierNameOther = parentElement.querySelector( + '.ppcp-shipment-carrier-other' + ); - toggleLoaderVisibility() - fetch(config.ajax.tracking_info.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: config.ajax.tracking_info.nonce, - capture_id: captureId ? captureId.value : null, - tracking_number: shipmentTrackingNumber ? shipmentTrackingNumber.value : null, - status: shipmentStatus ? shipmentStatus.value : null, - carrier: shipmentCarrier ? shipmentCarrier.value : null, - carrier_name_other: shipmentCarrierNameOther ? shipmentCarrierNameOther.value : null, - order_id: orderId ? orderId.value : null, - action: 'update' - }) - }).then(function (res) { - return res.json(); - }).then(function (data) { - toggleLoaderVisibility() + toggleLoaderVisibility(); + fetch( config.ajax.tracking_info.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: config.ajax.tracking_info.nonce, + capture_id: captureId ? captureId.value : null, + tracking_number: shipmentTrackingNumber + ? shipmentTrackingNumber.value + : null, + status: shipmentStatus ? shipmentStatus.value : null, + carrier: shipmentCarrier ? shipmentCarrier.value : null, + carrier_name_other: shipmentCarrierNameOther + ? shipmentCarrierNameOther.value + : null, + order_id: orderId ? orderId.value : null, + action: 'update', + } ), + } ) + .then( function ( res ) { + return res.json(); + } ) + .then( function ( data ) { + toggleLoaderVisibility(); - if (!data.success) { - jQuery( "

" + data.data.message + "

" ).insertAfter(updateShipment); - setTimeout(()=> jQuery('.tracking-info-message').remove(),3000); - console.error(data); - throw Error(data.data.message); - } + if ( ! data.success ) { + jQuery( + "

" + + data.data.message + + '

' + ).insertAfter( updateShipment ); + setTimeout( + () => jQuery( '.tracking-info-message' ).remove(), + 3000 + ); + console.error( data ); + throw Error( data.data.message ); + } - jQuery( "

" + data.data.message + "

" ).insertAfter(updateShipment); - setTimeout(()=> jQuery('.tracking-info-message').remove(),3000); - }); - }) - } + jQuery( + "

" + + data.data.message + + '

' + ).insertAfter( updateShipment ); + setTimeout( + () => jQuery( '.tracking-info-message' ).remove(), + 3000 + ); + } ); + } ); + } - handleAddShipment(); - handleUpdateShipment(); - toggleLineItemsSelectbox(); - toggleShipment(); - toggleShipmentUpdateButtonDisabled(); - toggleOtherCarrierName(); - }, -); + handleAddShipment(); + handleUpdateShipment(); + toggleLineItemsSelectbox(); + toggleShipment(); + toggleShipmentUpdateButtonDisabled(); + toggleOtherCarrierName(); +} ); diff --git a/modules/ppcp-paylater-block/resources/js/edit.js b/modules/ppcp-paylater-block/resources/js/edit.js index 28dd853a8..f3e9c0747 100644 --- a/modules/ppcp-paylater-block/resources/js/edit.js +++ b/modules/ppcp-paylater-block/resources/js/edit.js @@ -5,230 +5,502 @@ import { PanelBody, SelectControl, Spinner } from '@wordpress/components'; import { PayPalScriptProvider, PayPalMessages } from '@paypal/react-paypal-js'; import { useScriptParams } from './hooks/script-params'; -export default function Edit({ attributes, clientId, setAttributes }) { - const { layout, logo, position, color, size, flexColor, flexRatio, placement, id } = attributes; - const isFlex = layout === 'flex'; +export default function Edit( { attributes, clientId, setAttributes } ) { + const { + layout, + logo, + position, + color, + size, + flexColor, + flexRatio, + placement, + id, + } = attributes; + const isFlex = layout === 'flex'; - const [loaded, setLoaded] = useState(false); + const [ loaded, setLoaded ] = useState( false ); - let amount; - const postContent = String(wp.data.select('core/editor')?.getEditedPostContent()); - if (postContent.includes('woocommerce/checkout') || postContent.includes('woocommerce/cart')) { - amount = 50.0; - } + let amount; + const postContent = String( + wp.data.select( 'core/editor' )?.getEditedPostContent() + ); + if ( + postContent.includes( 'woocommerce/checkout' ) || + postContent.includes( 'woocommerce/cart' ) + ) { + amount = 50.0; + } - const previewStyle = { - layout, - logo: { - position, - type: logo, - }, - color: flexColor, - ratio: flexRatio, - text: { - color, - size - }, - }; + const previewStyle = { + layout, + logo: { + position, + type: logo, + }, + color: flexColor, + ratio: flexRatio, + text: { + color, + size, + }, + }; - let classes = ['ppcp-paylater-block-preview', 'ppcp-overlay-parent']; - if (PcpPayLaterBlock.vaultingEnabled || !PcpPayLaterBlock.placementEnabled) { - classes.push('ppcp-paylater-unavailable', 'block-editor-warning'); - } - const props = useBlockProps({ className: classes.join(' ') }); + const classes = [ 'ppcp-paylater-block-preview', 'ppcp-overlay-parent' ]; + if ( + PcpPayLaterBlock.vaultingEnabled || + ! PcpPayLaterBlock.placementEnabled + ) { + classes.push( 'ppcp-paylater-unavailable', 'block-editor-warning' ); + } + const props = useBlockProps( { className: classes.join( ' ' ) } ); - useEffect(() => { - if (!id) { - setAttributes({ id: `ppcp-${clientId}` }); - } - }, [id, clientId]); + useEffect( () => { + if ( ! id ) { + setAttributes( { id: `ppcp-${ clientId }` } ); + } + }, [ id, clientId ] ); - if (PcpPayLaterBlock.vaultingEnabled) { - return ( -
-
-

- {__('Pay Later Messaging cannot be used while PayPal Vaulting is active. Disable PayPal Vaulting in the PayPal Payment settings to reactivate this block', 'woocommerce-paypal-payments')} -

-
- - - - - - - - -
-
-
- ); - } + if ( PcpPayLaterBlock.vaultingEnabled ) { + return ( +
+
+

+ { __( + 'Pay Later Messaging cannot be used while PayPal Vaulting is active. Disable PayPal Vaulting in the PayPal Payment settings to reactivate this block', + 'woocommerce-paypal-payments' + ) } +

+
+ + + + + + + + +
+
+
+ ); + } - if (!PcpPayLaterBlock.placementEnabled) { - return ( -
-
-

- {__('Pay Later Messaging cannot be used while the “WooCommerce Block” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', 'woocommerce-paypal-payments')} -

-
- - - - - - - - -
-
-
- ); - } + if ( ! PcpPayLaterBlock.placementEnabled ) { + return ( +
+
+

+ { __( + 'Pay Later Messaging cannot be used while the “WooCommerce Block” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', + 'woocommerce-paypal-payments' + ) } +

+
+ + + + + + + + +
+
+
+ ); + } - const scriptParams = useScriptParams(PcpPayLaterBlock.ajax.cart_script_params); + const scriptParams = useScriptParams( + PcpPayLaterBlock.ajax.cart_script_params + ); - if (scriptParams === null) { - return
; - } + if ( scriptParams === null ) { + return ( +
+ +
+ ); + } - const urlParams = { - ...scriptParams.url_params, - components: 'messages', - dataNamespace: 'ppcp-block-editor-paylater-message', - }; + const urlParams = { + ...scriptParams.url_params, + components: 'messages', + dataNamespace: 'ppcp-block-editor-paylater-message', + }; - return ( - <> - - - setAttributes({ layout: value })} - /> - {!isFlex && ( - setAttributes({ logo: value })} - /> - )} - {!isFlex && logo === 'primary' && ( - setAttributes({ position: value })} - /> - )} - {!isFlex && ( - setAttributes({ color: value })} - /> - )} - {!isFlex && ( - setAttributes({ size: value })} - /> - )} - {isFlex && ( - setAttributes({ flexColor: value })} - /> - )} - {isFlex && ( - setAttributes({ flexRatio: value })} - /> - )} - setAttributes( { placement: value } ) } - /> - - -
-
- - setLoaded(true)} - amount={amount} - /> - -
-
{/* make the message not clickable */} - {!loaded && } -
-
- - ); + return ( + <> + + + + setAttributes( { layout: value } ) + } + /> + { ! isFlex && ( + + setAttributes( { logo: value } ) + } + /> + ) } + { ! isFlex && logo === 'primary' && ( + + setAttributes( { position: value } ) + } + /> + ) } + { ! isFlex && ( + + setAttributes( { color: value } ) + } + /> + ) } + { ! isFlex && ( + + setAttributes( { size: value } ) + } + /> + ) } + { isFlex && ( + + setAttributes( { flexColor: value } ) + } + /> + ) } + { isFlex && ( + + setAttributes( { flexRatio: value } ) + } + /> + ) } + + setAttributes( { placement: value } ) + } + /> + + +
+
+ + setLoaded( true ) } + amount={ amount } + /> + +
+
+ { ' ' } + { /* make the message not clickable */ } + { ! loaded && } +
+
+ + ); } diff --git a/modules/ppcp-paylater-block/resources/js/hooks/script-params.js b/modules/ppcp-paylater-block/resources/js/hooks/script-params.js index c5b9de66f..175600736 100644 --- a/modules/ppcp-paylater-block/resources/js/hooks/script-params.js +++ b/modules/ppcp-paylater-block/resources/js/hooks/script-params.js @@ -1,24 +1,24 @@ import { useState, useEffect } from '@wordpress/element'; -export const useScriptParams = (requestConfig) => { - const [data, setData] = useState(null); +export const useScriptParams = ( requestConfig ) => { + const [ data, setData ] = useState( null ); - useEffect(() => { - (async () => { - try { - const response = await fetch(requestConfig.endpoint); - const json = await response.json(); - if (json.success && json?.data?.url_params) { - setData(json.data); - } else { - setData(false); - } - } catch (e) { - console.error(e); - setData(false); - } - })(); - }, [requestConfig]); + useEffect( () => { + ( async () => { + try { + const response = await fetch( requestConfig.endpoint ); + const json = await response.json(); + if ( json.success && json?.data?.url_params ) { + setData( json.data ); + } else { + setData( false ); + } + } catch ( e ) { + console.error( e ); + setData( false ); + } + } )(); + }, [ requestConfig ] ); - return data; + return data; }; diff --git a/modules/ppcp-paylater-block/resources/js/paylater-block.js b/modules/ppcp-paylater-block/resources/js/paylater-block.js index 1132f66db..b11650f37 100644 --- a/modules/ppcp-paylater-block/resources/js/paylater-block.js +++ b/modules/ppcp-paylater-block/resources/js/paylater-block.js @@ -3,35 +3,45 @@ import { registerBlockType } from '@wordpress/blocks'; import Edit from './edit'; const paypalIcon = ( - - - - - - - -) + + + + + + + +); const blockId = 'woocommerce-paypal-payments/paylater-messages'; registerBlockType( blockId, { - icon: paypalIcon, - edit: Edit, - save() { - return null; - }, + icon: paypalIcon, + edit: Edit, + save() { + return null; + }, } ); document.addEventListener( 'DOMContentLoaded', () => { - const { registerCheckoutFilters } = window.wc.blocksCheckout; + const { registerCheckoutFilters } = window.wc.blocksCheckout; - // allow to add this block inside WC cart/checkout blocks - registerCheckoutFilters( blockId, { - additionalCartCheckoutInnerBlockTypes: ( - defaultValue - ) => { - defaultValue.push( blockId ); - return defaultValue; - }, - } ); + // allow to add this block inside WC cart/checkout blocks + registerCheckoutFilters( blockId, { + additionalCartCheckoutInnerBlockTypes: ( defaultValue ) => { + defaultValue.push( blockId ); + return defaultValue; + }, + } ); } ); diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php b/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php index 75fa9cf25..74e8586b3 100644 --- a/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php +++ b/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php @@ -28,7 +28,9 @@ class PayLaterBlockRenderer { public function render( array $attributes, ContainerInterface $c ): string { if ( PayLaterBlockModule::is_block_enabled( $c->get( 'wcgateway.settings.status' ) ) ) { - $html = '
'; + $bn_code = PPCP_PAYPAL_BN_CODE; + + $html = '
'; $processor = new \WP_HTML_Tag_Processor( $html ); diff --git a/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js b/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js index 5aa4df772..f3daad01e 100644 --- a/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js +++ b/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js @@ -1,85 +1,99 @@ -document.addEventListener('DOMContentLoaded', () => { - const form = document.querySelector('#mainform'); - const table = form.querySelector('.form-table'); - const headingRow = table.querySelector('#field-pay_later_messaging_heading'); - const saveChangesButton = form.querySelector('.woocommerce-save-button'); - const publishButtonClassName = PcpPayLaterConfigurator.publishButtonClassName; +document.addEventListener( 'DOMContentLoaded', () => { + const form = document.querySelector( '#mainform' ); + const table = form.querySelector( '.form-table' ); + const headingRow = table.querySelector( + '#field-pay_later_messaging_heading' + ); + const saveChangesButton = form.querySelector( '.woocommerce-save-button' ); + const publishButtonClassName = + PcpPayLaterConfigurator.publishButtonClassName; - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = `
`; + const tempContainer = document.createElement( 'div' ); + tempContainer.innerHTML = `
`; - // Get the new row element from the container - const newRow = tempContainer.firstChild; + // Get the new row element from the container + const newRow = tempContainer.firstChild; - // Insert the new row after the headingRow - headingRow.parentNode.insertBefore(newRow, headingRow.nextSibling); + // Insert the new row after the headingRow + headingRow.parentNode.insertBefore( newRow, headingRow.nextSibling ); - let isSaving = false; // Flag variable to track whether saving is in progress + let isSaving = false; // Flag variable to track whether saving is in progress - saveChangesButton.addEventListener('click', () => { - // Check if saving is not already in progress - if (!isSaving) { - isSaving = true; // Set flag to indicate saving is in progress + saveChangesButton.addEventListener( 'click', () => { + // Check if saving is not already in progress + if ( ! isSaving ) { + isSaving = true; // Set flag to indicate saving is in progress - // Trigger the click event on the publish button - form.querySelector('.' + publishButtonClassName).click(); - saveChangesButton.click(); // Trigger click event on saveChangesButton - isSaving = false; // Reset flag when saving is complete - } - }); + // Trigger the click event on the publish button + form.querySelector( '.' + publishButtonClassName ).click(); + saveChangesButton.click(); // Trigger click event on saveChangesButton + isSaving = false; // Reset flag when saving is complete + } + } ); - // Fetch the configuration settings - fetch(PcpPayLaterConfigurator.ajax.get_config.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - action: 'ppc-get-message-config', - nonce: PcpPayLaterConfigurator.ajax.get_config.nonce - }), - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - return response.json(); - }) - .then(data => { - if (data.success) { - const config = data.data; + // Fetch the configuration settings + fetch( PcpPayLaterConfigurator.ajax.get_config.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + action: 'ppc-get-message-config', + nonce: PcpPayLaterConfigurator.ajax.get_config.nonce, + } ), + } ) + .then( ( response ) => { + if ( ! response.ok ) { + throw new Error( `HTTP error! Status: ${ response.status }` ); + } + return response.json(); + } ) + .then( ( data ) => { + if ( data.success ) { + const config = data.data; - merchantConfigurators.Messaging({ - config: config, - merchantClientId: PcpPayLaterConfigurator.merchantClientId, - partnerClientId: PcpPayLaterConfigurator.partnerClientId, - partnerName: 'WooCommerce', - bnCode: 'Woo_PPCP', - placements: ['cart', 'checkout', 'product', 'shop', 'home', 'custom_placement'], - styleOverrides: { - button: publishButtonClassName, - header: PcpPayLaterConfigurator.headerClassName, - subheader: PcpPayLaterConfigurator.subheaderClassName - }, - onSave: data => { - fetch(PcpPayLaterConfigurator.ajax.save_config.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: PcpPayLaterConfigurator.ajax.save_config.nonce, - config: data, - }), - }); - } - }); - } else { - console.error('Failed to fetch configuration:', data); - } - }) - .catch(error => { - console.error('Error fetching configuration:', error); - }); -}); + merchantConfigurators.Messaging( { + config, + merchantClientId: PcpPayLaterConfigurator.merchantClientId, + partnerClientId: PcpPayLaterConfigurator.partnerClientId, + partnerName: 'WooCommerce', + bnCode: PcpPayLaterConfigurator.bnCode, + placements: [ + 'cart', + 'checkout', + 'product', + 'shop', + 'home', + 'custom_placement', + ], + styleOverrides: { + button: publishButtonClassName, + header: PcpPayLaterConfigurator.headerClassName, + subheader: PcpPayLaterConfigurator.subheaderClassName, + }, + onSave: ( data ) => { + fetch( + PcpPayLaterConfigurator.ajax.save_config.endpoint, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: PcpPayLaterConfigurator.ajax + .save_config.nonce, + config: data, + } ), + } + ); + }, + } ); + } else { + console.error( 'Failed to fetch configuration:', data ); + } + } ) + .catch( ( error ) => { + console.error( 'Error fetching configuration:', error ); + } ); +} ); diff --git a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php index 3588f393a..1aa35462c 100644 --- a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php +++ b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php @@ -113,6 +113,8 @@ class PayLaterConfiguratorModule implements ModuleInterface { $config_factory = $c->get( 'paylater-configurator.factory.config' ); assert( $config_factory instanceof ConfigFactory ); + $bn_code = PPCP_PAYPAL_BN_CODE; + wp_localize_script( 'ppcp-paylater-configurator', 'PcpPayLaterConfigurator', @@ -130,6 +132,7 @@ class PayLaterConfiguratorModule implements ModuleInterface { 'config' => $config_factory->from_settings( $settings ), 'merchantClientId' => $settings->get( 'client_id' ), 'partnerClientId' => $c->get( 'api.partner_merchant_id' ), + 'bnCode' => $bn_code, 'publishButtonClassName' => 'ppcp-paylater-configurator-publishButton', 'headerClassName' => 'ppcp-paylater-configurator-header', 'subheaderClassName' => 'ppcp-paylater-configurator-subheader', diff --git a/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block-inserter.js b/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block-inserter.js index 2a920e401..33792a506 100644 --- a/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block-inserter.js +++ b/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block-inserter.js @@ -1,141 +1,185 @@ -(function(wp) { - const { createBlock } = wp.blocks; - const { select, dispatch, subscribe } = wp.data; - const getBlocks = () => select('core/block-editor').getBlocks() || []; +( function ( wp ) { + const { createBlock } = wp.blocks; + const { select, dispatch, subscribe } = wp.data; + const getBlocks = () => select( 'core/block-editor' ).getBlocks() || []; - const { addFilter } = wp.hooks; - const { assign } = lodash; + const { addFilter } = wp.hooks; + const { assign } = lodash; - // We need to make sure the block is unlocked so that it doesn't get automatically inserted as the last block - addFilter( - 'blocks.registerBlockType', - 'woocommerce-paypal-payments/modify-cart-paylater-messages', - (settings, name) => { - if (name === 'woocommerce-paypal-payments/cart-paylater-messages') { - const newAttributes = assign({}, settings.attributes, { - lock: assign({}, settings.attributes.lock, { - default: assign({}, settings.attributes.lock.default, { - remove: false - }) - }) - }); + // We need to make sure the block is unlocked so that it doesn't get automatically inserted as the last block + addFilter( + 'blocks.registerBlockType', + 'woocommerce-paypal-payments/modify-cart-paylater-messages', + ( settings, name ) => { + if ( + name === 'woocommerce-paypal-payments/cart-paylater-messages' + ) { + const newAttributes = assign( {}, settings.attributes, { + lock: assign( {}, settings.attributes.lock, { + default: assign( {}, settings.attributes.lock.default, { + remove: false, + } ), + } ), + } ); - return assign({}, settings, { - attributes: newAttributes - }); - } - return settings; - } - ); + return assign( {}, settings, { + attributes: newAttributes, + } ); + } + return settings; + } + ); - /** - * Subscribes to changes in the block editor, specifically checking for the presence of 'woocommerce/cart'. - */ - subscribe(() => { - const currentBlocks = getBlocks(); + /** + * Subscribes to changes in the block editor, specifically checking for the presence of 'woocommerce/cart'. + */ + subscribe( () => { + const currentBlocks = getBlocks(); - currentBlocks.forEach(block => { - if (block.name === 'woocommerce/cart') { - ensurePayLaterBlockExists(block); - } - }); - }); + currentBlocks.forEach( ( block ) => { + if ( block.name === 'woocommerce/cart' ) { + ensurePayLaterBlockExists( block ); + } + } ); + } ); - /** - * Ensures the 'woocommerce-paypal-payments/cart-paylater-messages' block exists inside the 'woocommerce/cart' block. - * @param {Object} cartBlock - The cart block instance. - */ - function ensurePayLaterBlockExists(cartBlock) { - const payLaterBlock = findBlockByName(cartBlock.innerBlocks, 'woocommerce-paypal-payments/cart-paylater-messages'); - if (!payLaterBlock) { - waitForBlock('woocommerce/cart-totals-block', 'woocommerce-paypal-payments/cart-paylater-messages', 'woocommerce/cart-order-summary-block'); - } - } + /** + * Ensures the 'woocommerce-paypal-payments/cart-paylater-messages' block exists inside the 'woocommerce/cart' block. + * @param {Object} cartBlock - The cart block instance. + */ + function ensurePayLaterBlockExists( cartBlock ) { + const payLaterBlock = findBlockByName( + cartBlock.innerBlocks, + 'woocommerce-paypal-payments/cart-paylater-messages' + ); + if ( ! payLaterBlock ) { + waitForBlock( + 'woocommerce/cart-totals-block', + 'woocommerce-paypal-payments/cart-paylater-messages', + 'woocommerce/cart-order-summary-block' + ); + } + } - /** - * Waits for a specific block to appear using async/await pattern before executing the insertBlockAfter function. - * @param {string} targetBlockName - Name of the block to wait for. - * @param {string} newBlockName - Name of the new block to insert after the target. - * @param {string} anchorBlockName - Name of the anchor block to determine position. - * @param {number} attempts - The number of attempts made to find the target block. - */ - async function waitForBlock(targetBlockName, newBlockName, anchorBlockName = '', attempts = 0) { - const targetBlock = findBlockByName(getBlocks(), targetBlockName); - if (targetBlock) { - await delay(1000); // We need this to ensure the block is fully rendered - insertBlockAfter(targetBlockName, newBlockName, anchorBlockName); - } else if (attempts < 10) { // Poll up to 10 times - await delay(1000); // Wait 1 second before retrying - await waitForBlock(targetBlockName, newBlockName, anchorBlockName, attempts + 1); - } else { - console.log('Failed to find target block after several attempts.'); - } - } + /** + * Waits for a specific block to appear using async/await pattern before executing the insertBlockAfter function. + * @param {string} targetBlockName - Name of the block to wait for. + * @param {string} newBlockName - Name of the new block to insert after the target. + * @param {string} anchorBlockName - Name of the anchor block to determine position. + * @param {number} attempts - The number of attempts made to find the target block. + */ + async function waitForBlock( + targetBlockName, + newBlockName, + anchorBlockName = '', + attempts = 0 + ) { + const targetBlock = findBlockByName( getBlocks(), targetBlockName ); + if ( targetBlock ) { + await delay( 1000 ); // We need this to ensure the block is fully rendered + insertBlockAfter( targetBlockName, newBlockName, anchorBlockName ); + } else if ( attempts < 10 ) { + // Poll up to 10 times + await delay( 1000 ); // Wait 1 second before retrying + await waitForBlock( + targetBlockName, + newBlockName, + anchorBlockName, + attempts + 1 + ); + } else { + console.log( + 'Failed to find target block after several attempts.' + ); + } + } - /** - * Delays execution by a given number of milliseconds. - * @param {number} ms - Milliseconds to delay. - * @return {Promise} A promise that resolves after the delay. - */ - function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } + /** + * Delays execution by a given number of milliseconds. + * @param {number} ms - Milliseconds to delay. + * @return {Promise} A promise that resolves after the delay. + */ + function delay( ms ) { + return new Promise( ( resolve ) => setTimeout( resolve, ms ) ); + } - /** - * Inserts a block after a specified block if it doesn't already exist. - * @param {string} targetBlockName - Name of the block to find. - * @param {string} newBlockName - Name of the new block to insert. - * @param {string} anchorBlockName - Name of the anchor block to determine position. - */ - function insertBlockAfter(targetBlockName, newBlockName, anchorBlockName = '') { - const targetBlock = findBlockByName(getBlocks(), targetBlockName); - if (!targetBlock) { - // Target block not found - return; - } + /** + * Inserts a block after a specified block if it doesn't already exist. + * @param {string} targetBlockName - Name of the block to find. + * @param {string} newBlockName - Name of the new block to insert. + * @param {string} anchorBlockName - Name of the anchor block to determine position. + */ + function insertBlockAfter( + targetBlockName, + newBlockName, + anchorBlockName = '' + ) { + const targetBlock = findBlockByName( getBlocks(), targetBlockName ); + if ( ! targetBlock ) { + // Target block not found + return; + } - const parentBlock = select('core/block-editor').getBlock(targetBlock.clientId); - if (parentBlock.innerBlocks.some(block => block.name === newBlockName)) { - // The block is already inserted next to the target block - return; - } + const parentBlock = select( 'core/block-editor' ).getBlock( + targetBlock.clientId + ); + if ( + parentBlock.innerBlocks.some( + ( block ) => block.name === newBlockName + ) + ) { + // The block is already inserted next to the target block + return; + } - let offset = 0; - if (anchorBlockName !== '') { - // Find the anchor block and calculate the offset - const anchorIndex = parentBlock.innerBlocks.findIndex(block => block.name === anchorBlockName); - offset = parentBlock.innerBlocks.length - (anchorIndex + 1); - } + let offset = 0; + if ( anchorBlockName !== '' ) { + // Find the anchor block and calculate the offset + const anchorIndex = parentBlock.innerBlocks.findIndex( + ( block ) => block.name === anchorBlockName + ); + offset = parentBlock.innerBlocks.length - ( anchorIndex + 1 ); + } - const newBlock = createBlock(newBlockName); + const newBlock = createBlock( newBlockName ); - // Insert the block at the correct position - dispatch('core/block-editor').insertBlock(newBlock, parentBlock.innerBlocks.length - offset, parentBlock.clientId); + // Insert the block at the correct position + dispatch( 'core/block-editor' ).insertBlock( + newBlock, + parentBlock.innerBlocks.length - offset, + parentBlock.clientId + ); - // Lock the block after it has been inserted - dispatch('core/block-editor').updateBlockAttributes(newBlock.clientId, { - lock: { remove: true } - }); - } + // Lock the block after it has been inserted + dispatch( 'core/block-editor' ).updateBlockAttributes( + newBlock.clientId, + { + lock: { remove: true }, + } + ); + } - /** - * Recursively searches for a block by name among all blocks. - * @param {Array} blocks - The array of blocks to search. - * @param {string} blockName - The name of the block to find. - * @returns {Object|null} The found block, or null if not found. - */ - function findBlockByName(blocks, blockName) { - for (const block of blocks) { - if (block.name === blockName) { - return block; - } - if (block.innerBlocks.length > 0) { - const foundBlock = findBlockByName(block.innerBlocks, blockName); - if (foundBlock) { - return foundBlock; - } - } - } - return null; - } -})(window.wp); + /** + * Recursively searches for a block by name among all blocks. + * @param {Array} blocks - The array of blocks to search. + * @param {string} blockName - The name of the block to find. + * @return {Object|null} The found block, or null if not found. + */ + function findBlockByName( blocks, blockName ) { + for ( const block of blocks ) { + if ( block.name === blockName ) { + return block; + } + if ( block.innerBlocks.length > 0 ) { + const foundBlock = findBlockByName( + block.innerBlocks, + blockName + ); + if ( foundBlock ) { + return foundBlock; + } + } + } + return null; + } +} )( window.wp ); diff --git a/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block.js b/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block.js index cd77866ed..59957443e 100644 --- a/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block.js +++ b/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/cart-paylater-block.js @@ -10,32 +10,31 @@ import Edit from './edit'; import metadata from './block.json'; const paypalIcon = ( - - - - - - - + + + + + + + ); -registerBlockType(metadata, { - icon: paypalIcon, - edit: Edit, - save() { - return null; - }, -}); - +registerBlockType( metadata, { + icon: paypalIcon, + edit: Edit, + save() { + return null; + }, +} ); diff --git a/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/edit.js b/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/edit.js index 58546c5f3..3d4d95b77 100644 --- a/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/edit.js +++ b/modules/ppcp-paylater-wc-blocks/resources/js/CartPayLaterMessagesBlock/edit.js @@ -5,134 +5,190 @@ import { PanelBody, Spinner } from '@wordpress/components'; import { PayPalScriptProvider, PayPalMessages } from '@paypal/react-paypal-js'; import { useScriptParams } from '../../../../ppcp-paylater-block/resources/js/hooks/script-params'; -export default function Edit({ attributes, clientId, setAttributes }) { - const { ppcpId } = attributes; +export default function Edit( { attributes, clientId, setAttributes } ) { + const { ppcpId } = attributes; - const [loaded, setLoaded] = useState(false); + const [ loaded, setLoaded ] = useState( false ); - let amount; - const postContent = String(wp.data.select('core/editor')?.getEditedPostContent()); - if (postContent.includes('woocommerce/checkout') || postContent.includes('woocommerce/cart')) { - amount = 50.0; - } + let amount; + const postContent = String( + wp.data.select( 'core/editor' )?.getEditedPostContent() + ); + if ( + postContent.includes( 'woocommerce/checkout' ) || + postContent.includes( 'woocommerce/cart' ) + ) { + amount = 50.0; + } - const cartConfig = PcpCartPayLaterBlock.config.cart; + const cartConfig = PcpCartPayLaterBlock.config.cart; - // Dynamically setting previewStyle based on the layout attribute - let previewStyle = {}; - if (cartConfig.layout === 'flex') { - previewStyle = { - layout: cartConfig.layout, - color: cartConfig.color, - ratio: cartConfig.ratio, - }; - } else { - previewStyle = { - layout: cartConfig.layout, - logo: { - position: cartConfig['logo-position'], - type: cartConfig['logo-type'], - }, - text: { - color: cartConfig['text-color'], - size: cartConfig['text-size'], - }, - }; - } + // Dynamically setting previewStyle based on the layout attribute + let previewStyle = {}; + if ( cartConfig.layout === 'flex' ) { + previewStyle = { + layout: cartConfig.layout, + color: cartConfig.color, + ratio: cartConfig.ratio, + }; + } else { + previewStyle = { + layout: cartConfig.layout, + logo: { + position: cartConfig[ 'logo-position' ], + type: cartConfig[ 'logo-type' ], + }, + text: { + color: cartConfig[ 'text-color' ], + size: cartConfig[ 'text-size' ], + }, + }; + } - let classes = ['ppcp-paylater-block-preview', 'ppcp-overlay-parent']; - if (PcpCartPayLaterBlock.vaultingEnabled || !PcpCartPayLaterBlock.placementEnabled) { - classes = [...classes, 'ppcp-paylater-unavailable', 'block-editor-warning']; - } - const props = useBlockProps({ className: classes.join(' ') }); + let classes = [ 'ppcp-paylater-block-preview', 'ppcp-overlay-parent' ]; + if ( + PcpCartPayLaterBlock.vaultingEnabled || + ! PcpCartPayLaterBlock.placementEnabled + ) { + classes = [ + ...classes, + 'ppcp-paylater-unavailable', + 'block-editor-warning', + ]; + } + const props = useBlockProps( { className: classes.join( ' ' ) } ); - useEffect(() => { - if (!ppcpId) { - setAttributes({ ppcpId: `ppcp-${clientId}` }); - } - }, [ppcpId, clientId]); + useEffect( () => { + if ( ! ppcpId ) { + setAttributes( { ppcpId: `ppcp-${ clientId }` } ); + } + }, [ ppcpId, clientId ] ); - if (PcpCartPayLaterBlock.vaultingEnabled) { - return ( -
-
-

- {__('Cart - Pay Later Messaging cannot be used while PayPal Vaulting is active. Disable PayPal Vaulting in the PayPal Payment settings to reactivate this block', 'woocommerce-paypal-payments')} -

- -
-
- ); - } + if ( PcpCartPayLaterBlock.vaultingEnabled ) { + return ( +
+
+

+ { __( + 'Cart - Pay Later Messaging cannot be used while PayPal Vaulting is active. Disable PayPal Vaulting in the PayPal Payment settings to reactivate this block', + 'woocommerce-paypal-payments' + ) } +

+ +
+
+ ); + } - if (!PcpCartPayLaterBlock.placementEnabled) { - return ( -
-
-

- {__('Cart - Pay Later Messaging cannot be used while the “Cart” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', 'woocommerce-paypal-payments')} -

- -
-
- ); - } + if ( ! PcpCartPayLaterBlock.placementEnabled ) { + return ( +
+
+

+ { __( + 'Cart - Pay Later Messaging cannot be used while the “Cart” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', + 'woocommerce-paypal-payments' + ) } +

+ +
+
+ ); + } - const scriptParams = useScriptParams(PcpCartPayLaterBlock.ajax.cart_script_params); - if (scriptParams === null) { - return
; - } + const scriptParams = useScriptParams( + PcpCartPayLaterBlock.ajax.cart_script_params + ); + if ( scriptParams === null ) { + return ( +
+ +
+ ); + } - const urlParams = { - ...scriptParams.url_params, - components: 'messages', - dataNamespace: 'ppcp-block-editor-cart-paylater-message', - }; + const urlParams = { + ...scriptParams.url_params, + components: 'messages', + dataNamespace: 'ppcp-block-editor-cart-paylater-message', + }; - return ( - <> - - -

- {__('Choose the layout and color of your messaging in the PayPal Payments Pay Later settings for the “Cart” messaging placement.', 'woocommerce-paypal-payments')} -

- - - -
-
-
-
- - setLoaded(true)} - amount={amount} - /> - -
-
{/* make the message not clickable */} - {!loaded && } -
-
- - ); + return ( + <> + + +

+ { __( + 'Choose the layout and color of your messaging in the PayPal Payments Pay Later settings for the “Cart” messaging placement.', + 'woocommerce-paypal-payments' + ) } +

+ + + +
+
+
+
+ + setLoaded( true ) } + amount={ amount } + /> + +
+
+ { ' ' } + { /* make the message not clickable */ } + { ! loaded && } +
+
+ + ); } diff --git a/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/checkout-paylater-block.js b/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/checkout-paylater-block.js index cd77866ed..59957443e 100644 --- a/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/checkout-paylater-block.js +++ b/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/checkout-paylater-block.js @@ -10,32 +10,31 @@ import Edit from './edit'; import metadata from './block.json'; const paypalIcon = ( - - - - - - - + + + + + + + ); -registerBlockType(metadata, { - icon: paypalIcon, - edit: Edit, - save() { - return null; - }, -}); - +registerBlockType( metadata, { + icon: paypalIcon, + edit: Edit, + save() { + return null; + }, +} ); diff --git a/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/edit.js b/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/edit.js index 3b081cb69..fdeae046e 100644 --- a/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/edit.js +++ b/modules/ppcp-paylater-wc-blocks/resources/js/CheckoutPayLaterMessagesBlock/edit.js @@ -5,128 +5,190 @@ import { PanelBody, Spinner } from '@wordpress/components'; import { PayPalScriptProvider, PayPalMessages } from '@paypal/react-paypal-js'; import { useScriptParams } from '../../../../ppcp-paylater-block/resources/js/hooks/script-params'; -export default function Edit({ attributes, clientId, setAttributes }) { - const { ppcpId } = attributes; +export default function Edit( { attributes, clientId, setAttributes } ) { + const { ppcpId } = attributes; - const [loaded, setLoaded] = useState(false); + const [ loaded, setLoaded ] = useState( false ); - let amount = undefined; - const postContent = String(wp.data.select('core/editor')?.getEditedPostContent()); - if (postContent.includes('woocommerce/checkout') || postContent.includes('woocommerce/cart')) { - amount = 50.0; - } + let amount; + const postContent = String( + wp.data.select( 'core/editor' )?.getEditedPostContent() + ); + if ( + postContent.includes( 'woocommerce/checkout' ) || + postContent.includes( 'woocommerce/cart' ) + ) { + amount = 50.0; + } - const checkoutConfig = PcpCheckoutPayLaterBlock.config.checkout; + const checkoutConfig = PcpCheckoutPayLaterBlock.config.checkout; - // Dynamically setting previewStyle based on the layout attribute - let previewStyle = {}; - if (checkoutConfig.layout === 'flex') { - previewStyle = { - layout: checkoutConfig.layout, - color: checkoutConfig.color, - ratio: checkoutConfig.ratio, - }; - } else { - previewStyle = { - layout: checkoutConfig.layout, - logo: { - position: checkoutConfig['logo-position'], - type: checkoutConfig['logo-type'], - }, - text: { - color: checkoutConfig['text-color'], - size: checkoutConfig['text-size'], - }, - }; - } + // Dynamically setting previewStyle based on the layout attribute + let previewStyle = {}; + if ( checkoutConfig.layout === 'flex' ) { + previewStyle = { + layout: checkoutConfig.layout, + color: checkoutConfig.color, + ratio: checkoutConfig.ratio, + }; + } else { + previewStyle = { + layout: checkoutConfig.layout, + logo: { + position: checkoutConfig[ 'logo-position' ], + type: checkoutConfig[ 'logo-type' ], + }, + text: { + color: checkoutConfig[ 'text-color' ], + size: checkoutConfig[ 'text-size' ], + }, + }; + } - let classes = ['ppcp-paylater-block-preview', 'ppcp-overlay-parent']; - if (PcpCheckoutPayLaterBlock.vaultingEnabled || !PcpCheckoutPayLaterBlock.placementEnabled) { - classes = ['ppcp-paylater-block-preview', 'ppcp-paylater-unavailable', 'block-editor-warning']; - } - const props = useBlockProps({ className: classes }); + let classes = [ 'ppcp-paylater-block-preview', 'ppcp-overlay-parent' ]; + if ( + PcpCheckoutPayLaterBlock.vaultingEnabled || + ! PcpCheckoutPayLaterBlock.placementEnabled + ) { + classes = [ + 'ppcp-paylater-block-preview', + 'ppcp-paylater-unavailable', + 'block-editor-warning', + ]; + } + const props = useBlockProps( { className: classes } ); - useEffect(() => { - if (!ppcpId) { - setAttributes({ ppcpId: 'ppcp-' + clientId }); - } - }, [ppcpId, clientId]); + useEffect( () => { + if ( ! ppcpId ) { + setAttributes( { ppcpId: 'ppcp-' + clientId } ); + } + }, [ ppcpId, clientId ] ); - if (PcpCheckoutPayLaterBlock.vaultingEnabled) { - return ( -
-
-

{__('Checkout - Pay Later Messaging cannot be used while PayPal Vaulting is active. Disable PayPal Vaulting in the PayPal Payment settings to reactivate this block', 'woocommerce-paypal-payments')}

- -
-
- ); - } + if ( PcpCheckoutPayLaterBlock.vaultingEnabled ) { + return ( +
+
+

+ { __( + 'Checkout - Pay Later Messaging cannot be used while PayPal Vaulting is active. Disable PayPal Vaulting in the PayPal Payment settings to reactivate this block', + 'woocommerce-paypal-payments' + ) } +

+ +
+
+ ); + } - if (!PcpCheckoutPayLaterBlock.placementEnabled) { - return ( -
-
-

{__('Checkout - Pay Later Messaging cannot be used while the “Checkout” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', 'woocommerce-paypal-payments')}

- -
-
- ); - } + if ( ! PcpCheckoutPayLaterBlock.placementEnabled ) { + return ( +
+
+

+ { __( + 'Checkout - Pay Later Messaging cannot be used while the “Checkout” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', + 'woocommerce-paypal-payments' + ) } +

+ +
+
+ ); + } - const scriptParams = useScriptParams(PcpCheckoutPayLaterBlock.ajax.cart_script_params); - if (scriptParams === null) { - return
; - } + const scriptParams = useScriptParams( + PcpCheckoutPayLaterBlock.ajax.cart_script_params + ); + if ( scriptParams === null ) { + return ( +
+ +
+ ); + } - const urlParams = { - ...scriptParams.url_params, - components: 'messages', - dataNamespace: 'ppcp-block-editor-checkout-paylater-message', - }; + const urlParams = { + ...scriptParams.url_params, + components: 'messages', + dataNamespace: 'ppcp-block-editor-checkout-paylater-message', + }; - return ( - <> - - -

{__('Choose the layout and color of your messaging in the PayPal Payments Pay Later settings for the “Checkout” messaging placement.', 'woocommerce-paypal-payments')}

- - - -
-
-
-
- - setLoaded(true)} - amount={amount} - /> - -
-
{/* make the message not clickable */} - {!loaded && } -
-
- - ); + return ( + <> + + +

+ { __( + 'Choose the layout and color of your messaging in the PayPal Payments Pay Later settings for the “Checkout” messaging placement.', + 'woocommerce-paypal-payments' + ) } +

+ + + +
+
+
+
+ + setLoaded( true ) } + amount={ amount } + /> + +
+
+ { ' ' } + { /* make the message not clickable */ } + { ! loaded && } +
+
+ + ); } diff --git a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php index 6709b9a5b..f0720300b 100644 --- a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php +++ b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php @@ -95,14 +95,16 @@ class PayLaterWCBlocksRenderer { ) { if ( PayLaterWCBlocksModule::is_placement_enabled( $c->get( 'wcgateway.settings.status' ), $location ) ) { - $html = '
'; + $bn_code = PPCP_PAYPAL_BN_CODE; + + $html = '
'; $processor = new \WP_HTML_Tag_Processor( $html ); if ( $processor->next_tag( 'div' ) ) { $processor->set_attribute( 'data-block-name', esc_attr( $attributes['blockId'] ?? '' ) ); $processor->set_attribute( 'class', 'ppcp-messages' ); - $processor->set_attribute( 'data-partner-attribution-id', 'Woo_PPCP' ); + $processor->set_attribute( 'data-partner-attribution-id', $bn_code ); if ( $this->layout === 'flex' ) { $processor->set_attribute( 'data-pp-style-layout', 'flex' ); diff --git a/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js b/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js index 0c4466d8d..3e511a569 100644 --- a/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js +++ b/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js @@ -1,105 +1,148 @@ -document.addEventListener( - 'DOMContentLoaded', - () => { - const variations = document.querySelector('.woocommerce_variations'); - const disableFields = (productId) => { - if(variations) { - const children = variations.children; - for(let i=0; i < children.length; i++) { - const variableId = children[i].querySelector('h3').getElementsByClassName('variable_post_id')[0].value - if (parseInt(variableId) === productId) { - children[i].querySelector('.woocommerce_variable_attributes') - .getElementsByClassName('wc_input_subscription_period_interval')[0] - .setAttribute('disabled', 'disabled'); - children[i].querySelector('.woocommerce_variable_attributes') - .getElementsByClassName('wc_input_subscription_period')[0] - .setAttribute('disabled', 'disabled'); - children[i].querySelector('.woocommerce_variable_attributes') - .getElementsByClassName('wc_input_subscription_trial_length')[0] - .setAttribute('disabled', 'disabled'); - children[i].querySelector('.woocommerce_variable_attributes') - .getElementsByClassName('wc_input_subscription_trial_period')[0] - .setAttribute('disabled', 'disabled'); - children[i].querySelector('.woocommerce_variable_attributes') - .getElementsByClassName('wc_input_subscription_length')[0] - .setAttribute('disabled', 'disabled'); - } - } - } +document.addEventListener( 'DOMContentLoaded', () => { + const variations = document.querySelector( '.woocommerce_variations' ); + const disableFields = ( productId ) => { + if ( variations ) { + const children = variations.children; + for ( let i = 0; i < children.length; i++ ) { + const variableId = children[ i ] + .querySelector( 'h3' ) + .getElementsByClassName( 'variable_post_id' )[ 0 ].value; + if ( parseInt( variableId ) === productId ) { + children[ i ] + .querySelector( '.woocommerce_variable_attributes' ) + .getElementsByClassName( + 'wc_input_subscription_period_interval' + )[ 0 ] + .setAttribute( 'disabled', 'disabled' ); + children[ i ] + .querySelector( '.woocommerce_variable_attributes' ) + .getElementsByClassName( + 'wc_input_subscription_period' + )[ 0 ] + .setAttribute( 'disabled', 'disabled' ); + children[ i ] + .querySelector( '.woocommerce_variable_attributes' ) + .getElementsByClassName( + 'wc_input_subscription_trial_length' + )[ 0 ] + .setAttribute( 'disabled', 'disabled' ); + children[ i ] + .querySelector( '.woocommerce_variable_attributes' ) + .getElementsByClassName( + 'wc_input_subscription_trial_period' + )[ 0 ] + .setAttribute( 'disabled', 'disabled' ); + children[ i ] + .querySelector( '.woocommerce_variable_attributes' ) + .getElementsByClassName( + 'wc_input_subscription_length' + )[ 0 ] + .setAttribute( 'disabled', 'disabled' ); + } + } + } - const periodInterval = document.querySelector('#_subscription_period_interval'); - periodInterval.setAttribute('disabled', 'disabled'); + const periodInterval = document.querySelector( + '#_subscription_period_interval' + ); + periodInterval.setAttribute( 'disabled', 'disabled' ); - const subscriptionPeriod = document.querySelector('#_subscription_period'); - subscriptionPeriod.setAttribute('disabled', 'disabled'); + const subscriptionPeriod = document.querySelector( + '#_subscription_period' + ); + subscriptionPeriod.setAttribute( 'disabled', 'disabled' ); - const subscriptionLength = document.querySelector('._subscription_length_field'); - subscriptionLength.style.display = 'none'; + const subscriptionLength = document.querySelector( + '._subscription_length_field' + ); + subscriptionLength.style.display = 'none'; - const subscriptionTrial = document.querySelector('._subscription_trial_length_field'); - subscriptionTrial.style.display = 'none'; + const subscriptionTrial = document.querySelector( + '._subscription_trial_length_field' + ); + subscriptionTrial.style.display = 'none'; - const soldIndividually = document.querySelector( '#_sold_individually' ); - soldIndividually.setAttribute( 'disabled', 'disabled' ); - } + const soldIndividually = document.querySelector( + '#_sold_individually' + ); + soldIndividually.setAttribute( 'disabled', 'disabled' ); + }; - const setupProducts = () => { - PayPalCommerceGatewayPayPalSubscriptionProducts?.forEach((product) => { - if(product.product_connected === 'yes') { - disableFields(product.product_id); - } + const setupProducts = () => { + PayPalCommerceGatewayPayPalSubscriptionProducts?.forEach( + ( product ) => { + if ( product.product_connected === 'yes' ) { + disableFields( product.product_id ); + } - const unlinkBtn = document.getElementById(`ppcp-unlink-sub-plan-${product.product_id}`); - unlinkBtn?.addEventListener('click', (event)=>{ - event.preventDefault(); - unlinkBtn.disabled = true; - const spinner = document.getElementById('spinner-unlink-plan'); - spinner.style.display = 'inline-block'; + const unlinkBtn = document.getElementById( + `ppcp-unlink-sub-plan-${ product.product_id }` + ); + unlinkBtn?.addEventListener( 'click', ( event ) => { + event.preventDefault(); + unlinkBtn.disabled = true; + const spinner = document.getElementById( + 'spinner-unlink-plan' + ); + spinner.style.display = 'inline-block'; - fetch(product.ajax.deactivate_plan.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: product.ajax.deactivate_plan.nonce, - plan_id: product.plan_id, - product_id: product.product_id - }) - }).then(function (res) { - return res.json(); - }).then(function (data) { - if (!data.success) { - unlinkBtn.disabled = false; - spinner.style.display = 'none'; - console.error(data); - throw Error(data.data.message); - } + fetch( product.ajax.deactivate_plan.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: product.ajax.deactivate_plan.nonce, + plan_id: product.plan_id, + product_id: product.product_id, + } ), + } ) + .then( function ( res ) { + return res.json(); + } ) + .then( function ( data ) { + if ( ! data.success ) { + unlinkBtn.disabled = false; + spinner.style.display = 'none'; + console.error( data ); + throw Error( data.data.message ); + } - const enableSubscription = document.getElementById('ppcp-enable-subscription'); - const product = document.getElementById('pcpp-product'); - const plan = document.getElementById('pcpp-plan'); - enableSubscription.style.display = 'none'; - product.style.display = 'none'; - plan.style.display = 'none'; + const enableSubscription = document.getElementById( + 'ppcp-enable-subscription' + ); + const product = + document.getElementById( 'pcpp-product' ); + const plan = document.getElementById( 'pcpp-plan' ); + enableSubscription.style.display = 'none'; + product.style.display = 'none'; + plan.style.display = 'none'; - const enable_subscription_product = document.getElementById('ppcp_enable_subscription_product'); - enable_subscription_product.disabled = true; + const enable_subscription_product = + document.getElementById( + 'ppcp_enable_subscription_product' + ); + enable_subscription_product.disabled = true; - const planUnlinked = document.getElementById('pcpp-plan-unlinked'); - planUnlinked.style.display = 'block'; + const planUnlinked = + document.getElementById( 'pcpp-plan-unlinked' ); + planUnlinked.style.display = 'block'; - setTimeout(() => { - location.reload(); - }, 1000) - }); - }); - }) - } + setTimeout( () => { + location.reload(); + }, 1000 ); + } ); + } ); + } + ); + }; - setupProducts(); - jQuery( '#woocommerce-product-data' ).on('woocommerce_variations_loaded', () => { - setupProducts(); - }); - }); + setupProducts(); + jQuery( '#woocommerce-product-data' ).on( + 'woocommerce_variations_loaded', + () => { + setupProducts(); + } + ); +} ); diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 27d6b2a1e..77cb8ae49 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -109,34 +109,36 @@ class PayPalSubscriptionsModule implements ModuleInterface { $product = wc_get_product( $product_id ); + if ( ! ( is_a( $product, WC_Product::class ) ) ) { + wc_add_notice( __( 'Cannot add this product to cart (invalid product).', 'woocommerce-paypal-payments' ), 'error' ); + return false; + } + $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; + $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; + $is_paypal_subscription = static function ( $product ) use ( $subscriptions_mode ): bool { + return $product && + in_array( $product->get_type(), array( 'subscription', 'variable-subscription' ), true ) && + 'subscriptions_api' === $subscriptions_mode && + $product->get_meta( '_ppcp_enable_subscription_product', true ) === 'yes'; + }; - if ( 'subscriptions_api' !== $subscriptions_mode ) { - if ( $product && $product->get_sold_individually() ) { - $product->set_sold_individually( false ); - $product->save(); - } - - return $passed_validation; - } - - if ( $product && $product->get_meta( '_ppcp_enable_subscription_product', true ) === 'yes' ) { + if ( $is_paypal_subscription( $product ) ) { if ( ! $product->get_sold_individually() ) { $product->set_sold_individually( true ); $product->save(); } - wc_add_notice( __( 'You cannot add a subscription product to a cart with other items.', 'woocommerce-paypal-payments' ), 'error' ); + wc_add_notice( __( 'You cannot add a PayPal Subscription product to a cart with other items.', 'woocommerce-paypal-payments' ), 'error' ); return false; } - foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { + foreach ( WC()->cart->get_cart() as $cart_item ) { $cart_product = wc_get_product( $cart_item['product_id'] ); - if ( $cart_product && $cart_product->get_meta( '_ppcp_enable_subscription_product', true ) === 'yes' ) { - wc_add_notice( __( 'You can only have one subscription product in your cart.', 'woocommerce-paypal-payments' ), 'error' ); + if ( $is_paypal_subscription( $cart_product ) ) { + wc_add_notice( __( 'You can only have one PayPal Subscription product in your cart.', 'woocommerce-paypal-payments' ), 'error' ); return false; } } diff --git a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js index 6178bd92c..659d515f4 100644 --- a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js +++ b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js @@ -1,228 +1,306 @@ import { - getCurrentPaymentMethod, - ORDER_BUTTON_SELECTOR, - PaymentMethods -} from "../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState"; -import {loadScript} from "@paypal/paypal-js"; + getCurrentPaymentMethod, + ORDER_BUTTON_SELECTOR, + PaymentMethods, +} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { loadScript } from '@paypal/paypal-js'; import { - setVisible, - setVisibleByClass -} from "../../../ppcp-button/resources/js/modules/Helper/Hiding"; -import ErrorHandler from "../../../ppcp-button/resources/js/modules/ErrorHandler"; -import {cardFieldStyles} from "../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper"; + setVisible, + setVisibleByClass, +} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; +import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; +import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper'; const errorHandler = new ErrorHandler( - ppcp_add_payment_method.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') + ppcp_add_payment_method.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) ); const init = () => { - setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden'); - setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL); -} + setVisibleByClass( + ORDER_BUTTON_SELECTOR, + getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, + 'ppcp-hidden' + ); + setVisible( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`, + getCurrentPaymentMethod() === PaymentMethods.PAYPAL + ); +}; -document.addEventListener( - 'DOMContentLoaded', - () => { - jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () { - init() - }); +document.addEventListener( 'DOMContentLoaded', () => { + jQuery( document.body ).on( + 'click init_add_payment_method', + '.payment_methods input.input-radio', + function () { + init(); + } + ); - if(ppcp_add_payment_method.is_subscription_change_payment_page) { - const saveToAccount = document.querySelector('#wc-ppcp-credit-card-gateway-new-payment-method'); - if(saveToAccount) { - saveToAccount.checked = true; - saveToAccount.disabled = true; - } - } + if ( ppcp_add_payment_method.is_subscription_change_payment_page ) { + const saveToAccount = document.querySelector( + '#wc-ppcp-credit-card-gateway-new-payment-method' + ); + if ( saveToAccount ) { + saveToAccount.checked = true; + saveToAccount.disabled = true; + } + } - setTimeout(() => { - loadScript({ - clientId: ppcp_add_payment_method.client_id, - merchantId: ppcp_add_payment_method.merchant_id, - dataUserIdToken: ppcp_add_payment_method.id_token, - components: 'buttons,card-fields', - }) - .then((paypal) => { - errorHandler.clear(); + setTimeout( () => { + loadScript( { + clientId: ppcp_add_payment_method.client_id, + merchantId: ppcp_add_payment_method.merchant_id, + dataUserIdToken: ppcp_add_payment_method.id_token, + components: 'buttons,card-fields', + } ).then( ( paypal ) => { + errorHandler.clear(); - const paypalButtonContainer = document.querySelector(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`); - if(paypalButtonContainer) { - paypal.Buttons( - { - createVaultSetupToken: async () => { - const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, { - method: "POST", - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce, - }) - }) + const paypalButtonContainer = document.querySelector( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` + ); + if ( paypalButtonContainer ) { + paypal + .Buttons( { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .create_setup_token.nonce, + } ), + } + ); - const result = await response.json() - if (result.data.id) { - return result.data.id - } + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } - errorHandler.message(ppcp_add_payment_method.error_message); - }, - onApprove: async ({vaultSetupToken}) => { - const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, { - method: "POST", - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - }) - }) + errorHandler.message( + ppcp_add_payment_method.error_message + ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + ppcp_add_payment_method.ajax + .create_payment_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); - const result = await response.json(); - if(result.success === true) { - window.location.href = ppcp_add_payment_method.payment_methods_page; - return; - } + const result = await response.json(); + if ( result.success === true ) { + window.location.href = + ppcp_add_payment_method.payment_methods_page; + return; + } - errorHandler.message(ppcp_add_payment_method.error_message); - }, - onError: (error) => { - console.error(error) - errorHandler.message(ppcp_add_payment_method.error_message); - } - }, - ).render(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`); - } + errorHandler.message( + ppcp_add_payment_method.error_message + ); + }, + onError: ( error ) => { + console.error( error ); + errorHandler.message( + ppcp_add_payment_method.error_message + ); + }, + } ) + .render( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` + ); + } - const cardField = paypal.CardFields({ - createVaultSetupToken: async () => { - const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, { - method: "POST", - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce, - payment_method: PaymentMethods.CARDS, - verification_method: ppcp_add_payment_method.verification_method - }) - }) + const cardField = paypal.CardFields( { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .create_setup_token.nonce, + payment_method: PaymentMethods.CARDS, + verification_method: + ppcp_add_payment_method.verification_method, + } ), + } + ); - const result = await response.json() - if (result.data.id) { - return result.data.id - } + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } - errorHandler.message(ppcp_add_payment_method.error_message); - }, - onApprove: async ({vaultSetupToken}) => { - const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, { - method: "POST", - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - payment_method: PaymentMethods.CARDS - }) - }) + errorHandler.message( + ppcp_add_payment_method.error_message + ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_payment_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + payment_method: PaymentMethods.CARDS, + } ), + } + ); - const result = await response.json(); - if(result.success === true) { - if(ppcp_add_payment_method.is_subscription_change_payment_page) { - const subscriptionId = ppcp_add_payment_method.subscription_id_to_change_payment; - if(subscriptionId && result.data) { - const req = await fetch(ppcp_add_payment_method.ajax.subscription_change_payment_method.endpoint, { - method: "POST", - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nonce: ppcp_add_payment_method.ajax.subscription_change_payment_method.nonce, - subscription_id: subscriptionId, - payment_method: getCurrentPaymentMethod(), - wc_payment_token_id: result.data - }) - }); + const result = await response.json(); + if ( result.success === true ) { + if ( + ppcp_add_payment_method.is_subscription_change_payment_page + ) { + const subscriptionId = + ppcp_add_payment_method.subscription_id_to_change_payment; + if ( subscriptionId && result.data ) { + const req = await fetch( + ppcp_add_payment_method.ajax + .subscription_change_payment_method + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .subscription_change_payment_method + .nonce, + subscription_id: subscriptionId, + payment_method: + getCurrentPaymentMethod(), + wc_payment_token_id: result.data, + } ), + } + ); - const res = await req.json(); - if (res.success === true) { - window.location.href = `${ppcp_add_payment_method.view_subscriptions_page}/${subscriptionId}`; - return; - } - } + const res = await req.json(); + if ( res.success === true ) { + window.location.href = `${ ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; + return; + } + } - return; - } + return; + } - window.location.href = ppcp_add_payment_method.payment_methods_page; - return; - } + window.location.href = + ppcp_add_payment_method.payment_methods_page; + return; + } - errorHandler.message(ppcp_add_payment_method.error_message); - }, - onError: (error) => { - console.error(error) - errorHandler.message(ppcp_add_payment_method.error_message); - } - }); + errorHandler.message( + ppcp_add_payment_method.error_message + ); + }, + onError: ( error ) => { + console.error( error ); + errorHandler.message( + ppcp_add_payment_method.error_message + ); + }, + } ); - if (cardField.isEligible()) { - const nameField = document.getElementById('ppcp-credit-card-gateway-card-name'); - if (nameField) { - let styles = cardFieldStyles(nameField); - cardField.NameField({style: {'input': styles}}).render(nameField.parentNode); - nameField.hidden = true; - } + if ( cardField.isEligible() ) { + const nameField = document.getElementById( + 'ppcp-credit-card-gateway-card-name' + ); + if ( nameField ) { + const styles = cardFieldStyles( nameField ); + cardField + .NameField( { style: { input: styles } } ) + .render( nameField.parentNode ); + nameField.hidden = true; + } - const numberField = document.getElementById('ppcp-credit-card-gateway-card-number'); - if (numberField) { - let styles = cardFieldStyles(numberField); - cardField.NumberField({style: {'input': styles}}).render(numberField.parentNode); - numberField.hidden = true; - } + const numberField = document.getElementById( + 'ppcp-credit-card-gateway-card-number' + ); + if ( numberField ) { + const styles = cardFieldStyles( numberField ); + cardField + .NumberField( { style: { input: styles } } ) + .render( numberField.parentNode ); + numberField.hidden = true; + } - const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry'); - if (expiryField) { - let styles = cardFieldStyles(expiryField); - cardField.ExpiryField({style: {'input': styles}}).render(expiryField.parentNode); - expiryField.hidden = true; - } + const expiryField = document.getElementById( + 'ppcp-credit-card-gateway-card-expiry' + ); + if ( expiryField ) { + const styles = cardFieldStyles( expiryField ); + cardField + .ExpiryField( { style: { input: styles } } ) + .render( expiryField.parentNode ); + expiryField.hidden = true; + } - const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc'); - if (cvvField) { - let styles = cardFieldStyles(cvvField); - cardField.CVVField({style: {'input': styles}}).render(cvvField.parentNode); - cvvField.hidden = true; - } - } + const cvvField = document.getElementById( + 'ppcp-credit-card-gateway-card-cvc' + ); + if ( cvvField ) { + const styles = cardFieldStyles( cvvField ); + cardField + .CVVField( { style: { input: styles } } ) + .render( cvvField.parentNode ); + cvvField.hidden = true; + } + } - document.querySelector('#place_order')?.addEventListener("click", (event) => { - const cardPaymentToken = document.querySelector('input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked')?.value; - if ( - getCurrentPaymentMethod() !== 'ppcp-credit-card-gateway' - || cardPaymentToken && cardPaymentToken !== 'new' - ) { - return; - } + document + .querySelector( '#place_order' ) + ?.addEventListener( 'click', ( event ) => { + const cardPaymentToken = document.querySelector( + 'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked' + )?.value; + if ( + getCurrentPaymentMethod() !== + 'ppcp-credit-card-gateway' || + ( cardPaymentToken && cardPaymentToken !== 'new' ) + ) { + return; + } - event.preventDefault(); - - cardField.submit() - .catch((error) => { - console.error(error) - }); - }); - }) - }, 1000) - } -); + event.preventDefault(); + cardField.submit().catch( ( error ) => { + console.error( error ); + } ); + } ); + } ); + }, 1000 ); +} ); diff --git a/modules/ppcp-save-payment-methods/services.php b/modules/ppcp-save-payment-methods/services.php index a0b43f021..37f725d3c 100644 --- a/modules/ppcp-save-payment-methods/services.php +++ b/modules/ppcp-save-payment-methods/services.php @@ -153,6 +153,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', diff --git a/modules/ppcp-uninstall/resources/js/ppcp-clear-db.js b/modules/ppcp-uninstall/resources/js/ppcp-clear-db.js index f473a8e5c..eed3d358d 100644 --- a/modules/ppcp-uninstall/resources/js/ppcp-clear-db.js +++ b/modules/ppcp-uninstall/resources/js/ppcp-clear-db.js @@ -1,43 +1,57 @@ -document.addEventListener( - 'DOMContentLoaded', - () => { - const config = PayPalCommerceGatewayClearDb; - if (!typeof (config)) { - return; - } +document.addEventListener( 'DOMContentLoaded', () => { + const config = PayPalCommerceGatewayClearDb; + if ( ! typeof config ) { + return; + } - const clearDbConfig = config.clearDb; + const clearDbConfig = config.clearDb; - document.querySelector(clearDbConfig.button)?.addEventListener('click', function () { - const isConfirmed = confirm(clearDbConfig.confirmationMessage); - if (!isConfirmed) { - return; - } + document + .querySelector( clearDbConfig.button ) + ?.addEventListener( 'click', function () { + const isConfirmed = confirm( clearDbConfig.confirmationMessage ); + if ( ! isConfirmed ) { + return; + } - const clearButton = document.querySelector(clearDbConfig.button); + const clearButton = document.querySelector( clearDbConfig.button ); - clearButton.setAttribute('disabled', 'disabled'); - fetch(clearDbConfig.endpoint, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - nonce: clearDbConfig.nonce, - }) - }).then((res)=>{ - return res.json(); - }).then((data)=>{ - if (!data.success) { - jQuery(clearDbConfig.failureMessage).insertAfter(clearButton); - setTimeout(()=> jQuery(clearDbConfig.messageSelector).remove(),3000); - clearButton.removeAttribute('disabled'); - throw Error(data.data.message); - } + clearButton.setAttribute( 'disabled', 'disabled' ); + fetch( clearDbConfig.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: clearDbConfig.nonce, + } ), + } ) + .then( ( res ) => { + return res.json(); + } ) + .then( ( data ) => { + if ( ! data.success ) { + jQuery( clearDbConfig.failureMessage ).insertAfter( + clearButton + ); + setTimeout( + () => + jQuery( + clearDbConfig.messageSelector + ).remove(), + 3000 + ); + clearButton.removeAttribute( 'disabled' ); + throw Error( data.data.message ); + } - jQuery(clearDbConfig.successMessage).insertAfter(clearButton); - setTimeout(()=> jQuery(clearDbConfig.messageSelector).remove(),3000); - clearButton.removeAttribute('disabled'); - window.location.replace(clearDbConfig.redirectUrl); - }); - }) - }, -); + jQuery( clearDbConfig.successMessage ).insertAfter( + clearButton + ); + setTimeout( + () => jQuery( clearDbConfig.messageSelector ).remove(), + 3000 + ); + clearButton.removeAttribute( 'disabled' ); + window.location.replace( clearDbConfig.redirectUrl ); + } ); + } ); +} ); diff --git a/modules/ppcp-wc-gateway/resources/js/common.js b/modules/ppcp-wc-gateway/resources/js/common.js index 9b412ff70..5b87d0698 100644 --- a/modules/ppcp-wc-gateway/resources/js/common.js +++ b/modules/ppcp-wc-gateway/resources/js/common.js @@ -1,25 +1,21 @@ -import DisplayManager from "./common/display-manager/DisplayManager"; -import moveWrappedElements from "./common/wrapped-elements"; +import DisplayManager from './common/display-manager/DisplayManager'; +import moveWrappedElements from './common/wrapped-elements'; -document.addEventListener( - 'DOMContentLoaded', - () => { +document.addEventListener( 'DOMContentLoaded', () => { + // Wait for current execution context to end. + setTimeout( function () { + moveWrappedElements(); + }, 0 ); - // Wait for current execution context to end. - setTimeout(function () { - moveWrappedElements(); - }, 0); + // Initialize DisplayManager. + const displayManager = new DisplayManager(); - // Initialize DisplayManager. - const displayManager = new DisplayManager(); + jQuery( '*[data-ppcp-display]' ).each( ( index, el ) => { + const rules = jQuery( el ).data( 'ppcpDisplay' ); + for ( const rule of rules ) { + displayManager.addRule( rule ); + } + } ); - jQuery( '*[data-ppcp-display]' ).each( (index, el) => { - const rules = jQuery(el).data('ppcpDisplay'); - for (const rule of rules) { - displayManager.addRule(rule); - } - }); - - displayManager.register(); - } -); + displayManager.register(); +} ); diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/ActionFactory.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ActionFactory.js index caeb4ba3c..c80b4a26c 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/ActionFactory.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ActionFactory.js @@ -1,17 +1,19 @@ -import VisibilityAction from "./action/VisibilityAction"; -import AttributeAction from "./action/AttributeAction"; +import VisibilityAction from './action/VisibilityAction'; +import AttributeAction from './action/AttributeAction'; class ActionFactory { - static make(actionConfig) { - switch (actionConfig.type) { - case 'visibility': - return new VisibilityAction(actionConfig); - case 'attribute': - return new AttributeAction(actionConfig); - } + static make( actionConfig ) { + switch ( actionConfig.type ) { + case 'visibility': + return new VisibilityAction( actionConfig ); + case 'attribute': + return new AttributeAction( actionConfig ); + } - throw new Error('[ActionFactory] Unknown action: ' + actionConfig.type); - } + throw new Error( + '[ActionFactory] Unknown action: ' + actionConfig.type + ); + } } export default ActionFactory; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/ConditionFactory.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ConditionFactory.js index 928d0bd22..5d0a5717f 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/ConditionFactory.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ConditionFactory.js @@ -1,20 +1,25 @@ -import ElementCondition from "./condition/ElementCondition"; -import BoolCondition from "./condition/BoolCondition"; -import JsVariableCondition from "./condition/JsVariableCondition"; +import ElementCondition from './condition/ElementCondition'; +import BoolCondition from './condition/BoolCondition'; +import JsVariableCondition from './condition/JsVariableCondition'; class ConditionFactory { - static make(conditionConfig, triggerUpdate) { - switch (conditionConfig.type) { - case 'element': - return new ElementCondition(conditionConfig, triggerUpdate); - case 'bool': - return new BoolCondition(conditionConfig, triggerUpdate); - case 'js_variable': - return new JsVariableCondition(conditionConfig, triggerUpdate); - } + static make( conditionConfig, triggerUpdate ) { + switch ( conditionConfig.type ) { + case 'element': + return new ElementCondition( conditionConfig, triggerUpdate ); + case 'bool': + return new BoolCondition( conditionConfig, triggerUpdate ); + case 'js_variable': + return new JsVariableCondition( + conditionConfig, + triggerUpdate + ); + } - throw new Error('[ConditionFactory] Unknown condition: ' + conditionConfig.type); - } + throw new Error( + '[ConditionFactory] Unknown condition: ' + conditionConfig.type + ); + } } export default ConditionFactory; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js index 2aede73ee..6c925ecbd 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js @@ -1,32 +1,35 @@ -import Rule from "./Rule"; +import Rule from './Rule'; class DisplayManager { + constructor() { + this.rules = {}; + this.ruleStatus = {}; // The current status for each rule. Maybe not necessary, for now just for logging. - constructor() { - this.rules = {}; - this.ruleStatus = {}; // The current status for each rule. Maybe not necessary, for now just for logging. + document.ppcpDisplayManagerLog = () => { + console.log( 'DisplayManager', this ); + }; + } - document.ppcpDisplayManagerLog = () => { - console.log('DisplayManager', this); - } - } + addRule( ruleConfig ) { + const updateStatus = () => { + this.ruleStatus[ ruleConfig.key ] = + this.rules[ ruleConfig.key ].status; + //console.log('ruleStatus', this.ruleStatus); + }; - addRule(ruleConfig) { - const updateStatus = () => { - this.ruleStatus[ruleConfig.key] = this.rules[ruleConfig.key].status; - //console.log('ruleStatus', this.ruleStatus); - } + this.rules[ ruleConfig.key ] = new Rule( + ruleConfig, + updateStatus.bind( this ) + ); + //console.log('Rule', this.rules[ruleConfig.key]); + } - this.rules[ruleConfig.key] = new Rule(ruleConfig, updateStatus.bind(this)); - //console.log('Rule', this.rules[ruleConfig.key]); - } - - register() { - this.ruleStatus = {}; - for (const [key, rule] of Object.entries(this.rules)) { - rule.register(); - } - } + register() { + this.ruleStatus = {}; + for ( const [ key, rule ] of Object.entries( this.rules ) ) { + rule.register(); + } + } } export default DisplayManager; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js index 20581b025..a3797a83b 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js @@ -1,68 +1,69 @@ -import ConditionFactory from "./ConditionFactory"; -import ActionFactory from "./ActionFactory"; +import ConditionFactory from './ConditionFactory'; +import ActionFactory from './ActionFactory'; class Rule { + constructor( config, triggerUpdate ) { + this.config = config; + this.conditions = {}; + this.actions = {}; + this.triggerUpdate = triggerUpdate; + this.status = null; - constructor(config, triggerUpdate) { - this.config = config; - this.conditions = {}; - this.actions = {}; - this.triggerUpdate = triggerUpdate; - this.status = null; + const updateStatus = this.updateStatus.bind( this ); + for ( const conditionConfig of this.config.conditions ) { + const condition = ConditionFactory.make( + conditionConfig, + updateStatus + ); + this.conditions[ condition.key ] = condition; - const updateStatus = this.updateStatus.bind(this); - for (const conditionConfig of this.config.conditions) { - const condition = ConditionFactory.make(conditionConfig, updateStatus); - this.conditions[condition.key] = condition; + //console.log('Condition', condition); + } - //console.log('Condition', condition); - } + for ( const actionConfig of this.config.actions ) { + const action = ActionFactory.make( actionConfig ); + this.actions[ action.key ] = action; - for (const actionConfig of this.config.actions) { - const action = ActionFactory.make(actionConfig); - this.actions[action.key] = action; + //console.log('Action', action); + } + } - //console.log('Action', action); - } - } + get key() { + return this.config.key; + } - get key() { - return this.config.key; - } + updateStatus( forceRunActions = false ) { + let status = true; - updateStatus(forceRunActions = false) { - let status = true; + for ( const [ key, condition ] of Object.entries( this.conditions ) ) { + status &= condition.status; + } - for (const [key, condition] of Object.entries(this.conditions)) { - status &= condition.status; - } + if ( status !== this.status ) { + this.status = status; + this.triggerUpdate(); + this.runActions(); + } else if ( forceRunActions ) { + this.runActions(); + } + } - if (status !== this.status) { - this.status = status; - this.triggerUpdate(); - this.runActions(); - } else if (forceRunActions) { - this.runActions(); - } - } + runActions() { + for ( const [ key, action ] of Object.entries( this.actions ) ) { + action.run( this.status ); + } + } - runActions() { - for (const [key, action] of Object.entries(this.actions)) { - action.run(this.status); - } - } - - register() { - for (const [key, condition] of Object.entries(this.conditions)) { - condition.register(this.updateStatus.bind(this)); - } - for (const [key, action] of Object.entries(this.actions)) { - action.register(); - } - - this.updateStatus(true); - } + register() { + for ( const [ key, condition ] of Object.entries( this.conditions ) ) { + condition.register( this.updateStatus.bind( this ) ); + } + for ( const [ key, action ] of Object.entries( this.actions ) ) { + action.register(); + } + this.updateStatus( true ); + } } export default Rule; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/AttributeAction.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/AttributeAction.js index 82a6a114a..44199794a 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/AttributeAction.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/AttributeAction.js @@ -1,17 +1,15 @@ -import BaseAction from "./BaseAction"; +import BaseAction from './BaseAction'; class AttributeAction extends BaseAction { - - run(status) { - - if (status) { - jQuery(this.config.selector).addClass(this.config.html_class); - } else { - jQuery(this.config.selector).removeClass(this.config.html_class); - } - - } - + run( status ) { + if ( status ) { + jQuery( this.config.selector ).addClass( this.config.html_class ); + } else { + jQuery( this.config.selector ).removeClass( + this.config.html_class + ); + } + } } export default AttributeAction; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/BaseAction.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/BaseAction.js index 13b07df4f..519d0bb87 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/BaseAction.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/BaseAction.js @@ -1,21 +1,19 @@ - class BaseAction { + constructor( config ) { + this.config = config; + } - constructor(config) { - this.config = config; - } + get key() { + return this.config.key; + } - get key() { - return this.config.key; - } + register() { + // To override. + } - register() { - // To override. - } - - run(status) { - // To override. - } + run( status ) { + // To override. + } } export default BaseAction; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/VisibilityAction.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/VisibilityAction.js index 32bc1fb70..f25033966 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/VisibilityAction.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/VisibilityAction.js @@ -1,35 +1,35 @@ -import BaseAction from "./BaseAction"; +import BaseAction from './BaseAction'; class VisibilityAction extends BaseAction { - - run(status) { - - if (status) { - if (this.config.action === 'visible') { - jQuery(this.config.selector).removeClass('ppcp-field-hidden'); - } - if (this.config.action === 'enable') { - jQuery(this.config.selector).removeClass('ppcp-field-disabled') - .off('mouseup') - .find('> *') - .css('pointer-events', ''); - } - } else { - if (this.config.action === 'visible') { - jQuery(this.config.selector).addClass('ppcp-field-hidden'); - } - if (this.config.action === 'enable') { - jQuery(this.config.selector).addClass('ppcp-field-disabled') - .on('mouseup', function(event) { - event.stopImmediatePropagation(); - }) - .find('> *') - .css('pointer-events', 'none'); - } - } - - } - + run( status ) { + if ( status ) { + if ( this.config.action === 'visible' ) { + jQuery( this.config.selector ).removeClass( + 'ppcp-field-hidden' + ); + } + if ( this.config.action === 'enable' ) { + jQuery( this.config.selector ) + .removeClass( 'ppcp-field-disabled' ) + .off( 'mouseup' ) + .find( '> *' ) + .css( 'pointer-events', '' ); + } + } else { + if ( this.config.action === 'visible' ) { + jQuery( this.config.selector ).addClass( 'ppcp-field-hidden' ); + } + if ( this.config.action === 'enable' ) { + jQuery( this.config.selector ) + .addClass( 'ppcp-field-disabled' ) + .on( 'mouseup', function ( event ) { + event.stopImmediatePropagation(); + } ) + .find( '> *' ) + .css( 'pointer-events', 'none' ); + } + } + } } export default VisibilityAction; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BaseCondition.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BaseCondition.js index 1d126c128..09ebc5bea 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BaseCondition.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BaseCondition.js @@ -1,19 +1,17 @@ - class BaseCondition { + constructor( config, triggerUpdate ) { + this.config = config; + this.status = false; + this.triggerUpdate = triggerUpdate; + } - constructor(config, triggerUpdate) { - this.config = config; - this.status = false; - this.triggerUpdate = triggerUpdate; - } + get key() { + return this.config.key; + } - get key() { - return this.config.key; - } - - register() { - // To override. - } + register() { + // To override. + } } export default BaseCondition; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BoolCondition.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BoolCondition.js index 27699d2ca..27c079559 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BoolCondition.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BoolCondition.js @@ -1,15 +1,13 @@ -import BaseCondition from "./BaseCondition"; +import BaseCondition from './BaseCondition'; class BoolCondition extends BaseCondition { + register() { + this.status = this.check(); + } - register() { - this.status = this.check(); - } - - check() { - return !! this.config.value; - } - + check() { + return !! this.config.value; + } } export default BoolCondition; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/ElementCondition.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/ElementCondition.js index efb0b4613..4c093e82d 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/ElementCondition.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/ElementCondition.js @@ -1,27 +1,25 @@ -import BaseCondition from "./BaseCondition"; -import {inputValue} from "../../../helper/form"; +import BaseCondition from './BaseCondition'; +import { inputValue } from '../../../helper/form'; class ElementCondition extends BaseCondition { + register() { + jQuery( document ).on( 'change', this.config.selector, () => { + const status = this.check(); + if ( status !== this.status ) { + this.status = status; + this.triggerUpdate(); + } + } ); - register() { - jQuery(document).on('change', this.config.selector, () => { - const status = this.check(); - if (status !== this.status) { - this.status = status; - this.triggerUpdate(); - } - }); + this.status = this.check(); + } - this.status = this.check(); - } - - check() { - let value = inputValue(this.config.selector); - value = (value !== null ? value.toString() : value); - - return this.config.value === value; - } + check() { + let value = inputValue( this.config.selector ); + value = value !== null ? value.toString() : value; + return this.config.value === value; + } } export default ElementCondition; diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/JsVariableCondition.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/JsVariableCondition.js index 0b51a1f0b..114ec320d 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/JsVariableCondition.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/JsVariableCondition.js @@ -1,24 +1,22 @@ -import BaseCondition from "./BaseCondition"; +import BaseCondition from './BaseCondition'; class JsVariableCondition extends BaseCondition { + register() { + jQuery( document ).on( 'ppcp-display-change', () => { + const status = this.check(); + if ( status !== this.status ) { + this.status = status; + this.triggerUpdate(); + } + } ); - register() { - jQuery(document).on('ppcp-display-change', () => { - const status = this.check(); - if (status !== this.status) { - this.status = status; - this.triggerUpdate(); - } - }); - - this.status = this.check(); - } - - check() { - let value = document[this.config.variable]; - return this.config.value === value; - } + this.status = this.check(); + } + check() { + const value = document[ this.config.variable ]; + return this.config.value === value; + } } export default JsVariableCondition; diff --git a/modules/ppcp-wc-gateway/resources/js/common/wrapped-elements.js b/modules/ppcp-wc-gateway/resources/js/common/wrapped-elements.js index 827e50586..b49594ceb 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/wrapped-elements.js +++ b/modules/ppcp-wc-gateway/resources/js/common/wrapped-elements.js @@ -1,14 +1,13 @@ - // This function is needed because WordPress moves our custom notices to the global placeholder. function moveWrappedElements() { - (($) => { - $('*[data-ppcp-wrapper]').each(function() { - let $wrapper = $('.' + $(this).data('ppcpWrapper')); - if ($wrapper.length) { - $wrapper.append(this); - } - }); - })(jQuery) + ( ( $ ) => { + $( '*[data-ppcp-wrapper]' ).each( function () { + const $wrapper = $( '.' + $( this ).data( 'ppcpWrapper' ) ); + if ( $wrapper.length ) { + $wrapper.append( this ); + } + } ); + } )( jQuery ); } export default moveWrappedElements; diff --git a/modules/ppcp-wc-gateway/resources/js/fraudnet.js b/modules/ppcp-wc-gateway/resources/js/fraudnet.js index dcf30dc03..87cd68416 100644 --- a/modules/ppcp-wc-gateway/resources/js/fraudnet.js +++ b/modules/ppcp-wc-gateway/resources/js/fraudnet.js @@ -1,55 +1,64 @@ -window.addEventListener('load', function() { +window.addEventListener( 'load', function () { + function _loadBeaconJS( options ) { + const script = document.createElement( 'script' ); + script.src = options.fnUrl; + document.body.appendChild( script ); + } - function _loadBeaconJS(options) { - var script = document.createElement('script'); - script.src = options.fnUrl; - document.body.appendChild(script); - } + function _injectConfig() { + let script = document.querySelector( + "[fncls='fnparams-dede7cc5-15fd-4c75-a9f4-36c430ee3a99']" + ); + if ( script ) { + if ( script.parentNode ) { + script.parentNode.removeChild( script ); + } + } - function _injectConfig() { - var script = document.querySelector("[fncls='fnparams-dede7cc5-15fd-4c75-a9f4-36c430ee3a99']"); - if (script) { - if (script.parentNode) { - script.parentNode.removeChild(script); - } - } + script = document.createElement( 'script' ); + script.id = 'fconfig'; + script.type = 'application/json'; + script.setAttribute( + 'fncls', + 'fnparams-dede7cc5-15fd-4c75-a9f4-36c430ee3a99' + ); - script = document.createElement('script'); - script.id = 'fconfig'; - script.type = 'application/json'; - script.setAttribute('fncls', 'fnparams-dede7cc5-15fd-4c75-a9f4-36c430ee3a99'); + const configuration = { + f: FraudNetConfig.f, + s: FraudNetConfig.s, + }; + if ( FraudNetConfig.sandbox === '1' ) { + configuration.sandbox = true; + } - var configuration = { - 'f': FraudNetConfig.f, - 's': FraudNetConfig.s - }; - if(FraudNetConfig.sandbox === '1') { - configuration.sandbox = true; - } + script.text = JSON.stringify( configuration ); + document.body.appendChild( script ); - script.text = JSON.stringify(configuration); - document.body.appendChild(script); + const payForOrderForm = document.forms.order_review; + if ( payForOrderForm ) { + const puiPayForOrderSessionId = document.createElement( 'input' ); + puiPayForOrderSessionId.setAttribute( 'type', 'hidden' ); + puiPayForOrderSessionId.setAttribute( + 'name', + 'pui_pay_for_order_session_id' + ); + puiPayForOrderSessionId.setAttribute( 'value', FraudNetConfig.f ); + payForOrderForm.appendChild( puiPayForOrderSessionId ); + } - const payForOrderForm = document.forms.order_review; - if(payForOrderForm) { - const puiPayForOrderSessionId = document.createElement('input'); - puiPayForOrderSessionId.setAttribute('type', 'hidden'); - puiPayForOrderSessionId.setAttribute('name', 'pui_pay_for_order_session_id'); - puiPayForOrderSessionId.setAttribute('value', FraudNetConfig.f); - payForOrderForm.appendChild(puiPayForOrderSessionId); - } + _loadBeaconJS( { fnUrl: 'https://c.paypal.com/da/r/fb.js' } ); + } - _loadBeaconJS({fnUrl: "https://c.paypal.com/da/r/fb.js"}) - } + document.addEventListener( 'hosted_fields_loaded', ( event ) => { + if ( + PAYPAL.asyncData && + typeof PAYPAL.asyncData.initAndCollect === 'function' + ) { + PAYPAL.asyncData.initAndCollect(); + } - document.addEventListener('hosted_fields_loaded', (event) => { - if (PAYPAL.asyncData && typeof PAYPAL.asyncData.initAndCollect === 'function') { - PAYPAL.asyncData.initAndCollect() - } - - _injectConfig(); - }); - - _injectConfig(); -}) + _injectConfig(); + } ); + _injectConfig(); +} ); diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 9257bd86d..82152b2ad 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -1,450 +1,611 @@ -import { loadScript } from "@paypal/paypal-js"; -import {debounce} from "./helper/debounce"; -import { buttonRefreshTriggerFactory, buttonSettingsGetterFactory } from './helper/preview-button'; -import Renderer from '../../../ppcp-button/resources/js/modules/Renderer/Renderer' -import MessageRenderer from "../../../ppcp-button/resources/js/modules/Renderer/MessageRenderer"; -import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/modules/Helper/Hiding"; -import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; - -document.addEventListener( - 'DOMContentLoaded', - () => { - function disableAll(nodeList){ - nodeList.forEach(node => node.setAttribute('disabled', 'true')) - } - - const disabledCheckboxes = document.querySelectorAll( - '.ppcp-disabled-checkbox' - ) - - disableAll( disabledCheckboxes ) - - const form = jQuery('#mainform'); - - const payLaterButtonInput = document.querySelector('#ppcp-pay_later_button_enabled'); - - initializePayLaterPreview() - - const separateCardButtonCheckbox = document.querySelector('#ppcp-allow_card_button_gateway'); - if (separateCardButtonCheckbox) { - separateCardButtonCheckbox.addEventListener('change', () => { - setVisibleByClass('#field-button_layout', !separateCardButtonCheckbox.checked, 'hide'); - setVisibleByClass('#field-button_general_layout', !separateCardButtonCheckbox.checked, 'hide'); - }); - } - - [ - {layoutSelector: '#ppcp-button_layout', taglineSelector: '#field-button_tagline', canHaveSeparateButtons: true}, - {layoutSelector: '#ppcp-button_general_layout', taglineSelector: '#field-button_general_tagline', canHaveSeparateButtons: true}, - {layoutSelector: '#ppcp-button_product_layout', taglineSelector: '#field-button_product_tagline'}, - {layoutSelector: '#ppcp-button_cart_layout', taglineSelector: '#field-button_cart_tagline'}, - {layoutSelector: '#ppcp-button_mini-cart_layout', taglineSelector: '#field-button_mini-cart_tagline'}, - ].forEach(location => { - const layoutSelect = document.querySelector(location.layoutSelector); - const taglineField = document.querySelector(location.taglineSelector); - if (layoutSelect && taglineField) { - const setTaglineFieldVisibility = () => { - const supportsTagline = jQuery(layoutSelect).val() === 'horizontal' - && (!location.canHaveSeparateButtons || (separateCardButtonCheckbox && !separateCardButtonCheckbox.checked)) - && isVisible(layoutSelect.parentElement); - setVisibleByClass(taglineField, supportsTagline, 'hide'); - }; - setTaglineFieldVisibility(); - // looks like only jQuery event fires for WC selects - jQuery(layoutSelect).change(setTaglineFieldVisibility); - if (location.canHaveSeparateButtons && separateCardButtonCheckbox) { - separateCardButtonCheckbox.addEventListener('change', setTaglineFieldVisibility); - } - } - }); - - function initializePayLaterPreview() { - if (!payLaterButtonInput) { - return; - } - - const payLaterButtonPreview = document.querySelector( - '.ppcp-button-preview[data-ppcp-preview-block="paylater"]'); - - if (!payLaterButtonPreview) { - return; - } - - if (!payLaterButtonInput.checked) { - payLaterButtonPreview.classList.add('disabled'); - } - - if (payLaterButtonInput.classList.contains('ppcp-disabled-checkbox')) { - payLaterButtonPreview.style.display = 'none'; - } - - payLaterButtonInput.addEventListener('click', () => { - payLaterButtonPreview.classList.remove('disabled'); - - if (!payLaterButtonInput.checked) { - payLaterButtonPreview.classList.add('disabled'); - } - }); - } - - function createButtonPreview(settingsCallback) { - const render = (settings) => { - const wrapperSelector = Object.values(settings.separate_buttons).length > 0 ? Object.values(settings.separate_buttons)[0].wrapper : settings.button.wrapper; - const wrapper = document.querySelector(wrapperSelector); - if (!wrapper) { - return; - } - wrapper.innerHTML = ''; - - const renderer = new Renderer(null, settings, (data, actions) => actions.reject(), null); - - try { - renderer.render({}); - jQuery(document).trigger('ppcp_paypal_render_preview', settings); - } catch (err) { - console.error(err); - } - }; - - renderPreview(settingsCallback, render); - } - - function currentTabId() { - const params = new URLSearchParams(location.search); - return params.has('ppcp-tab') ? params.get('ppcp-tab') : params.get('section'); - } - - function shouldShowPayLaterButton() { - const payLaterButtonLocations = document.querySelector('[name="ppcp[pay_later_button_locations][]"]'); - - if(!payLaterButtonInput || !payLaterButtonLocations) { - return PayPalCommerceGatewaySettings.is_pay_later_button_enabled - } - - return payLaterButtonInput.checked && payLaterButtonLocations.selectedOptions.length > 0 - } - - function shouldDisableCardButton() { - return PayPalCommerceGatewaySettings.is_acdc_enabled || jQuery('#ppcp-allow_card_button_gateway').is(':checked'); - } - - function getPaypalScriptSettings() { - const disableFundingInput = jQuery('[name="ppcp[disable_funding][]"]'); - let disabledSources = disableFundingInput.length > 0 ? disableFundingInput.val() : PayPalCommerceGatewaySettings.disabled_sources; - const payLaterButtonPreview = jQuery('#ppcpPayLaterButtonPreview'); - const settings = { - 'client-id': PayPalCommerceGatewaySettings.client_id, - 'currency': PayPalCommerceGatewaySettings.currency, - 'integration-date': PayPalCommerceGatewaySettings.integration_date, - 'components': PayPalCommerceGatewaySettings.components, - 'enable-funding': ['venmo', 'paylater'] - }; - - if (PayPalCommerceGatewaySettings.environment === 'sandbox') { - settings['buyer-country'] = PayPalCommerceGatewaySettings.country; - } - - if (payLaterButtonPreview?.length) { - disabledSources = Object.keys(PayPalCommerceGatewaySettings.all_funding_sources); - } - - if (!shouldShowPayLaterButton()) { - disabledSources = disabledSources.concat('credit') - } - - if (shouldDisableCardButton()) { - const standardCardButtonInput = document.querySelector('#woocommerce_ppcp-card-button-gateway_enabled'); - if (standardCardButtonInput) { - standardCardButtonInput.disabled = true; - } - - disabledSources = disabledSources.concat('card'); - } - - if (disabledSources?.length) { - settings['disable-funding'] = disabledSources; - } - - const smartButtonLocale = document.getElementById('ppcp-smart_button_language'); - if (smartButtonLocale?.length > 0 && smartButtonLocale?.value !== '') { - settings['locale'] = smartButtonLocale.value; - } - - return settings; - } - - function loadPaypalScript(settings, onLoaded = () => {}) { - loadScript(JSON.parse(JSON.stringify(settings))) // clone the object to prevent modification - .then(paypal => { - widgetBuilder.setPaypal(paypal); - - document.dispatchEvent(new CustomEvent('ppcp_paypal_script_loaded')); - - onLoaded(paypal); - }) - .catch((error) => console.error('failed to load the PayPal JS SDK script', error)); - } - - function getButtonSettings(wrapperSelector, fields, apm = null) { - const layoutElement = jQuery(fields['layout']); - const layout = (layoutElement.length && layoutElement.is(':visible')) ? layoutElement.val() : 'vertical'; - const style = { - 'color': jQuery(fields['color']).val(), - 'shape': jQuery(fields['shape']).val(), - 'label': jQuery(fields['label']).val(), - 'tagline': layout === 'horizontal' && jQuery(fields['tagline']).is(':checked'), - 'layout': layout, - }; - if ('height' in fields) { - style['height'] = parseInt(jQuery(fields['height']).val()); - } - if ('poweredby_tagline' in fields) { - style['layout'] = jQuery(fields['poweredby_tagline']).is(':checked') ? 'vertical' : 'horizontal'; - } - const settings = { - 'button': { - 'wrapper': wrapperSelector, - 'style': style, - }, - 'separate_buttons': {}, - }; - if (apm) { - settings.separate_buttons[apm] = { - 'wrapper': wrapperSelector, - 'style': style, - }; - settings.button.wrapper = null; - } - return settings; - } - - function createMessagesPreview(settingsCallback) { - const render = (settings) => { - let wrapper = document.querySelector(settings.wrapper); - if (!wrapper) { - return; - } - // looks like .innerHTML = '' is not enough, PayPal somehow renders with old style - const parent = wrapper.parentElement; - parent.removeChild(wrapper); - wrapper = document.createElement('div'); - wrapper.setAttribute('id', settings.wrapper.replace('#', '')); - parent.appendChild(wrapper); - - const messageRenderer = new MessageRenderer(settings); - - try { - messageRenderer.renderWithAmount(settings.amount); - } catch (err) { - console.error(err); - } - }; - - renderPreview(settingsCallback, render); - } - - function getMessageSettings(wrapperSelector, fields) { - const layout = jQuery(fields['layout']).val(); - const style = { - 'layout': layout, - 'logo': { - 'type': jQuery(fields['logo_type']).val(), - 'position': jQuery(fields['logo_position']).val() - }, - 'text': { - 'color': jQuery(fields['text_color']).val() - }, - 'color': jQuery(fields['flex_color']).val(), - 'ratio': jQuery(fields['flex_ratio']).val(), - }; - - return { - 'wrapper': wrapperSelector, - 'style': style, - 'amount': 30, - 'placement': 'product', - }; - } - - function renderPreview(settingsCallback, render) { - let oldSettings = settingsCallback(); - - form.on('change', ':input', debounce(() => { - const newSettings = settingsCallback(); - if (JSON.stringify(oldSettings) === JSON.stringify(newSettings)) { - return; - } - - render(newSettings); - - oldSettings = newSettings; - }, 300)); - - jQuery(document).on('ppcp_paypal_script_loaded', () => { - oldSettings = settingsCallback(); - - render(oldSettings); - }); - - render(oldSettings); - } - - function getButtonDefaultSettings(wrapperSelector) { - const style = { - 'color': 'gold', - 'shape': 'pill', - 'label': 'paypal', - 'tagline': false, - 'layout': 'vertical', - }; - return { - 'button': { - 'wrapper': wrapperSelector, - 'style': style, - }, - 'separate_buttons': {}, - }; - } - - const previewElements = document.querySelectorAll('.ppcp-preview'); - if (previewElements.length) { - let oldScriptSettings = getPaypalScriptSettings(); - - form.on('change', ':input', debounce(() => { - const newSettings = getPaypalScriptSettings(); - if (JSON.stringify(oldScriptSettings) === JSON.stringify(newSettings)) { - return; - } - - loadPaypalScript(newSettings); - - oldScriptSettings = newSettings; - }, 1000)); - - loadPaypalScript(oldScriptSettings, () => { - const payLaterMessagingLocations = ['product', 'cart', 'checkout', 'shop', 'home', 'general']; - const paypalButtonLocations = ['product', 'cart', 'checkout', 'mini-cart', 'cart-block', 'checkout-block-express', 'general']; - - // Default preview buttons; on "Standard Payments" tab. - paypalButtonLocations.forEach((location) => { - const inputNamePrefix = location === 'checkout' ? '#ppcp-button' : '#ppcp-button_' + location; - const wrapperName = location.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(''); - const fields = { - 'color': inputNamePrefix + '_color', - 'shape': inputNamePrefix + '_shape', - 'label': inputNamePrefix + '_label', - 'tagline': inputNamePrefix + '_tagline', - 'layout': inputNamePrefix + '_layout', - } - - if (document.querySelector(inputNamePrefix + '_height')) { - fields['height'] = inputNamePrefix + '_height'; - } - - createButtonPreview(() => getButtonSettings('#ppcp' + wrapperName + 'ButtonPreview', fields)); - }); - - /** - * Inspect DOM to find APM button previews; on tabs like "Advanced Card Payments". - * - * How it works: - * - * 1. Add a
to hold the preview button to the settings page: - * - `id="ppcp[NAME]ButtonPreview"` - * - `data-ppc-apm-preview="[NAME]"` - * 2. Mark all fields that are relevant for the preview button: - * - custom_attribute: `data-ppcp-apm-name="[NAME]"` - * - custom_attribute: `data-ppcp-field-name="[FIELD]"` - * - * This block will find all marked input fields and trigger a re-render of the - * preview button when one of those fields value changes. - * - * Example: See the ppcp-google-pay "extensions.php" file. - */ - document.querySelectorAll('[data-ppcp-preview-block]').forEach(item => { - const apmName = item.dataset.ppcpPreviewBlock; - const getSettings = buttonSettingsGetterFactory(apmName) - const renderButtonPreview = buttonRefreshTriggerFactory(apmName); - - renderPreview(getSettings, renderButtonPreview) - }); - - payLaterMessagingLocations.forEach((location) => { - const inputNamePrefix = '#ppcp-pay_later_' + location + '_message'; - const wrapperName = location.charAt(0).toUpperCase() + location.slice(1); - createMessagesPreview(() => getMessageSettings('#ppcp' + wrapperName + 'MessagePreview', { - 'layout': inputNamePrefix + '_layout', - 'logo_type': inputNamePrefix + '_logo', - 'logo_position': inputNamePrefix + '_position', - 'text_color': inputNamePrefix + '_color', - 'flex_color': inputNamePrefix + '_flex_color', - 'flex_ratio': inputNamePrefix + '_flex_ratio', - })); - }); - - createButtonPreview(() => getButtonDefaultSettings('#ppcpPayLaterButtonPreview')); - - const apmFieldPrefix = '#ppcp-card_button_'; - createButtonPreview(() => getButtonSettings('#ppcpCardButtonPreview', { - 'color': apmFieldPrefix + 'color', - 'shape': apmFieldPrefix + 'shape', - 'poweredby_tagline': apmFieldPrefix + 'poweredby_tagline', - }, 'card')); - }); - } - - // Logic to handle the "Check available features" button. - (($, props, anchor) => { - const $btn = $(props.button); - - const printStatus = (message, showSpinner) => { - const html = message + (showSpinner ? '' : ''); - $btn.siblings('.ppcp-status-text').html(html); - }; - - // If the reload comes from a successful refresh. - if (typeof URLSearchParams === 'function' && (new URLSearchParams(window.location.search)).get('feature-refreshed')) { - printStatus('✔️ ' + props.messages.success + ''); - $('html, body').animate({ - scrollTop: $(anchor).offset().top - }, 500); - } - - $btn.click(async () => { - $btn.prop('disabled', true); - printStatus(props.messages.waiting, true); - - const response = await fetch( - props.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json' - }, - body: JSON.stringify( - { - nonce: props.nonce, - } - ) - } - ); - - const responseData = await response.json(); - - if (!responseData.success) { - printStatus(responseData.data.message); - $btn.prop('disabled', false); - } else { - window.location.href += (window.location.href.indexOf('?') > -1 ? '&' : '?') + "feature-refreshed=1#"; - } - }); - - })( - jQuery, - PayPalCommerceGatewaySettings.ajax.refresh_feature_status, - '#field-credentials_feature_onboarding_heading' - ); - - } -); +import { loadScript } from '@paypal/paypal-js'; +import { debounce } from './helper/debounce'; +import { + buttonRefreshTriggerFactory, + buttonSettingsGetterFactory, +} from './helper/preview-button'; +import Renderer from '../../../ppcp-button/resources/js/modules/Renderer/Renderer'; +import MessageRenderer from '../../../ppcp-button/resources/js/modules/Renderer/MessageRenderer'; +import { + setVisibleByClass, + isVisible, +} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; +import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; + +document.addEventListener( 'DOMContentLoaded', () => { + function disableAll( nodeList ) { + nodeList.forEach( ( node ) => node.setAttribute( 'disabled', 'true' ) ); + } + + const disabledCheckboxes = document.querySelectorAll( + '.ppcp-disabled-checkbox' + ); + + disableAll( disabledCheckboxes ); + + const form = jQuery( '#mainform' ); + + const payLaterButtonInput = document.querySelector( + '#ppcp-pay_later_button_enabled' + ); + + initializePayLaterPreview(); + + const separateCardButtonCheckbox = document.querySelector( + '#ppcp-allow_card_button_gateway' + ); + if ( separateCardButtonCheckbox ) { + separateCardButtonCheckbox.addEventListener( 'change', () => { + setVisibleByClass( + '#field-button_layout', + ! separateCardButtonCheckbox.checked, + 'hide' + ); + setVisibleByClass( + '#field-button_general_layout', + ! separateCardButtonCheckbox.checked, + 'hide' + ); + } ); + } + + [ + { + layoutSelector: '#ppcp-button_layout', + taglineSelector: '#field-button_tagline', + canHaveSeparateButtons: true, + }, + { + layoutSelector: '#ppcp-button_general_layout', + taglineSelector: '#field-button_general_tagline', + canHaveSeparateButtons: true, + }, + { + layoutSelector: '#ppcp-button_product_layout', + taglineSelector: '#field-button_product_tagline', + }, + { + layoutSelector: '#ppcp-button_cart_layout', + taglineSelector: '#field-button_cart_tagline', + }, + { + layoutSelector: '#ppcp-button_mini-cart_layout', + taglineSelector: '#field-button_mini-cart_tagline', + }, + ].forEach( ( location ) => { + const layoutSelect = document.querySelector( location.layoutSelector ); + const taglineField = document.querySelector( location.taglineSelector ); + if ( layoutSelect && taglineField ) { + const setTaglineFieldVisibility = () => { + const supportsTagline = + jQuery( layoutSelect ).val() === 'horizontal' && + ( ! location.canHaveSeparateButtons || + ( separateCardButtonCheckbox && + ! separateCardButtonCheckbox.checked ) ) && + isVisible( layoutSelect.parentElement ); + setVisibleByClass( taglineField, supportsTagline, 'hide' ); + }; + setTaglineFieldVisibility(); + // looks like only jQuery event fires for WC selects + jQuery( layoutSelect ).change( setTaglineFieldVisibility ); + if ( + location.canHaveSeparateButtons && + separateCardButtonCheckbox + ) { + separateCardButtonCheckbox.addEventListener( + 'change', + setTaglineFieldVisibility + ); + } + } + } ); + + function initializePayLaterPreview() { + if ( ! payLaterButtonInput ) { + return; + } + + const payLaterButtonPreview = document.querySelector( + '.ppcp-button-preview[data-ppcp-preview-block="paylater"]' + ); + + if ( ! payLaterButtonPreview ) { + return; + } + + if ( ! payLaterButtonInput.checked ) { + payLaterButtonPreview.classList.add( 'disabled' ); + } + + if ( + payLaterButtonInput.classList.contains( 'ppcp-disabled-checkbox' ) + ) { + payLaterButtonPreview.style.display = 'none'; + } + + payLaterButtonInput.addEventListener( 'click', () => { + payLaterButtonPreview.classList.remove( 'disabled' ); + + if ( ! payLaterButtonInput.checked ) { + payLaterButtonPreview.classList.add( 'disabled' ); + } + } ); + } + + function createButtonPreview( settingsCallback ) { + const render = ( settings ) => { + const wrapperSelector = + Object.values( settings.separate_buttons ).length > 0 + ? Object.values( settings.separate_buttons )[ 0 ].wrapper + : settings.button.wrapper; + const wrapper = document.querySelector( wrapperSelector ); + if ( ! wrapper ) { + return; + } + wrapper.innerHTML = ''; + + const renderer = new Renderer( + null, + settings, + ( data, actions ) => actions.reject(), + null + ); + + try { + renderer.render( {} ); + jQuery( document ).trigger( + 'ppcp_paypal_render_preview', + settings + ); + } catch ( err ) { + console.error( err ); + } + }; + + renderPreview( settingsCallback, render ); + } + + function currentTabId() { + const params = new URLSearchParams( location.search ); + return params.has( 'ppcp-tab' ) + ? params.get( 'ppcp-tab' ) + : params.get( 'section' ); + } + + function shouldShowPayLaterButton() { + const payLaterButtonLocations = document.querySelector( + '[name="ppcp[pay_later_button_locations][]"]' + ); + + if ( ! payLaterButtonInput || ! payLaterButtonLocations ) { + return PayPalCommerceGatewaySettings.is_pay_later_button_enabled; + } + + return ( + payLaterButtonInput.checked && + payLaterButtonLocations.selectedOptions.length > 0 + ); + } + + function shouldDisableCardButton() { + return ( + PayPalCommerceGatewaySettings.is_acdc_enabled || + jQuery( '#ppcp-allow_card_button_gateway' ).is( ':checked' ) + ); + } + + function getPaypalScriptSettings() { + const disableFundingInput = jQuery( + '[name="ppcp[disable_funding][]"]' + ); + let disabledSources = + disableFundingInput.length > 0 + ? disableFundingInput.val() + : PayPalCommerceGatewaySettings.disabled_sources; + const payLaterButtonPreview = jQuery( '#ppcpPayLaterButtonPreview' ); + const settings = { + 'client-id': PayPalCommerceGatewaySettings.client_id, + currency: PayPalCommerceGatewaySettings.currency, + 'integration-date': PayPalCommerceGatewaySettings.integration_date, + components: PayPalCommerceGatewaySettings.components, + 'enable-funding': [ 'venmo', 'paylater' ], + }; + + if ( PayPalCommerceGatewaySettings.environment === 'sandbox' ) { + settings[ 'buyer-country' ] = PayPalCommerceGatewaySettings.country; + } + + if ( payLaterButtonPreview?.length ) { + disabledSources = Object.keys( + PayPalCommerceGatewaySettings.all_funding_sources + ); + } + + if ( ! shouldShowPayLaterButton() ) { + disabledSources = disabledSources.concat( 'credit' ); + } + + if ( shouldDisableCardButton() ) { + const standardCardButtonInput = document.querySelector( + '#woocommerce_ppcp-card-button-gateway_enabled' + ); + + if ( standardCardButtonInput ) { + standardCardButtonInput.disabled = true; + } + + disabledSources = disabledSources.concat( 'card' ); + } + + if ( disabledSources?.length ) { + settings[ 'disable-funding' ] = disabledSources; + } + + const smartButtonLocale = document.getElementById( + 'ppcp-smart_button_language' + ); + if ( + smartButtonLocale?.length > 0 && + smartButtonLocale?.value !== '' + ) { + settings.locale = smartButtonLocale.value; + } + + return settings; + } + + function loadPaypalScript( settings, onLoaded = () => {} ) { + loadScript( JSON.parse( JSON.stringify( settings ) ) ) // clone the object to prevent modification + .then( ( paypal ) => { + widgetBuilder.setPaypal( paypal ); + + document.dispatchEvent( + new CustomEvent( 'ppcp_paypal_script_loaded' ) + ); + + onLoaded( paypal ); + } ) + .catch( ( error ) => + console.error( + 'failed to load the PayPal JS SDK script', + error + ) + ); + } + + function getButtonSettings( wrapperSelector, fields, apm = null ) { + const layoutElement = jQuery( fields.layout ); + const layout = + layoutElement.length && layoutElement.is( ':visible' ) + ? layoutElement.val() + : 'vertical'; + const style = { + color: jQuery( fields.color ).val(), + shape: jQuery( fields.shape ).val(), + label: jQuery( fields.label ).val(), + tagline: + layout === 'horizontal' && + jQuery( fields.tagline ).is( ':checked' ), + layout, + }; + if ( 'height' in fields ) { + style.height = parseInt( jQuery( fields.height ).val() ); + } + if ( 'poweredby_tagline' in fields ) { + style.layout = jQuery( fields.poweredby_tagline ).is( ':checked' ) + ? 'vertical' + : 'horizontal'; + } + const settings = { + button: { + wrapper: wrapperSelector, + style, + }, + separate_buttons: {}, + }; + if ( apm ) { + settings.separate_buttons[ apm ] = { + wrapper: wrapperSelector, + style, + }; + settings.button.wrapper = null; + } + return settings; + } + + function createMessagesPreview( settingsCallback ) { + const render = ( settings ) => { + let wrapper = document.querySelector( settings.wrapper ); + if ( ! wrapper ) { + return; + } + // looks like .innerHTML = '' is not enough, PayPal somehow renders with old style + const parent = wrapper.parentElement; + parent.removeChild( wrapper ); + wrapper = document.createElement( 'div' ); + wrapper.setAttribute( 'id', settings.wrapper.replace( '#', '' ) ); + parent.appendChild( wrapper ); + + const messageRenderer = new MessageRenderer( settings ); + + try { + messageRenderer.renderWithAmount( settings.amount ); + } catch ( err ) { + console.error( err ); + } + }; + + renderPreview( settingsCallback, render ); + } + + function getMessageSettings( wrapperSelector, fields ) { + const layout = jQuery( fields.layout ).val(); + const style = { + layout, + logo: { + type: jQuery( fields.logo_type ).val(), + position: jQuery( fields.logo_position ).val(), + }, + text: { + color: jQuery( fields.text_color ).val(), + }, + color: jQuery( fields.flex_color ).val(), + ratio: jQuery( fields.flex_ratio ).val(), + }; + + return { + wrapper: wrapperSelector, + style, + amount: 30, + placement: 'product', + }; + } + + function renderPreview( settingsCallback, render ) { + let oldSettings = settingsCallback(); + + form.on( + 'change', + ':input', + debounce( () => { + const newSettings = settingsCallback(); + if ( + JSON.stringify( oldSettings ) === + JSON.stringify( newSettings ) + ) { + return; + } + + render( newSettings ); + + oldSettings = newSettings; + }, 300 ) + ); + + jQuery( document ).on( 'ppcp_paypal_script_loaded', () => { + oldSettings = settingsCallback(); + + render( oldSettings ); + } ); + + render( oldSettings ); + } + + function getButtonDefaultSettings( wrapperSelector ) { + const style = { + color: 'gold', + shape: 'pill', + label: 'paypal', + tagline: false, + layout: 'vertical', + }; + return { + button: { + wrapper: wrapperSelector, + style, + }, + separate_buttons: {}, + }; + } + + const previewElements = document.querySelectorAll( '.ppcp-preview' ); + if ( previewElements.length ) { + let oldScriptSettings = getPaypalScriptSettings(); + + form.on( + 'change', + ':input', + debounce( () => { + const newSettings = getPaypalScriptSettings(); + if ( + JSON.stringify( oldScriptSettings ) === + JSON.stringify( newSettings ) + ) { + return; + } + + loadPaypalScript( newSettings ); + + oldScriptSettings = newSettings; + }, 1000 ) + ); + + loadPaypalScript( oldScriptSettings, () => { + const payLaterMessagingLocations = [ + 'product', + 'cart', + 'checkout', + 'shop', + 'home', + 'general', + ]; + const paypalButtonLocations = [ + 'product', + 'cart', + 'checkout', + 'mini-cart', + 'cart-block', + 'checkout-block-express', + 'general', + ]; + + // Default preview buttons; on "Standard Payments" tab. + paypalButtonLocations.forEach( ( location ) => { + const inputNamePrefix = + location === 'checkout' + ? '#ppcp-button' + : '#ppcp-button_' + location; + const wrapperName = location + .split( '-' ) + .map( ( s ) => s.charAt( 0 ).toUpperCase() + s.slice( 1 ) ) + .join( '' ); + const fields = { + color: inputNamePrefix + '_color', + shape: inputNamePrefix + '_shape', + label: inputNamePrefix + '_label', + tagline: inputNamePrefix + '_tagline', + layout: inputNamePrefix + '_layout', + }; + + if ( document.querySelector( inputNamePrefix + '_height' ) ) { + fields.height = inputNamePrefix + '_height'; + } + + createButtonPreview( () => + getButtonSettings( + '#ppcp' + wrapperName + 'ButtonPreview', + fields + ) + ); + } ); + + /** + * Inspect DOM to find APM button previews; on tabs like "Advanced Card Payments". + * + * How it works: + * + * 1. Add a
to hold the preview button to the settings page: + * - `id="ppcp[NAME]ButtonPreview"` + * - `data-ppc-apm-preview="[NAME]"` + * 2. Mark all fields that are relevant for the preview button: + * - custom_attribute: `data-ppcp-apm-name="[NAME]"` + * - custom_attribute: `data-ppcp-field-name="[FIELD]"` + * + * This block will find all marked input fields and trigger a re-render of the + * preview button when one of those fields value changes. + * + * Example: See the ppcp-google-pay "extensions.php" file. + */ + document + .querySelectorAll( '[data-ppcp-preview-block]' ) + .forEach( ( item ) => { + const apmName = item.dataset.ppcpPreviewBlock; + const getSettings = buttonSettingsGetterFactory( apmName ); + const renderButtonPreview = + buttonRefreshTriggerFactory( apmName ); + + renderPreview( getSettings, renderButtonPreview ); + } ); + + payLaterMessagingLocations.forEach( ( location ) => { + const inputNamePrefix = + '#ppcp-pay_later_' + location + '_message'; + const wrapperName = + location.charAt( 0 ).toUpperCase() + location.slice( 1 ); + createMessagesPreview( () => + getMessageSettings( + '#ppcp' + wrapperName + 'MessagePreview', + { + layout: inputNamePrefix + '_layout', + logo_type: inputNamePrefix + '_logo', + logo_position: inputNamePrefix + '_position', + text_color: inputNamePrefix + '_color', + flex_color: inputNamePrefix + '_flex_color', + flex_ratio: inputNamePrefix + '_flex_ratio', + } + ) + ); + } ); + + createButtonPreview( () => + getButtonDefaultSettings( '#ppcpPayLaterButtonPreview' ) + ); + + const apmFieldPrefix = '#ppcp-card_button_'; + createButtonPreview( () => + getButtonSettings( + '#ppcpCardButtonPreview', + { + color: apmFieldPrefix + 'color', + shape: apmFieldPrefix + 'shape', + poweredby_tagline: apmFieldPrefix + 'poweredby_tagline', + }, + 'card' + ) + ); + } ); + } + + // Logic to handle the "Check available features" button. + ( ( $, props, anchor ) => { + const $btn = $( props.button ); + + const printStatus = ( message, showSpinner ) => { + const html = + message + + ( showSpinner + ? '' + : '' ); + $btn.siblings( '.ppcp-status-text' ).html( html ); + }; + + // If the reload comes from a successful refresh. + if ( + typeof URLSearchParams === 'function' && + new URLSearchParams( window.location.search ).get( + 'feature-refreshed' + ) + ) { + printStatus( + '✔️ ' + props.messages.success + '' + ); + $( 'html, body' ).animate( + { + scrollTop: $( anchor ).offset().top, + }, + 500 + ); + } + + $btn.click( async () => { + $btn.prop( 'disabled', true ); + printStatus( props.messages.waiting, true ); + + const response = await fetch( props.endpoint, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify( { + nonce: props.nonce, + } ), + } ); + + const responseData = await response.json(); + + if ( ! responseData.success ) { + printStatus( responseData.data.message ); + $btn.prop( 'disabled', false ); + } else { + window.location.href += + ( window.location.href.indexOf( '?' ) > -1 ? '&' : '?' ) + + 'feature-refreshed=1#'; + } + } ); + } )( + jQuery, + PayPalCommerceGatewaySettings.ajax.refresh_feature_status, + '#field-credentials_feature_onboarding_heading' + ); +} ); diff --git a/modules/ppcp-wc-gateway/resources/js/helper/debounce.js b/modules/ppcp-wc-gateway/resources/js/helper/debounce.js index 68a34e771..0de89aaa1 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/debounce.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/debounce.js @@ -1,9 +1,9 @@ -export const debounce = (callback, delayMs) => { - let timeoutId = null; - return (...args) => { - window.clearTimeout(timeoutId); - timeoutId = window.setTimeout(() => { - callback.apply(null, args); - }, delayMs); - }; +export const debounce = ( callback, delayMs ) => { + let timeoutId = null; + return ( ...args ) => { + window.clearTimeout( timeoutId ); + timeoutId = window.setTimeout( () => { + callback.apply( null, args ); + }, delayMs ); + }; }; diff --git a/modules/ppcp-wc-gateway/resources/js/helper/form.js b/modules/ppcp-wc-gateway/resources/js/helper/form.js index 4f4891f03..d8ec5331e 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/form.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/form.js @@ -1,13 +1,11 @@ -export const inputValue = (element) => { - const $el = jQuery(element); +export const inputValue = ( element ) => { + const $el = jQuery( element ); - if ($el.is(':checkbox') || $el.is(':radio')) { - if ($el.is(':checked')) { - return $el.val(); - } else { - return null; - } - } else { - return $el.val(); - } -} + if ( $el.is( ':checkbox' ) || $el.is( ':radio' ) ) { + if ( $el.is( ':checked' ) ) { + return $el.val(); + } + return null; + } + return $el.val(); +}; diff --git a/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js b/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js index 50ff3b710..30b71f511 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js @@ -5,38 +5,39 @@ * @param {string} apmName - Value of the custom attribute `data-ppcp-apm-name`. * @return {Map} */ -export function getButtonFormFields(apmName) { - const inputFields = document.querySelectorAll(`[data-ppcp-apm-name="${apmName}"]`); +export function getButtonFormFields( apmName ) { + const inputFields = document.querySelectorAll( + `[data-ppcp-apm-name="${ apmName }"]` + ); - return [...inputFields].reduce((fieldMap, el) => { - const key = el.dataset.ppcpFieldName; - let getter = () => el.value; + return [ ...inputFields ].reduce( ( fieldMap, el ) => { + const key = el.dataset.ppcpFieldName; + let getter = () => el.value; - if ('LABEL' === el.tagName) { - el = el.querySelector('input[type="checkbox"]'); - getter = () => el.checked; - } + if ( 'LABEL' === el.tagName ) { + el = el.querySelector( 'input[type="checkbox"]' ); + getter = () => el.checked; + } - return fieldMap.set(key, { - val: getter, - el, - }); - }, new Map()); + return fieldMap.set( key, { + val: getter, + el, + } ); + }, new Map() ); } - /** * Returns a function that triggers an update of the specified preview button, when invoked. - + * @param {string} apmName * @return {((object) => void)} */ -export function buttonRefreshTriggerFactory(apmName) { - const eventName = `ppcp_paypal_render_preview_${apmName}`; +export function buttonRefreshTriggerFactory( apmName ) { + const eventName = `ppcp_paypal_render_preview_${ apmName }`; - return (settings) => { - jQuery(document).trigger(eventName, settings); - }; + return ( settings ) => { + jQuery( document ).trigger( eventName, settings ); + }; } /** @@ -45,24 +46,24 @@ export function buttonRefreshTriggerFactory(apmName) { * @param {string} apmName * @return {() => {button: {wrapper:string, is_enabled:boolean, style:{}}}} */ -export function buttonSettingsGetterFactory(apmName) { - const fields = getButtonFormFields(apmName); +export function buttonSettingsGetterFactory( apmName ) { + const fields = getButtonFormFields( apmName ); - return () => { - const buttonConfig = { - wrapper: `#ppcp${apmName}ButtonPreview`, - 'is_enabled': true, - style: {}, - }; + return () => { + const buttonConfig = { + wrapper: `#ppcp${ apmName }ButtonPreview`, + is_enabled: true, + style: {}, + }; - fields.forEach((item, name) => { - if ('is_enabled' === name) { - buttonConfig[name] = item.val(); - } else { - buttonConfig.style[name] = item.val(); - } - }); + fields.forEach( ( item, name ) => { + if ( 'is_enabled' === name ) { + buttonConfig[ name ] = item.val(); + } else { + buttonConfig.style[ name ] = item.val(); + } + } ); - return { button: buttonConfig }; - }; + return { button: buttonConfig }; + }; } diff --git a/modules/ppcp-wc-gateway/resources/js/oxxo.js b/modules/ppcp-wc-gateway/resources/js/oxxo.js index a6d976a9c..6332251cf 100644 --- a/modules/ppcp-wc-gateway/resources/js/oxxo.js +++ b/modules/ppcp-wc-gateway/resources/js/oxxo.js @@ -1,18 +1,25 @@ -document.addEventListener( - 'DOMContentLoaded', - function() { - jQuery('form.checkout').on('checkout_place_order_success', function(type, data) { - if(data.payer_action && data.payer_action !== '') { - const width = screen.width / 2; - const height = screen.height / 2; - const left = width - (width / 2); - const top = height - (height / 2); - window.open( - data.payer_action, - '_blank', - 'popup, width=' + width + ', height=' + height + ', top=' + top + ', left=' + left - ); - } - }); - } -); +document.addEventListener( 'DOMContentLoaded', function () { + jQuery( 'form.checkout' ).on( + 'checkout_place_order_success', + function ( type, data ) { + if ( data.payer_action && data.payer_action !== '' ) { + const width = screen.width / 2; + const height = screen.height / 2; + const left = width - width / 2; + const top = height - height / 2; + window.open( + data.payer_action, + '_blank', + 'popup, width=' + + width + + ', height=' + + height + + ', top=' + + top + + ', left=' + + left + ); + } + } + ); +} ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index f0d848216..ce41619ca 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; +use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; use WooCommerce\PayPalCommerce\Onboarding\State; @@ -196,6 +197,7 @@ return array( OXXOGateway::ID, Settings::PAY_LATER_TAB_ID, AxoGateway::ID, + GooglePayGateway::ID, ), true ); @@ -217,6 +219,7 @@ return array( CardButtonGateway::ID, Settings::PAY_LATER_TAB_ID, Settings::CONNECTION_TAB_ID, + GooglePayGateway::ID, ), true ); diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index de07ecfef..a55fe621c 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -106,6 +106,12 @@ class DisableGateways { return $methods; } + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) ); + if ( $payment_method && is_string( $payment_method ) ) { + return array( $payment_method => $methods[ $payment_method ] ); + } + return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] ); } diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php index 02a6bcb87..3a2513e98 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php @@ -28,7 +28,7 @@ return function ( ContainerInterface $container, array $fields ): array { $has_enabled_separate_button_gateways = $container->get( 'wcgateway.settings.has_enabled_separate_button_gateways' ); - $preview_message = __( 'Standard Card Button Styling Preview', 'woocommerce-paypal-payments' ); + $preview_message = __( 'Button Styling Preview', 'woocommerce-paypal-payments' ); $axo_smart_button_location_notice = $container->has( 'axo.smart-button-location-notice' ) ? $container->get( 'axo.smart-button-location-notice' ) : ''; diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index df31b45bc..835ec4d4a 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -416,7 +416,9 @@ class WcSubscriptionsModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - if ( $subscriptions_helper->plugin_is_active() ) { + $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; + + if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) { $supports = array( 'subscriptions', 'subscription_cancellation', @@ -442,7 +444,12 @@ class WcSubscriptionsModule implements ModuleInterface { $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscriptions_helper instanceof SubscriptionHelper ); - if ( $subscriptions_helper->plugin_is_active() ) { + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + $vaulting_enabled = $settings->has( 'vault_enabled_dcc' ) && $settings->get( 'vault_enabled_dcc' ); + + if ( $vaulting_enabled && $subscriptions_helper->plugin_is_active() ) { $supports = array( 'subscriptions', 'subscription_cancellation', @@ -467,7 +474,12 @@ class WcSubscriptionsModule implements ModuleInterface { $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscriptions_helper instanceof SubscriptionHelper ); - if ( $subscriptions_helper->plugin_is_active() ) { + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; + + if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) { $supports = array( 'subscriptions', 'subscription_cancellation', diff --git a/modules/ppcp-webhooks/resources/js/status-page.js b/modules/ppcp-webhooks/resources/js/status-page.js index 8f5334f51..afeccd83c 100644 --- a/modules/ppcp-webhooks/resources/js/status-page.js +++ b/modules/ppcp-webhooks/resources/js/status-page.js @@ -1,173 +1,195 @@ -import {setVisibleByClass} from "../../../ppcp-button/resources/js/modules/Helper/Hiding" +import { setVisibleByClass } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -document.addEventListener( - 'DOMContentLoaded', - () => { - const resubscribeBtn = jQuery(PayPalCommerceGatewayWebhooksStatus.resubscribe.button); +document.addEventListener( 'DOMContentLoaded', () => { + const resubscribeBtn = jQuery( + PayPalCommerceGatewayWebhooksStatus.resubscribe.button + ); - resubscribeBtn.click(async () => { - resubscribeBtn.prop('disabled', true); + resubscribeBtn.click( async () => { + resubscribeBtn.prop( 'disabled', true ); - const response = await fetch( - PayPalCommerceGatewayWebhooksStatus.resubscribe.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json' - }, - body: JSON.stringify( - { - nonce: PayPalCommerceGatewayWebhooksStatus.resubscribe.nonce, - } - ) - } - ); + const response = await fetch( + PayPalCommerceGatewayWebhooksStatus.resubscribe.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify( { + nonce: PayPalCommerceGatewayWebhooksStatus.resubscribe + .nonce, + } ), + } + ); - const reportError = error => { - const msg = PayPalCommerceGatewayWebhooksStatus.resubscribe.failureMessage + ' ' + error; - alert(msg); - } + const reportError = ( error ) => { + const msg = + PayPalCommerceGatewayWebhooksStatus.resubscribe.failureMessage + + ' ' + + error; + alert( msg ); + }; - if (!response.ok) { - try { - const result = await response.json(); - reportError(result.data); - } catch (exc) { - console.error(exc); - reportError(response.status); - } - } + if ( ! response.ok ) { + try { + const result = await response.json(); + reportError( result.data ); + } catch ( exc ) { + console.error( exc ); + reportError( response.status ); + } + } - window.location.reload(); - }); + window.location.reload(); + } ); - function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } + function sleep( ms ) { + return new Promise( ( resolve ) => setTimeout( resolve, ms ) ); + } - const simulateBtn = jQuery(PayPalCommerceGatewayWebhooksStatus.simulation.start.button); - simulateBtn.click(async () => { - simulateBtn.prop('disabled', true); + const simulateBtn = jQuery( + PayPalCommerceGatewayWebhooksStatus.simulation.start.button + ); + simulateBtn.click( async () => { + simulateBtn.prop( 'disabled', true ); - try { - const response = await fetch( - PayPalCommerceGatewayWebhooksStatus.simulation.start.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json' - }, - body: JSON.stringify( - { - nonce: PayPalCommerceGatewayWebhooksStatus.simulation.start.nonce, - } - ) - } - ); + try { + const response = await fetch( + PayPalCommerceGatewayWebhooksStatus.simulation.start.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify( { + nonce: PayPalCommerceGatewayWebhooksStatus.simulation + .start.nonce, + } ), + } + ); - const reportError = error => { - const msg = PayPalCommerceGatewayWebhooksStatus.simulation.start.failureMessage + ' ' + error; - alert(msg); - }; + const reportError = ( error ) => { + const msg = + PayPalCommerceGatewayWebhooksStatus.simulation.start + .failureMessage + + ' ' + + error; + alert( msg ); + }; - if (!response.ok) { - try { - const result = await response.json(); - reportError(result.data); - } catch (exc) { - console.error(exc); - reportError(response.status); - } + if ( ! response.ok ) { + try { + const result = await response.json(); + reportError( result.data ); + } catch ( exc ) { + console.error( exc ); + reportError( response.status ); + } - return; - } + return; + } - const showStatus = html => { - let statusBlock = simulateBtn.siblings('.ppcp-webhooks-status-text'); - if (!statusBlock.length) { - statusBlock = jQuery('
').insertAfter(simulateBtn); - } - statusBlock.html(html); - }; + const showStatus = ( html ) => { + let statusBlock = simulateBtn.siblings( + '.ppcp-webhooks-status-text' + ); + if ( ! statusBlock.length ) { + statusBlock = jQuery( + '
' + ).insertAfter( simulateBtn ); + } + statusBlock.html( html ); + }; - simulateBtn.siblings('.description').hide(); + simulateBtn.siblings( '.description' ).hide(); - showStatus( - PayPalCommerceGatewayWebhooksStatus.simulation.state.waitingMessage + - '' - ); + showStatus( + PayPalCommerceGatewayWebhooksStatus.simulation.state + .waitingMessage + + '' + ); - const delay = 2000; - const retriesBeforeErrorMessage = 15; - const maxRetries = 30; + const delay = 2000; + const retriesBeforeErrorMessage = 15; + const maxRetries = 30; - for (let i = 0; i < maxRetries; i++) { - await sleep(delay); + for ( let i = 0; i < maxRetries; i++ ) { + await sleep( delay ); - const stateResponse = await fetch( - PayPalCommerceGatewayWebhooksStatus.simulation.state.endpoint, - { - method: 'GET', - credentials: 'same-origin', - } - ); + const stateResponse = await fetch( + PayPalCommerceGatewayWebhooksStatus.simulation.state + .endpoint, + { + method: 'GET', + credentials: 'same-origin', + } + ); - try { - const result = await stateResponse.json(); + try { + const result = await stateResponse.json(); - if (!stateResponse.ok || !result.success) { - console.error('Simulation state query failed: ' + result.data); - continue; - } + if ( ! stateResponse.ok || ! result.success ) { + console.error( + 'Simulation state query failed: ' + result.data + ); + continue; + } - const state = result.data.state; - if (state === PayPalCommerceGatewayWebhooksStatus.simulation.state.successState) { - showStatus( - '' + - '✔️ ' + - PayPalCommerceGatewayWebhooksStatus.simulation.state.successMessage + - '' - ); - return; - } - } catch (exc) { - console.error(exc); - } + const state = result.data.state; + if ( + state === + PayPalCommerceGatewayWebhooksStatus.simulation.state + .successState + ) { + showStatus( + '' + + '✔️ ' + + PayPalCommerceGatewayWebhooksStatus.simulation + .state.successMessage + + '' + ); + return; + } + } catch ( exc ) { + console.error( exc ); + } - if (i === retriesBeforeErrorMessage) { - showStatus( - '' + - PayPalCommerceGatewayWebhooksStatus.simulation.state.tooLongDelayMessage + - '' - ); - } - } + if ( i === retriesBeforeErrorMessage ) { + showStatus( + '' + + PayPalCommerceGatewayWebhooksStatus.simulation.state + .tooLongDelayMessage + + '' + ); + } + } + } finally { + simulateBtn.prop( 'disabled', false ); + } + } ); - } finally { - simulateBtn.prop('disabled', false); - } - }); + const sandboxCheckbox = document.querySelector( '#ppcp-sandbox_on' ); + if ( sandboxCheckbox ) { + const setWebhooksVisibility = ( show ) => { + [ + '#field-webhook_status_heading', + '#field-webhooks_list', + '#field-webhooks_resubscribe', + '#field-webhooks_simulate', + ].forEach( ( selector ) => { + setVisibleByClass( selector, show, 'hide' ); + } ); + }; - const sandboxCheckbox = document.querySelector('#ppcp-sandbox_on'); - if (sandboxCheckbox) { - const setWebhooksVisibility = (show) => { - [ - '#field-webhook_status_heading', - '#field-webhooks_list', - '#field-webhooks_resubscribe', - '#field-webhooks_simulate', - ].forEach(selector => { - setVisibleByClass(selector, show, 'hide'); - }); - }; - - const serverSandboxState = PayPalCommerceGatewayWebhooksStatus.environment === 'sandbox'; - setWebhooksVisibility(serverSandboxState === sandboxCheckbox.checked); - sandboxCheckbox.addEventListener('click', () => { - setWebhooksVisibility(serverSandboxState === sandboxCheckbox.checked); - }); - } - } -); + const serverSandboxState = + PayPalCommerceGatewayWebhooksStatus.environment === 'sandbox'; + setWebhooksVisibility( serverSandboxState === sandboxCheckbox.checked ); + sandboxCheckbox.addEventListener( 'click', () => { + setWebhooksVisibility( + serverSandboxState === sandboxCheckbox.checked + ); + } ); + } +} ); diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php index 2458e5b4d..939a8d512 100644 --- a/modules/ppcp-webhooks/src/WebhookModule.php +++ b/modules/ppcp-webhooks/src/WebhookModule.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks; +use WC_Order; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; diff --git a/package.json b/package.json index 162ed110a..61c625ca9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "2.8.1", + "version": "2.8.2", "description": "WooCommerce PayPal Payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "license": "GPL-2.0", @@ -87,7 +87,7 @@ "prearchive": "rm -rf $npm_package_name.zip", "archive": "zip -r $npm_package_name.zip . -x **.git/\\* **node_modules/\\*", "postarchive": "yarn run archive:cleanup && rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name", - "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js wp-cli.yml \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\" + "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig .eslintrc babel.config.json tests/\\* \\*test/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js wp-cli.yml \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\" }, "config": { "wp_org_slug": "woocommerce-paypal-payments" diff --git a/readme.txt b/readme.txt index c1d1b0504..e4404042e 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,10 @@ === WooCommerce PayPal Payments === -Contributors: woocommerce, automattic, inpsyde +Contributors: woocommerce, automattic, syde Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple pay, subscriptions, debit card, credit card, google pay Requires at least: 5.3 -Tested up to: 6.5 +Tested up to: 6.6 Requires PHP: 7.2 -Stable tag: 2.8.1 +Stable tag: 2.8.2 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -179,6 +179,24 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == 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 diff --git a/tests/PHPUnit/ModularTestCase.php b/tests/PHPUnit/ModularTestCase.php index a880acfbe..14b7e61ef 100644 --- a/tests/PHPUnit/ModularTestCase.php +++ b/tests/PHPUnit/ModularTestCase.php @@ -54,6 +54,7 @@ class ModularTestCase extends TestCase !defined('CONNECT_WOO_SANDBOX_MERCHANT_ID') && define('CONNECT_WOO_SANDBOX_MERCHANT_ID', 'merchant-id2'); !defined('CONNECT_WOO_URL') && define('CONNECT_WOO_URL', 'https://connect.woocommerce.com/ppc'); !defined('CONNECT_WOO_SANDBOX_URL') && define('CONNECT_WOO_SANDBOX_URL', 'https://connect.woocommerce.com/ppcsandbox'); + !defined('PPCP_PAYPAL_BN_CODE') && define('PPCP_PAYPAL_BN_CODE', 'Woo_PPCP'); } /** diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 32ea572df..2cdf4f8ed 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,14 +3,14 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 2.8.1 + * Version: 2.8.2 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 * Requires PHP: 7.2 * Requires Plugins: woocommerce * WC requires at least: 3.9 - * WC tested up to: 9.0 + * WC tested up to: 9.1 * Text Domain: woocommerce-paypal-payments * * @package WooCommerce\PayPalCommerce @@ -26,7 +26,8 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' ); define( 'PAYPAL_URL', 'https://www.paypal.com' ); define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' ); -define( 'PAYPAL_INTEGRATION_DATE', '2024-06-25' ); +define( 'PAYPAL_INTEGRATION_DATE', '2024-07-17' ); +define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); ! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' ); @@ -222,6 +223,22 @@ define( 'PAYPAL_INTEGRATION_DATE', '2024-06-25' ); } ); + add_action( + 'in_plugin_update_message-woocommerce-paypal-payments/woocommerce-paypal-payments.php', + static function( array $plugin_data, \stdClass $new_data ) { + if ( version_compare( $plugin_data['Version'], '3.0.0', '<' ) && + version_compare( $new_data->new_version, '3.0.0', '>=' ) ) { + printf( + '
%s: %s', + esc_html__( 'Warning', 'woocommerce-paypal-payments' ), + esc_html__( 'WooCommerce PayPal Payments version 3.0.0 contains significant changes that may impact your website. We strongly recommend reviewing the changes and testing the update on a staging site before updating it on your production environment.', 'woocommerce-paypal-payments' ) + ); + } + }, + 10, + 2 + ); + /** * Check if WooCommerce is active. *