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

Refactor existing error notices from the API #7728

Merged
merged 20 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from 14 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
1 change: 0 additions & 1 deletion assets/js/base/context/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export * from './use-store-products';
export * from './use-store-add-to-cart';
export * from './use-customer-data';
export * from './use-checkout-address';
export * from './use-checkout-notices';
export * from './use-checkout-submit';
export * from './use-checkout-extension-data';
export * from './use-validation';
55 changes: 0 additions & 55 deletions assets/js/base/context/hooks/use-checkout-notices.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ import {
* Internal dependencies
*/
import { useEventEmitters, reducer as emitReducer } from './event-emit';
import type { emitterCallback } from '../../../event-emit';
import { emitterCallback, noticeContexts } from '../../../event-emit';
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,
Expand Down Expand Up @@ -111,11 +110,29 @@ export const CheckoutEventsProvider = ( {
}

const { setValidationErrors } = useDispatch( VALIDATION_STORE_KEY );
const { createErrorNotice } = useDispatch( 'core/notices' );

const { dispatchCheckoutEvent } = useStoreEvents();
const { checkoutNotices, paymentNotices, expressPaymentNotices } =
useCheckoutNotices();
useSelect( ( select ) => {
const { getNotices } = select( 'core/notices' );
const checkoutContexts = Object.values( noticeContexts ).filter(
( context ) =>
context !== noticeContexts.PAYMENTS &&
context !== noticeContexts.EXPRESS_PAYMENTS
);
const allCheckoutNotices = checkoutContexts.reduce(
( acc, context ) => {
return [ ...acc, ...getNotices( context ) ];
},
[]
);
return {
checkoutNotices: allCheckoutNotices,
paymentNotices: getNotices( noticeContexts.PAYMENTS ),
expressPaymentNotices: getNotices(
noticeContexts.EXPRESS_PAYMENTS
),
};
}, [] );

const [ observers, observerDispatch ] = useReducer( emitReducer, {} );
const currentObservers = useRef( observers );
Expand Down Expand Up @@ -160,12 +177,7 @@ export const CheckoutEventsProvider = ( {
setValidationErrors,
} );
}
}, [
checkoutState.status,
setValidationErrors,
createErrorNotice,
checkoutActions,
] );
}, [ checkoutState.status, setValidationErrors, checkoutActions ] );

const previousStatus = usePrevious( checkoutState.status );
const previousHasError = usePrevious( checkoutState.hasError );
Expand Down Expand Up @@ -199,7 +211,6 @@ export const CheckoutEventsProvider = ( {
checkoutState.orderNotes,
previousStatus,
previousHasError,
createErrorNotice,
checkoutNotices,
expressPaymentNotices,
paymentNotices,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';
import triggerFetch from '@wordpress/api-fetch';
import {
useEffect,
Expand All @@ -12,18 +12,25 @@ import {
} from '@wordpress/element';
import {
emptyHiddenAddressFields,
formatStoreApiErrorMessage,
removeAllNotices,
} from '@woocommerce/base-utils';
import { useDispatch, useSelect } from '@wordpress/data';
import {
CHECKOUT_STORE_KEY,
PAYMENT_STORE_KEY,
VALIDATION_STORE_KEY,
processErrorResponse,
} from '@woocommerce/block-data';
import {
getPaymentMethods,
getExpressPaymentMethods,
} from '@woocommerce/blocks-registry';
import {
ApiResponse,
CheckoutResponseSuccess,
CheckoutResponseError,
assertResponseIsValid,
} from '@woocommerce/types';

/**
* Internal dependencies
Expand All @@ -41,7 +48,6 @@ import { useStoreCart } from '../../hooks/cart/use-store-cart';
*/
const CheckoutProcessor = () => {
const { onCheckoutValidationBeforeProcessing } = useCheckoutEventsContext();

const {
hasError: checkoutHasError,
redirectUrl,
Expand All @@ -60,17 +66,14 @@ const CheckoutProcessor = () => {
isComplete: store.isComplete(),
};
} );

const { __internalSetHasError, __internalProcessCheckoutResponse } =
useDispatch( CHECKOUT_STORE_KEY );

const hasValidationErrors = useSelect(
( select ) => select( VALIDATION_STORE_KEY ).hasValidationErrors
);
const { shippingErrorStatus } = useShippingDataContext();
const { billingAddress, shippingAddress } = useCustomerDataContext();
const { cartNeedsPayment, cartNeedsShipping, receiveCart } = useStoreCart();
const { createErrorNotice, removeNotice } = useDispatch( 'core/notices' );

const {
activePaymentMethod,
Expand Down Expand Up @@ -118,8 +121,8 @@ const CheckoutProcessor = () => {
( isPaymentSuccess || ! cartNeedsPayment ) &&
checkoutIsProcessing;

// Determine if checkout has an error.
useEffect( () => {
// Determine if checkout has an error.
if (
checkoutWillHaveError !== checkoutHasError &&
( checkoutIsProcessing || checkoutIsBeforeProcessing ) &&
Expand All @@ -136,8 +139,8 @@ const CheckoutProcessor = () => {
__internalSetHasError,
] );

// Keep the billing, shipping and redirectUrl current
useEffect( () => {
// Keep the billing, shipping and redirectUrl current
currentBillingAddress.current = billingAddress;
currentShippingAddress.current = shippingAddress;
currentRedirectUrl.current = redirectUrl;
Expand Down Expand Up @@ -167,17 +170,20 @@ const CheckoutProcessor = () => {
return true;
}, [ hasValidationErrors, hasPaymentError, shippingErrorStatus.hasError ] );

// Validate the checkout using the CHECKOUT_VALIDATION_BEFORE_PROCESSING event
useEffect( () => {
let unsubscribeProcessing;
// Validate the checkout using the CHECKOUT_VALIDATION_BEFORE_PROCESSING event.
let unsubscribeProcessing: () => void;
if ( ! isExpressPaymentMethodActive ) {
unsubscribeProcessing = onCheckoutValidationBeforeProcessing(
checkValidation,
0
);
}
return () => {
if ( ! isExpressPaymentMethodActive ) {
if (
! isExpressPaymentMethodActive &&
typeof unsubscribeProcessing === 'function'
) {
unsubscribeProcessing();
}
};
Expand All @@ -187,8 +193,8 @@ const CheckoutProcessor = () => {
isExpressPaymentMethodActive,
] );

// Redirect when checkout is complete and there is a redirect url.
useEffect( () => {
// Redirect when checkout is complete and there is a redirect url.
if ( currentRedirectUrl.current ) {
window.location.href = currentRedirectUrl.current;
}
Expand All @@ -199,8 +205,8 @@ const CheckoutProcessor = () => {
if ( isProcessingOrder ) {
return;
}
removeAllNotices();
setIsProcessingOrder( true );
removeNotice( 'checkout' );

const paymentData = cartNeedsPayment
? {
Expand All @@ -213,97 +219,67 @@ const CheckoutProcessor = () => {
}
: {};

const data = {
billing_address: emptyHiddenAddressFields(
currentBillingAddress.current
),
customer_note: orderNotes,
create_account: shouldCreateAccount,
...paymentData,
extensions: { ...extensionData },
};

if ( cartNeedsShipping ) {
data.shipping_address = emptyHiddenAddressFields(
currentShippingAddress.current
);
}

triggerFetch( {
path: '/wc/store/v1/checkout',
method: 'POST',
data,
data: {
shipping_address: cartNeedsShipping
? emptyHiddenAddressFields( currentShippingAddress.current )
: undefined,
billing_address: emptyHiddenAddressFields(
currentBillingAddress.current
),
customer_note: orderNotes,
create_account: shouldCreateAccount,
...paymentData,
extensions: { ...extensionData },
},
cache: 'no-store',
parse: false,
} )
.then( ( response ) => {
.then( ( response: unknown ) => {
assertResponseIsValid( response );
processCheckoutResponseHeaders( response.headers );
if ( ! response.ok ) {
throw new Error( response );
throw response;
}
return response.json();
return response.json() as Promise< CheckoutResponseSuccess >;
} )
.then( ( responseJson ) => {
.then( ( responseJson: CheckoutResponseSuccess ) => {
__internalProcessCheckoutResponse( responseJson );
setIsProcessingOrder( false );
} )
.catch( ( errorResponse ) => {
.catch( ( errorResponse: ApiResponse ) => {
processCheckoutResponseHeaders( errorResponse?.headers );
try {
if ( errorResponse?.headers ) {
processCheckoutResponseHeaders( errorResponse.headers );
}
// This attempts to parse a JSON error response where the status code was 4xx/5xx.
errorResponse.json().then( ( response ) => {
// If updated cart state was returned, update the store.
if ( response.data?.cart ) {
receiveCart( response.data.cart );
}
createErrorNotice(
formatStoreApiErrorMessage( response ),
{
id: 'checkout',
context: 'wc/checkout',
__unstableHTML: true,
errorResponse
.json()
.then(
( response ) => response as CheckoutResponseError
)
.then( ( response: CheckoutResponseError ) => {
if ( response.data?.cart ) {
receiveCart( response.data.cart );
}
);
response?.additional_errors?.forEach?.(
( additionalError ) => {
createErrorNotice( additionalError.message, {
id: additionalError.error_code,
context: 'wc/checkout',
__unstableHTML: true,
} );
}
);
__internalProcessCheckoutResponse( response );
} );
processErrorResponse( response );
Copy link
Member Author

Choose a reason for hiding this comment

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

processErrorResponse is the new shared util which translates error codes to notices.

__internalProcessCheckoutResponse( response );
} );
} catch {
createErrorNotice(
sprintf(
// Translators: %s Error text.
__(
'%s Please try placing your order again.',
'woo-gutenberg-products-block'
),
errorResponse?.message ??
__(
'Something went wrong. Please contact us for assistance.',
'woo-gutenberg-products-block'
)
processErrorResponse( {
code: 'unknown_error',
message: __(
'Something went wrong. Please try placing your order again.',
'woo-gutenberg-products-block'
),
{
id: 'checkout',
context: 'wc/checkout',
__unstableHTML: true,
}
);
data: null,
} );
}
__internalSetHasError( true );
setIsProcessingOrder( false );
} );
}, [
isProcessingOrder,
removeNotice,
cartNeedsPayment,
paymentMethodId,
paymentMethodData,
Expand All @@ -313,7 +289,6 @@ const CheckoutProcessor = () => {
shouldCreateAccount,
extensionData,
cartNeedsShipping,
createErrorNotice,
receiveCart,
__internalSetHasError,
__internalProcessCheckoutResponse,
Expand Down
Loading