Fix wrong keys being sent in `canMakePayment` and customer data showing in the Checkout block in the editor (https://github.com/woocommerce/woocommerce-blocks/pull/7434)

* Construct args for canMakePayment with correct keys

* When the CheckoutEventsContext mounts, initialize payment store

* Destructure useSelect correctly

* Dispatch __internalInitializePaymentStore in selector tests

* Update selector name to __internalUpdateAvailablePaymentMethods

* Remove check for editor when registering checkout store

* Add check for when express payment methods have updated too

* Ensure billingAddress key exists in canMakePayment arg

* Use editor context to know if we're in editor
This commit is contained in:
Thomas Roberts 2022-10-20 16:02:43 +01:00 committed by GitHub
parent 48c450c469
commit 1eda6f8f3d
7 changed files with 149 additions and 47 deletions

View File

@ -16,6 +16,7 @@ import deprecated from '@wordpress/deprecated';
import { useDispatch, useSelect } from '@wordpress/data';
import {
CHECKOUT_STORE_KEY,
PAYMENT_STORE_KEY,
VALIDATION_STORE_KEY,
} from '@woocommerce/block-data';
@ -28,6 +29,11 @@ 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';
import { useEditorContext } from '../../editor-context';
type CheckoutEventsContextType = {
// Submits the checkout and begins processing.
@ -69,6 +75,32 @@ export const CheckoutEventsProvider = ( {
children: React.ReactChildren;
redirectUrl: string;
} ): JSX.Element => {
const paymentMethods = getPaymentMethods();
const expressPaymentMethods = getExpressPaymentMethods();
const { isEditor } = useEditorContext();
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()

View File

@ -81,7 +81,7 @@ const registerMockPaymentMethods = () => {
ariaLabel: name,
} );
} );
dispatch( PAYMENT_STORE_KEY ).__internalInitializePaymentStore();
dispatch( PAYMENT_STORE_KEY ).__internalUpdateAvailablePaymentMethods();
};
const resetMockPaymentMethods = () => {

View File

@ -50,7 +50,7 @@ const unsubscribeInitializePaymentStore = registeredStore.subscribe(
if ( cartLoaded ) {
wpDataDispatch(
'wc/store/payment'
).__internalInitializePaymentStore();
).__internalUpdateAvailablePaymentMethods();
unsubscribeInitializePaymentStore();
}
}

View File

@ -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
@ -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,
@ -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(

View File

@ -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 ) );
}
};
}

View File

@ -7,7 +7,10 @@ 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';
@ -15,6 +18,7 @@ import {
getExpressPaymentMethods,
getPaymentMethods,
} from '@woocommerce/blocks-registry';
import { previewCart } from '@woocommerce/resource-previews';
/**
* Internal dependencies
@ -22,6 +26,15 @@ import {
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';
import {
defaultBillingAddress,
defaultShippingAddress,
} from '../../base/context/providers/cart-checkout/customer/constants';
export const checkPaymentMethodsCanPay = async ( express = false ) => {
const isEditor = !! select( 'core/editor' );
@ -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 ) {

View File

@ -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;
@ -132,8 +131,9 @@ const registerMockPaymentMethods = ( savedCards = true ) => {
},
} );
} );
checkPaymentMethodsCanPay();
checkPaymentMethodsCanPay( true );
wpDataFunctions
.dispatch( PAYMENT_STORE_KEY )
.__internalUpdateAvailablePaymentMethods();
};
const resetMockPaymentMethods = () => {