Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Fix wrong keys being sent in canMakePayment and customer data showing in the Checkout block in the editor #7434

Merged
merged 9 commits into from
Oct 20, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ import {
useMemo,
useEffect,
useCallback,
useState,
} from '@wordpress/element';
import { usePrevious } from '@woocommerce/base-hooks';
import deprecated from '@wordpress/deprecated';
import { useDispatch, useSelect } from '@wordpress/data';
import {
select as wpDataSelect,
useDispatch,
useSelect,
} from '@wordpress/data';
import {
CHECKOUT_STORE_KEY,
PAYMENT_STORE_KEY,
VALIDATION_STORE_KEY,
} from '@woocommerce/block-data';

Expand All @@ -28,6 +34,10 @@ import { STATUS } from '../../../../../data/checkout/constants';
import { useStoreEvents } from '../../../hooks/use-store-events';
import { useCheckoutNotices } from '../../../hooks/use-checkout-notices';
import { CheckoutState } from '../../../../../data/checkout/default-state';
import {
getExpressPaymentMethods,
getPaymentMethods,
} from '../../../../../blocks-registry/payment-methods/registry';

type CheckoutEventsContextType = {
// Submits the checkout and begins processing.
Expand Down Expand Up @@ -69,6 +79,34 @@ export const CheckoutEventsProvider = ( {
children: React.ReactChildren;
redirectUrl: string;
} ): JSX.Element => {
const paymentMethods = getPaymentMethods();
const expressPaymentMethods = getExpressPaymentMethods();

// Go into useState as once this is set it won't change.
const [ isEditor ] = useState( !! wpDataSelect( 'core/editor' ) );
Copy link
Member

Choose a reason for hiding this comment

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

Given you can use hooks here, why not use isEditor from useEditorContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to use useEditorContext thanks @senadir


const { __internalUpdateAvailablePaymentMethods } =
useDispatch( PAYMENT_STORE_KEY );

// Update the payment method store when paymentMethods or expressPaymentMethods changes.
// Ensure this happens in the editor even if paymentMethods is empty. This won't happen instantly when the objects
// are updated, but on the next re-render.
useEffect( () => {
if (
! isEditor &&
Object.keys( paymentMethods ).length === 0 &&
Object.keys( expressPaymentMethods ).length === 0
) {
return;
}
__internalUpdateAvailablePaymentMethods();
}, [
isEditor,
paymentMethods,
expressPaymentMethods,
__internalUpdateAvailablePaymentMethods,
] );

const checkoutActions = useDispatch( CHECKOUT_STORE_KEY );
const checkoutState: CheckoutState = useSelect( ( select ) =>
select( CHECKOUT_STORE_KEY ).getCheckoutState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const registerMockPaymentMethods = () => {
ariaLabel: name,
} );
} );
dispatch( PAYMENT_STORE_KEY ).__internalInitializePaymentStore();
dispatch( PAYMENT_STORE_KEY ).__internalUpdateAvailablePaymentMethods();
};

const resetMockPaymentMethods = () => {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/data/cart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const unsubscribeInitializePaymentStore = registeredStore.subscribe(
if ( cartLoaded ) {
wpDataDispatch(
'wc/store/payment'
).__internalInitializePaymentStore();
).__internalUpdateAvailablePaymentMethods();
unsubscribeInitializePaymentStore();
}
}
Expand Down
25 changes: 1 addition & 24 deletions assets/js/data/checkout/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
/**
* External dependencies
*/
import {
createReduxStore,
register,
subscribe,
select as wpDataSelect,
dispatch as wpDataDispatch,
} from '@wordpress/data';
import { createReduxStore, register } from '@wordpress/data';

/**
* Internal dependencies
Expand All @@ -17,7 +11,6 @@ import * as selectors from './selectors';
import * as actions from './actions';
import reducer from './reducers';
import { DispatchFromMap, SelectFromMap } from '../mapped-types';
import { checkPaymentMethodsCanPay } from '../payment/check-payment-methods';

export const config = {
reducer,
Expand All @@ -32,22 +25,6 @@ export const config = {
const store = createReduxStore( STORE_KEY, config );
register( store );

const isEditor = !! wpDataSelect( 'core/editor' );

// This is needed to ensure that the payment methods are displayed in the editor
if ( isEditor ) {
const unsubscribeEditor = subscribe( async () => {
await checkPaymentMethodsCanPay();
await checkPaymentMethodsCanPay( true );
} );

const unsubscribeInitializePaymentStore = subscribe( async () => {
wpDataDispatch( 'wc/store/payment' ).__internalInitializePaymentStore();
unsubscribeEditor();
unsubscribeInitializePaymentStore();
} );
}

export const CHECKOUT_STORE_KEY = STORE_KEY;
declare module '@wordpress/data' {
function dispatch(
Expand Down
12 changes: 8 additions & 4 deletions assets/js/data/payment/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,17 @@ export const __internalRemoveAvailableExpressPaymentMethod = (
/**
* The store is initialised once we have checked whether the payment methods registered can pay or not
*/
export function __internalInitializePaymentStore() {
return async ( { dispatch } ) => {
export function __internalUpdateAvailablePaymentMethods() {
return async ( { select, dispatch } ) => {
const expressRegistered = await checkPaymentMethodsCanPay( true );
const registered = await checkPaymentMethodsCanPay( false );
if ( registered && expressRegistered ) {
dispatch( __internalSetExpressPaymentMethodsInitialized( true ) );
const { paymentMethodsInitialized, expressPaymentMethodsInitialized } =
select;
if ( registered && paymentMethodsInitialized ) {
dispatch( __internalSetPaymentMethodsInitialized( true ) );
}
if ( expressRegistered && expressPaymentMethodsInitialized ) {
dispatch( __internalSetExpressPaymentMethodsInitialized( true ) );
}
};
}
117 changes: 103 additions & 14 deletions assets/js/data/payment/check-payment-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,34 @@ import {
} from '@woocommerce/type-defs/payments';
import { CURRENT_USER_IS_ADMIN, getSetting } from '@woocommerce/settings';
import { dispatch, select } from '@wordpress/data';
import { deriveSelectedShippingRates } from '@woocommerce/base-utils';
import {
deriveSelectedShippingRates,
emptyHiddenAddressFields,
} from '@woocommerce/base-utils';
import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';

import {
getExpressPaymentMethods,
getPaymentMethods,
} from '@woocommerce/blocks-registry';
import { previewCart } from '@woocommerce/resource-previews';

/**
* Internal dependencies
*/
import { STORE_KEY as CART_STORE_KEY } from '../cart/constants';
import { STORE_KEY as PAYMENT_STORE_KEY } from './constants';
import { noticeContexts } from '../../base/context/event-emit';
import {
EMPTY_CART_ERRORS,
EMPTY_CART_ITEM_ERRORS,
EMPTY_EXTENSIONS,
} from '../../data/constants';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Need to import from file otherwise it will be a circular dependency.

import {
defaultBillingAddress,
defaultShippingAddress,
} from '../../base/context/providers/cart-checkout/customer/constants';

export const checkPaymentMethodsCanPay = async ( express = false ) => {
const isEditor = !! select( 'core/editor' );
Expand All @@ -46,19 +59,95 @@ export const checkPaymentMethodsCanPay = async ( express = false ) => {
const noticeContext = express
? noticeContexts.EXPRESS_PAYMENTS
: noticeContexts.PAYMENTS;
const cart = select( CART_STORE_KEY ).getCartData();
const selectedShippingMethods = deriveSelectedShippingRates(
cart.shippingRates
);
const canPayArgument = {
cart,
cartTotals: cart.totals,
cartNeedsShipping: cart.needsShipping,
billingData: cart.billingAddress,
shippingAddress: cart.shippingAddress,
selectedShippingMethods,
paymentRequirements: cart.paymentRequirements,
};

let cartForCanPayArgument: Record< string, unknown > = {};
let canPayArgument: Record< string, unknown > = {};

if ( ! isEditor ) {
const store = select( CART_STORE_KEY );
const cart = store.getCartData();
const cartErrors = store.getCartErrors();
const cartTotals = store.getCartTotals();
const cartIsLoading = ! store.hasFinishedResolution( 'getCartData' );
const isLoadingRates = store.isCustomerDataUpdating();
const selectedShippingMethods = deriveSelectedShippingRates(
cart.shippingRates
);

cartForCanPayArgument = {
cartCoupons: cart.coupons,
cartItems: cart.items,
crossSellsProducts: cart.crossSells,
cartFees: cart.fees,
cartItemsCount: cart.itemsCount,
cartItemsWeight: cart.itemsWeight,
cartNeedsPayment: cart.needsPayment,
cartNeedsShipping: cart.needsShipping,
cartItemErrors: cart.errors,
cartTotals,
cartIsLoading,
cartErrors,
billingData: emptyHiddenAddressFields( cart.billingAddress ),
billingAddress: emptyHiddenAddressFields( cart.billingAddress ),
shippingAddress: emptyHiddenAddressFields( cart.shippingAddress ),
extensions: cart.extensions,
shippingRates: cart.shippingRates,
isLoadingRates,
cartHasCalculatedShipping: cart.hasCalculatedShipping,
paymentRequirements: cart.paymentRequirements,
receiveCart: dispatch( CART_STORE_KEY ).receiveCart,
};

canPayArgument = {
cart: cartForCanPayArgument,
cartTotals: cart.totals,
cartNeedsShipping: cart.needsShipping,
billingData: cart.billingAddress,
billingAddress: cart.billingAddress,
shippingAddress: cart.shippingAddress,
selectedShippingMethods,
paymentRequirements: cart.paymentRequirements,
};
} else {
cartForCanPayArgument = {
cartCoupons: previewCart.coupons,
cartItems: previewCart.items,
crossSellsProducts: previewCart.cross_sells,
cartFees: previewCart.fees,
cartItemsCount: previewCart.items_count,
cartItemsWeight: previewCart.items_weight,
cartNeedsPayment: previewCart.needs_payment,
cartNeedsShipping: previewCart.needs_shipping,
cartItemErrors: EMPTY_CART_ITEM_ERRORS,
cartTotals: previewCart.totals,
cartIsLoading: false,
cartErrors: EMPTY_CART_ERRORS,
billingData: defaultBillingAddress,
billingAddress: defaultBillingAddress,
shippingAddress: defaultShippingAddress,
extensions: EMPTY_EXTENSIONS,
shippingRates: previewCart.shipping_rates,
isLoadingRates: false,
cartHasCalculatedShipping: previewCart.has_calculated_shipping,
paymentRequirements: previewCart.paymentRequirements,
receiveCart:
typeof previewCart?.receiveCart === 'function'
? previewCart.receiveCart
: () => undefined,
};
canPayArgument = {
cart: cartForCanPayArgument,
cartTotals: cartForCanPayArgument.totals,
cartNeedsShipping: cartForCanPayArgument.needsShipping,
billingData: cartForCanPayArgument.billingAddress,
billingAddress: cartForCanPayArgument.billingAddress,
shippingAddress: cartForCanPayArgument.shippingAddress,
selectedShippingMethods: deriveSelectedShippingRates(
cartForCanPayArgument.shippingRates
),
paymentRequirements: cartForCanPayArgument.paymentRequirements,
};
}

let paymentMethodsOrder;
if ( express ) {
Expand Down
6 changes: 3 additions & 3 deletions assets/js/data/payment/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
CheckoutExpressPayment,
SavedPaymentMethodOptions,
} from '../../../blocks/cart-checkout-shared/payment-methods';
import { checkPaymentMethodsCanPay } from '../check-payment-methods';
import { defaultCartState } from '../../cart/default-state';

const originalSelect = jest.requireActual( '@wordpress/data' ).select;
Expand Down Expand Up @@ -132,8 +131,9 @@ const registerMockPaymentMethods = ( savedCards = true ) => {
},
} );
} );
checkPaymentMethodsCanPay();
checkPaymentMethodsCanPay( true );
wpDataFunctions
.dispatch( PAYMENT_STORE_KEY )
.__internalUpdateAvailablePaymentMethods();
};

const resetMockPaymentMethods = () => {
Expand Down