Skip to content

Commit afe6f11

Browse files
committed
fix(payments): Coupons - Handle error when currency for coupon and cart do not match
1 parent 7e18bba commit afe6f11

7 files changed

Lines changed: 86 additions & 13 deletions

File tree

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,9 @@ describe('CartService', () => {
533533
CartInvalidPromoCodeError
534534
);
535535

536+
expect(
537+
promotionCodeManager.assertValidPromotionCodeNameForPrice
538+
).toHaveBeenCalledWith(args.promoCode, mockPrice, mockResolvedCurrency);
536539
expect(cartManager.createCart).not.toHaveBeenCalled();
537540
});
538541

@@ -683,6 +686,13 @@ describe('CartService', () => {
683686
cartService.restartCart(mockOldCart.id)
684687
).rejects.toThrowError(CartInvalidPromoCodeError);
685688

689+
expect(
690+
promotionCodeManager.assertValidPromotionCodeNameForPrice
691+
).toHaveBeenCalledWith(
692+
mockOldCart.couponCode,
693+
mockPrice,
694+
mockOldCart.currency
695+
);
686696
expect(cartManager.createCart).not.toHaveBeenCalled();
687697
expect(cartManager.finishErrorCart).toHaveBeenCalled();
688698
});
@@ -998,6 +1008,13 @@ describe('CartService', () => {
9981008
cartService.updateCart(mockCart.id, mockCart.version, mockUpdateCart)
9991009
).rejects.toBeInstanceOf(CouponErrorExpired);
10001010

1011+
expect(
1012+
promotionCodeManager.assertValidPromotionCodeNameForPrice
1013+
).toHaveBeenCalledWith(
1014+
mockUpdateCart.couponCode,
1015+
mockPrice,
1016+
mockUpdateCart.currency
1017+
);
10011018
expect(cartManager.updateFreshCart).not.toHaveBeenCalledWith();
10021019
expect(cartManager.finishErrorCart).toHaveBeenCalled();
10031020
});

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ export class CartService {
262262
try {
263263
await this.promotionCodeManager.assertValidPromotionCodeNameForPrice(
264264
args.promoCode,
265-
price
265+
price,
266+
currency
266267
);
267268
} catch (e) {
268269
throw new CartInvalidPromoCodeError(args.promoCode);
@@ -318,7 +319,8 @@ export class CartService {
318319

319320
await this.promotionCodeManager.assertValidPromotionCodeNameForPrice(
320321
oldCart.couponCode,
321-
price
322+
price,
323+
oldCart.currency || DEFAULT_CURRENCY
322324
);
323325
} catch (e) {
324326
throw new CartInvalidPromoCodeError(oldCart.couponCode);
@@ -491,7 +493,8 @@ export class CartService {
491493

492494
await this.promotionCodeManager.assertValidPromotionCodeNameForPrice(
493495
cartDetails.couponCode,
494-
price
496+
price,
497+
cartDetails.currency || DEFAULT_CURRENCY
495498
);
496499
}
497500

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,11 @@ describe('CheckoutService', () => {
304304
it('fetches promotion code by name', async () => {
305305
expect(
306306
promotionCodeManager.assertValidPromotionCodeNameForPrice
307-
).toHaveBeenCalledWith(mockCart.couponCode, mockPrice);
307+
).toHaveBeenCalledWith(
308+
mockCart.couponCode,
309+
mockPrice,
310+
mockCart.currency
311+
);
308312
});
309313
});
310314

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ export class CheckoutService {
188188
try {
189189
await this.promotionCodeManager.assertValidPromotionCodeNameForPrice(
190190
cart.couponCode,
191-
price
191+
price,
192+
cart.currency
192193
);
193194

194195
promotionCode = await this.promotionCodeManager.retrieveByName(

libs/payments/customer/src/lib/promotionCode.manager.spec.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ import {
1919
StripeSubscriptionFactory,
2020
StripeSubscriptionItemFactory,
2121
MockStripeConfigProvider,
22+
StripeCouponFactory,
2223
} from '@fxa/payments/stripe';
23-
import { PromotionCodeCouldNotBeAttachedError } from './error';
24+
import {
25+
CouponErrorInvalid,
26+
PromotionCodeCouldNotBeAttachedError,
27+
} from './error';
2428
import { STRIPE_PRICE_METADATA } from './types';
2529
import { SubscriptionManager } from './subscription.manager';
2630

@@ -157,8 +161,13 @@ describe('PromotionCodeManager', () => {
157161

158162
describe('assertValidPromotionCodeNameForPrice', () => {
159163
it('resolves correctly when valid', async () => {
160-
const mockPromotionCode = StripePromotionCodeFactory();
161164
const mockPrice = StripePriceFactory();
165+
const mockPromotionCode = StripePromotionCodeFactory({
166+
coupon: StripeCouponFactory({
167+
currency: mockPrice.currency,
168+
}),
169+
});
170+
const mockCartCurrency = mockPrice.currency;
162171

163172
jest
164173
.spyOn(promotionCodeManager, 'retrieveByName')
@@ -170,14 +179,16 @@ describe('PromotionCodeManager', () => {
170179
await expect(
171180
promotionCodeManager.assertValidPromotionCodeNameForPrice(
172181
mockPromotionCode.code,
173-
mockPrice
182+
mockPrice,
183+
mockCartCurrency
174184
)
175185
).resolves.toEqual(undefined);
176186
});
177187

178188
it('throws an error if promotion code is not found', async () => {
179189
const mockPromotionCode = StripePromotionCodeFactory();
180190
const mockPrice = StripePriceFactory();
191+
const mockCartCurrency = mockPrice.currency;
181192

182193
jest
183194
.spyOn(stripeClient, 'promotionCodesList')
@@ -186,14 +197,16 @@ describe('PromotionCodeManager', () => {
186197
await expect(() =>
187198
promotionCodeManager.assertValidPromotionCodeNameForPrice(
188199
mockPromotionCode.code,
189-
mockPrice
200+
mockPrice,
201+
mockCartCurrency
190202
)
191203
).rejects.toBeInstanceOf(PromotionCodeCouldNotBeAttachedError);
192204
});
193205

194206
it('throws an error if promotion code is not valid', async () => {
195207
const mockPromotionCode = StripePromotionCodeFactory();
196208
const mockPrice = StripePriceFactory();
209+
const mockCartCurrency = mockPrice.currency;
197210

198211
jest
199212
.spyOn(promotionCodeManager, 'retrieveByName')
@@ -207,10 +220,35 @@ describe('PromotionCodeManager', () => {
207220
await expect(
208221
promotionCodeManager.assertValidPromotionCodeNameForPrice(
209222
mockPromotionCode.code,
210-
mockPrice
223+
mockPrice,
224+
mockCartCurrency
211225
)
212226
).rejects.toBeInstanceOf(PromotionCodeCouldNotBeAttachedError);
213227
});
228+
229+
it('throws an error if currencies do no match', async () => {
230+
const mockPrice = StripePriceFactory({
231+
currency: 'cad',
232+
});
233+
const mockPromotionCode = StripePromotionCodeFactory({
234+
coupon: StripeCouponFactory({
235+
currency: 'cad',
236+
}),
237+
});
238+
const mockCartCurrency = 'usd';
239+
240+
jest
241+
.spyOn(promotionCodeManager, 'retrieveByName')
242+
.mockResolvedValue(mockPromotionCode);
243+
244+
await expect(
245+
promotionCodeManager.assertValidPromotionCodeNameForPrice(
246+
mockPromotionCode.code,
247+
mockPrice,
248+
mockCartCurrency
249+
)
250+
).rejects.toBeInstanceOf(CouponErrorInvalid);
251+
});
214252
});
215253

216254
describe('applyPromoCodeToSubscription', () => {

libs/payments/customer/src/lib/promotionCode.manager.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
StripePrice,
1010
StripePromotionCode,
1111
} from '@fxa/payments/stripe';
12-
import { PromotionCodeCouldNotBeAttachedError } from './error';
12+
import {
13+
CouponErrorInvalid,
14+
PromotionCodeCouldNotBeAttachedError,
15+
} from './error';
1316
import { assertPromotionCodeApplicableToPrice } from './util/assertPromotionCodeApplicableToPrice';
1417
import { assertPromotionCodeActive } from './util/assertPromotionCodeActive';
1518
import { getPriceFromSubscription } from './util/getPriceFromSubscription';
@@ -33,12 +36,16 @@ export class PromotionCodeManager {
3336

3437
async assertValidPromotionCodeNameForPrice(
3538
promoCodeName: string,
36-
price: StripePrice
39+
price: StripePrice,
40+
cartCurrency: string
3741
) {
3842
const promoCode = await this.retrieveByName(promoCodeName);
3943
if (!promoCode)
4044
throw new PromotionCodeCouldNotBeAttachedError('PromoCode not found');
4145

46+
if (promoCode.coupon.currency !== cartCurrency)
47+
throw new CouponErrorInvalid();
48+
4249
await this.assertValidPromotionCodeForPrice(promoCode, price);
4350
}
4451

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ const Expanded = ({
241241
</Form.Message>
242242
{serverErrors.productNotAvailable && (
243243
<Form.Message>
244-
<Localized id="select-tax-location-product-not-available">
244+
<Localized
245+
id="select-tax-location-product-not-available"
246+
vars={{ productName }}
247+
>
245248
<p className="mt-1 text-alert-red" role="alert">
246249
{productName} is not available in this location.
247250
</p>

0 commit comments

Comments
 (0)