Skip to content

Commit

Permalink
Add payment processing using ECE on the Blocks checkout and cart pages (
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelzaleski authored Jun 3, 2024
1 parent 08f4852 commit ef195c0
Show file tree
Hide file tree
Showing 22 changed files with 1,456 additions and 548 deletions.
4 changes: 4 additions & 0 deletions changelog/add-8773-ece-support-blocks-checkout-page
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Add payment processing using ECE in the Blocks checkout and cart pages.
33 changes: 33 additions & 0 deletions client/checkout/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
getPaymentRequestData,
getPaymentRequestAjaxURL,
buildAjaxURL,
getExpressCheckoutAjaxURL,
getExpressCheckoutConfig,
} from 'utils/express-checkout';

/**
Expand Down Expand Up @@ -406,6 +408,37 @@ export default class WCPayAPI {
} );
}

/**
* Submits shipping address to get available shipping options
* from Express Checkout ECE payment method.
*
* @param {Object} shippingAddress Shipping details.
* @return {Promise} Promise for the request to the server.
*/
expressCheckoutECECalculateShippingOptions( shippingAddress ) {
return this.request(
getExpressCheckoutAjaxURL( 'get_shipping_options' ),
{
security: getExpressCheckoutConfig( 'nonce' )?.shipping,
is_product_page: getExpressCheckoutConfig( 'is_product_page' ),
...shippingAddress,
}
);
}

/**
* Creates order based on Express Checkout ECE payment method.
*
* @param {Object} paymentData Order data.
* @return {Promise} Promise for the request to the server.
*/
expressCheckoutECECreateOrder( paymentData ) {
return this.request( getExpressCheckoutAjaxURL( 'create_order' ), {
_wpnonce: getExpressCheckoutConfig( 'nonce' )?.checkout,
...paymentData,
} );
}

initWooPay( userEmail, woopayUserSession ) {
if ( ! this.isWooPayRequesting ) {
this.isWooPayRequesting = true;
Expand Down
7 changes: 5 additions & 2 deletions client/checkout/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,11 @@ if ( getUPEConfig( 'isWooPayEnabled' ) ) {
}
}

registerExpressPaymentMethod( paymentRequestPaymentMethod( api ) );
registerExpressPaymentMethod( expressCheckoutElementPaymentMethod( api ) );
if ( getUPEConfig( 'isExpressCheckoutElementEnabled' ) ) {
registerExpressPaymentMethod( expressCheckoutElementPaymentMethod( api ) );
} else {
registerExpressPaymentMethod( paymentRequestPaymentMethod( api ) );
}
window.addEventListener( 'load', () => {
enqueueFraudScripts( getUPEConfig( 'fraudServices' ) );
addCheckoutTracking();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* External dependencies
*/
import { ExpressCheckoutElement } from '@stripe/react-stripe-js';
import { shippingAddressChangeHandler } from '../../event-handlers';
import { useExpressCheckout } from '../hooks/use-express-checkout';

/**
* ExpressCheckout express payment method component.
*
* @param {Object} props PaymentMethodProps.
*
* @return {ReactNode} Stripe Elements component.
*/
const ExpressCheckoutComponent = ( {
api,
billing,
shippingData,
setExpressPaymentError,
onClick,
onClose,
} ) => {
const {
buttonOptions,
onButtonClick,
onConfirm,
onCancel,
} = useExpressCheckout( {
api,
billing,
shippingData,
onClick,
onClose,
setExpressPaymentError,
} );

const onShippingAddressChange = ( event ) => {
shippingAddressChangeHandler( api, event );
};

return (
<ExpressCheckoutElement
options={ buttonOptions }
onClick={ onButtonClick }
onConfirm={ onConfirm }
onCancel={ onCancel }
onShippingAddressChange={ onShippingAddressChange }
/>
);
};

export default ExpressCheckoutComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* External dependencies
*/
import { Elements } from '@stripe/react-stripe-js';

/**
* Internal dependencies
*/
import ExpressCheckoutComponent from './express-checkout-component';

const ExpressCheckoutContainer = ( props ) => {
const { stripe, billing } = props;

const options = {
mode: 'payment',
paymentMethodCreation: 'manual',
amount: billing.cartTotal.value,
currency: billing.currency.code.toLowerCase(),
};

return (
<Elements stripe={ stripe } options={ options }>
<ExpressCheckoutComponent { ...props } />
</Elements>
);
};

export default ExpressCheckoutContainer;
36 changes: 0 additions & 36 deletions client/express-checkout/blocks/express-checkout.js

This file was deleted.

93 changes: 93 additions & 0 deletions client/express-checkout/blocks/hooks/use-express-checkout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* global wcpayExpressCheckoutParams */

/**
* External dependencies
*/
import { useCallback } from '@wordpress/element';
import { useStripe, useElements } from '@stripe/react-stripe-js';
import { normalizeLineItems } from 'wcpay/express-checkout/utils';
import { onConfirmHandler } from 'wcpay/express-checkout/event-handlers';

export const useExpressCheckout = ( {
api,
billing,
shippingData,
onClick,
onClose,
setExpressPaymentError,
} ) => {
const stripe = useStripe();
const elements = useElements();

const buttonOptions = {
paymentMethods: {
applePay: 'always',
googlePay: 'always',
link: 'auto',
},
buttonType: {
googlePay: wcpayExpressCheckoutParams.button.type,
applePay: wcpayExpressCheckoutParams.button.type,
},
};

const onCancel = () => {
onClose();
};

const completePayment = ( redirectUrl ) => {
window.location = redirectUrl;
};

const abortPayment = ( onConfirmEvent, message ) => {
onConfirmEvent.paymentFailed( 'fail' );
setExpressPaymentError( message );
};

const onButtonClick = useCallback(
( event ) => {
const options = {
lineItems: normalizeLineItems( billing?.cartTotalItems ),
emailRequired: true,
shippingAddressRequired: shippingData?.needsShipping,
phoneNumberRequired:
wcpayExpressCheckoutParams?.checkout?.needs_payer_phone,
shippingRates: shippingData?.shippingRates[ 0 ]?.shipping_rates?.map(
( r ) => {
return {
id: r.rate_id,
amount: parseInt( r.price, 10 ),
displayName: r.name,
};
}
),
};
event.resolve( options );
onClick();
},
[
onClick,
billing.cartTotalItems,
shippingData.needsShipping,
shippingData.shippingRates,
]
);

const onConfirm = async ( event ) => {
onConfirmHandler(
api,
stripe,
elements,
completePayment,
abortPayment,
event
);
};

return {
buttonOptions,
onButtonClick,
onConfirm,
onCancel,
};
};
9 changes: 7 additions & 2 deletions client/express-checkout/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
* Internal dependencies
*/
import { PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT } from '../../checkout/constants';
import { ExpressCheckout } from './express-checkout';
import ExpressCheckoutContainer from './components/express-checkout-container';
import { getConfig } from '../../utils/checkout';
import ApplePayPreview from './apple-pay-preview';

const expressCheckoutElementPaymentMethod = ( api ) => ( {
name: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT,
content: <ExpressCheckout api={ api } stripe={ api.loadStripe( true ) } />,
content: (
<ExpressCheckoutContainer
api={ api }
stripe={ api.loadStripe( true ) }
/>
),
edit: <ApplePayPreview />,
paymentMethodId: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT,
supports: {
Expand Down
61 changes: 61 additions & 0 deletions client/express-checkout/event-handlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Internal dependencies
*/
import { normalizeOrderData, normalizeShippingAddress } from './utils';
import { getErrorMessageFromNotice } from 'utils/express-checkout';

export const shippingAddressChangeHandler = async ( api, event ) => {
const response = await api.expressCheckoutECECalculateShippingOptions(
normalizeShippingAddress( event.shippingAddress )
);
event.resolve( {
shippingRates: response.shipping_options,
} );
};

export const onConfirmHandler = async (
api,
stripe,
elements,
completePayment,
abortPayment,
event
) => {
const { paymentMethod, error } = await stripe.createPaymentMethod( {
elements,
} );

if ( error ) {
abortPayment( event, error.message );
return;
}

// Kick off checkout processing step.
const createOrderResponse = await api.expressCheckoutECECreateOrder(
normalizeOrderData( event, paymentMethod.id )
);

if ( createOrderResponse.result !== 'success' ) {
return abortPayment(
event,
getErrorMessageFromNotice( createOrderResponse.messages )
);
}

try {
const confirmationRequest = api.confirmIntent(
createOrderResponse.redirect
);

// `true` means there is no intent to confirm.
if ( confirmationRequest === true ) {
completePayment( createOrderResponse.redirect );
} else {
const redirectUrl = await confirmationRequest;

completePayment( redirectUrl );
}
} catch ( e ) {
abortPayment( event, error.message );
}
};
1 change: 1 addition & 0 deletions client/express-checkout/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './normalize';
Loading

0 comments on commit ef195c0

Please sign in to comment.