Merge branch 'trunk' into PCP-3170-add-es-lint-and-testing-library-for-js-code

This commit is contained in:
Emili Castells Guasch 2024-07-03 10:54:51 +02:00
commit fef4566f52
17 changed files with 472 additions and 104 deletions

View file

@ -4,23 +4,22 @@
--apple-pay-button-min-height: 35px;
--apple-pay-button-width: 100%;
--apple-pay-button-max-width: 750px;
--apple-pay-button-border-radius: 4px;
--apple-pay-button-border-radius: var(--apm-button-border-radius);
--apple-pay-button-overflow: hidden;
--apple-pay-button-box-sizing: border-box;
.ppcp-width-min & {
--apple-pay-button-height: 35px;
}
.ppcp-width-300 & {
--apple-pay-button-height: 45px;
}
.ppcp-width-500 & {
--apple-pay-button-height: 55px;
}
&.ppcp-button-pill {
--apple-pay-button-border-radius: 50px;
}
&.ppcp-button-minicart {
--apple-pay-button-display: block;
}

View file

@ -1,6 +1,7 @@
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,
@ -15,21 +16,12 @@ import ApplepayManager from "./ApplepayManager";
manager.init();
};
jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
setupButtonEvents(function() {
if (manager) {
manager.reinit();
}
});
// Use set timeout as it's unnecessary to refresh upon Minicart initial render.
setTimeout(() => {
jQuery(document.body).on('wc_fragments_loaded wc_fragments_refreshed', () => {
if (manager) {
manager.reinit();
}
});
}, 1000);
document.addEventListener(
'DOMContentLoaded',
() => {

View file

@ -505,6 +505,30 @@ const PayPalComponent = ({
const PayPalButton = paypal.Buttons.driver("react", { React, ReactDOM });
const getOnShippingOptionsChange = (fundingSource) => {
if(fundingSource === 'venmo') {
return null;
}
return (data, actions) => {
shouldHandleShippingInPayPal()
? handleShippingOptionsChange(data, actions)
: null;
};
}
const getOnShippingAddressChange = (fundingSource) => {
if(fundingSource === 'venmo') {
return null;
}
return (data, actions) => {
shouldHandleShippingInPayPal()
? handleShippingAddressChange(data, actions)
: null;
};
}
if(isPayPalSubscription(config.scriptData)) {
return (
<PayPalButton
@ -515,16 +539,8 @@ const PayPalComponent = ({
onError={onClose}
createSubscription={createSubscription}
onApprove={handleApproveSubscription}
onShippingOptionsChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleSubscriptionShippingOptionsChange(data, actions)
: null;
}}
onShippingAddressChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleSubscriptionShippingAddressChange(data, actions)
: null;
}}
onShippingOptionsChange={getOnShippingOptionsChange(fundingSource)}
onShippingAddressChange={getOnShippingAddressChange(fundingSource)}
/>
);
}
@ -538,16 +554,8 @@ const PayPalComponent = ({
onError={onClose}
createOrder={createOrder}
onApprove={handleApprove}
onShippingOptionsChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleShippingOptionsChange(data, actions)
: null;
}}
onShippingAddressChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleShippingAddressChange(data, actions)
: null;
}}
onShippingOptionsChange={getOnShippingOptionsChange(fundingSource)}
onShippingAddressChange={getOnShippingAddressChange(fundingSource)}
/>
);
}

View file

@ -1,17 +1,19 @@
@mixin button {
--apm-button-border-radius: 4px;
overflow: hidden;
min-width: 0;
max-width: 750px;
line-height: 0;
border-radius: 4px;
border-radius: var(--apm-button-border-radius);
// Defaults
height: 45px;
margin-top: 14px;
&.ppcp-button-pill {
border-radius: 50px;
--apm-button-border-radius: 50px;
}
&.ppcp-button-minicart {

View file

@ -17,6 +17,7 @@ import {
import {setVisibleByClass} from "./modules/Helper/Hiding";
import {isChangePaymentPage} from "./modules/Helper/Subscriptions";
import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler";
import MultistepCheckoutHelper from "./modules/Helper/MultistepCheckoutHelper";
import FormSaver from './modules/Helper/FormSaver';
import FormValidator from "./modules/Helper/FormValidator";
import {loadPaypalScript} from "./modules/Helper/ScriptLoading";
@ -53,6 +54,8 @@ const bootstrap = () => {
const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, formValidator, spinner, errorHandler);
new MultistepCheckoutHelper(checkoutFormSelector);
jQuery('form.woocommerce-checkout input').on('keydown', e => {
if (e.key === 'Enter' && [
PaymentMethods.PAYPAL,

View file

@ -0,0 +1,37 @@
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
const REFRESH_BUTTON_EVENT = 'ppcp_refresh_payment_buttons';
/**
* Triggers a refresh of the payment buttons.
* This function dispatches a custom event that the button components listen for.
*
* Use this function on the front-end to update payment buttons after the checkout form was updated.
*/
export function refreshButtons() {
document.dispatchEvent(new Event(REFRESH_BUTTON_EVENT));
}
/**
* Sets up event listeners for various cart and checkout update events.
* When these events occur, it triggers a refresh of the payment buttons.
*
* @param {Function} refresh - Callback responsible to re-render the payment button.
*/
export function setupButtonEvents(refresh) {
const miniCartInitDelay = 1000;
const debouncedRefresh = debounce(refresh, 50);
// Listen for our custom refresh event.
document.addEventListener(REFRESH_BUTTON_EVENT, debouncedRefresh);
// Listen for cart and checkout update events.
document.body.addEventListener('updated_cart_totals', debouncedRefresh);
document.body.addEventListener('updated_checkout', debouncedRefresh);
// Use setTimeout for fragment events to avoid unnecessary refresh on initial render.
setTimeout(() => {
document.body.addEventListener('wc_fragments_loaded', debouncedRefresh);
document.body.addEventListener('wc_fragments_refreshed', debouncedRefresh);
}, miniCartInitDelay);
}

View file

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

View file

@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\DisabledFundingSources;
use WooCommerce\PayPalCommerce\Button\Helper\WooCommerceOrderCreator;
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
@ -149,7 +150,8 @@ return array(
$container->get( 'vaulting.vault-v3-enabled' ),
$container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'button.handle-shipping-in-paypal' )
$container->get( 'button.handle-shipping-in-paypal' ),
$container->get( 'button.helper.disabled-funding-sources' )
);
},
'button.url' => static function ( ContainerInterface $container ): string {
@ -306,12 +308,10 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'button.helper.cart-products' => static function ( ContainerInterface $container ): CartProductsHelper {
$data_store = \WC_Data_Store::load( 'product' );
return new CartProductsHelper( $data_store );
},
'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure {
return new ThreeDSecure(
$container->get( 'api.factory.card-authentication-result-factory' ),
@ -323,7 +323,12 @@ return array(
$container->get( 'api.shop.country' )
);
},
'button.helper.disabled-funding-sources' => static function ( ContainerInterface $container ): DisabledFundingSources {
return new DisabledFundingSources(
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.all-funding-sources' )
);
},
'button.is-logged-in' => static function ( ContainerInterface $container ): bool {
return is_user_logged_in();
},

View file

@ -32,6 +32,7 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\StartPayPalVaultingEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\DisabledFundingSources;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule;
@ -225,6 +226,13 @@ class SmartButton implements SmartButtonInterface {
*/
private $should_handle_shipping_in_paypal;
/**
* List of funding sources to be disabled.
*
* @var DisabledFundingSources
*/
private $disabled_funding_sources;
/**
* SmartButton constructor.
*
@ -251,6 +259,7 @@ class SmartButton implements SmartButtonInterface {
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param LoggerInterface $logger The logger.
* @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal.
* @param DisabledFundingSources $disabled_funding_sources List of funding sources to be disabled.
*/
public function __construct(
string $module_url,
@ -275,7 +284,8 @@ class SmartButton implements SmartButtonInterface {
bool $vault_v3_enabled,
PaymentTokensEndpoint $payment_tokens_endpoint,
LoggerInterface $logger,
bool $should_handle_shipping_in_paypal
bool $should_handle_shipping_in_paypal,
DisabledFundingSources $disabled_funding_sources
) {
$this->module_url = $module_url;
@ -301,6 +311,7 @@ class SmartButton implements SmartButtonInterface {
$this->logger = $logger;
$this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal;
$this->disabled_funding_sources = $disabled_funding_sources;
}
/**
@ -1400,54 +1411,18 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
}
}
$disable_funding = $this->settings->has( 'disable_funding' )
? $this->settings->get( 'disable_funding' )
: array();
if ( ! is_checkout() ) {
$disable_funding[] = 'card';
}
$is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' );
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$is_separate_card_enabled = isset( $available_gateways[ CardButtonGateway::ID ] );
if ( is_checkout() && ( $is_dcc_enabled || $is_separate_card_enabled ) ) {
$key = array_search( 'card', $disable_funding, true );
if ( false !== $key ) {
unset( $disable_funding[ $key ] );
}
}
if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) {
$disable_funding = array_merge(
$disable_funding,
array_diff(
array_keys( $this->all_funding_sources ),
array( 'venmo', 'paylater', 'paypal', 'card' )
)
);
}
if ( $this->is_free_trial_cart() ) {
$all_sources = array_keys( $this->all_funding_sources );
if ( $is_dcc_enabled || $is_separate_card_enabled ) {
$all_sources = array_diff( $all_sources, array( 'card' ) );
}
$disable_funding = $all_sources;
}
$disabled_funding_sources = $this->disabled_funding_sources->sources( $context );
$enable_funding = array( 'venmo' );
if ( $this->is_pay_later_button_enabled_for_location( $context ) ) {
$enable_funding[] = 'paylater';
} else {
$disable_funding[] = 'paylater';
$disabled_funding_sources[] = 'paylater';
}
$disable_funding = array_filter(
$disable_funding,
$disabled_funding_sources = array_filter(
$disabled_funding_sources,
/**
* Make sure paypal is not sent in disable funding.
*
@ -1460,8 +1435,8 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
}
);
if ( count( $disable_funding ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
if ( count( $disabled_funding_sources ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disabled_funding_sources ) );
}
if ( $this->is_free_trial_cart() ) {

View file

@ -0,0 +1,109 @@
<?php
/**
* Creates the list of disabled funding sources.
*
* @package WooCommerce\PayPalCommerce\Button\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
/**
* Class DisabledFundingSources
*/
class DisabledFundingSources {
use FreeTrialHandlerTrait;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* All existing funding sources.
*
* @var array
*/
private $all_funding_sources;
/**
* DisabledFundingSources constructor.
*
* @param Settings $settings The settings.
* @param array $all_funding_sources All existing funding sources.
*/
public function __construct( Settings $settings, array $all_funding_sources ) {
$this->settings = $settings;
$this->all_funding_sources = $all_funding_sources;
}
/**
* Returns the list of funding sources to be disabled.
*
* @param string $context The context.
* @return array|int[]|mixed|string[]
* @throws NotFoundException When the setting is not found.
*/
public function sources( string $context ) {
$disable_funding = $this->settings->has( 'disable_funding' )
? $this->settings->get( 'disable_funding' )
: array();
$is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' );
if (
! is_checkout()
|| ( $is_dcc_enabled && in_array( $context, array( 'checkout-block', 'cart-block' ), true ) )
) {
$disable_funding[] = 'card';
}
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$is_separate_card_enabled = isset( $available_gateways[ CardButtonGateway::ID ] );
if (
(
is_checkout()
&& ! in_array( $context, array( 'checkout-block', 'cart-block' ), true )
)
&& (
$is_dcc_enabled
|| $is_separate_card_enabled
)
) {
$key = array_search( 'card', $disable_funding, true );
if ( false !== $key ) {
unset( $disable_funding[ $key ] );
}
}
if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) {
$disable_funding = array_merge(
$disable_funding,
array_diff(
array_keys( $this->all_funding_sources ),
array( 'venmo', 'paylater', 'paypal', 'card' )
)
);
}
if ( $this->is_free_trial_cart() ) {
$all_sources = array_keys( $this->all_funding_sources );
if ( $is_dcc_enabled || $is_separate_card_enabled ) {
$all_sources = array_diff( $all_sources, array( 'card' ) );
}
$disable_funding = $all_sources;
}
return $disable_funding;
}
}

View file

@ -1,5 +1,11 @@
.ppcp-button-googlepay {
min-height: 40px;
.gpay-card-info-container,
.gpay-button {
outline-offset: -1px;
border-radius: var(--apm-button-border-radius);
}
}
.wp-block-woocommerce-checkout, .wp-block-woocommerce-cart {

View file

@ -1,6 +1,7 @@
import {loadCustomScript} from "@paypal/paypal-js";
import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
import GooglepayManager from "./GooglepayManager";
import {setupButtonEvents} from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
(function ({
buttonConfig,
@ -15,21 +16,12 @@ import GooglepayManager from "./GooglepayManager";
manager.init();
};
jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
setupButtonEvents(function() {
if (manager) {
manager.reinit();
}
});
// Use set timeout as it's unnecessary to refresh upon Minicart initial render.
setTimeout(() => {
jQuery(document.body).on('wc_fragments_loaded wc_fragments_refreshed', () => {
if (manager) {
manager.reinit();
}
});
}, 1000);
document.addEventListener(
'DOMContentLoaded',
() => {

View file

@ -57,7 +57,8 @@ return array(
$container->get( 'order-tracking.allowed-shipping-statuses' ),
$container->get( 'order-tracking.available-carriers' ),
$container->get( 'order-tracking.endpoint.controller' ),
$container->get( 'order-tracking.should-use-second-version-of-api' )
$container->get( 'order-tracking.should-use-second-version-of-api' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'order-tracking.allowed-shipping-statuses' => static function ( ContainerInterface $container ): array {

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\OrderTracking;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentInterface;
@ -54,6 +55,13 @@ class MetaBoxRenderer {
*/
protected $should_use_new_api;
/**
* The logger.
*
* @var LoggerInterface
*/
protected $logger;
/**
* MetaBoxRenderer constructor.
*
@ -62,18 +70,21 @@ class MetaBoxRenderer {
* @psalm-param Carriers $carriers
* @param OrderTrackingEndpoint $order_tracking_endpoint The order tracking endpoint.
* @param bool $should_use_new_api Whether new API should be used.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
array $allowed_statuses,
array $carriers,
OrderTrackingEndpoint $order_tracking_endpoint,
bool $should_use_new_api
bool $should_use_new_api,
LoggerInterface $logger
) {
$this->allowed_statuses = $allowed_statuses;
$this->carriers = $carriers;
$this->order_tracking_endpoint = $order_tracking_endpoint;
$this->should_use_new_api = $should_use_new_api;
$this->logger = $logger;
}
/**
@ -86,12 +97,17 @@ class MetaBoxRenderer {
$order_items = $wc_order->get_items();
$order_item_count = ! empty( $order_items ) ? count( $order_items ) : 0;
/**
* The shipments
*
* @var ShipmentInterface[] $shipments
*/
$shipments = $this->order_tracking_endpoint->list_tracking_information( $wc_order->get_id() ) ?? array();
try {
/**
* The shipments
*
* @var ShipmentInterface[] $shipments
*/
$shipments = $this->order_tracking_endpoint->list_tracking_information( $wc_order->get_id() ) ?? array();
} catch ( Exception $exception ) {
$this->logger->log( 'warning', $exception->getMessage() );
return;
}
?>
<div class="ppcp-tracking-columns-wrapper">
<div class="ppcp-tracking-column">