1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-24 17:51:14 +02:00

feat: enterprise consumption billing (#9862)

Initial PR that adds logic for displaying a link to stripe to view
consumption based pricing in the billing overview

---------

Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
Fredrik Strand Oseberg 2025-05-06 13:15:31 +02:00 committed by GitHub
parent c6ab2a1cf7
commit db90ad9c6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 62 additions and 5 deletions

View File

@ -1,15 +1,18 @@
import { type IInstanceStatus, InstancePlan } from 'interfaces/instance'; import { type IInstanceStatus, InstancePlan } from 'interfaces/instance';
import { BillingDetailsPro } from './BillingDetailsPro'; import { BillingDetailsPro } from './BillingDetailsPro';
import { BillingDetailsPAYG } from './BillingDetailsPAYG'; import { BillingDetailsPAYG } from './BillingDetailsPAYG';
import { BillingDetailsEnterpriseConsumption } from './BillingDetailsEnterpriseConsumption';
interface IBillingDetailsProps { interface IBillingDetailsProps {
instanceStatus: IInstanceStatus; instanceStatus: IInstanceStatus;
isPAYG: boolean; isPAYG: boolean;
isEnterpriseConsumption: boolean;
} }
export const BillingDetails = ({ export const BillingDetails = ({
instanceStatus, instanceStatus,
isPAYG, isPAYG,
isEnterpriseConsumption,
}: IBillingDetailsProps) => { }: IBillingDetailsProps) => {
if (isPAYG) { if (isPAYG) {
return <BillingDetailsPAYG instanceStatus={instanceStatus} />; return <BillingDetailsPAYG instanceStatus={instanceStatus} />;
@ -19,5 +22,9 @@ export const BillingDetails = ({
return <BillingDetailsPro instanceStatus={instanceStatus} />; return <BillingDetailsPro instanceStatus={instanceStatus} />;
} }
if (isEnterpriseConsumption) {
return <BillingDetailsEnterpriseConsumption />;
}
return null; return null;
}; };

View File

@ -0,0 +1,40 @@
import { styled, Typography, Grid, Button } from '@mui/material';
import { Link } from 'react-router-dom';
import { GridRow } from 'component/common/GridRow/GridRow';
import { GridCol } from 'component/common/GridCol/GridCol';
import LaunchIcon from '@mui/icons-material/Launch';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
import { formatApiPath } from 'utils/formatPath';
const StyledInfoLabel = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
color: theme.palette.text.secondary,
}));
const StyledButton = styled(Button)(({ theme }) => ({
marginTop: theme.spacing(1),
display: 'inline-flex',
alignItems: 'center',
}));
export const BillingDetailsEnterpriseConsumption = () => {
const PORTAL_URL = formatApiPath('api/admin/invoices/portal');
return (
<>
<Grid container>
<GridRow sx={(theme) => ({ marginBottom: theme.spacing(3) })}>
<GridCol vertical>
<StyledButton
href={PORTAL_URL}
variant='outlined'
endIcon={<LaunchIcon />}
>
View usage charges
</StyledButton>
</GridCol>
</GridRow>
</Grid>
</>
);
};

View File

@ -1,6 +1,6 @@
import { Alert, Grid, styled } from '@mui/material'; import { Alert, Grid, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { InstanceState } from 'interfaces/instance'; import { InstancePlan, InstanceState } from 'interfaces/instance';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { trialHasExpired, isTrialInstance } from 'utils/instanceTrial'; import { trialHasExpired, isTrialInstance } from 'utils/instanceTrial';
import { GridRow } from 'component/common/GridRow/GridRow'; import { GridRow } from 'component/common/GridRow/GridRow';
@ -65,7 +65,10 @@ export const BillingPlan = () => {
} = useUiConfig(); } = useUiConfig();
const { instanceStatus } = useInstanceStatus(); const { instanceStatus } = useInstanceStatus();
const isPro =
instanceStatus?.plan && instanceStatus?.plan === InstancePlan.PRO;
const isPAYG = billing === 'pay-as-you-go'; const isPAYG = billing === 'pay-as-you-go';
const isEnterpriseConsumption = billing === 'enterprise-consumption';
if (!instanceStatus) if (!instanceStatus)
return ( return (
@ -130,7 +133,7 @@ export const BillingPlan = () => {
</GridCol> </GridCol>
<GridCol> <GridCol>
<ConditionallyRender <ConditionallyRender
condition={!isPAYG} condition={Boolean(isPro)}
show={ show={
<StyledPriceSpan> <StyledPriceSpan>
${baseProPrice.toFixed(2)} ${baseProPrice.toFixed(2)}
@ -141,9 +144,14 @@ export const BillingPlan = () => {
</GridRow> </GridRow>
<GridRow> <GridRow>
<ConditionallyRender <ConditionallyRender
condition={isPAYG} condition={isPAYG || isEnterpriseConsumption}
show={ show={
<StyledPAYGSpan>Pay-as-You-Go</StyledPAYGSpan> <StyledPAYGSpan>
Pay-as-You-Go{' '}
{isEnterpriseConsumption
? 'Consumption'
: ''}
</StyledPAYGSpan>
} }
/> />
</GridRow> </GridRow>
@ -151,6 +159,7 @@ export const BillingPlan = () => {
<BillingDetails <BillingDetails
instanceStatus={instanceStatus} instanceStatus={instanceStatus}
isPAYG={isPAYG} isPAYG={isPAYG}
isEnterpriseConsumption={isEnterpriseConsumption}
/> />
</StyledPlanBox> </StyledPlanBox>
</Grid> </Grid>

View File

@ -39,6 +39,7 @@ export const useInstanceStatus = (): IUseInstanceStatusOutput => {
refresh, refresh,
isBilling: isBilling:
uiConfig.billing === 'pay-as-you-go' || uiConfig.billing === 'pay-as-you-go' ||
uiConfig.billing === 'enterprise-consumption' ||
billingPlans.includes(data?.plan ?? InstancePlan.UNKNOWN), billingPlans.includes(data?.plan ?? InstancePlan.UNKNOWN),
loading, loading,
error, error,

View File

@ -19,7 +19,7 @@ export interface IUiConfig {
name: string; name: string;
slogan: string; slogan: string;
environment?: string; environment?: string;
billing?: 'subscription' | 'pay-as-you-go'; billing?: 'subscription' | 'pay-as-you-go' | 'enterprise-consumption';
unleashUrl?: string; unleashUrl?: string;
version: string; version: string;
versionInfo?: IVersionInfo; versionInfo?: IVersionInfo;