mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #4721 from Infisical/fix/pricing-calculation-on-billing-page
[ENG-3823] fix: pricing calculation on billing page
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -66,6 +66,8 @@ export type PlanBillingInfo = {
|
||||
interval: "month" | "year";
|
||||
intervalCount: number;
|
||||
quantity: number;
|
||||
users: number;
|
||||
identities: number;
|
||||
};
|
||||
|
||||
export type Invoice = {
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user