From 40768631e0c407f1d97c1ec29101f7a71033fc93 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 30 Jan 2024 17:46:52 +0700 Subject: [PATCH 1/2] PAdd customer portal redirect, patch free trial endpoint --- backend/src/ee/routes/v1/license-router.ts | 20 ++++++ .../ee/services/license/license-service.ts | 64 +++++++++++++++++-- .../src/ee/services/license/license-types.ts | 4 +- .../src/hooks/api/organization/queries.tsx | 2 +- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index 675dcdc00..72777e4db 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -89,6 +89,26 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + url: "/:organizationId/customer-portal-session", + method: "POST", + schema: { + params: z.object({ organizationId: z.string().trim() }), + response: { + 200: z.any() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const data = await server.services.license.createOrganizationPortalSession({ + actorId: req.permission.id, + actor: req.permission.type, + orgId: req.params.organizationId + }); + return data; + } + }); + server.route({ url: "/:organizationId/plan/billing", method: "GET", diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index d633916e0..6ed9230be 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -24,7 +24,8 @@ import { TOrgPlanDTO, TOrgPlansTableDTO, TOrgPmtMethodsDTO, - TStartOrgTrailDTO, + TStartOrgTrialDTO, + TCreateOrgPortalSession, TUpdateOrgBillingDetailsDTO } from "./license-types"; @@ -201,7 +202,7 @@ export const licenseServiceFactory = ({ return plan; }; - const startOrgTrail = async ({ orgId, actorId, actor, success_url }: TStartOrgTrailDTO) => { + const startOrgTrial = async ({ orgId, actorId, actor, success_url }: TStartOrgTrialDTO) => { const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, @@ -222,13 +223,67 @@ export const licenseServiceFactory = ({ const { data: { url } } = await licenseServerCloudApi.request.post( - `/api/license-server/v1/customers/${organization.customerId}/session/trail`, + `/api/license-server/v1/customers/${organization.customerId}/session/trial`, { success_url } ); featureStore.del(FEATURE_CACHE_KEY(orgId)); return { url }; }; + const createOrganizationPortalSession = async ({ orgId, actorId, actor }: TCreateOrgPortalSession) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionActions.Create, + OrgPermissionSubjects.Billing + ); + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionActions.Edit, + OrgPermissionSubjects.Billing + ); + + const organization = await orgDAL.findOrgById(orgId); + if (!organization) { + throw new BadRequestError({ + message: "Failed to find organization" + }); + } + + const { + data: { pmtMethods } + } = await licenseServerCloudApi.request.get( + `/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods` + ); + + if (pmtMethods.length < 1) { + // case: organization has no payment method on file + // -> redirect to add payment method portal + const { + data: { url } + } = await licenseServerCloudApi.request.post( + `/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods`, + { + success_url: appCfg.SITE_URL + "/dashboard", + cancel_url: appCfg.SITE_URL + "/dashboard" + } + ); + + return { url }; + } else { + // case: organization has payment method on file + // -> redirect to billing portal + const { + data: { url } + } = await licenseServerCloudApi.request.post( + `/api/license-server/v1/customers/${organization.customerId}/billing-details/billing-portal`, + { + return_url: appCfg.SITE_URL + "/dashboard" + } + ); + + return { url }; + } + } + const getOrgBillingInfo = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => { const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); ForbiddenError.from(permission).throwUnlessCan( @@ -511,7 +566,8 @@ export const licenseServiceFactory = ({ refreshPlan, getOrgPlan, getOrgPlansTableByBillCycle, - startOrgTrail, + startOrgTrial, + createOrganizationPortalSession, getOrgBillingInfo, getOrgPlanTable, getOrgBillingDetails, diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index f4cd2db1c..762aff5b2 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -40,10 +40,12 @@ export type TOrgPlanDTO = { projectId?: string; } & TOrgPermission; -export type TStartOrgTrailDTO = { +export type TStartOrgTrialDTO = { success_url: string; } & TOrgPermission; +export type TCreateOrgPortalSession = TOrgPermission; + export type TGetOrgBillInfoDTO = TOrgPermission; export type TOrgPlanTableDTO = TOrgPermission; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 5b8b145c2..0edaf2c2f 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -328,7 +328,7 @@ export const useCreateCustomerPortalSession = () => { return useMutation({ mutationFn: async (organizationId: string) => { const { data } = await apiRequest.post( - `/api/v1/organization/${organizationId}/customer-portal-session` + `/api/v1/organizations/${organizationId}/customer-portal-session` ); return data; } From a2ef7482ea01f28520033aed1725bebfead13618 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 30 Jan 2024 21:14:22 +0700 Subject: [PATCH 2/2] Correct startOrgTrial spelling --- backend/src/ee/routes/v1/license-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index 72777e4db..36c3a571d 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -79,7 +79,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const data = await server.services.license.startOrgTrail({ + const data = await server.services.license.startOrgTrial({ actorId: req.permission.id, actor: req.permission.type, orgId: req.params.organizationId,