diff --git a/plugins/woocommerce-blocks/assets/js/base/context/hooks/cart/use-store-cart.ts b/plugins/woocommerce-blocks/assets/js/base/context/hooks/cart/use-store-cart.ts index 968e182d318..ac730f699be 100644 --- a/plugins/woocommerce-blocks/assets/js/base/context/hooks/cart/use-store-cart.ts +++ b/plugins/woocommerce-blocks/assets/js/base/context/hooks/cart/use-store-cart.ts @@ -26,7 +26,7 @@ import type { CartResponseBillingAddress, CartResponseShippingAddress, CartResponseCouponItem, - CartResponseCouponItemWithLabel, + CartResponseCoupons, } from '@woocommerce/types'; import { emptyHiddenAddressFields, @@ -129,6 +129,7 @@ export const defaultCartData: StoreCart = { * * @return {StoreCart} Object containing cart data. */ + export const useStoreCart = ( options: { shouldSelect: boolean } = { shouldSelect: true } ): StoreCart => { @@ -136,7 +137,6 @@ export const useStoreCart = ( const previewCart = previewData?.previewCart; const { shouldSelect } = options; const currentResults = useRef(); - const results: StoreCart = useSelect( ( select, { dispatch } ) => { if ( ! shouldSelect ) { @@ -194,7 +194,7 @@ export const useStoreCart = ( // Add a text property to the coupon to allow extensions to modify // the text used to display the coupon, without affecting the // functionality when it comes to removing the coupon. - const cartCoupons: CartResponseCouponItemWithLabel[] = + const cartCoupons: CartResponseCoupons = cartData.coupons.length > 0 ? cartData.coupons.map( ( coupon: CartResponseCouponItem ) => ( { diff --git a/plugins/woocommerce-blocks/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts b/plugins/woocommerce-blocks/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts index 68ae5cdfa39..e1985e42f77 100644 --- a/plugins/woocommerce-blocks/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts +++ b/plugins/woocommerce-blocks/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts @@ -15,6 +15,7 @@ import type { PaymentMethodConfigInstance, ExpressPaymentMethodConfigInstance, } from '@woocommerce/type-defs/payments'; +import { useDebouncedCallback } from 'use-debounce'; /** * Internal dependencies @@ -51,12 +52,10 @@ const usePaymentMethodRegistration = ( const { billingData, shippingAddress } = useCustomerDataContext(); const selectedShippingMethods = useShallowEqual( selectedRates ); const paymentMethodsOrder = useShallowEqual( paymentMethodsSortOrder ); - const { - cartTotals, - cartNeedsShipping, - paymentRequirements, - } = useStoreCart(); + const cart = useStoreCart(); + const { cartTotals, cartNeedsShipping, paymentRequirements } = cart; const canPayArgument = useRef( { + cart, cartTotals, cartNeedsShipping, billingData, @@ -68,6 +67,7 @@ const usePaymentMethodRegistration = ( useEffect( () => { canPayArgument.current = { + cart, cartTotals, cartNeedsShipping, billingData, @@ -76,6 +76,7 @@ const usePaymentMethodRegistration = ( paymentRequirements, }; }, [ + cart, cartTotals, cartNeedsShipping, billingData, @@ -160,16 +161,21 @@ const usePaymentMethodRegistration = ( registeredPaymentMethods, ] ); + const [ debouncedRefreshCanMakePayments ] = useDebouncedCallback( + refreshCanMakePayments, + 500 + ); + // Determine which payment methods are available initially and whenever - // shipping methods or cart totals change. + // shipping methods, cart or the billing data change. // Some payment methods (e.g. COD) can be disabled for specific shipping methods. useEffect( () => { - refreshCanMakePayments(); + debouncedRefreshCanMakePayments(); }, [ - refreshCanMakePayments, - cartTotals, + debouncedRefreshCanMakePayments, + cart, selectedShippingMethods, - paymentRequirements, + billingData, ] ); return isInitialized; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js index eeeeb381184..10e6c227c40 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js @@ -2,6 +2,9 @@ * External dependencies */ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { previewCart } from '@woocommerce/resource-previews'; +import { dispatch } from '@wordpress/data'; +import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data'; import { registerPaymentMethod, __experimentalDeRegisterPaymentMethod, @@ -15,6 +18,7 @@ import { * Internal dependencies */ import PaymentMethods from '../payment-methods'; +import { defaultCartState } from '../../../../data/default-states'; jest.mock( '../saved-payment-method-options', () => ( { onChange } ) => { return ( @@ -63,6 +67,22 @@ const resetMockPaymentMethods = () => { }; describe( 'PaymentMethods', () => { + beforeEach( async () => { + fetchMock.mockResponse( ( req ) => { + if ( req.url.match( /wc\/store\/cart/ ) ) { + return Promise.resolve( JSON.stringify( previewCart ) ); + } + return Promise.resolve( '' ); + } ); + // need to clear the store resolution state between tests. + await dispatch( storeKey ).invalidateResolutionForStore(); + await dispatch( storeKey ).receiveCart( defaultCartState.cartData ); + } ); + + afterEach( () => { + fetchMock.resetMocks(); + } ); + test( 'should show no payment methods component when there are no payment methods', async () => { render( @@ -78,6 +98,8 @@ describe( 'PaymentMethods', () => { // creates an extra `div` with the notice contents used for a11y. expect( noPaymentMethods.length ).toBeGreaterThanOrEqual( 1 ); } ); + // ["`select` control in `@wordpress/data-controls` is deprecated. Please use built-in `resolveSelect` control in `@wordpress/data` instead."] + expect( console ).toHaveWarned(); } ); test( 'selecting new payment method', async () => { diff --git a/plugins/woocommerce-blocks/assets/js/types/type-defs/cart-response.ts b/plugins/woocommerce-blocks/assets/js/types/type-defs/cart-response.ts index 69d3007cf29..ee968c9dd86 100644 --- a/plugins/woocommerce-blocks/assets/js/types/type-defs/cart-response.ts +++ b/plugins/woocommerce-blocks/assets/js/types/type-defs/cart-response.ts @@ -26,6 +26,8 @@ export interface CartResponseCouponItemWithLabel label: string; } +export type CartResponseCoupons = CartResponseCouponItemWithLabel[]; + export interface ResponseFirstNameLastName { first_name: string; last_name: string; diff --git a/plugins/woocommerce-blocks/assets/js/types/type-defs/hooks.ts b/plugins/woocommerce-blocks/assets/js/types/type-defs/hooks.ts index 6ce5a89656f..efe3d40dee9 100644 --- a/plugins/woocommerce-blocks/assets/js/types/type-defs/hooks.ts +++ b/plugins/woocommerce-blocks/assets/js/types/type-defs/hooks.ts @@ -11,6 +11,7 @@ import type { CartResponseBillingAddress, CartResponseShippingRate, CartResponse, + CartResponseCoupons, } from './cart-response'; import type { ResponseError } from '../../data/types'; export interface StoreCartItemQuantity { @@ -31,7 +32,7 @@ export interface StoreCartCoupon { } export interface StoreCart { - cartCoupons: Array< CartResponseCouponItem >; + cartCoupons: CartResponseCoupons; cartItems: Array< CartResponseItem >; cartFees: Array< CartResponseFeeItem >; cartItemsCount: number; diff --git a/plugins/woocommerce-blocks/assets/js/types/type-defs/payments.ts b/plugins/woocommerce-blocks/assets/js/types/type-defs/payments.ts index c4f94b32c75..46acb01f984 100644 --- a/plugins/woocommerce-blocks/assets/js/types/type-defs/payments.ts +++ b/plugins/woocommerce-blocks/assets/js/types/type-defs/payments.ts @@ -6,7 +6,7 @@ import type { ReactNode } from 'react'; /** * Internal dependencies */ -import type { CartTotals } from './cart'; +import type { CartTotals, Cart } from './cart'; import { CartResponseBillingAddress, CartResponseShippingAddress, @@ -27,6 +27,7 @@ export interface Supports extends SupportsConfiguration { } export interface CanMakePaymentArgument { + cart: Cart; cartTotals: CartTotals; cartNeedsShipping: boolean; billingData: CartResponseBillingAddress; diff --git a/plugins/woocommerce-blocks/docs/extensibility/payment-method-integration.md b/plugins/woocommerce-blocks/docs/extensibility/payment-method-integration.md index f3d8746678f..dd7a9ef964c 100644 --- a/plugins/woocommerce-blocks/docs/extensibility/payment-method-integration.md +++ b/plugins/woocommerce-blocks/docs/extensibility/payment-method-integration.md @@ -4,21 +4,21 @@ The checkout block has an API interface for payment methods to integrate that co ## Table of Contents -- [Client Side integration](#client-side-integration) - - [Express payment methods - `registerExpressPaymentMethod( options )`](#express-payment-methods---registerexpresspaymentmethod-options-) - - [`name` (required)](#name-required) - - [`content` (required)](#content-required) - - [`edit` (required)](#edit-required) - - [`canMakePayment` (required):](#canmakepayment-required) - - [`paymentMethodId`](#paymentmethodid) - - [`supports:features`](#supportsfeatures) - - [Payment Methods - `registerPaymentMethod( options )`](#payment-methods---registerpaymentmethod-options-) - - [Props Fed to Payment Method Nodes](#props-fed-to-payment-method-nodes) -- [Server Side Integration](#server-side-integration) - - [Processing Payment](#processing-payment) - - [Registering Assets](#registering-assets) - - [Hooking into the Checkout processing by the Store API.](#hooking-into-the-checkout-processing-by-the-store-api) - - [Putting it all together](#putting-it-all-together) +- [Client Side integration](#client-side-integration) + - [Express payment methods - `registerExpressPaymentMethod( options )`](#express-payment-methods---registerexpresspaymentmethod-options-) + - [`name` (required)](#name-required) + - [`content` (required)](#content-required) + - [`edit` (required)](#edit-required) + - [`canMakePayment` (required):](#canmakepayment-required) + - [`paymentMethodId`](#paymentmethodid) + - [`supports:features`](#supportsfeatures) + - [Payment Methods - `registerPaymentMethod( options )`](#payment-methods---registerpaymentmethod-options-) + - [Props Fed to Payment Method Nodes](#props-fed-to-payment-method-nodes) +- [Server Side Integration](#server-side-integration) + - [Processing Payment](#processing-payment) + - [Registering Assets](#registering-assets) + - [Hooking into the Checkout processing by the Store API.](#hooking-into-the-checkout-processing-by-the-store-api) + - [Putting it all together](#putting-it-all-together) ## Client Side integration @@ -52,7 +52,7 @@ The registry function expects a JavaScript object with options specific to the p registerExpressPaymentMethod( options ); ``` -The options you feed the configuration instance should be an object in this shape (see `ExpressPaymentMethodRegistrationOptions` typedef): +The options you feed the configuration instance should be an object in this shape (see `ExpressPaymentMethodConfiguration` typedef): ```js const options = { @@ -87,11 +87,12 @@ A callback to determine whether the payment method should be available as an opt ``` canMakePayment( { + cart: Cart, cartTotals: CartTotals, cartNeedsShipping: boolean, shippingAddress: CartShippingAddress, billingData: BillingData, - selectedShippingMethods: string[], + selectedShippingMethods: Record, paymentRequirements: string[], } ) ```