From f1711c74bd3c469e9e757a167843a4e4b22ae794 Mon Sep 17 00:00:00 2001 From: elizabeth-ilina Date: Fri, 10 Apr 2026 15:49:58 -0400 Subject: [PATCH] feat(payments-next): [PayPal] Do not restrict payments with mismatched currency + billing countries Because: * On PayPal, there are some inconsistencies noted by QA where at times (PAY-3481) invoices are unable to be processed due to a mismatch of currency + billing country, where others (PAY-3486) are successful in the same endeavor, with different countries and currencies at play. This commit: * Does not block in cases of mismatch for supported markets & currencies. Closes #[PAY-3587](https://mozilla-hub.atlassian.net/browse/PAY-3587) --- .../currency/src/lib/currency.manager.spec.ts | 68 ------------------- .../currency/src/lib/currency.manager.ts | 48 ------------- .../subscriptionManagement.service.spec.ts | 8 --- .../src/lib/subscriptionManagement.service.ts | 7 -- 4 files changed, 131 deletions(-) diff --git a/libs/payments/currency/src/lib/currency.manager.spec.ts b/libs/payments/currency/src/lib/currency.manager.spec.ts index 39a437a4967..c7fc7af52ee 100644 --- a/libs/payments/currency/src/lib/currency.manager.spec.ts +++ b/libs/payments/currency/src/lib/currency.manager.spec.ts @@ -2,19 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Test } from '@nestjs/testing'; -import { faker } from '@faker-js/faker'; import { CurrencyManager } from './currency.manager'; -import { - CurrencyCodeInvalidError, - CountryCodeInvalidError, - CurrencyCountryMismatchError, - CurrencyCodeMissingError, - CountryCodeMissingError, -} from './currency.error'; import { CurrencyConfig, - MockCurrencyConfig, MockCurrencyConfigProvider, } from './currency.config'; @@ -31,65 +22,6 @@ describe('CurrencyManager', () => { mockCurrencyConfig = module.get(CurrencyConfig); }); - describe('assertCurrencyCompatibleWithCountry', () => { - const validCountry = faker.helpers.arrayElement( - MockCurrencyConfig.currenciesToCountries.USD - ); - const validCurrency = 'USD'; - - it('asserts when currency to country is valid', () => { - currencyManager.assertCurrencyCompatibleWithCountry( - validCurrency, - validCountry - ); - }); - - it('throws an error when currency is empty', () => { - expect(() => - currencyManager.assertCurrencyCompatibleWithCountry('', validCountry) - ).toThrow(CurrencyCodeMissingError); - }); - - it('throws an error when currency is invalid', () => { - expect(() => - currencyManager.assertCurrencyCompatibleWithCountry('KPW', validCountry) - ).toThrow(CurrencyCodeInvalidError); - }); - - it('throws an error when country is missing', () => { - const countryCode = ''; - - expect(() => - currencyManager.assertCurrencyCompatibleWithCountry( - validCurrency, - countryCode - ) - ).toThrow(CountryCodeMissingError); - }); - - it('throws an error when country is invalid', () => { - const countryCode = faker.location.countryCode('alpha-3'); - - expect(() => - currencyManager.assertCurrencyCompatibleWithCountry( - validCurrency, - countryCode - ) - ).toThrow(CountryCodeInvalidError); - }); - - it('throws an error when currency to country do not match', () => { - const currencyCode = 'EUR'; - - expect(() => - currencyManager.assertCurrencyCompatibleWithCountry( - currencyCode, - validCountry - ) - ).toThrow(CurrencyCountryMismatchError); - }); - }); - describe('getTaxId', () => { it('returns the correct tax id for currency', async () => { const mockCurrency = Object.entries(mockCurrencyConfig.taxIds)[0]; diff --git a/libs/payments/currency/src/lib/currency.manager.ts b/libs/payments/currency/src/lib/currency.manager.ts index 9f0b3d1550a..27d3745c843 100644 --- a/libs/payments/currency/src/lib/currency.manager.ts +++ b/libs/payments/currency/src/lib/currency.manager.ts @@ -3,18 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Injectable } from '@nestjs/common'; -import { - SUPPORTED_PAYPAL_CURRENCIES, - VALID_COUNTRY_CODES, - VALID_CURRENCY_CODES, -} from './currency.constants'; -import { - CurrencyCodeInvalidError, - CountryCodeInvalidError, - CurrencyCountryMismatchError, - CurrencyCodeMissingError, - CountryCodeMissingError, -} from './currency.error'; import { CurrencyConfig } from './currency.config'; @Injectable() @@ -24,42 +12,6 @@ export class CurrencyManager { this.taxIds = this.config.taxIds; } - /** - * Verify that provided source country and plan currency are compatible with - * valid currencies and countries as listed in constants - * - * @param currency Currency of customer - * @param country Country of customer - * @returns True if currency is compatible with country, else throws error - */ - assertCurrencyCompatibleWithCountry(currency: string, country: string): void { - if (!currency) throw new CurrencyCodeMissingError(); - if (!country) throw new CountryCodeMissingError(); - - const currencyUpper = currency.toUpperCase(); - - if ( - !VALID_CURRENCY_CODES.includes(currencyUpper) || - !SUPPORTED_PAYPAL_CURRENCIES.includes(currencyUpper) || - !this.config.currenciesToCountries.hasOwnProperty(currencyUpper) - ) { - throw new CurrencyCodeInvalidError(currencyUpper); - } - - if (!VALID_COUNTRY_CODES.includes(country)) { - throw new CountryCodeInvalidError(country); - } - - if ( - currencyUpper in this.config.currenciesToCountries && - !this.config.currenciesToCountries[ - currencyUpper as keyof typeof this.config.currenciesToCountries - ].includes(country) - ) { - throw new CurrencyCountryMismatchError(currencyUpper, country); - } - } - getTaxId(currency: string) { return this.taxIds[currency.toUpperCase()]; } diff --git a/libs/payments/management/src/lib/subscriptionManagement.service.spec.ts b/libs/payments/management/src/lib/subscriptionManagement.service.spec.ts index ac7bc250a69..34f0884d7a0 100644 --- a/libs/payments/management/src/lib/subscriptionManagement.service.spec.ts +++ b/libs/payments/management/src/lib/subscriptionManagement.service.spec.ts @@ -51,7 +51,6 @@ import { TrialSubscriptionContentFactory, } from '@fxa/payments/management'; import { - BillingAgreementFactory, MockPaypalClientConfigProvider, PaypalBillingAgreementManager, PayPalClient, @@ -2242,7 +2241,6 @@ describe('SubscriptionManagementService', () => { collection_method: 'send_invoice', }) ); - const mockBillingAgreement = BillingAgreementFactory(); const mockStripeCustomer = StripeResponseFactory(StripeCustomerFactory()); jest @@ -2266,12 +2264,6 @@ describe('SubscriptionManagementService', () => { jest .spyOn(paypalBillingAgreementManager, 'create') .mockResolvedValue(faker.string.uuid()); - jest - .spyOn(paypalBillingAgreementManager, 'retrieve') - .mockResolvedValue(mockBillingAgreement); - jest - .spyOn(currencyManager, 'assertCurrencyCompatibleWithCountry') - .mockReturnValue(); jest .spyOn(customerManager, 'update') .mockResolvedValue(mockStripeCustomer); diff --git a/libs/payments/management/src/lib/subscriptionManagement.service.ts b/libs/payments/management/src/lib/subscriptionManagement.service.ts index 34a0a0652bf..e732f27185a 100644 --- a/libs/payments/management/src/lib/subscriptionManagement.service.ts +++ b/libs/payments/management/src/lib/subscriptionManagement.service.ts @@ -1228,13 +1228,6 @@ export class SubscriptionManagementService { token ); - const billingAgreement = - await this.paypalBillingAgreementManager.retrieve(billingAgreementId); - this.currencyManager.assertCurrencyCompatibleWithCountry( - currency, - billingAgreement.countryCode - ); - await this.customerManager.update(accountCustomer.stripeCustomerId, { metadata: { [STRIPE_CUSTOMER_METADATA.PaypalAgreement]: billingAgreementId,