From b4ed1fa96a6aaa559e6e60b54ed425456b0ff229 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 21 Jul 2025 21:17:36 -0300 Subject: [PATCH] Prevent users from deleting the last payment method attached to the org --- .../ee/services/license/license-service.ts | 23 +++++++++++++++---- .../BillingDetailsTab/PmtMethodsTable.tsx | 7 ++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index 7e784d9ad..891fa208f 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -5,13 +5,14 @@ // TODO(akhilmhdh): With tony find out the api structure and fill it here import { ForbiddenError } from "@casl/ability"; +import { AxiosError } from "axios"; import { CronJob } from "cron"; import { Knex } from "knex"; import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { verifyOfflineLicense } from "@app/lib/crypto"; -import { NotFoundError } from "@app/lib/errors"; +import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; @@ -603,10 +604,22 @@ export const licenseServiceFactory = ({ }); } - const { data } = await licenseServerCloudApi.request.delete( - `/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods/${pmtMethodId}` - ); - return data; + try { + const { data } = await licenseServerCloudApi.request.delete( + `/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods/${pmtMethodId}` + ); + return data; + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + message: `Failed to remove payment method: ${error.response?.data?.message}` + }); + } + throw new BadRequestError({ + message: "Unable to remove payment method" + }); + } }; const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => { diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx index c82dbddec..bf0851e11 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx @@ -32,6 +32,13 @@ export const PmtMethodsTable = () => { const handleDeletePmtMethodBtnClick = async () => { if (!currentOrg?.id || !pmtMethodToRemove) return; + if (data?.length === 1) { + createNotification({ + type: "error", + text: "You must have at least one payment method" + }); + return; + } try { await deleteOrgPmtMethod.mutateAsync({ organizationId: currentOrg.id,