chore: cleaned up current api plan files to prepare for new schema
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
apiPlanItem,
|
||||
type CreateBalanceParams,
|
||||
enrichEntitlementWithFeature,
|
||||
type Feature,
|
||||
type FullCustomer,
|
||||
planFeaturesToItems,
|
||||
} from "@autumn/shared";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||
import { initCusEntitlement } from "@/internal/customers/add-product/initCusEnt";
|
||||
@@ -21,13 +21,13 @@ export const prepareNewBalanceForInsertion = async ({
|
||||
fullCustomer: FullCustomer;
|
||||
params: CreateBalanceParams;
|
||||
}) => {
|
||||
const inputAsItem = planFeaturesToItems({
|
||||
features: [feature],
|
||||
planFeatures: [params],
|
||||
const inputAsItem = apiPlanItem.map.v0ToProductItem({
|
||||
ctx,
|
||||
planItem: params,
|
||||
});
|
||||
|
||||
const { ent: newEntitlement } = toFeature({
|
||||
item: inputAsItem[0],
|
||||
item: inputAsItem,
|
||||
orgId: ctx.org.id,
|
||||
isCustom: true,
|
||||
internalFeatureId: feature.internal_id!,
|
||||
|
||||
@@ -93,6 +93,7 @@ const handleAllowanceUsed = async ({
|
||||
legacyData: newLegacyData,
|
||||
}),
|
||||
feature: dbToApiFeatureV1({
|
||||
ctx,
|
||||
dbFeature: feature,
|
||||
targetVersion: ctx.apiVersion,
|
||||
}),
|
||||
@@ -157,6 +158,7 @@ export const handleThresholdReached = async ({
|
||||
legacyData: newLegacyData,
|
||||
}),
|
||||
feature: dbToApiFeatureV1({
|
||||
ctx,
|
||||
dbFeature: feature,
|
||||
targetVersion: ctx.apiVersion,
|
||||
}),
|
||||
|
||||
@@ -135,7 +135,7 @@ export const getApiBalance = ({
|
||||
expand: ctx.expand,
|
||||
includes: [CheckExpand.BalanceFeature, CusExpand.BalancesFeature],
|
||||
})
|
||||
? dbToApiFeatureV1({ dbFeature: feature })
|
||||
? dbToApiFeatureV1({ ctx, dbFeature: feature })
|
||||
: undefined;
|
||||
|
||||
// 1. If feature is boolean
|
||||
|
||||
@@ -37,7 +37,7 @@ export const handleCreateFeature = createRoute({
|
||||
}
|
||||
|
||||
return c.json(
|
||||
dbToApiFeatureV1({ dbFeature, targetVersion: ctx.apiVersion }),
|
||||
dbToApiFeatureV1({ ctx, dbFeature, targetVersion: ctx.apiVersion }),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ export const handleGetFeature = createRoute({
|
||||
}
|
||||
|
||||
const apiFeature = dbToApiFeatureV1({
|
||||
ctx,
|
||||
dbFeature: feature,
|
||||
targetVersion: ctx.apiVersion,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,11 @@ export const handleListFeatures = createRoute({
|
||||
const ctx = c.get("ctx");
|
||||
|
||||
const apiFeatures = ctx.features.map((feature) =>
|
||||
dbToApiFeatureV1({ dbFeature: feature, targetVersion: ctx.apiVersion }),
|
||||
dbToApiFeatureV1({
|
||||
ctx,
|
||||
dbFeature: feature,
|
||||
targetVersion: ctx.apiVersion,
|
||||
}),
|
||||
);
|
||||
|
||||
return c.json({ list: apiFeatures });
|
||||
|
||||
@@ -65,6 +65,7 @@ export const handleUpdateFeature = createRoute({
|
||||
|
||||
return c.json(
|
||||
dbToApiFeatureV1({
|
||||
ctx,
|
||||
dbFeature: updatedFeature,
|
||||
targetVersion: ctx.apiVersion,
|
||||
}),
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
AffectedResource,
|
||||
type ApiPlan,
|
||||
ApiVersion,
|
||||
apiPlan,
|
||||
applyResponseVersionChanges,
|
||||
CreatePlanParamsSchema,
|
||||
type CreateProductV2Params,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
type FullProduct,
|
||||
type Price,
|
||||
ProductAlreadyExistsError,
|
||||
planToProductV2,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
handleNewFreeTrial,
|
||||
validateOneOffTrial,
|
||||
} from "../free-trials/freeTrialUtils.js";
|
||||
|
||||
import { ProductService } from "../ProductService.js";
|
||||
import { handleNewProductItems } from "../product-items/productItemUtils/handleNewProductItems.js";
|
||||
import { getPlanResponse } from "../productUtils/productResponseUtils/getPlanResponse.js";
|
||||
@@ -45,7 +46,7 @@ export const handleCreatePlan = createRoute({
|
||||
|
||||
const v1_2Body = (
|
||||
ctx.apiVersion.gte(ApiVersion.V2_0)
|
||||
? planToProductV2({ plan: body, features: ctx.features })
|
||||
? apiPlan.map.v0ToProductV2({ ctx, plan: body })
|
||||
: body
|
||||
) as CreateProductV2Params;
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {
|
||||
ApiVersion,
|
||||
type CreatePlanParams,
|
||||
apiPlan,
|
||||
ProductNotFoundError,
|
||||
type ProductV2,
|
||||
planToProductV2,
|
||||
productsAreSame,
|
||||
} from "@autumn/shared";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler";
|
||||
@@ -38,9 +37,9 @@ export const handlePlanHasCustomersV2 = createRoute({
|
||||
// V2.0+ (CLI): body is CreatePlanParams, convert to ProductV2
|
||||
// < V2.0 (Dashboard): body is already ProductV2
|
||||
const productV2 = apiVersion.gte(ApiVersion.V2_0)
|
||||
? (planToProductV2({
|
||||
plan: body as CreatePlanParams,
|
||||
features,
|
||||
? (apiPlan.map.v0ToProductV2({
|
||||
ctx,
|
||||
plan: body,
|
||||
}) as ProductV2)
|
||||
: (body as ProductV2);
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
type ApiPlan,
|
||||
ApiVersion,
|
||||
ApiVersionClass,
|
||||
apiPlan,
|
||||
applyResponseVersionChanges,
|
||||
type FreeTrial,
|
||||
mapToProductV2,
|
||||
notNullish,
|
||||
ProductNotFoundError,
|
||||
type ProductV2,
|
||||
planToProductV2,
|
||||
productsAreSame,
|
||||
RecaseError,
|
||||
UpdatePlanParamsSchema,
|
||||
@@ -59,7 +59,10 @@ export const handleUpdatePlan = createRoute({
|
||||
// V1.2 clients already send ProductV2, no conversion needed
|
||||
|
||||
const v1_2Body = ctx.apiVersion.gte(new ApiVersionClass(ApiVersion.V2_0))
|
||||
? planToProductV2({ plan: body as ApiPlan, features: ctx.features })
|
||||
? (apiPlan.map.v0ToProductV2({
|
||||
ctx,
|
||||
plan: body,
|
||||
}) as UpdateProductV2Params)
|
||||
: (body as UpdateProductV2Params);
|
||||
|
||||
const [fullProduct, rewardPrograms, _defaultProds] = await Promise.all([
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type ApiFreeTrial,
|
||||
ApiFreeTrialSchema,
|
||||
ApiProductItemSchema,
|
||||
ApiProductItemV0Schema,
|
||||
ApiProductPropertiesSchema,
|
||||
ApiProductSchema,
|
||||
AttachScenario,
|
||||
@@ -71,7 +71,7 @@ export const getProductItemResponse = ({
|
||||
}
|
||||
|
||||
const feature = features.find((f) => f.id === item.feature_id);
|
||||
return ApiProductItemSchema.parse({
|
||||
return ApiProductItemV0Schema.parse({
|
||||
type,
|
||||
...item,
|
||||
feature: feature ? toApiFeature({ feature }) : null,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { EntityDataSchema } from "../../../common/entityData.js";
|
||||
import { ApiCusProductV3Schema } from "../../../customers/cusPlans/previousVersions/apiCusProductV3.js";
|
||||
import {
|
||||
ApiCusFeatureV3Schema,
|
||||
ApiProductItemSchema,
|
||||
ApiProductItemV0Schema,
|
||||
} from "../../../models.js";
|
||||
import { balancesOpenApi } from "./balancesOpenApi1.2.0.js";
|
||||
import { coreOpenApi } from "./coreOpenApi.js";
|
||||
@@ -54,7 +54,7 @@ const OPENAPI_1_2_0 = createDocument(
|
||||
description: "Customer feature object returned by the API",
|
||||
}),
|
||||
Product: ApiProductWithMeta,
|
||||
ProductItem: ApiProductItemSchema,
|
||||
ProductItem: ApiProductItemV0Schema,
|
||||
Feature: ApiFeatureWithMeta,
|
||||
Entity: ApiEntityWithMeta,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { ApiPlanFeatureWithMeta } from "@api/products/planFeature/apiPlanFeature.js";
|
||||
import { ApiPlanItemV0WithMeta } from "@api/products/items/apiPlanItemV0.js";
|
||||
import yaml from "yaml";
|
||||
import { createDocument } from "zod-openapi";
|
||||
import { CustomerDataSchema } from "../common/customerData.js";
|
||||
@@ -41,7 +41,7 @@ const openapi2_0 = createDocument(
|
||||
CustomerData: CustomerDataSchema,
|
||||
EntityData: EntityDataSchema,
|
||||
Plan: ApiPlanWithMeta,
|
||||
PlanFeature: ApiPlanFeatureWithMeta,
|
||||
PlanFeature: ApiPlanItemV0WithMeta,
|
||||
Customer: ApiCustomerSchema,
|
||||
BaseCustomer: BaseApiCustomerSchema,
|
||||
Entity: ApiEntityWithMeta,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
CreatePlanParamsSchema,
|
||||
ListPlansQuerySchema,
|
||||
UpdatePlanParamsSchema,
|
||||
} from "../products/planOpModels.js";
|
||||
} from "../products/crud/planOpModels.js";
|
||||
|
||||
export const ApiPlanWithMeta = ApiPlanSchema.meta({
|
||||
id: "Plan",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProductSchema } from "@api/products/previousVersions/apiProduct";
|
||||
import { z } from "zod/v4";
|
||||
import { ApiProductSchema } from "../../models.js";
|
||||
|
||||
// Check Feature Preview Schemas
|
||||
export const CheckFeaturePreviewSchema = z.object({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
|
||||
import { ApiProductItemV0Schema } from "@api/products/items/previousVersions/apiProductItemV0.js";
|
||||
import { ApiProductSchema } from "@api/products/previousVersions/apiProduct.js";
|
||||
import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js";
|
||||
import { z } from "zod/v4";
|
||||
@@ -7,7 +7,7 @@ import { z } from "zod/v4";
|
||||
export const CheckoutLineV0Schema = z.object({
|
||||
description: z.string(),
|
||||
amount: z.number(),
|
||||
item: ApiProductItemSchema.nullish(),
|
||||
item: ApiProductItemV0Schema.nullish(),
|
||||
});
|
||||
|
||||
export const CheckoutResponseV0Schema = z.object({
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { CreatePlanItemParamsV0Schema } from "@api/products/items/crud/createPlanItemV0Params.js";
|
||||
import { z } from "zod/v4";
|
||||
import { ApiFreeTrialV2Schema } from "../../models.js";
|
||||
import { UpdatePlanFeatureSchema } from "../../products/planFeature/planFeatureOpModels.js";
|
||||
import { PlanPriceSchema } from "../../products/planOpModels.js";
|
||||
import { PlanPriceSchema } from "../../products/crud/planOpModels.js";
|
||||
|
||||
export const PlanOverrideSchema = z
|
||||
.object({
|
||||
price: PlanPriceSchema.optional(),
|
||||
features: z.array(UpdatePlanFeatureSchema).optional(),
|
||||
features: z.array(CreatePlanItemParamsV0Schema).optional(),
|
||||
free_trial: ApiFreeTrialV2Schema.nullable().optional(),
|
||||
})
|
||||
.refine(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
|
||||
import type { ApiProductItemV0Schema } from "@api/products/items/previousVersions/apiProductItemV0.js";
|
||||
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
|
||||
import {
|
||||
AffectedResource,
|
||||
@@ -25,7 +25,7 @@ import { ApiCusProductV2Schema } from "../previousVersions/apiCusProductV2.js";
|
||||
const transformItemToPrice = ({
|
||||
item,
|
||||
}: {
|
||||
item: z.infer<typeof ApiProductItemSchema>;
|
||||
item: z.infer<typeof ApiProductItemV0Schema>;
|
||||
}) => {
|
||||
const singleTier =
|
||||
isPriceItem(item) ||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import type { ApiProductItem } from "@api/models.js";
|
||||
import { type ApiProductItem, apiPlan } from "@api/models.js";
|
||||
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
|
||||
import {
|
||||
AffectedResource,
|
||||
defineVersionChange,
|
||||
type VersionContext,
|
||||
} from "@api/versionUtils/versionChangeUtils/VersionChange.js";
|
||||
import { CusProductStatus } from "@models/cusProductModels/cusProductEnums.js";
|
||||
import { convertPlanToItems } from "@utils/planFeatureUtils/planToItems.js";
|
||||
import { getProductItemResponse } from "@utils/productV2Utils/productItemUtils/getProductItemRes.js";
|
||||
import type { z } from "zod/v4";
|
||||
import type { SharedContext } from "../../../../types/sharedContext.js";
|
||||
import {
|
||||
type ApiSubscription,
|
||||
ApiSubscriptionSchema,
|
||||
@@ -27,7 +26,7 @@ export function transformSubscriptionToCusProductV3({
|
||||
}: {
|
||||
input: z.infer<typeof ApiSubscriptionSchema>;
|
||||
legacyData?: z.infer<typeof CusProductLegacyDataSchema>;
|
||||
ctx: VersionContext;
|
||||
ctx: SharedContext;
|
||||
}): z.infer<typeof ApiCusProductV3Schema> {
|
||||
const cusPlanToCusProductV3Status = (plan: ApiSubscription) => {
|
||||
if (plan.status === CusProductStatus.Active) {
|
||||
@@ -46,13 +45,10 @@ export function transformSubscriptionToCusProductV3({
|
||||
|
||||
let items: ApiProductItem[] | null = null;
|
||||
|
||||
// Get features
|
||||
|
||||
// console.log("Features:", ctx.features);
|
||||
if (input.plan && ctx.features) {
|
||||
const productItems = convertPlanToItems({
|
||||
const productItems = apiPlan.map.v0ToProductItems({
|
||||
ctx,
|
||||
plan: input.plan,
|
||||
features: ctx.features,
|
||||
});
|
||||
|
||||
const itemResponses = productItems.map((item) =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
|
||||
import { ApiProductItemV0Schema } from "@api/products/items/previousVersions/apiProductItemV0.js";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,7 @@ export const ApiCusProductV2Schema = z.object({
|
||||
|
||||
entity_id: z.string().nullish(),
|
||||
|
||||
items: z.array(ApiProductItemSchema).nullish(),
|
||||
items: z.array(ApiProductItemV0Schema).nullish(),
|
||||
|
||||
quantity: z.number().optional(),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
|
||||
import { ApiProductItemV0Schema } from "@api/products/items/previousVersions/apiProductItemV0.js";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const ApiCusProductV3Schema = z.object({
|
||||
@@ -62,7 +62,7 @@ export const ApiCusProductV3Schema = z.object({
|
||||
example: "entity_1234abcd",
|
||||
}),
|
||||
items: z
|
||||
.array(ApiProductItemSchema)
|
||||
.array(ApiProductItemV0Schema)
|
||||
.nullish()
|
||||
.meta({
|
||||
description: "Array of product items defining the features and pricing",
|
||||
|
||||
@@ -41,13 +41,7 @@ export * from "./features/prevVersions/featureV0OpModels.js";
|
||||
export * from "./others/apiDiscount.js";
|
||||
export * from "./others/apiInvoice/apiInvoiceV1.js";
|
||||
// Product
|
||||
export * from "./products/apiFreeTrial.js";
|
||||
export * from "./products/apiPlan.js";
|
||||
export * from "./products/planFeature/previousVersions/apiProductItem.js";
|
||||
export * from "./products/planOpModels.js";
|
||||
export * from "./products/previousVersions/apiProduct.js";
|
||||
export * from "./products/productOpModels.js";
|
||||
export * from "./products/productsOpenApi.js";
|
||||
export * from "./products/index.js";
|
||||
// Referrals
|
||||
export * from "./referrals/apiReferralCode.js";
|
||||
export * from "./referrals/referralOpModels.js";
|
||||
@@ -100,4 +94,3 @@ export * from "./others/apiInvoice/apiInvoiceItem.js";
|
||||
export * from "./others/apiInvoice/apiInvoiceV1.js";
|
||||
// Models
|
||||
export * from "./platform/platformModels.js";
|
||||
export * from "./products/planLegacyData.js";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FreeTrialDuration } from "@models/productModels/freeTrialModels/freeTri
|
||||
import { BillingInterval } from "@models/productModels/intervals/billingInterval.js";
|
||||
import { z } from "zod/v4";
|
||||
import { DisplaySchema } from "./components/display.js";
|
||||
import { ApiPlanFeatureSchema } from "./planFeature/apiPlanFeature.js";
|
||||
import { ApiPlanItemV0Schema } from "./items/apiPlanItemV0.js";
|
||||
|
||||
export const ApiFreeTrialV2Schema = z.object({
|
||||
duration_type: z.enum(FreeTrialDuration),
|
||||
@@ -34,7 +34,7 @@ export const ApiPlanSchema = z.object({
|
||||
})
|
||||
.nullable(),
|
||||
|
||||
features: z.array(ApiPlanFeatureSchema),
|
||||
features: z.array(ApiPlanItemV0Schema),
|
||||
free_trial: ApiFreeTrialV2Schema.nullable().optional(),
|
||||
|
||||
// Misc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { planV0ToProductItems } from "@api/products/mappers/planV0ToProductItems.js";
|
||||
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
|
||||
import {
|
||||
AffectedResource,
|
||||
@@ -5,13 +6,10 @@ import {
|
||||
} from "@api/versionUtils/versionChangeUtils/VersionChange.js";
|
||||
import type { PriceItem } from "@models/productV2Models/productItemModels/priceItem.js";
|
||||
import { isPriceItem } from "@utils/index.js";
|
||||
import { convertPlanToItems } from "@utils/planFeatureUtils/planToItems.js";
|
||||
import type { SharedContext } from "../../../types/sharedContext.js";
|
||||
import { productV2ToProperties } from "../../../utils/productV2Utils/productV2ToProperties.js";
|
||||
import { type ApiPlan, ApiPlanSchema } from "../apiPlan.js";
|
||||
import {
|
||||
type PlanLegacyData,
|
||||
PlanLegacyDataSchema,
|
||||
} from "../planLegacyData.js";
|
||||
import { PlanLegacyDataSchema } from "../planLegacyData.js";
|
||||
import {
|
||||
type ApiProduct,
|
||||
ApiProductSchema,
|
||||
@@ -65,25 +63,26 @@ export const V1_2_ProductChanges = defineVersionChange({
|
||||
|
||||
// Response: V2 Plan -> V1.2 ProductV2
|
||||
transformResponse: ({
|
||||
ctx,
|
||||
input,
|
||||
legacyData,
|
||||
}: {
|
||||
ctx: SharedContext;
|
||||
input: ApiPlan;
|
||||
legacyData?: PlanLegacyData;
|
||||
}): ApiProduct => {
|
||||
// Convert plan to items using shared utility (handles base price + features)
|
||||
|
||||
const items = convertPlanToItems({
|
||||
const productItems = planV0ToProductItems({
|
||||
ctx,
|
||||
plan: input,
|
||||
features: legacyData?.features || [],
|
||||
}).filter((x) => {
|
||||
if (isPriceItem(x)) {
|
||||
const y: PriceItem = x as unknown as PriceItem;
|
||||
return y.price > 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
})
|
||||
.filter((x) => {
|
||||
if (isPriceItem(x)) {
|
||||
const y: PriceItem = x as unknown as PriceItem;
|
||||
return y.price > 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
const productV2 = {
|
||||
id: input.id,
|
||||
@@ -96,7 +95,7 @@ export const V1_2_ProductChanges = defineVersionChange({
|
||||
archived: input.archived,
|
||||
version: input.version,
|
||||
created_at: input.created_at,
|
||||
items: items, // Already includes base price and features
|
||||
items: productItems, // Already includes base price and features
|
||||
free_trial: input.free_trial
|
||||
? {
|
||||
duration: input.free_trial.duration_type,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BillingInterval } from "@models/productModels/intervals/billingInterval.js";
|
||||
import { idRegex } from "@utils/utils.js";
|
||||
import { z } from "zod/v4";
|
||||
import { ApiFreeTrialV2Schema } from "./apiPlan.js";
|
||||
import { UpdatePlanFeatureSchema } from "./planFeature/planFeatureOpModels.js";
|
||||
import { ApiFreeTrialV2Schema } from "../apiPlan.js";
|
||||
import { CreatePlanItemParamsV0Schema } from "../items/crud/createPlanItemV0Params.js";
|
||||
|
||||
export const PlanPriceSchema = z.object({
|
||||
amount: z.number(),
|
||||
@@ -25,40 +25,17 @@ export const CreatePlanParamsSchema = z.object({
|
||||
.object({
|
||||
amount: z.number(),
|
||||
interval: z.enum(BillingInterval),
|
||||
interval_count: z.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
features: z.array(UpdatePlanFeatureSchema).optional(),
|
||||
features: z.array(CreatePlanItemParamsV0Schema).optional(),
|
||||
free_trial: ApiFreeTrialV2Schema.nullable().optional(),
|
||||
});
|
||||
|
||||
export const UpdatePlanParamsSchema = z.object({
|
||||
id: z.string().nonempty().regex(idRegex).optional(),
|
||||
group: z.string().default("").optional(),
|
||||
|
||||
name: z
|
||||
.string()
|
||||
.refine((val) => val.length > 0, {
|
||||
message: "name must be a non-empty string",
|
||||
})
|
||||
.optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
|
||||
export const UpdatePlanParamsSchema = CreatePlanParamsSchema.partial().extend({
|
||||
version: z.number().optional(),
|
||||
|
||||
add_on: z.boolean().default(false).optional(),
|
||||
default: z.boolean().default(false).optional(),
|
||||
archived: z.boolean().default(false).optional(),
|
||||
|
||||
price: z
|
||||
.object({
|
||||
amount: z.number().optional(),
|
||||
interval: z.enum(BillingInterval).optional(),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
features: z.array(UpdatePlanFeatureSchema).optional(),
|
||||
free_trial: ApiFreeTrialV2Schema.nullish(),
|
||||
});
|
||||
|
||||
export const UpdatePlanQuerySchema = z.object({
|
||||
22
shared/api/products/index.ts
Normal file
22
shared/api/products/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { planV0ToProductItems } from "@api/products/mappers/planV0ToProductItems.js";
|
||||
import { planV0ToProductV2 } from "@api/products/mappers/planV0ToProductV2.js";
|
||||
|
||||
export * from "./apiFreeTrial.js";
|
||||
export * from "./apiPlan.js";
|
||||
export * from "./components/display.js";
|
||||
export * from "./crud/planOpModels.js";
|
||||
export * from "./items/index.js";
|
||||
export * from "./mappers/index.js";
|
||||
export * from "./planLegacyData.js";
|
||||
export * from "./previousVersions/apiProduct.js";
|
||||
export * from "./productOpModels.js";
|
||||
export * from "./productsOpenApi.js";
|
||||
// Note: V1.2_ProductChanges.js is NOT exported here to avoid circular deps
|
||||
// It's only imported directly by versionChangeRegistry.ts
|
||||
|
||||
export const apiPlan = {
|
||||
map: {
|
||||
v0ToProductItems: planV0ToProductItems,
|
||||
v0ToProductV2: planV0ToProductV2,
|
||||
},
|
||||
};
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { ApiFeatureV0Schema } from "../../features/prevVersions/apiFeatureV0.js";
|
||||
import { DisplaySchema } from "../components/display.js";
|
||||
|
||||
export const ApiPlanFeatureSchema = z
|
||||
export const ApiPlanItemV0Schema = z
|
||||
.object({
|
||||
feature_id: z.string(),
|
||||
feature: ApiFeatureV0Schema.optional(),
|
||||
@@ -89,9 +89,9 @@ export const ApiPlanFeatureSchema = z
|
||||
}
|
||||
});
|
||||
|
||||
export type ApiPlanFeature = z.infer<typeof ApiPlanFeatureSchema>;
|
||||
export type ApiPlanItemV0 = z.infer<typeof ApiPlanItemV0Schema>;
|
||||
|
||||
export const ApiPlanFeatureWithMeta = ApiPlanFeatureSchema.meta({
|
||||
export const ApiPlanItemV0WithMeta = ApiPlanItemV0Schema.meta({
|
||||
id: "PlanFeature",
|
||||
description: "Plan feature object returned by the API",
|
||||
example: {
|
||||
@@ -1,15 +1,15 @@
|
||||
import { BillingInterval } from "@models/productModels/intervals/billingInterval.js";
|
||||
import { ResetInterval } from "@models/productModels/intervals/resetInterval.js";
|
||||
import { UsageTierSchema } from "@models/productModels/priceModels/priceConfig/usagePriceConfig.js";
|
||||
import { UsageModel } from "@models/productV2Models/productItemModels/productItemModels.js";
|
||||
import { z } from "zod/v4";
|
||||
import { RolloverExpiryDurationType } from "../../../models/productModels/durationTypes/rolloverExpiryDurationType.js";
|
||||
import { RolloverExpiryDurationType } from "@models/productModels/durationTypes/rolloverExpiryDurationType";
|
||||
import { BillingInterval } from "@models/productModels/intervals/billingInterval";
|
||||
import { ResetInterval } from "@models/productModels/intervals/resetInterval";
|
||||
import { UsageTierSchema } from "@models/productModels/priceModels/priceConfig/usagePriceConfig";
|
||||
import {
|
||||
OnDecrease,
|
||||
OnIncrease,
|
||||
} from "../../../models/productV2Models/productItemModels/productItemEnums.js";
|
||||
} from "@models/productV2Models/productItemModels/productItemEnums";
|
||||
import { UsageModel } from "@models/productV2Models/productItemModels/productItemModels";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const UpdatePlanFeatureSchema = z
|
||||
export const CreatePlanItemParamsV0Schema = z
|
||||
.object({
|
||||
feature_id: z.string(),
|
||||
granted_balance: z.number().optional(),
|
||||
@@ -91,5 +91,6 @@ export const UpdatePlanFeatureSchema = z
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export type UpdatePlanFeatureParams = z.infer<typeof UpdatePlanFeatureSchema>;
|
||||
export type CreatePlanItemParamsV0 = z.infer<
|
||||
typeof CreatePlanItemParamsV0Schema
|
||||
>;
|
||||
12
shared/api/products/items/index.ts
Normal file
12
shared/api/products/items/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { planItemV0ToProductItem } from "@api/products/items/mappers/planItemV0ToProductItem.js";
|
||||
|
||||
export * from "./apiPlanItemV0.js";
|
||||
export * from "./crud/createPlanItemV0Params.js";
|
||||
export * from "./mappers/planItemV0ToProductItem.js";
|
||||
export * from "./previousVersions/apiProductItemV0.js";
|
||||
|
||||
export const apiPlanItem = {
|
||||
map: {
|
||||
v0ToProductItem: planItemV0ToProductItem,
|
||||
},
|
||||
};
|
||||
162
shared/api/products/items/mappers/planItemV0ToProductItem.ts
Normal file
162
shared/api/products/items/mappers/planItemV0ToProductItem.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import type { ApiPlanItemV0 } from "@api/products/items/apiPlanItemV0.js";
|
||||
import type { ProrationConfig } from "@models/productModels/priceModels/priceModels.js";
|
||||
import { Infinite } from "@models/productModels/productEnums.js";
|
||||
import {
|
||||
OnDecrease,
|
||||
OnIncrease,
|
||||
} from "@models/productV2Models/productItemModels/productItemEnums.js";
|
||||
import {
|
||||
type ProductItem,
|
||||
type ProductItemConfig,
|
||||
ProductItemSchema,
|
||||
ProductItemType,
|
||||
type RolloverConfig,
|
||||
} from "@models/productV2Models/productItemModels/productItemModels.js";
|
||||
import { dbToApiFeatureV1 } from "@utils/featureUtils/apiFeatureToDbFeature.js";
|
||||
import { featureToItemFeatureType } from "@utils/featureUtils/convertFeatureUtils.js";
|
||||
|
||||
import { resetIntvToItemIntv } from "@utils/planFeatureUtils/planFeatureIntervals.js";
|
||||
import { billingToItemInterval } from "@utils/productV2Utils/productItemUtils/itemIntervalUtils.js";
|
||||
import type { SharedContext } from "../../../../types/sharedContext.js";
|
||||
import {
|
||||
type ApiFeatureV0,
|
||||
type CreateBalanceParams,
|
||||
FeatureNotFoundError,
|
||||
} from "../../../models.js";
|
||||
import { ApiVersion } from "../../../versionUtils/ApiVersion.js";
|
||||
import { ApiVersionClass } from "../../../versionUtils/ApiVersionClass.js";
|
||||
import type { CreatePlanItemParamsV0 } from "../crud/createPlanItemV0Params.js";
|
||||
import { hasPrice, hasResetInterval } from "../utils/classifyPlanItemV0.js";
|
||||
|
||||
const planItemV0ToProductItemInterval = ({
|
||||
planItemV0,
|
||||
}: {
|
||||
planItemV0: ApiPlanItemV0 | CreatePlanItemParamsV0;
|
||||
}) => {
|
||||
// 1. If feature has reset interval, use it
|
||||
if (hasResetInterval(planItemV0)) {
|
||||
return resetIntvToItemIntv(planItemV0.reset.interval);
|
||||
}
|
||||
|
||||
// 2. If feature has price interval, use it
|
||||
if (hasPrice(planItemV0)) {
|
||||
return billingToItemInterval({
|
||||
billingInterval: planItemV0.price.interval,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const planItemV0ToItemConfig = ({
|
||||
planItemV0,
|
||||
}: {
|
||||
planItemV0: ApiPlanItemV0 | CreatePlanItemParamsV0;
|
||||
}) => {
|
||||
const toItemRollover = () => {
|
||||
if (planItemV0.rollover) {
|
||||
return {
|
||||
max: planItemV0.rollover.max,
|
||||
duration: planItemV0.rollover.expiry_duration_type,
|
||||
length: planItemV0.rollover.expiry_duration_length ?? 1,
|
||||
} satisfies RolloverConfig;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const toItemProration = () => {
|
||||
if (planItemV0.proration) {
|
||||
return {
|
||||
on_increase:
|
||||
planItemV0.proration.on_increase ?? OnIncrease.ProrateImmediately,
|
||||
on_decrease: planItemV0.proration.on_decrease ?? OnDecrease.Prorate,
|
||||
} satisfies ProrationConfig;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const rollover = toItemRollover();
|
||||
const proration = toItemProration();
|
||||
|
||||
if (rollover || proration) {
|
||||
return {
|
||||
rollover,
|
||||
on_increase: proration?.on_increase,
|
||||
on_decrease: proration?.on_decrease,
|
||||
} satisfies ProductItemConfig;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Augmented CreateBalanceParams that can be used for planFeaturesToItems function
|
||||
*/
|
||||
type CreateBalanceForPlanFeatureMap = CreateBalanceParams & {
|
||||
price?: undefined;
|
||||
} & {
|
||||
reset?: CreateBalanceParams["reset"] & { reset_when_enabled: true };
|
||||
};
|
||||
|
||||
export const planItemV0ToProductItem = ({
|
||||
ctx,
|
||||
planItem,
|
||||
}: {
|
||||
ctx: SharedContext;
|
||||
planItem:
|
||||
| ApiPlanItemV0
|
||||
| CreatePlanItemParamsV0
|
||||
| CreateBalanceForPlanFeatureMap;
|
||||
}): ProductItem => {
|
||||
const { features } = ctx;
|
||||
|
||||
const feature = features.find((f) => f.id === planItem.feature_id);
|
||||
if (!feature) {
|
||||
throw new FeatureNotFoundError({ featureId: planItem.feature_id });
|
||||
}
|
||||
|
||||
// Get interval
|
||||
const interval = planItemV0ToProductItemInterval({ planItemV0: planItem });
|
||||
const config = planItemV0ToItemConfig({ planItemV0: planItem });
|
||||
|
||||
const type = planItem.price
|
||||
? ProductItemType.FeaturePrice
|
||||
: ProductItemType.Feature;
|
||||
|
||||
return ProductItemSchema.parse({
|
||||
type,
|
||||
|
||||
feature_id: planItem.feature_id,
|
||||
feature_type: featureToItemFeatureType({ feature }),
|
||||
feature: dbToApiFeatureV1({
|
||||
ctx,
|
||||
dbFeature: feature,
|
||||
targetVersion: new ApiVersionClass(ApiVersion.V1_2),
|
||||
}) as unknown as ApiFeatureV0,
|
||||
|
||||
included_usage: planItem.unlimited ? Infinite : planItem.granted_balance,
|
||||
|
||||
interval,
|
||||
interval_count: planItem.reset?.interval_count,
|
||||
|
||||
entity_feature_id: null,
|
||||
|
||||
price: planItem.price?.amount,
|
||||
|
||||
tiers: planItem.price?.tiers?.map((tier) => ({
|
||||
amount: tier.amount,
|
||||
to: tier.to,
|
||||
})),
|
||||
|
||||
usage_model: planItem.price?.usage_model,
|
||||
billing_units: planItem.price?.billing_units,
|
||||
usage_limit: planItem.price?.max_purchase
|
||||
? planItem.price.max_purchase + (planItem.granted_balance ?? 0)
|
||||
: undefined,
|
||||
|
||||
reset_usage_when_enabled: planItem.reset?.reset_when_enabled,
|
||||
|
||||
config,
|
||||
|
||||
display: "display" in planItem ? planItem.display : undefined,
|
||||
} satisfies ProductItem);
|
||||
};
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@models/productV2Models/productItemModels/productItemModels.js";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const ApiProductItemSchema = z
|
||||
export const ApiProductItemV0Schema = z
|
||||
.object({
|
||||
// Feature stuff
|
||||
type: z
|
||||
@@ -118,4 +118,4 @@ export const ApiProductItemSchema = z
|
||||
description: "Product item defining features and pricing within a product",
|
||||
});
|
||||
|
||||
export type ApiProductItem = z.infer<typeof ApiProductItemSchema>;
|
||||
export type ApiProductItem = z.infer<typeof ApiProductItemV0Schema>;
|
||||
23
shared/api/products/items/utils/classifyPlanItemV0.ts
Normal file
23
shared/api/products/items/utils/classifyPlanItemV0.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { ApiPlanItemV0 } from "@api/products/items/apiPlanItemV0.js";
|
||||
import type { CreatePlanItemParamsV0 } from "@api/products/items/crud/createPlanItemV0Params.js";
|
||||
import { notNullish } from "@utils/utils.js";
|
||||
|
||||
type PlanFeatureWithReset = (ApiPlanItemV0 | CreatePlanItemParamsV0) & {
|
||||
reset: NonNullable<(ApiPlanItemV0 | CreatePlanItemParamsV0)["reset"]>;
|
||||
};
|
||||
|
||||
type PlanFeatureWithPrice = (ApiPlanItemV0 | CreatePlanItemParamsV0) & {
|
||||
price: NonNullable<(ApiPlanItemV0 | CreatePlanItemParamsV0)["price"]>;
|
||||
};
|
||||
|
||||
export const hasResetInterval = (
|
||||
planFeature: ApiPlanItemV0 | CreatePlanItemParamsV0,
|
||||
): planFeature is PlanFeatureWithReset => {
|
||||
return notNullish(planFeature.reset?.interval);
|
||||
};
|
||||
|
||||
export const hasPrice = (
|
||||
planFeature: ApiPlanItemV0 | CreatePlanItemParamsV0,
|
||||
): planFeature is PlanFeatureWithPrice => {
|
||||
return notNullish(planFeature.price);
|
||||
};
|
||||
1
shared/api/products/mappers/index.ts
Normal file
1
shared/api/products/mappers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./planV0ToProductItems.js";
|
||||
50
shared/api/products/mappers/planV0ToBasePriceProductItem.ts
Normal file
50
shared/api/products/mappers/planV0ToBasePriceProductItem.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ApiPlan } from "@api/products/apiPlan";
|
||||
import type {
|
||||
CreatePlanParams,
|
||||
UpdatePlanParams,
|
||||
} from "@api/products/crud/planOpModels";
|
||||
import { BillingInterval } from "@models/productModels/intervals/billingInterval";
|
||||
import {
|
||||
type ProductItem,
|
||||
ProductItemType,
|
||||
} from "@models/productV2Models/productItemModels/productItemModels";
|
||||
import { getProductItemDisplay } from "@utils/productDisplayUtils";
|
||||
import { billingToItemInterval } from "@utils/productV2Utils/productItemUtils/itemIntervalUtils";
|
||||
import type { SharedContext } from "../../../types";
|
||||
|
||||
export const planV0ToBasePriceProductItem = ({
|
||||
ctx,
|
||||
plan,
|
||||
}: {
|
||||
ctx: SharedContext;
|
||||
plan: ApiPlan | CreatePlanParams | UpdatePlanParams;
|
||||
}): ProductItem | undefined => {
|
||||
if (!plan.price) return;
|
||||
|
||||
const basePrice = plan.price;
|
||||
|
||||
const basePriceDisplay =
|
||||
"display" in basePrice ? basePrice.display : undefined;
|
||||
|
||||
if (basePrice) {
|
||||
const item = {
|
||||
type: ProductItemType.Price,
|
||||
feature_id: null,
|
||||
feature: null,
|
||||
interval: billingToItemInterval({
|
||||
billingInterval: basePrice.interval ?? BillingInterval.Month,
|
||||
}),
|
||||
interval_count: basePrice.interval_count ?? 1,
|
||||
price: basePrice.amount ?? 0,
|
||||
} satisfies ProductItem;
|
||||
|
||||
const display =
|
||||
basePriceDisplay ??
|
||||
getProductItemDisplay({ item, features: ctx.features });
|
||||
|
||||
return {
|
||||
...item,
|
||||
display,
|
||||
};
|
||||
}
|
||||
};
|
||||
41
shared/api/products/mappers/planV0ToProductItems.ts
Normal file
41
shared/api/products/mappers/planV0ToProductItems.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
apiPlanItem,
|
||||
type CreatePlanParams,
|
||||
type UpdatePlanParams,
|
||||
} from "@api/models";
|
||||
import type { ApiPlan } from "@api/products/apiPlan";
|
||||
import { planV0ToBasePriceProductItem } from "@api/products/mappers/planV0ToBasePriceProductItem";
|
||||
import type { ProductItem } from "@models/productV2Models/productItemModels/productItemModels";
|
||||
import type { SharedContext } from "../../../types";
|
||||
|
||||
export const planV0ToProductItems = ({
|
||||
ctx,
|
||||
plan,
|
||||
}: {
|
||||
ctx: SharedContext;
|
||||
plan: ApiPlan | CreatePlanParams | UpdatePlanParams;
|
||||
}): ProductItem[] => {
|
||||
// Convert features to items
|
||||
const featureItems =
|
||||
plan.features?.map((planItem) =>
|
||||
apiPlanItem.map.v0ToProductItem({ ctx, planItem }),
|
||||
) ?? [];
|
||||
|
||||
const basePriceItem = planV0ToBasePriceProductItem({ ctx, plan });
|
||||
|
||||
if (basePriceItem) {
|
||||
featureItems.splice(0, 0, basePriceItem);
|
||||
}
|
||||
|
||||
return featureItems;
|
||||
|
||||
// if (plan.price) {
|
||||
// // Add base price if plan has one (independent of feature pricing)
|
||||
// const priceItem = planToProductV2PriceItem({ price: plan.price, features });
|
||||
|
||||
// items.splice(0, 0, priceItem);
|
||||
// }
|
||||
|
||||
// return items;
|
||||
// return [];
|
||||
};
|
||||
48
shared/api/products/mappers/planV0ToProductV2.ts
Normal file
48
shared/api/products/mappers/planV0ToProductV2.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { ApiPlan } from "@api/products/apiPlan";
|
||||
import type {
|
||||
CreatePlanParams,
|
||||
UpdatePlanParams,
|
||||
} from "@api/products/crud/planOpModels";
|
||||
import { planV0ToProductItems } from "@api/products/mappers/planV0ToProductItems";
|
||||
import type {
|
||||
CreateProductV2Params,
|
||||
UpdateProductV2Params,
|
||||
} from "@api/products/productOpModels";
|
||||
import type { ProductV2 } from "@models/productV2Models/productV2Models";
|
||||
import type { SharedContext } from "../../../types/sharedContext";
|
||||
|
||||
export function planV0ToProductV2({
|
||||
ctx,
|
||||
plan,
|
||||
}: {
|
||||
ctx: SharedContext;
|
||||
plan: ApiPlan | CreatePlanParams | UpdatePlanParams;
|
||||
}): CreateProductV2Params | UpdateProductV2Params | ProductV2 {
|
||||
// Convert plan to items using shared utility
|
||||
const items = planV0ToProductItems({ ctx, plan });
|
||||
|
||||
// Check if archived field exists on plan (it's on ApiPlan, not CreatePlanParams)
|
||||
const archived =
|
||||
"archived" in plan && plan.archived !== undefined
|
||||
? plan.archived
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: plan.id,
|
||||
name: plan.name,
|
||||
description: plan.description ?? null,
|
||||
is_add_on: plan.add_on,
|
||||
is_default: plan.default,
|
||||
group: plan.group ?? "",
|
||||
items,
|
||||
free_trial: plan.free_trial
|
||||
? {
|
||||
duration: plan.free_trial.duration_type,
|
||||
length: plan.free_trial.duration_length,
|
||||
unique_fingerprint: false,
|
||||
card_required: plan.free_trial.card_required,
|
||||
}
|
||||
: null,
|
||||
...(archived !== undefined && { archived }),
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { AttachScenario } from "@models/checkModels/checkPreviewModels.js";
|
||||
import { AppEnv } from "@models/genModels/genEnums.js";
|
||||
import { z } from "zod/v4";
|
||||
import { ApiFreeTrialSchema } from "../apiFreeTrial.js";
|
||||
import { ApiProductItemSchema } from "../planFeature/previousVersions/apiProductItem.js";
|
||||
import { ApiProductItemV0Schema } from "../items/previousVersions/apiProductItemV0.js";
|
||||
|
||||
export const PRODUCT_EXAMPLE = {
|
||||
id: "Pro Product",
|
||||
@@ -167,7 +167,7 @@ export const ApiProductSchema = z.object({
|
||||
),
|
||||
|
||||
items: z
|
||||
.array(ApiProductItemSchema)
|
||||
.array(ApiProductItemV0Schema)
|
||||
.describe(
|
||||
"Array of product items that define the product's features and pricing",
|
||||
),
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { ZodType, z } from "zod/v4";
|
||||
import type { Feature } from "../../../models/featureModels/featureModels.js";
|
||||
import type { SharedContext } from "../../../types/sharedContext.js";
|
||||
import type { ApiVersion } from "../ApiVersion.js";
|
||||
|
||||
/**
|
||||
* Context passed to version transforms for accessing runtime data
|
||||
*/
|
||||
export interface VersionContext {
|
||||
features: Feature[];
|
||||
}
|
||||
// /**
|
||||
// * Context passed to version transforms for accessing runtime data
|
||||
// */
|
||||
// export interface VersionContext {
|
||||
// features: Feature[];
|
||||
// }
|
||||
|
||||
/**
|
||||
* Resources that can be affected by version changes
|
||||
@@ -157,7 +157,7 @@ export abstract class VersionChange<
|
||||
legacyData?: TLegacyDataSchema extends ZodType
|
||||
? z.infer<TLegacyDataSchema>
|
||||
: never;
|
||||
ctx?: VersionContext;
|
||||
ctx?: SharedContext;
|
||||
}): z.infer<TOldSchema> {
|
||||
// Default: no-op (override if change affects responses)
|
||||
return input as unknown as z.infer<TOldSchema>;
|
||||
@@ -256,7 +256,7 @@ export interface VersionChangeConfig<
|
||||
legacyData?: TLegacyDataSchema extends ZodType
|
||||
? z.infer<TLegacyDataSchema>
|
||||
: never;
|
||||
ctx: VersionContext;
|
||||
ctx: SharedContext;
|
||||
}) => z.infer<TOldSchema>;
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ export function defineVersionChange<
|
||||
legacyData?: TLegacyDataSchema extends ZodType
|
||||
? z.infer<TLegacyDataSchema>
|
||||
: never;
|
||||
ctx: VersionContext;
|
||||
ctx: SharedContext;
|
||||
}): z.infer<TOldSchema> {
|
||||
if (config.transformResponse) {
|
||||
const result = config.transformResponse(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SharedContext } from "../../../types/sharedContext.js";
|
||||
import { type ApiVersion, LATEST_VERSION } from "../ApiVersion.js";
|
||||
import { ApiVersionClass } from "../ApiVersionClass.js";
|
||||
import {
|
||||
@@ -8,7 +9,6 @@ import type {
|
||||
AffectedResource,
|
||||
VersionChange,
|
||||
VersionChangeConstructor,
|
||||
VersionContext,
|
||||
} from "./VersionChange.js";
|
||||
import { VersionChangeRegistryClass } from "./VersionChangeRegistryClass.js";
|
||||
|
||||
@@ -39,7 +39,7 @@ export function applyResponseVersionChanges<T = any, TLegacyData = any>({
|
||||
currentVersion?: ApiVersionClass;
|
||||
targetVersion: ApiVersionClass;
|
||||
resource: AffectedResource;
|
||||
ctx: VersionContext;
|
||||
ctx: SharedContext;
|
||||
}): T {
|
||||
// Default currentVersion to latest if not provided
|
||||
const _currentVersion = currentVersion || new ApiVersionClass(LATEST_VERSION);
|
||||
@@ -253,7 +253,7 @@ export function applyResponseVersionChangesToArray<T = any, TLegacyData = any>({
|
||||
currentVersion?: ApiVersionClass;
|
||||
targetVersion: ApiVersionClass;
|
||||
resource: AffectedResource;
|
||||
ctx: VersionContext;
|
||||
ctx: SharedContext;
|
||||
}): T[] {
|
||||
return inputArray.map((item) =>
|
||||
applyResponseVersionChanges({
|
||||
|
||||
@@ -19,7 +19,9 @@ import { V1_2_FeatureChange } from "@api/features/changes/V1.2_FeatureChange.js"
|
||||
import { V1_2_CreateFeatureChange } from "@api/features/changes/V1.2_FeatureParamsChange.js";
|
||||
// Import invoice changes
|
||||
import { V1_2_InvoiceChange } from "@api/others/apiInvoice/changes/V1.2_InvoiceChange.js";
|
||||
|
||||
// Import product changes
|
||||
|
||||
import { V1_2_ProductChanges } from "@api/products/changes/V1.2_ProductChanges.js";
|
||||
import { V0_2_CheckChange } from "../../balances/check/changes/V0.2_CheckChange.js";
|
||||
import { V1_2_CheckChange } from "../../balances/check/changes/V1.2_CheckChange.js";
|
||||
|
||||
@@ -33,7 +33,6 @@ export {
|
||||
AffectedResource,
|
||||
VersionChange,
|
||||
type VersionChangeConstructor,
|
||||
type VersionContext,
|
||||
} from "./versionChangeUtils/VersionChange.js";
|
||||
export { VersionChangeRegistryClass } from "./versionChangeUtils/VersionChangeRegistryClass.js";
|
||||
// Version registry
|
||||
|
||||
@@ -85,8 +85,8 @@ export * from "./models/featureModels/featureModels.js";
|
||||
|
||||
// export * from "./models/featureModels/featureResModels.js";
|
||||
|
||||
export * from "./api/products/planFeature/apiPlanFeature.js";
|
||||
export * from "./api/products/planOpModels.js";
|
||||
export * from "./api/products/crud/planOpModels.js";
|
||||
export * from "./api/products/items/apiPlanItemV0.js";
|
||||
|
||||
// 2. Feature Models
|
||||
export * from "./models/featureModels/featureTable.js";
|
||||
@@ -96,12 +96,12 @@ export * from "./models/genModels/processorSchemas.js";
|
||||
|
||||
// Idempotency Models
|
||||
|
||||
export * from "./api/products/items/mappers/planItemV0ToProductItem.js";
|
||||
export * from "./api/products/items/mappers/planItemV0ToProductItem.js";
|
||||
// Attach Function Response
|
||||
export * from "./models/attachModels/attachFunctionResponse.js";
|
||||
|
||||
// Billing Models (all from single index)
|
||||
export * from "./models/billingModels/index.js";
|
||||
|
||||
// Checkout Models
|
||||
export * from "./models/checkouts/index.js";
|
||||
export * from "./models/cusProductModels/cusPriceModels/customerPriceWithCustomerProduct.js";
|
||||
@@ -173,6 +173,7 @@ export * from "./models/rewardModels/rewardProgramModels/rewardProgramModels.js"
|
||||
export * from "./models/rewardModels/rewardProgramModels/rewardProgramTable.js";
|
||||
export * from "./models/subModels/subModels.js";
|
||||
export * from "./models/subModels/subTable.js";
|
||||
export * from "./types";
|
||||
// Agent Types (for pricing agent AI)
|
||||
export * from "./utils/agentTypes.js";
|
||||
export * from "./utils/billingUtils/index.js";
|
||||
@@ -192,12 +193,8 @@ export * from "./utils/planFeatureUtils/itemsToPlanFeatures.js";
|
||||
export * from "./utils/planFeatureUtils/itemsToPlanFeatures.js";
|
||||
export * from "./utils/planFeatureUtils/planFeatureIntervals.js";
|
||||
export * from "./utils/planFeatureUtils/planFeatureIntervals.js";
|
||||
export * from "./utils/planFeatureUtils/planFeaturesToItems.js";
|
||||
export * from "./utils/planFeatureUtils/planFeaturesToItems.js";
|
||||
export * from "./utils/planFeatureUtils/planToDbFreeTrial.js";
|
||||
export * from "./utils/planFeatureUtils/planToItems.js";
|
||||
export * from "./utils/planFeatureUtils/planToItems.js";
|
||||
export * from "./utils/planFeatureUtils/planToProductV2.js";
|
||||
|
||||
export * from "./utils/productDisplayUtils/sortProductItems.js";
|
||||
export * from "./utils/productDisplayUtils.js";
|
||||
export * from "./utils/productUtils/convertProductUtils.js";
|
||||
|
||||
2
shared/types/index.ts
Normal file
2
shared/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./logger";
|
||||
export * from "./sharedContext";
|
||||
7
shared/types/logger.ts
Normal file
7
shared/types/logger.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type AutumnLogger = {
|
||||
debug: (...args: unknown[]) => void;
|
||||
info: (...args: unknown[]) => void;
|
||||
warn: (...args: unknown[]) => void;
|
||||
error: (...args: unknown[]) => void;
|
||||
child: (opts: { context: Record<string, unknown>; onlyProd?: boolean }) => AutumnLogger;
|
||||
};
|
||||
35
shared/types/sharedContext.ts
Normal file
35
shared/types/sharedContext.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { Feature } from "@models/featureModels/featureModels";
|
||||
import type { AppEnv } from "@models/genModels/genEnums";
|
||||
import type { Organization } from "@models/orgModels/orgTable";
|
||||
import type { AutumnLogger } from "./logger";
|
||||
|
||||
export type SharedContext = {
|
||||
// Variables
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
features: Feature[];
|
||||
|
||||
// Objects
|
||||
logger: AutumnLogger;
|
||||
// db: DrizzleCli;
|
||||
// clickhouseClient?: ClickHouseClient;
|
||||
|
||||
// // Info
|
||||
// id: string;
|
||||
// isPublic: boolean;
|
||||
// authType: AuthType;
|
||||
// apiVersion: ApiVersionClass;
|
||||
// timestamp: number;
|
||||
|
||||
// // Query params
|
||||
// expand: string[];
|
||||
// skipCache: boolean;
|
||||
|
||||
// extraLogs: Record<string, unknown>;
|
||||
|
||||
// testOptions?: {
|
||||
// skipCacheDeletion?: boolean;
|
||||
// skipWebhooks?: boolean;
|
||||
// eventId?: string;
|
||||
// };
|
||||
};
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
LATEST_VERSION,
|
||||
} from "../../api/versionUtils/versionUtils.js";
|
||||
import type { CreditSchemaItem } from "../../models/featureModels/featureConfig/creditConfig.js";
|
||||
import type { SharedContext } from "../../types/sharedContext.js";
|
||||
import { notNullish, nullish } from "../utils.js";
|
||||
|
||||
export const apiFeatureToDbFeature = ({
|
||||
@@ -177,9 +178,11 @@ export const featureV1ToDbFeature = ({
|
||||
* - For API version V1_Beta and older, responses are automatically converted to ApiFeatureV0 format
|
||||
*/
|
||||
export const dbToApiFeatureV1 = ({
|
||||
ctx,
|
||||
dbFeature,
|
||||
targetVersion,
|
||||
}: {
|
||||
ctx: SharedContext;
|
||||
dbFeature: Feature;
|
||||
targetVersion?: ApiVersionClass;
|
||||
}) => {
|
||||
@@ -212,9 +215,7 @@ export const dbToApiFeatureV1 = ({
|
||||
input: result,
|
||||
targetVersion: targetVersion ?? new ApiVersionClass(LATEST_VERSION),
|
||||
resource: AffectedResource.Feature,
|
||||
ctx: {
|
||||
features: [dbFeature],
|
||||
},
|
||||
ctx,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { ApiPlanFeature } from "../../api/products/planFeature/apiPlanFeature.js";
|
||||
import type { UpdatePlanFeatureParams } from "../../api/products/planFeature/planFeatureOpModels.js";
|
||||
import { notNullish } from "../utils.js";
|
||||
|
||||
type PlanFeatureWithReset = (ApiPlanFeature | UpdatePlanFeatureParams) & {
|
||||
reset: NonNullable<(ApiPlanFeature | UpdatePlanFeatureParams)["reset"]>;
|
||||
};
|
||||
|
||||
type PlanFeatureWithPrice = (ApiPlanFeature | UpdatePlanFeatureParams) & {
|
||||
price: NonNullable<(ApiPlanFeature | UpdatePlanFeatureParams)["price"]>;
|
||||
};
|
||||
|
||||
export const hasResetInterval = (
|
||||
planFeature: ApiPlanFeature | UpdatePlanFeatureParams,
|
||||
): planFeature is PlanFeatureWithReset => {
|
||||
return notNullish(planFeature.reset?.interval);
|
||||
};
|
||||
|
||||
export const hasPrice = (
|
||||
planFeature: ApiPlanFeature | UpdatePlanFeatureParams,
|
||||
): planFeature is PlanFeatureWithPrice => {
|
||||
return notNullish(planFeature.price);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type ApiPlanFeature,
|
||||
ApiPlanFeatureSchema,
|
||||
} from "@api/products/planFeature/apiPlanFeature.js";
|
||||
type ApiPlanItemV0,
|
||||
ApiPlanItemV0Schema,
|
||||
} from "@api/products/items/apiPlanItemV0.js";
|
||||
import { CusExpand } from "@models/cusModels/cusExpand.js";
|
||||
import { Infinite } from "@models/productModels/productEnums.js";
|
||||
import {
|
||||
@@ -63,7 +63,7 @@ const itemToReset = ({
|
||||
? item.interval_count
|
||||
: undefined,
|
||||
reset_when_enabled: item.reset_usage_when_enabled ?? false,
|
||||
} satisfies ApiPlanFeature["reset"];
|
||||
} satisfies ApiPlanItemV0["reset"];
|
||||
};
|
||||
|
||||
const itemToPlanFeaturePrice = ({ item }: { item: ProductItem }) => {
|
||||
@@ -101,7 +101,7 @@ const itemToPlanFeaturePrice = ({ item }: { item: ProductItem }) => {
|
||||
billing_units: item.billing_units ?? 1,
|
||||
usage_model: item.usage_model || UsageModel.PayPerUse,
|
||||
max_purchase: maxPurchase,
|
||||
} satisfies ApiPlanFeature["price"];
|
||||
} satisfies ApiPlanItemV0["price"];
|
||||
};
|
||||
|
||||
const itemToPlanFeatureRollover = ({ item }: { item: ProductItem }) => {
|
||||
@@ -111,7 +111,7 @@ const itemToPlanFeatureRollover = ({ item }: { item: ProductItem }) => {
|
||||
max: item.config.rollover.max ?? null,
|
||||
expiry_duration_type: item.config.rollover.duration,
|
||||
expiry_duration_length: item.config.rollover.length,
|
||||
} satisfies ApiPlanFeature["rollover"];
|
||||
} satisfies ApiPlanItemV0["rollover"];
|
||||
};
|
||||
|
||||
const itemToPlanFeatureProration = ({ item }: { item: ProductItem }) => {
|
||||
@@ -122,7 +122,7 @@ const itemToPlanFeatureProration = ({ item }: { item: ProductItem }) => {
|
||||
return {
|
||||
on_increase: item.config.on_increase,
|
||||
on_decrease: item.config.on_decrease,
|
||||
} satisfies ApiPlanFeature["proration"];
|
||||
} satisfies ApiPlanItemV0["proration"];
|
||||
};
|
||||
|
||||
export const itemsToPlanFeatures = ({
|
||||
@@ -133,7 +133,7 @@ export const itemsToPlanFeatures = ({
|
||||
items: ProductItem[];
|
||||
features: Feature[];
|
||||
expand?: string[];
|
||||
}): ApiPlanFeature[] => {
|
||||
}): ApiPlanItemV0[] => {
|
||||
if (!items) return [];
|
||||
|
||||
const shouldExpandFeature = expandIncludes({
|
||||
@@ -164,7 +164,7 @@ export const itemsToPlanFeatures = ({
|
||||
? toApiFeature({ feature })
|
||||
: undefined;
|
||||
|
||||
return ApiPlanFeatureSchema.parse({
|
||||
return ApiPlanItemV0Schema.parse({
|
||||
feature_id: item.feature_id,
|
||||
feature: apiFeature,
|
||||
granted_balance: grantedBalance,
|
||||
@@ -180,7 +180,7 @@ export const itemsToPlanFeatures = ({
|
||||
|
||||
// Other fields
|
||||
// entity_feature_id: item.entity_feature_id,
|
||||
} satisfies ApiPlanFeature);
|
||||
} satisfies ApiPlanItemV0);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
import type { ApiPlanFeature } from "@api/products/planFeature/apiPlanFeature.js";
|
||||
import {
|
||||
type ProductItem,
|
||||
type ProductItemConfig,
|
||||
ProductItemSchema,
|
||||
ProductItemType,
|
||||
type RolloverConfig,
|
||||
} from "@models/productV2Models/productItemModels/productItemModels.js";
|
||||
import {
|
||||
type ApiFeatureV0,
|
||||
type CreateBalanceParams,
|
||||
FeatureNotFoundError,
|
||||
} from "../../api/models.js";
|
||||
import type { UpdatePlanFeatureParams } from "../../api/products/planFeature/planFeatureOpModels.js";
|
||||
import { ApiVersion } from "../../api/versionUtils/ApiVersion.js";
|
||||
import { ApiVersionClass } from "../../api/versionUtils/ApiVersionClass.js";
|
||||
import type { Feature } from "../../models/featureModels/featureModels.js";
|
||||
import type { ProrationConfig } from "../../models/productModels/priceModels/priceModels.js";
|
||||
import { Infinite } from "../../models/productModels/productEnums.js";
|
||||
import {
|
||||
OnDecrease,
|
||||
OnIncrease,
|
||||
} from "../../models/productV2Models/productItemModels/productItemEnums.js";
|
||||
import { dbToApiFeatureV1 } from "../featureUtils/apiFeatureToDbFeature.js";
|
||||
import { featureToItemFeatureType } from "../featureUtils/convertFeatureUtils.js";
|
||||
import { billingToItemInterval } from "../productV2Utils/productItemUtils/itemIntervalUtils.js";
|
||||
import { hasPrice, hasResetInterval } from "./classifyPlanFeature.js";
|
||||
import { resetIntvToItemIntv } from "./planFeatureIntervals.js";
|
||||
|
||||
const planFeatureToItemInterval = ({
|
||||
planFeature,
|
||||
}: {
|
||||
planFeature: ApiPlanFeature | UpdatePlanFeatureParams;
|
||||
}) => {
|
||||
// 1. If feature has reset interval, use it
|
||||
if (hasResetInterval(planFeature)) {
|
||||
return resetIntvToItemIntv(planFeature.reset.interval);
|
||||
}
|
||||
|
||||
// 2. If feature has price interval, use it
|
||||
if (hasPrice(planFeature)) {
|
||||
return billingToItemInterval({
|
||||
billingInterval: planFeature.price.interval,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const planFeatureToItemConfig = ({
|
||||
planFeature,
|
||||
}: {
|
||||
planFeature: ApiPlanFeature | UpdatePlanFeatureParams;
|
||||
}) => {
|
||||
const toItemRollover = () => {
|
||||
if (planFeature.rollover) {
|
||||
return {
|
||||
max: planFeature.rollover.max,
|
||||
duration: planFeature.rollover.expiry_duration_type,
|
||||
length: planFeature.rollover.expiry_duration_length ?? 1,
|
||||
} satisfies RolloverConfig;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const toItemProration = () => {
|
||||
if (planFeature.proration) {
|
||||
return {
|
||||
on_increase:
|
||||
planFeature.proration.on_increase ?? OnIncrease.ProrateImmediately,
|
||||
on_decrease: planFeature.proration.on_decrease ?? OnDecrease.Prorate,
|
||||
} satisfies ProrationConfig;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const rollover = toItemRollover();
|
||||
const proration = toItemProration();
|
||||
|
||||
if (rollover || proration) {
|
||||
return {
|
||||
rollover,
|
||||
on_increase: proration?.on_increase,
|
||||
on_decrease: proration?.on_decrease,
|
||||
} satisfies ProductItemConfig;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Augmented CreateBalanceParams that can be used for planFeaturesToItems function
|
||||
*/
|
||||
type CreateBalanceForPlanFeatureMap = CreateBalanceParams & {
|
||||
price?: undefined;
|
||||
} & {
|
||||
reset?: CreateBalanceParams["reset"] & { reset_when_enabled: true };
|
||||
};
|
||||
|
||||
export const planFeaturesToItems = ({
|
||||
planFeatures,
|
||||
features,
|
||||
}: {
|
||||
planFeatures: (
|
||||
| ApiPlanFeature
|
||||
| UpdatePlanFeatureParams
|
||||
| CreateBalanceForPlanFeatureMap
|
||||
)[];
|
||||
features: Feature[];
|
||||
}): ProductItem[] => {
|
||||
if (!planFeatures) return [];
|
||||
|
||||
return planFeatures.map((planFeature) => {
|
||||
const feature = features.find((f) => f.id === planFeature.feature_id);
|
||||
if (!feature) {
|
||||
throw new FeatureNotFoundError({ featureId: planFeature.feature_id });
|
||||
}
|
||||
|
||||
// Get interval
|
||||
const interval = planFeatureToItemInterval({ planFeature });
|
||||
const config = planFeatureToItemConfig({ planFeature });
|
||||
|
||||
const type = planFeature.price
|
||||
? ProductItemType.FeaturePrice
|
||||
: ProductItemType.Feature;
|
||||
|
||||
return ProductItemSchema.parse({
|
||||
type,
|
||||
|
||||
feature_id: planFeature.feature_id,
|
||||
feature_type: featureToItemFeatureType({ feature }),
|
||||
feature: dbToApiFeatureV1({
|
||||
dbFeature: feature,
|
||||
targetVersion: new ApiVersionClass(ApiVersion.V1_2),
|
||||
}) as unknown as ApiFeatureV0,
|
||||
|
||||
included_usage: planFeature.unlimited
|
||||
? Infinite
|
||||
: planFeature.granted_balance,
|
||||
|
||||
interval,
|
||||
interval_count: planFeature.reset?.interval_count,
|
||||
|
||||
entity_feature_id: null,
|
||||
|
||||
price: planFeature.price?.amount,
|
||||
|
||||
tiers: planFeature.price?.tiers?.map((tier) => ({
|
||||
amount: tier.amount,
|
||||
to: tier.to,
|
||||
})),
|
||||
|
||||
usage_model: planFeature.price?.usage_model,
|
||||
billing_units: planFeature.price?.billing_units,
|
||||
usage_limit: planFeature.price?.max_purchase
|
||||
? planFeature.price.max_purchase + (planFeature.granted_balance ?? 0)
|
||||
: undefined,
|
||||
|
||||
reset_usage_when_enabled: planFeature.reset?.reset_when_enabled,
|
||||
|
||||
config,
|
||||
|
||||
display: "display" in planFeature ? planFeature.display : undefined,
|
||||
} satisfies ProductItem);
|
||||
});
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { ApiPlan } from "@api/products/apiPlan.js";
|
||||
import { BillingInterval } from "@models/productModels/intervals/billingInterval.js";
|
||||
import {
|
||||
type ProductItem,
|
||||
ProductItemType,
|
||||
} from "@models/productV2Models/productItemModels/productItemModels.js";
|
||||
import type { CreatePlanParams } from "../../api/products/planOpModels.js";
|
||||
import type { Feature } from "../../models/featureModels/featureModels.js";
|
||||
import { getProductItemDisplay } from "../productDisplayUtils.js";
|
||||
import { billingToItemInterval } from "../productV2Utils/productItemUtils/itemIntervalUtils.js";
|
||||
import { planFeaturesToItems } from "./planFeaturesToItems.js";
|
||||
|
||||
/**
|
||||
* Construct a price item (base price with no feature)
|
||||
*/
|
||||
export const planToProductV2PriceItem = ({
|
||||
price,
|
||||
features,
|
||||
}: {
|
||||
price: ApiPlan["price"];
|
||||
features: Feature[];
|
||||
}): ProductItem => {
|
||||
const item = {
|
||||
type: ProductItemType.Price,
|
||||
feature_id: null,
|
||||
feature: null,
|
||||
interval: billingToItemInterval({
|
||||
billingInterval: price?.interval ?? BillingInterval.Month,
|
||||
}),
|
||||
interval_count: price?.interval_count ?? 1,
|
||||
price: price?.amount ?? 0,
|
||||
} satisfies ProductItem;
|
||||
|
||||
const display = price?.display ?? getProductItemDisplay({ item, features });
|
||||
|
||||
return {
|
||||
...item,
|
||||
display,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert ApiPlan to ProductItem[] (features + base price)
|
||||
* This is the shared logic used by both planToProductV2 and V1.2_ProductChanges
|
||||
*/
|
||||
export const convertPlanToItems = ({
|
||||
plan,
|
||||
features,
|
||||
}: {
|
||||
plan: ApiPlan | CreatePlanParams;
|
||||
features: Feature[];
|
||||
}): ProductItem[] => {
|
||||
// Convert features to items
|
||||
const featureItems = planFeaturesToItems({
|
||||
planFeatures: plan.features || [],
|
||||
features,
|
||||
});
|
||||
|
||||
const items = [...featureItems];
|
||||
|
||||
// Add base price if plan has one (independent of feature pricing)
|
||||
if (plan.price) {
|
||||
const priceItem = planToProductV2PriceItem({ price: plan.price, features });
|
||||
|
||||
items.splice(0, 0, priceItem);
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { ApiPlan } from "@api/products/apiPlan.js";
|
||||
import type {
|
||||
CreateProductV2Params,
|
||||
UpdateProductV2Params,
|
||||
} from "@api/products/productOpModels.js";
|
||||
import type { CreatePlanParams } from "../../api/products/planOpModels.js";
|
||||
import type { Feature } from "../../models/featureModels/featureModels.js";
|
||||
import { convertPlanToItems } from "./planToItems.js";
|
||||
|
||||
/**
|
||||
* Convert Plan format to ProductV2 format
|
||||
*
|
||||
* This function converts the newer Plan API format to the internal ProductV2 format
|
||||
* used by handlers. Works for both CREATE and UPDATE operations.
|
||||
*
|
||||
* @param plan - ApiPlan (validated at API boundary)
|
||||
* @returns CreateProductV2Params | UpdateProductV2Params
|
||||
*/
|
||||
export function planToProductV2({
|
||||
plan,
|
||||
features,
|
||||
}: {
|
||||
plan: ApiPlan | CreatePlanParams;
|
||||
features: Feature[];
|
||||
}): CreateProductV2Params | UpdateProductV2Params {
|
||||
// Convert plan to items using shared utility
|
||||
const items = convertPlanToItems({ plan, features });
|
||||
|
||||
// Check if archived field exists on plan (it's on ApiPlan, not CreatePlanParams)
|
||||
const archived =
|
||||
"archived" in plan && plan.archived !== undefined
|
||||
? plan.archived
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: plan.id,
|
||||
name: plan.name,
|
||||
description: plan.description ?? null,
|
||||
is_add_on: plan.add_on,
|
||||
is_default: plan.default,
|
||||
group: plan.group ?? "",
|
||||
items,
|
||||
free_trial: plan.free_trial
|
||||
? {
|
||||
duration: plan.free_trial.duration_type,
|
||||
length: plan.free_trial.duration_length,
|
||||
unique_fingerprint: false,
|
||||
card_required: plan.free_trial.card_required,
|
||||
}
|
||||
: null,
|
||||
...(archived !== undefined && { archived }),
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
|
||||
import { ApiProductItemV0Schema } from "@api/products/items/previousVersions/apiProductItemV0.js";
|
||||
import type { FeatureOptions } from "@models/cusProductModels/cusProductModels.js";
|
||||
import type { Feature } from "@models/featureModels/featureModels.js";
|
||||
import {
|
||||
@@ -140,7 +140,7 @@ export const getProductItemResponse = ({
|
||||
}
|
||||
|
||||
const feature = features.find((f) => f.id === item.feature_id);
|
||||
return ApiProductItemSchema.parse({
|
||||
return ApiProductItemV0Schema.parse({
|
||||
type,
|
||||
...item,
|
||||
feature: feature ? toApiFeature({ feature }) : null,
|
||||
|
||||
Reference in New Issue
Block a user