Skip to content

Commit 8da0531

Browse files
Merge pull request #20422 from mozilla/PAY-3668
feat(payment-next): Pass experimentationPreview param through to Nimbus
2 parents ecab001 + d7c6bd0 commit 8da0531

11 files changed

Lines changed: 93 additions & 12 deletions

File tree

apps/payments/next/app/[locale]/error.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,19 @@ export default function Error({
3232
const [loading, setLoading] = useState(false);
3333
const { locale, offeringId, interval, cartId } = useParams<ErrorParams>();
3434
const hasProductData = locale && offeringId && interval;
35-
const queryParams = useSearchParams().toString();
35+
const searchParams = useSearchParams();
36+
const queryParams = searchParams.toString();
3637

3738
const SUPPORT_URL = process.env.SUPPORT_URL ?? 'https://support.mozilla.org';
3839

3940
// Reset the view if the cart is in a success state, or restart the cart and redirect
4041
async function redirectWithCart() {
4142
setLoading(true);
4243
try {
43-
const cart = await getCartAction(cartId);
44+
const cart = await getCartAction(
45+
cartId,
46+
Object.fromEntries(searchParams.entries())
47+
);
4448
if (cart.state === 'start') {
4549
const newCart = await restartCartAction(cartId);
4650
setLoading(false);

libs/payments/cart/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44
export * from './lib/cart.types';
5+
export * from './lib/searchParams.schema';
56
export * from './lib/cart.factories';
67
export * from './lib/cart.manager';
78
export * from './lib/cart.service';

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2491,6 +2491,7 @@ describe('CartService', () => {
24912491
countryCode: mockCart.taxAddress?.countryCode,
24922492
interval: mockCart.interval,
24932493
eligibilityStatus: EligibilityStatus.CREATE,
2494+
searchParams: undefined,
24942495
});
24952496
});
24962497

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import {
107107
} from './cart.utils';
108108
import { SubmitNeedsInputFailedError } from './checkout.error';
109109
import { CheckoutService } from './checkout.service';
110+
import type { PaymentsSearchParams } from './searchParams.schema';
110111
import { resolveErrorInstance } from './util/resolveErrorInstance';
111112
import { isPaymentIntentId } from './util/isPaymentIntentId';
112113
import { isPaymentIntent } from './util/isPaymentIntent';
@@ -881,7 +882,10 @@ export class CartService {
881882
* Fetch a cart from the database by ID
882883
*/
883884
@SanitizeExceptions()
884-
async getCart(cartId: string): Promise<CartDTO> {
885+
async getCart(
886+
cartId: string,
887+
searchParams?: PaymentsSearchParams
888+
): Promise<CartDTO> {
885889
const cart = (await this.cartManager.fetchCartById(
886890
cartId
887891
)) as ResultCart & { taxAddress: TaxAddress; currency: string };
@@ -938,6 +942,7 @@ export class CartService {
938942
countryCode: cart.taxAddress.countryCode || '',
939943
interval: cart.interval as SubplatInterval,
940944
eligibilityStatus: eligibility.subscriptionEligibilityResult,
945+
searchParams,
941946
}
942947
);
943948
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import {
9898
} from '@fxa/payments/metrics';
9999
import { isCancelInterstitialOffer } from './util/isCancelInterstitialOffer';
100100
import { FreeTrialManager } from './free-trial.manager';
101+
import type { PaymentsSearchParams } from './searchParams.schema';
101102

102103
const FREE_TRIAL_PREPAID_CARD_BLOCKED_HOURS = 24;
103104

@@ -999,9 +1000,16 @@ export class CheckoutService {
9991000
countryCode: string;
10001001
interval: SubplatInterval;
10011002
eligibilityStatus: EligibilityStatus;
1003+
searchParams?: PaymentsSearchParams;
10021004
}): Promise<FreeTrial | null> {
1003-
const { uid, offeringConfigId, countryCode, interval, eligibilityStatus } =
1004-
args;
1005+
const {
1006+
uid,
1007+
offeringConfigId,
1008+
countryCode,
1009+
interval,
1010+
eligibilityStatus,
1011+
searchParams,
1012+
} = args;
10051013

10061014
if (eligibilityStatus !== EligibilityStatus.CREATE) {
10071015
return null;
@@ -1010,7 +1018,7 @@ export class CheckoutService {
10101018
const fetchResult = await Promise.all([
10111019
this.nimbusManager.fetchExperiments({
10121020
nimbusUserId: this.nimbusManager.generateNimbusId(uid),
1013-
preview: false,
1021+
preview: searchParams?.experimentationPreview === 'true',
10141022
}),
10151023
this.productConfigurationManager.getFreeTrial(offeringConfigId),
10161024
]).catch((error) => {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
import { z } from 'zod';
6+
7+
// Known query params that payments code reads off of the incoming URL.
8+
// `.passthrough()` keeps unknown keys on the parsed object — searchParams
9+
// are user-controlled and we never want to drop or reject unexpected values.
10+
export const paymentsSearchParamsSchema = z
11+
.object({
12+
experimentationPreview: z.string().optional(),
13+
})
14+
.passthrough();
15+
16+
export type PaymentsSearchParams = z.infer<typeof paymentsSearchParamsSchema>;

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
'use server';
66

77
import { getApp } from '../nestapp/app';
8+
import { parseSearchParams } from '../utils/searchParams';
89

9-
export const getCartAction = async (cartId: string) => {
10+
export const getCartAction = async (
11+
cartId: string,
12+
searchParams?: Record<string, string | string[] | undefined>
13+
) => {
1014
const cart = await getApp().getActionsService().getCart({
1115
cartId,
16+
searchParams: parseSearchParams(searchParams),
1217
});
1318

1419
return cart;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getRedirect, validateCartState } from '../utils/get-cart';
1010
import { SupportedPages } from '../utils/types';
1111
import { URLSearchParams } from 'url';
1212
import { sanitizePathname } from '../utils/sanitizePathname';
13+
import { parseSearchParams } from '../utils/searchParams';
1314
import {
1415
StartCartDTO,
1516
ProcessingCartDTO,
@@ -68,6 +69,7 @@ async function getCartOrRedirectAction(
6869
const params = searchParams ? `?${urlSearchParams.toString()}` : '';
6970
const cart = await getApp().getActionsService().getCart({
7071
cartId,
72+
searchParams: parseSearchParams(searchParams),
7173
});
7274

7375
if (!validateCartState(cart.state, page)) {

libs/payments/ui/src/lib/nestapp/nextjs-actions.service.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GoogleManager } from '@fxa/google';
88
import {
99
CartInvalidStateForActionError,
1010
CartService,
11+
type PaymentsSearchParams,
1112
SubscriptionAttributionParams,
1213
SuccessCartDTO,
1314
TaxChangeAllowedStatus,
@@ -207,8 +208,11 @@ export class NextJSActionsService {
207208
@NextIOValidator(GetCartActionArgs, GetCartActionResult)
208209
@WithTypeCachableAsyncLocalStorage()
209210
@CaptureTimingWithStatsD()
210-
async getCart(args: { cartId: string }) {
211-
return this.cartService.getCart(args.cartId);
211+
async getCart(args: {
212+
cartId: string;
213+
searchParams?: PaymentsSearchParams;
214+
}) {
215+
return this.cartService.getCart(args.cartId, args.searchParams);
212216
}
213217

214218
@SanitizeExceptions()
@@ -249,8 +253,11 @@ export class NextJSActionsService {
249253
@NextIOValidator(GetCartActionArgs, GetSuccessCartActionResult)
250254
@WithTypeCachableAsyncLocalStorage()
251255
@CaptureTimingWithStatsD()
252-
async getSuccessCart(args: { cartId: string }): Promise<SuccessCartDTO> {
253-
const cart = await this.cartService.getCart(args.cartId);
256+
async getSuccessCart(args: {
257+
cartId: string;
258+
searchParams?: PaymentsSearchParams;
259+
}): Promise<SuccessCartDTO> {
260+
const cart = await this.cartService.getCart(args.cartId, args.searchParams);
254261

255262
if (cart.state !== CartState.SUCCESS) {
256263
throw new Error('Cart is not in success state');

libs/payments/ui/src/lib/nestapp/validators/GetCartActionArgs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
import { IsString } from 'class-validator';
5+
import type { PaymentsSearchParams } from '@fxa/payments/cart';
6+
import { IsObject, IsOptional, IsString } from 'class-validator';
67

78
export class GetCartActionArgs {
89
@IsString()
910
cartId!: string;
11+
12+
@IsObject()
13+
@IsOptional()
14+
searchParams?: PaymentsSearchParams;
1015
}

0 commit comments

Comments
 (0)