Skip to content

Commit 05310fd

Browse files
committed
feat(carts): Remove cart email column
Because: * The cart email is a diplication of the data from the cart's mozilla account (if it has one) This commit: * Removes the email field from the carts table * Updates the type references * Provides the necessary patches Closes #FXA-11314
1 parent 995e99e commit 05310fd

File tree

26 files changed

+78
-60
lines changed

26 files changed

+78
-60
lines changed

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import type { Metadata } from 'next';
66
import { headers } from 'next/headers';
77
import Image from 'next/image';
8-
8+
import { auth } from 'apps/payments/next/auth';
99
import { getCardIcon } from '@fxa/payments/ui';
1010
import {
1111
fetchCMSData,
@@ -47,8 +47,13 @@ export default async function CheckoutSuccess({
4747
SupportedPages.SUCCESS,
4848
searchParams
4949
);
50+
const sessionPromise = auth();
5051
const l10n = getApp().getL10n(locale);
51-
const [cms, cart] = await Promise.all([cmsDataPromise, cartDataPromise]);
52+
const [cms, cart, session] = await Promise.all([
53+
cmsDataPromise,
54+
cartDataPromise,
55+
sessionPromise,
56+
]);
5257

5358
recordEmitterEventAction(
5459
'checkoutSuccess',
@@ -76,9 +81,9 @@ export default async function CheckoutSuccess({
7681
{l10n.getString(
7782
'payment-confirmation-thanks-subheading-account-exists-2',
7883
{
79-
email: cart.email || '',
84+
email: session?.user?.email || '',
8085
},
81-
`You’ll receive an email at ${cart.email} with instructions about your subscription, as well as your payment details.`
86+
`You’ll receive an email at ${session?.user?.email} with instructions about your subscription, as well as your payment details.`
8287
)}
8388
</p>
8489
</div>

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

55
import { headers } from 'next/headers';
6-
6+
import { auth } from 'apps/payments/next/auth';
77
import { getCardIcon } from '@fxa/payments/ui';
88
import { PriceInterval, SupportedPages, getApp } from '@fxa/payments/ui/server';
99
import {
@@ -42,8 +42,13 @@ export default async function UpgradeSuccess({
4242
SupportedPages.SUCCESS,
4343
searchParams
4444
);
45+
const sessionPromise = auth();
4546
const l10n = getApp().getL10n(locale);
46-
const [cms, cart] = await Promise.all([cmsDataPromise, cartDataPromise]);
47+
const [cms, cart, session] = await Promise.all([
48+
cmsDataPromise,
49+
cartDataPromise,
50+
sessionPromise,
51+
]);
4752

4853
recordEmitterEventAction(
4954
'checkoutSuccess',
@@ -71,9 +76,9 @@ export default async function UpgradeSuccess({
7176
{l10n.getString(
7277
'payment-confirmation-thanks-subheading-account-exists-2',
7378
{
74-
email: cart.email || '',
79+
email: session?.user?.email || '',
7580
},
76-
`You’ll receive an email at ${cart.email} with instructions about your subscription, as well as your payment details.`
81+
`You’ll receive an email at${session?.user?.email} with instructions about your subscription, as well as your payment details.`
7782
)}
7883
</p>
7984
</div>

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ export class CartEligibilityMismatchError extends CartError {
109109
}
110110
}
111111

112-
export class CartEmailNotFoundError extends CartError {
112+
export class CartAccountNotFoundError extends CartError {
113113
constructor(cartId: string) {
114-
super('Cart email not found', {
114+
super('Cart account not found for uid', {
115115
cartId,
116116
});
117117
}

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

-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export const CheckoutCustomerDataFactory = (
4040
});
4141

4242
export const SetupCartFactory = (override?: Partial<SetupCart>): SetupCart => ({
43-
4443
offeringConfigId: faker.helpers.arrayElement(OFFERING_CONFIG_IDS),
4544
interval: faker.helpers.arrayElement(INTERVALS),
4645
amount: faker.number.int(10000),
@@ -103,7 +102,6 @@ export const ResultCartFactory = (
103102
couponCode: null,
104103
stripeCustomerId: faker.string.uuid(),
105104
stripeSubscriptionId: faker.string.uuid(),
106-
email: faker.internet.email(),
107105
amount: faker.number.int(),
108106
version: faker.number.int(),
109107
eligibilityStatus: faker.helpers.enumValue(CartEligibilityStatus),

libs/payments/cart/src/lib/cart.manager.in.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ describe('CartManager', () => {
152152
it('succeeds', async () => {
153153
const updateItems = UpdateCartFactory({
154154
couponCode: 'testcoupon',
155-
156155
});
157156

158157
await cartManager.updateFreshCart(

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

-1
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,6 @@ describe('CartService', () => {
671671
taxAddress: mockOldCart.taxAddress,
672672
currency: mockOldCart.currency,
673673
stripeCustomerId: mockAccountCustomer.stripeCustomerId,
674-
email: mockOldCart.email,
675674
amount: mockOldCart.amount,
676675
eligibilityStatus: mockOldCart.eligibilityStatus,
677676
});

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

-8
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,6 @@ export class CartService {
222222
args.interval
223223
);
224224

225-
const fxaAccounts = args.uid
226-
? await this.accountManager.getAccounts([args.uid])
227-
: undefined;
228-
const fxaAccount =
229-
fxaAccounts && fxaAccounts.length > 0 ? fxaAccounts[0] : undefined;
230-
231225
let currency: string | undefined = undefined;
232226
if (taxAddress?.countryCode) {
233227
currency = this.currencyManager.getCurrencyForCountry(
@@ -275,7 +269,6 @@ export class CartService {
275269
offeringConfigId: args.offeringConfigId,
276270
amount: upcomingInvoice.subtotal,
277271
uid: args.uid,
278-
email: fxaAccount?.email,
279272
stripeCustomerId: accountCustomer?.stripeCustomerId || undefined,
280273
experiment: args.experiment,
281274
taxAddress,
@@ -346,7 +339,6 @@ export class CartService {
346339
currency: oldCart.currency || undefined,
347340
couponCode: oldCart.couponCode || undefined,
348341
stripeCustomerId: accountCustomer?.stripeCustomerId || undefined,
349-
email: oldCart.email || undefined,
350342
amount: oldCart.amount,
351343
eligibilityStatus: oldCart.eligibilityStatus,
352344
});

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

-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ export type SetupCart = {
114114
currency?: string;
115115
couponCode?: string;
116116
stripeCustomerId?: string;
117-
email?: string;
118117
amount: number;
119118
eligibilityStatus: CartEligibilityStatus;
120119
};
@@ -130,7 +129,6 @@ export type UpdateCart = {
130129
taxAddress?: TaxAddress;
131130
currency?: string;
132131
couponCode?: string;
133-
email?: string;
134132
stripeCustomerId?: string;
135133
stripeSubscriptionId?: string;
136134
};

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

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export const PrePayStepsResultFactory = (
1515
return {
1616
version: cart.version,
1717
uid: faker.string.uuid(),
18-
email: cart.email,
1918
customer: StripeCustomerFactory(),
2019
enableAutomaticTax: true,
2120
price: StripePriceFactory(),

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

+13-8
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import {
7070
} from '@fxa/shared/cms';
7171
import { MockFirestoreProvider } from '@fxa/shared/db/firestore';
7272
import {
73+
AccountFactory,
7374
CartEligibilityStatus,
7475
MockAccountDatabaseNestFactory,
7576
} from '@fxa/shared/db/mysql/account';
@@ -83,7 +84,7 @@ import {
8384
import {
8485
CartEligibilityMismatchError,
8586
CartTotalMismatchError,
86-
CartEmailNotFoundError,
87+
CartAccountNotFoundError,
8788
CartInvalidPromoCodeError,
8889
CartInvalidCurrencyError,
8990
CartUidNotFoundError,
@@ -101,6 +102,7 @@ import { MockCurrencyConfigProvider } from 'libs/payments/currency/src/lib/curre
101102

102103
describe('CheckoutService', () => {
103104
let accountCustomerManager: AccountCustomerManager;
105+
let accountManager: AccountManager;
104106
let cartManager: CartManager;
105107
let checkoutService: CheckoutService;
106108
let customerManager: CustomerManager;
@@ -171,6 +173,7 @@ describe('CheckoutService', () => {
171173
}).compile();
172174

173175
accountCustomerManager = moduleRef.get(AccountCustomerManager);
176+
accountManager = moduleRef.get(AccountManager);
174177
cartManager = moduleRef.get(CartManager);
175178
checkoutService = moduleRef.get(CheckoutService);
176179
customerManager = moduleRef.get(CustomerManager);
@@ -229,6 +232,9 @@ describe('CheckoutService', () => {
229232
stripeCustomerId: mockCart.stripeCustomerId,
230233
})
231234
);
235+
const mockAccount = StripeResponseFactory(
236+
AccountFactory({ uid: Buffer.from(uid, 'hex') })
237+
);
232238

233239
const mockPrice = StripePriceFactory();
234240

@@ -261,6 +267,9 @@ describe('CheckoutService', () => {
261267
jest
262268
.spyOn(promotionCodeManager, 'retrieveByName')
263269
.mockResolvedValue(mockPromotionCode);
270+
jest
271+
.spyOn(accountManager, 'getAccounts')
272+
.mockResolvedValue([mockAccount]);
264273
});
265274

266275
describe('success - with stripeCustomerId attached to cart', () => {
@@ -316,16 +325,12 @@ describe('CheckoutService', () => {
316325
});
317326

318327
describe('fail', () => {
319-
it('throws cart email not found error', async () => {
320-
const mockCart = StripeResponseFactory(
321-
ResultCartFactory({
322-
email: null,
323-
})
324-
);
328+
it('throws cart account not found error', async () => {
329+
jest.spyOn(accountManager, 'getAccounts').mockResolvedValue([]);
325330

326331
await expect(
327332
checkoutService.prePaySteps(mockCart, mockCustomerData)
328-
).rejects.toBeInstanceOf(CartEmailNotFoundError);
333+
).rejects.toBeInstanceOf(CartAccountNotFoundError);
329334
});
330335

331336
it('throws cart uid not found error', async () => {

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

+10-7
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ import {
3131
StripeCustomer,
3232
StripePromotionCode,
3333
} from '@fxa/payments/stripe';
34+
import { AccountManager } from '@fxa/shared/account/account';
3435
import { ProfileClient } from '@fxa/profile/client';
3536
import { ProductConfigurationManager } from '@fxa/shared/cms';
3637
import { StatsDService } from '@fxa/shared/metrics/statsd';
3738
import { NotifierService } from '@fxa/shared/notifier';
3839
import {
3940
CartTotalMismatchError,
4041
CartEligibilityMismatchError,
41-
CartEmailNotFoundError,
42+
CartAccountNotFoundError,
4243
CartInvalidPromoCodeError,
4344
CartInvalidCurrencyError,
4445
CartUidNotFoundError,
@@ -55,6 +56,7 @@ import { CheckoutPaymentError, CheckoutUpgradeError } from './checkout.error';
5556
export class CheckoutService {
5657
constructor(
5758
private accountCustomerManager: AccountCustomerManager,
59+
private accountManager: AccountManager,
5860
private cartManager: CartManager,
5961
private customerManager: CustomerManager,
6062
private eligibilityService: EligibilityService,
@@ -92,10 +94,6 @@ export class CheckoutService {
9294
const taxAddress = cart.taxAddress as any as TaxAddress;
9395
let version = cart.version;
9496

95-
if (!cart.email) {
96-
throw new CartEmailNotFoundError(cart.id);
97-
}
98-
9997
if (!cart.currency) {
10098
throw new CartInvalidCurrencyError(
10199
cart.currency || undefined,
@@ -108,12 +106,18 @@ export class CheckoutService {
108106
throw new CartUidNotFoundError(cart.id);
109107
}
110108

109+
const fxaAccounts = await this.accountManager.getAccounts([uid]);
110+
if (!(fxaAccounts && fxaAccounts.length > 0)) {
111+
throw new CartAccountNotFoundError(cart.id);
112+
}
113+
const email = fxaAccounts[0].email;
114+
111115
let stripeCustomerId = cart.stripeCustomerId;
112116
let customer: StripeCustomer;
113117
if (!stripeCustomerId) {
114118
customer = await this.customerManager.create({
115119
uid,
116-
email: cart.email,
120+
email,
117121
displayName: customerData.displayName,
118122
taxAddress,
119123
});
@@ -208,7 +212,6 @@ export class CheckoutService {
208212
return {
209213
uid: uid,
210214
customer,
211-
email: cart.email,
212215
enableAutomaticTax,
213216
promotionCode,
214217
version,

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
import { ResultCart } from './cart.types';
77
import { SubscriptionEligibilityResult } from '@fxa/payments/eligibility';
88

9-
export type PrePayStepsResult = Pick<ResultCart, 'version' | 'email'> & {
9+
export type PrePayStepsResult = Pick<ResultCart, 'version'> & {
1010
uid: string;
1111
customer: StripeCustomer;
1212
enableAutomaticTax: boolean;

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

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ interface CheckoutFormProps {
4343
cart: {
4444
id: string;
4545
version: number;
46-
email: string | null;
4746
uid?: string | null;
4847
errorReasonId: string | null;
4948
couponCode: string | null;

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

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ interface PaymentFormProps {
2020
cart: {
2121
id: string;
2222
version: number;
23-
email: string | null;
2423
uid?: string | null;
2524
errorReasonId: string | null;
2625
couponCode: string | null;

libs/shared/db/mysql/account/src/lib/kysely-types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ export interface Carts {
105105
couponCode: string | null;
106106
stripeCustomerId: string | null;
107107
stripeSubscriptionId: string | null;
108-
email: string | null;
109108
amount: number;
110109
version: number;
111110
eligibilityStatus: CartEligibilityStatus;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Remove `email` column from the `carts` table.
2+
-- `email` field can be populated via the `uid` column in the `accounts` table.
3+
-- Throw an error if any carts have an email value but no uid value. Only update the patch level if successful
4+
5+
SELECT COUNT(*) INTO @invalid_records FROM carts WHERE email IS NOT NULL AND uid IS NULL;
6+
7+
SET @sql = CASE
8+
WHEN @invalid_records > 0 THEN
9+
'SELECT CONCAT(''Migration aborted: '', @invalid_records, '' carts have email values but no uid values'') INTO @error_message; SIGNAL SQLSTATE ''45000'' SET MESSAGE_TEXT = @error_message;'
10+
ELSE
11+
'ALTER TABLE carts DROP COLUMN email;'
12+
END;
13+
14+
PREPARE stmt FROM @sql;
15+
EXECUTE stmt;
16+
DEALLOCATE PREPARE stmt;
17+
18+
UPDATE dbMetadata SET value = '164' WHERE name = 'schema-patch-level';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Rollback migration to restore the `email` column to the `carts` table.
2+
-- Repopulates the `email` column with the email values from the `accounts` table.
3+
4+
ALTER TABLE carts
5+
ADD COLUMN email VARCHAR(255) AFTER stripeSubscriptionId;
6+
7+
UPDATE carts c
8+
INNER JOIN accounts a ON c.uid = a.uid
9+
SET c.email = a.email
10+
WHERE c.uid IS NOT NULL;
11+
12+
UPDATE dbMetadata SET value = '163' WHERE name = 'schema-patch-level';
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"level": 163
2+
"level": 164
33
}

packages/fxa-admin-panel/src/components/PageAccountSearch/Cart/index.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export const Cart = ({ cart }: CartProps) => {
6868
header="Eligibility Status"
6969
children={cart.eligibilityStatus}
7070
/>
71-
<TableRowYHeader header="Email" children={cart.email ?? ''} />
7271
<TableRowYHeader
7372
header="Tax Address: Country Code"
7473
children={cart.taxAddress?.countryCode ?? ''}

packages/fxa-admin-panel/src/components/PageAccountSearch/index.gql.ts

-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ export const GET_ACCOUNT_BY_EMAIL = gql`
109109
}
110110
stripeCustomerId
111111
stripeSubscriptionId
112-
email
113112
amount
114113
version
115114
eligibilityStatus
@@ -224,7 +223,6 @@ export const GET_ACCOUNT_BY_UID = gql`
224223
}
225224
stripeCustomerId
226225
stripeSubscriptionId
227-
email
228226
amount
229227
version
230228
eligibilityStatus

0 commit comments

Comments
 (0)