chore(billing) - remove feature flag (#20531)
- remove feature flag - remove old enforce cap usage logic
This commit is contained in:
+1
-3
@@ -37,7 +37,6 @@ import {
|
||||
|
||||
const STRIPE_DASHBOARD_BASE_URL = 'https://dashboard.stripe.com';
|
||||
const BASE_PRODUCT_KEY = 'BASE_PRODUCT';
|
||||
const METERED_PRODUCT_KEY = 'WORKFLOW_NODE_EXECUTION';
|
||||
const RESOURCE_CREDIT_KEY = 'RESOURCE_CREDIT';
|
||||
const EM_DASH = '\u2014';
|
||||
|
||||
@@ -320,8 +319,7 @@ export const SettingsAdminWorkspaceBillingContent = ({
|
||||
Icon:
|
||||
item.productKey === BASE_PRODUCT_KEY
|
||||
? IconUsers
|
||||
: item.productKey === METERED_PRODUCT_KEY ||
|
||||
item.productKey === RESOURCE_CREDIT_KEY
|
||||
: item.productKey === RESOURCE_CREDIT_KEY
|
||||
? IconCoins
|
||||
: IconBox,
|
||||
label: item.productName || t`Unnamed product`,
|
||||
|
||||
+2
-12
@@ -5,10 +5,8 @@ import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { SettingsBillingCreditsSection } from '@/settings/billing/components/SettingsBillingCreditsSection';
|
||||
import { SettingsBillingSubscriptionInfo } from '@/settings/billing/components/SettingsBillingSubscriptionInfo';
|
||||
import { useGetResourceCreditUsage } from '@/settings/billing/hooks/useGetResourceCreditUsage';
|
||||
import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -17,7 +15,6 @@ import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import {
|
||||
BillingPortalSessionDocument,
|
||||
FeatureFlagKey,
|
||||
SubscriptionStatus,
|
||||
} from '~/generated-metadata/graphql';
|
||||
export const SettingsBillingContent = () => {
|
||||
@@ -33,15 +30,8 @@ export const SettingsBillingContent = () => {
|
||||
|
||||
const subscriptionStatus = useSubscriptionStatus();
|
||||
|
||||
const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED);
|
||||
|
||||
const { isGetMeteredProductsUsageQueryLoaded } =
|
||||
useGetWorkflowNodeExecutionUsage();
|
||||
const { isGetResourceCreditUsageQueryLoaded } = useGetResourceCreditUsage();
|
||||
|
||||
const isUsageQueryLoaded = isV2
|
||||
? isGetResourceCreditUsageQueryLoaded
|
||||
: isGetMeteredProductsUsageQueryLoaded;
|
||||
const { isGetResourceCreditUsageQueryLoaded: isUsageQueryLoaded } =
|
||||
useGetResourceCreditUsage();
|
||||
|
||||
const hasNotCanceledCurrentSubscription =
|
||||
isDefined(subscriptionStatus) &&
|
||||
|
||||
+8
-62
@@ -1,24 +1,20 @@
|
||||
import { type CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { ResourceCreditPriceSelector } from '@/settings/billing/components/internal/ResourceCreditPriceSelector';
|
||||
import { MeteredPriceSelector } from '@/settings/billing/components/internal/MeteredPriceSelector';
|
||||
import { SettingsBillingLabelValueItem } from '@/settings/billing/components/internal/SettingsBillingLabelValueItem';
|
||||
import { SubscriptionInfoContainer } from '@/settings/billing/components/SubscriptionInfoContainer';
|
||||
import { useBillingWording } from '@/settings/billing/hooks/useBillingWording';
|
||||
import { useCurrentBillingFlags } from '@/settings/billing/hooks/useCurrentBillingFlags';
|
||||
import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered';
|
||||
import { useCurrentResourceCredit } from '@/settings/billing/hooks/useCurrentResourceCredit';
|
||||
import { useGetResourceCreditUsage } from '@/settings/billing/hooks/useGetResourceCreditUsage';
|
||||
import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage';
|
||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import { DOCUMENTATION_PATHS } from 'twenty-shared/constants';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { formatToShortNumber, getSettingsPath } from 'twenty-shared/utils';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
H2Title,
|
||||
HorizontalSeparator,
|
||||
@@ -30,10 +26,7 @@ import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
SubscriptionStatus,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { SubscriptionStatus } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledCreditUsageFooterActions = styled.div`
|
||||
display: flex;
|
||||
@@ -54,40 +47,21 @@ export const SettingsBillingCreditsSection = ({
|
||||
|
||||
const { isMonthlyPlan } = useCurrentBillingFlags();
|
||||
|
||||
const { getCurrentMeteredPricesByInterval } = useCurrentMetered();
|
||||
const { getResourceCreditPricesByInterval } = useCurrentResourceCredit();
|
||||
|
||||
const { getIntervalLabel } = useBillingWording();
|
||||
|
||||
const isTrialing = subscriptionStatus === SubscriptionStatus.Trialing;
|
||||
|
||||
const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED);
|
||||
|
||||
const { getWorkflowNodeExecutionUsage } = useGetWorkflowNodeExecutionUsage();
|
||||
const { getResourceCreditUsage } = useGetResourceCreditUsage();
|
||||
|
||||
const {
|
||||
usedCredits,
|
||||
grantedCredits,
|
||||
totalGrantedCredits,
|
||||
unitPriceCents,
|
||||
rolloverCredits,
|
||||
} = isV2 ? getResourceCreditUsage() : getWorkflowNodeExecutionUsage();
|
||||
const { usedCredits, grantedCredits, totalGrantedCredits, rolloverCredits } =
|
||||
getResourceCreditUsage();
|
||||
|
||||
const progressBarValue = (usedCredits / totalGrantedCredits) * 100;
|
||||
|
||||
const intervalLabel = getIntervalLabel(isMonthlyPlan);
|
||||
|
||||
const extraCreditsUsed = Math.max(0, usedCredits - totalGrantedCredits);
|
||||
|
||||
const costPerExtraCredits = unitPriceCents / 100;
|
||||
|
||||
const costExtraCredits = (extraCreditsUsed * unitPriceCents) / 100;
|
||||
|
||||
const meteredBillingPrices = getCurrentMeteredPricesByInterval(
|
||||
currentBillingSubscription.interval,
|
||||
);
|
||||
|
||||
const resourceCreditPrices = getResourceCreditPricesByInterval(
|
||||
currentBillingSubscription.interval,
|
||||
);
|
||||
@@ -143,27 +117,6 @@ export const SettingsBillingCreditsSection = ({
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{!isV2 && (
|
||||
<>
|
||||
<HorizontalSeparator
|
||||
noMargin
|
||||
color={theme.background.tertiary}
|
||||
/>
|
||||
<SettingsBillingLabelValueItem
|
||||
label={t`Extra Credits Used`}
|
||||
value={`${formatToShortNumber(extraCreditsUsed)}`}
|
||||
/>
|
||||
<SettingsBillingLabelValueItem
|
||||
label={t`Cost per Extra Credits`}
|
||||
value={`$${formatNumber(costPerExtraCredits, { abbreviate: true, decimals: 2 })}`}
|
||||
/>
|
||||
<SettingsBillingLabelValueItem
|
||||
label={t`Cost`}
|
||||
isValueInPrimaryColor={true}
|
||||
value={`$${formatNumber(costExtraCredits, { decimals: 2 })}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</SubscriptionInfoContainer>
|
||||
@@ -192,17 +145,10 @@ export const SettingsBillingCreditsSection = ({
|
||||
</StyledCreditUsageFooterActions>
|
||||
</Section>
|
||||
<Section>
|
||||
{isV2 ? (
|
||||
<ResourceCreditPriceSelector
|
||||
resourceCreditPrices={resourceCreditPrices}
|
||||
isTrialing={isTrialing}
|
||||
/>
|
||||
) : (
|
||||
<MeteredPriceSelector
|
||||
meteredBillingPrices={meteredBillingPrices}
|
||||
isTrialing={isTrialing}
|
||||
/>
|
||||
)}
|
||||
<ResourceCreditPriceSelector
|
||||
resourceCreditPrices={resourceCreditPrices}
|
||||
isTrialing={isTrialing}
|
||||
/>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
|
||||
+29
-68
@@ -13,12 +13,10 @@ import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { PlansTags } from '@/settings/billing/components/internal/PlansTags';
|
||||
import { useBillingWording } from '@/settings/billing/hooks/useBillingWording';
|
||||
import { useCurrentBillingFlags } from '@/settings/billing/hooks/useCurrentBillingFlags';
|
||||
import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered';
|
||||
import { useCurrentPlan } from '@/settings/billing/hooks/useCurrentPlan';
|
||||
import { useCurrentResourceCredit } from '@/settings/billing/hooks/useCurrentResourceCredit';
|
||||
import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod';
|
||||
import { useGetResourceCreditUsage } from '@/settings/billing/hooks/useGetResourceCreditUsage';
|
||||
import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage';
|
||||
import { useHasNextBillingPhase } from '@/settings/billing/hooks/useHasNextBillingPhase';
|
||||
import { useNextBillingPhase } from '@/settings/billing/hooks/useNextBillingPhase';
|
||||
import { useNextBillingSeats } from '@/settings/billing/hooks/useNextBillingSeats';
|
||||
@@ -29,7 +27,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { styled } from '@linaria/react';
|
||||
@@ -55,8 +52,7 @@ import {
|
||||
BillingProductKey,
|
||||
CancelSwitchBillingIntervalDocument,
|
||||
CancelSwitchBillingPlanDocument,
|
||||
CancelSwitchMeteredPriceDocument,
|
||||
FeatureFlagKey,
|
||||
CancelSwitchResourceCreditPriceDocument,
|
||||
PermissionFlagType,
|
||||
SubscriptionInterval,
|
||||
SubscriptionStatus,
|
||||
@@ -107,18 +103,10 @@ export const SettingsBillingSubscriptionInfo = ({
|
||||
|
||||
const { openModal } = useModal();
|
||||
|
||||
const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED);
|
||||
|
||||
const { refetchMeteredProductsUsage } = useGetWorkflowNodeExecutionUsage();
|
||||
const { refetchResourceCreditUsage } = useGetResourceCreditUsage();
|
||||
|
||||
const refetchUsage = isV2
|
||||
? refetchResourceCreditUsage
|
||||
: refetchMeteredProductsUsage;
|
||||
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const { currentMeteredBillingPrice } = useCurrentMetered();
|
||||
const { currentResourceCreditBillingPrice } = useCurrentResourceCredit();
|
||||
|
||||
const { currentPlan, oppositPlan } = useCurrentPlan();
|
||||
@@ -132,23 +120,16 @@ export const SettingsBillingSubscriptionInfo = ({
|
||||
const { nextBillingPhase } = useNextBillingPhase();
|
||||
const nextInterval =
|
||||
splitedPhaseItemsInPrices?.nextBasePrice?.recurringInterval;
|
||||
const nextMeteredBillingPrice = splitedPhaseItemsInPrices.nextMeteredPrice;
|
||||
const nextResourceCreditPrice =
|
||||
splitedPhaseItemsInPrices.nextResourceCreditPrice;
|
||||
const subscriptionStatus = useSubscriptionStatus();
|
||||
|
||||
const currentInterval = isV2
|
||||
? currentBillingSubscription.interval
|
||||
: currentMeteredBillingPrice?.recurringInterval;
|
||||
const currentInterval = currentBillingSubscription.interval;
|
||||
|
||||
const currentCreditsByPeriod = isV2
|
||||
? (currentResourceCreditBillingPrice?.creditAmount ?? null)
|
||||
: ((currentMeteredBillingPrice as { tiers?: { upTo: number }[] } | null)
|
||||
?.tiers?.[0]?.upTo ?? null);
|
||||
const currentCreditsByPeriod =
|
||||
currentResourceCreditBillingPrice?.creditAmount ?? null;
|
||||
|
||||
const nextCreditsByPeriod = isV2
|
||||
? (nextResourceCreditPrice?.creditAmount ?? null)
|
||||
: (nextMeteredBillingPrice?.tiers?.[0]?.upTo ?? null);
|
||||
const nextCreditsByPeriod = nextResourceCreditPrice?.creditAmount ?? null;
|
||||
|
||||
const {
|
||||
getIntervalLabelAsAdjectiveCapitalize,
|
||||
@@ -175,8 +156,8 @@ export const SettingsBillingSubscriptionInfo = ({
|
||||
CancelSwitchBillingPlanDocument,
|
||||
);
|
||||
|
||||
const [cancelSwitchMeteredPrice] = useMutation(
|
||||
CancelSwitchMeteredPriceDocument,
|
||||
const [cancelSwitchResourceCreditPrice] = useMutation(
|
||||
CancelSwitchResourceCreditPriceDocument,
|
||||
);
|
||||
|
||||
const setCurrentWorkspace = useSetAtomState(currentWorkspaceState);
|
||||
@@ -237,7 +218,7 @@ export const SettingsBillingSubscriptionInfo = ({
|
||||
currentBillingSubscription,
|
||||
billingSubscriptions,
|
||||
});
|
||||
refetchUsage();
|
||||
refetchResourceCreditUsage();
|
||||
};
|
||||
|
||||
const switchInterval = async () => {
|
||||
@@ -351,28 +332,26 @@ export const SettingsBillingSubscriptionInfo = ({
|
||||
}
|
||||
};
|
||||
|
||||
const cancelMeteredSwitching = async () => {
|
||||
const cancelResourceCreditSwitching = async () => {
|
||||
if (isAnyActionLoading || isCancellingMeteredSwitch) return;
|
||||
setIsCancellingMeteredSwitch(true);
|
||||
try {
|
||||
const { data } = await cancelSwitchMeteredPrice();
|
||||
const { data } = await cancelSwitchResourceCreditPrice();
|
||||
|
||||
if (
|
||||
isDefined(data?.cancelSwitchMeteredPrice?.currentBillingSubscription)
|
||||
isDefined(
|
||||
data?.cancelSwitchResourceCreditPrice?.currentBillingSubscription,
|
||||
)
|
||||
) {
|
||||
refreshWorkspace(data.cancelSwitchMeteredPrice);
|
||||
refreshWorkspace(data.cancelSwitchResourceCreditPrice);
|
||||
}
|
||||
|
||||
enqueueSuccessSnackBar({
|
||||
message: isV2
|
||||
? t`Credit pack switching has been cancelled.`
|
||||
: t`Metered tier switching has been cancelled.`,
|
||||
message: t`Credit pack switching has been cancelled.`,
|
||||
});
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: isV2
|
||||
? t`Error while cancelling credit pack switching.`
|
||||
: t`Error while cancelling metered tier switching.`,
|
||||
message: t`Error while cancelling credit pack switching.`,
|
||||
});
|
||||
} finally {
|
||||
setIsCancellingMeteredSwitch(false);
|
||||
@@ -529,26 +508,16 @@ export const SettingsBillingSubscriptionInfo = ({
|
||||
disabled={!canSwitchSubscription || isAnyActionLoading}
|
||||
/>
|
||||
)}
|
||||
{/*@todo: find a way to check if the metered tier match when interval change too*/}
|
||||
{(isV2
|
||||
? nextResourceCreditPrice &&
|
||||
currentCreditsByPeriod !== nextCreditsByPeriod
|
||||
: isDefined(nextInterval) &&
|
||||
isDefined(nextCreditsByPeriod) &&
|
||||
currentInterval === nextInterval &&
|
||||
currentCreditsByPeriod !== nextCreditsByPeriod) && (
|
||||
<Button
|
||||
Icon={IconCircleX}
|
||||
title={
|
||||
isV2
|
||||
? t`Cancel credit pack switching`
|
||||
: t`Cancel metered tier switching`
|
||||
}
|
||||
variant="secondary"
|
||||
onClick={() => openModal(CANCEL_SWITCH_METERED_PRICE_MODAL_ID)}
|
||||
disabled={!canSwitchSubscription || isAnyActionLoading}
|
||||
/>
|
||||
)}
|
||||
{nextResourceCreditPrice &&
|
||||
currentCreditsByPeriod !== nextCreditsByPeriod && (
|
||||
<Button
|
||||
Icon={IconCircleX}
|
||||
title={t`Cancel credit pack switching`}
|
||||
variant="secondary"
|
||||
onClick={() => openModal(CANCEL_SWITCH_METERED_PRICE_MODAL_ID)}
|
||||
disabled={!canSwitchSubscription || isAnyActionLoading}
|
||||
/>
|
||||
)}
|
||||
</StyledSwitchButtonContainer>
|
||||
<ConfirmationModal
|
||||
modalInstanceId={SWITCH_BILLING_INTERVAL_TO_YEARLY_MODAL_ID}
|
||||
@@ -615,17 +584,9 @@ export const SettingsBillingSubscriptionInfo = ({
|
||||
/>
|
||||
<ConfirmationModal
|
||||
modalInstanceId={CANCEL_SWITCH_METERED_PRICE_MODAL_ID}
|
||||
title={
|
||||
isV2
|
||||
? t`Cancel credit pack switching?`
|
||||
: t`Cancel metered tier switching?`
|
||||
}
|
||||
subtitle={
|
||||
isV2
|
||||
? t`You have scheduled a credit pack change. Do you want to cancel it?`
|
||||
: t`You have scheduled a metered tier change. Do you want to cancel it?`
|
||||
}
|
||||
onConfirmClick={cancelMeteredSwitching}
|
||||
title={t`Cancel credit pack switching?`}
|
||||
subtitle={t`You have scheduled a credit pack change. Do you want to cancel it?`}
|
||||
onConfirmClick={cancelResourceCreditSwitching}
|
||||
confirmButtonText={t`Confirm`}
|
||||
confirmButtonAccent="blue"
|
||||
loading={isCancellingMeteredSwitch}
|
||||
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { useBillingWording } from '@/settings/billing/hooks/useBillingWording';
|
||||
import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered';
|
||||
import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage';
|
||||
import {
|
||||
type BillingPriceTiers,
|
||||
type MeteredBillingPrice,
|
||||
} from '@/settings/billing/types/billing-price-tiers.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { findOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
SetMeteredSubscriptionPriceDocument,
|
||||
SubscriptionInterval,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledRow = styled.div`
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSelectContainer = styled.div`
|
||||
flex: 1 1;
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
flex: 0 0 auto;
|
||||
`;
|
||||
|
||||
export const MeteredPriceSelector = ({
|
||||
meteredBillingPrices,
|
||||
isTrialing = false,
|
||||
}: {
|
||||
meteredBillingPrices: Array<MeteredBillingPrice>;
|
||||
isTrialing?: boolean;
|
||||
}) => {
|
||||
const { currentMeteredBillingPrice } = useCurrentMetered();
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
const [currentWorkspace, setCurrentWorkspace] = useAtomState(
|
||||
currentWorkspaceState,
|
||||
);
|
||||
|
||||
const { refetchMeteredProductsUsage } = useGetWorkflowNodeExecutionUsage();
|
||||
|
||||
const { getIntervalLabel } = useBillingWording();
|
||||
|
||||
const [currentMeteredPrice, setCurrentMeteredPrice] = useState(
|
||||
currentMeteredBillingPrice,
|
||||
);
|
||||
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const [setMeteredSubscriptionPrice, { loading: isUpdating }] = useMutation(
|
||||
SetMeteredSubscriptionPriceDocument,
|
||||
);
|
||||
|
||||
const { openModal } = useModal();
|
||||
const [selectedPriceId, setSelectedPriceId] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (!currentMeteredPrice) return null;
|
||||
|
||||
const toOption = (meteredBillingPrice: MeteredBillingPrice) => {
|
||||
const price = formatNumber(meteredBillingPrice.tiers[0].flatAmount / 100);
|
||||
const credits = formatNumber(meteredBillingPrice.tiers[0].upTo, {
|
||||
abbreviate: true,
|
||||
decimals: 2,
|
||||
});
|
||||
|
||||
return {
|
||||
label: t`${credits} Credits - $${price}`,
|
||||
value: meteredBillingPrice.stripePriceId,
|
||||
};
|
||||
};
|
||||
|
||||
const options = [...meteredBillingPrices]
|
||||
.sort((a, b) => a.tiers[0].flatAmount - b.tiers[0].flatAmount)
|
||||
.map(toOption);
|
||||
|
||||
const selectedPrice = meteredBillingPrices.find(
|
||||
({ stripePriceId }) => stripePriceId === selectedPriceId,
|
||||
);
|
||||
|
||||
const isChanged =
|
||||
isDefined(selectedPriceId) &&
|
||||
selectedPriceId !== currentMeteredPrice?.stripePriceId;
|
||||
|
||||
const isUpgrade = () => {
|
||||
if (
|
||||
!isChanged ||
|
||||
!isDefined(selectedPrice) ||
|
||||
!isDefined(currentMeteredPrice)
|
||||
)
|
||||
return false;
|
||||
return (
|
||||
(selectedPrice.tiers as BillingPriceTiers)[0].flatAmount >
|
||||
(currentMeteredPrice.tiers as BillingPriceTiers)[0].flatAmount
|
||||
);
|
||||
};
|
||||
|
||||
const handleChange = (priceId: string) => {
|
||||
setSelectedPriceId(priceId);
|
||||
};
|
||||
|
||||
const confirmModalId = 'METERED_PRICE_CHANGE_CONFIRMATION_MODAL';
|
||||
|
||||
const handleOpenConfirm = () => {
|
||||
if (!isChanged || !selectedPrice) return;
|
||||
openModal(confirmModalId);
|
||||
};
|
||||
|
||||
const recurringInterval = getIntervalLabel(
|
||||
currentMeteredPrice?.recurringInterval === SubscriptionInterval.Month,
|
||||
);
|
||||
|
||||
const handleConfirmClick = async () => {
|
||||
if (!selectedPrice) return;
|
||||
try {
|
||||
const { data } = await setMeteredSubscriptionPrice({
|
||||
variables: { priceId: selectedPrice.stripePriceId },
|
||||
});
|
||||
if (
|
||||
isDefined(
|
||||
data?.setMeteredSubscriptionPrice.currentBillingSubscription,
|
||||
) &&
|
||||
isDefined(currentWorkspace)
|
||||
) {
|
||||
const newCurrentWorkspace = {
|
||||
...currentWorkspace,
|
||||
currentBillingSubscription:
|
||||
data.setMeteredSubscriptionPrice.currentBillingSubscription,
|
||||
billingSubscriptions:
|
||||
data?.setMeteredSubscriptionPrice.billingSubscriptions,
|
||||
};
|
||||
setCurrentWorkspace(newCurrentWorkspace);
|
||||
refetchMeteredProductsUsage();
|
||||
}
|
||||
enqueueSuccessSnackBar({ message: t`Price updated.` });
|
||||
setCurrentMeteredPrice(
|
||||
findOrThrow(
|
||||
meteredBillingPrices,
|
||||
({ stripePriceId }) => stripePriceId === selectedPrice.stripePriceId,
|
||||
),
|
||||
);
|
||||
setSelectedPriceId(undefined);
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Failed to update price.` });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<H2Title
|
||||
title={t`Credit Plan`}
|
||||
description={t`Number of new credits allocated every ${recurringInterval}`}
|
||||
/>
|
||||
<StyledRow>
|
||||
<StyledSelectContainer>
|
||||
<Select
|
||||
dropdownId="settings_billing-metered-price"
|
||||
options={options}
|
||||
value={selectedPriceId ?? currentMeteredPrice.stripePriceId}
|
||||
onChange={handleChange}
|
||||
disabled={isUpdating || isTrialing}
|
||||
description={
|
||||
isTrialing ? t`Please start your subscription first` : undefined
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSelectContainer>
|
||||
{isChanged && (
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
title={isUpgrade() ? t`Upgrade` : t`Downgrade`}
|
||||
onClick={handleOpenConfirm}
|
||||
variant="primary"
|
||||
isLoading={isUpdating}
|
||||
disabled={!isChanged}
|
||||
accent={isUpgrade() ? 'blue' : 'danger'}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
)}
|
||||
</StyledRow>
|
||||
<ConfirmationModal
|
||||
modalInstanceId={confirmModalId}
|
||||
title={isUpgrade() ? t`Confirm upgrade` : t`Confirm downgrade`}
|
||||
subtitle={t`Confirm changing your current credit plan.`}
|
||||
confirmButtonText={isUpgrade() ? t`Upgrade` : t`Downgrade`}
|
||||
confirmButtonAccent={isUpgrade() ? 'blue' : 'danger'}
|
||||
loading={isUpdating}
|
||||
onConfirmClick={handleConfirmClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+5
-5
@@ -17,7 +17,7 @@ import { H2Title } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
SetMeteredSubscriptionPriceDocument,
|
||||
SetResourceCreditSubscriptionPriceDocument,
|
||||
SubscriptionInterval,
|
||||
type BillingPriceLicensed,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@@ -75,7 +75,7 @@ export const ResourceCreditPriceSelector = ({
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const [setResourceCreditPrice, { loading: isUpdating }] = useMutation(
|
||||
SetMeteredSubscriptionPriceDocument,
|
||||
SetResourceCreditSubscriptionPriceDocument,
|
||||
);
|
||||
|
||||
const options = [...resourceCreditPrices]
|
||||
@@ -133,16 +133,16 @@ export const ResourceCreditPriceSelector = ({
|
||||
});
|
||||
if (
|
||||
isDefined(
|
||||
data?.setMeteredSubscriptionPrice.currentBillingSubscription,
|
||||
data?.setResourceCreditSubscriptionPrice.currentBillingSubscription,
|
||||
) &&
|
||||
isDefined(currentWorkspace)
|
||||
) {
|
||||
const newCurrentWorkspace = {
|
||||
...currentWorkspace,
|
||||
currentBillingSubscription:
|
||||
data.setMeteredSubscriptionPrice.currentBillingSubscription,
|
||||
data.setResourceCreditSubscriptionPrice.currentBillingSubscription,
|
||||
billingSubscriptions:
|
||||
data?.setMeteredSubscriptionPrice.billingSubscriptions,
|
||||
data?.setResourceCreditSubscriptionPrice.billingSubscriptions,
|
||||
};
|
||||
setCurrentWorkspace(newCurrentWorkspace);
|
||||
refetchResourceCreditUsage();
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CANCEL_SWITCH_METERED_PRICE = gql`
|
||||
mutation CancelSwitchMeteredPrice {
|
||||
cancelSwitchMeteredPrice {
|
||||
export const CANCEL_SWITCH_RESOURCE_CREDIT_PRICE = gql`
|
||||
mutation CancelSwitchResourceCreditPrice {
|
||||
cancelSwitchResourceCreditPrice {
|
||||
currentBillingSubscription {
|
||||
...CurrentBillingSubscriptionFragment
|
||||
}
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const SET_METERED_SUBSCRIPTION_PRICE = gql`
|
||||
mutation SetMeteredSubscriptionPrice($priceId: String!) {
|
||||
setMeteredSubscriptionPrice(priceId: $priceId) {
|
||||
export const SET_RESOURCE_CREDIT_SUBSCRIPTION_PRICE = gql`
|
||||
mutation SetResourceCreditSubscriptionPrice($priceId: String!) {
|
||||
setResourceCreditSubscriptionPrice(priceId: $priceId) {
|
||||
currentBillingSubscription {
|
||||
...CurrentBillingSubscriptionFragment
|
||||
}
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_METERED_PRODUCTS_USAGE = gql`
|
||||
query GetMeteredProductsUsage {
|
||||
getMeteredProductsUsage {
|
||||
export const GET_RESOURCE_CREDIT_USAGE = gql`
|
||||
query GetResourceCreditUsage {
|
||||
getResourceCreditUsage {
|
||||
productKey
|
||||
usedCredits
|
||||
grantedCredits
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered';
|
||||
|
||||
describe('useCurrentMetered', () => {
|
||||
it('should be a function', () => {
|
||||
expect(typeof useCurrentMetered).toBe('function');
|
||||
});
|
||||
});
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage';
|
||||
|
||||
describe('useGetWorkflowNodeExecutionUsage', () => {
|
||||
it('should be a function', () => {
|
||||
expect(typeof useGetWorkflowNodeExecutionUsage).toBe('function');
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useFormatPrices } from '@/settings/billing/hooks/useFormatPrices';
|
||||
import {
|
||||
BillingPlanKey,
|
||||
FeatureFlagKey,
|
||||
SubscriptionInterval,
|
||||
SubscriptionStatus,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@@ -11,10 +10,8 @@ import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { beautifyExactDate } from '~/utils/date-utils';
|
||||
import { useCurrentPlan } from '@/settings/billing/hooks/useCurrentPlan';
|
||||
import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered';
|
||||
import { useCurrentBillingFlags } from '@/settings/billing/hooks/useCurrentBillingFlags';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
export const useBillingWording = () => {
|
||||
const { t } = useLingui();
|
||||
@@ -28,12 +25,9 @@ export const useBillingWording = () => {
|
||||
|
||||
assertIsDefinedOrThrow(currentBillingSubscription);
|
||||
|
||||
const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED);
|
||||
|
||||
const { formatPrices } = useFormatPrices();
|
||||
|
||||
const { currentPlan } = useCurrentPlan();
|
||||
const { currentMeteredBillingPrice } = useCurrentMetered();
|
||||
|
||||
const subscriptionStatus = useSubscriptionStatus();
|
||||
|
||||
@@ -78,10 +72,7 @@ export const useBillingWording = () => {
|
||||
|
||||
const getCurrentIntervalLabel = () =>
|
||||
getIntervalLabelAsAdjectiveCapitalize(
|
||||
isV2
|
||||
? currentBillingSubscription.interval === SubscriptionInterval.Month
|
||||
: currentMeteredBillingPrice?.recurringInterval ===
|
||||
SubscriptionInterval.Month,
|
||||
currentBillingSubscription.interval === SubscriptionInterval.Month,
|
||||
);
|
||||
|
||||
const enterprisePrice =
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useCurrentPlan } from '@/settings/billing/hooks/useCurrentPlan';
|
||||
import type { MeteredBillingPrice } from '@/settings/billing/types/billing-price-tiers.type';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import {
|
||||
BillingProductKey,
|
||||
type SubscriptionInterval,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useCurrentMetered = () => {
|
||||
const { currentPlan } = useCurrentPlan();
|
||||
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
|
||||
assertIsDefinedOrThrow(currentWorkspace);
|
||||
|
||||
const getCurrentMeteredPricesByInterval = (
|
||||
interval?: SubscriptionInterval | null,
|
||||
): Array<MeteredBillingPrice> => {
|
||||
const meteredPrices = currentPlan.meteredProducts
|
||||
.map(({ prices }) => prices)
|
||||
.flat() as Array<MeteredBillingPrice>;
|
||||
|
||||
return interval
|
||||
? meteredPrices.filter((p) => p.recurringInterval === interval)
|
||||
: meteredPrices;
|
||||
};
|
||||
|
||||
const items =
|
||||
currentWorkspace.currentBillingSubscription?.billingSubscriptionItems;
|
||||
|
||||
const currentMeteredBillingSubscriptionItem =
|
||||
items?.find(
|
||||
(it) =>
|
||||
it.billingProduct.metadata?.['productKey'] ===
|
||||
BillingProductKey.WORKFLOW_NODE_EXECUTION,
|
||||
) ?? null;
|
||||
|
||||
const meteredPrices = getCurrentMeteredPricesByInterval();
|
||||
const currentMeteredBillingPrice = currentMeteredBillingSubscriptionItem
|
||||
? ((meteredPrices.find(
|
||||
(price) =>
|
||||
price.stripePriceId ===
|
||||
currentMeteredBillingSubscriptionItem.stripePriceId,
|
||||
) ?? null) as MeteredBillingPrice | null)
|
||||
: null;
|
||||
|
||||
return {
|
||||
currentMeteredBillingSubscriptionItem,
|
||||
currentMeteredBillingPrice,
|
||||
getCurrentMeteredPricesByInterval,
|
||||
};
|
||||
};
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
type SubscriptionInterval,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
// V2 hook — reads the RESOURCE_CREDIT subscription item and available pack prices
|
||||
// from resourceCreditProducts. Counterpart of useCurrentMetered for V2 workspaces.
|
||||
export const useCurrentResourceCredit = () => {
|
||||
const { currentPlan } = useCurrentPlan();
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { usePlans } from '@/settings/billing/hooks/usePlans';
|
||||
import type { MeteredBillingPrice } from '@/settings/billing/types/billing-price-tiers.type';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import {
|
||||
type BillingPlanKey,
|
||||
BillingProductKey,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useGetNextMeteredBillingPrice = (): MeteredBillingPrice | null => {
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
const { listPlans, isPlansLoaded } = usePlans();
|
||||
|
||||
const items =
|
||||
currentWorkspace?.currentBillingSubscription?.billingSubscriptionItems;
|
||||
const interval = currentWorkspace?.currentBillingSubscription?.interval;
|
||||
const planKey = currentWorkspace?.currentBillingSubscription?.metadata?.[
|
||||
'plan'
|
||||
] as BillingPlanKey | undefined;
|
||||
|
||||
if (!items || !planKey || !isPlansLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const plans = listPlans();
|
||||
const currentPlan = plans.find((plan) => plan.planKey === planKey);
|
||||
if (!currentPlan) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentMeteredItem = items.find(
|
||||
(item) =>
|
||||
item.billingProduct.metadata?.['productKey'] ===
|
||||
BillingProductKey.WORKFLOW_NODE_EXECUTION,
|
||||
);
|
||||
if (!currentMeteredItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const meteredPrices = currentPlan.meteredProducts.flatMap(
|
||||
({ prices }) => prices,
|
||||
) as Array<MeteredBillingPrice>;
|
||||
|
||||
const pricesForInterval = meteredPrices
|
||||
.filter((price) => price.recurringInterval === interval)
|
||||
.sort(
|
||||
(priceA, priceB) =>
|
||||
priceA.tiers[0].flatAmount - priceB.tiers[0].flatAmount,
|
||||
);
|
||||
|
||||
const currentIndex = pricesForInterval.findIndex(
|
||||
({ stripePriceId }) => stripePriceId === currentMeteredItem.stripePriceId,
|
||||
);
|
||||
|
||||
if (currentIndex === -1 || currentIndex === pricesForInterval.length - 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pricesForInterval[currentIndex + 1];
|
||||
};
|
||||
+4
-6
@@ -2,20 +2,18 @@ import { useQuery } from '@apollo/client/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
BillingProductKey,
|
||||
GetMeteredProductsUsageDocument,
|
||||
GetResourceCreditUsageDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
// V2 hook — same query shape as useGetWorkflowNodeExecutionUsage but finds
|
||||
// the RESOURCE_CREDIT product instead of WORKFLOW_NODE_EXECUTION.
|
||||
export const useGetResourceCreditUsage = () => {
|
||||
const { data, loading, refetch } = useQuery(GetMeteredProductsUsageDocument);
|
||||
const { data, loading, refetch } = useQuery(GetResourceCreditUsageDocument);
|
||||
|
||||
const refetchResourceCreditUsage = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
const isGetResourceCreditUsageQueryLoaded = () => {
|
||||
return isDefined(data?.getMeteredProductsUsage) && !loading;
|
||||
return isDefined(data?.getResourceCreditUsage) && !loading;
|
||||
};
|
||||
|
||||
const getResourceCreditUsage = () => {
|
||||
@@ -23,7 +21,7 @@ export const useGetResourceCreditUsage = () => {
|
||||
throw new Error('getResourceCreditUsage was not loaded');
|
||||
}
|
||||
|
||||
const usage = data.getMeteredProductsUsage.find(
|
||||
const usage = data.getResourceCreditUsage.find(
|
||||
(productUsage) =>
|
||||
productUsage.productKey === BillingProductKey.RESOURCE_CREDIT,
|
||||
);
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
BillingProductKey,
|
||||
GetMeteredProductsUsageDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { findOrThrow } from 'twenty-shared/utils';
|
||||
|
||||
export const useGetWorkflowNodeExecutionUsage = () => {
|
||||
const { data, loading, refetch } = useQuery(GetMeteredProductsUsageDocument);
|
||||
|
||||
const refetchMeteredProductsUsage = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
const isGetMeteredProductsUsageQueryLoaded = () => {
|
||||
return data?.getMeteredProductsUsage && !loading;
|
||||
};
|
||||
|
||||
const getMeteredProductUsage = () => {
|
||||
if (!data) {
|
||||
throw new Error('getMeteredProductUsage was not loaded');
|
||||
}
|
||||
|
||||
return data.getMeteredProductsUsage;
|
||||
};
|
||||
|
||||
const getWorkflowNodeExecutionUsage = () => {
|
||||
return findOrThrow(
|
||||
getMeteredProductUsage(),
|
||||
(productUsage) =>
|
||||
productUsage.productKey === BillingProductKey.WORKFLOW_NODE_EXECUTION,
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
refetchMeteredProductsUsage,
|
||||
isGetMeteredProductsUsageQueryLoaded:
|
||||
isGetMeteredProductsUsageQueryLoaded(),
|
||||
getWorkflowNodeExecutionUsage,
|
||||
};
|
||||
};
|
||||
+6
-20
@@ -1,11 +1,8 @@
|
||||
import { useNextBillingPhase } from '@/settings/billing/hooks/useNextBillingPhase';
|
||||
import { usePriceAndBillingUsageByPriceId } from '@/settings/billing/hooks/usePriceAndBillingUsageByPriceId';
|
||||
import { type MeteredBillingPrice } from '@/settings/billing/types/billing-price-tiers.type';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
BillingUsageType,
|
||||
FeatureFlagKey,
|
||||
type BillingPriceLicensed,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
@@ -13,7 +10,6 @@ export const useSplitPhaseItemsInPrices = () => {
|
||||
const { nextBillingPhase } = useNextBillingPhase();
|
||||
const { getPriceAndBillingUsageByPriceId } =
|
||||
usePriceAndBillingUsageByPriceId();
|
||||
const isV2 = useIsFeatureEnabled(FeatureFlagKey.IS_BILLING_V2_ENABLED);
|
||||
|
||||
const splitedPhaseItemsInPrices = (nextBillingPhase?.items ?? []).reduce(
|
||||
(acc, item) => {
|
||||
@@ -21,27 +17,17 @@ export const useSplitPhaseItemsInPrices = () => {
|
||||
item.price,
|
||||
);
|
||||
|
||||
if (isV2) {
|
||||
if (billingUsage === BillingUsageType.LICENSED) {
|
||||
const licensedPrice = price as BillingPriceLicensed;
|
||||
if (isDefined(licensedPrice.creditAmount)) {
|
||||
acc.nextResourceCreditPrice = licensedPrice;
|
||||
} else {
|
||||
acc.nextBasePrice = licensedPrice;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (billingUsage === BillingUsageType.LICENSED) {
|
||||
acc.nextBasePrice = price;
|
||||
}
|
||||
if (billingUsage === BillingUsageType.METERED) {
|
||||
acc.nextMeteredPrice = price as MeteredBillingPrice;
|
||||
if (billingUsage === BillingUsageType.LICENSED) {
|
||||
const licensedPrice = price as BillingPriceLicensed;
|
||||
if (isDefined(licensedPrice.creditAmount)) {
|
||||
acc.nextResourceCreditPrice = licensedPrice;
|
||||
} else {
|
||||
acc.nextBasePrice = licensedPrice;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as {
|
||||
nextMeteredPrice: MeteredBillingPrice | undefined;
|
||||
nextBasePrice: BillingPriceLicensed | undefined;
|
||||
nextResourceCreditPrice: BillingPriceLicensed | undefined;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user