diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts index 0ad70da8e84..49f3e10c582 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts @@ -4,15 +4,13 @@ import { mock, mockReset } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; -import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; import { LogService } from "@bitwarden/logging"; -import { AccountBillingClient } from "../../../../clients"; -import { TokenizedPaymentMethod } from "../../../../payment/types"; +import { AccountBillingClient, TaxAmounts, TaxClient } from "../../../../clients"; +import { BillingAddress, TokenizedPaymentMethod } from "../../../../payment/types"; import { PersonalSubscriptionPricingTierIds } from "../../../../types/subscription-pricing-tier"; import { UpgradePaymentService, PlanDetails } from "./upgrade-payment.service"; @@ -20,7 +18,7 @@ import { UpgradePaymentService, PlanDetails } from "./upgrade-payment.service"; describe("UpgradePaymentService", () => { const mockOrganizationBillingService = mock(); const mockAccountBillingClient = mock(); - const mockTaxService = mock(); + const mockTaxClient = mock(); const mockLogService = mock(); const mockApiService = mock(); const mockSyncService = mock(); @@ -42,9 +40,14 @@ describe("UpgradePaymentService", () => { type: "card", }; - const mockBillingAddress = { + const mockBillingAddress: BillingAddress = { + line1: "123 Test St", + line2: null, + city: "Test City", + state: "TS", country: "US", postalCode: "12345", + taxId: null, }; const mockPremiumPlanDetails: PlanDetails = { @@ -89,7 +92,7 @@ describe("UpgradePaymentService", () => { beforeEach(() => { mockReset(mockOrganizationBillingService); mockReset(mockAccountBillingClient); - mockReset(mockTaxService); + mockReset(mockTaxClient); mockReset(mockLogService); TestBed.configureTestingModule({ @@ -101,7 +104,7 @@ describe("UpgradePaymentService", () => { useValue: mockOrganizationBillingService, }, { provide: AccountBillingClient, useValue: mockAccountBillingClient }, - { provide: TaxServiceAbstraction, useValue: mockTaxService }, + { provide: TaxClient, useValue: mockTaxClient }, { provide: LogService, useValue: mockLogService }, { provide: ApiService, useValue: mockApiService }, { provide: SyncService, useValue: mockSyncService }, @@ -114,55 +117,52 @@ describe("UpgradePaymentService", () => { describe("calculateEstimatedTax", () => { it("should calculate tax for premium plan", async () => { // Arrange - const mockResponse = mock(); - mockResponse.taxAmount = 2.5; + const mockResponse = mock(); + mockResponse.tax = 2.5; - mockTaxService.previewIndividualInvoice.mockResolvedValue(mockResponse); + mockTaxClient.previewTaxForPremiumSubscriptionPurchase.mockResolvedValue(mockResponse); // Act const result = await sut.calculateEstimatedTax(mockPremiumPlanDetails, mockBillingAddress); // Assert expect(result).toEqual(2.5); - expect(mockTaxService.previewIndividualInvoice).toHaveBeenCalledWith({ - passwordManager: { additionalStorage: 0 }, - taxInformation: { - postalCode: "12345", - country: "US", - }, - }); + expect(mockTaxClient.previewTaxForPremiumSubscriptionPurchase).toHaveBeenCalledWith( + 0, + mockBillingAddress, + ); }); it("should calculate tax for families plan", async () => { // Arrange - const mockResponse = mock(); - mockResponse.taxAmount = 5.0; + const mockResponse = mock(); + mockResponse.tax = 5.0; - mockTaxService.previewOrganizationInvoice.mockResolvedValue(mockResponse); + mockTaxClient.previewTaxForOrganizationSubscriptionPurchase.mockResolvedValue(mockResponse); // Act const result = await sut.calculateEstimatedTax(mockFamiliesPlanDetails, mockBillingAddress); // Assert expect(result).toEqual(5.0); - expect(mockTaxService.previewOrganizationInvoice).toHaveBeenCalledWith({ - passwordManager: { - additionalStorage: 0, - plan: PlanType.FamiliesAnnually, - seats: 6, + expect(mockTaxClient.previewTaxForOrganizationSubscriptionPurchase).toHaveBeenCalledWith( + { + cadence: "annually", + tier: "families", + passwordManager: { + additionalStorage: 0, + seats: 6, + sponsored: false, + }, }, - taxInformation: { - postalCode: "12345", - country: "US", - taxId: null, - }, - }); + mockBillingAddress, + ); }); it("should throw and log error if personal tax calculation fails", async () => { // Arrange const error = new Error("Tax service error"); - mockTaxService.previewIndividualInvoice.mockRejectedValue(error); + mockTaxClient.previewTaxForPremiumSubscriptionPurchase.mockRejectedValue(error); // Act & Assert await expect( @@ -174,7 +174,7 @@ describe("UpgradePaymentService", () => { it("should throw and log error if organization tax calculation fails", async () => { // Arrange const error = new Error("Tax service error"); - mockTaxService.previewOrganizationInvoice.mockRejectedValue(error); + mockTaxClient.previewTaxForOrganizationSubscriptionPurchase.mockRejectedValue(error); // Act & Assert await expect( sut.calculateEstimatedTax(mockFamiliesPlanDetails, mockBillingAddress), @@ -309,22 +309,5 @@ describe("UpgradePaymentService", () => { }), ).rejects.toThrow("Payment method type or token is missing"); }); - - describe("tokenizablePaymentMethodToLegacyEnum", () => { - it("should convert 'card' to PaymentMethodType.Card", () => { - const result = sut.tokenizablePaymentMethodToLegacyEnum("card"); - expect(result).toBe(PaymentMethodType.Card); - }); - - it("should convert 'bankAccount' to PaymentMethodType.BankAccount", () => { - const result = sut.tokenizablePaymentMethodToLegacyEnum("bankAccount"); - expect(result).toBe(PaymentMethodType.BankAccount); - }); - - it("should convert 'payPal' to PaymentMethodType.PayPal", () => { - const result = sut.tokenizablePaymentMethodToLegacyEnum("payPal"); - expect(result).toBe(PaymentMethodType.PayPal); - }); - }); }); }); diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts index 2cb8818309d..cabd148a539 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts @@ -7,17 +7,19 @@ import { OrganizationBillingServiceAbstraction, SubscriptionInformation, } from "@bitwarden/common/billing/abstractions"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; +import { PlanType } from "@bitwarden/common/billing/enums"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { LogService } from "@bitwarden/logging"; -import { AccountBillingClient } from "../../../../clients"; +import { + AccountBillingClient, + OrganizationSubscriptionPurchase, + TaxAmounts, + TaxClient, +} from "../../../../clients"; import { BillingAddress, - TokenizablePaymentMethod, + tokenizablePaymentMethodToLegacyEnum, TokenizedPaymentMethod, } from "../../../../payment/types"; import { @@ -26,12 +28,6 @@ import { PersonalSubscriptionPricingTierIds, } from "../../../../types/subscription-pricing-tier"; -type TaxInformation = { - postalCode: string; - country: string; - taxId: string | null; -}; - export type PlanDetails = { tier: PersonalSubscriptionPricingTierId; details: PersonalSubscriptionPricingTier; @@ -53,7 +49,7 @@ export class UpgradePaymentService { constructor( private organizationBillingService: OrganizationBillingServiceAbstraction, private accountBillingClient: AccountBillingClient, - private taxService: TaxServiceAbstraction, + private taxClient: TaxClient, private logService: LogService, private apiService: ApiService, private syncService: SyncService, @@ -64,20 +60,13 @@ export class UpgradePaymentService { */ async calculateEstimatedTax( planDetails: PlanDetails, - billingAddress: Pick, + billingAddress: BillingAddress, ): Promise { try { - const taxInformation: TaxInformation = { - postalCode: billingAddress.postalCode, - country: billingAddress.country, - // This is null for now since we only process Families and Premium plans - taxId: null, - }; - const isOrganizationPlan = planDetails.tier === PersonalSubscriptionPricingTierIds.Families; const isPremiumPlan = planDetails.tier === PersonalSubscriptionPricingTierIds.Premium; - let taxServiceCall: Promise<{ taxAmount: number }> | null = null; + let taxClientCall: Promise | null = null; if (isOrganizationPlan) { const seats = this.getPasswordManagerSeats(planDetails); @@ -85,36 +74,28 @@ export class UpgradePaymentService { throw new Error("Seats must be greater than 0 for organization plan"); } // Currently, only Families plan is supported for organization plans - const request: PreviewOrganizationInvoiceRequest = { - passwordManager: { - additionalStorage: 0, - plan: PlanType.FamiliesAnnually, - seats: seats, - }, - taxInformation, + const request: OrganizationSubscriptionPurchase = { + tier: "families", + cadence: "annually", + passwordManager: { seats, additionalStorage: 0, sponsored: false }, }; - taxServiceCall = this.taxService.previewOrganizationInvoice(request); + taxClientCall = this.taxClient.previewTaxForOrganizationSubscriptionPurchase( + request, + billingAddress, + ); } if (isPremiumPlan) { - const request: PreviewIndividualInvoiceRequest = { - passwordManager: { additionalStorage: 0 }, - taxInformation: { - postalCode: billingAddress.postalCode, - country: billingAddress.country, - }, - }; - - taxServiceCall = this.taxService.previewIndividualInvoice(request); + taxClientCall = this.taxClient.previewTaxForPremiumSubscriptionPurchase(0, billingAddress); } - if (taxServiceCall === null) { - throw new Error("Tax service call is not defined"); + if (taxClientCall === null) { + throw new Error("Tax client call is not defined"); } - const invoice = await taxServiceCall; - return invoice.taxAmount; + const preview = await taxClientCall; + return preview.tax; } catch (error: unknown) { this.logService.error("Tax calculation failed:", error); throw error; @@ -166,7 +147,7 @@ export class UpgradePaymentService { payment: { paymentMethod: [ paymentMethod.token, - this.tokenizablePaymentMethodToLegacyEnum(paymentMethod.type), + tokenizablePaymentMethodToLegacyEnum(paymentMethod.type), ], billing: { country: billingAddress.country, @@ -183,21 +164,6 @@ export class UpgradePaymentService { return result; } - /** - * Convert tokenizable payment method to legacy enum - * note: this will be removed once another PR is merged - */ - tokenizablePaymentMethodToLegacyEnum(paymentMethod: TokenizablePaymentMethod): PaymentMethodType { - switch (paymentMethod) { - case "bankAccount": - return PaymentMethodType.BankAccount; - case "card": - return PaymentMethodType.Card; - case "payPal": - return PaymentMethodType.PayPal; - } - } - private getPasswordManagerSeats(planDetails: PlanDetails): number { return "users" in planDetails.details.passwordManager ? planDetails.details.passwordManager.users diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts index df6d1fc8860..ebd1af34671 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts @@ -249,8 +249,13 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { this.upgradePaymentService .calculateEstimatedTax(this.selectedPlan, { + line1: null, + line2: null, + city: null, + state: null, country: billingAddress.country, postalCode: billingAddress.postalCode, + taxId: null, }) .then((tax) => { this.estimatedTax = tax;