Skip to content

Commit b1c790f

Browse files
committed
feat(payments-next): reduce getCart & Stripe calls
Because: - We're exceeding Stripe rate limits with payments-next This commit: - Greatly reduces the number of getCart calls during checkout Closes FXA-11715
1 parent 265926d commit b1c790f

13 files changed

Lines changed: 128 additions & 38 deletions

File tree

apps/payments/next/app/[locale]/[offeringId]/[interval]/checkout/[cartId]/processing/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from '@fxa/payments/ui';
1010
import { getApp, SupportedPages } from '@fxa/payments/ui/server';
1111
import { headers } from 'next/headers';
12-
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
12+
import { validateCartStateAndRedirectAction } from '@fxa/payments/ui/actions';
1313
import type { Metadata } from 'next';
1414

1515
export const metadata: Metadata = {
@@ -27,7 +27,7 @@ export default async function ProcessingPage({
2727
const { locale } = params;
2828
const acceptLanguage = headers().get('accept-language');
2929
const l10n = getApp().getL10n(acceptLanguage, locale);
30-
const cart = await getCartOrRedirectAction(
30+
await validateCartStateAndRedirectAction(
3131
params.cartId,
3232
SupportedPages.PROCESSING,
3333
searchParams
@@ -38,7 +38,7 @@ export default async function ProcessingPage({
3838
data-testid="payment-processing"
3939
>
4040
<LoadingSpinner className="w-10 h-10" />
41-
<PaymentStateObserver cartId={cart.id} />
41+
<PaymentStateObserver cartId={params.cartId} />
4242
{l10n.getString(
4343
'next-payment-processing-message',
4444
`Please wait while we process your payment…`

apps/payments/next/app/[locale]/[offeringId]/[interval]/upgrade/[cartId]/(mainLayout)/processing/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from '@fxa/payments/ui';
1010
import { getApp, SupportedPages } from '@fxa/payments/ui/server';
1111
import { headers } from 'next/headers';
12-
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
12+
import { validateCartStateAndRedirectAction } from '@fxa/payments/ui/actions';
1313
import { Metadata } from 'next';
1414

1515
export const metadata: Metadata = {
@@ -27,7 +27,7 @@ export default async function ProcessingPage({
2727
const { locale } = params;
2828
const acceptLanguage = headers().get('accept-language');
2929
const l10n = getApp().getL10n(acceptLanguage, locale);
30-
const cart = await getCartOrRedirectAction(
30+
await validateCartStateAndRedirectAction(
3131
params.cartId,
3232
SupportedPages.PROCESSING,
3333
searchParams
@@ -38,7 +38,7 @@ export default async function ProcessingPage({
3838
data-testid="payment-processing"
3939
>
4040
<LoadingSpinner className="w-10 h-10" />
41-
<PaymentStateObserver cartId={cart.id} />
41+
<PaymentStateObserver cartId={params.cartId} />
4242
{l10n.getString(
4343
'next-payment-processing-message',
4444
`Please wait while we process your payment…`

libs/payments/cart/src/lib/cart.service.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,25 @@ describe('CartService', () => {
12681268
});
12691269
});
12701270

1271+
describe('getCartState', () => {
1272+
it('returns cart state', async () => {
1273+
const mockCart = ResultCartFactory({
1274+
state: CartState.START,
1275+
stripeSubscriptionId: null,
1276+
eligibilityStatus: CartEligibilityStatus.CREATE,
1277+
});
1278+
1279+
jest.spyOn(cartManager, 'fetchCartById').mockResolvedValue(mockCart);
1280+
1281+
const result = await cartService.getCartState(mockCart.id);
1282+
expect(result).toEqual({
1283+
state: mockCart.state
1284+
});
1285+
1286+
expect(cartManager.fetchCartById).toHaveBeenCalledWith(mockCart.id);
1287+
});
1288+
});
1289+
12711290
describe('getCart', () => {
12721291
const mockCustomerSession = StripeResponseFactory(
12731292
StripeCustomerSessionFactory()

libs/payments/cart/src/lib/cart.service.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,18 @@ export class CartService {
632632
);
633633
}
634634

635+
/**
636+
* Fetch a cart from the database by ID
637+
*/
638+
@SanitizeExceptions()
639+
async getCartState(cartId: string): Promise<{ state: CartState }> {
640+
const cart = await this.cartManager.fetchCartById(cartId);
641+
642+
return {
643+
state: cart.state,
644+
};
645+
}
646+
635647
/**
636648
* Fetch a cart from the database by ID
637649
*/

libs/payments/ui/src/lib/actions/getCartOrRedirect.ts

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ import {
1515
FailCartDTO,
1616
SuccessCartDTO,
1717
CartDTO,
18-
CartInvalidStateForActionError,
1918
} from '@fxa/payments/cart';
20-
import { VError } from 'verror';
2119

2220
/**
2321
* Get Cart or Redirect if cart state does not match supported page
@@ -55,34 +53,11 @@ async function getCartOrRedirectAction(
5553
page: SupportedPages,
5654
searchParams?: Record<string, string>
5755
): Promise<CartDTO> {
58-
let cart: CartDTO | undefined;
5956
const urlSearchParams = new URLSearchParams(searchParams);
6057
const params = searchParams ? `?${urlSearchParams.toString()}` : '';
61-
switch (page) {
62-
case SupportedPages.SUCCESS: {
63-
try {
64-
cart = await getApp().getActionsService().getSuccessCart({
65-
cartId,
66-
});
67-
} catch (error) {
68-
if (error instanceof CartInvalidStateForActionError) {
69-
redirect(getRedirect(VError.info(error).state) + params);
70-
} else {
71-
throw error;
72-
}
73-
}
74-
break;
75-
}
76-
case SupportedPages.START:
77-
case SupportedPages.PROCESSING:
78-
case SupportedPages.NEEDS_INPUT:
79-
case SupportedPages.ERROR: {
80-
cart = await getApp().getActionsService().getCart({
81-
cartId,
82-
});
83-
break;
84-
}
85-
}
58+
const cart = await getApp().getActionsService().getCart({
59+
cartId,
60+
});
8661

8762
if (!validateCartState(cart.state, page)) {
8863
redirect(getRedirect(cart.state) + params);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
'use server';
6+
7+
import { getApp } from '../nestapp/app';
8+
9+
export const getCartStateAction = async (cartId: string) => {
10+
const cart = await getApp().getActionsService().getCartState({
11+
cartId,
12+
});
13+
14+
return cart;
15+
};

libs/payments/ui/src/lib/actions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ export { finalizeProcessingCartAction } from './finalizeProcessingCart';
2222
export { getNeedsInputAction } from './getNeedsInput';
2323
export { submitNeedsInputAndRedirectAction } from './submitNeedsInputAndRedirect';
2424
export { validateAndFormatPostalCode } from './validateAndFormatPostalCode';
25+
export { validateCartStateAndRedirectAction } from './validateCartStateAndRedirect';
2526
export { validateLocationAction } from './validateLocation';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
'use server';
6+
7+
import { redirect } from 'next/navigation';
8+
import { getApp } from '../nestapp/app';
9+
import { getRedirect, validateCartState } from '../utils/get-cart';
10+
import { SupportedPages } from '../utils/types';
11+
12+
/**
13+
* Redirect if cart state does not match supported page
14+
* @@param cartId - Cart ID
15+
* @@param page - Page that action is being called from
16+
*/
17+
async function validateCartStateAndRedirectAction(
18+
cartId: string,
19+
page: SupportedPages,
20+
searchParams?: Record<string, string>
21+
): Promise<void> {
22+
const urlSearchParams = new URLSearchParams(searchParams);
23+
const params = searchParams ? `?${urlSearchParams.toString()}` : '';
24+
const { state } = await getApp().getActionsService().getCartState({
25+
cartId,
26+
});
27+
28+
if (!validateCartState(state, page)) {
29+
redirect(getRedirect(state) + params);
30+
}
31+
}
32+
33+
export { validateCartStateAndRedirectAction };

libs/payments/ui/src/lib/client/components/PaymentInputHandler/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import {
88
getNeedsInputAction,
99
submitNeedsInputAndRedirectAction,
10-
getCartOrRedirectAction,
10+
validateCartStateAndRedirectAction,
1111
} from '@fxa/payments/ui/actions';
1212
import { useEffect } from 'react';
1313
import { useStripe } from '@stripe/react-stripe-js';
@@ -34,7 +34,7 @@ export function PaymentInputHandler({ cartId }: { cartId: string }) {
3434
await submitNeedsInputAndRedirectAction(cartId);
3535
break;
3636
case 'notRequired':
37-
await getCartOrRedirectAction(
37+
await validateCartStateAndRedirectAction(
3838
cartId,
3939
SupportedPages.NEEDS_INPUT,
4040
searchParamsRecord

libs/payments/ui/src/lib/client/components/PaymentStateObserver/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
'use client';
66

77
import { SupportedPages } from '@fxa/payments/ui';
8-
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
98
import { useSearchParams } from 'next/navigation';
109
import { useEffect } from 'react';
10+
import { validateCartStateAndRedirectAction } from '../../../actions/validateCartStateAndRedirect';
1111

1212
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
1313

@@ -24,7 +24,7 @@ export function PaymentStateObserver({ cartId }: { cartId: string }) {
2424
const poll = async () => {
2525
if (!isPolling) return;
2626

27-
await getCartOrRedirectAction(
27+
await validateCartStateAndRedirectAction(
2828
cartId,
2929
SupportedPages.PROCESSING,
3030
searchParamsRecord

0 commit comments

Comments
 (0)