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

Cart Checkout Flow Architectural Update (reference pull). Includes Apple Pay Integration POC #1780

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions assets/js/base/components/payment-methods/express-checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const ExpressCheckoutContainer = ( { children } ) => {
);
};

const ExpressCheckoutFormControl = () => {
const ExpressCheckoutFormControl = ( { isEditor } ) => {
const { paymentMethods, isInitialized } = useExpressPaymentMethods();

// determine whether we even show this
Expand All @@ -44,7 +44,7 @@ const ExpressCheckoutFormControl = () => {
'woo-gutenberg-products-block'
) }
</p>
<ExpressPaymentMethods />
<ExpressPaymentMethods isEditor={ isEditor } />
</ExpressCheckoutContainer>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
* External dependencies
*/
import {
useCheckoutData,
usePaymentEvents,
usePaymentMethodInterface,
useExpressPaymentMethods,
} from '@woocommerce/base-hooks';
import { cloneElement } from '@wordpress/element';
import { cloneElement, isValidElement } from '@wordpress/element';

const ExpressPaymentMethods = () => {
const [ checkoutData ] = useCheckoutData();
const { dispatch, select } = usePaymentEvents();
const ExpressPaymentMethods = ( { isEditor } ) => {
const paymentMethodInterface = usePaymentMethodInterface();
// not implementing isInitialized here because it's utilized further
// up in the tree for express payment methods. We won't even get here if
// there's no payment methods after initialization.
Expand All @@ -19,17 +17,16 @@ const ExpressPaymentMethods = () => {
const content =
paymentMethodSlugs.length > 0 ? (
paymentMethodSlugs.map( ( slug ) => {
const expressPaymentMethod =
paymentMethods[ slug ].activeContent;
const paymentEvents = { dispatch, select };
return (
const expressPaymentMethod = isEditor
? paymentMethods[ slug ].edit
: paymentMethods[ slug ].activeContent;
return isValidElement( expressPaymentMethod ) ? (
<li key={ slug } id={ `express-payment-method-${ slug }` }>
{ cloneElement( expressPaymentMethod, {
checkoutData,
paymentEvents,
...paymentMethodInterface,
} ) }
</li>
);
) : null;
} )
) : (
<li key="noneRegistered">No registered Payment Methods</li>
Expand Down
44 changes: 28 additions & 16 deletions assets/js/base/components/payment-methods/payment-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
* External dependencies
*/
import {
useCheckoutData,
usePaymentEvents,
useActivePaymentMethod,
usePaymentMethodInterface,
usePaymentMethods,
} from '@woocommerce/base-hooks';
import { useCallback, cloneElement } from '@wordpress/element';
import {
cloneElement,
useRef,
useCallback,
useEffect,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
Expand Down Expand Up @@ -38,30 +41,39 @@ const createTabs = ( paymentMethods ) => {
: [ noPaymentMethodTab() ];
};

const PaymentMethods = () => {
const [ checkoutData ] = useCheckoutData();
const { dispatch, select } = usePaymentEvents();
const PaymentMethods = ( { isEditor } ) => {
const { isInitialized, paymentMethods } = usePaymentMethods();
const {
activePaymentMethod,
setActivePaymentMethod,
} = useActivePaymentMethod();
...paymentMethodInterface
} = usePaymentMethodInterface();
const currentPaymentMethodInterface = useRef( paymentMethodInterface );
const currentPaymentMethods = useRef( paymentMethods );

// update refs on changes
useEffect( () => {
currentPaymentMethods.current = paymentMethods;
currentPaymentMethodInterface.current = paymentMethodInterface;
}, [ paymentMethods, paymentMethodInterface ] );

const getRenderedTab = useCallback(
() => ( selectedTab ) => {
const paymentMethod =
( paymentMethods[ selectedTab ] &&
paymentMethods[ selectedTab ].activeContent ) ||
null;
const paymentEvents = { dispatch, select };
let paymentMethod =
currentPaymentMethods.current[ selectedTab ] || null;
if ( paymentMethod ) {
paymentMethod = isEditor
? paymentMethod.edit
: paymentMethod.activeContent;
}
return paymentMethod
? cloneElement( paymentMethod, {
isActive: true,
checkoutData,
paymentEvents,
...currentPaymentMethodInterface.current,
} )
: null;
},
[ checkoutData, dispatch, select ]
[]
);
if (
! isInitialized ||
Expand Down
43 changes: 43 additions & 0 deletions assets/js/base/context/cart-checkout/checkout/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Internal dependencies
*/
import { TYPES } from './constants';

const {
SET_PRISTINE,
SET_PROCESSING,
SET_REDIRECT_URL,
SET_COMPLETE,
SET_HAS_ERROR,
SET_NO_ERROR,
INCREMENT_CALCULATING,
DECREMENT_CALCULATING,
} = TYPES;

export const actions = {
setPristine: () => ( {
type: SET_PRISTINE,
} ),
setProcessing: () => ( {
type: SET_PROCESSING,
} ),
setRedirectUrl: ( url ) => ( {
type: SET_REDIRECT_URL,
url,
} ),
setComplete: () => ( {
type: SET_COMPLETE,
} ),
setHasError: () => ( {
type: SET_HAS_ERROR,
} ),
clearError: () => ( {
type: SET_NO_ERROR,
} ),
incrementCalculating: () => ( {
type: INCREMENT_CALCULATING,
} ),
decrementCalculating: () => ( {
type: DECREMENT_CALCULATING,
} ),
};
31 changes: 31 additions & 0 deletions assets/js/base/context/cart-checkout/checkout/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @type {import("@woocommerce/type-defs/checkout").CheckoutStatusConstants}
*/
export const STATUS = {
PRISTINE: 'pristine',
IDLE: 'idle',
CALCULATING: 'calculating',
PROCESSING: 'processing',
COMPLETE: 'complete',
};

export const DEFAULT_STATE = {
redirectUrl: '',
status: STATUS.PRISTINE,
// this is used by the reducer to set what status the state will
// take after calculating is complete.
nextStatus: STATUS.IDLE,
hasError: false,
calculatingCount: 0,
};

export const TYPES = {
SET_PRISTINE: 'set_pristine',
SET_REDIRECT_URL: 'set_redirect_url',
SET_COMPLETE: 'set_checkout_complete',
SET_PROCESSING: 'set_checkout_is_processing',
SET_HAS_ERROR: 'set_checkout_has_error',
SET_NO_ERROR: 'set_checkout_no_error',
INCREMENT_CALCULATING: 'increment_calculating',
DECREMENT_CALCULATING: 'decrement_calculating',
};
73 changes: 73 additions & 0 deletions assets/js/base/context/cart-checkout/checkout/event-emit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Internal dependencies
*/
import { actions, reducer, emitEvent } from '../event_emit';

const EMIT_TYPES = {
CHECKOUT_COMPLETE_WITH_SUCCESS: 'checkout_complete',
CHECKOUT_COMPLETE_WITH_ERROR: 'checkout_complete_error',
CHECKOUT_PROCESSING: 'checkout_processing',
};

/**
* Receives a reducer dispatcher and returns an object with the
* onCheckoutComplete callback registration function for the checkout emit
* events.
*
* Calling the event registration function with the callback will register it
* for the event emitter and will return a dispatcher for removing the
* registered callback (useful for implementation in `useEffect`).
*
* @param {Function} dispatcher The emitter reducer dispatcher.
*
* @return {Object} An object with the `onCheckoutComplete` emmitter registration
*/
const emitterSubscribers = ( dispatcher ) => ( {
onCheckoutCompleteSuccess: ( callback ) => {
const action = actions.addEventCallback(
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
callback
);
dispatcher( action );
return () => {
dispatcher(
actions.removeEventCallback(
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
action.id
)
);
};
},
onCheckoutCompleteError: ( callback ) => {
const action = actions.addEventCallback(
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR,
callback
);
dispatcher( action );
return () => {
dispatcher(
actions.removeEventCallback(
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR,
action.id
)
);
};
},
onCheckoutProcessing: ( callback ) => {
const action = actions.addEventCallback(
EMIT_TYPES.CHECKOUT_PROCESSING,
callback
);
dispatcher( action );
return () => {
dispatcher(
actions.removeEventCallback(
EMIT_TYPES.CHECKOUT_PROCESSING,
action.id
)
);
};
},
} );

export { EMIT_TYPES, emitterSubscribers, reducer, emitEvent };
Loading