diff --git a/server/src/honoMiddlewares/apiVersionMiddleware.ts b/server/src/honoMiddlewares/apiVersionMiddleware.ts index c9cc4ab20..6753c943b 100644 --- a/server/src/honoMiddlewares/apiVersionMiddleware.ts +++ b/server/src/honoMiddlewares/apiVersionMiddleware.ts @@ -1,6 +1,6 @@ import { ErrCode } from "@autumn/shared"; import type { Context, Next } from "hono"; -import type { HonoEnv } from "@/initHono.js"; +import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; import RecaseError from "@/utils/errorUtils.js"; import { floatToVersion } from "@/utils/versionUtils.js"; diff --git a/server/src/honoMiddlewares/betterAuthMiddleware.ts b/server/src/honoMiddlewares/betterAuthMiddleware.ts index 46f81f52a..2a7048538 100644 --- a/server/src/honoMiddlewares/betterAuthMiddleware.ts +++ b/server/src/honoMiddlewares/betterAuthMiddleware.ts @@ -1,6 +1,6 @@ import { type AppEnv, AuthType, ErrCode } from "@autumn/shared"; import type { Context, Next } from "hono"; -import type { HonoEnv } from "@/initHono.js"; +import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; import { OrgService } from "@/internal/orgs/OrgService.js"; import { auth } from "@/utils/auth.js"; import RecaseError from "@/utils/errorUtils.js"; diff --git a/server/src/honoMiddlewares/errorMiddleware.ts b/server/src/honoMiddlewares/errorMiddleware.ts index 9743a655b..d4c63e2fd 100644 --- a/server/src/honoMiddlewares/errorMiddleware.ts +++ b/server/src/honoMiddlewares/errorMiddleware.ts @@ -3,7 +3,7 @@ import type { Context } from "hono"; import type { ContentfulStatusCode } from "hono/utils/http-status"; import Stripe from "stripe"; import { ZodError } from "zod/v4"; -import type { HonoEnv } from "@/initHono.js"; +import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; import RecaseError, { formatZodError } from "@/utils/errorUtils.js"; /** diff --git a/server/src/honoMiddlewares/orgConfigMiddleware.ts b/server/src/honoMiddlewares/orgConfigMiddleware.ts index ffec321f4..1b0681590 100644 --- a/server/src/honoMiddlewares/orgConfigMiddleware.ts +++ b/server/src/honoMiddlewares/orgConfigMiddleware.ts @@ -1,5 +1,5 @@ import type { Context, Next } from "hono"; -import type { HonoEnv } from "@/initHono.js"; +import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; /** * Middleware to handle org-config header and merge with org config diff --git a/server/src/honoMiddlewares/secretKeyMiddleware.ts b/server/src/honoMiddlewares/secretKeyMiddleware.ts index 565c6cff4..1f38a300f 100644 --- a/server/src/honoMiddlewares/secretKeyMiddleware.ts +++ b/server/src/honoMiddlewares/secretKeyMiddleware.ts @@ -1,6 +1,6 @@ import { AuthType, ErrCode } from "@autumn/shared"; import type { Context, Next } from "hono"; -import type { HonoEnv } from "@/initHono.js"; +import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; import { verifyKey } from "@/internal/dev/api-keys/apiKeyUtils.js"; import RecaseError from "@/utils/errorUtils.js"; import { betterAuthMiddleware } from "./betterAuthMiddleware.js"; diff --git a/server/src/honoMiddlewares/traceMiddleware.ts b/server/src/honoMiddlewares/traceMiddleware.ts index 6b58504f9..4c93d78df 100644 --- a/server/src/honoMiddlewares/traceMiddleware.ts +++ b/server/src/honoMiddlewares/traceMiddleware.ts @@ -1,7 +1,7 @@ import { context, trace } from "@opentelemetry/api"; import type { Context, Next } from "hono"; import { logger } from "@/external/logtail/logtailUtils.js"; -import type { HonoEnv } from "@/initHono.js"; +import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; const tracer = trace.getTracer("hono"); diff --git a/server/src/internal/api/entities/getEntityUtils.ts b/server/src/internal/api/entities/getEntityUtils.ts index 22e79b34f..fb1884784 100644 --- a/server/src/internal/api/entities/getEntityUtils.ts +++ b/server/src/internal/api/entities/getEntityUtils.ts @@ -1,25 +1,24 @@ +import { + type APICusProduct, + APIVersion, + type AppEnv, + type Entity, + type EntityExpand, + type EntityResponse, + ErrCode, + type Feature, + type FullCusProduct, + type FullCustomer, + notNullish, + type Organization, + type Subscription, +} from "@autumn/shared"; import type { DrizzleCli } from "@/db/initDrizzle.js"; import { getCusWithCache } from "@/internal/customers/cusCache/getCusWithCache.js"; import { getCusFeaturesResponse } from "@/internal/customers/cusUtils/cusFeatureResponseUtils/getCusFeaturesResponse.js"; import { processFullCusProducts } from "@/internal/customers/cusUtils/cusProductResponseUtils/processFullCusProducts.js"; - import RecaseError from "@/utils/errorUtils.js"; import { nullish } from "@/utils/genUtils.js"; -import { - type AppEnv, - Feature, - type Entity, - EntityExpand, - type EntityResponse, - ErrCode, - type FullCusProduct, - type Organization, - type Subscription, - CusProductResponse, - notNullish, - FullCustomer, - APIVersion, -} from "@autumn/shared"; export const getSingleEntityResponse = async ({ entityId, @@ -53,24 +52,24 @@ export const getSingleEntityResponse = async ({ if (org.config.entity_product) { return ( notNullish(p.internal_entity_id) && - p.internal_entity_id == entity.internal_id + p.internal_entity_id === entity.internal_id ); } return ( - p.internal_entity_id == entity.internal_id || + p.internal_entity_id === entity.internal_id || nullish(p.internal_entity_id) ); }, ); - let entitySubs = (fullCus.subscriptions || []).filter((s: Subscription) => + const entitySubs = (fullCus.subscriptions || []).filter((s: Subscription) => entityCusProducts.some((p: FullCusProduct) => p.subscription_ids?.includes(s.stripe_id || ""), ), ); - let { main, addOns } = await processFullCusProducts({ + const { main, addOns } = await processFullCusProducts({ fullCusProducts: entityCusProducts, entity, subs: entitySubs, @@ -79,9 +78,9 @@ export const getSingleEntityResponse = async ({ features, }); - let products: CusProductResponse[] = [...main, ...addOns]; + const products: APICusProduct[] = [...main, ...addOns]; - let cusFeatures = await getCusFeaturesResponse({ + const cusFeatures = await getCusFeaturesResponse({ cusProducts: entityCusProducts, org, entity, @@ -128,7 +127,7 @@ export const getEntityResponse = async ({ logger: any; skipCache?: boolean; }) => { - let fullCus = await getCusWithCache({ + const fullCus = await getCusWithCache({ db, idOrInternalId: customerId, org, @@ -151,7 +150,7 @@ export const getEntityResponse = async ({ for (const entityId of entityIds) { const entity = fullCus.entities.find( - (e: Entity) => e.id == entityId || e.internal_id == entityId, + (e: Entity) => e.id === entityId || e.internal_id === entityId, ); if (!entity) { @@ -162,7 +161,7 @@ export const getEntityResponse = async ({ }); } - let entityResponse = await getSingleEntityResponse({ + const entityResponse = await getSingleEntityResponse({ entityId, org, env, diff --git a/server/src/internal/api/entitled/checkUtils/getV2CheckResponse.ts b/server/src/internal/api/entitled/checkUtils/getV2CheckResponse.ts index 8f3ac9ce8..e7bd34378 100644 --- a/server/src/internal/api/entitled/checkUtils/getV2CheckResponse.ts +++ b/server/src/internal/api/entitled/checkUtils/getV2CheckResponse.ts @@ -1,19 +1,19 @@ -import { getUnlimitedAndUsageAllowed } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js"; -import { cusEntMatchesFeature } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/findCusEntUtils.js"; -import { getCusBalances } from "@/internal/customers/cusUtils/cusFeatureResponseUtils/getCusBalances.js"; -import { balancesToFeatureResponse } from "@/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.js"; import { - CheckResponseSchema, - Feature, + CheckResultSchema, + type Feature, FeatureType, - FullCusEntWithFullCusProduct, - FullCusProduct, - FullCustomer, - Organization, + type FullCusEntWithFullCusProduct, + type FullCusProduct, + type FullCustomer, + type Organization, SuccessCode, } from "@autumn/shared"; -import { notNullish } from "@/utils/genUtils.js"; +import { cusEntMatchesFeature } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/findCusEntUtils.js"; +import { getUnlimitedAndUsageAllowed } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js"; +import { balancesToFeatureResponse } from "@/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.js"; +import { getCusBalances } from "@/internal/customers/cusUtils/cusFeatureResponseUtils/getCusBalances.js"; import { featureToCreditSystem } from "@/internal/features/creditSystemUtils.js"; +import { notNullish } from "@/utils/genUtils.js"; export const getFeatureToUse = ({ creditSystems, @@ -25,12 +25,12 @@ export const getFeatureToUse = ({ cusEnts: FullCusEntWithFullCusProduct[]; }) => { // 1. If there's a credit system - let featureCusEnts = cusEnts.filter((cusEnt) => + const featureCusEnts = cusEnts.filter((cusEnt) => cusEntMatchesFeature({ cusEnt, feature }), ); if (creditSystems.length > 0) { - let creditCusEnts = cusEnts.filter((cusEnt) => + const creditCusEnts = cusEnts.filter((cusEnt) => cusEntMatchesFeature({ cusEnt, feature: creditSystems[0] }), ); @@ -95,7 +95,7 @@ export const getV2CheckResponse = async ({ apiVersion, }); - let cusFeatures = balancesToFeatureResponse({ + const cusFeatures = balancesToFeatureResponse({ cusEnts: featureCusEnts, balances, }); @@ -104,8 +104,8 @@ export const getV2CheckResponse = async ({ let allowed = false; - let totalPaidUsageAllowance = featureCusEnts.reduce((acc, ce) => { - let ent = ce.entitlement; + const totalPaidUsageAllowance = featureCusEnts.reduce((acc, ce) => { + const ent = ce.entitlement; if (notNullish(ent.usage_limit)) { return acc + ent.usage_limit! - (ent.allowance || 0); } @@ -122,7 +122,7 @@ export const getV2CheckResponse = async ({ } let finalRequired = notNullish(requiredBalance) ? requiredBalance : 1; - if (featureToUse.type == FeatureType.CreditSystem) { + if (featureToUse.type === FeatureType.CreditSystem) { finalRequired = featureToCreditSystem({ featureId: feature.id, creditSystem: featureToUse, @@ -130,7 +130,7 @@ export const getV2CheckResponse = async ({ }); } - return CheckResponseSchema.parse({ + return CheckResultSchema.parse({ customer_id: fullCus.id, feature_id: featureToUse.id, entity_id: fullCus.entity?.id, diff --git a/server/src/internal/api/events/eventRouter.ts b/server/src/internal/api/events/eventRouter.ts index ea6f7b4d3..52e99aa9e 100644 --- a/server/src/internal/api/events/eventRouter.ts +++ b/server/src/internal/api/events/eventRouter.ts @@ -1,34 +1,32 @@ -import { Router } from "express"; - import { APIVersion, - AppEnv, + type AppEnv, CreateEventSchema, CusProductStatus, - EntityData, + type EntityData, ErrCode, - EventInsert, - Feature, + type EventInsert, + type Feature, FeatureType, - FullCustomer, - Organization, + type FullCustomer, + type Organization, } from "@autumn/shared"; - +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import type { DrizzleCli } from "@/db/initDrizzle.js"; +import { getOrCreateCustomer } from "@/internal/customers/cusUtils/getOrCreateCustomer.js"; +import { creditSystemContainsFeature } from "@/internal/features/creditSystemUtils.js"; +import { FeatureService } from "@/internal/features/FeatureService.js"; +import { OrgService } from "@/internal/orgs/OrgService.js"; +import { JobName } from "@/queue/JobName.js"; +import { addTaskToQueue } from "@/queue/queueUtils.js"; import RecaseError, { handleRequestError } from "@/utils/errorUtils.js"; import { generateId, notNullish } from "@/utils/genUtils.js"; -import { EventService } from "./EventService.js"; -import { OrgService } from "@/internal/orgs/OrgService.js"; -import { handleUsageEvent } from "./usageRouter.js"; -import { StatusCodes } from "http-status-codes"; -import { getOrCreateCustomer } from "@/internal/customers/cusUtils/getOrCreateCustomer.js"; -import { FeatureService } from "@/internal/features/FeatureService.js"; -import { creditSystemContainsFeature } from "@/internal/features/creditSystemUtils.js"; -import { addTaskToQueue } from "@/queue/queueUtils.js"; -import { JobName } from "@/queue/JobName.js"; +import type { ExtendedRequest } from "@/utils/models/Request.js"; import { orgToVersion } from "@/utils/versionUtils.js"; -import type { DrizzleCli } from "@/db/initDrizzle.js"; -import { ExtendedRequest } from "@/utils/models/Request.js"; +import { EventService } from "./EventService.js"; import { getEventTimestamp } from "./eventUtils.js"; +import { handleUsageEvent } from "./usageRouter.js"; export const eventsRouter: Router = Router(); @@ -99,7 +97,7 @@ const getEventAndCustomer = async ({ internal_entity_id: internalEntityId, }; - let event = await EventService.insert({ db, event: newEvent }); + const event = await EventService.insert({ db, event: newEvent }); return { customer, event }; }; @@ -111,20 +109,20 @@ const getAffectedFeatures = async ({ req: any; eventName: string; }) => { - let features = await FeatureService.getFromReq(req); + const features = await FeatureService.getFromReq(req); - let featuresWithEvent = features.filter((feature) => { + const featuresWithEvent = features.filter((feature) => { return ( - feature.type == FeatureType.Metered && + feature.type === FeatureType.Metered && feature.config.filters.some((filter: any) => { return filter.value.includes(eventName); }) ); }); - let creditSystems = features.filter((cs: Feature) => { + const creditSystems = features.filter((cs: Feature) => { return ( - cs.type == FeatureType.CreditSystem && + cs.type === FeatureType.CreditSystem && featuresWithEvent.some((f) => creditSystemContainsFeature({ creditSystem: cs, @@ -156,7 +154,7 @@ export const handleEventSent = async ({ const { env, db } = req; - let eventName = event_data.event_name; + const eventName = event_data.event_name; const org = await OrgService.getFromReq(req); const features = await FeatureService.getFromReq(req); @@ -166,7 +164,7 @@ export const handleEventSent = async ({ eventName, }); - if (affectedFeatures.length == 0) { + if (affectedFeatures.length === 0) { throw new RecaseError({ message: `No features found for event_name ${event_data.event_name}`, code: ErrCode.InvalidEventName, @@ -236,19 +234,19 @@ eventsRouter.post("", async (req: any, res: any) => { }); } - let { event, org }: any = await handleEventSent({ + const { event, org }: any = await handleEventSent({ req, customer_id: body.customer_id, customer_data: body.customer_data, event_data: body, }); - let apiVersion = orgToVersion({ + const apiVersion = orgToVersion({ org, reqApiVersion: req.apiVersion, }); - let response: any = { + const response: any = { id: event?.id, code: "event_received", customer_id: body.customer_id, diff --git a/server/src/internal/customers/cusProducts/cusProductUtils.ts b/server/src/internal/customers/cusProducts/cusProductUtils.ts index 4bdac2366..a60102045 100644 --- a/server/src/internal/customers/cusProducts/cusProductUtils.ts +++ b/server/src/internal/customers/cusProducts/cusProductUtils.ts @@ -1,9 +1,9 @@ import { ACTIVE_STATUSES, + APICusProductSchema, APIVersion, type AppEnv, AttachScenario, - CusProductResponseSchema, CusProductStatus, cusProductToPrices, type Entity, @@ -508,28 +508,25 @@ export const processFullCusProduct = ({ } if (apiVersion >= APIVersion.v1_1) { - if ((!subIds || subIds.length == 0) && trialing) { + if ((!subIds || subIds.length === 0) && trialing) { stripeSubData = { current_period_start: cusProduct.starts_at, current_period_end: cusProduct.trial_ends_at, }; } - return CusProductResponseSchema.parse({ + return APICusProductSchema.parse({ id: cusProduct.product.id, name: cusProduct.product.name, group: cusProduct.product.group || null, status: trialing ? CusProductStatus.Trialing : cusProduct.status, - // created_at: cusProduct.created_at, canceled_at: cusProduct.canceled_at, is_default: cusProduct.product.is_default || false, is_add_on: cusProduct.product.is_add_on || false, - stripe_subscription_ids: cusProduct.subscription_ids || [], started_at: cusProduct.starts_at, - // entity_id: cusProduct.entity_id, entity_id: cusProduct.internal_entity_id - ? entities?.find((e) => e.internal_id == cusProduct.internal_entity_id) + ? entities?.find((e) => e.internal_id === cusProduct.internal_entity_id) ?.id : cusProduct.entity_id || undefined, diff --git a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts index 420b8b088..47d3770b0 100644 --- a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts +++ b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts @@ -1,19 +1,19 @@ import { - CreditSchemaItem, - CusEntResponse, + type CreditSchemaItem, + type CusEntResponse, CusEntResponseSchema, - CusEntResponseV2, - CusRollover, - Feature, + type CusEntResponseV2, + type CusRollover, + type Feature, FeatureType, - FullCustomerEntitlement, + type FullCustomerEntitlement, } from "@autumn/shared"; -import { CusFeatureBalance } from "./getCusBalances.js"; import { getCusFeatureType, isCreditSystem, } from "@/internal/features/featureUtils.js"; import { notNullish } from "@/utils/genUtils.js"; +import type { CusFeatureBalance } from "./getCusBalances.js"; export const sumValues = ( entList: CusEntResponse[], @@ -29,7 +29,7 @@ export const sumValues = ( }; export const getEarliestNextResetAt = (entList: CusEntResponse[]) => { - let earliest = entList.reduce((acc, curr) => { + const earliest = entList.reduce((acc, curr) => { if (curr.next_reset_at && curr.next_reset_at < acc) { return curr.next_reset_at; } @@ -47,21 +47,21 @@ export const featuresToObject = ({ features: Feature[]; entList: CusEntResponse[]; }) => { - let featureObject: Record = {}; + const featureObject: Record = {}; - for (let entRes of entList) { - let feature = features.find((f) => f.id == entRes.feature_id)!; - let featureType = getCusFeatureType({ feature }); + for (const entRes of entList) { + const feature = features.find((f) => f.id == entRes.feature_id)!; + const featureType = getCusFeatureType({ feature }); - let featureId = feature.id; - let unlimited = entRes.unlimited; - let relatedEnts = entList.filter((e) => e.feature_id == featureId); + const featureId = feature.id; + const unlimited = entRes.unlimited; + const relatedEnts = entList.filter((e) => e.feature_id == featureId); if (featureObject[featureId]) { continue; } - let includedUsage = sumValues(relatedEnts, "included_usage"); + const includedUsage = sumValues(relatedEnts, "included_usage"); let usageLimit: number | undefined = sumValues(relatedEnts, "usage_limit"); if (notNullish(usageLimit) && usageLimit === includedUsage) { usageLimit = undefined; @@ -69,13 +69,13 @@ export const featuresToObject = ({ // console.log(`Feature ${featureId} list:`, relatedEnts); - let hasRollovers = relatedEnts.some((e) => notNullish(e.rollovers)); - let rollovers = hasRollovers + const hasRollovers = relatedEnts.some((e) => notNullish(e.rollovers)); + const rollovers = hasRollovers ? (relatedEnts .flatMap((e) => e.rollovers) .filter(notNullish) as CusRollover[]) : undefined; - let cusFeature: CusEntResponseV2 = { + const cusFeature: CusEntResponseV2 = { id: featureId, name: feature.name, type: featureType, @@ -124,11 +124,11 @@ export const balancesToFeatureResponse = ({ cusEnts: FullCustomerEntitlement[]; balances: CusFeatureBalance[]; }) => { - let features = cusEnts.map((cusEnt) => cusEnt.entitlement.feature); + const features = cusEnts.map((cusEnt) => cusEnt.entitlement.feature); let entList: any = balances.map((b) => { - let isBoolean = - features.find((f: Feature) => f.id == b.feature_id)?.type == + const isBoolean = + features.find((f: Feature) => f.id === b.feature_id)?.type === FeatureType.Boolean; if (b.unlimited || isBoolean) { diff --git a/server/src/internal/customers/cusUtils/cusProductResponseUtils/getCusProductResponse.ts b/server/src/internal/customers/cusUtils/cusProductResponseUtils/getCusProductResponse.ts index f862724a6..f659e5fad 100644 --- a/server/src/internal/customers/cusUtils/cusProductResponseUtils/getCusProductResponse.ts +++ b/server/src/internal/customers/cusUtils/cusProductResponseUtils/getCusProductResponse.ts @@ -1,42 +1,27 @@ +import { + APICusProductSchema, + APIVersion, + CusProductStatus, + type Entity, + type Feature, + type FixedPriceConfig, + type FullCusProduct, + type Organization, + PriceType, + type Subscription, + TierInfinite, + type UsagePriceConfig, +} from "@autumn/shared"; import { getPriceOptions, getUsageTier, } from "@/internal/products/prices/priceUtils.js"; +import { getProductResponse } from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js"; import { BREAK_API_VERSION } from "@/utils/constants.js"; import { notNullish } from "@/utils/genUtils.js"; -import { - FullCusProduct, - Organization, - Subscription, - PriceType, - FixedPriceConfig, - UsagePriceConfig, - TierInfinite, - APIVersion, - CusProductResponseSchema, - CusProductStatus, - Entity, - Feature, -} from "@autumn/shared"; - -import Stripe from "stripe"; import { getRelatedCusEnt } from "../../cusProducts/cusPrices/cusPriceUtils.js"; import { fullCusProductToProduct } from "../../cusProducts/cusProductUtils.js"; -import { - getProductItemResponse, - getProductResponse, -} from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js"; -const getQuantityData = ({ cusProduct }: { cusProduct: FullCusProduct }) => { - return { - prepaid_quantities: cusProduct.options.map((o) => { - return { - quantity: o.quantity, - feature_id: o.feature_id, - }; - }), - }; -}; export const getCusProductResponse = async ({ cusProduct, subs, @@ -55,26 +40,27 @@ export const getCusProductResponse = async ({ // Process prices const prices = cusProduct.customer_prices.map((cp) => { - let price = cp.price; + const price = cp.price; - if (price.config?.type == PriceType.Fixed) { - let config = price.config as FixedPriceConfig; + if (price.config?.type === PriceType.Fixed) { + const config = price.config as FixedPriceConfig; return { amount: config.amount, interval: config.interval, }; } else { - let config = price.config as UsagePriceConfig; - let priceOptions = getPriceOptions(price, cusProduct.options); - let usageTier = getUsageTier(price, priceOptions?.quantity!); - let cusEnt = getRelatedCusEnt({ + const config = price.config as UsagePriceConfig; + const priceOptions = getPriceOptions(price, cusProduct.options); + const usageTier = getUsageTier(price, priceOptions?.quantity!); + const cusEnt = getRelatedCusEnt({ cusPrice: cp, cusEnts: cusProduct.customer_entitlements, }); - let ent = cusEnt?.entitlement; + const ent = cusEnt?.entitlement; - let singleTier = ent?.allowance == 0 && config.usage_tiers.length == 1; + const singleTier = + ent?.allowance === 0 && config.usage_tiers.length === 1; if (singleTier) { return { @@ -84,7 +70,7 @@ export const getCusProductResponse = async ({ }; } else { // Add allowance to tiers - let allowance = ent?.allowance; + const allowance = ent?.allowance; let tiers; if (notNullish(allowance) && allowance! > 0) { @@ -94,7 +80,7 @@ export const getCusProductResponse = async ({ amount: 0, }, ...config.usage_tiers.map((tier) => { - let isLastTier = tier.to == -1 || tier.to == TierInfinite; + const isLastTier = tier.to === -1 || tier.to === TierInfinite; return { to: isLastTier ? tier.to : Number(tier.to) + allowance!, amount: tier.amount, @@ -103,7 +89,7 @@ export const getCusProductResponse = async ({ ]; } else { tiers = config.usage_tiers.map((tier) => { - let isLastTier = tier.to == -1 || tier.to == TierInfinite; + const isLastTier = tier.to === -1 || tier.to === TierInfinite; return { to: isLastTier ? tier.to : Number(tier.to) + allowance!, amount: tier.amount, @@ -131,8 +117,8 @@ export const getCusProductResponse = async ({ subIds.length > 0 && org.config.api_version >= BREAK_API_VERSION ) { - let baseSub = subs?.find( - (s) => s.id == subIds[0] || (s as Subscription).stripe_id == subIds[0], + const baseSub = subs?.find( + (s) => s.id === subIds[0] || (s as Subscription).stripe_id === subIds[0], ); stripeSubData = { current_period_end: baseSub?.current_period_end @@ -152,7 +138,7 @@ export const getCusProductResponse = async ({ } if (apiVersion >= APIVersion.v1_1) { - if ((!subIds || subIds.length == 0) && trialing) { + if ((!subIds || subIds.length === 0) && trialing) { stripeSubData = { current_period_start: cusProduct.starts_at, current_period_end: cusProduct.trial_ends_at, @@ -167,35 +153,26 @@ export const getCusProductResponse = async ({ options: cusProduct.options, }); - return CusProductResponseSchema.parse({ + return APICusProductSchema.parse({ id: fullProduct.id, name: fullProduct.name, group: fullProduct.group || null, status: trialing ? CusProductStatus.Trialing : cusProduct.status, canceled_at: cusProduct.canceled_at || null, - // canceled: cusProduct.canceled || false, - // trialing: trialing ? true : false, - is_default: fullProduct.is_default || false, is_add_on: fullProduct.is_add_on || false, version: fullProduct.version, quantity: cusProduct.quantity, - // stripe_subscription_ids: cusProduct.subscription_ids || [], started_at: cusProduct.starts_at, entity_id: entity?.id || cusProduct.entity_id || undefined, - // entity_id: cusProduct.internal_entity_id - // ? entities?.find( - // (e: Entity) => e.internal_id == cusProduct.internal_entity_id - // )?.id - // : cusProduct.entity_id || undefined, ...stripeSubData, items: v2Product.items, }); } else { - let cusProductResponse = { + const cusProductResponse = { id: cusProduct.product.id, name: cusProduct.product.name, group: cusProduct.product.group, diff --git a/server/src/internal/customers/cusUtils/cusProductResponseUtils/processFullCusProducts.ts b/server/src/internal/customers/cusUtils/cusProductResponseUtils/processFullCusProducts.ts index 503c69826..acdb2e0ca 100644 --- a/server/src/internal/customers/cusUtils/cusProductResponseUtils/processFullCusProducts.ts +++ b/server/src/internal/customers/cusUtils/cusProductResponseUtils/processFullCusProducts.ts @@ -1,20 +1,21 @@ import { ACTIVE_STATUSES, + type APICusProduct, APIVersion, - CusProductResponse, - Entity, - Feature, - Organization, + type CusProductStatus, + type Entity, + type Feature, + type Organization, } from "@autumn/shared"; import { getCusProductResponse } from "./getCusProductResponse.js"; const mergeCusProductResponses = ({ cusProductResponses, }: { - cusProductResponses: CusProductResponse[]; + cusProductResponses: APICusProduct[]; }) => { - const getProductKey = (product: CusProductResponse) => { - let status = ACTIVE_STATUSES.includes(product.status) + const getProductKey = (product: APICusProduct) => { + const status = ACTIVE_STATUSES.includes(product.status as CusProductStatus) ? "active" : product.status; return `${product.id}:${status}`; @@ -63,7 +64,7 @@ export const processFullCusProducts = async ({ let main = []; let addOns = []; for (const cusProduct of fullCusProducts) { - let processed = await getCusProductResponse({ + const processed = await getCusProductResponse({ cusProduct, subs, org, @@ -72,7 +73,7 @@ export const processFullCusProducts = async ({ features, }); - let isAddOn = cusProduct.product.is_add_on; + const isAddOn = cusProduct.product.is_add_on; if (isAddOn) { addOns.push(processed); } else { @@ -82,10 +83,10 @@ export const processFullCusProducts = async ({ if (apiVersion >= APIVersion.v1_1) { main = mergeCusProductResponses({ - cusProductResponses: main as CusProductResponse[], + cusProductResponses: main as APICusProduct[], }); addOns = mergeCusProductResponses({ - cusProductResponses: addOns as CusProductResponse[], + cusProductResponses: addOns as APICusProduct[], }); } diff --git a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusReferrals.ts b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusReferrals.ts index b369d4952..89cc2763b 100644 --- a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusReferrals.ts +++ b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusReferrals.ts @@ -1,7 +1,10 @@ -import { DrizzleCli } from "@/db/initDrizzle.js"; +import { + APICusReferralSchema, + CusExpand, + type FullCustomer, +} from "@autumn/shared"; +import type { DrizzleCli } from "@/db/initDrizzle.js"; import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js"; -import { CusExpand, FullCustomer } from "@autumn/shared"; -import { CusReferralResponseSchema } from "@autumn/shared"; export const getCusReferrals = async ({ db, @@ -16,7 +19,7 @@ export const getCusReferrals = async ({ return undefined; } - let referred = await RewardRedemptionService.getByReferrer({ + const referred = await RewardRedemptionService.getByReferrer({ db, internalCustomerId: fullCus.internal_id, withCustomer: true, @@ -25,7 +28,7 @@ export const getCusReferrals = async ({ }); return referred.map((r) => - CusReferralResponseSchema.parse({ + APICusReferralSchema.parse({ program_id: r.reward_program?.id, customer: { id: r.customer.id, diff --git a/server/src/internal/customers/cusUtils/cusUtils.ts b/server/src/internal/customers/cusUtils/cusUtils.ts index 22be1db9d..9c0086d10 100644 --- a/server/src/internal/customers/cusUtils/cusUtils.ts +++ b/server/src/internal/customers/cusUtils/cusUtils.ts @@ -1,25 +1,25 @@ -import { z } from "zod"; import { + type APIInvoice, CusExpand, - Customer, - CustomerData, + type Customer, + type CustomerData, ErrCode, - Feature, - FullCustomer, - Invoice, - InvoiceResponse, - Organization, + type Feature, + type FullCustomer, + type Invoice, + type Organization, sortCusEntsForDeduction, } from "@autumn/shared"; - -import { CusService } from "@/internal/customers/CusService.js"; - -import { InvoiceService } from "@/internal/invoices/InvoiceService.js"; import { StatusCodes } from "http-status-codes"; -import { notNullish, nullish } from "@/utils/genUtils.js"; -import { DrizzleCli } from "@/db/initDrizzle.js"; +import { z } from "zod"; +import type { DrizzleCli } from "@/db/initDrizzle.js"; +import { CusService } from "@/internal/customers/CusService.js"; +import { + InvoiceService, + processInvoice, +} from "@/internal/invoices/InvoiceService.js"; import RecaseError from "@/utils/errorUtils.js"; -import { processInvoice } from "@/internal/invoices/InvoiceService.js"; +import { notNullish, nullish } from "@/utils/genUtils.js"; import { refreshCusCache } from "../cusCache/updateCachedCus.js"; export const updateCustomerDetails = async ({ @@ -35,7 +35,7 @@ export const updateCustomerDetails = async ({ org: Organization; logger: any; }) => { - let updates: any = {}; + const updates: any = {}; if (!customer.name && customerData?.name) { updates.name = customerData.name; } @@ -84,7 +84,7 @@ export const getCusInvoices = async ({ limit?: number; withItems?: boolean; features?: Feature[]; -}): Promise => { +}): Promise => { const finalInvoices = notNullish(invoices) ? invoices : await InvoiceService.list({ @@ -116,7 +116,7 @@ export const getCusEntsInFeatures = async ({ logger: any; reverseOrder?: boolean; }) => { - let cusProducts = customer.customer_products; + const cusProducts = customer.customer_products; // This is important, attaching customer_product to cus ent is used elsewhere, don't delete. let cusEnts = cusProducts.flatMap((cusProduct) => { @@ -126,7 +126,7 @@ export const getCusEntsInFeatures = async ({ })); }); - let cusPrices = cusProducts.flatMap((cusProduct) => { + const cusPrices = cusProducts.flatMap((cusProduct) => { return cusProduct.customer_prices || []; }); @@ -137,7 +137,7 @@ export const getCusEntsInFeatures = async ({ } if (customer.entity) { - let entity = customer.entity; + const entity = customer.entity; cusEnts = cusEnts.filter( (cusEnt) => nullish(cusEnt.customer_product.internal_entity_id) || @@ -152,8 +152,8 @@ export const getCusEntsInFeatures = async ({ export const parseCusExpand = (expand?: string): CusExpand[] => { if (expand) { - let options = expand.split(","); - let result: CusExpand[] = []; + const options = expand.split(","); + const result: CusExpand[] = []; for (const option of options) { if (!Object.values(CusExpand).includes(option as CusExpand)) { throw new RecaseError({ @@ -171,7 +171,7 @@ export const parseCusExpand = (expand?: string): CusExpand[] => { }; export const newCusToFullCus = ({ newCus }: { newCus: Customer }) => { - let fullCus: FullCustomer = { + const fullCus: FullCustomer = { ...newCus, customer_products: [], entities: [], diff --git a/server/src/internal/customers/cusUtils/getCustomerDetails.ts b/server/src/internal/customers/cusUtils/getCustomerDetails.ts index 1c2f6fe41..2e71eaafc 100644 --- a/server/src/internal/customers/cusUtils/getCustomerDetails.ts +++ b/server/src/internal/customers/cusUtils/getCustomerDetails.ts @@ -1,10 +1,10 @@ import { + APICustomerSchema, APIVersion, type AppEnv, CusEntResponseSchema, CusExpand, CusProductStatus, - CusResponseSchema, CustomerResponseSchema, cusProductsToCusEnts, cusProductsToCusPrices, @@ -142,7 +142,7 @@ export const getCustomerDetails = async ({ }); const cusResponse = { - ...CusResponseSchema.parse({ + ...APICustomerSchema.parse({ ...customer, stripe_id: customer.processor?.id, features: entList, diff --git a/server/src/internal/customers/handlers/handlePostCustomer.ts b/server/src/internal/customers/handlers/handlePostCustomer.ts index 1d2359c43..74512a584 100644 --- a/server/src/internal/customers/handlers/handlePostCustomer.ts +++ b/server/src/internal/customers/handlers/handlePostCustomer.ts @@ -1,11 +1,8 @@ -import { handleRequestError } from "@/utils/errorUtils.js"; -import RecaseError from "@/utils/errorUtils.js"; import { CusProductStatus, ErrCode } from "@autumn/shared"; -import { StatusCodes } from "http-status-codes"; -import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js"; - -import { getOrCreateCustomer } from "../cusUtils/getOrCreateCustomer.js"; +import RecaseError, { handleRequestError } from "@/utils/errorUtils.js"; import { parseCusExpand } from "../cusUtils/cusUtils.js"; +import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js"; +import { getOrCreateCustomer } from "../cusUtils/getOrCreateCustomer.js"; export const handlePostCustomerRequest = async (req: any, res: any) => { const logger = req.logtail; @@ -37,7 +34,7 @@ export const handlePostCustomerRequest = async (req: any, res: any) => { }); } - let customer = await getOrCreateCustomer({ + const customer = await getOrCreateCustomer({ req, customerId: data.id, customerData: data, @@ -53,7 +50,7 @@ export const handlePostCustomerRequest = async (req: any, res: any) => { withCache: true, }); - let cusDetails = await getCustomerDetails({ + const cusDetails = await getCustomerDetails({ db, customer, org, diff --git a/server/src/internal/customers/handlers/handleUpdateCustomer.ts b/server/src/internal/customers/handlers/handleUpdateCustomer.ts index 2424c5390..2f82499f3 100644 --- a/server/src/internal/customers/handlers/handleUpdateCustomer.ts +++ b/server/src/internal/customers/handlers/handleUpdateCustomer.ts @@ -1,15 +1,17 @@ -import RecaseError from "@/utils/errorUtils.js"; -import { createStripeCli } from "@/external/stripe/utils.js"; -import { CusService } from "@/internal/customers/CusService.js"; -import { notNullish } from "@/utils/genUtils.js"; -import { routeHandler } from "@/utils/routerUtils.js"; import { CreateCustomerSchema, ErrCode, ProcessorType } from "@autumn/shared"; import { StatusCodes } from "http-status-codes"; -import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js"; -import { parseCusExpand } from "../cusUtils/cusUtils.js"; +import { createStripeCli } from "@/external/stripe/utils.js"; +import { CusService } from "@/internal/customers/CusService.js"; import { FeatureService } from "@/internal/features/FeatureService.js"; -import { ExtendedResponse } from "@/utils/models/Request.js"; -import { ExtendedRequest } from "@/utils/models/Request.js"; +import RecaseError from "@/utils/errorUtils.js"; +import { notNullish } from "@/utils/genUtils.js"; +import type { + ExtendedRequest, + ExtendedResponse, +} from "@/utils/models/Request.js"; +import { routeHandler } from "@/utils/routerUtils.js"; +import { parseCusExpand } from "../cusUtils/cusUtils.js"; +import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js"; export const handleUpdateCustomer = async (req: any, res: any) => routeHandler({ @@ -38,7 +40,7 @@ export const handleUpdateCustomer = async (req: any, res: any) => }); } - let newCusData: any = CreateCustomerSchema.parse(req.body); + const newCusData: any = CreateCustomerSchema.parse(req.body); if (req.body.id === null) { throw new RecaseError({ @@ -70,7 +72,7 @@ export const handleUpdateCustomer = async (req: any, res: any) => // Try to update stripe ID let stripeId = originalCustomer.processor?.id; - let newStripeId = newCusData.stripe_id; + const newStripeId = newCusData.stripe_id; if (notNullish(newStripeId) && stripeId !== newStripeId) { const stripeCli = createStripeCli({ org, env: req.env }); @@ -83,16 +85,16 @@ export const handleUpdateCustomer = async (req: any, res: any) => } // 2. Check if customer email is being changed - let oldMetadata = originalCustomer.metadata || {}; - let newMetadata = newCusData.metadata || {}; - for (let key in newMetadata) { + const oldMetadata = originalCustomer.metadata || {}; + const newMetadata = newCusData.metadata || {}; + for (const key in newMetadata) { if (newMetadata[key] === null) { delete newMetadata[key]; delete oldMetadata[key]; } } - let stripeUpdate = { + const stripeUpdate = { email: originalCustomer.email !== newCusData.email ? newCusData.email @@ -123,7 +125,7 @@ export const handleUpdateCustomer = async (req: any, res: any) => }, }); - let finalCustomer = await CusService.getFull({ + const finalCustomer = await CusService.getFull({ db, idOrInternalId: originalCustomer.internal_id, orgId: req.orgId, @@ -132,7 +134,7 @@ export const handleUpdateCustomer = async (req: any, res: any) => }); // res.status(200).json({ customer: updatedCustomer }); - let customerDetails = await getCustomerDetails({ + const customerDetails = await getCustomerDetails({ db, customer: finalCustomer, org, diff --git a/server/src/internal/invoices/InvoiceService.ts b/server/src/internal/invoices/InvoiceService.ts index bc37166e1..faf508d41 100644 --- a/server/src/internal/invoices/InvoiceService.ts +++ b/server/src/internal/invoices/InvoiceService.ts @@ -1,10 +1,10 @@ import { + type APIInvoice, + APIInvoiceItemSchema, type Customer, type Feature, type Invoice, type InvoiceItem, - InvoiceItemResponseSchema, - type InvoiceResponse, type InvoiceStatus, invoices, type Organization, @@ -41,7 +41,7 @@ export const processInvoice = ({ (f) => f.internal_id === i.internal_feature_id, ); - return InvoiceItemResponseSchema.parse({ + return APIInvoiceItemSchema.parse({ description: i.description, period_start: i.period_start, period_end: i.period_end, @@ -50,7 +50,7 @@ export const processInvoice = ({ }); }) : undefined, - } as InvoiceResponse; + } as APIInvoice; }; export class InvoiceService { diff --git a/server/src/internal/orgs/onboarding/parseChatProducts.ts b/server/src/internal/orgs/onboarding/parseChatProducts.ts index 1c475d7e2..0d5d98b30 100644 --- a/server/src/internal/orgs/onboarding/parseChatProducts.ts +++ b/server/src/internal/orgs/onboarding/parseChatProducts.ts @@ -1,6 +1,6 @@ import { AppEnv, - CreateProductSchema, + CreateProductV2ParamsSchema, EntInsertSchema, type Entitlement, type Feature, @@ -32,7 +32,7 @@ export const parseChatProducts = async ({ for (const product of chatProducts) { const backendProduct: Product = constructProduct({ - productData: CreateProductSchema.parse({ + productData: CreateProductV2ParamsSchema.parse({ ...product, }), orgId, diff --git a/server/src/internal/products/handlers/handleCreateProduct_old.ts b/server/src/internal/products/handlers/handleCreateProduct_old.ts deleted file mode 100644 index 61292e904..000000000 --- a/server/src/internal/products/handlers/handleCreateProduct_old.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { - APIProductSchema, - CreateProductSchema, - type Entitlement, - ErrCode, - type FreeTrial, - type FullProduct, - type Price, - type Product, - ProductAlreadyExistsError, - type ProductItem, -} from "@autumn/shared"; -import { - handleNewFreeTrial, - validateAndInitFreeTrial, -} from "@/internal/products/free-trials/freeTrialUtils.js"; -import { ProductService } from "@/internal/products/ProductService.js"; -import { handleNewProductItems } from "@/internal/products/product-items/productItemUtils/handleNewProductItems.js"; -import { validateProductItems } from "@/internal/products/product-items/validateProductItems.js"; -import { - constructProduct, - getGroupToDefaults, - initProductInStripe, -} from "@/internal/products/productUtils.js"; -import { JobName } from "@/queue/JobName.js"; -import { addTaskToQueue } from "@/queue/queueUtils.js"; -import RecaseError from "@/utils/errorUtils.js"; -import { - keyToTitle, - notNullish, - nullish, - validateId, -} from "@/utils/genUtils.js"; -import type { ExtendedRequest } from "@/utils/models/Request.js"; -import { routeHandler } from "@/utils/routerUtils.js"; -import { validateOneOffTrial } from "../free-trials/freeTrialUtils.js"; -import { isDefaultTrial } from "../productUtils/classifyProduct.js"; - -const validateCreateProduct = async ({ req }: { req: ExtendedRequest }) => { - const { free_trial, items } = req.body; - const { orgId, env, db, features } = req; - - const productData = CreateProductSchema.parse(req.body); - - validateId("Product", productData.id); - - if (nullish(req.body.name)) { - productData.name = keyToTitle(productData.id); - } - - const existing = await ProductService.get({ - db, - orgId, - env, - id: productData.id, - }); - - // 1. If existing product, throw error - if (existing) { - throw new ProductAlreadyExistsError({ - productId: productData.id, - }); - } - - // 2. Validate items if exist - - if (items && !Array.isArray(items)) { - throw new RecaseError({ - message: "Items must be an array", - code: ErrCode.InvalidRequest, - }); - } else if (items) { - validateProductItems({ - newItems: items, - features, - orgId: req.orgId, - env: req.env, - }); - } - - // 3. Validate free trial if exist - let freeTrial: FreeTrial | null = null; - if (notNullish(free_trial)) { - // console.log("Free trial before:", free_trial); - freeTrial = validateAndInitFreeTrial({ - freeTrial: free_trial, - internalProductId: productData.id, - isCustom: false, - }); - // console.log("Free trial after:", freeTrial); - } - - return { - features, - freeTrial, - productData, - }; -}; - -export const disableCurrentDefault = async ({ - req, - newProduct, - items, - freeTrial, -}: { - req: ExtendedRequest; - newProduct: Product; - items: ProductItem[]; - - freeTrial: FreeTrial | null; -}) => { - const { db, org, env, logger } = req; - let defaultProds = await ProductService.listDefault({ - db, - orgId: org.id, - env, - }); - - defaultProds = defaultProds.filter((prod) => prod.id !== newProduct.id); - - if (defaultProds.length === 0) return; - - const defaults = getGroupToDefaults({ - defaultProds, - })?.[newProduct.group]; - - const willBeDefaultTrial = isDefaultTrial({ - product: { - ...newProduct, - free_trial: freeTrial, - items: items || [], - }, - }); - - if (willBeDefaultTrial) { - // Disable current default trial - const curDefault = defaults?.defaultTrial; - if (curDefault) { - logger.info( - `Disabling trial on cur default trial product: ${curDefault.id}`, - ); - await ProductService.updateByInternalId({ - db, - internalId: curDefault.internal_id, - update: { is_default: false }, - }); - } - } else if (newProduct.is_default) { - const curDefault = defaults?.free; - if (curDefault) { - logger.info(`Disabling trial on cur default product: ${curDefault.id}`); - await ProductService.updateByInternalId({ - db, - internalId: curDefault.internal_id, - update: { is_default: false }, - }); - } - } -}; - -export const handleCreateProduct = async (req: Request, res: any) => - routeHandler({ - req, - res, - action: "POST /products", - handler: async (req, res) => { - const { items } = req.body; - - const { logtail: logger, org, features, env, db } = req; - - const { freeTrial, productData } = await validateCreateProduct({ - req, - }); - - const newProduct = constructProduct({ - productData, - orgId: org.id, - env, - }); - - await disableCurrentDefault({ - req, - newProduct, - items, - freeTrial: freeTrial || null, - }); - - const product = await ProductService.insert({ db, product: newProduct }); - - let prices: Price[] = []; - let entitlements: Entitlement[] = []; - if (notNullish(items)) { - const res = await handleNewProductItems({ - db, - product, - features, - curPrices: [], - curEnts: [], - newItems: items, - logger, - isCustom: false, - newVersion: false, - }); - prices = res.prices; - entitlements = res.entitlements; - } - - await validateOneOffTrial({ - prices, - freeTrial: freeTrial || null, - }); - - await initProductInStripe({ - db, - product: { - ...product, - prices, - entitlements, - } as FullProduct, - org, - env, - logger, - }); - - if (notNullish(freeTrial)) { - await handleNewFreeTrial({ - db, - newFreeTrial: freeTrial, - curFreeTrial: null, - internalProductId: product.internal_id, - isCustom: false, - }); - } - - await addTaskToQueue({ - jobName: JobName.DetectBaseVariant, - payload: { - curProduct: { - ...product, - prices, - entitlements: [], - }, - }, - }); - - res.status(200).json( - APIProductSchema.parse({ - ...product, - autumn_id: product.internal_id, - items: items || [], - free_trial: freeTrial, - }), - ); - }, - }); diff --git a/server/src/internal/products/handlers/handleUpdateProduct/handleUpdateProduct_old.ts b/server/src/internal/products/handlers/handleUpdateProduct/handleUpdateProduct_old.ts index 6448f7e6f..973eef702 100644 --- a/server/src/internal/products/handlers/handleUpdateProduct/handleUpdateProduct_old.ts +++ b/server/src/internal/products/handlers/handleUpdateProduct/handleUpdateProduct_old.ts @@ -1,214 +1,213 @@ -import { - ErrCode, - ProductNotFoundError, - UpdateProductSchema, -} from "@autumn/shared"; -import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js"; -import { FeatureService } from "@/internal/features/FeatureService.js"; -import { OrgService } from "@/internal/orgs/OrgService.js"; -import { handleNewFreeTrial } from "@/internal/products/free-trials/freeTrialUtils.js"; -import { handleNewProductItems } from "@/internal/products/product-items/productItemUtils/handleNewProductItems.js"; -import { RewardProgramService } from "@/internal/rewards/RewardProgramService.js"; -import { JobName } from "@/queue/JobName.js"; -import { addTaskToQueue } from "@/queue/queueUtils.js"; -import RecaseError from "@/utils/errorUtils.js"; -import { notNullish } from "@/utils/genUtils.js"; -import { routeHandler } from "@/utils/routerUtils.js"; -import { validateOneOffTrial } from "../../free-trials/freeTrialUtils.js"; -import { ProductService } from "../../ProductService.js"; -import { productsAreSame } from "../../productUtils/compareProductUtils.js"; -import { initProductInStripe } from "../../productUtils.js"; -import { mapToProductItems } from "../../productV2Utils.js"; -import { - disableCurrentDefault, - handleCreateProduct, -} from "../handleCreateProduct_old.js"; -import { handleVersionProductV2 } from "../handleVersionProduct.js"; -import { handleUpdateProductDetails } from "./updateProductDetails.js"; +// import { +// ErrCode, +// ProductNotFoundError, +// UpdateProductSchema, +// } from "@autumn/shared"; +// import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js"; +// import { FeatureService } from "@/internal/features/FeatureService.js"; +// import { OrgService } from "@/internal/orgs/OrgService.js"; +// import { handleNewFreeTrial } from "@/internal/products/free-trials/freeTrialUtils.js"; +// import { handleNewProductItems } from "@/internal/products/product-items/productItemUtils/handleNewProductItems.js"; +// import { RewardProgramService } from "@/internal/rewards/RewardProgramService.js"; +// import { JobName } from "@/queue/JobName.js"; +// import { addTaskToQueue } from "@/queue/queueUtils.js"; +// import RecaseError from "@/utils/errorUtils.js"; +// import { notNullish } from "@/utils/genUtils.js"; +// import { routeHandler } from "@/utils/routerUtils.js"; +// import { validateOneOffTrial } from "../../free-trials/freeTrialUtils.js"; +// import { ProductService } from "../../ProductService.js"; +// import { productsAreSame } from "../../productUtils/compareProductUtils.js"; +// import { initProductInStripe } from "../../productUtils.js"; +// import { mapToProductItems } from "../../productV2Utils.js"; +// import { +// disableCurrentDefault, +// handleCreateProduct, +// } from "../handleCreateProduct_old.js"; +// import { handleUpdateProductDetails } from "./updateProductDetails.js"; -export const handleUpdateProductV2 = async (req: any, res: any) => - routeHandler({ - req, - res, - action: "Update product", - handler: async () => { - const { productId } = req.params; - const { version, upsert, disable_version } = req.query; - const { orgId, env, logger, db } = req; +// export const handleUpdateProductV2 = async (req: any, res: any) => +// routeHandler({ +// req, +// res, +// action: "Update product", +// handler: async () => { +// const { productId } = req.params; +// const { version, upsert, disable_version } = req.query; +// const { orgId, env, logger, db } = req; - const [features, org, fullProduct, rewardPrograms, _defaultProds] = - await Promise.all([ - FeatureService.getFromReq(req), - OrgService.getFromReq(req), - ProductService.getFull({ - db, - idOrInternalId: productId, - orgId, - env, - version: version ? parseInt(version) : undefined, - allowNotFound: upsert === "true", - }), - RewardProgramService.getByProductId({ - db, - productIds: [productId], - orgId, - env, - }), - ProductService.listDefault({ - db, - orgId, - env, - }), - ]); +// const [features, org, fullProduct, rewardPrograms, _defaultProds] = +// await Promise.all([ +// FeatureService.getFromReq(req), +// OrgService.getFromReq(req), +// ProductService.getFull({ +// db, +// idOrInternalId: productId, +// orgId, +// env, +// version: version ? parseInt(version) : undefined, +// allowNotFound: upsert === "true", +// }), +// RewardProgramService.getByProductId({ +// db, +// productIds: [productId], +// orgId, +// env, +// }), +// ProductService.listDefault({ +// db, +// orgId, +// env, +// }), +// ]); - if (!fullProduct) { - if (upsert === "true") { - await handleCreateProduct(req, res); - return; - } +// if (!fullProduct) { +// if (upsert === "true") { +// await handleCreateProduct(req, res); +// return; +// } - throw new ProductNotFoundError({ productId: productId }); - } +// throw new ProductNotFoundError({ productId: productId }); +// } - const cusProductsCurVersion = - await CusProductService.getByInternalProductId({ - db, - internalProductId: fullProduct.internal_id, - }); +// const cusProductsCurVersion = +// await CusProductService.getByInternalProductId({ +// db, +// internalProductId: fullProduct.internal_id, +// }); - const cusProductExists = cusProductsCurVersion.length > 0; +// const cusProductExists = cusProductsCurVersion.length > 0; - await disableCurrentDefault({ - req, - newProduct: { - ...fullProduct, - ...req.body, - }, - items: - req.body.items || - mapToProductItems({ - prices: fullProduct.prices, - entitlements: fullProduct.entitlements, - features, - }), - freeTrial: req.body.free_trial || fullProduct.free_trial || null, - }); +// await disableCurrentDefault({ +// req, +// newProduct: { +// ...fullProduct, +// ...req.body, +// }, +// items: +// req.body.items || +// mapToProductItems({ +// prices: fullProduct.prices, +// entitlements: fullProduct.entitlements, +// features, +// }), +// freeTrial: req.body.free_trial || fullProduct.free_trial || null, +// }); - await handleUpdateProductDetails({ - db, - curProduct: fullProduct, - newProduct: UpdateProductSchema.parse(req.body), - newFreeTrial: req.body.free_trial, - items: req.body.items, - org, - rewardPrograms, - logger, - }); +// await handleUpdateProductDetails({ +// db, +// curProduct: fullProduct, +// newProduct: UpdateProductSchema.parse(req.body), +// newFreeTrial: req.body.free_trial, +// items: req.body.items, +// org, +// rewardPrograms, +// logger, +// }); - const itemsExist = notNullish(req.body.items); - if (cusProductExists && itemsExist) { - if (disable_version === "true") { - throw new RecaseError({ - message: "Cannot auto save product as there are existing customers", - code: ErrCode.InvalidRequest, - statusCode: 400, - }); - } +// const itemsExist = notNullish(req.body.items); +// if (cusProductExists && itemsExist) { +// if (disable_version === "true") { +// throw new RecaseError({ +// message: "Cannot auto save product as there are existing customers", +// code: ErrCode.InvalidRequest, +// statusCode: 400, +// }); +// } - const { itemsSame, freeTrialsSame } = productsAreSame({ - newProductV2: req.body, - curProductV1: fullProduct, - features, - }); - const productSame = itemsSame && freeTrialsSame; +// const { itemsSame, freeTrialsSame } = productsAreSame({ +// newProductV2: req.body, +// curProductV1: fullProduct, +// features, +// }); +// const productSame = itemsSame && freeTrialsSame; - if (!productSame) { - await handleVersionProductV2({ - req, - res, - latestProduct: fullProduct, - org, - env, - items: req.body.items, - freeTrial: req.body.free_trial, - }); - return; - } - res.status(200).send(fullProduct); - return; - } +// if (!productSame) { +// // await handleVersionProductV2({ +// // req, +// // res, +// // latestProduct: fullProduct, +// // org, +// // env, +// // items: req.body.items, +// // freeTrial: req.body.free_trial, +// // }); +// return; +// } +// res.status(200).send(fullProduct); +// return; +// } - const { items, free_trial } = req.body; +// const { items, free_trial } = req.body; - if (free_trial !== undefined) { - await validateOneOffTrial({ - prices: fullProduct.prices, - freeTrial: free_trial, - }); - } +// if (free_trial !== undefined) { +// await validateOneOffTrial({ +// prices: fullProduct.prices, +// freeTrial: free_trial, +// }); +// } - await handleNewProductItems({ - db, - curPrices: fullProduct.prices, - curEnts: fullProduct.entitlements, - newItems: items, - features, - product: fullProduct, - logger, - isCustom: false, - }); +// await handleNewProductItems({ +// db, +// curPrices: fullProduct.prices, +// curEnts: fullProduct.entitlements, +// newItems: items, +// features, +// product: fullProduct, +// logger, +// isCustom: false, +// }); - // New full product - const newFullProduct = await ProductService.getFull({ - db, - idOrInternalId: fullProduct.id, - orgId, - env, - }); +// // New full product +// const newFullProduct = await ProductService.getFull({ +// db, +// idOrInternalId: fullProduct.id, +// orgId, +// env, +// }); - if (free_trial !== undefined) { - await validateOneOffTrial({ - prices: newFullProduct.prices, - freeTrial: free_trial, - }); +// if (free_trial !== undefined) { +// await validateOneOffTrial({ +// prices: newFullProduct.prices, +// freeTrial: free_trial, +// }); - await handleNewFreeTrial({ - db, - curFreeTrial: fullProduct.free_trial, - newFreeTrial: free_trial, - internalProductId: fullProduct.internal_id, - isCustom: false, - product: fullProduct, - }); - } +// await handleNewFreeTrial({ +// db, +// curFreeTrial: fullProduct.free_trial, +// newFreeTrial: free_trial, +// internalProductId: fullProduct.internal_id, +// isCustom: false, +// product: fullProduct, +// }); +// } - // New full product - await initProductInStripe({ - db, - product: newFullProduct, - org, - env, - logger, - }); +// // New full product +// await initProductInStripe({ +// db, +// product: newFullProduct, +// org, +// env, +// logger, +// }); - logger.info("Adding task to queue to detect base variant"); - await addTaskToQueue({ - jobName: JobName.DetectBaseVariant, - payload: { - curProduct: newFullProduct, - }, - }); +// logger.info("Adding task to queue to detect base variant"); +// await addTaskToQueue({ +// jobName: JobName.DetectBaseVariant, +// payload: { +// curProduct: newFullProduct, +// }, +// }); - await addTaskToQueue({ - jobName: JobName.RewardMigration, - payload: { - oldPrices: fullProduct.prices, - productId: fullProduct.id, - orgId: org.id, - env, - }, - }); - res.status(200).send({ message: "Product updated" }); - return; - }, - }); +// await addTaskToQueue({ +// jobName: JobName.RewardMigration, +// payload: { +// oldPrices: fullProduct.prices, +// productId: fullProduct.id, +// orgId: org.id, +// env, +// }, +// }); +// res.status(200).send({ message: "Product updated" }); +// return; +// }, +// }); diff --git a/server/src/internal/products/productUtils/classifyProduct.ts b/server/src/internal/products/productUtils/classifyProduct.ts index 80908ea52..e8b6d733b 100644 --- a/server/src/internal/products/productUtils/classifyProduct.ts +++ b/server/src/internal/products/productUtils/classifyProduct.ts @@ -1,7 +1,8 @@ import type { - CreateProductParams, + CreateProductV2Params, FullProduct, Price, + ProductItem, ProductV2, } from "@autumn/shared"; import { pricesOnlyOneOff } from "../prices/priceUtils.js"; @@ -39,16 +40,18 @@ export const isMainProduct = ({ export const isFreeProductV2 = ({ product, }: { - product: ProductV2 | CreateProductParams; + product: ProductV2 | CreateProductV2Params; }) => { - return (product.items || []).every((item) => isFeatureItem(item)); + return (product.items || []).every((item: ProductItem) => + isFeatureItem(item), + ); }; export const isDefaultTrial = ({ product, skipDefault = false, }: { - product: ProductV2 | CreateProductParams; + product: ProductV2 | CreateProductV2Params; skipDefault?: boolean; }) => { return ( diff --git a/shared/api/core/attachModels.ts b/shared/api/core/attachModels.ts new file mode 100644 index 000000000..84198fb01 --- /dev/null +++ b/shared/api/core/attachModels.ts @@ -0,0 +1,143 @@ +import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js"; +import { CreateFreeTrialSchema } from "@models/productModels/freeTrialModels/freeTrialModels.js"; +import { ProductItemSchema } from "@models/productV2Models/productItemModels/productItemModels.js"; +import { z } from "zod/v4"; +import { notNullish } from "../../utils/utils.js"; + +export const ProductOptions = z.object({ + product_id: z.string(), + quantity: z.number().nullish(), + entity_id: z.string().nullish(), + options: z.array(FeatureOptionsSchema).nullish(), +}); + +export const ExtAttachBodySchema = z.object({ + // Customer / Entity Info + + customer_id: z + .string() + .describe("ID of the customer to attach the product to"), + + customer_data: z + .any() + .nullish() + .describe("Customer data if using attach to auto create customer"), + + entity_id: z.string().nullish(), + entity_data: z.any().nullish(), + + // Product Info + product_id: z.string().nullish(), + product_ids: z.array(z.string()).min(1).nullish(), + options: z.array(FeatureOptionsSchema).nullish(), + free_trial: z.boolean(), + + // Others + success_url: z.string().optional(), + force_checkout: z.boolean().optional(), + checkout_session_params: z.any().optional(), + reward: z.string().or(z.array(z.string())).optional(), + invoice: z.boolean().optional(), + + // Checkout params + skip_checkout: z.boolean().optional(), + setup_payment: z.boolean().optional(), +}); + +export const AttachBodySchema = ExtAttachBodySchema.extend({ + products: z.array(ProductOptions).nullish(), + + // Custom Product + is_custom: z.boolean().optional(), + items: z.array(ProductItemSchema).optional(), + + // Override free_trial to support CreateFreeTrialSchema + free_trial: CreateFreeTrialSchema.or(z.boolean()).optional(), + + // New Version + version: z.number().optional(), + + // Others + invoice_only: z.boolean().optional(), + metadata: z.any().optional(), + billing_cycle_anchor: z.number().optional(), + enable_product_immediately: z.boolean().optional(), + finalize_invoice: z.boolean().optional(), +}) + .refine( + (data) => { + if (!data.product_id && !data.product_ids && !data.products) { + return false; + } + + return true; + }, + { + message: "`product_id` is not provided", + }, + ) + .refine( + (data) => { + if (data.product_id && data.product_ids) { + return false; + } + + return true; + }, + { + message: "provide either one `product_id` or `product_ids`", + }, + ) + .refine( + (data) => { + if ( + notNullish(data.product_ids) && + new Set(data.product_ids).size !== data.product_ids!.length + ) { + return false; + } + + return true; + }, + { + message: "Can't pass in duplicate product_ids", + }, + ) + .refine( + (data) => { + if (data.product_ids && data.is_custom) { + return false; + } + + return true; + }, + { + message: "Can't pass in product_ids if is_custom is true", + }, + ) + .refine( + (data) => { + if (data.items && !data.is_custom) { + return false; + } + + return true; + }, + { + message: "Can't pass in items if is_custom is false", + }, + ); + +export const AttachResultSchema = z.object({ + customer_id: z.string(), + product_ids: z.array(z.string()), + code: z.string(), + message: z.string(), + + checkout_url: z.string().nullish(), + invoice: z.any().nullish(), +}); + +export type AttachBody = z.infer; +export type ProductOptions = z.infer; +export type AttachResult = z.infer; diff --git a/shared/api/core/checkoutModels.ts b/shared/api/core/checkoutModels.ts new file mode 100644 index 000000000..aa878c0ec --- /dev/null +++ b/shared/api/core/checkoutModels.ts @@ -0,0 +1,42 @@ +import { APIProductSchema } from "@api/products/apiProduct.js"; +import { APIProductItemSchema } from "@api/products/apiProductItem.js"; +import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js"; +import { z } from "zod/v4"; +import { AttachBodySchema, ExtAttachBodySchema } from "./attachModels.js"; + +export const ExtCheckoutParamsSchema = ExtAttachBodySchema.extend({ + setup_payment: z.boolean().optional(), +}); + +export const CheckoutParamsSchema = AttachBodySchema.extend({ + skip_checkout: z.boolean().optional(), + setup_payment: z.boolean().optional(), +}); + +// RESULT +export const CheckoutLineSchema = z.object({ + description: z.string(), + amount: z.number(), + item: APIProductItemSchema.nullish(), +}); + +export const CheckoutResponseSchema = z.object({ + url: z.string().nullish(), + customer_id: z.string(), + lines: z.array(CheckoutLineSchema), + product: APIProductSchema.nullish(), + current_product: APIProductSchema.nullish(), + options: z.array(FeatureOptionsSchema).nullish(), + total: z.number().nullish(), + currency: z.string().nullish(), + has_prorations: z.boolean().nullish(), + next_cycle: z + .object({ + starts_at: z.number().nullish(), + total: z.number().nullish(), + }) + .nullish(), +}); + +export type CheckoutLine = z.infer; +export type CheckoutParams = z.infer; diff --git a/shared/api/core/coreOpenApi.ts b/shared/api/core/coreOpenApi.ts new file mode 100644 index 000000000..984745d6d --- /dev/null +++ b/shared/api/core/coreOpenApi.ts @@ -0,0 +1,43 @@ +import { + AttachResultSchema, + CheckoutResponseSchema, + ExtAttachBodySchema, + ExtCheckoutParamsSchema, +} from "@api/models.js"; + +export const coreOps = { + "/core": { + "/attach": { + post: { + summary: "Attach Product", + tags: ["core"], + requestBody: { + content: { + "application/json": { schema: ExtAttachBodySchema }, + }, + }, + responses: { + "200": { + description: "200 OK", + content: { "application/json": { schema: AttachResultSchema } }, + }, + }, + }, + }, + "/checkout": { + post: { + summary: "Checkout", + tags: ["core"], + requestBody: { + content: { "application/json": { schema: ExtCheckoutParamsSchema } }, + }, + responses: { + "200": { + description: "200 OK", + content: { "application/json": { schema: CheckoutResponseSchema } }, + }, + }, + }, + }, + }, +}; diff --git a/shared/api/customers/apiCusV1.ts b/shared/api/customers/apiCusV1.ts deleted file mode 100644 index 39e195476..000000000 --- a/shared/api/customers/apiCusV1.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { AppEnv } from "@models/genModels/genEnums.js"; -import { z } from "zod/v4"; -// import { AppEnv } from "../genModels/genEnums.js"; -// import { RewardResponseSchema } from "../rewardModels/rewardModels/rewardResponseModels.js"; -// import { CusProductResponseSchema } from "./cusResModels/cusProductResponse.js"; -// import { CusReferralResponseSchema } from "./cusResModels/cusReferralsResponse.js"; -// import { UpcomingInvoiceResponseSchema } from "./cusResModels/upcomingInvoiceResponse.js"; -// import { EntityResponseSchema } from "./entityModels/entityResModels.js"; -// import { InvoiceResponseSchema } from "./invoiceModels/invoiceResponseModels.js"; - -// export const TrialUsedResponseSchema = z.object({ -// product_id: z.string().meta({ -// description: "The ID of the product for which the trial was used", -// example: "prod_123", -// }), -// customer_id: z.string().meta({ -// description: "The ID of the customer who used the trial", -// example: "cus_123", -// }), -// fingerprint: z.string().nullish().meta({ -// description: "Device/browser fingerprint when the trial was used", -// example: "fp_1234567890abcdef", -// }), -// }); - -export const APICustomerSchema = z.object({ - id: z.string().nullable().meta({ - description: "Your internal ID for the customer", - example: "cus_123", - }), - - name: z.string().nullable().meta({ - description: "The customer's full name", - example: "John Doe", - }), - - email: z.string().nullable().meta({ - description: "The customer's email address", - example: "john.doe@example.com", - }), - - fingerprint: z.string().nullable().meta({ - description: "Unique device/browser fingerprint for the customer", - example: "fp_1234567890abcdef", - }), - - stripe_id: z.string().nullable().meta({ - description: "The customer's Stripe customer ID", - example: "cus_1234567890abcdef", - }), - - created_at: z.number().meta({ - description: - "The date and time the customer was created in milliseconds since epoch", - example: 1717000000, - }), - - env: z.enum(AppEnv), - - // products: z.array(CusProductResponseSchema), - // features: z.any(), - // invoices: z.array(InvoiceResponseSchema).optional(), - // trials_used: z.array(TrialUsedResponseSchema).optional(), - // rewards: RewardResponseSchema.nullish(), - // metadata: z.record(z.any(), z.any()).default({}), - // entities: z.array(EntityResponseSchema).optional(), - // referrals: z.array(CusReferralResponseSchema).optional(), - // payment_method: z.any().nullish(), - // upcoming_invoice: UpcomingInvoiceResponseSchema.nullish(), -}); - -// export const IntAPICustomer = CusResponseV2Schema.extend({ -// autumn_id: z.string().nullish().meta({ -// description: "Internal Autumn system ID for the customer", -// example: "autumn_cus_1234567890", -// }), -// }); - -export type APICustomer = z.infer; -// export type CusProductResponse = z.infer; diff --git a/shared/api/customers/apiCustomer.ts b/shared/api/customers/apiCustomer.ts new file mode 100644 index 000000000..95a9e36ee --- /dev/null +++ b/shared/api/customers/apiCustomer.ts @@ -0,0 +1,51 @@ +import { APICusFeatureSchema } from "@api/customers/components/apiCusFeature.js"; +import { APICusProductSchema } from "@api/customers/components/apiCusProduct.js"; +import { APICusReferralSchema } from "@api/customers/components/apiCusReferral.js"; +import { APICusRewardsSchema } from "@api/models.js"; +import { APIInvoiceSchema } from "@api/others/apiInvoice.js"; +import { EntityResponseSchema } from "@models/cusModels/entityModels/entityResModels.js"; +import { AppEnv } from "@models/genModels/genEnums.js"; +import { z } from "zod/v4"; +import { APICusUpcomingInvoiceSchema } from "./components/apiCusUpcomingInvoice.js"; + +export const APITrialsUsedSchema = z.object({ + product_id: z.string(), + customer_id: z.string(), + fingerprint: z.string().nullish(), +}); + +export const APICustomerSchema = z.object({ + // Internal fields + autumn_id: z.string().nullish(), + + id: z.string().nullable().meta({ + description: "Your internal ID for the customer", + example: "cus_123", + }), + + created_at: z.number().meta({ + description: + "The date and time the customer was created in milliseconds since epoch", + example: 1717000000, + }), + + name: z.string().nullable(), + email: z.string().nullable(), + fingerprint: z.string().nullable(), + stripe_id: z.string().nullable().default(null), + env: z.enum(AppEnv), + + products: z.array(APICusProductSchema), + features: z.record(z.string(), APICusFeatureSchema), + invoices: z.array(APIInvoiceSchema).optional(), + trials_used: z.array(APITrialsUsedSchema).optional(), + + rewards: APICusRewardsSchema.nullish(), + metadata: z.record(z.any(), z.any()).default({}), + entities: z.array(EntityResponseSchema).optional(), + referrals: z.array(APICusReferralSchema).optional(), + upcoming_invoice: APICusUpcomingInvoiceSchema.nullish(), + payment_method: z.any().nullish(), +}); + +export type APICustomer = z.infer; diff --git a/shared/models/cusModels/cusResModels/cusFeatureResponse.ts b/shared/api/customers/components/apiCusFeature.ts similarity index 68% rename from shared/models/cusModels/cusResModels/cusFeatureResponse.ts rename to shared/api/customers/components/apiCusFeature.ts index 52f212e67..14b3cd2c5 100644 --- a/shared/models/cusModels/cusResModels/cusFeatureResponse.ts +++ b/shared/api/customers/components/apiCusFeature.ts @@ -1,15 +1,16 @@ +import { EntInterval } from "@models/productModels/entModels/entEnums.js"; +import { ProductItemFeatureType } from "@models/productV2Models/productItemModels/productItemModels.js"; import { z } from "zod/v4"; -import { EntInterval } from "../../productModels/entModels/entEnums.js"; -import { ProductItemFeatureType } from "../../productV2Models/productItemModels/productItemModels.js"; export const CusRolloverSchema = z.object({ balance: z.number(), expires_at: z.number(), }); +// OLD CUS FEATURE RESPONSE export const CusEntResponseSchema = z.object({ feature_id: z.string(), - interval: z.nativeEnum(EntInterval).nullish(), + interval: z.enum(EntInterval).nullish(), interval_count: z.number().nullish(), unlimited: z.boolean().nullish(), balance: z.number().nullish(), // @@ -21,8 +22,9 @@ export const CusEntResponseSchema = z.object({ rollovers: z.array(CusRolloverSchema).nullish(), }); -export const CoreCusFeatureResponseSchema = z.object({ - interval: z.nativeEnum(EntInterval).or(z.literal("multiple")).nullish(), +// NEW CUS FEATURE RESPONSE +export const CoreCusFeatureSchema = z.object({ + interval: z.enum(EntInterval).or(z.literal("multiple")).nullish(), interval_count: z.number().nullish(), unlimited: z.boolean().nullish(), balance: z.number().nullish(), @@ -34,7 +36,7 @@ export const CoreCusFeatureResponseSchema = z.object({ breakdown: z .array( z.object({ - interval: z.nativeEnum(EntInterval), + interval: z.enum(EntInterval), interval_count: z.number().nullish(), balance: z.number().nullish(), usage: z.number().nullish(), @@ -56,15 +58,15 @@ export const CoreCusFeatureResponseSchema = z.object({ rollovers: z.array(CusRolloverSchema).nullish(), }); -export const CusEntResponseV2Schema = z +export const APICusFeatureSchema = z .object({ id: z.string(), - type: z.nativeEnum(ProductItemFeatureType), + type: z.enum(ProductItemFeatureType), name: z.string().nullish(), }) - .extend(CoreCusFeatureResponseSchema.shape); + .extend(CoreCusFeatureSchema.shape); -export const CheckResponseSchema = z +export const CheckResultSchema = z .object({ allowed: z.boolean(), customer_id: z.string(), @@ -73,9 +75,9 @@ export const CheckResponseSchema = z required_balance: z.number(), code: z.string(), }) - .extend(CoreCusFeatureResponseSchema.shape); + .extend(CoreCusFeatureSchema.shape); export type CusEntResponse = z.infer; -export type CusEntResponseV2 = z.infer; -export type CheckResponse = z.infer; +export type CusEntResponseV2 = z.infer; +export type CheckResponse = z.infer; export type CusRollover = z.infer; diff --git a/shared/models/cusModels/cusResModels/cusProductResponse.ts b/shared/api/customers/components/apiCusProduct.ts similarity index 60% rename from shared/models/cusModels/cusResModels/cusProductResponse.ts rename to shared/api/customers/components/apiCusProduct.ts index fe7bf4813..a216bed1d 100644 --- a/shared/models/cusModels/cusResModels/cusProductResponse.ts +++ b/shared/api/customers/components/apiCusProduct.ts @@ -1,15 +1,11 @@ import { APIProductItemSchema } from "@api/products/apiProductItem.js"; import { z } from "zod/v4"; -import { CusProductStatus } from "../../cusProductModels/cusProductEnums.js"; -export const CusProductResponseSchema = z.object({ +export const APICusProductSchema = z.object({ id: z.string(), name: z.string().nullable(), group: z.string().nullable(), - status: z.nativeEnum(CusProductStatus), - // created_at: z.number(), - canceled: z.boolean().nullish(), - trialing: z.boolean().nullish(), + status: z.enum(["active", "expired", "scheduled"]), canceled_at: z.number().nullish(), started_at: z.number(), @@ -18,18 +14,14 @@ export const CusProductResponseSchema = z.object({ version: z.number().nullish(), stripe_subscription_ids: z.array(z.string()).nullish(), - current_period_start: z.number().nullish(), current_period_end: z.number().nullish(), + entity_id: z.string().nullish(), + items: z.array(APIProductItemSchema).nullish(), + quantity: z.number().optional(), - prepaid_quantities: z - .array( - z.object({ - quantity: z.number(), - feature_id: z.string(), - }), - ) - .nullish(), }); + +export type APICusProduct = z.infer; diff --git a/shared/api/customers/cusResV2/cusReferralsResponse.ts b/shared/api/customers/components/apiCusReferral.ts similarity index 63% rename from shared/api/customers/cusResV2/cusReferralsResponse.ts rename to shared/api/customers/components/apiCusReferral.ts index 2e1b52d42..184751cfb 100644 --- a/shared/api/customers/cusResV2/cusReferralsResponse.ts +++ b/shared/api/customers/components/apiCusReferral.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; -export const CusReferralResponseSchema = z.object({ +export const APICusReferralSchema = z.object({ program_id: z.string(), customer: z.object({ id: z.string(), @@ -11,4 +11,4 @@ export const CusReferralResponseSchema = z.object({ created_at: z.number(), }); -export type CusReferralResponse = z.infer; +export type APICusReferralResponse = z.infer; diff --git a/shared/api/customers/components/apiCusUpcomingInvoice.ts b/shared/api/customers/components/apiCusUpcomingInvoice.ts new file mode 100644 index 000000000..0b6036c2c --- /dev/null +++ b/shared/api/customers/components/apiCusUpcomingInvoice.ts @@ -0,0 +1,18 @@ +import { APIDiscountSchema } from "@api/others/apiDiscount.js"; +import { z } from "zod/v4"; + +export const APICusUpcomingInvoiceSchema = z.object({ + lines: z.array( + z.object({ + product_id: z.string().nullish(), + description: z.string(), + amount: z.number(), + }), + ), + discounts: z.array(APIDiscountSchema), + subtotal: z.number(), + total: z.number(), + currency: z.string(), +}); + +export type APICusUpcomingInvoice = z.infer; diff --git a/shared/api/customers/components/cusProductRes.ts b/shared/api/customers/components/cusProductRes.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/shared/api/customers/cusResV1/cusResV1.ts b/shared/api/customers/cusResV1/cusResV1.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/shared/api/customers/cusResV2/cusFeatureResponse.ts b/shared/api/customers/cusResV2/cusFeatureResponse.ts deleted file mode 100644 index 99bc9b93d..000000000 --- a/shared/api/customers/cusResV2/cusFeatureResponse.ts +++ /dev/null @@ -1,81 +0,0 @@ -// import { z } from "zod/v4"; -// import { EntInterval } from "../../productModels/entModels/entEnums.js"; -// import { ProductItemFeatureType } from "../../productV2Models/productItemModels/productItemModels.js"; - -// export const CusRolloverSchema = z.object({ -// balance: z.number(), -// expires_at: z.number(), -// }); - -// export const CusEntResponseSchema = z.object({ -// feature_id: z.string(), -// interval: z.nativeEnum(EntInterval).nullish(), -// interval_count: z.number().nullish(), -// unlimited: z.boolean().nullish(), -// balance: z.number().nullish(), // -// usage: z.number().nullish(), -// included_usage: z.number().nullish(), -// next_reset_at: z.number().nullish(), -// overage_allowed: z.boolean().nullish(), -// usage_limit: z.number().nullish(), -// rollovers: z.array(CusRolloverSchema).nullish(), -// }); - -// export const CoreCusFeatureResponseSchema = z.object({ -// interval: z.nativeEnum(EntInterval).or(z.literal("multiple")).nullish(), -// interval_count: z.number().nullish(), -// unlimited: z.boolean().nullish(), -// balance: z.number().nullish(), -// usage: z.number().nullish(), -// included_usage: z.number().nullish(), -// next_reset_at: z.number().nullish(), -// overage_allowed: z.boolean().nullish(), - -// breakdown: z -// .array( -// z.object({ -// interval: z.nativeEnum(EntInterval), -// interval_count: z.number().nullish(), -// balance: z.number().nullish(), -// usage: z.number().nullish(), -// included_usage: z.number().nullish(), -// next_reset_at: z.number().nullish(), -// }), -// ) -// .nullish(), -// credit_schema: z -// .array( -// z.object({ -// feature_id: z.string(), -// credit_amount: z.number(), -// }), -// ) -// .nullish(), - -// usage_limit: z.number().nullish(), -// rollovers: z.array(CusRolloverSchema).nullish(), -// }); - -// export const CusEntResponseV2Schema = z -// .object({ -// id: z.string(), -// type: z.nativeEnum(ProductItemFeatureType), -// name: z.string().nullish(), -// }) -// .extend(CoreCusFeatureResponseSchema.shape); - -// export const CheckResponseSchema = z -// .object({ -// allowed: z.boolean(), -// customer_id: z.string(), -// feature_id: z.string(), -// entity_id: z.string().nullish(), -// required_balance: z.number(), -// code: z.string(), -// }) -// .extend(CoreCusFeatureResponseSchema.shape); - -// export type CusEntResponse = z.infer; -// export type CusEntResponseV2 = z.infer; -// export type CheckResponse = z.infer; -// export type CusRollover = z.infer; diff --git a/shared/api/customers/cusResV2/upcomingInvoiceResponse.ts b/shared/api/customers/cusResV2/upcomingInvoiceResponse.ts deleted file mode 100644 index e97728bb8..000000000 --- a/shared/api/customers/cusResV2/upcomingInvoiceResponse.ts +++ /dev/null @@ -1,20 +0,0 @@ -// import { z } from "zod/v4"; -// import { DiscountResponseSchema } from "../../rewardModels/rewardModels/rewardResponseModels.js"; - -// export const UpcomingInvoiceResponseSchema = z.object({ -// lines: z.array( -// z.object({ -// product_id: z.string().nullish(), -// description: z.string(), -// amount: z.number(), -// }), -// ), -// discounts: z.array(DiscountResponseSchema), -// subtotal: z.number(), -// total: z.number(), -// currency: z.string(), -// }); - -// export type UpcomingInvoiceResponse = z.infer< -// typeof UpcomingInvoiceResponseSchema -// >; diff --git a/shared/api/models.ts b/shared/api/models.ts index d97fa18f6..3a1e20318 100644 --- a/shared/api/models.ts +++ b/shared/api/models.ts @@ -1,10 +1,24 @@ -export * from "./features/apiFeature.js"; +// Core +export * from "./core/attachModels.js"; +export * from "./core/checkoutModels.js"; +// Customers + +export * from "./customers/apiCustomer.js"; +export * from "./customers/components/apiCusFeature.js"; +export * from "./customers/components/apiCusProduct.js"; +export * from "./customers/components/apiCusReferral.js"; + +// Features +export * from "./features/apiFeature.js"; +// Others +export * from "./others/apiDiscount.js"; +export * from "./others/apiInvoice.js"; // Product export * from "./products/apiFreeTrial.js"; export * from "./products/apiProduct.js"; export * from "./products/apiProductItem.js"; -export * from "./products/operations/createProductParams.js"; +export * from "./products/productOpModels.js"; // export * from "./products/apiFreeTrial.js"; // export * from "./products/apiProduct.js"; diff --git a/shared/api/openapi.ts b/shared/api/openapi.ts index 1680a2d79..619166d9c 100644 --- a/shared/api/openapi.ts +++ b/shared/api/openapi.ts @@ -3,8 +3,8 @@ import { AppEnv } from "@models/genModels/genEnums.js"; import yaml from "yaml"; import { z } from "zod/v4"; import { createDocument } from "zod-openapi"; -import { APIProductSchema } from "./products/apiProduct.js"; -import { CreateProductParamsSchema } from "./products/operations/createProductParams.js"; +import { coreOps } from "./core/coreOpenApi.js"; +import { productOps } from "./products/productsOpenApi.js"; const API_VERSION = "1.2.0"; @@ -50,33 +50,8 @@ const document = createDocument({ }, paths: { - "/products": { - post: { - summary: "Create Product", - tags: ["products"], - requestBody: { - content: { - "application/json": { schema: CreateProductParamsSchema }, - }, - }, - responses: { - "200": { - description: "200 OK", - content: { - "application/json": { schema: APIProductSchema }, - }, - }, - }, - // responses: withErrorResponses({ - // "200": { - // description: "200 OK", - // content: { - // "application/json": { schema: APIProductSchema }, - // }, - // }, - // }), - }, - }, + ...productOps, + ...coreOps, }, }); diff --git a/shared/api/others/apiDiscount.ts b/shared/api/others/apiDiscount.ts new file mode 100644 index 000000000..675eae384 --- /dev/null +++ b/shared/api/others/apiDiscount.ts @@ -0,0 +1,29 @@ +import { + CouponDurationType, + RewardType, +} from "@models/rewardModels/rewardModels/rewardEnums.js"; +import { z } from "zod/v4"; + +export const APIDiscountSchema = z.object({ + id: z.string(), // either from Autumn or Stripe + name: z.string(), + + type: z.enum(RewardType), + discount_value: z.number(), + duration_type: z.enum(CouponDurationType), + duration_value: z.number().nullish(), + currency: z.string().nullish(), + + start: z.number().nullish(), + end: z.number().nullish(), + + subscription_id: z.string().nullish(), + total_discount_amount: z.number().nullish(), +}); + +export const APICusRewardsSchema = z.object({ + discounts: z.array(APIDiscountSchema), +}); + +export type APIDiscount = z.infer; +export type APIDiscountList = z.infer; diff --git a/shared/models/cusModels/invoiceModels/invoiceResponseModels.ts b/shared/api/others/apiInvoice.ts similarity index 65% rename from shared/models/cusModels/invoiceModels/invoiceResponseModels.ts rename to shared/api/others/apiInvoice.ts index e674b4229..889171b62 100644 --- a/shared/models/cusModels/invoiceModels/invoiceResponseModels.ts +++ b/shared/api/others/apiInvoice.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; -export const InvoiceItemResponseSchema = z.object({ +export const APIInvoiceItemSchema = z.object({ description: z.string(), period_start: z.number(), period_end: z.number(), @@ -9,7 +9,7 @@ export const InvoiceItemResponseSchema = z.object({ feature_name: z.string().optional(), }); -export const InvoiceResponseSchema = z.object({ +export const APIInvoiceSchema = z.object({ product_ids: z.array(z.string()), stripe_id: z.string(), status: z.string(), @@ -21,5 +21,5 @@ export const InvoiceResponseSchema = z.object({ // period_end: z.number().nullish(), }); -export const InvoiceResponseListSchema = z.array(InvoiceResponseSchema); -export type InvoiceResponse = z.infer; +export const APIInvoiceListSchema = z.array(APIInvoiceSchema); +export type APIInvoice = z.infer; diff --git a/shared/api/products/operations/createProductParams.ts b/shared/api/products/productOpModels.ts similarity index 65% rename from shared/api/products/operations/createProductParams.ts rename to shared/api/products/productOpModels.ts index 70eb7fc5c..a4c6443fd 100644 --- a/shared/api/products/operations/createProductParams.ts +++ b/shared/api/products/productOpModels.ts @@ -7,21 +7,26 @@ export const CreateProductItemParamsSchema = ProductItemSchema; // Base product params -export const CreateProductV2ParamsSchema = z.object({ - id: z.string().nonempty().regex(idRegex), +export const CreateProductV2ParamsSchema = z + .object({ + id: z.string().nonempty().regex(idRegex), - name: z.string().refine((val) => val.length > 0, { - message: "name must be a non-empty string", - }), + name: z.string().refine((val) => val.length > 0, { + message: "name must be a non-empty string", + }), - is_add_on: z.boolean().default(false), - is_default: z.boolean().default(false), - version: z.number().optional(), - group: z.string().default(""), + is_add_on: z.boolean().default(false), + is_default: z.boolean().default(false), + version: z.number().optional(), + group: z.string().default(""), - items: z.array(CreateProductItemParamsSchema).optional(), - free_trial: CreateFreeTrialSchema.nullish().default(null), -}); + items: z.array(CreateProductItemParamsSchema).optional(), + free_trial: CreateFreeTrialSchema.nullish().default(null), + }) + .meta({ + id: "CreateProductParams", + description: "Create Product", + }); export const UpdateProductV2ParamsSchema = CreateProductV2ParamsSchema.extend({ id: z.string().nonempty().regex(idRegex).optional(), @@ -39,6 +44,9 @@ export const UpdateProductV2ParamsSchema = CreateProductV2ParamsSchema.extend({ // items: z.array(CreateProductItemParamsSchema).optional(), free_trial: CreateFreeTrialSchema.nullish(), +}).meta({ + id: "UpdateProductParams", + description: "Update Product", }); export const UpdateProductQuerySchema = z.object({ diff --git a/shared/api/products/productsOpenApi.ts b/shared/api/products/productsOpenApi.ts index 0a0305749..4e2ecbbf5 100644 --- a/shared/api/products/productsOpenApi.ts +++ b/shared/api/products/productsOpenApi.ts @@ -1,5 +1,8 @@ +import { + CreateProductV2ParamsSchema, + UpdateProductV2ParamsSchema, +} from "@api/models.js"; import { APIProductSchema } from "./apiProduct.js"; -import { CreateProductParamsSchema } from "./operations/createProductParams.js"; export const productOps = { "/products": { @@ -8,7 +11,22 @@ export const productOps = { tags: ["products"], requestBody: { content: { - "application/json": { schema: CreateProductParamsSchema }, + "application/json": { schema: CreateProductV2ParamsSchema }, + }, + }, + responses: { + "200": { + description: "200 OK", + content: { "application/json": { schema: APIProductSchema } }, + }, + }, + }, + patch: { + summary: "Update Product", + tags: ["products"], + requestBody: { + content: { + "application/json": { schema: UpdateProductV2ParamsSchema }, }, }, responses: { diff --git a/shared/index.ts b/shared/index.ts index e8a1ff866..f6f34eb32 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -39,11 +39,9 @@ export * from "./models/cusModels/cusExpand.js"; // 8. Customer Models export * from "./models/cusModels/cusModels.js"; -export * from "./models/cusModels/cusResModels/cusFeatureResponse.js"; -export * from "./models/cusModels/cusResModels/cusProductResponse.js"; -export * from "./models/cusModels/cusResModels/cusReferralsResponse.js"; + // Cus response -export * from "./models/cusModels/cusResponseModels.js"; + export * from "./models/cusModels/cusTable.js"; export * from "./models/cusModels/entityModels/entityExpand.js"; export * from "./models/cusModels/entityModels/entityModels.js"; @@ -51,7 +49,6 @@ export * from "./models/cusModels/entityModels/entityResModels.js"; export * from "./models/cusModels/entityModels/entityTable.js"; export * from "./models/cusModels/fullCusModel.js"; export * from "./models/cusModels/invoiceModels/invoiceModels.js"; -export * from "./models/cusModels/invoiceModels/invoiceResponseModels.js"; export * from "./models/cusModels/invoiceModels/invoiceTable.js"; export * from "./models/cusProductModels/cusEntModels/cusEntModels.js"; export * from "./models/cusProductModels/cusEntModels/cusEntTable.js"; diff --git a/shared/models/attachModels/attachBody.ts b/shared/models/attachModels/attachBody.ts index 50510cd6c..bebcf2d64 100644 --- a/shared/models/attachModels/attachBody.ts +++ b/shared/models/attachModels/attachBody.ts @@ -1,126 +1,126 @@ -import { z } from "zod/v4"; -import { notNullish } from "../../utils/utils.js"; -import { FeatureOptionsSchema } from "../cusProductModels/cusProductModels.js"; -import { CreateFreeTrialSchema } from "../productModels/freeTrialModels/freeTrialModels.js"; -import { ProductItemSchema } from "../productV2Models/productItemModels/productItemModels.js"; +// import { z } from "zod/v4"; +// import { notNullish } from "../../utils/utils.js"; +// import { FeatureOptionsSchema } from "../cusProductModels/cusProductModels.js"; +// import { CreateFreeTrialSchema } from "../productModels/freeTrialModels/freeTrialModels.js"; +// import { ProductItemSchema } from "../productV2Models/productItemModels/productItemModels.js"; -export const ProductOptions = z.object({ - product_id: z.string(), - quantity: z.number().nullish(), - entity_id: z.string().nullish(), - options: z.array(FeatureOptionsSchema).nullish(), -}); +// export const ProductOptions = z.object({ +// product_id: z.string(), +// quantity: z.number().nullish(), +// entity_id: z.string().nullish(), +// options: z.array(FeatureOptionsSchema).nullish(), +// }); -export const AttachBodySchema = z - .object({ - // Customer Info - customer_id: z - .string() - .describe("ID of the customer to attach the product to"), +// export const AttachBodySchema = z +// .object({ +// // Customer Info +// customer_id: z +// .string() +// .describe("ID of the customer to attach the product to"), - customer_data: z - .any() - .nullish() - .describe("Customer data if using attach to auto create customer"), +// customer_data: z +// .any() +// .nullish() +// .describe("Customer data if using attach to auto create customer"), - // Entity Info - entity_id: z.string().nullish(), - entity_data: z.any().nullish(), +// // Entity Info +// entity_id: z.string().nullish(), +// entity_data: z.any().nullish(), - // Product Info - product_id: z.string().nullish(), - product_ids: z.array(z.string()).min(1).nullish(), - options: z.array(FeatureOptionsSchema).nullish(), +// // Product Info +// product_id: z.string().nullish(), +// product_ids: z.array(z.string()).min(1).nullish(), +// options: z.array(FeatureOptionsSchema).nullish(), - products: z.array(ProductOptions).nullish(), +// products: z.array(ProductOptions).nullish(), - // Custom Product - is_custom: z.boolean().optional(), - items: z.array(ProductItemSchema).optional(), - free_trial: CreateFreeTrialSchema.or(z.boolean()).optional(), +// // Custom Product +// is_custom: z.boolean().optional(), +// items: z.array(ProductItemSchema).optional(), +// free_trial: CreateFreeTrialSchema.or(z.boolean()).optional(), - // New Version - version: z.number().optional(), +// // New Version +// version: z.number().optional(), - // Others - success_url: z.string().optional(), - force_checkout: z.boolean().optional(), - invoice_only: z.boolean().optional(), - metadata: z.any().optional(), - billing_cycle_anchor: z.number().optional(), - checkout_session_params: z.any().optional(), - reward: z.string().or(z.array(z.string())).optional(), - invoice: z.boolean().optional(), - enable_product_immediately: z.boolean().optional(), - finalize_invoice: z.boolean().optional(), +// // Others +// success_url: z.string().optional(), +// force_checkout: z.boolean().optional(), +// invoice_only: z.boolean().optional(), +// metadata: z.any().optional(), +// billing_cycle_anchor: z.number().optional(), +// checkout_session_params: z.any().optional(), +// reward: z.string().or(z.array(z.string())).optional(), +// invoice: z.boolean().optional(), +// enable_product_immediately: z.boolean().optional(), +// finalize_invoice: z.boolean().optional(), - // Checkout params - skip_checkout: z.boolean().optional(), - setup_payment: z.boolean().optional(), - }) - .refine( - (data) => { - if (!data.product_id && !data.product_ids && !data.products) { - return false; - } +// // Checkout params +// skip_checkout: z.boolean().optional(), +// setup_payment: z.boolean().optional(), +// }) +// .refine( +// (data) => { +// if (!data.product_id && !data.product_ids && !data.products) { +// return false; +// } - return true; - }, - { - message: "`product_id` is not provided", - }, - ) - .refine( - (data) => { - if (data.product_id && data.product_ids) { - return false; - } +// return true; +// }, +// { +// message: "`product_id` is not provided", +// }, +// ) +// .refine( +// (data) => { +// if (data.product_id && data.product_ids) { +// return false; +// } - return true; - }, - { - message: "provide either one `product_id` or `product_ids`", - }, - ) - .refine( - (data) => { - if ( - notNullish(data.product_ids) && - new Set(data.product_ids).size !== data.product_ids!.length - ) { - return false; - } +// return true; +// }, +// { +// message: "provide either one `product_id` or `product_ids`", +// }, +// ) +// .refine( +// (data) => { +// if ( +// notNullish(data.product_ids) && +// new Set(data.product_ids).size !== data.product_ids!.length +// ) { +// return false; +// } - return true; - }, - { - message: "Can't pass in duplicate product_ids", - }, - ) - .refine( - (data) => { - if (data.product_ids && data.is_custom) { - return false; - } +// return true; +// }, +// { +// message: "Can't pass in duplicate product_ids", +// }, +// ) +// .refine( +// (data) => { +// if (data.product_ids && data.is_custom) { +// return false; +// } - return true; - }, - { - message: "Can't pass in product_ids if is_custom is true", - }, - ) - .refine( - (data) => { - if (data.items && !data.is_custom) { - return false; - } +// return true; +// }, +// { +// message: "Can't pass in product_ids if is_custom is true", +// }, +// ) +// .refine( +// (data) => { +// if (data.items && !data.is_custom) { +// return false; +// } - return true; - }, - { - message: "Can't pass in items if is_custom is false", - }, - ); +// return true; +// }, +// { +// message: "Can't pass in items if is_custom is false", +// }, +// ); -export type AttachBody = z.infer; -export type ProductOptions = z.infer; +// export type AttachBody = z.infer; +// export type ProductOptions = z.infer; diff --git a/shared/models/attachModels/checkoutModels.ts b/shared/models/attachModels/checkoutModels.ts index 6ca89d0a5..22859c038 100644 --- a/shared/models/attachModels/checkoutModels.ts +++ b/shared/models/attachModels/checkoutModels.ts @@ -1,32 +1,32 @@ -import { APIProductSchema } from "@api/products/apiProduct.js"; -import { APIProductItemSchema } from "@api/products/apiProductItem.js"; +// import { APIProductSchema } from "@api/products/apiProduct.js"; +// import { APIProductItemSchema } from "@api/products/apiProductItem.js"; -import { z } from "zod/v4"; -import { FeatureOptionsSchema } from "../cusProductModels/cusProductModels.js"; +// import { z } from "zod/v4"; +// import { FeatureOptionsSchema } from "../cusProductModels/cusProductModels.js"; -export const CheckoutLineSchema = z.object({ - description: z.string(), - amount: z.number(), - item: APIProductItemSchema.nullish(), -}); +// export const CheckoutLineSchema = z.object({ +// description: z.string(), +// amount: z.number(), +// item: APIProductItemSchema.nullish(), +// }); -export const CheckoutResponseSchema = z.object({ - url: z.string().nullish(), - customer_id: z.string(), - lines: z.array(CheckoutLineSchema), - product: APIProductSchema.nullish(), - current_product: APIProductSchema.nullish(), - options: z.array(FeatureOptionsSchema).nullish(), - total: z.number().nullish(), - currency: z.string().nullish(), - has_prorations: z.boolean().nullish(), - // next_cycle_at: z.number().nullish(), - next_cycle: z - .object({ - starts_at: z.number().nullish(), - total: z.number().nullish(), - }) - .nullish(), -}); +// export const CheckoutResponseSchema = z.object({ +// url: z.string().nullish(), +// customer_id: z.string(), +// lines: z.array(CheckoutLineSchema), +// product: APIProductSchema.nullish(), +// current_product: APIProductSchema.nullish(), +// options: z.array(FeatureOptionsSchema).nullish(), +// total: z.number().nullish(), +// currency: z.string().nullish(), +// has_prorations: z.boolean().nullish(), +// // next_cycle_at: z.number().nullish(), +// next_cycle: z +// .object({ +// starts_at: z.number().nullish(), +// total: z.number().nullish(), +// }) +// .nullish(), +// }); -export type CheckoutLine = z.infer; +// export type CheckoutLine = z.infer; diff --git a/shared/models/cusModels/cusResModels/cusReferralsResponse.ts b/shared/models/cusModels/cusResModels/cusReferralsResponse.ts deleted file mode 100644 index 2e1b52d42..000000000 --- a/shared/models/cusModels/cusResModels/cusReferralsResponse.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { z } from "zod/v4"; - -export const CusReferralResponseSchema = z.object({ - program_id: z.string(), - customer: z.object({ - id: z.string(), - name: z.string().nullish(), - email: z.string().nullish(), - }), - reward_applied: z.boolean(), - created_at: z.number(), -}); - -export type CusReferralResponse = z.infer; diff --git a/shared/models/cusModels/cusResModels/upcomingInvoiceResponse.ts b/shared/models/cusModels/cusResModels/upcomingInvoiceResponse.ts deleted file mode 100644 index a3e09ef57..000000000 --- a/shared/models/cusModels/cusResModels/upcomingInvoiceResponse.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { z } from "zod/v4"; -import { DiscountResponseSchema } from "../../rewardModels/rewardModels/rewardResponseModels.js"; - -export const UpcomingInvoiceResponseSchema = z.object({ - lines: z.array( - z.object({ - product_id: z.string().nullish(), - description: z.string(), - amount: z.number(), - }), - ), - discounts: z.array(DiscountResponseSchema), - subtotal: z.number(), - total: z.number(), - currency: z.string(), -}); - -export type UpcomingInvoiceResponse = z.infer< - typeof UpcomingInvoiceResponseSchema ->; diff --git a/shared/models/cusModels/cusResponseModels.ts b/shared/models/cusModels/cusResponseModels.ts deleted file mode 100644 index 20e2202e9..000000000 --- a/shared/models/cusModels/cusResponseModels.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { z } from "zod/v4"; -import { AppEnv } from "../genModels/genEnums.js"; -import { RewardResponseSchema } from "../rewardModels/rewardModels/rewardResponseModels.js"; -import { CusProductResponseSchema } from "./cusResModels/cusProductResponse.js"; -import { CusReferralResponseSchema } from "./cusResModels/cusReferralsResponse.js"; -import { UpcomingInvoiceResponseSchema } from "./cusResModels/upcomingInvoiceResponse.js"; -import { EntityResponseSchema } from "./entityModels/entityResModels.js"; -import { InvoiceResponseSchema } from "./invoiceModels/invoiceResponseModels.js"; - -export const TrialUsedResponseSchema = z.object({ - product_id: z.string(), - customer_id: z.string(), - fingerprint: z.string().nullish(), -}); - -export const CusResponseSchema = z.object({ - // Internal fields - autumn_id: z.string().nullish(), - - id: z.string().nullable().meta({ - description: "Your internal ID for the customer", - example: "cus_123", - }), - - created_at: z.number().meta({ - description: - "The date and time the customer was created in milliseconds since epoch", - example: 1717000000, - }), - - name: z.string().nullable(), - email: z.string().nullable(), - fingerprint: z.string().nullable(), - stripe_id: z.string().nullable().default(null), - env: z.nativeEnum(AppEnv), - - products: z.array(CusProductResponseSchema), - features: z.any(), - - invoices: z.array(InvoiceResponseSchema).optional(), - trials_used: z.array(TrialUsedResponseSchema).optional(), - rewards: RewardResponseSchema.nullish(), - metadata: z.record(z.any(), z.any()).default({}), - entities: z.array(EntityResponseSchema).optional(), - referrals: z.array(CusReferralResponseSchema).optional(), - payment_method: z.any().nullish(), - upcoming_invoice: UpcomingInvoiceResponseSchema.nullish(), -}); - -export type CusResponse = z.infer; -export type CusProductResponse = z.infer; diff --git a/shared/models/cusModels/entityModels/entityResModels.ts b/shared/models/cusModels/entityModels/entityResModels.ts index d2f9dbcb8..2db5ce195 100644 --- a/shared/models/cusModels/entityModels/entityResModels.ts +++ b/shared/models/cusModels/entityModels/entityResModels.ts @@ -1,8 +1,8 @@ +import { APICusFeatureSchema } from "@api/customers/components/apiCusFeature.js"; +import { APICusProductSchema } from "@api/customers/components/apiCusProduct.js"; +import { APIInvoiceSchema } from "@api/others/apiInvoice.js"; import { z } from "zod/v4"; import { AppEnv } from "../../genModels/genEnums.js"; -import { CusEntResponseV2Schema } from "../cusResModels/cusFeatureResponse.js"; -import { CusProductResponseSchema } from "../cusResModels/cusProductResponse.js"; -import { InvoiceResponseSchema } from "../invoiceModels/invoiceResponseModels.js"; export const EntityResponseSchema = z.object({ id: z.string().nullable(), @@ -11,10 +11,10 @@ export const EntityResponseSchema = z.object({ feature_id: z.string().nullish(), created_at: z.number(), - env: z.nativeEnum(AppEnv), - products: z.array(CusProductResponseSchema).optional(), - features: z.record(z.string(), CusEntResponseV2Schema).optional(), - invoices: z.array(InvoiceResponseSchema).optional(), + env: z.enum(AppEnv), + products: z.array(APICusProductSchema).optional(), + features: z.record(z.string(), APICusFeatureSchema).optional(), + invoices: z.array(APIInvoiceSchema).optional(), }); export type EntityResponse = z.infer;