woocommerce/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/checkout/index.js

214 lines
6.1 KiB
JavaScript
Raw Normal View History

/**
* External dependencies
*/
import {
createContext,
useContext,
useReducer,
useRef,
useMemo,
useEffect,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { PaymentMethodDataProvider } from '../payment-methods';
import { ShippingDataProvider } from '../shipping';
import { BillingDataProvider } from '../billing';
import { actions } from './actions';
import { reducer } from './reducer';
import { DEFAULT_STATE, STATUS } from './constants';
import {
EMIT_TYPES,
emitterSubscribers,
emitEvent,
emitEventWithAbort,
reducer as emitReducer,
} from './event-emit';
import CheckoutProcessor from './processor/index.js';
/**
* @typedef {import('@woocommerce/type-defs/checkout').CheckoutDispatchActions} CheckoutDispatchActions
* @typedef {import('@woocommerce/type-defs/contexts').CheckoutDataContext} CheckoutDataContext
*/
const CheckoutContext = createContext( {
submitLabel: '',
onSubmit: () => void null,
isComplete: false,
isIdle: false,
isCalculating: false,
isProcessing: false,
hasError: false,
redirectUrl: '',
onCheckoutCompleteSuccess: () => void null,
onCheckoutCompleteError: () => void null,
onCheckoutProcessing: () => void null,
dispatchActions: {
resetCheckout: () => void null,
setRedirectUrl: () => void null,
setHasError: () => void null,
clearError: () => void null,
incrementCalculating: () => void null,
decrementCalculating: () => void null,
},
} );
/**
* @return {CheckoutDataContext} Returns the checkout data context value
*/
export const useCheckoutContext = () => {
return useContext( CheckoutContext );
};
/**
* Checkout provider
* This wraps the checkout and provides an api interface for the checkout to
* children via various hooks.
*
* @param {Object} props Incoming props for the provider.
* @param {Array} props.children The children being wrapped.
* @param {string} props.activePaymentMethod Can be used to set what the
* initial active payment method is
* set to.
* @param {string} props.redirectUrl Initialize what the checkout will
* redirect to after successful
* submit.
* @param {string} props.submitLabel What will be used for the checkout
* submit button label.
* @param {boolean} props.isEditor Whether the checkout is in the
* editor context or not.
*/
export const CheckoutProvider = ( {
children,
activePaymentMethod: initialActivePaymentMethod,
redirectUrl,
isEditor,
submitLabel = __( 'Place Order', 'woo-gutenberg-product-block' ),
} ) => {
// note, this is done intentionally so that the default state now has
// the redirectUrl for when checkout is reset to PRISTINE state.
DEFAULT_STATE.redirectUrl = redirectUrl;
const [ checkoutState, dispatch ] = useReducer( reducer, DEFAULT_STATE );
const [ observers, subscriber ] = useReducer( emitReducer, {} );
const currentObservers = useRef( observers );
// set observers on ref so it's always current
useEffect( () => {
currentObservers.current = observers;
}, [ observers ] );
Implement Stripe CC and Stripe ApplePay payment methods (https://github.com/woocommerce/woocommerce-blocks/pull/1983) * Server side changes for payment method integrations Including adding a stripe class temporarily * update needed npm packages (and add some types) * updates to contexts * remove stepContent from payment config for payment methods * update payment method interface and typedefs Exposing a components property to pass along components that payment methods can use (so we keep styles consistent for them) * add apple pay and stripe cc integration and remove paypal * remove save payment checkbox from checkout block It is handled by payment methods. * Include an id prop for tabs * fix activePaymentMethod pass through on rendered payment method element also adds an id for the rendered tab * add styles for payment method fields If payment methods use these classes for their fields then the styles will get applied. It _could_ allow for consistent styling, we may have to provide design documentation for this? These are styles in cases where payment methods have to use elements provided by the gateway (eg. Stripe elements). In future iterations we could look at providing components to payment methods to use (if they aren’t restricted by the gateway). * fix rebase conflict * do a test payment request for applePay to determine if the current browser supports it * don’t console.error for stripe loading. * Fix placeholder errors in the editor * improve styling and add missing validation for inline card element * update pacakge-lock * rename payment-methods-demo folder to payment-methods-extension * expose checkbox control on payment method interface * export payment-methods-extension to it’s own asset build This allows us to more accurately demonstrate how payment extensions would hook in to the blocks. * don’t enqueue a style that doesn’t exist * add full stop to comments and remove obsolete comment blcok * fix spacing * switch `activeContent` to `content` for payment method registration config
2020-03-30 12:07:49 +00:00
const onCheckoutCompleteSuccess = useMemo(
() => emitterSubscribers( subscriber ).onCheckoutCompleteSuccess,
[]
);
const onCheckoutCompleteError = useMemo(
() => emitterSubscribers( subscriber ).onCheckoutCompleteError,
[]
);
const onCheckoutProcessing = useMemo(
() => emitterSubscribers( subscriber ).onCheckoutProcessing,
[]
);
/**
* @type {CheckoutDispatchActions}
*/
const dispatchActions = useMemo(
() => ( {
resetCheckout: () => void dispatch( actions.setPristine() ),
setRedirectUrl: ( url ) =>
void dispatch( actions.setRedirectUrl( url ) ),
setHasError: () => void dispatch( actions.setHasError() ),
clearError: () => void dispatch( actions.clearError() ),
incrementCalculating: () =>
void dispatch( actions.incrementCalculating() ),
decrementCalculating: () =>
void dispatch( actions.decrementCalculating() ),
} ),
[]
);
// emit events
useEffect( () => {
const status = checkoutState.status;
if ( status === STATUS.PROCESSING ) {
emitEventWithAbort(
currentObservers.current,
EMIT_TYPES.CHECKOUT_PROCESSING,
{}
).then( ( response ) => {
if ( response !== true ) {
// @todo handle any validation error property values in the
// response
dispatchActions.setHasError();
}
dispatch( actions.setComplete() );
} );
}
if ( checkoutState.isComplete ) {
if ( checkoutState.hasError ) {
emitEvent(
currentObservers.current,
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR,
{}
);
} else {
emitEvent(
currentObservers.current,
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
{}
).then( () => {
// all observers have done their thing so let's redirect
// (if no error)
if ( ! checkoutState.hasError ) {
window.location = checkoutState.redirectUrl;
}
} );
}
}
}, [
checkoutState.status,
checkoutState.hasError,
checkoutState.isComplete,
checkoutState.redirectUrl,
] );
const onSubmit = () => {
dispatch( actions.setProcessing() );
};
/**
* @type {CheckoutDataContext}
*/
const checkoutData = {
submitLabel,
onSubmit,
isComplete: checkoutState.status === STATUS.COMPLETE,
isIdle: checkoutState.status === STATUS.IDLE,
isCalculating: checkoutState.status === STATUS.CALCULATING,
isProcessing: checkoutState.status === STATUS.PROCESSING,
hasError: checkoutState.hasError,
redirectUrl: checkoutState.redirectUrl,
onCheckoutCompleteSuccess,
onCheckoutCompleteError,
onCheckoutProcessing,
dispatchActions,
isEditor,
};
return (
<CheckoutContext.Provider value={ checkoutData }>
<BillingDataProvider>
<PaymentMethodDataProvider
activePaymentMethod={ initialActivePaymentMethod }
>
<ShippingDataProvider>
{ children }
<CheckoutProcessor />
</ShippingDataProvider>
</PaymentMethodDataProvider>
</BillingDataProvider>
</CheckoutContext.Provider>
);
};