Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions modules/ppcp-blocks/resources/js/Helper/Subscription.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @param {Object} scriptData
* @return {boolean}
* @return {boolean} Whether the store runs in PayPal Subscriptions mode.
*/
export const isPayPalSubscription = ( scriptData ) => {
return (
Expand All @@ -11,8 +11,58 @@ export const isPayPalSubscription = ( scriptData ) => {

/**
* @param {Object} scriptData
* @return {boolean}
* @return {boolean} Whether the cart contains at least one subscription product.
*/
export const cartHasSubscriptionProducts = ( scriptData ) => {
return !! scriptData?.locations_with_subscription_product?.cart;
};

/**
* Whether the PayPal button is allowed for the current (subscription) cart.
*
* Single, mode-aware rule shared by the block cart, classic cart and mini-cart
* so the button is shown (or hidden) consistently. Prefers the authoritative
* server flag `subscription_button_allowed`; the explicit checks act as a
* fallback and also cover the free-trial guest case on the block cart.
*
* @param {Object} scriptData
* @return {boolean} Whether the PayPal button may be displayed for this cart.
*/
export const paypalSubscriptionButtonAllowed = ( scriptData ) => {
if ( ! cartHasSubscriptionProducts( scriptData ) ) {
return true;
}

// Don't show buttons on the block cart page if the user is not logged in
// and the cart contains a free trial product.
if (
! scriptData.user?.is_logged &&
scriptData.context === 'cart-block' &&
scriptData.is_free_trial_cart
) {
return false;
}

if ( typeof scriptData.subscription_button_allowed !== 'undefined' ) {
return !! scriptData.subscription_button_allowed;
}

// Vaulting mode but vaulting disabled.
if (
! isPayPalSubscription( scriptData ) &&
! scriptData.can_save_vault_token
) {
return false;
}

// PayPal Subscriptions mode but product not associated with a PayPal plan
// (or the cart contains more than one item).
if (
isPayPalSubscription( scriptData ) &&
! scriptData.subscription_product_allowed
) {
return false;
}

return true;
};
79 changes: 79 additions & 0 deletions modules/ppcp-blocks/resources/js/Helper/Subscription.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { paypalSubscriptionButtonAllowed } from './Subscription';

const baseData = ( overrides = {} ) => ( {
locations_with_subscription_product: { cart: true },
user: { is_logged: true },
context: 'cart',
is_free_trial_cart: false,
can_save_vault_token: false,
subscription_product_allowed: false,
data_client_id: {
has_subscriptions: false,
paypal_subscriptions_enabled: false,
},
...overrides,
} );

describe( 'paypalSubscriptionButtonAllowed', () => {
it( 'allows a cart without subscription products', () => {
const data = baseData( {
locations_with_subscription_product: { cart: false },
} );
expect( paypalSubscriptionButtonAllowed( data ) ).toBe( true );
} );

it( 'hides for a guest with a free trial on the block cart', () => {
const data = baseData( {
user: { is_logged: false },
context: 'cart-block',
is_free_trial_cart: true,
subscription_button_allowed: true,
} );
expect( paypalSubscriptionButtonAllowed( data ) ).toBe( false );
} );

it( 'prefers the server subscription_button_allowed flag when present', () => {
expect(
paypalSubscriptionButtonAllowed(
baseData( { subscription_button_allowed: false } )
)
).toBe( false );
expect(
paypalSubscriptionButtonAllowed(
baseData( { subscription_button_allowed: true } )
)
).toBe( true );
} );

it( 'falls back to hide in vaulting mode when vaulting is disabled', () => {
const data = baseData( { can_save_vault_token: false } );
expect( paypalSubscriptionButtonAllowed( data ) ).toBe( false );
} );

it( 'falls back to show in vaulting mode when vaulting is enabled', () => {
const data = baseData( { can_save_vault_token: true } );
expect( paypalSubscriptionButtonAllowed( data ) ).toBe( true );
} );

it( 'falls back to hide in subscriptions mode when the product is not allowed', () => {
const data = baseData( {
data_client_id: {
has_subscriptions: true,
paypal_subscriptions_enabled: true,
},
subscription_product_allowed: false,
} );
expect( paypalSubscriptionButtonAllowed( data ) ).toBe( false );
} );

it( 'falls back to show in subscriptions mode when the product is allowed', () => {
const data = baseData( {
data_client_id: {
has_subscriptions: true,
paypal_subscriptions_enabled: true,
},
subscription_product_allowed: true,
} );
expect( paypalSubscriptionButtonAllowed( data ) ).toBe( true );
} );
} );
30 changes: 4 additions & 26 deletions modules/ppcp-blocks/resources/js/checkout-block.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
import { __ } from '@wordpress/i18n';
import {
cartHasSubscriptionProducts,
isPayPalSubscription,
paypalSubscriptionButtonAllowed,
} from './Helper/Subscription';
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
import BlockCheckoutMessagesBootstrap from './Bootstrap/BlockCheckoutMessagesBootstrap';
Expand All @@ -25,31 +25,9 @@ const features = [ 'products' ];
let blockEnabled = true;

if ( cartHasSubscriptionProducts( config.scriptData ) ) {
// Don't show buttons on block cart page if user is not logged in and cart contains free trial product
if (
! config.scriptData.user.is_logged &&
config.scriptData.context === 'cart-block' &&
cartHasSubscriptionProducts( config.scriptData ) &&
config.scriptData.is_free_trial_cart
) {
blockEnabled = false;
}

// Don't render if vaulting disabled and is in vault subscription mode
if (
! isPayPalSubscription( config.scriptData ) &&
! config.scriptData.can_save_vault_token
) {
blockEnabled = false;
}

// Don't render buttons if in subscription mode and product not associated with a PayPal subscription
if (
isPayPalSubscription( config.scriptData ) &&
! config.scriptData.subscription_product_allowed
) {
blockEnabled = false;
}
// Show the button only for subscription carts PayPal can process
// (shared rule used by the classic cart and mini-cart as well).
blockEnabled = paypalSubscriptionButtonAllowed( config.scriptData );

features.push( 'subscriptions' );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import CartActionHandler from '../ActionHandler/CartActionHandler';
import BootstrapHelper from '../Helper/BootstrapHelper';
import { paypalSubscriptionButtonAllowed } from '@ppcp-blocks/Helper/Subscription';

class CartBootstrap {
constructor( gateway, renderer, errorHandler ) {
Expand Down Expand Up @@ -61,8 +62,23 @@ class CartBootstrap {
if ( result.data.messages ) {
newData.messages = result.data.messages;
}
// Keep the subscription gate in sync with the current cart.
if (
typeof result.data.subscription_button_allowed !==
'undefined'
) {
newData.subscription_button_allowed =
result.data.subscription_button_allowed;
}
if ( result.data.locations_with_subscription_product ) {
newData.locations_with_subscription_product =
result.data.locations_with_subscription_product;
}
if ( newData ) {
BootstrapHelper.updateScriptData( this, newData );
if ( this.shouldRender() ) {
this.render();
}
this.handleButtonStatus();
}

Expand All @@ -87,11 +103,39 @@ class CartBootstrap {
return BootstrapHelper.shouldEnable( this );
}

/**
* Whether the button is allowed for the current (subscription) cart.
* Shared rule used by the block cart and mini-cart as well.
*
* @return {boolean} True when the button may be displayed.
*/
subscriptionButtonAllowed() {
return paypalSubscriptionButtonAllowed( this.gateway );
}

hide() {
jQuery( this.gateway.button.wrapper ).hide();
}

show() {
jQuery( this.gateway.button.wrapper ).show();
}

render() {
if ( ! this.shouldRender() ) {
return;
}

// Hide the button for subscription carts PayPal cannot process, so the
// classic and block carts behave consistently and the button does not
// render only to be removed afterwards.
if ( ! this.subscriptionButtonAllowed() ) {
this.hide();
return;
}

this.show();

const actionHandler = new CartActionHandler(
PayPalCommerceGateway,
this.errorHandler
Expand All @@ -115,11 +159,6 @@ class CartBootstrap {
actionHandler.subscriptionsConfiguration( subscription_plan_id )
);

if ( ! PayPalCommerceGateway.subscription_product_allowed ) {
this.gateway.button.is_disabled = true;
this.handleButtonStatus();
}

return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import CartActionHandler from '../ActionHandler/CartActionHandler';
import BootstrapHelper from '../Helper/BootstrapHelper';
import { paypalSubscriptionButtonAllowed } from '@ppcp-blocks/Helper/Subscription';
import { debounce } from '@ppcp-blocks/Helper/debounce';

class MiniCartBootstrap {
constructor( gateway, renderer, errorHandler ) {
this.gateway = gateway;
this.renderer = renderer;
this.errorHandler = errorHandler;
this.actionHandler = null;
this.lastItemsCount = null;
}

init() {
Expand All @@ -23,6 +26,7 @@ class MiniCartBootstrap {
miniCartConfig,
this.errorHandler
);
this.gateway = miniCartConfig;
this.render();
this.handleButtonStatus();

Expand All @@ -34,6 +38,20 @@ class MiniCartBootstrap {
}
);

/*
The block cart/mini-cart mutates the `wc/store/cart` data store instead of
firing the jQuery fragment events above, so subscribe to it to keep the
mini-cart button in sync (and clear it when the cart becomes empty).
*/
if ( typeof wp !== 'undefined' && wp.data?.subscribe ) {
wp.data.subscribe(
debounce( () => {
this._handleStoreCartChange();
}, 300 ),
'wc/store/cart'
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fires on every store change, not just wc/store/cart.
subscribe() accepts an optional storeNameOrDescriptor second argument to scope the listener to one store like wp.data.subscribe(callback, 'wc/store/cart')

}

this.renderer.onButtonsInit(
this.gateway.button.mini_cart_wrapper,
() => {
Expand All @@ -43,6 +61,35 @@ class MiniCartBootstrap {
);
}

/**
* @private
*/
_handleStoreCartChange() {
if ( typeof wp === 'undefined' || ! wp.data?.select ) {
return;
}

const cart = wp.data.select( 'wc/store/cart' );
if ( ! cart ) {
return;
}

const itemsCount =
cart.getCartData?.()?.itemsCount ?? cart.getItemsCount?.() ?? null;
if ( itemsCount === null || itemsCount === this.lastItemsCount ) {
return;
}
this.lastItemsCount = itemsCount;

if ( itemsCount === 0 ) {
this.hide();
return;
}

this.render();
this.handleButtonStatus();
}

handleButtonStatus() {
BootstrapHelper.handleButtonStatus( this, {
wrapper: this.gateway.button.mini_cart_wrapper,
Expand All @@ -66,11 +113,34 @@ class MiniCartBootstrap {
} );
}

/**
* Whether the button is allowed for the current (subscription) cart.
* Shared rule used by the classic cart and block cart as well.
*
* @return {boolean} True when the button may be displayed.
*/
subscriptionButtonAllowed() {
return paypalSubscriptionButtonAllowed( this.gateway );
}

hide() {
jQuery( this.gateway.button.mini_cart_wrapper ).empty().hide();
}

render() {
if ( ! this.shouldRender() ) {
return;
}

// Hide the button for subscription carts PayPal cannot process, so the
// mini-cart behaves consistently with the classic and block carts.
if ( ! this.subscriptionButtonAllowed() ) {
this.hide();
return;
}

jQuery( this.gateway.button.mini_cart_wrapper ).show();

this.renderer.render( this.actionHandler.configuration(), {
button: {
wrapper: this.gateway.button.mini_cart_wrapper,
Expand Down
Loading
Loading