220 lines
5.5 KiB
TypeScript
220 lines
5.5 KiB
TypeScript
import {
|
|
type ApiBalanceBreakdownV1,
|
|
type ApiBalanceV1,
|
|
CheckExpand,
|
|
CustomerExpand,
|
|
cusEntsToAdjustment,
|
|
cusEntsToAllowance,
|
|
cusEntsToCurrentBalance,
|
|
cusEntsToMaxPurchase,
|
|
cusEntsToNextResetAt,
|
|
cusEntsToPlanId,
|
|
cusEntsToPrepaidQuantity,
|
|
cusEntsToReset,
|
|
cusEntsToRolloverBalance,
|
|
cusEntsToRolloverGranted,
|
|
cusEntsToRollovers,
|
|
cusEntsToRolloverUsage,
|
|
cusEntsToUsage,
|
|
cusEntToInvoiceOverage,
|
|
customerEntitlementToBalancePrice,
|
|
dbToApiFeatureV1,
|
|
expandIncludes,
|
|
expandPathIncludes,
|
|
type Feature,
|
|
FeatureType,
|
|
type FullCusEntWithFullCusProduct,
|
|
type FullCustomer,
|
|
getCusEntBalance,
|
|
isUnlimitedCusEnt,
|
|
nullish,
|
|
type SharedContext,
|
|
scopeExpandForCtx,
|
|
sumValues,
|
|
} from "@autumn/shared";
|
|
import { AllowanceType } from "@models/productModels/entModels/entModels.js";
|
|
import { Decimal } from "decimal.js";
|
|
import {
|
|
getBooleanApiBalance,
|
|
getUnlimitedApiBalance,
|
|
} from "./apiBalanceUtils.js";
|
|
|
|
const getUnlimitedAndUsageAllowed = ({
|
|
cusEnts,
|
|
internalFeatureId,
|
|
includeUsageLimit = true,
|
|
}: {
|
|
cusEnts: FullCusEntWithFullCusProduct[];
|
|
internalFeatureId: string;
|
|
includeUsageLimit?: boolean;
|
|
}) => {
|
|
const unlimited = cusEnts.some(
|
|
(cusEnt) =>
|
|
cusEnt.internal_feature_id === internalFeatureId &&
|
|
(cusEnt.entitlement.allowance_type === AllowanceType.Unlimited ||
|
|
cusEnt.unlimited),
|
|
);
|
|
|
|
const usageAllowed = cusEnts.some(
|
|
(cusEnt) =>
|
|
cusEnt.internal_feature_id === internalFeatureId &&
|
|
cusEnt.usage_allowed &&
|
|
(includeUsageLimit ? nullish(cusEnt.entitlement.usage_limit) : true),
|
|
);
|
|
|
|
return { unlimited, usageAllowed };
|
|
};
|
|
|
|
const getApiBalanceBreakdownItem = ({
|
|
ctx,
|
|
fullCus,
|
|
customerEntitlement,
|
|
}: {
|
|
ctx: SharedContext;
|
|
fullCus: FullCustomer;
|
|
customerEntitlement: FullCusEntWithFullCusProduct;
|
|
}): ApiBalanceBreakdownV1 => {
|
|
const entityId = fullCus.entity?.id ?? fullCus.entity?.internal_id;
|
|
const planId = cusEntsToPlanId({ cusEnts: [customerEntitlement] });
|
|
const allowance = cusEntsToAllowance({
|
|
cusEnts: [customerEntitlement],
|
|
entityId,
|
|
});
|
|
const adjustment = cusEntsToAdjustment({
|
|
cusEnts: [customerEntitlement],
|
|
entityId,
|
|
});
|
|
const includedGrant = new Decimal(allowance).add(adjustment).toNumber();
|
|
const prepaidGrant = cusEntsToPrepaidQuantity({
|
|
cusEnts: [customerEntitlement],
|
|
sumAcrossEntities: nullish(entityId),
|
|
});
|
|
const remaining = cusEntsToCurrentBalance({
|
|
cusEnts: [customerEntitlement],
|
|
entityId,
|
|
});
|
|
const usage = cusEntsToUsage({ cusEnts: [customerEntitlement], entityId });
|
|
const unlimited = isUnlimitedCusEnt(customerEntitlement);
|
|
const reset = cusEntsToReset({ cusEnts: [customerEntitlement] });
|
|
const price = customerEntitlementToBalancePrice({ customerEntitlement });
|
|
const overage = cusEntToInvoiceOverage({
|
|
cusEnt: customerEntitlement,
|
|
entityId,
|
|
});
|
|
|
|
const includePrice = expandPathIncludes({
|
|
expand: ctx.expand,
|
|
includes: ["breakdown.price"],
|
|
});
|
|
|
|
return {
|
|
object: "balance_breakdown",
|
|
id: customerEntitlement.external_id ?? customerEntitlement.id,
|
|
plan_id: planId,
|
|
included_grant: includedGrant,
|
|
prepaid_grant: prepaidGrant,
|
|
remaining,
|
|
usage,
|
|
unlimited,
|
|
reset,
|
|
price,
|
|
expires_at: customerEntitlement.expires_at,
|
|
overage,
|
|
};
|
|
};
|
|
|
|
export const getApiBalance = ({
|
|
ctx,
|
|
fullCus,
|
|
cusEnts,
|
|
feature,
|
|
}: {
|
|
ctx: SharedContext;
|
|
fullCus: FullCustomer;
|
|
cusEnts: FullCusEntWithFullCusProduct[];
|
|
feature: Feature;
|
|
}): { data: ApiBalanceV1 } => {
|
|
const entityId = fullCus.entity?.id ?? fullCus.entity?.internal_id;
|
|
|
|
const apiFeature = expandIncludes({
|
|
expand: ctx.expand,
|
|
includes: [
|
|
CheckExpand.BalanceFeature,
|
|
CustomerExpand.BalancesFeature,
|
|
"feature",
|
|
],
|
|
})
|
|
? dbToApiFeatureV1({ ctx, dbFeature: feature })
|
|
: undefined;
|
|
|
|
if (feature.type === FeatureType.Boolean) {
|
|
return {
|
|
data: getBooleanApiBalance({
|
|
cusEnts,
|
|
apiFeature,
|
|
}),
|
|
};
|
|
}
|
|
|
|
const { unlimited, usageAllowed } = getUnlimitedAndUsageAllowed({
|
|
cusEnts,
|
|
internalFeatureId: feature.internal_id,
|
|
includeUsageLimit: false,
|
|
});
|
|
|
|
if (unlimited) {
|
|
return {
|
|
data: getUnlimitedApiBalance({ apiFeature, cusEnts }),
|
|
};
|
|
}
|
|
|
|
const totalUnused = sumValues(
|
|
cusEnts.map((cusEnt) => {
|
|
const { unused } = getCusEntBalance({ cusEnt, entityId });
|
|
return unused;
|
|
}),
|
|
);
|
|
|
|
const breakdownItems = cusEnts.map((cusEnt) =>
|
|
getApiBalanceBreakdownItem({ ctx, fullCus, customerEntitlement: cusEnt }),
|
|
);
|
|
const totalGranted = sumValues(
|
|
breakdownItems.map((item) =>
|
|
new Decimal(item.included_grant).add(item.prepaid_grant).toNumber(),
|
|
),
|
|
);
|
|
const totalUsage = sumValues(breakdownItems.map((item) => item.usage));
|
|
const totalRemaining = sumValues(
|
|
breakdownItems.map((item) => item.remaining),
|
|
);
|
|
const totalMaxPurchase = cusEntsToMaxPurchase({ cusEnts, entityId });
|
|
const nextResetAt = cusEntsToNextResetAt({ cusEnts });
|
|
const totalRollovers = cusEntsToRollovers({ cusEnts, entityId });
|
|
const totalRolloverGranted = cusEntsToRolloverGranted({ cusEnts, entityId });
|
|
const totalRolloverBalance = cusEntsToRolloverBalance({ cusEnts, entityId });
|
|
const totalRolloverUsage = cusEntsToRolloverUsage({ cusEnts, entityId });
|
|
|
|
return {
|
|
data: {
|
|
object: "balance",
|
|
feature_id: feature.id,
|
|
feature: apiFeature,
|
|
granted: new Decimal(totalGranted).add(totalRolloverGranted).toNumber(),
|
|
remaining: new Decimal(totalRemaining)
|
|
.add(totalRolloverBalance)
|
|
.add(totalUnused)
|
|
.toNumber(),
|
|
usage: new Decimal(totalUsage)
|
|
.add(totalRolloverUsage)
|
|
.sub(totalUnused)
|
|
.toNumber(),
|
|
unlimited,
|
|
overage_allowed: usageAllowed ?? false,
|
|
max_purchase: totalMaxPurchase,
|
|
next_reset_at: nextResetAt,
|
|
breakdown: breakdownItems,
|
|
rollovers: totalRollovers,
|
|
},
|
|
};
|
|
};
|