Skip to content

Commit 9bea644

Browse files
authored
Merge pull request #20388 from mozilla/PAY-3541
fix(payments-cart): validate account customer prior to Stripe customer creation
2 parents 2584a57 + 691d6a5 commit 9bea644

3 files changed

Lines changed: 53 additions & 4 deletions

File tree

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ export class InvalidIntentStateError extends CheckoutError {
4747
}
4848
}
4949

50+
export class AccountCustomerAlreadyExistsError extends CheckoutError {
51+
constructor(uid: string) {
52+
super('account customer already exists for uid', { uid });
53+
this.name = 'AccountCustomerAlreadyExistsError';
54+
}
55+
}
56+
5057
export class SubmitNeedsInputFailedError extends CheckoutError {
5158
constructor(cartId: string) {
5259
super('payment failed while submitting user needs_input', { cartId });

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {
6565
ResultAccountCustomerFactory,
6666
MockStripeConfigProvider,
6767
AccountCustomerManager,
68+
AccountCustomerNotFoundError,
6869
StripeConfirmationTokenFactory,
6970
StripeSetupIntentFactory,
7071
} from '@fxa/payments/stripe';
@@ -99,6 +100,7 @@ import {
99100
CartNoTaxAddressError,
100101
CartUidMismatchError,
101102
} from './cart.error';
103+
import { AccountCustomerAlreadyExistsError } from './checkout.error';
102104
import { CheckoutService } from './checkout.service';
103105
import { PrePayStepsResultFactory } from './checkout.factories';
104106
import { AccountManager } from '@fxa/shared/account/account';
@@ -502,6 +504,11 @@ describe('CheckoutService', () => {
502504
})
503505
);
504506

507+
jest
508+
.spyOn(accountCustomerManager, 'getAccountCustomerByUid')
509+
.mockRejectedValue(
510+
new AccountCustomerNotFoundError(uid, new Error('not found'))
511+
);
505512
jest
506513
.spyOn(promotionCodeManager, 'assertValidPromotionCodeNameForPrice')
507514
.mockRejectedValue(
@@ -528,10 +535,36 @@ describe('CheckoutService', () => {
528535
})
529536
);
530537

538+
jest
539+
.spyOn(accountCustomerManager, 'getAccountCustomerByUid')
540+
.mockRejectedValue(
541+
new AccountCustomerNotFoundError(uid, new Error('not found'))
542+
);
543+
531544
await expect(
532545
checkoutService.prePaySteps(mockCart, mockCart.uid)
533546
).rejects.toBeInstanceOf(CartTotalMismatchError);
534547
});
548+
549+
it('throws account customer already exists error', async () => {
550+
const mockCart = StripeResponseFactory(
551+
ResultCartFactory({
552+
uid: uid,
553+
couponCode: faker.string.uuid(),
554+
stripeCustomerId: null,
555+
eligibilityStatus: CartEligibilityStatus.CREATE,
556+
amount: mockInvoicePreview.subtotal,
557+
})
558+
);
559+
560+
jest
561+
.spyOn(accountCustomerManager, 'getAccountCustomerByUid')
562+
.mockResolvedValue(mockAccountCustomer);
563+
564+
await expect(
565+
checkoutService.prePaySteps(mockCart, mockCart.uid)
566+
).rejects.toBeInstanceOf(AccountCustomerAlreadyExistsError);
567+
});
535568
});
536569
});
537570

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
StripePromotionCode,
4242
type StripePaymentIntent,
4343
type StripeSetupIntent,
44+
AccountCustomerNotFoundError,
4445
} from '@fxa/payments/stripe';
4546
import { AccountManager } from '@fxa/shared/account/account';
4647
import { ProfileClient } from '@fxa/profile/client';
@@ -80,6 +81,7 @@ import {
8081
PayWithStripeNullCurrencyError,
8182
UpgradeSubscriptionNullCurrencyError,
8283
UnexpectedSubscriptionStatusForTrialError,
84+
AccountCustomerAlreadyExistsError,
8385
} from './checkout.error';
8486
import { isPaymentIntentId } from './util/isPaymentIntentId';
8587
import { isPaymentIntent } from './util/isPaymentIntent';
@@ -175,6 +177,16 @@ export class CheckoutService {
175177
let stripeCustomerId = cart.stripeCustomerId;
176178
let customer: StripeCustomer;
177179
if (!stripeCustomerId) {
180+
try {
181+
await this.accountCustomerManager.getAccountCustomerByUid(uid);
182+
183+
throw new AccountCustomerAlreadyExistsError(uid);
184+
} catch(error) {
185+
if (!(error instanceof AccountCustomerNotFoundError)) {
186+
throw error;
187+
}
188+
}
189+
178190
customer = await this.customerManager.create({
179191
uid,
180192
email,
@@ -196,15 +208,12 @@ export class CheckoutService {
196208
});
197209
}
198210

199-
if (uid && !cart.stripeCustomerId) {
211+
if (!cart.stripeCustomerId) {
200212
await this.accountCustomerManager.createAccountCustomer({
201213
uid,
202214
stripeCustomerId,
203215
});
204-
}
205216

206-
// Cart only needs to be updated if we created a customer
207-
if (!cart.uid || !cart.stripeCustomerId) {
208217
await this.cartManager.updateFreshCart(cart.id, cart.version, {
209218
uid,
210219
stripeCustomerId,

0 commit comments

Comments
 (0)