woocommerce/plugins/woocommerce-blocks/assets/js/data/checkout/utils.ts

225 lines
6.6 KiB
TypeScript
Raw Normal View History

Move checkout state code into thunks and rename `CheckoutState` context to `CheckoutEvents` (https://github.com/woocommerce/woocommerce-blocks/pull/6455) * Add checkout data store * wip on checkout data store * CheckoutContext now uses the checkout store * Investigated and removed setting the redirectUrl on the default state * update extension and address hooks to use checkout data store * use checkout data store in checkout-processor and use-checkout-button * trim useCheckoutContext from use-payment-method-interface && use-store-cart-item-quantity * Remove useCheckoutContext from shipping provider * Remove isCalculating from state * Removed useCheckoutContext from lots of places * Remove useCheckoutContext from checkout-payment-block * Remove useCheckoutContext in checkout-shipping-methods-block and checkout-shipping-address-block * add isCart selector and action and update the checkoutstate context * Fixed redirectUrl bug by using thunks * Remove dispatchActions from checkout-state * Change SET_HAS_ERROR action to be neater * Thomas' feedback * Tidy up * Oops, deleted things I shouldn't have * Typescript * Fix types * Fix tests * Remove isCart * Update docs and remove unecessary getRedirectUrl() selector * validate event emitter button * Added thunks in a separate file * Call thunks from checkout-state * Checkout logic tested and working * Remove dependency injection as much as poss, tidy up and fix some TS errors * Fix types in thunks.ts * Fixed some ts errors * WIP * Fixed bug * Shift side effects from checkout-state to checkout-processor * Revert "Shift side effects from checkout-state to checkout-processor" This reverts commit 059533da4eb34f9982f66cd4adacc7b2c24f939f. * Rename CheckoutState to CheckoutEvents * Move status check outside the thunk * remove duplicate EVENTS constant * remove temp buttons * Remove console logs * Augment @wordpress/data package with our new store types * Add correct type for CheckoutAction * Remove createErrorNotice arg from runCheckoutAfterProcessingWithErrorObservers * Remove createErrorNotice from emit event types * Use type keyword when importing types * Add correct types for dispatch and select in thunks * Update wordpress/data types * Replace store creation with new preferred method * Set correct action type on reducer * Remove unnecessary async from thunk * add CHECKOUT_ prefix to checkout events again * export EVENTS with eveything else in checkout0-events/event-emit * Remove duplicate SelectFromMap and TailParameters * Updated type for paymentStatus * TODO remove wp/data experimental thunks * Remove `setCustomerId` from events and `processCheckoutResponseHeaders` (https://github.com/woocommerce/woocommerce-blocks/pull/6586) * Prevent passing dispatch, instead get actions direct from store * Get setCustomerId from the store instead of passing it to processCheckoutResponseHeaders * Revert "Prevent passing dispatch, instead get actions direct from store" This reverts commit 4479a2ef5599d9c8d99c3629616b3d662210fc08. * Auto stash before revert of "Prevent passing dispatch, instead get actions direct from store" * Remove duplicate dispatch * Fix unit tests Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
2022-06-21 14:09:22 +00:00
/**
* External dependencies
*/
import { isString, isObject } from '@woocommerce/types';
import { __ } from '@wordpress/i18n';
import { decodeEntities } from '@wordpress/html-entities';
import type { PaymentResult, CheckoutResponse } from '@woocommerce/types';
import type { createErrorNotice as originalCreateErrorNotice } from '@wordpress/notices/store/actions';
/**
* Internal dependencies
*/
import { useEmitResponse } from '../../base/context/hooks/use-emit-response';
import {
CheckoutAndPaymentNotices,
CheckoutAfterProcessingWithErrorEventData,
} from './types';
import { DispatchFromMap } from '../mapped-types';
import * as actions from './actions';
2022-06-23 09:15:25 +00:00
const { isErrorResponse, isFailResponse, isSuccessResponse, shouldRetry } =
useEmitResponse(); // eslint-disable-line react-hooks/rules-of-hooks
Move checkout state code into thunks and rename `CheckoutState` context to `CheckoutEvents` (https://github.com/woocommerce/woocommerce-blocks/pull/6455) * Add checkout data store * wip on checkout data store * CheckoutContext now uses the checkout store * Investigated and removed setting the redirectUrl on the default state * update extension and address hooks to use checkout data store * use checkout data store in checkout-processor and use-checkout-button * trim useCheckoutContext from use-payment-method-interface && use-store-cart-item-quantity * Remove useCheckoutContext from shipping provider * Remove isCalculating from state * Removed useCheckoutContext from lots of places * Remove useCheckoutContext from checkout-payment-block * Remove useCheckoutContext in checkout-shipping-methods-block and checkout-shipping-address-block * add isCart selector and action and update the checkoutstate context * Fixed redirectUrl bug by using thunks * Remove dispatchActions from checkout-state * Change SET_HAS_ERROR action to be neater * Thomas' feedback * Tidy up * Oops, deleted things I shouldn't have * Typescript * Fix types * Fix tests * Remove isCart * Update docs and remove unecessary getRedirectUrl() selector * validate event emitter button * Added thunks in a separate file * Call thunks from checkout-state * Checkout logic tested and working * Remove dependency injection as much as poss, tidy up and fix some TS errors * Fix types in thunks.ts * Fixed some ts errors * WIP * Fixed bug * Shift side effects from checkout-state to checkout-processor * Revert "Shift side effects from checkout-state to checkout-processor" This reverts commit 059533da4eb34f9982f66cd4adacc7b2c24f939f. * Rename CheckoutState to CheckoutEvents * Move status check outside the thunk * remove duplicate EVENTS constant * remove temp buttons * Remove console logs * Augment @wordpress/data package with our new store types * Add correct type for CheckoutAction * Remove createErrorNotice arg from runCheckoutAfterProcessingWithErrorObservers * Remove createErrorNotice from emit event types * Use type keyword when importing types * Add correct types for dispatch and select in thunks * Update wordpress/data types * Replace store creation with new preferred method * Set correct action type on reducer * Remove unnecessary async from thunk * add CHECKOUT_ prefix to checkout events again * export EVENTS with eveything else in checkout0-events/event-emit * Remove duplicate SelectFromMap and TailParameters * Updated type for paymentStatus * TODO remove wp/data experimental thunks * Remove `setCustomerId` from events and `processCheckoutResponseHeaders` (https://github.com/woocommerce/woocommerce-blocks/pull/6586) * Prevent passing dispatch, instead get actions direct from store * Get setCustomerId from the store instead of passing it to processCheckoutResponseHeaders * Revert "Prevent passing dispatch, instead get actions direct from store" This reverts commit 4479a2ef5599d9c8d99c3629616b3d662210fc08. * Auto stash before revert of "Prevent passing dispatch, instead get actions direct from store" * Remove duplicate dispatch * Fix unit tests Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
2022-06-21 14:09:22 +00:00
// TODO: `useEmitResponse` is not a react hook, it just exposes some functions as
// properties of an object. Refactor this to not be a hook, we could simply import
// those functions where needed
/**
* Based on the given observers, create Error Notices where necessary
* and return the error response of the last registered observer
*/
export const handleErrorResponse = ( {
observerResponses,
createErrorNotice,
}: {
observerResponses: unknown[];
createErrorNotice: typeof originalCreateErrorNotice;
} ) => {
let errorResponse = null;
observerResponses.forEach( ( response ) => {
if ( isErrorResponse( response ) || isFailResponse( response ) ) {
if ( response.message && isString( response.message ) ) {
const errorOptions =
response.messageContext &&
isString( response.messageContext )
? // The `as string` is OK here because of the type guard above.
{
context: response.messageContext as string,
}
: undefined;
errorResponse = response;
createErrorNotice( response.message, errorOptions );
}
}
} );
return errorResponse;
};
/**
* This functions runs after the CHECKOUT_AFTER_PROCESSING_WITH_ERROR event has been triggered and
* all observers have been processed. It sets any Error Notices and the status of the Checkout
* based on the observer responses
*/
export const runCheckoutAfterProcessingWithErrorObservers = ( {
observerResponses,
notices,
dispatch,
createErrorNotice,
data,
}: {
observerResponses: unknown[];
notices: CheckoutAndPaymentNotices;
dispatch: DispatchFromMap< typeof actions >;
data: CheckoutAfterProcessingWithErrorEventData;
createErrorNotice: typeof originalCreateErrorNotice;
} ) => {
const errorResponse = handleErrorResponse( {
observerResponses,
createErrorNotice,
} );
if ( errorResponse !== null ) {
// irrecoverable error so set complete
if ( ! shouldRetry( errorResponse ) ) {
dispatch.setComplete( errorResponse );
} else {
dispatch.setIdle();
}
} else {
const hasErrorNotices =
notices.checkoutNotices.some(
( notice: { status: string } ) => notice.status === 'error'
) ||
notices.expressPaymentNotices.some(
( notice: { status: string } ) => notice.status === 'error'
) ||
notices.paymentNotices.some(
( notice: { status: string } ) => notice.status === 'error'
);
if ( ! hasErrorNotices ) {
// no error handling in place by anything so let's fall
// back to default
const message =
data.processingResponse?.message ||
__(
'Something went wrong. Please contact us to get assistance.',
'woo-gutenberg-products-block'
);
createErrorNotice( message, {
id: 'checkout',
context: 'wc/checkout',
} );
}
dispatch.setIdle();
}
};
/**
* This functions runs after the CHECKOUT_AFTER_PROCESSING_WITH_SUCCESS event has been triggered and
* all observers have been processed. It sets any Error Notices and the status of the Checkout
* based on the observer responses
*/
export const runCheckoutAfterProcessingWithSuccessObservers = ( {
observerResponses,
dispatch,
createErrorNotice,
}: {
observerResponses: unknown[];
dispatch: DispatchFromMap< typeof actions >;
createErrorNotice: typeof originalCreateErrorNotice;
} ) => {
let successResponse = null as null | Record< string, unknown >;
let errorResponse = null as null | Record< string, unknown >;
observerResponses.forEach( ( response ) => {
if ( isSuccessResponse( response ) ) {
// the last observer response always "wins" for success.
successResponse = response;
}
if ( isErrorResponse( response ) || isFailResponse( response ) ) {
errorResponse = response;
}
} );
if ( successResponse && ! errorResponse ) {
dispatch.setComplete( successResponse );
} else if ( isObject( errorResponse ) ) {
if ( errorResponse.message && isString( errorResponse.message ) ) {
const errorOptions =
errorResponse.messageContext &&
isString( errorResponse.messageContext )
? {
context: errorResponse.messageContext,
}
: undefined;
createErrorNotice( errorResponse.message, errorOptions );
}
if ( ! shouldRetry( errorResponse ) ) {
dispatch.setComplete( errorResponse );
} else {
// this will set an error which will end up
// triggering the onCheckoutAfterProcessingWithError emitter.
// and then setting checkout to IDLE state.
dispatch.setHasError( true );
}
} else {
// nothing hooked in had any response type so let's just consider successful.
dispatch.setComplete();
}
};
/**
* Prepares the payment_result data from the server checkout endpoint response.
*/
export const getPaymentResultFromCheckoutResponse = (
response: CheckoutResponse
): PaymentResult => {
const paymentResult = {
message: '',
paymentStatus: 'not set',
redirectUrl: '',
paymentDetails: {},
} as PaymentResult;
// payment_result is present in successful responses.
if ( 'payment_result' in response ) {
paymentResult.paymentStatus = response.payment_result.payment_status;
paymentResult.redirectUrl = response.payment_result.redirect_url;
if (
response.payment_result.hasOwnProperty( 'payment_details' ) &&
Array.isArray( response.payment_result.payment_details )
) {
response.payment_result.payment_details.forEach(
( { key, value }: { key: string; value: string } ) => {
2022-06-23 09:15:25 +00:00
paymentResult.paymentDetails[ key ] =
decodeEntities( value );
Move checkout state code into thunks and rename `CheckoutState` context to `CheckoutEvents` (https://github.com/woocommerce/woocommerce-blocks/pull/6455) * Add checkout data store * wip on checkout data store * CheckoutContext now uses the checkout store * Investigated and removed setting the redirectUrl on the default state * update extension and address hooks to use checkout data store * use checkout data store in checkout-processor and use-checkout-button * trim useCheckoutContext from use-payment-method-interface && use-store-cart-item-quantity * Remove useCheckoutContext from shipping provider * Remove isCalculating from state * Removed useCheckoutContext from lots of places * Remove useCheckoutContext from checkout-payment-block * Remove useCheckoutContext in checkout-shipping-methods-block and checkout-shipping-address-block * add isCart selector and action and update the checkoutstate context * Fixed redirectUrl bug by using thunks * Remove dispatchActions from checkout-state * Change SET_HAS_ERROR action to be neater * Thomas' feedback * Tidy up * Oops, deleted things I shouldn't have * Typescript * Fix types * Fix tests * Remove isCart * Update docs and remove unecessary getRedirectUrl() selector * validate event emitter button * Added thunks in a separate file * Call thunks from checkout-state * Checkout logic tested and working * Remove dependency injection as much as poss, tidy up and fix some TS errors * Fix types in thunks.ts * Fixed some ts errors * WIP * Fixed bug * Shift side effects from checkout-state to checkout-processor * Revert "Shift side effects from checkout-state to checkout-processor" This reverts commit 059533da4eb34f9982f66cd4adacc7b2c24f939f. * Rename CheckoutState to CheckoutEvents * Move status check outside the thunk * remove duplicate EVENTS constant * remove temp buttons * Remove console logs * Augment @wordpress/data package with our new store types * Add correct type for CheckoutAction * Remove createErrorNotice arg from runCheckoutAfterProcessingWithErrorObservers * Remove createErrorNotice from emit event types * Use type keyword when importing types * Add correct types for dispatch and select in thunks * Update wordpress/data types * Replace store creation with new preferred method * Set correct action type on reducer * Remove unnecessary async from thunk * add CHECKOUT_ prefix to checkout events again * export EVENTS with eveything else in checkout0-events/event-emit * Remove duplicate SelectFromMap and TailParameters * Updated type for paymentStatus * TODO remove wp/data experimental thunks * Remove `setCustomerId` from events and `processCheckoutResponseHeaders` (https://github.com/woocommerce/woocommerce-blocks/pull/6586) * Prevent passing dispatch, instead get actions direct from store * Get setCustomerId from the store instead of passing it to processCheckoutResponseHeaders * Revert "Prevent passing dispatch, instead get actions direct from store" This reverts commit 4479a2ef5599d9c8d99c3629616b3d662210fc08. * Auto stash before revert of "Prevent passing dispatch, instead get actions direct from store" * Remove duplicate dispatch * Fix unit tests Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
2022-06-21 14:09:22 +00:00
}
);
}
}
// message is present in error responses.
if ( 'message' in response ) {
paymentResult.message = decodeEntities( response.message );
}
// If there was an error code but no message, set a default message.
if (
! paymentResult.message &&
'data' in response &&
'status' in response.data &&
response.data.status > 299
) {
paymentResult.message = __(
'Something went wrong. Please contact us to get assistance.',
'woo-gutenberg-products-block'
);
}
return paymentResult;
};