Refactor checkout status and event emitters to support stripe intents and more complex payment methods. (https://github.com/woocommerce/woocommerce-blocks/pull/2189)
* initial mapping out of stripe payment intents * rename checkout processing statuses to be clearer * Add new status and refactor checkout complete behaviour. * Make sure payment result data is included in checkout processing response * add payment intent handling Still testing * make sure promise is returned * include site url with endpoint * modify setComplete status to optionally receive redirectUrl for changing in state at the same time as setting status * fix typo in property retrieval * add error handling for after checkout processing event * add notices area for payment methods * implement error handling for stripe intents * hook into stripe error processing and include error in payment response * clear notices so they don’t show in block and merge payment details * add notice handling to payment context * modify error processing in checkout processor * handle errors with fallback in checkout state context * hook into after processing for stripe cc error handling * set checkout to idle status if before processing emitters result in error * Add emit response type-defs and normalize expectations for observer responses * improve doc block * switch checkoutIsComplete check to checkoutAfterProcessing for payment complete status change * remove unneeded event emitters and consolidate some logic * fix idle status set logic
This commit is contained in:
parent
6b63f19fcb
commit
5850966894
|
@ -5,8 +5,11 @@ import { TYPES } from './constants';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
SET_PRISTINE,
|
SET_PRISTINE,
|
||||||
|
SET_IDLE,
|
||||||
SET_PROCESSING,
|
SET_PROCESSING,
|
||||||
SET_PROCESSING_COMPLETE,
|
SET_BEFORE_PROCESSING,
|
||||||
|
SET_AFTER_PROCESSING,
|
||||||
|
SET_PROCESSING_RESPONSE,
|
||||||
SET_REDIRECT_URL,
|
SET_REDIRECT_URL,
|
||||||
SET_COMPLETE,
|
SET_COMPLETE,
|
||||||
SET_HAS_ERROR,
|
SET_HAS_ERROR,
|
||||||
|
@ -23,6 +26,9 @@ export const actions = {
|
||||||
setPristine: () => ( {
|
setPristine: () => ( {
|
||||||
type: SET_PRISTINE,
|
type: SET_PRISTINE,
|
||||||
} ),
|
} ),
|
||||||
|
setIdle: () => ( {
|
||||||
|
type: SET_IDLE,
|
||||||
|
} ),
|
||||||
setProcessing: () => ( {
|
setProcessing: () => ( {
|
||||||
type: SET_PROCESSING,
|
type: SET_PROCESSING,
|
||||||
} ),
|
} ),
|
||||||
|
@ -30,11 +36,19 @@ export const actions = {
|
||||||
type: SET_REDIRECT_URL,
|
type: SET_REDIRECT_URL,
|
||||||
url,
|
url,
|
||||||
} ),
|
} ),
|
||||||
setComplete: () => ( {
|
setProcessingResponse: ( data ) => ( {
|
||||||
type: SET_COMPLETE,
|
type: SET_PROCESSING_RESPONSE,
|
||||||
|
data,
|
||||||
} ),
|
} ),
|
||||||
setProcessingComplete: () => ( {
|
setComplete: ( data ) => ( {
|
||||||
type: SET_PROCESSING_COMPLETE,
|
type: SET_COMPLETE,
|
||||||
|
data,
|
||||||
|
} ),
|
||||||
|
setBeforeProcessing: () => ( {
|
||||||
|
type: SET_BEFORE_PROCESSING,
|
||||||
|
} ),
|
||||||
|
setAfterProcessing: () => ( {
|
||||||
|
type: SET_AFTER_PROCESSING,
|
||||||
} ),
|
} ),
|
||||||
setHasError: ( hasError = true ) => {
|
setHasError: ( hasError = true ) => {
|
||||||
const type = hasError ? SET_HAS_ERROR : SET_NO_ERROR;
|
const type = hasError ? SET_HAS_ERROR : SET_NO_ERROR;
|
||||||
|
|
|
@ -11,7 +11,8 @@ export const STATUS = {
|
||||||
IDLE: 'idle',
|
IDLE: 'idle',
|
||||||
PROCESSING: 'processing',
|
PROCESSING: 'processing',
|
||||||
COMPLETE: 'complete',
|
COMPLETE: 'complete',
|
||||||
PROCESSING_COMPLETE: 'processing_complete',
|
BEFORE_PROCESSING: 'before_processing',
|
||||||
|
AFTER_PROCESSING: 'after_processing',
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkoutData = getSetting( 'checkoutData', {
|
const checkoutData = getSetting( 'checkoutData', {
|
||||||
|
@ -26,13 +27,17 @@ export const DEFAULT_STATE = {
|
||||||
calculatingCount: 0,
|
calculatingCount: 0,
|
||||||
orderId: checkoutData.order_id,
|
orderId: checkoutData.order_id,
|
||||||
customerId: checkoutData.customer_id,
|
customerId: checkoutData.customer_id,
|
||||||
|
processingResponse: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TYPES = {
|
export const TYPES = {
|
||||||
|
SET_IDLE: 'set_idle',
|
||||||
SET_PRISTINE: 'set_pristine',
|
SET_PRISTINE: 'set_pristine',
|
||||||
SET_REDIRECT_URL: 'set_redirect_url',
|
SET_REDIRECT_URL: 'set_redirect_url',
|
||||||
SET_COMPLETE: 'set_checkout_complete',
|
SET_COMPLETE: 'set_checkout_complete',
|
||||||
SET_PROCESSING_COMPLETE: 'set_processing_complete',
|
SET_BEFORE_PROCESSING: 'set_before_processing',
|
||||||
|
SET_AFTER_PROCESSING: 'set_after_processing',
|
||||||
|
SET_PROCESSING_RESPONSE: 'set_processing_response',
|
||||||
SET_PROCESSING: 'set_checkout_is_processing',
|
SET_PROCESSING: 'set_checkout_is_processing',
|
||||||
SET_HAS_ERROR: 'set_checkout_has_error',
|
SET_HAS_ERROR: 'set_checkout_has_error',
|
||||||
SET_NO_ERROR: 'set_checkout_no_error',
|
SET_NO_ERROR: 'set_checkout_no_error',
|
||||||
|
|
|
@ -9,14 +9,16 @@ import {
|
||||||
} from '../event-emit';
|
} from '../event-emit';
|
||||||
|
|
||||||
const EMIT_TYPES = {
|
const EMIT_TYPES = {
|
||||||
CHECKOUT_COMPLETE_WITH_SUCCESS: 'checkout_complete',
|
CHECKOUT_BEFORE_PROCESSING: 'checkout_before_processing',
|
||||||
CHECKOUT_COMPLETE_WITH_ERROR: 'checkout_complete_error',
|
CHECKOUT_AFTER_PROCESSING_WITH_SUCCESS:
|
||||||
CHECKOUT_PROCESSING: 'checkout_processing',
|
'checkout_after_processing_with_success',
|
||||||
|
CHECKOUT_AFTER_PROCESSING_WITH_ERROR:
|
||||||
|
'checkout_after_processing_with_error',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives a reducer dispatcher and returns an object with the
|
* Receives a reducer dispatcher and returns an object with the
|
||||||
* onCheckoutComplete callback registration function for the checkout emit
|
* callback registration function for the checkout emit
|
||||||
* events.
|
* events.
|
||||||
*
|
*
|
||||||
* Calling the event registration function with the callback will register it
|
* Calling the event registration function with the callback will register it
|
||||||
|
@ -25,19 +27,19 @@ const EMIT_TYPES = {
|
||||||
*
|
*
|
||||||
* @param {Function} dispatcher The emitter reducer dispatcher.
|
* @param {Function} dispatcher The emitter reducer dispatcher.
|
||||||
*
|
*
|
||||||
* @return {Object} An object with the `onCheckoutComplete` emmitter registration
|
* @return {Object} An object with the checkout emmitter registration
|
||||||
*/
|
*/
|
||||||
const emitterSubscribers = ( dispatcher ) => ( {
|
const emitterSubscribers = ( dispatcher ) => ( {
|
||||||
onCheckoutCompleteSuccess: emitterCallback(
|
onCheckoutAfterProcessingWithSuccess: emitterCallback(
|
||||||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
|
EMIT_TYPES.CHECKOUT_AFTER_PROCESSING_WITH_SUCCESS,
|
||||||
dispatcher
|
dispatcher
|
||||||
),
|
),
|
||||||
onCheckoutCompleteError: emitterCallback(
|
onCheckoutAfterProcessingWithError: emitterCallback(
|
||||||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR,
|
EMIT_TYPES.CHECKOUT_AFTER_PROCESSING_WITH_ERROR,
|
||||||
dispatcher
|
dispatcher
|
||||||
),
|
),
|
||||||
onCheckoutProcessing: emitterCallback(
|
onCheckoutBeforeProcessing: emitterCallback(
|
||||||
EMIT_TYPES.CHECKOUT_PROCESSING,
|
EMIT_TYPES.CHECKOUT_BEFORE_PROCESSING,
|
||||||
dispatcher
|
dispatcher
|
||||||
),
|
),
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -10,18 +10,19 @@ import {
|
||||||
useEffect,
|
useEffect,
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { useStoreNotices } from '@woocommerce/base-hooks';
|
import { useStoreNotices, useEmitResponse } from '@woocommerce/base-hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { actions } from './actions';
|
import { actions } from './actions';
|
||||||
import { reducer } from './reducer';
|
import { reducer, prepareResponseData } from './reducer';
|
||||||
import { DEFAULT_STATE, STATUS } from './constants';
|
import { DEFAULT_STATE, STATUS } from './constants';
|
||||||
import {
|
import {
|
||||||
EMIT_TYPES,
|
EMIT_TYPES,
|
||||||
emitterSubscribers,
|
emitterSubscribers,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
|
emitEventWithAbort,
|
||||||
reducer as emitReducer,
|
reducer as emitReducer,
|
||||||
} from './event-emit';
|
} from './event-emit';
|
||||||
import { useValidationContext } from '../validation';
|
import { useValidationContext } from '../validation';
|
||||||
|
@ -38,18 +39,20 @@ const CheckoutContext = createContext( {
|
||||||
isIdle: false,
|
isIdle: false,
|
||||||
isCalculating: false,
|
isCalculating: false,
|
||||||
isProcessing: false,
|
isProcessing: false,
|
||||||
isProcessingComplete: false,
|
isBeforeProcessing: false,
|
||||||
|
isAfterProcessing: false,
|
||||||
hasError: false,
|
hasError: false,
|
||||||
redirectUrl: '',
|
redirectUrl: '',
|
||||||
orderId: 0,
|
orderId: 0,
|
||||||
onCheckoutCompleteSuccess: ( callback ) => void callback,
|
customerId: 0,
|
||||||
onCheckoutCompleteError: ( callback ) => void callback,
|
onCheckoutAfterProcessingWithSuccess: ( callback ) => void callback,
|
||||||
onCheckoutProcessing: ( callback ) => void callback,
|
onCheckoutAfterProcessingWithError: ( callback ) => void callback,
|
||||||
|
onCheckoutBeforeProcessing: ( callback ) => void callback,
|
||||||
dispatchActions: {
|
dispatchActions: {
|
||||||
resetCheckout: () => void null,
|
resetCheckout: () => void null,
|
||||||
setRedirectUrl: ( url ) => void url,
|
setRedirectUrl: ( url ) => void url,
|
||||||
setHasError: ( hasError ) => void hasError,
|
setHasError: ( hasError ) => void hasError,
|
||||||
setComplete: () => void null,
|
setAfterProcessing: ( response ) => void response,
|
||||||
incrementCalculating: () => void null,
|
incrementCalculating: () => void null,
|
||||||
decrementCalculating: () => void null,
|
decrementCalculating: () => void null,
|
||||||
setOrderId: ( id ) => void id,
|
setOrderId: ( id ) => void id,
|
||||||
|
@ -94,23 +97,30 @@ export const CheckoutStateProvider = ( {
|
||||||
const currentObservers = useRef( observers );
|
const currentObservers = useRef( observers );
|
||||||
const { setValidationErrors } = useValidationContext();
|
const { setValidationErrors } = useValidationContext();
|
||||||
const { addErrorNotice, removeNotices } = useStoreNotices();
|
const { addErrorNotice, removeNotices } = useStoreNotices();
|
||||||
|
|
||||||
const isCalculating = checkoutState.calculatingCount > 0;
|
const isCalculating = checkoutState.calculatingCount > 0;
|
||||||
|
const {
|
||||||
|
isSuccessResponse,
|
||||||
|
isErrorResponse,
|
||||||
|
isFailResponse,
|
||||||
|
} = useEmitResponse();
|
||||||
|
|
||||||
// set observers on ref so it's always current.
|
// set observers on ref so it's always current.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
currentObservers.current = observers;
|
currentObservers.current = observers;
|
||||||
}, [ observers ] );
|
}, [ observers ] );
|
||||||
const onCheckoutCompleteSuccess = useMemo(
|
const onCheckoutAfterProcessingWithSuccess = useMemo(
|
||||||
() => emitterSubscribers( subscriber ).onCheckoutCompleteSuccess,
|
() =>
|
||||||
|
emitterSubscribers( subscriber )
|
||||||
|
.onCheckoutAfterProcessingWithSuccess,
|
||||||
[ subscriber ]
|
[ subscriber ]
|
||||||
);
|
);
|
||||||
const onCheckoutCompleteError = useMemo(
|
const onCheckoutAfterProcessingWithError = useMemo(
|
||||||
() => emitterSubscribers( subscriber ).onCheckoutCompleteError,
|
() =>
|
||||||
|
emitterSubscribers( subscriber ).onCheckoutAfterProcessingWithError,
|
||||||
[ subscriber ]
|
[ subscriber ]
|
||||||
);
|
);
|
||||||
const onCheckoutProcessing = useMemo(
|
const onCheckoutBeforeProcessing = useMemo(
|
||||||
() => emitterSubscribers( subscriber ).onCheckoutProcessing,
|
() => emitterSubscribers( subscriber ).onCheckoutBeforeProcessing,
|
||||||
[ subscriber ]
|
[ subscriber ]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -130,8 +140,25 @@ export const CheckoutStateProvider = ( {
|
||||||
void dispatch( actions.decrementCalculating() ),
|
void dispatch( actions.decrementCalculating() ),
|
||||||
setOrderId: ( orderId ) =>
|
setOrderId: ( orderId ) =>
|
||||||
void dispatch( actions.setOrderId( orderId ) ),
|
void dispatch( actions.setOrderId( orderId ) ),
|
||||||
setComplete: () => {
|
setAfterProcessing: ( response ) => {
|
||||||
void dispatch( actions.setComplete() );
|
if ( response.payment_result ) {
|
||||||
|
if (
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
response.payment_result?.redirect_url
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
actions.setRedirectUrl(
|
||||||
|
response.payment_result.redirect_url
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
actions.setProcessingResponse(
|
||||||
|
prepareResponseData( response.payment_result )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
void dispatch( actions.setAfterProcessing() );
|
||||||
},
|
},
|
||||||
} ),
|
} ),
|
||||||
[]
|
[]
|
||||||
|
@ -140,11 +167,11 @@ export const CheckoutStateProvider = ( {
|
||||||
// emit events.
|
// emit events.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
const { status } = checkoutState;
|
const { status } = checkoutState;
|
||||||
if ( status === STATUS.PROCESSING ) {
|
if ( status === STATUS.BEFORE_PROCESSING ) {
|
||||||
removeNotices( 'error' );
|
removeNotices( 'error' );
|
||||||
emitEvent(
|
emitEvent(
|
||||||
currentObservers.current,
|
currentObservers.current,
|
||||||
EMIT_TYPES.CHECKOUT_PROCESSING,
|
EMIT_TYPES.CHECKOUT_BEFORE_PROCESSING,
|
||||||
{}
|
{}
|
||||||
).then( ( response ) => {
|
).then( ( response ) => {
|
||||||
if ( response !== true ) {
|
if ( response !== true ) {
|
||||||
|
@ -156,34 +183,109 @@ export const CheckoutStateProvider = ( {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
dispatch( actions.setComplete() );
|
dispatch( actions.setIdle() );
|
||||||
} else {
|
} else {
|
||||||
dispatch( actions.setProcessingComplete() );
|
dispatch( actions.setProcessing() );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
}, [ checkoutState.status, setValidationErrors ] );
|
}, [ checkoutState.status, setValidationErrors ] );
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
if ( checkoutState.status === STATUS.COMPLETE ) {
|
if ( checkoutState.status === STATUS.AFTER_PROCESSING ) {
|
||||||
|
const data = {
|
||||||
|
redirectUrl: checkoutState.redirectUrl,
|
||||||
|
orderId: checkoutState.orderId,
|
||||||
|
customerId: checkoutState.customerId,
|
||||||
|
customerNote: checkoutState.customerNote,
|
||||||
|
processingResponse: checkoutState.processingResponse,
|
||||||
|
};
|
||||||
if ( checkoutState.hasError ) {
|
if ( checkoutState.hasError ) {
|
||||||
emitEvent(
|
// allow payment methods or other things to customize the error
|
||||||
|
// with a fallback if nothing customizes it.
|
||||||
|
emitEventWithAbort(
|
||||||
currentObservers.current,
|
currentObservers.current,
|
||||||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR,
|
EMIT_TYPES.CHECKOUT_AFTER_PROCESSING_WITH_ERROR,
|
||||||
{}
|
data
|
||||||
);
|
).then( ( response ) => {
|
||||||
|
if (
|
||||||
|
isErrorResponse( response ) ||
|
||||||
|
isFailResponse( response )
|
||||||
|
) {
|
||||||
|
if ( response.message ) {
|
||||||
|
const errorOptions = response.messageContext
|
||||||
|
? { context: response.messageContext }
|
||||||
|
: undefined;
|
||||||
|
addErrorNotice( response.message, errorOptions );
|
||||||
|
}
|
||||||
|
// irrecoverable error so set complete
|
||||||
|
if (
|
||||||
|
typeof response.retry !== 'undefined' &&
|
||||||
|
response.retry !== true
|
||||||
|
) {
|
||||||
|
dispatch( actions.setComplete( response ) );
|
||||||
|
} else {
|
||||||
|
dispatch( actions.setIdle() );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 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'
|
||||||
|
);
|
||||||
|
addErrorNotice( message, {
|
||||||
|
id: 'checkout',
|
||||||
|
} );
|
||||||
|
dispatch( actions.setIdle() );
|
||||||
|
}
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
emitEvent(
|
emitEventWithAbort(
|
||||||
currentObservers.current,
|
currentObservers.current,
|
||||||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
|
EMIT_TYPES.CHECKOUT_AFTER_PROCESSING_WITH_SUCCESS,
|
||||||
{}
|
data
|
||||||
);
|
).then( ( response ) => {
|
||||||
|
if ( isSuccessResponse( response ) ) {
|
||||||
|
dispatch( actions.setComplete( response ) );
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isErrorResponse( response ) ||
|
||||||
|
isFailResponse( response )
|
||||||
|
) {
|
||||||
|
if ( response.message ) {
|
||||||
|
const errorOptions = response.messageContext
|
||||||
|
? { context: response.messageContext }
|
||||||
|
: undefined;
|
||||||
|
addErrorNotice( response.message, errorOptions );
|
||||||
|
}
|
||||||
|
if ( ! response.retry ) {
|
||||||
|
dispatch( actions.setComplete( response ) );
|
||||||
|
} else {
|
||||||
|
// this will set an error which will end up
|
||||||
|
// triggering the onCheckoutAfterProcessingWithErrors emitter.
|
||||||
|
// and then setting checkout to IDLE state.
|
||||||
|
dispatch( actions.setHasError( true ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ checkoutState.status, checkoutState.hasError ] );
|
}, [
|
||||||
|
checkoutState.status,
|
||||||
|
checkoutState.hasError,
|
||||||
|
checkoutState.redirectUrl,
|
||||||
|
checkoutState.orderId,
|
||||||
|
checkoutState.customerId,
|
||||||
|
checkoutState.customerNote,
|
||||||
|
checkoutState.processingResponse,
|
||||||
|
dispatchActions,
|
||||||
|
] );
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
dispatch( actions.setProcessing() );
|
dispatch( actions.setBeforeProcessing() );
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,13 +298,13 @@ export const CheckoutStateProvider = ( {
|
||||||
isIdle: checkoutState.status === STATUS.IDLE,
|
isIdle: checkoutState.status === STATUS.IDLE,
|
||||||
isCalculating,
|
isCalculating,
|
||||||
isProcessing: checkoutState.status === STATUS.PROCESSING,
|
isProcessing: checkoutState.status === STATUS.PROCESSING,
|
||||||
isProcessingComplete:
|
isBeforeProcessing: checkoutState.status === STATUS.BEFORE_PROCESSING,
|
||||||
checkoutState.status === STATUS.PROCESSING_COMPLETE,
|
isAfterProcessing: checkoutState.status === STATUS.AFTER_PROCESSING,
|
||||||
hasError: checkoutState.hasError,
|
hasError: checkoutState.hasError,
|
||||||
redirectUrl: checkoutState.redirectUrl,
|
redirectUrl: checkoutState.redirectUrl,
|
||||||
onCheckoutCompleteSuccess,
|
onCheckoutAfterProcessingWithSuccess,
|
||||||
onCheckoutCompleteError,
|
onCheckoutAfterProcessingWithError,
|
||||||
onCheckoutProcessing,
|
onCheckoutBeforeProcessing,
|
||||||
dispatchActions,
|
dispatchActions,
|
||||||
isCart,
|
isCart,
|
||||||
orderId: checkoutState.orderId,
|
orderId: checkoutState.orderId,
|
||||||
|
|
|
@ -5,8 +5,11 @@ import { TYPES, DEFAULT_STATE, STATUS } from './constants';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
SET_PRISTINE,
|
SET_PRISTINE,
|
||||||
|
SET_IDLE,
|
||||||
SET_PROCESSING,
|
SET_PROCESSING,
|
||||||
SET_PROCESSING_COMPLETE,
|
SET_BEFORE_PROCESSING,
|
||||||
|
SET_AFTER_PROCESSING,
|
||||||
|
SET_PROCESSING_RESPONSE,
|
||||||
SET_REDIRECT_URL,
|
SET_REDIRECT_URL,
|
||||||
SET_COMPLETE,
|
SET_COMPLETE,
|
||||||
SET_HAS_ERROR,
|
SET_HAS_ERROR,
|
||||||
|
@ -16,7 +19,41 @@ const {
|
||||||
SET_ORDER_ID,
|
SET_ORDER_ID,
|
||||||
} = TYPES;
|
} = TYPES;
|
||||||
|
|
||||||
const { PRISTINE, IDLE, PROCESSING, PROCESSING_COMPLETE, COMPLETE } = STATUS;
|
const {
|
||||||
|
PRISTINE,
|
||||||
|
IDLE,
|
||||||
|
PROCESSING,
|
||||||
|
BEFORE_PROCESSING,
|
||||||
|
AFTER_PROCESSING,
|
||||||
|
COMPLETE,
|
||||||
|
} = STATUS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the payment_result data from the server checkout endpoint response.
|
||||||
|
*
|
||||||
|
* @param {Object} data The value of `payment_result` from the checkout
|
||||||
|
* processing endpoint response.
|
||||||
|
* @param {string} data.payment_status The payment status. One of 'success', 'failure',
|
||||||
|
* 'pending', 'error'.
|
||||||
|
* @param {Array<Object>} data.payment_details An array of Objects with a 'key' property that is a
|
||||||
|
* string and value property that is a string. These are
|
||||||
|
* converted to a flat object where the key becomes the
|
||||||
|
* object property and value the property value.
|
||||||
|
*
|
||||||
|
* @return {Object} A new object with 'paymentStatus', and 'paymentDetails' as the properties.
|
||||||
|
*/
|
||||||
|
export const prepareResponseData = ( data ) => {
|
||||||
|
const responseData = {
|
||||||
|
paymentStatus: data.payment_status,
|
||||||
|
paymentDetails: {},
|
||||||
|
};
|
||||||
|
if ( Array.isArray( data.payment_details ) ) {
|
||||||
|
data.payment_details.forEach( ( { key, value } ) => {
|
||||||
|
responseData.paymentDetails[ key ] = value;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
return responseData;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reducer for the checkout state
|
* Reducer for the checkout state
|
||||||
|
@ -24,12 +61,24 @@ const { PRISTINE, IDLE, PROCESSING, PROCESSING_COMPLETE, COMPLETE } = STATUS;
|
||||||
* @param {Object} state Current state.
|
* @param {Object} state Current state.
|
||||||
* @param {Object} action Incoming action object.
|
* @param {Object} action Incoming action object.
|
||||||
*/
|
*/
|
||||||
export const reducer = ( state = DEFAULT_STATE, { url, type, orderId } ) => {
|
export const reducer = (
|
||||||
|
state = DEFAULT_STATE,
|
||||||
|
{ url, type, orderId, data }
|
||||||
|
) => {
|
||||||
let newState;
|
let newState;
|
||||||
switch ( type ) {
|
switch ( type ) {
|
||||||
case SET_PRISTINE:
|
case SET_PRISTINE:
|
||||||
newState = DEFAULT_STATE;
|
newState = DEFAULT_STATE;
|
||||||
break;
|
break;
|
||||||
|
case SET_IDLE:
|
||||||
|
newState =
|
||||||
|
state.state !== IDLE
|
||||||
|
? {
|
||||||
|
...state,
|
||||||
|
status: IDLE,
|
||||||
|
}
|
||||||
|
: state;
|
||||||
|
break;
|
||||||
case SET_REDIRECT_URL:
|
case SET_REDIRECT_URL:
|
||||||
newState =
|
newState =
|
||||||
url !== state.url
|
url !== state.url
|
||||||
|
@ -39,12 +88,20 @@ export const reducer = ( state = DEFAULT_STATE, { url, type, orderId } ) => {
|
||||||
}
|
}
|
||||||
: state;
|
: state;
|
||||||
break;
|
break;
|
||||||
|
case SET_PROCESSING_RESPONSE:
|
||||||
|
newState = {
|
||||||
|
...state,
|
||||||
|
processingResponse: data,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case SET_COMPLETE:
|
case SET_COMPLETE:
|
||||||
newState =
|
newState =
|
||||||
state.status !== COMPLETE
|
state.status !== COMPLETE
|
||||||
? {
|
? {
|
||||||
...state,
|
...state,
|
||||||
status: COMPLETE,
|
status: COMPLETE,
|
||||||
|
redirectUrl: data.redirectUrl || state.redirectUrl,
|
||||||
}
|
}
|
||||||
: state;
|
: state;
|
||||||
break;
|
break;
|
||||||
|
@ -63,16 +120,25 @@ export const reducer = ( state = DEFAULT_STATE, { url, type, orderId } ) => {
|
||||||
? newState
|
? newState
|
||||||
: { ...newState, hasError: false };
|
: { ...newState, hasError: false };
|
||||||
break;
|
break;
|
||||||
case SET_PROCESSING_COMPLETE:
|
case SET_BEFORE_PROCESSING:
|
||||||
newState =
|
newState =
|
||||||
state.status !== SET_PROCESSING_COMPLETE
|
state.status !== BEFORE_PROCESSING
|
||||||
? {
|
? {
|
||||||
...state,
|
...state,
|
||||||
status: PROCESSING_COMPLETE,
|
status: BEFORE_PROCESSING,
|
||||||
hasError: false,
|
hasError: false,
|
||||||
}
|
}
|
||||||
: state;
|
: state;
|
||||||
break;
|
break;
|
||||||
|
case SET_AFTER_PROCESSING:
|
||||||
|
newState =
|
||||||
|
state.status !== AFTER_PROCESSING
|
||||||
|
? {
|
||||||
|
...state,
|
||||||
|
status: AFTER_PROCESSING,
|
||||||
|
}
|
||||||
|
: state;
|
||||||
|
break;
|
||||||
case SET_HAS_ERROR:
|
case SET_HAS_ERROR:
|
||||||
newState = state.hasError
|
newState = state.hasError
|
||||||
? state
|
? state
|
||||||
|
@ -82,7 +148,7 @@ export const reducer = ( state = DEFAULT_STATE, { url, type, orderId } ) => {
|
||||||
};
|
};
|
||||||
newState =
|
newState =
|
||||||
state.status === PROCESSING ||
|
state.status === PROCESSING ||
|
||||||
state.status === PROCESSING_COMPLETE
|
state.status === BEFORE_PROCESSING
|
||||||
? {
|
? {
|
||||||
...newState,
|
...newState,
|
||||||
status: IDLE,
|
status: IDLE,
|
||||||
|
|
|
@ -47,12 +47,12 @@ const preparePaymentData = ( paymentData ) => {
|
||||||
const CheckoutProcessor = () => {
|
const CheckoutProcessor = () => {
|
||||||
const {
|
const {
|
||||||
hasError: checkoutHasError,
|
hasError: checkoutHasError,
|
||||||
onCheckoutProcessing,
|
onCheckoutBeforeProcessing,
|
||||||
onCheckoutCompleteSuccess,
|
|
||||||
dispatchActions,
|
dispatchActions,
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
isProcessing: checkoutIsProcessing,
|
isProcessing: checkoutIsProcessing,
|
||||||
isProcessingComplete: checkoutIsProcessingComplete,
|
isBeforeProcessing: checkoutIsBeforeProcessing,
|
||||||
|
isComplete: checkoutIsComplete,
|
||||||
} = useCheckoutContext();
|
} = useCheckoutContext();
|
||||||
const { hasValidationErrors } = useValidationContext();
|
const { hasValidationErrors } = useValidationContext();
|
||||||
const { shippingAddress, shippingErrorStatus } = useShippingDataContext();
|
const { shippingAddress, shippingErrorStatus } = useShippingDataContext();
|
||||||
|
@ -69,6 +69,7 @@ const CheckoutProcessor = () => {
|
||||||
const { addErrorNotice, removeNotice } = useStoreNotices();
|
const { addErrorNotice, removeNotice } = useStoreNotices();
|
||||||
const currentBillingData = useRef( billingData );
|
const currentBillingData = useRef( billingData );
|
||||||
const currentShippingAddress = useRef( shippingAddress );
|
const currentShippingAddress = useRef( shippingAddress );
|
||||||
|
const currentRedirectUrl = useRef( redirectUrl );
|
||||||
const [ isProcessingOrder, setIsProcessingOrder ] = useState( false );
|
const [ isProcessingOrder, setIsProcessingOrder ] = useState( false );
|
||||||
const expressPaymentMethodActive = Object.keys(
|
const expressPaymentMethodActive = Object.keys(
|
||||||
expressPaymentMethods
|
expressPaymentMethods
|
||||||
|
@ -87,7 +88,7 @@ const CheckoutProcessor = () => {
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
if (
|
if (
|
||||||
checkoutWillHaveError !== checkoutHasError &&
|
checkoutWillHaveError !== checkoutHasError &&
|
||||||
( checkoutIsProcessing || checkoutIsProcessingComplete ) &&
|
( checkoutIsProcessing || checkoutIsBeforeProcessing ) &&
|
||||||
! expressPaymentMethodActive
|
! expressPaymentMethodActive
|
||||||
) {
|
) {
|
||||||
dispatchActions.setHasError( checkoutWillHaveError );
|
dispatchActions.setHasError( checkoutWillHaveError );
|
||||||
|
@ -96,7 +97,7 @@ const CheckoutProcessor = () => {
|
||||||
checkoutWillHaveError,
|
checkoutWillHaveError,
|
||||||
checkoutHasError,
|
checkoutHasError,
|
||||||
checkoutIsProcessing,
|
checkoutIsProcessing,
|
||||||
checkoutIsProcessingComplete,
|
checkoutIsBeforeProcessing,
|
||||||
expressPaymentMethodActive,
|
expressPaymentMethodActive,
|
||||||
] );
|
] );
|
||||||
|
|
||||||
|
@ -104,12 +105,13 @@ const CheckoutProcessor = () => {
|
||||||
! checkoutHasError &&
|
! checkoutHasError &&
|
||||||
! checkoutWillHaveError &&
|
! checkoutWillHaveError &&
|
||||||
( currentPaymentStatus.isSuccessful || ! cartNeedsPayment ) &&
|
( currentPaymentStatus.isSuccessful || ! cartNeedsPayment ) &&
|
||||||
checkoutIsProcessingComplete;
|
checkoutIsProcessing;
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
currentBillingData.current = billingData;
|
currentBillingData.current = billingData;
|
||||||
currentShippingAddress.current = shippingAddress;
|
currentShippingAddress.current = shippingAddress;
|
||||||
}, [ billingData, shippingAddress ] );
|
currentRedirectUrl.current = redirectUrl;
|
||||||
|
}, [ billingData, shippingAddress, redirectUrl ] );
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
if ( errorMessage ) {
|
if ( errorMessage ) {
|
||||||
|
@ -157,14 +159,21 @@ const CheckoutProcessor = () => {
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
let unsubscribeProcessing;
|
let unsubscribeProcessing;
|
||||||
if ( ! expressPaymentMethodActive ) {
|
if ( ! expressPaymentMethodActive ) {
|
||||||
unsubscribeProcessing = onCheckoutProcessing( checkValidation, 0 );
|
unsubscribeProcessing = onCheckoutBeforeProcessing(
|
||||||
|
checkValidation,
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
if ( ! expressPaymentMethodActive ) {
|
if ( ! expressPaymentMethodActive ) {
|
||||||
unsubscribeProcessing();
|
unsubscribeProcessing();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [ onCheckoutProcessing, checkValidation, expressPaymentMethodActive ] );
|
}, [
|
||||||
|
onCheckoutBeforeProcessing,
|
||||||
|
checkValidation,
|
||||||
|
expressPaymentMethodActive,
|
||||||
|
] );
|
||||||
|
|
||||||
const processOrder = useCallback( () => {
|
const processOrder = useCallback( () => {
|
||||||
setIsProcessingOrder( true );
|
setIsProcessingOrder( true );
|
||||||
|
@ -212,29 +221,17 @@ const CheckoutProcessor = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
dispatchActions.setHasError();
|
dispatchActions.setHasError();
|
||||||
} else {
|
|
||||||
dispatchActions.setRedirectUrl(
|
|
||||||
response.payment_result.redirect_url
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
dispatchActions.setAfterProcessing( response );
|
||||||
dispatchActions.setComplete();
|
|
||||||
setIsProcessingOrder( false );
|
setIsProcessingOrder( false );
|
||||||
} );
|
} );
|
||||||
} )
|
} )
|
||||||
.catch( ( error ) => {
|
.catch( ( error ) => {
|
||||||
const message =
|
error.json().then( function( response ) {
|
||||||
error.message ||
|
dispatchActions.setHasError();
|
||||||
__(
|
dispatchActions.setAfterProcessing( response );
|
||||||
'Something went wrong. Please contact us to get assistance.',
|
setIsProcessingOrder( false );
|
||||||
'woo-gutenberg-products-block'
|
|
||||||
);
|
|
||||||
addErrorNotice( message, {
|
|
||||||
id: 'checkout',
|
|
||||||
} );
|
} );
|
||||||
dispatchActions.setHasError();
|
|
||||||
dispatchActions.setComplete();
|
|
||||||
setIsProcessingOrder( false );
|
|
||||||
} );
|
} );
|
||||||
}, [
|
}, [
|
||||||
addErrorNotice,
|
addErrorNotice,
|
||||||
|
@ -243,15 +240,12 @@ const CheckoutProcessor = () => {
|
||||||
paymentMethodData,
|
paymentMethodData,
|
||||||
cartNeedsPayment,
|
cartNeedsPayment,
|
||||||
] );
|
] );
|
||||||
// setup checkout processing event observers.
|
// redirect when checkout is complete and there is a redirect url.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
const unsubscribeRedirect = onCheckoutCompleteSuccess( () => {
|
if ( currentRedirectUrl.current ) {
|
||||||
window.location.href = redirectUrl;
|
window.location.href = currentRedirectUrl.current;
|
||||||
}, 999 );
|
}
|
||||||
return () => {
|
}, [ checkoutIsComplete ] );
|
||||||
unsubscribeRedirect();
|
|
||||||
};
|
|
||||||
}, [ onCheckoutCompleteSuccess, redirectUrl ] );
|
|
||||||
|
|
||||||
// process order if conditions are good.
|
// process order if conditions are good.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
|
|
|
@ -10,9 +10,6 @@ import {
|
||||||
|
|
||||||
const EMIT_TYPES = {
|
const EMIT_TYPES = {
|
||||||
PAYMENT_PROCESSING: 'payment_processing',
|
PAYMENT_PROCESSING: 'payment_processing',
|
||||||
PAYMENT_SUCCESS: 'payment_success',
|
|
||||||
PAYMENT_FAIL: 'payment_fail',
|
|
||||||
PAYMENT_ERROR: 'payment_error',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,9 +30,6 @@ const emitterSubscribers = ( dispatcher ) => ( {
|
||||||
EMIT_TYPES.PAYMENT_PROCESSING,
|
EMIT_TYPES.PAYMENT_PROCESSING,
|
||||||
dispatcher
|
dispatcher
|
||||||
),
|
),
|
||||||
onPaymentSuccess: emitterCallback( EMIT_TYPES.PAYMENT_SUCCESS, dispatcher ),
|
|
||||||
onPaymentFail: emitterCallback( EMIT_TYPES.PAYMENT_FAIL, dispatcher ),
|
|
||||||
onPaymentError: emitterCallback( EMIT_TYPES.PAYMENT_ERROR, dispatcher ),
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import { useShippingDataContext } from '../shipping';
|
||||||
import {
|
import {
|
||||||
EMIT_TYPES,
|
EMIT_TYPES,
|
||||||
emitterSubscribers,
|
emitterSubscribers,
|
||||||
emitEvent,
|
|
||||||
emitEventWithAbort,
|
emitEventWithAbort,
|
||||||
reducer as emitReducer,
|
reducer as emitReducer,
|
||||||
} from './event-emit';
|
} from './event-emit';
|
||||||
|
@ -45,7 +44,7 @@ import {
|
||||||
useMemo,
|
useMemo,
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import { getSetting } from '@woocommerce/settings';
|
import { getSetting } from '@woocommerce/settings';
|
||||||
import { useStoreNotices } from '@woocommerce/base-hooks';
|
import { useStoreNotices, useEmitResponse } from '@woocommerce/base-hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('@woocommerce/type-defs/contexts').PaymentMethodDataContext} PaymentMethodDataContext
|
* @typedef {import('@woocommerce/type-defs/contexts').PaymentMethodDataContext} PaymentMethodDataContext
|
||||||
|
@ -76,23 +75,6 @@ export const usePaymentMethodDataContext = () => {
|
||||||
return useContext( PaymentMethodDataContext );
|
return useContext( PaymentMethodDataContext );
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSuccessResponse = ( response ) => {
|
|
||||||
return (
|
|
||||||
( typeof response === 'object' &&
|
|
||||||
typeof response.fail === 'undefined' &&
|
|
||||||
typeof response.errorMessage === 'undefined' ) ||
|
|
||||||
response === true
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isFailResponse = ( response ) => {
|
|
||||||
return response && typeof response.fail === 'object';
|
|
||||||
};
|
|
||||||
|
|
||||||
const isErrorResponse = ( response ) => {
|
|
||||||
return response && typeof response.errorMessage !== 'undefined';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PaymentMethodDataProvider is automatically included in the
|
* PaymentMethodDataProvider is automatically included in the
|
||||||
* CheckoutDataProvider.
|
* CheckoutDataProvider.
|
||||||
|
@ -112,11 +94,16 @@ export const PaymentMethodDataProvider = ( {
|
||||||
} ) => {
|
} ) => {
|
||||||
const { setBillingData } = useBillingDataContext();
|
const { setBillingData } = useBillingDataContext();
|
||||||
const {
|
const {
|
||||||
isComplete: checkoutIsComplete,
|
isProcessing: checkoutIsProcessing,
|
||||||
isProcessingComplete: checkoutIsProcessingComplete,
|
isIdle: checkoutIsIdle,
|
||||||
isCalculating: checkoutIsCalculating,
|
isCalculating: checkoutIsCalculating,
|
||||||
hasError: checkoutHasError,
|
hasError: checkoutHasError,
|
||||||
} = useCheckoutContext();
|
} = useCheckoutContext();
|
||||||
|
const {
|
||||||
|
isSuccessResponse,
|
||||||
|
isErrorResponse,
|
||||||
|
isFailResponse,
|
||||||
|
} = useEmitResponse();
|
||||||
const [ activePaymentMethod, setActive ] = useState(
|
const [ activePaymentMethod, setActive ] = useState(
|
||||||
initialActivePaymentMethod
|
initialActivePaymentMethod
|
||||||
);
|
);
|
||||||
|
@ -164,18 +151,6 @@ export const PaymentMethodDataProvider = ( {
|
||||||
() => emitterSubscribers( subscriber ).onPaymentProcessing,
|
() => emitterSubscribers( subscriber ).onPaymentProcessing,
|
||||||
[ subscriber ]
|
[ subscriber ]
|
||||||
);
|
);
|
||||||
const onPaymentSuccess = useMemo(
|
|
||||||
() => emitterSubscribers( subscriber ).onPaymentSuccess,
|
|
||||||
[ subscriber ]
|
|
||||||
);
|
|
||||||
const onPaymentFail = useMemo(
|
|
||||||
() => emitterSubscribers( subscriber ).onPaymentFail,
|
|
||||||
[ subscriber ]
|
|
||||||
);
|
|
||||||
const onPaymentError = useMemo(
|
|
||||||
() => emitterSubscribers( subscriber ).onPaymentError,
|
|
||||||
[ subscriber ]
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentStatus = useMemo(
|
const currentStatus = useMemo(
|
||||||
() => ( {
|
() => ( {
|
||||||
|
@ -196,7 +171,7 @@ export const PaymentMethodDataProvider = ( {
|
||||||
// no errors, and payment status is started.
|
// no errors, and payment status is started.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
if (
|
if (
|
||||||
checkoutIsProcessingComplete &&
|
checkoutIsProcessing &&
|
||||||
! checkoutHasError &&
|
! checkoutHasError &&
|
||||||
! checkoutIsCalculating &&
|
! checkoutIsCalculating &&
|
||||||
! currentStatus.isFinished
|
! currentStatus.isFinished
|
||||||
|
@ -204,12 +179,17 @@ export const PaymentMethodDataProvider = ( {
|
||||||
setPaymentStatus().processing();
|
setPaymentStatus().processing();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
checkoutIsProcessingComplete,
|
checkoutIsProcessing,
|
||||||
checkoutHasError,
|
checkoutHasError,
|
||||||
checkoutIsCalculating,
|
checkoutIsCalculating,
|
||||||
currentStatus.isFinished,
|
currentStatus.isFinished,
|
||||||
] );
|
] );
|
||||||
|
|
||||||
|
// when checkout is returned to idle, set payment status to pristine.
|
||||||
|
useEffect( () => {
|
||||||
|
dispatch( statusOnly( PRISTINE ) );
|
||||||
|
}, [ checkoutIsIdle ] );
|
||||||
|
|
||||||
// set initial active payment method if it's undefined.
|
// set initial active payment method if it's undefined.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
const paymentMethodKeys = Object.keys( paymentData.paymentMethods );
|
const paymentMethodKeys = Object.keys( paymentData.paymentMethods );
|
||||||
|
@ -289,9 +269,8 @@ export const PaymentMethodDataProvider = ( {
|
||||||
|
|
||||||
// emit events.
|
// emit events.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
// Note: the nature of this event emitter is that it will bail on a
|
// Note: the nature of this event emitter is that it will bail on any
|
||||||
// successful payment (that is an observer hooked in that returns an
|
// observer that returns a response that !== true. However, this still
|
||||||
// object in the shape of a successful payment). However, this still
|
|
||||||
// allows for other observers that return true for continuing through
|
// allows for other observers that return true for continuing through
|
||||||
// to the next observer (or bailing if there's a problem).
|
// to the next observer (or bailing if there's a problem).
|
||||||
if ( currentStatus.isProcessing ) {
|
if ( currentStatus.isProcessing ) {
|
||||||
|
@ -302,48 +281,29 @@ export const PaymentMethodDataProvider = ( {
|
||||||
).then( ( response ) => {
|
).then( ( response ) => {
|
||||||
if ( isSuccessResponse( response ) ) {
|
if ( isSuccessResponse( response ) ) {
|
||||||
setPaymentStatus().success(
|
setPaymentStatus().success(
|
||||||
response.paymentMethodData,
|
response?.meta?.paymentMethodData,
|
||||||
response.billingData,
|
response?.meta?.billingData,
|
||||||
response.shippingData
|
response?.meta?.shippingData
|
||||||
);
|
);
|
||||||
} else if ( isFailResponse( response ) ) {
|
} else if ( isFailResponse( response ) ) {
|
||||||
|
addErrorNotice( response.message, {
|
||||||
|
context: 'wc/payment-area',
|
||||||
|
} );
|
||||||
setPaymentStatus().failed(
|
setPaymentStatus().failed(
|
||||||
response.fail.errorMessage,
|
response.message,
|
||||||
response.fail.paymentMethodData,
|
response?.meta?.paymentMethodData,
|
||||||
response.fail.billingData
|
response?.meta?.billingData
|
||||||
);
|
);
|
||||||
} else if ( isErrorResponse( response ) ) {
|
} else if ( isErrorResponse( response ) ) {
|
||||||
setPaymentStatus().error( response.errorMessage );
|
addErrorNotice( response.message, {
|
||||||
setValidationErrors( response.validationErrors );
|
context: 'wc/payment-area',
|
||||||
|
} );
|
||||||
|
setPaymentStatus().error( response.message );
|
||||||
|
setValidationErrors( response?.validationErrors );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
if (
|
}, [ currentStatus, setValidationErrors, setPaymentStatus ] );
|
||||||
currentStatus.isSuccessful &&
|
|
||||||
checkoutIsComplete &&
|
|
||||||
! checkoutHasError
|
|
||||||
) {
|
|
||||||
emitEvent(
|
|
||||||
currentObservers.current,
|
|
||||||
EMIT_TYPES.PAYMENT_SUCCESS,
|
|
||||||
{}
|
|
||||||
).then( () => {
|
|
||||||
setPaymentStatus().completed();
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
if ( currentStatus.hasFailed ) {
|
|
||||||
emitEvent( currentObservers.current, EMIT_TYPES.PAYMENT_FAIL, {} );
|
|
||||||
}
|
|
||||||
if ( currentStatus.hasError ) {
|
|
||||||
emitEvent( currentObservers.current, EMIT_TYPES.PAYMENT_ERROR, {} );
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
currentStatus,
|
|
||||||
setValidationErrors,
|
|
||||||
setPaymentStatus,
|
|
||||||
checkoutIsComplete,
|
|
||||||
checkoutHasError,
|
|
||||||
] );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {PaymentMethodDataContext}
|
* @type {PaymentMethodDataContext}
|
||||||
|
@ -357,9 +317,6 @@ export const PaymentMethodDataProvider = ( {
|
||||||
activePaymentMethod,
|
activePaymentMethod,
|
||||||
setActivePaymentMethod,
|
setActivePaymentMethod,
|
||||||
onPaymentProcessing,
|
onPaymentProcessing,
|
||||||
onPaymentSuccess,
|
|
||||||
onPaymentFail,
|
|
||||||
onPaymentError,
|
|
||||||
customerPaymentMethods,
|
customerPaymentMethods,
|
||||||
paymentMethods: paymentData.paymentMethods,
|
paymentMethods: paymentData.paymentMethods,
|
||||||
expressPaymentMethods: paymentData.expressPaymentMethods,
|
expressPaymentMethods: paymentData.expressPaymentMethods,
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './use-checkout-redirect-url';
|
export * from './use-checkout-redirect-url';
|
||||||
export * from './use-checkout-submit';
|
export * from './use-checkout-submit';
|
||||||
|
export * from './use-emit-response';
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* @typedef {import('@woocommerce/type-defs/hooks').EmitResponseTypes} EmitResponseTypes
|
||||||
|
* @typedef {import('@woocommerce/type-defs/hooks').NoticeContexts} NoticeContexts
|
||||||
|
* @typedef {import('@woocommerce/type-defs/hooks').EmitResponseApi} EmitResponseApi
|
||||||
|
*/
|
||||||
|
|
||||||
|
const isResponseOf = ( response, type ) => {
|
||||||
|
return !! response.type && response.type === type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {EmitResponseTypes}
|
||||||
|
*/
|
||||||
|
const responseTypes = {
|
||||||
|
SUCCESS: 'success',
|
||||||
|
FAIL: 'failure',
|
||||||
|
ERROR: 'error',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {NoticeContexts}
|
||||||
|
*/
|
||||||
|
const noticeContexts = {
|
||||||
|
PAYMENTS: 'wc/payment-area',
|
||||||
|
EXPRESS_PAYMENTS: 'wc/express-payment-area',
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSuccessResponse = ( response ) => {
|
||||||
|
return isResponseOf( response, responseTypes.SUCCESS );
|
||||||
|
};
|
||||||
|
|
||||||
|
const isErrorResponse = ( response ) => {
|
||||||
|
return isResponseOf( response, responseTypes.ERROR );
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFailResponse = ( response ) => {
|
||||||
|
return isResponseOf( response, responseTypes.FAIL );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom hook exposing response utilities for emitters.
|
||||||
|
*
|
||||||
|
* @return {EmitResponseApi} Various interfaces for validating and implementing
|
||||||
|
* emitter response properties.
|
||||||
|
*/
|
||||||
|
export const useEmitResponse = () => {
|
||||||
|
return {
|
||||||
|
responseTypes,
|
||||||
|
noticeContexts,
|
||||||
|
isSuccessResponse,
|
||||||
|
isErrorResponse,
|
||||||
|
isFailResponse,
|
||||||
|
};
|
||||||
|
};
|
|
@ -13,6 +13,7 @@ import { useEffect, useRef } from '@wordpress/element';
|
||||||
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
|
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
|
||||||
import { ValidationInputError } from '@woocommerce/base-components/validation';
|
import { ValidationInputError } from '@woocommerce/base-components/validation';
|
||||||
import CheckboxControl from '@woocommerce/base-components/checkbox-control';
|
import CheckboxControl from '@woocommerce/base-components/checkbox-control';
|
||||||
|
import { useEmitResponse } from '@woocommerce/base-hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -87,9 +88,9 @@ export const usePaymentMethodInterface = () => {
|
||||||
isComplete,
|
isComplete,
|
||||||
isIdle,
|
isIdle,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
onCheckoutCompleteSuccess,
|
onCheckoutAfterProcessingWithSuccess,
|
||||||
onCheckoutCompleteError,
|
onCheckoutAfterProcessingWithError,
|
||||||
onCheckoutProcessing,
|
onCheckoutBeforeProcessing,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
customerId,
|
customerId,
|
||||||
} = useCheckoutContext();
|
} = useCheckoutContext();
|
||||||
|
@ -97,9 +98,6 @@ export const usePaymentMethodInterface = () => {
|
||||||
currentStatus,
|
currentStatus,
|
||||||
activePaymentMethod,
|
activePaymentMethod,
|
||||||
onPaymentProcessing,
|
onPaymentProcessing,
|
||||||
onPaymentSuccess,
|
|
||||||
onPaymentFail,
|
|
||||||
onPaymentError,
|
|
||||||
setExpressPaymentError,
|
setExpressPaymentError,
|
||||||
} = usePaymentMethodDataContext();
|
} = usePaymentMethodDataContext();
|
||||||
const {
|
const {
|
||||||
|
@ -122,6 +120,7 @@ export const usePaymentMethodInterface = () => {
|
||||||
const { order, isLoading: orderLoading } = useStoreOrder();
|
const { order, isLoading: orderLoading } = useStoreOrder();
|
||||||
const { cartTotals } = useStoreCart();
|
const { cartTotals } = useStoreCart();
|
||||||
const { appliedCoupons } = useStoreCartCoupons();
|
const { appliedCoupons } = useStoreCartCoupons();
|
||||||
|
const { noticeContexts, responseTypes } = useEmitResponse();
|
||||||
const currentCartTotals = useRef(
|
const currentCartTotals = useRef(
|
||||||
prepareTotalItems( cartTotals, needsShipping )
|
prepareTotalItems( cartTotals, needsShipping )
|
||||||
);
|
);
|
||||||
|
@ -176,22 +175,23 @@ export const usePaymentMethodInterface = () => {
|
||||||
customerId,
|
customerId,
|
||||||
},
|
},
|
||||||
eventRegistration: {
|
eventRegistration: {
|
||||||
onCheckoutCompleteSuccess,
|
onCheckoutAfterProcessingWithSuccess,
|
||||||
onCheckoutCompleteError,
|
onCheckoutAfterProcessingWithError,
|
||||||
onCheckoutProcessing,
|
onCheckoutBeforeProcessing,
|
||||||
onShippingRateSuccess,
|
onShippingRateSuccess,
|
||||||
onShippingRateFail,
|
onShippingRateFail,
|
||||||
onShippingRateSelectSuccess,
|
onShippingRateSelectSuccess,
|
||||||
onShippingRateSelectFail,
|
onShippingRateSelectFail,
|
||||||
onPaymentProcessing,
|
onPaymentProcessing,
|
||||||
onPaymentSuccess,
|
|
||||||
onPaymentFail,
|
|
||||||
onPaymentError,
|
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ValidationInputError,
|
ValidationInputError,
|
||||||
CheckboxControl,
|
CheckboxControl,
|
||||||
},
|
},
|
||||||
|
emitResponse: {
|
||||||
|
noticeContexts,
|
||||||
|
responseTypes,
|
||||||
|
},
|
||||||
onSubmit,
|
onSubmit,
|
||||||
activePaymentMethod,
|
activePaymentMethod,
|
||||||
setExpressPaymentError,
|
setExpressPaymentError,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
useShippingDataContext,
|
useShippingDataContext,
|
||||||
useBillingDataContext,
|
useBillingDataContext,
|
||||||
useValidationContext,
|
useValidationContext,
|
||||||
|
StoreNoticesProvider,
|
||||||
} from '@woocommerce/base-context';
|
} from '@woocommerce/base-context';
|
||||||
import { useStoreCart, usePaymentMethods } from '@woocommerce/base-hooks';
|
import { useStoreCart, usePaymentMethods } from '@woocommerce/base-hooks';
|
||||||
import {
|
import {
|
||||||
|
@ -355,7 +356,9 @@ const Checkout = ( { attributes, scrollToTop } ) => {
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PaymentMethods />
|
<StoreNoticesProvider context="wc/payment-area">
|
||||||
|
<PaymentMethods />
|
||||||
|
</StoreNoticesProvider>
|
||||||
</FormStep>
|
</FormStep>
|
||||||
) }
|
) }
|
||||||
</CheckoutForm>
|
</CheckoutForm>
|
||||||
|
|
|
@ -24,7 +24,12 @@ import { __ } from '@wordpress/i18n';
|
||||||
*
|
*
|
||||||
* @param {RegisteredPaymentMethodProps} props Incoming props
|
* @param {RegisteredPaymentMethodProps} props Incoming props
|
||||||
*/
|
*/
|
||||||
const CreditCardComponent = ( { billing, eventRegistration, components } ) => {
|
const CreditCardComponent = ( {
|
||||||
|
billing,
|
||||||
|
eventRegistration,
|
||||||
|
emitResponse,
|
||||||
|
components,
|
||||||
|
} ) => {
|
||||||
const { ValidationInputError, CheckboxControl } = components;
|
const { ValidationInputError, CheckboxControl } = components;
|
||||||
const { customerId } = billing;
|
const { customerId } = billing;
|
||||||
const [ sourceId, setSourceId ] = useState( 0 );
|
const [ sourceId, setSourceId ] = useState( 0 );
|
||||||
|
@ -39,6 +44,7 @@ const CreditCardComponent = ( { billing, eventRegistration, components } ) => {
|
||||||
sourceId,
|
sourceId,
|
||||||
setSourceId,
|
setSourceId,
|
||||||
shouldSavePayment,
|
shouldSavePayment,
|
||||||
|
emitResponse,
|
||||||
stripe,
|
stripe,
|
||||||
elements
|
elements
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,11 +12,12 @@ import {
|
||||||
getStripeServerData,
|
getStripeServerData,
|
||||||
getErrorMessageForTypeAndCode,
|
getErrorMessageForTypeAndCode,
|
||||||
} from '../stripe-utils';
|
} from '../stripe-utils';
|
||||||
|
import { usePaymentIntents } from './use-payment-intents';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EventRegistrationProps} EventRegistrationProps
|
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EventRegistrationProps} EventRegistrationProps
|
||||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').PaymentStatusProps} PaymentStatusProps
|
|
||||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').BillingDataProps} BillingDataProps
|
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').BillingDataProps} BillingDataProps
|
||||||
|
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EmitResponseProps} EmitResponseProps
|
||||||
* @typedef {import('../stripe-utils/type-defs').Stripe} Stripe
|
* @typedef {import('../stripe-utils/type-defs').Stripe} Stripe
|
||||||
* @typedef {import('react').Dispatch<number>} SourceIdDispatch
|
* @typedef {import('react').Dispatch<number>} SourceIdDispatch
|
||||||
*/
|
*/
|
||||||
|
@ -24,16 +25,13 @@ import {
|
||||||
/**
|
/**
|
||||||
* A custom hook for the Stripe processing and event observer logic.
|
* A custom hook for the Stripe processing and event observer logic.
|
||||||
*
|
*
|
||||||
* @param {EventRegistrationProps} eventRegistration Event registration
|
* @param {EventRegistrationProps} eventRegistration Event registration functions.
|
||||||
* functions.
|
* @param {EmitResponseProps} emitResponse Various helpers for usage with observer
|
||||||
* @param {BillingDataProps} billing Various billing data
|
* response objects.
|
||||||
* items.
|
* @param {BillingDataProps} billing Various billing data items.
|
||||||
* @param {number} sourceId Current set stripe
|
* @param {number} sourceId Current set stripe source id.
|
||||||
* source id.
|
* @param {SourceIdDispatch} setSourceId Setter for stripe source id.
|
||||||
* @param {SourceIdDispatch} setSourceId Setter for stripe
|
* @param {boolean} shouldSavePayment Whether to save the payment or not.
|
||||||
* source id.
|
|
||||||
* @param {boolean} shouldSavePayment Whether to save the
|
|
||||||
* payment or not.
|
|
||||||
* @param {Stripe} stripe The stripe.js object.
|
* @param {Stripe} stripe The stripe.js object.
|
||||||
* @param {Object} elements Stripe Elements object.
|
* @param {Object} elements Stripe Elements object.
|
||||||
*
|
*
|
||||||
|
@ -45,6 +43,7 @@ export const useCheckoutSubscriptions = (
|
||||||
sourceId,
|
sourceId,
|
||||||
setSourceId,
|
setSourceId,
|
||||||
shouldSavePayment,
|
shouldSavePayment,
|
||||||
|
emitResponse,
|
||||||
stripe,
|
stripe,
|
||||||
elements
|
elements
|
||||||
) => {
|
) => {
|
||||||
|
@ -52,6 +51,11 @@ export const useCheckoutSubscriptions = (
|
||||||
const onStripeError = useRef( ( event ) => {
|
const onStripeError = useRef( ( event ) => {
|
||||||
return event;
|
return event;
|
||||||
} );
|
} );
|
||||||
|
usePaymentIntents(
|
||||||
|
stripe,
|
||||||
|
eventRegistration.onCheckoutAfterProcessingWithSuccess,
|
||||||
|
emitResponse
|
||||||
|
);
|
||||||
// hook into and register callbacks for events.
|
// hook into and register callbacks for events.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
onStripeError.current = ( event ) => {
|
onStripeError.current = ( event ) => {
|
||||||
|
@ -80,19 +84,23 @@ export const useCheckoutSubscriptions = (
|
||||||
// if there's an error return that.
|
// if there's an error return that.
|
||||||
if ( error ) {
|
if ( error ) {
|
||||||
return {
|
return {
|
||||||
errorMessage: error,
|
type: emitResponse.responseTypes.ERROR,
|
||||||
|
message: error,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// use token if it's set.
|
// use token if it's set.
|
||||||
if ( sourceId !== 0 ) {
|
if ( sourceId !== 0 ) {
|
||||||
return {
|
return {
|
||||||
paymentMethodData: {
|
type: emitResponse.responseTypes.SUCCESS,
|
||||||
paymentMethod: PAYMENT_METHOD_NAME,
|
meta: {
|
||||||
paymentRequestType: 'cc',
|
paymentMethodData: {
|
||||||
stripe_source: sourceId,
|
paymentMethod: PAYMENT_METHOD_NAME,
|
||||||
shouldSavePayment,
|
paymentRequestType: 'cc',
|
||||||
|
stripe_source: sourceId,
|
||||||
|
shouldSavePayment,
|
||||||
|
},
|
||||||
|
billingData,
|
||||||
},
|
},
|
||||||
billingData,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const ownerInfo = {
|
const ownerInfo = {
|
||||||
|
@ -118,35 +126,54 @@ export const useCheckoutSubscriptions = (
|
||||||
const response = await createSource( ownerInfo );
|
const response = await createSource( ownerInfo );
|
||||||
if ( response.error ) {
|
if ( response.error ) {
|
||||||
return {
|
return {
|
||||||
errorMessage: onStripeError.current( response ),
|
type: emitResponse.responseTypes.ERROR,
|
||||||
|
message: onStripeError.current( response ),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
setSourceId( response.source.id );
|
setSourceId( response.source.id );
|
||||||
return {
|
return {
|
||||||
paymentMethodData: {
|
type: emitResponse.responseTypes.SUCCESS,
|
||||||
stripe_source: response.source.id,
|
meta: {
|
||||||
paymentMethod: PAYMENT_METHOD_NAME,
|
paymentMethodData: {
|
||||||
paymentRequestType: 'cc',
|
stripe_source: response.source.id,
|
||||||
shouldSavePayment,
|
paymentMethod: PAYMENT_METHOD_NAME,
|
||||||
|
paymentRequestType: 'cc',
|
||||||
|
shouldSavePayment,
|
||||||
|
},
|
||||||
|
billingData,
|
||||||
},
|
},
|
||||||
billingData,
|
|
||||||
};
|
};
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
return {
|
return {
|
||||||
errorMessage: e,
|
type: emitResponse.responseTypes.ERROR,
|
||||||
|
message: e,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const onError = ( { processingResponse } ) => {
|
||||||
|
if ( processingResponse?.paymentDetails?.errorMessage ) {
|
||||||
|
return {
|
||||||
|
type: emitResponse.responseTypes.ERROR,
|
||||||
|
message: processingResponse.paymentDetails.errorMessage,
|
||||||
|
messageContext: emitResponse.noticeContexts.PAYMENTS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// leave for checkout to handle.
|
||||||
|
return null;
|
||||||
|
};
|
||||||
const unsubscribeProcessing = eventRegistration.onPaymentProcessing(
|
const unsubscribeProcessing = eventRegistration.onPaymentProcessing(
|
||||||
onSubmit
|
onSubmit
|
||||||
);
|
);
|
||||||
|
const unsubscribeAfterProcessing = eventRegistration.onCheckoutAfterProcessingWithError(
|
||||||
|
onError
|
||||||
|
);
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribeProcessing();
|
unsubscribeProcessing();
|
||||||
|
unsubscribeAfterProcessing();
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
eventRegistration.onCheckoutProcessing,
|
eventRegistration.onPaymentProcessing,
|
||||||
eventRegistration.onCheckoutCompleteSuccess,
|
eventRegistration.onCheckoutAfterProcessingWithError,
|
||||||
eventRegistration.onCheckoutCompleteError,
|
|
||||||
stripe,
|
stripe,
|
||||||
sourceId,
|
sourceId,
|
||||||
billing.billingData,
|
billing.billingData,
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { useEffect } from '@wordpress/element';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EmitResponseProps} EmitResponseProps
|
||||||
|
* @typedef {import('../stripe-utils/type-defs').Stripe} Stripe
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the modal for PaymentIntent authorizations.
|
||||||
|
*
|
||||||
|
* @param {Stripe} stripe The stripe object.
|
||||||
|
* @param {Object} paymentDetails The payment details from the server after checkout
|
||||||
|
* processing.
|
||||||
|
* @param {EmitResponseProps} emitResponse Various helpers for usage with observer response
|
||||||
|
* objects.
|
||||||
|
*/
|
||||||
|
const openIntentModal = ( stripe, paymentDetails, emitResponse ) => {
|
||||||
|
const checkoutResponse = { type: emitResponse.responseTypes.SUCCESS };
|
||||||
|
if (
|
||||||
|
! paymentDetails.setup_intent &&
|
||||||
|
! paymentDetails.payment_intent_secret
|
||||||
|
) {
|
||||||
|
return checkoutResponse;
|
||||||
|
}
|
||||||
|
const isSetupIntent = !! paymentDetails.setupIntent;
|
||||||
|
const verificationUrl = paymentDetails.verification_endpoint;
|
||||||
|
const intentSecret = isSetupIntent
|
||||||
|
? paymentDetails.setup_intent
|
||||||
|
: paymentDetails.payment_intent_secret;
|
||||||
|
return stripe[ isSetupIntent ? 'confirmCardSetup' : 'confirmCardPayment' ](
|
||||||
|
intentSecret
|
||||||
|
)
|
||||||
|
.then( function( response ) {
|
||||||
|
if ( response.error ) {
|
||||||
|
throw response.error;
|
||||||
|
}
|
||||||
|
const intent =
|
||||||
|
response[ isSetupIntent ? 'setupIntent' : 'paymentIntent' ];
|
||||||
|
if (
|
||||||
|
intent.status !== 'requires_capture' &&
|
||||||
|
intent.status !== 'succeeded'
|
||||||
|
) {
|
||||||
|
return checkoutResponse;
|
||||||
|
}
|
||||||
|
checkoutResponse.redirectUrl = verificationUrl;
|
||||||
|
return checkoutResponse;
|
||||||
|
} )
|
||||||
|
.catch( function( error ) {
|
||||||
|
checkoutResponse.type = emitResponse.responseTypes.ERROR;
|
||||||
|
checkoutResponse.message = error.message;
|
||||||
|
checkoutResponse.retry = true;
|
||||||
|
checkoutResponse.messageContext =
|
||||||
|
emitResponse.noticeContexts.PAYMENTS;
|
||||||
|
// Reports back to the server.
|
||||||
|
window.fetch( verificationUrl + '&is_ajax' );
|
||||||
|
return checkoutResponse;
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePaymentIntents = ( stripe, subscriber, emitResponse ) => {
|
||||||
|
useEffect( () => {
|
||||||
|
const unsubscribe = subscriber( ( { processingResponse } ) => {
|
||||||
|
const paymentDetails = processingResponse.paymentDetails || {};
|
||||||
|
return openIntentModal( stripe, paymentDetails, emitResponse );
|
||||||
|
} );
|
||||||
|
return () => unsubscribe();
|
||||||
|
}, [ subscriber, stripe ] );
|
||||||
|
};
|
|
@ -55,6 +55,7 @@ const PaymentRequestExpressComponent = ( {
|
||||||
eventRegistration,
|
eventRegistration,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setExpressPaymentError,
|
setExpressPaymentError,
|
||||||
|
emitResponse,
|
||||||
onClick,
|
onClick,
|
||||||
onClose,
|
onClose,
|
||||||
} ) => {
|
} ) => {
|
||||||
|
@ -210,29 +211,48 @@ const PaymentRequestExpressComponent = ( {
|
||||||
const handlers = eventHandlers.current;
|
const handlers = eventHandlers.current;
|
||||||
if ( handlers.sourceEvent && isProcessing ) {
|
if ( handlers.sourceEvent && isProcessing ) {
|
||||||
const response = {
|
const response = {
|
||||||
billingData: getBillingData( handlers.sourceEvent ),
|
type: emitResponse.responseTypes.SUCCESS,
|
||||||
paymentMethodData: getPaymentMethodData(
|
meta: {
|
||||||
handlers.sourceEvent,
|
billingData: getBillingData( handlers.sourceEvent ),
|
||||||
paymentRequestType
|
paymentMethodData: getPaymentMethodData(
|
||||||
),
|
handlers.sourceEvent,
|
||||||
shippingData: getShippingData( handlers.sourceEvent ),
|
paymentRequestType
|
||||||
|
),
|
||||||
|
shippingData: getShippingData( handlers.sourceEvent ),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
return true;
|
return { type: emitResponse.responseTypes.SUCCESS };
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCheckoutComplete = ( forSuccess = true ) => () => {
|
const onCheckoutComplete = ( checkoutResponse ) => {
|
||||||
const handlers = eventHandlers.current;
|
const handlers = eventHandlers.current;
|
||||||
|
let response = { type: emitResponse.responseTypes.SUCCESS };
|
||||||
if ( handlers.sourceEvent && isProcessing ) {
|
if ( handlers.sourceEvent && isProcessing ) {
|
||||||
if ( forSuccess ) {
|
const { paymentStatus, paymentDetails } = checkoutResponse;
|
||||||
|
if ( paymentStatus === emitResponse.responseTypes.SUCCESS ) {
|
||||||
completePayment( handlers.sourceEvent );
|
completePayment( handlers.sourceEvent );
|
||||||
} else {
|
}
|
||||||
abortPayment( handlers.sourceEvent );
|
if (
|
||||||
|
paymentStatus === emitResponse.responseTypes.ERROR ||
|
||||||
|
paymentStatus === emitResponse.responseTypes.FAIL
|
||||||
|
) {
|
||||||
|
const paymentResponse = abortPayment(
|
||||||
|
handlers.sourceEvent,
|
||||||
|
paymentDetails?.errorMessage
|
||||||
|
);
|
||||||
|
response = {
|
||||||
|
type: emitResponse.responseTypes.ERROR,
|
||||||
|
message: paymentResponse.message,
|
||||||
|
messageContext:
|
||||||
|
emitResponse.noticeContexts.EXPRESS_PAYMENTS,
|
||||||
|
retry: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
handlers.sourceEvent = null;
|
handlers.sourceEvent = null;
|
||||||
}
|
}
|
||||||
return true;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
// when canMakePayment is true, then we set listeners on payment request for
|
// when canMakePayment is true, then we set listeners on payment request for
|
||||||
|
@ -301,11 +321,11 @@ const PaymentRequestExpressComponent = ( {
|
||||||
const unsubscribePaymentProcessing = subscriber.onPaymentProcessing(
|
const unsubscribePaymentProcessing = subscriber.onPaymentProcessing(
|
||||||
onPaymentProcessing
|
onPaymentProcessing
|
||||||
);
|
);
|
||||||
const unsubscribeCheckoutCompleteSuccess = subscriber.onCheckoutCompleteSuccess(
|
const unsubscribeCheckoutCompleteSuccess = subscriber.onCheckoutAfterProcessingWithSuccess(
|
||||||
onCheckoutComplete()
|
onCheckoutComplete
|
||||||
);
|
);
|
||||||
const unsubscribeCheckoutCompleteFail = subscriber.onCheckoutCompleteError(
|
const unsubscribeCheckoutCompleteFail = subscriber.onCheckoutAfterProcessingWithError(
|
||||||
onCheckoutComplete( false )
|
onCheckoutComplete
|
||||||
);
|
);
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribeCheckoutCompleteFail();
|
unsubscribeCheckoutCompleteFail();
|
||||||
|
@ -326,8 +346,8 @@ const PaymentRequestExpressComponent = ( {
|
||||||
eventRegistration.onShippingRateSelectSuccess,
|
eventRegistration.onShippingRateSelectSuccess,
|
||||||
eventRegistration.onShippingRateSelectFail,
|
eventRegistration.onShippingRateSelectFail,
|
||||||
eventRegistration.onPaymentProcessing,
|
eventRegistration.onPaymentProcessing,
|
||||||
eventRegistration.onCheckoutCompleteSuccess,
|
eventRegistration.onCheckoutAfterProcessingWithSuccess,
|
||||||
eventRegistration.onCheckoutCompleteError,
|
eventRegistration.onCheckoutAfterProcessingWithError,
|
||||||
] );
|
] );
|
||||||
|
|
||||||
// locale is not a valid value for the paymentRequestButton style.
|
// locale is not a valid value for the paymentRequestButton style.
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
* redirectUrl to the given value.
|
* redirectUrl to the given value.
|
||||||
* @property {function(boolean=)} setHasError Dispatches an action that sets the
|
* @property {function(boolean=)} setHasError Dispatches an action that sets the
|
||||||
* checkout status to having an error.
|
* checkout status to having an error.
|
||||||
* @property {function()} setComplete Dispatches an action that sets the
|
* @property {function(Object)} setAfterProcessing Dispatches an action that sets the
|
||||||
* checkout status to complete.
|
* checkout status to after processing and
|
||||||
|
* also sets the response data accordingly.
|
||||||
* @property {function()} incrementCalculating Dispatches an action that increments
|
* @property {function()} incrementCalculating Dispatches an action that increments
|
||||||
* the calculating state for checkout by one.
|
* the calculating state for checkout by one.
|
||||||
* @property {function()} decrementCalculating Dispatches an action that decrements
|
* @property {function()} decrementCalculating Dispatches an action that decrements
|
||||||
|
@ -20,18 +21,20 @@
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CheckoutStatusConstants
|
* @typedef {Object} CheckoutStatusConstants
|
||||||
*
|
*
|
||||||
* @property {string} PRISTINE Checkout is in it's initialized state.
|
* @property {string} PRISTINE Checkout is in it's initialized state.
|
||||||
* @property {string} IDLE When checkout state has changed but
|
* @property {string} IDLE When checkout state has changed but there is no
|
||||||
* there is no activity happening.
|
* activity happening.
|
||||||
* @property {string} PROCESSING This is the state when the checkout
|
* @property {string} BEFORE_PROCESSING This is the state before checkout processing
|
||||||
* button has been pressed and the
|
* begins after the checkout button has been
|
||||||
* checkout data has been sent to the
|
* pressed/submitted.
|
||||||
* server for processing.
|
* @property {string} PROCESSING After BEFORE_PROCESSING status emitters have
|
||||||
* @property {string} PROCESSING_COMPLETE This is the state when the checkout
|
* finished successfully. Payment processing is
|
||||||
* processing has been completed.
|
* started on this checkout status.
|
||||||
* @property {string} COMPLETE This is the status when the server has
|
* @property {string} AFTER_PROCESSING After server side checkout processing is completed
|
||||||
* completed processing the data
|
* this status is set.
|
||||||
* successfully.
|
* @property {string} COMPLETE After the AFTER_PROCESSING event emitters have
|
||||||
|
* completed. This status triggers the checkout
|
||||||
|
* redirect.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -199,19 +199,6 @@
|
||||||
* observers for the
|
* observers for the
|
||||||
* payment processing
|
* payment processing
|
||||||
* event.
|
* event.
|
||||||
* @property {function(function())} onPaymentSuccess Event registration
|
|
||||||
* callback for registering
|
|
||||||
* observers for the
|
|
||||||
* successful payment
|
|
||||||
* event.
|
|
||||||
* @property {function(function())} onPaymentFail Event registration
|
|
||||||
* callback for registering
|
|
||||||
* observers for the
|
|
||||||
* failed payment event.
|
|
||||||
* @property {function(function())} onPaymentError Event registration
|
|
||||||
* callback for registering
|
|
||||||
* observers for the
|
|
||||||
* payment error event.
|
|
||||||
* @property {function(string)} setExpressPaymentError A function used by
|
* @property {function(string)} setExpressPaymentError A function used by
|
||||||
* express payment methods
|
* express payment methods
|
||||||
* to indicate an error
|
* to indicate an error
|
||||||
|
@ -224,56 +211,65 @@
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CheckoutDataContext
|
* @typedef {Object} CheckoutDataContext
|
||||||
*
|
*
|
||||||
* @property {string} submitLabel The label to use for the
|
* @property {string} submitLabel The label to use for the
|
||||||
* submit checkout button.
|
* submit checkout button.
|
||||||
* @property {function()} onSubmit The callback to register with
|
* @property {function()} onSubmit The callback to register with
|
||||||
* the checkout submit button.
|
* the checkout submit button.
|
||||||
* @property {boolean} isComplete True when checkout is complete
|
* @property {boolean} isComplete True when checkout is complete
|
||||||
* and ready for redirect.
|
* and ready for redirect.
|
||||||
* @property {boolean} isProcessingComplete True when checkout processing
|
* @property {boolean} isBeforeProcessing True during any observers
|
||||||
* is complete.
|
* executing logic before
|
||||||
* @property {boolean} isIdle True when the checkout state
|
* checkout processing (eg.
|
||||||
* has changed and checkout has
|
* validation).
|
||||||
* no activity.
|
* @property {boolean} isAfterProcessing True when checkout status is
|
||||||
* @property {boolean} isProcessing True when checkout has been
|
* AFTER_PROCESSING.
|
||||||
* submitted and is being
|
* @property {boolean} isIdle True when the checkout state
|
||||||
* processed by the server.
|
* has changed and checkout has
|
||||||
* @property {boolean} isCalculating True when something in the
|
* no activity.
|
||||||
* checkout is resulting in
|
* @property {boolean} isProcessing True when checkout has been
|
||||||
* totals being calculated.
|
* submitted and is being
|
||||||
* @property {boolean} hasError True when the checkout is in
|
* processed. Note, payment
|
||||||
* an error state. Whatever
|
* related processing happens
|
||||||
* caused the error
|
* during this state. When
|
||||||
* (validation/payment method)
|
* payemnt status is success,
|
||||||
* will likely have triggered a
|
* processing happens on the
|
||||||
* notice.
|
* server.
|
||||||
* @property {string} redirectUrl This is the url that checkout
|
* @property {boolean} isCalculating True when something in the
|
||||||
* will redirect to when it's
|
* checkout is resulting in
|
||||||
* ready.
|
* totals being calculated.
|
||||||
* @property {function(function(),number=)} onCheckoutCompleteSuccess Used to register a callback
|
* @property {boolean} hasError True when the checkout is in
|
||||||
* that will fire when the
|
* an error state. Whatever
|
||||||
* checkout is marked complete
|
* caused the error
|
||||||
* successfully.
|
* (validation/payment method)
|
||||||
* @property {function(function(),number=)} onCheckoutCompleteError Used to register a callback
|
* will likely have triggered a
|
||||||
* that will fire when the
|
* notice.
|
||||||
* checkout is marked complete
|
* @property {string} redirectUrl This is the url that checkout
|
||||||
* and has an error.
|
* will redirect to when it's
|
||||||
* @property {function(function(),number=)} onCheckoutProcessing Used to register a callback
|
* ready.
|
||||||
* that will fire when the
|
* @property {function(function(),number=)} onCheckoutAfterProcessingWithSuccess Used to register a
|
||||||
* checkout has been submitted
|
* callback that will fire after
|
||||||
* before being sent off to the
|
* checkout has been processed
|
||||||
* server.
|
* and there are no errors.
|
||||||
* @property {CheckoutDispatchActions} dispatchActions Various actions that can be
|
* @property {function(function(),number=)} onCheckoutAfterProcessingWithError Used to register a
|
||||||
* dispatched for the checkout
|
* callback that will fire when
|
||||||
* context data.
|
* the checkout has been
|
||||||
* @property {number} orderId This is the ID for the draft
|
* processed and has an error.
|
||||||
* order if one exists.
|
* @property {function(function(),number=)} onCheckoutBeforeProcessing Used to register a callback
|
||||||
* @property {boolean} hasOrder True when the checkout has a
|
* that will fire when the
|
||||||
* draft order from the API.
|
* checkout has been submitted
|
||||||
* @property {boolean} isCart When true, means the provider
|
* before being sent off to the
|
||||||
* is providing data for the cart.
|
* server.
|
||||||
* @property {number} customerId This is the ID of the customer
|
* @property {CheckoutDispatchActions} dispatchActions Various actions that can be
|
||||||
* the draft order belongs to.
|
* dispatched for the checkout
|
||||||
|
* context data.
|
||||||
|
* @property {number} orderId This is the ID for the draft
|
||||||
|
* order if one exists.
|
||||||
|
* @property {boolean} hasOrder True when the checkout has a
|
||||||
|
* draft order from the API.
|
||||||
|
* @property {boolean} isCart When true, means the provider
|
||||||
|
* is providing data for the cart.
|
||||||
|
* @property {number} customerId This is the ID of the customer
|
||||||
|
* the draft order belongs to.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -73,4 +73,82 @@
|
||||||
* the cart.
|
* the cart.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} EmitResponseTypes
|
||||||
|
*
|
||||||
|
* @property {string} SUCCESS To indicate a success response.
|
||||||
|
* @property {string} FAIL To indicate a failed response.
|
||||||
|
* @property {string} ERROR To indicate an error response.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} NoticeContexts
|
||||||
|
*
|
||||||
|
* @property {string} PAYMENTS Notices for the payments step.
|
||||||
|
* @property {string} EXPRESS_PAYMENTS Notices for the express payments step.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {NoticeContexts['PAYMENTS']|NoticeContexts['EXPRESS_PAYMENTS']} NoticeContextsEnum
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} EmitSuccessResponse
|
||||||
|
*
|
||||||
|
* @property {EmitResponseTypes['SUCCESS']} type Should have the value of
|
||||||
|
* EmitResponseTypes.SUCCESS.
|
||||||
|
* @property {string} [redirectUrl] If the redirect url should be changed set
|
||||||
|
* this. Note, this is ignored for some
|
||||||
|
* emitters.
|
||||||
|
* @property {Object} [meta] Additional data returned for the success
|
||||||
|
* response. This varies between context
|
||||||
|
* emitters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} EmitFailResponse
|
||||||
|
*
|
||||||
|
* @property {EmitResponseTypes['FAIL']} type Should have the value of
|
||||||
|
* EmitResponseTypes.FAIL
|
||||||
|
* @property {string} message A message to trigger a notice for.
|
||||||
|
* @property {NoticeContextsEnum} [messageContext] What context to display any message in.
|
||||||
|
* @property {Object} [meta] Additional data returned for the fail
|
||||||
|
* response. This varies between context
|
||||||
|
* emitters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} EmitErrorResponse
|
||||||
|
*
|
||||||
|
* @property {EmitResponseTypes['ERROR']} type Should have the value of
|
||||||
|
* EmitResponseTypes.ERROR
|
||||||
|
* @property {string} message A message to trigger a notice for.
|
||||||
|
* @property {boolean} retry If false, then it means an
|
||||||
|
* irrecoverable error so don't allow for
|
||||||
|
* shopper to retry checkout (which may
|
||||||
|
* mean either a different payment or
|
||||||
|
* fixing validation errors).
|
||||||
|
* @property {Object} [validationErrors] If provided, will be set as validation
|
||||||
|
* errors in the validation context.
|
||||||
|
* @property {NoticeContextsEnum} [messageContext] What context to display any message in.
|
||||||
|
* @property {Object} [meta] Additional data returned for the fail
|
||||||
|
* response. This varies between context
|
||||||
|
* emitters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} EmitResponseApi
|
||||||
|
*
|
||||||
|
* @property {EmitResponseTypes} responseTypes An object of various response types that can
|
||||||
|
* be used in returned response objects.
|
||||||
|
* @property {NoticeContexts} noticeContexts An object of various notice contexts that can
|
||||||
|
* be used for targeting where a notice appears.
|
||||||
|
* @property {function(Object):boolean} isSuccessResponse Returns whether the given response is of a
|
||||||
|
* success response type.
|
||||||
|
* @property {function(Object):boolean} isErrorResponse Returns whether the given response is of an
|
||||||
|
* error response type.
|
||||||
|
* @property {function(Object):boolean} isFailResponse Returns whether the given response is of a
|
||||||
|
* fail response type.
|
||||||
|
*/
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
* @typedef {import('@woocommerce/type-defs/contexts').ShippingErrorStatus} ShippingErrorStatus
|
* @typedef {import('@woocommerce/type-defs/contexts').ShippingErrorStatus} ShippingErrorStatus
|
||||||
* @typedef {import('@woocommerce/type-defs/contexts').ShippingErrorTypes} ShippingErrorTypes
|
* @typedef {import('@woocommerce/type-defs/contexts').ShippingErrorTypes} ShippingErrorTypes
|
||||||
* @typedef {import('@woocommerce/type-defs/settings').WooCommerceSiteCurrency} SiteCurrency
|
* @typedef {import('@woocommerce/type-defs/settings').WooCommerceSiteCurrency} SiteCurrency
|
||||||
|
* @typedef {import('@woocommerce/type-defs/hooks').EmitResponseTypes} EmitResponseTypes
|
||||||
|
* @typedef {import('@woocommerce/type-defs/hooks').NoticeContexts} NoticeContexts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,13 +136,13 @@
|
||||||
/**
|
/**
|
||||||
* @typedef EventRegistrationProps
|
* @typedef EventRegistrationProps
|
||||||
*
|
*
|
||||||
* @property {function(function())} onCheckoutCompleteSuccess Used to subscribe callbacks firing
|
* @property {function(function())} onCheckoutAfterProcessingWithSuccess Used to subscribe callbacks
|
||||||
* when checkout has completed
|
* firing when checkout has completed
|
||||||
* processing successfully.
|
* processing successfully.
|
||||||
* @property {function(function())} onCheckoutCompleteError Used to subscribe callbacks firing
|
* @property {function(function())} onCheckoutAfterProcessingWithError Used to subscribe callbacks
|
||||||
* when checkout has completed
|
* firing when checkout has completed
|
||||||
* processing with an error.
|
* processing with an error.
|
||||||
* @property {function(function())} onCheckoutProcessing Used to subscribe callbacks that
|
* @property {function(function())} onCheckoutBeforeProcessing Used to subscribe callbacks that
|
||||||
* will fire when checkout begins
|
* will fire when checkout begins
|
||||||
* processing (as a part of the
|
* processing (as a part of the
|
||||||
* processing process).
|
* processing process).
|
||||||
|
@ -160,15 +162,6 @@
|
||||||
* @property {function(function())} onPaymentProcessing Event registration callback for
|
* @property {function(function())} onPaymentProcessing Event registration callback for
|
||||||
* registering observers for the
|
* registering observers for the
|
||||||
* payment processing event.
|
* payment processing event.
|
||||||
* @property {function(function())} onPaymentSuccess Event registration callback for
|
|
||||||
* registering observers for the
|
|
||||||
* successful payment event.
|
|
||||||
* @property {function(function())} onPaymentFail Event registration callback for
|
|
||||||
* registering observers for the
|
|
||||||
* failed payment event.
|
|
||||||
* @property {function(function())} onPaymentError Event registration callback for
|
|
||||||
* registering observers for the
|
|
||||||
* payment error event.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,6 +173,16 @@
|
||||||
* saved payment method functionality
|
* saved payment method functionality
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef EmitResponseProps
|
||||||
|
*
|
||||||
|
* @property {EmitResponseTypes} responseTypes Response types that can be returned from emitter
|
||||||
|
* observers.
|
||||||
|
* @property {NoticeContexts} noticeContexts Available contexts that can be returned as the value
|
||||||
|
* for the messageContext property on the object
|
||||||
|
* returned from an emitter observer.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registered payment method props
|
* Registered payment method props
|
||||||
*
|
*
|
||||||
|
@ -194,13 +197,15 @@
|
||||||
* @property {EventRegistrationProps} eventRegistration Various event registration helpers
|
* @property {EventRegistrationProps} eventRegistration Various event registration helpers
|
||||||
* for subscribing callbacks for
|
* for subscribing callbacks for
|
||||||
* events.
|
* events.
|
||||||
|
* @property {EmitResponseProps} emitResponse Utilities for usage in event
|
||||||
|
* observer response objects.
|
||||||
* @property {Function} [onSubmit] Used to trigger checkout
|
* @property {Function} [onSubmit] Used to trigger checkout
|
||||||
* processing.
|
* processing.
|
||||||
* @property {string} [activePaymentMethod] Indicates what the active payment
|
* @property {string} [activePaymentMethod] Indicates what the active payment
|
||||||
* method is.
|
* method is.
|
||||||
* @property {ComponentProps} components Components exposed to payment
|
* @property {ComponentProps} components Components exposed to payment
|
||||||
* methods for use.
|
* methods for use.
|
||||||
* @property {function(string)} [setExpressPaymentError] For setting an error (error
|
* @property {function(string)} [setExpressPaymentError] For setting an error (error
|
||||||
* message string) for express
|
* message string) for express
|
||||||
* payment methods. Does not change
|
* payment methods. Does not change
|
||||||
* payment status.
|
* payment status.
|
||||||
|
|
|
@ -231,13 +231,12 @@ class Library {
|
||||||
$_POST = $context->payment_data;
|
$_POST = $context->payment_data;
|
||||||
|
|
||||||
// Call the process payment method of the chosen gatway.
|
// Call the process payment method of the chosen gatway.
|
||||||
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
$payment_method_object = $context->get_payment_method_instance();
|
||||||
|
|
||||||
if ( ! isset( $available_gateways[ $context->payment_method ] ) ) {
|
if ( ! $payment_method_object instanceof \WC_Payment_Gateway ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$payment_method_object = $available_gateways[ $context->payment_method ];
|
|
||||||
$payment_method_object->validate_fields();
|
$payment_method_object->validate_fields();
|
||||||
|
|
||||||
if ( 0 !== wc_notice_count( 'error' ) ) {
|
if ( 0 !== wc_notice_count( 'error' ) ) {
|
||||||
|
@ -250,9 +249,14 @@ class Library {
|
||||||
// Restore $_POST data.
|
// Restore $_POST data.
|
||||||
$_POST = $post_data;
|
$_POST = $post_data;
|
||||||
|
|
||||||
|
// Clear notices so they don't show up in the block.
|
||||||
|
wc_clear_notices();
|
||||||
|
|
||||||
// Handle result.
|
// Handle result.
|
||||||
$result->set_status( isset( $gateway_result['result'] ) && 'success' === $gateway_result['result'] ? 'success' : 'failure' );
|
$result->set_status( isset( $gateway_result['result'] ) && 'success' === $gateway_result['result'] ? 'success' : 'failure' );
|
||||||
$result->set_payment_details( [] );
|
|
||||||
|
// set payment_details from result.
|
||||||
|
$result->set_payment_details( array_merge( $result->payment_details, $gateway_result ) );
|
||||||
$result->set_redirect_url( $gateway_result['redirect'] );
|
$result->set_redirect_url( $gateway_result['redirect'] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ final class Stripe extends AbstractPaymentMethodType {
|
||||||
public function __construct( Api $asset_api ) {
|
public function __construct( Api $asset_api ) {
|
||||||
$this->asset_api = $asset_api;
|
$this->asset_api = $asset_api;
|
||||||
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_payment_request_order_meta' ], 8, 2 );
|
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_payment_request_order_meta' ], 8, 2 );
|
||||||
|
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_stripe_intents' ], 9999, 2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -227,7 +228,7 @@ final class Stripe extends AbstractPaymentMethodType {
|
||||||
* @param PaymentContext $context Holds context for the payment.
|
* @param PaymentContext $context Holds context for the payment.
|
||||||
* @param PaymentResult $result Result object for the payment.
|
* @param PaymentResult $result Result object for the payment.
|
||||||
*/
|
*/
|
||||||
public function add_payment_request_order_meta( PaymentContext $context, PaymentResult $result ) {
|
public function add_payment_request_order_meta( PaymentContext $context, PaymentResult &$result ) {
|
||||||
$data = $context->payment_data;
|
$data = $context->payment_data;
|
||||||
if ( ! empty( $data['payment_request_type'] ) && 'stripe' === $context->payment_method ) {
|
if ( ! empty( $data['payment_request_type'] ) && 'stripe' === $context->payment_method ) {
|
||||||
// phpcs:ignore WordPress.Security.NonceVerification
|
// phpcs:ignore WordPress.Security.NonceVerification
|
||||||
|
@ -236,5 +237,50 @@ final class Stripe extends AbstractPaymentMethodType {
|
||||||
WC_Stripe_Payment_Request::add_order_meta( $context->order->id, $context->payment_data );
|
WC_Stripe_Payment_Request::add_order_meta( $context->order->id, $context->payment_data );
|
||||||
$_POST = $post_data;
|
$_POST = $post_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hook into stripe error processing so that we can capture the error to
|
||||||
|
// payment details (which is added to notices and thus not helpful for
|
||||||
|
// this context).
|
||||||
|
if ( 'stripe' === $context->payment_method ) {
|
||||||
|
add_action(
|
||||||
|
'wc_gateway_stripe_process_payment_error',
|
||||||
|
function( $error ) use ( &$result ) {
|
||||||
|
$payment_details = $result->payment_details;
|
||||||
|
$payment_details['errorMessage'] = $error->getLocalizedMessage();
|
||||||
|
$result->set_payment_details( $payment_details );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles any potential stripe intents on the order that need handled.
|
||||||
|
*
|
||||||
|
* This is configured to execute after legacy payment processing has
|
||||||
|
* happened on the woocommerce_rest_checkout_process_payment_with_context
|
||||||
|
* action hook.
|
||||||
|
*
|
||||||
|
* @param PaymentContext $context Holds context for the payment.
|
||||||
|
* @param PaymentResult $result Result object for the payment.
|
||||||
|
*/
|
||||||
|
public function add_stripe_intents( PaymentContext $context, PaymentResult &$result ) {
|
||||||
|
if ( 'stripe' === $context->payment_method
|
||||||
|
&& (
|
||||||
|
! empty( $result->payment_details['payment_intent_secret'] )
|
||||||
|
|| ! empty( $result->payment_details['setup_intent_secret'] )
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
$payment_details = $result->payment_details;
|
||||||
|
$payment_details['verification_endpoint'] = add_query_arg(
|
||||||
|
[
|
||||||
|
'order' => $context->order->get_id(),
|
||||||
|
'nonce' => wp_create_nonce( 'wc_stripe_confirm_pi' ),
|
||||||
|
'redirect_to' => rawurlencode( $result->redirect_url ),
|
||||||
|
],
|
||||||
|
home_url() . \WC_Ajax::get_endpoint( 'wc_stripe_verify_intent' )
|
||||||
|
);
|
||||||
|
$result->set_payment_details( $payment_details );
|
||||||
|
$result->set_status( 'success' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,19 @@ class PaymentContext {
|
||||||
$this->payment_method = (string) $payment_method;
|
$this->payment_method = (string) $payment_method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the payment method instance for the current set payment method.
|
||||||
|
*
|
||||||
|
* @return {\WC_Payment_Gateway|null} An instance of the payment gateway if it exists.
|
||||||
|
*/
|
||||||
|
public function get_payment_method_instance() {
|
||||||
|
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||||
|
if ( ! isset( $available_gateways[ $this->payment_method ] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return $available_gateways[ $this->payment_method ];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the order context.
|
* Set the order context.
|
||||||
*
|
*
|
||||||
|
|
|
@ -166,9 +166,31 @@ class CheckoutSchema extends AbstractSchema {
|
||||||
'payment_method' => $order->get_payment_method(),
|
'payment_method' => $order->get_payment_method(),
|
||||||
'payment_result' => [
|
'payment_result' => [
|
||||||
'payment_status' => $payment_result->status,
|
'payment_status' => $payment_result->status,
|
||||||
'payment_details' => $payment_result->payment_details,
|
'payment_details' => $this->prepare_payment_details_for_response( $payment_result->payment_details ),
|
||||||
'redirect_url' => $payment_result->redirect_url,
|
'redirect_url' => $payment_result->redirect_url,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This prepares the payment details for the response so it's following the
|
||||||
|
* schema where it's an array of objects.
|
||||||
|
*
|
||||||
|
* @param array $payment_details An array of payment details from the processed payment.
|
||||||
|
*
|
||||||
|
* @return array An array of objects where each object has the key and value
|
||||||
|
* as distinct properties.
|
||||||
|
*/
|
||||||
|
protected function prepare_payment_details_for_response( array $payment_details ) {
|
||||||
|
return array_map(
|
||||||
|
function( $key, $value ) {
|
||||||
|
return (object) [
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
array_keys( $payment_details ),
|
||||||
|
$payment_details
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue