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/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 69745082e..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 true; - } + 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 a12fd3636..d56f5c39f 100644 --- a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js @@ -1,42 +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; - shippingAllowed() { - return false; - } - - 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 8299dc51c..b0837bcd6 100644 --- a/modules/ppcp-axo/resources/js/AxoManager.js +++ b/modules/ppcp-axo/resources/js/AxoManager.js @@ -1,972 +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(); - - } else { - this.shippingView.deactivate(); - this.billingView.deactivate(); - this.cardView.deactivate(); - } - - 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/Assets/AxoManager.php b/modules/ppcp-axo/src/Assets/AxoManager.php index 36f0ea069..363f0d092 100644 --- a/modules/ppcp-axo/src/Assets/AxoManager.php +++ b/modules/ppcp-axo/src/Assets/AxoManager.php @@ -237,8 +237,6 @@ class AxoManager { * Renders the HTML for the AXO submit button. */ public function render_checkout_button(): void { - $id = 'ppcp-axo-submit-button-container'; - /** * The WC filter returning the WC order button text. * phpcs:disable WordPress.WP.I18n.TextDomainMismatch @@ -246,10 +244,9 @@ class AxoManager { $label = apply_filters( 'woocommerce_order_button_text', __( 'Place order', 'woocommerce' ) ); printf( - '