Skip to content

Commit

Permalink
Fix wrong keys being sent in canMakePayment and customer data showi…
Browse files Browse the repository at this point in the history
…ng in the Checkout block in the editor (woocommerce#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
  • Loading branch information
opr authored and senadir committed Nov 12, 2022
1 parent 5e79dce commit e652ebb
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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.
Expand Down Expand Up @@ -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()
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';
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

0 comments on commit e652ebb

Please sign in to comment.