Skip to content

Commit

Permalink
feat: tokenized cart ECE base implementation (#9739)
Browse files Browse the repository at this point in the history
  • Loading branch information
frosso authored Nov 22, 2024
1 parent b49f724 commit 986f195
Show file tree
Hide file tree
Showing 23 changed files with 768 additions and 1,257 deletions.
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ vendor/*
release/*
tests/e2e/docker*
tests/e2e/deps*

# We'll delete the directory and its contents as part of https://github.com/Automattic/woocommerce-payments/issues/9722 .
# ignoring it because we're temporariily cleaning it up.
client/tokenized-payment-request
5 changes: 5 additions & 0 deletions changelog/refactor-tokenized-ece-base-implementation
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: update
Comment: feat: tokenized cart ECE shortcode base implementation.


4 changes: 4 additions & 0 deletions client/express-checkout/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface WCPayExpressCheckoutParams {
currency_code: string;
needs_payer_phone: boolean;
needs_shipping: boolean;
currency_decimals: number;
};

/**
Expand All @@ -54,6 +55,9 @@ export interface WCPayExpressCheckoutParams {
platform_tracker: string;
shipping: string;
update_shipping: string;
tokenized_cart_nonce: string;
tokenized_cart_session_nonce: string;
store_api_nonce: string;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@ import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import PaymentRequestCartApi from '../cart-api';
import ExpressCheckoutCartApi from '../cart-api';

jest.mock( '@wordpress/api-fetch', () => jest.fn() );

global.wcpayPaymentRequestParams = {};
global.wcpayPaymentRequestParams.nonce = {};
global.wcpayPaymentRequestParams.nonce.store_api_nonce =
global.wcpayExpressCheckoutParams = {};
global.wcpayExpressCheckoutParams.nonce = {};
global.wcpayExpressCheckoutParams.nonce.store_api_nonce =
'global_store_api_nonce';
global.wcpayPaymentRequestParams.nonce.tokenized_cart_nonce =
global.wcpayExpressCheckoutParams.nonce.tokenized_cart_nonce =
'global_tokenized_cart_nonce';
global.wcpayPaymentRequestParams.nonce.tokenized_cart_session_nonce =
global.wcpayExpressCheckoutParams.nonce.tokenized_cart_session_nonce =
'global_tokenized_cart_session_nonce';
global.wcpayPaymentRequestParams.checkout = {};
global.wcpayPaymentRequestParams.checkout.currency_code = 'USD';
global.wcpayExpressCheckoutParams.checkout = {};
global.wcpayExpressCheckoutParams.checkout.currency_code = 'USD';

describe( 'PaymentRequestCartApi', () => {
describe( 'ExpressCheckoutCartApi', () => {
afterEach( () => {
jest.resetAllMocks();
} );

it( 'should allow to create an anonymous cart for a specific class instance, without affecting other instances', async () => {
global.wcpayPaymentRequestParams.button_context = 'product';
global.wcpayExpressCheckoutParams.button_context = 'product';
const headers = new Headers();
headers.append(
'X-WooPayments-Tokenized-Cart-Session',
Expand All @@ -39,8 +39,8 @@ describe( 'PaymentRequestCartApi', () => {
json: () => Promise.resolve( {} ),
} );

const api = new PaymentRequestCartApi();
const anotherApi = new PaymentRequestCartApi();
const api = new ExpressCheckoutCartApi();
const anotherApi = new ExpressCheckoutCartApi();

api.useSeparateCart();
await api.getCart();
Expand Down Expand Up @@ -120,12 +120,12 @@ describe( 'PaymentRequestCartApi', () => {
} );

it( 'should call `/cart/update-customer` with the global headers if the cart is not anonymous', async () => {
global.wcpayPaymentRequestParams.button_context = 'cart';
global.wcpayExpressCheckoutParams.button_context = 'cart';
apiFetch.mockResolvedValue( {
headers: new Headers(),
json: () => Promise.resolve( {} ),
} );
const api = new PaymentRequestCartApi();
const api = new ExpressCheckoutCartApi();

await api.updateCustomer( {
billing_address: { last_name: 'Last' },
Expand All @@ -150,14 +150,14 @@ describe( 'PaymentRequestCartApi', () => {
} );

it( 'should store received header information for subsequent usage', async () => {
global.wcpayPaymentRequestParams.button_context = 'cart';
global.wcpayExpressCheckoutParams.button_context = 'cart';
const headers = new Headers();
headers.append( 'Nonce', 'nonce-value' );
apiFetch.mockResolvedValue( {
headers,
json: () => Promise.resolve( {} ),
} );
const api = new PaymentRequestCartApi();
const api = new ExpressCheckoutCartApi();

await api.getCart();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import PaymentRequestOrderApi from '../order-api';
import ExpressCheckoutOrderApi from '../order-api';

jest.mock( '@wordpress/api-fetch', () => jest.fn() );

global.wcpayPaymentRequestParams = {};
global.wcpayPaymentRequestParams.nonce = {};
global.wcpayPaymentRequestParams.nonce.store_api_nonce =
global.wcpayExpressCheckoutParams = {};
global.wcpayExpressCheckoutParams.nonce = {};
global.wcpayExpressCheckoutParams.nonce.store_api_nonce =
'global_store_api_nonce';

describe( 'PaymentRequestOrderApi', () => {
describe( 'ExpressCheckoutOrderApi', () => {
afterEach( () => {
jest.resetAllMocks();
} );

it( 'gets order data with the provided arguments', async () => {
const api = new PaymentRequestOrderApi( {
const api = new ExpressCheckoutOrderApi( {
orderId: '1',
key: 'key_123',
billingEmail: 'cheese@toast.com',
Expand All @@ -40,7 +40,7 @@ describe( 'PaymentRequestOrderApi', () => {
} );

it( 'places an order', async () => {
const api = new PaymentRequestOrderApi( {
const api = new ExpressCheckoutOrderApi( {
orderId: '1',
key: 'key_123',
billingEmail: 'cheese@toast.com',
Expand Down Expand Up @@ -74,7 +74,7 @@ describe( 'PaymentRequestOrderApi', () => {
} );

it( 'places an order with the previous API request data', async () => {
const api = new PaymentRequestOrderApi( {
const api = new ExpressCheckoutOrderApi( {
orderId: '1',
key: 'key_123',
billingEmail: 'cheese@toast.com',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ const ExpressCheckoutComponent = ( {
} );
const onClickHandler = ! isPreview ? onButtonClick : () => {};
const onShippingAddressChange = ( event ) =>
shippingAddressChangeHandler( api, event, elements );
shippingAddressChangeHandler( event, elements );

const onShippingRateChange = ( event ) =>
shippingRateChangeHandler( api, event, elements );
shippingRateChangeHandler( event, elements );

const onElementsReady = ( event ) => {
const paymentMethodContainer = document.getElementById(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { addQueryArgs } from '@wordpress/url';
/**
* Internal dependencies
*/
import { getPaymentRequestData } from './frontend-utils';
import { getExpressCheckoutData } from './utils';

export default class PaymentRequestCartApi {
export default class ExpressCheckoutCartApi {
// Used on product pages to interact with an anonymous cart.
// This anonymous cart is separate from the customer's cart, which might contain additional products.
// This functionality is also useful to calculate product/shipping pricing (and shipping needs)
Expand All @@ -30,23 +30,23 @@ export default class PaymentRequestCartApi {
...options,
parse: false,
path: addQueryArgs( options.path, {
// `wcpayPaymentRequestParams` will always be defined if this file is needed.
// If there's an issue with it, ask yourself why this file is queued and `wcpayPaymentRequestParams` isn't present.
currency: getPaymentRequestData(
// `wcpayExpressCheckoutParams` will always be defined if this file is needed.
// If there's an issue with it, ask yourself why this file is queued and `wcpayExpressCheckoutParams` isn't present.
currency: getExpressCheckoutData(
'checkout'
).currency_code.toUpperCase(),
} ),
headers: {
// the Store API nonce, which could later be overwritten in subsequent requests.
Nonce: getPaymentRequestData( 'nonce' ).store_api_nonce,
Nonce: getExpressCheckoutData( 'nonce' ).store_api_nonce,
// needed for validation of address data, etc.
'X-WooPayments-Tokenized-Cart-Nonce':
getPaymentRequestData( 'nonce' ).tokenized_cart_nonce ||
getExpressCheckoutData( 'nonce' ).tokenized_cart_nonce ||
undefined,
// necessary to validate any request made to the backend from the PDP.
'X-WooPayments-Tokenized-Cart-Session-Nonce':
getPaymentRequestData( 'button_context' ) === 'product'
? getPaymentRequestData( 'nonce' )
getExpressCheckoutData( 'button_context' ) === 'product'
? getExpressCheckoutData( 'nonce' )
.tokenized_cart_session_nonce
: undefined,
...this.cartRequestHeaders,
Expand Down Expand Up @@ -170,7 +170,7 @@ export default class PaymentRequestCartApi {
method: 'POST',
path: '/wc/store/v1/cart/add-item',
data: applyFilters(
'wcpay.payment-request.cart-add-item',
'wcpay.express-checkout.cart-add-item',
productData
),
} );
Expand Down
Loading

0 comments on commit 986f195

Please sign in to comment.