172 lines
3.5 KiB
TypeScript
172 lines
3.5 KiB
TypeScript
import {
|
|
type AppEnv,
|
|
BillingType,
|
|
type Customer,
|
|
type Entity,
|
|
EntityExpand,
|
|
ErrCode,
|
|
type Feature,
|
|
type FullCustomerEntitlement,
|
|
type FullCustomerPrice,
|
|
type Organization,
|
|
type UsagePriceConfig,
|
|
} from "@autumn/shared";
|
|
import { StatusCodes } from "http-status-codes";
|
|
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
|
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
|
import { submitUsageToStripe } from "@/external/stripe/stripeMeterUtils.js";
|
|
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
|
import {
|
|
getBillingType,
|
|
roundUsage,
|
|
} from "@/internal/products/prices/priceUtils.js";
|
|
import RecaseError from "@/utils/errorUtils.js";
|
|
import { notNullish } from "@/utils/genUtils.js";
|
|
|
|
export const getLinkedCusEnt = ({
|
|
linkedFeature,
|
|
cusEnts,
|
|
}: {
|
|
linkedFeature: any;
|
|
cusEnts: any;
|
|
}) => {
|
|
// Get linked cus ent...
|
|
const linkedCusEnt = cusEnts.find(
|
|
(e: any) => e.entitlement.feature.id === linkedFeature.id,
|
|
);
|
|
|
|
if (!linkedCusEnt) {
|
|
return null;
|
|
}
|
|
|
|
return linkedCusEnt;
|
|
};
|
|
|
|
export const entityFeatureIdExists = ({
|
|
cusEnt,
|
|
}: {
|
|
cusEnt: FullCustomerEntitlement;
|
|
}) => {
|
|
const ent = cusEnt.entitlement;
|
|
return notNullish(ent.entity_feature_id);
|
|
};
|
|
|
|
export const entityMatchesFeature = ({
|
|
feature,
|
|
entity,
|
|
}: {
|
|
feature: Feature;
|
|
entity: Entity;
|
|
}) => {
|
|
return feature.id === entity.feature_id;
|
|
};
|
|
|
|
export const isLinkedToEntity = ({
|
|
cusEnt,
|
|
entity,
|
|
}: {
|
|
cusEnt: FullCustomerEntitlement;
|
|
entity: Entity;
|
|
}) => {
|
|
return cusEnt.entitlement.entity_feature_id === entity.feature_id;
|
|
};
|
|
|
|
export const removeEntityFromCusEnt = async ({
|
|
db,
|
|
cusEnt,
|
|
entity,
|
|
logger,
|
|
cusPrice,
|
|
customer,
|
|
org,
|
|
env,
|
|
}: {
|
|
db: DrizzleCli;
|
|
cusEnt: FullCustomerEntitlement;
|
|
entity: Entity;
|
|
logger: any;
|
|
cusPrice?: FullCustomerPrice;
|
|
customer: Customer;
|
|
org: Organization;
|
|
env: AppEnv;
|
|
}) => {
|
|
// isLinked
|
|
const isLinked = isLinkedToEntity({
|
|
cusEnt,
|
|
entity,
|
|
});
|
|
|
|
if (!isLinked) {
|
|
return;
|
|
}
|
|
|
|
const entitlement = cusEnt.entitlement;
|
|
console.log(
|
|
`Linked cus ent: ${entitlement.feature.id}, isLinked: ${isLinked}`,
|
|
);
|
|
|
|
// Delete cus ent ids
|
|
const newEntities = structuredClone(cusEnt.entities!);
|
|
|
|
// TODO: Send usage to stripe if cus price exists
|
|
const stripeCli = createStripeCli({
|
|
org,
|
|
env,
|
|
});
|
|
if (cusPrice) {
|
|
const config = cusPrice.price.config as UsagePriceConfig;
|
|
const billingType = getBillingType(config);
|
|
if (billingType === BillingType.UsageInArrear) {
|
|
let usage = -newEntities[entity.id]?.balance;
|
|
|
|
usage = roundUsage({
|
|
usage,
|
|
billingUnits: config.billing_units!,
|
|
});
|
|
|
|
await submitUsageToStripe({
|
|
price: cusPrice.price,
|
|
usage,
|
|
customer,
|
|
feature: entitlement.feature,
|
|
logger,
|
|
stripeCli,
|
|
});
|
|
}
|
|
}
|
|
|
|
delete newEntities[entity.id];
|
|
|
|
await CusEntService.update({
|
|
db,
|
|
id: cusEnt.id,
|
|
updates: {
|
|
entities: newEntities,
|
|
},
|
|
});
|
|
|
|
logger.info(
|
|
`Feature: ${entitlement.feature.id}, customer: ${cusEnt.customer_id}, deleted entities from cus ent`,
|
|
);
|
|
};
|
|
|
|
export const parseEntityExpand = (expand: string): EntityExpand[] => {
|
|
if (expand) {
|
|
const options = expand.split(",");
|
|
const result: EntityExpand[] = [];
|
|
for (const option of options) {
|
|
if (!Object.values(EntityExpand).includes(option as EntityExpand)) {
|
|
throw new RecaseError({
|
|
message: `Invalid expand option: ${option}`,
|
|
code: ErrCode.InvalidExpand,
|
|
statusCode: StatusCodes.BAD_REQUEST,
|
|
});
|
|
}
|
|
result.push(option as EntityExpand);
|
|
}
|
|
return result;
|
|
} else {
|
|
return [];
|
|
}
|
|
};
|