woocommerce/plugins/woocommerce-blocks/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-checkout-subscriptions.js

242 lines
8.5 KiB
JavaScript
Raw Normal View History

Fix shipping rate and address handling in Stripe payment request payment method. (https://github.com/woocommerce/woocommerce-blocks/pull/2484) * fix dependencies * refactor stripe payment-request to extract things into smaller units - adds/fixes typedefs - fixes dependencies - improves logic. * implement memoizing for functions. * if same shipping address is selected, just call updateWith immediately * add separate handler for failed shipping rate retrieval * improve logic around shipping rate fail/success status * add notice suppression logic to store notices. - this is implemented in checkout processor to suppress notices when express payment methods are active. * add error detection for shipping address errors and update the shipping status accordingly * update type-def * set billingData before shippingData This is needed because of the shipping data and billing data sync logic in use-checkout-address. * have to tighten dependencies to prevent unnecessary firing With us now adding error status setters for shippping, the potential for the shipping status changes to trigger the effect went up. So tightening the dependencies to only the stati we care about prevent unnecessary effect calls. * refactor event handlers to be named and remove all listeners. This is an undocumented api on the stripe `paymentRequest.on` return value, but I’m trusting it will be relatively stable for this api. The need for this is caused by the fact that without it, the listeners are re-registered on the paymentRequest event everytime the paymentRequest modal is closed and reopened. * fix typo in doc block
2020-05-14 23:55:22 +00:00
/**
* External dependencies
*/
import { useEffect, useRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import {
normalizeShippingOptions,
getTotalPaymentItem,
normalizeLineItems,
getBillingData,
getPaymentMethodData,
getShippingData,
} from '../stripe-utils';
/**
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EventRegistrationProps} EventRegistrationProps
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').BillingDataProps} BillingDataProps
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').ShippingDataProps} ShippingDataProps
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EmitResponseProps} EmitResponseProps
*/
/**
* @param {Object} props
*
* @param {boolean} props.canMakePayment Whether the payment request
* can make payment or not.
* @param {boolean} props.isProcessing Whether the express payment
* method is processing or not.
* @param {EventRegistrationProps} props.eventRegistration Various functions for
* registering observers to
* events.
* @param {Object} props.paymentRequestEventHandlers Cached handlers registered
* for paymentRequest events.
* @param {function(string):void} props.clearPaymentRequestEventHandler Clears the cached payment
* request event handler.
* @param {BillingDataProps} props.billing
* @param {ShippingDataProps} props.shippingData
* @param {EmitResponseProps} props.emitResponse
* @param {string} props.paymentRequestType The derived payment request
* type for the express
* payment being processed.
* @param {function(any):void} props.completePayment This is a callback
* receiving the source event
* and setting it to
* successful payment.
* @param {function(any,string):any} props.abortPayment This is a callback
* receiving the source
* event and setting it to
* failed payment.
*/
export const useCheckoutSubscriptions = ( {
canMakePayment,
isProcessing,
eventRegistration,
paymentRequestEventHandlers,
clearPaymentRequestEventHandler,
billing,
shippingData,
emitResponse,
paymentRequestType,
completePayment,
abortPayment,
} ) => {
const {
onShippingRateSuccess,
onShippingRateFail,
onShippingRateSelectSuccess,
onShippingRateSelectFail,
onPaymentProcessing,
onCheckoutAfterProcessingWithSuccess,
onCheckoutAfterProcessingWithError,
} = eventRegistration;
const { noticeContexts, responseTypes } = emitResponse;
const eventHandlers = useRef( paymentRequestEventHandlers );
const currentBilling = useRef( billing );
const currentShipping = useRef( shippingData );
const currentPaymentRequestType = useRef( paymentRequestType );
useEffect( () => {
eventHandlers.current = paymentRequestEventHandlers;
currentBilling.current = billing;
currentShipping.current = shippingData;
currentPaymentRequestType.current = paymentRequestType;
}, [
paymentRequestEventHandlers,
billing,
shippingData,
paymentRequestType,
] );
// subscribe to events.
useEffect( () => {
const onShippingRatesEvent = ( shippingRates ) => {
const handlers = eventHandlers.current;
const billingData = currentBilling.current;
if ( handlers.shippingAddressChange && isProcessing ) {
handlers.shippingAddressChange.updateWith( {
status: 'success',
shippingOptions: normalizeShippingOptions( shippingRates ),
total: getTotalPaymentItem( billingData.cartTotal ),
displayItems: normalizeLineItems(
billingData.cartTotalItems
),
} );
clearPaymentRequestEventHandler( 'shippingAddressChange' );
}
};
const onShippingRatesEventFail = ( currentErrorStatus ) => {
const handlers = eventHandlers.current;
if ( handlers.shippingAddressChange && isProcessing ) {
handlers.shippingAddressChange.updateWith( {
status: currentErrorStatus.hasInvalidAddress
? 'invalid_shipping_address'
: 'fail',
shippingOptions: [],
} );
}
clearPaymentRequestEventHandler( 'shippingAddressChange' );
};
const onShippingSelectedRate = ( forSuccess = true ) => () => {
const handlers = eventHandlers.current;
const shipping = currentShipping.current;
const billingData = currentBilling.current;
if (
handlers.shippingOptionChange &&
! shipping.isSelectingRate &&
isProcessing
) {
const updateObject = forSuccess
? {
status: 'success',
total: getTotalPaymentItem( billingData.cartTotal ),
displayItems: normalizeLineItems(
billingData.cartTotalItems
),
}
: {
status: 'fail',
};
handlers.shippingOptionChange.updateWith( updateObject );
clearPaymentRequestEventHandler( 'shippingOptionChange' );
}
};
const onProcessingPayment = () => {
const handlers = eventHandlers.current;
if ( handlers.sourceEvent && isProcessing ) {
const response = {
type: responseTypes.SUCCESS,
meta: {
billingData: getBillingData( handlers.sourceEvent ),
paymentMethodData: getPaymentMethodData(
handlers.sourceEvent,
currentPaymentRequestType.current
),
shippingData: getShippingData( handlers.sourceEvent ),
},
};
return response;
}
return { type: responseTypes.SUCCESS };
};
const onCheckoutComplete = ( checkoutResponse ) => {
const handlers = eventHandlers.current;
let response = { type: responseTypes.SUCCESS };
if ( handlers.sourceEvent && isProcessing ) {
const { paymentStatus, paymentDetails } = checkoutResponse;
if ( paymentStatus === responseTypes.SUCCESS ) {
completePayment( handlers.sourceEvent );
}
if (
paymentStatus === responseTypes.ERROR ||
paymentStatus === responseTypes.FAIL
) {
const paymentResponse = abortPayment(
handlers.sourceEvent,
paymentDetails?.errorMessage
);
response = {
type: responseTypes.ERROR,
message: paymentResponse.message,
messageContext: noticeContexts.EXPRESS_PAYMENTS,
retry: true,
};
}
clearPaymentRequestEventHandler( 'sourceEvent' );
}
return response;
};
if ( canMakePayment && isProcessing ) {
const unsubscribeShippingRateSuccess = onShippingRateSuccess(
onShippingRatesEvent
);
const unsubscribeShippingRateFail = onShippingRateFail(
onShippingRatesEventFail
);
const unsubscribeShippingRateSelectSuccess = onShippingRateSelectSuccess(
onShippingSelectedRate()
);
const unsubscribeShippingRateSelectFail = onShippingRateSelectFail(
onShippingRatesEventFail
);
const unsubscribePaymentProcessing = onPaymentProcessing(
onProcessingPayment
);
const unsubscribeCheckoutCompleteSuccess = onCheckoutAfterProcessingWithSuccess(
onCheckoutComplete
);
const unsubscribeCheckoutCompleteFail = onCheckoutAfterProcessingWithError(
onCheckoutComplete
);
return () => {
unsubscribeCheckoutCompleteFail();
unsubscribeCheckoutCompleteSuccess();
unsubscribePaymentProcessing();
unsubscribeShippingRateFail();
unsubscribeShippingRateSuccess();
unsubscribeShippingRateSelectSuccess();
unsubscribeShippingRateSelectFail();
};
}
return undefined;
}, [
canMakePayment,
isProcessing,
onShippingRateSuccess,
onShippingRateFail,
onShippingRateSelectSuccess,
onShippingRateSelectFail,
onPaymentProcessing,
onCheckoutAfterProcessingWithSuccess,
onCheckoutAfterProcessingWithError,
responseTypes,
noticeContexts,
completePayment,
abortPayment,
clearPaymentRequestEventHandler,
] );
};