fix: pricing calculation on billing page

This commit is contained in:
Piyush Gupta
2025-10-22 20:59:25 +05:30
parent 0c7a5cfe27
commit eb1bf79066
4 changed files with 65 additions and 28 deletions

View File

@@ -40,6 +40,7 @@ import {
TOrgPlanDTO,
TOrgPlansTableDTO,
TOrgPmtMethodsDTO,
TPlanBillingInfo,
TStartOrgTrialDTO,
TUpdateOrgBillingDetailsDTO
} from "./license-types";
@@ -465,6 +466,21 @@ export const licenseServiceFactory = ({
return { url };
};
const getUsageMetrics = async (orgId: string) => {
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
orgDAL.countAllOrgMembers(orgId),
licenseDAL.countOfOrgIdentities(orgId),
projectDAL.countOfOrgProjects(orgId)
]);
return {
orgMembersUsed,
identityUsed,
projectCount,
totalIdentities: identityUsed + orgMembersUsed
};
};
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission({
actorId,
@@ -483,10 +499,16 @@ export const licenseServiceFactory = ({
});
}
if (instanceType === InstanceType.Cloud) {
const { data } = await licenseServerCloudApi.request.get(
const { data } = await licenseServerCloudApi.request.get<TPlanBillingInfo>(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
);
return data;
const { identityUsed, orgMembersUsed } = await getUsageMetrics(orgId);
return {
...data,
users: orgMembersUsed,
identities: identityUsed
};
}
return {
@@ -495,7 +517,9 @@ export const licenseServiceFactory = ({
interval: "month",
intervalCount: 1,
amount: 0,
quantity: 1
quantity: 1,
users: 0,
identities: 0
};
};
@@ -539,21 +563,6 @@ export const licenseServiceFactory = ({
throw new Error(`Unsupported instance type for server-based plan table: ${instanceType}`);
};
const getUsageMetrics = async (orgId: string) => {
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
orgDAL.countAllOrgMembers(orgId),
licenseDAL.countOfOrgIdentities(orgId),
projectDAL.countOfOrgProjects(orgId)
]);
return {
orgMembersUsed,
identityUsed,
projectCount,
totalIdentities: identityUsed + orgMembersUsed
};
};
// returns org current plan feature table
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission({

View File

@@ -22,6 +22,15 @@ export type TOfflineLicense = {
features: TFeatureSet;
};
export type TPlanBillingInfo = {
currentPeriodStart: number;
currentPeriodEnd: number;
interval: "month" | "year";
intervalCount: number;
amount: number;
quantity: number;
};
export type TFeatureSet = {
_id: null;
slug: string | null;

View File

@@ -66,6 +66,8 @@ export type PlanBillingInfo = {
interval: "month" | "year";
intervalCount: number;
quantity: number;
users: number;
identities: number;
};
export type Invoice = {

View File

@@ -1,10 +1,10 @@
import { useEffect } from "react";
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { faArrowUpRightFromSquare, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQueryClient } from "@tanstack/react-query";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { Button, Tooltip } from "@app/components/v2";
import {
OrgPermissionBillingActions,
OrgPermissionSubjects,
@@ -27,6 +27,9 @@ export const PreviewSection = () => {
const { subscription } = useSubscription(true);
const queryClient = useQueryClient();
const { data, isPending } = useGetOrgPlanBillingInfo(currentOrg?.id ?? "");
const totalAmount = data?.amount ? data.amount * (data.users + data.identities) : 0;
const getOrgTrialUrl = useGetOrgTrialUrl();
const createCustomerPortalSession = useCreateCustomerPortalSession();
@@ -190,14 +193,28 @@ export const PreviewSection = () => {
</OrgPermissionCan>
)}
</div>
<div className="mr-4 flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-2 text-gray-400">Price</p>
<p className="mb-8 text-2xl font-medium text-mineshaft-50">
{subscription.status === "trialing"
? "$0.00 / month"
: `${formatAmount(data.amount)} / ${data.interval}`}
</p>
</div>
{subscription.slug !== "enterprise" ? (
<div className="mr-4 flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-2 text-gray-400">Price</p>
<p className="mb-8 text-2xl font-medium text-mineshaft-50">
{subscription.status === "trialing" ? (
"$0.00 / month"
) : (
<>
{formatAmount(totalAmount)} / {data.interval}
{(subscription.slug === "pro" || subscription.slug === "pro-annual") && (
<Tooltip
content={`Total price is based on the number of users and machine identities at ${formatAmount(data.amount)} each. You have ${data.users} ${data.users > 1 ? "users" : "user"} and ${data.identities} ${data.identities > 1 ? "machine identities" : "machine identity"}.`}
className="max-w-lg"
>
<FontAwesomeIcon icon={faInfoCircle} className="ml-2" size="xs" />
</Tooltip>
)}
</>
)}
</p>
</div>
) : null}
<div className="flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-2 text-gray-400">Subscription renews on</p>
<p className="mb-8 text-2xl font-medium text-mineshaft-50">