fix: api customer schema
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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<string, CusEntResponseV2> = {};
|
||||
const featureObject: Record<string, CusEntResponseV2> = {};
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<InvoiceResponse[]> => {
|
||||
}): Promise<APIInvoice[]> => {
|
||||
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: [],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
// },
|
||||
// });
|
||||
|
||||
@@ -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 (
|
||||
|
||||
143
shared/api/core/attachModels.ts
Normal file
143
shared/api/core/attachModels.ts
Normal file
@@ -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<typeof AttachBodySchema>;
|
||||
export type ProductOptions = z.infer<typeof ProductOptions>;
|
||||
export type AttachResult = z.infer<typeof AttachResultSchema>;
|
||||
42
shared/api/core/checkoutModels.ts
Normal file
42
shared/api/core/checkoutModels.ts
Normal file
@@ -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<typeof CheckoutLineSchema>;
|
||||
export type CheckoutParams = z.infer<typeof CheckoutParamsSchema>;
|
||||
43
shared/api/core/coreOpenApi.ts
Normal file
43
shared/api/core/coreOpenApi.ts
Normal file
@@ -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 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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<typeof APICustomerSchema>;
|
||||
// export type CusProductResponse = z.infer<typeof CusProductResponseSchema>;
|
||||
51
shared/api/customers/apiCustomer.ts
Normal file
51
shared/api/customers/apiCustomer.ts
Normal file
@@ -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<typeof APICustomerSchema>;
|
||||
@@ -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<typeof CusEntResponseSchema>;
|
||||
export type CusEntResponseV2 = z.infer<typeof CusEntResponseV2Schema>;
|
||||
export type CheckResponse = z.infer<typeof CheckResponseSchema>;
|
||||
export type CusEntResponseV2 = z.infer<typeof APICusFeatureSchema>;
|
||||
export type CheckResponse = z.infer<typeof CheckResultSchema>;
|
||||
export type CusRollover = z.infer<typeof CusRolloverSchema>;
|
||||
@@ -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<typeof APICusProductSchema>;
|
||||
@@ -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<typeof CusReferralResponseSchema>;
|
||||
export type APICusReferralResponse = z.infer<typeof APICusReferralSchema>;
|
||||
18
shared/api/customers/components/apiCusUpcomingInvoice.ts
Normal file
18
shared/api/customers/components/apiCusUpcomingInvoice.ts
Normal file
@@ -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<typeof APICusUpcomingInvoiceSchema>;
|
||||
@@ -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<typeof CusEntResponseSchema>;
|
||||
// export type CusEntResponseV2 = z.infer<typeof CusEntResponseV2Schema>;
|
||||
// export type CheckResponse = z.infer<typeof CheckResponseSchema>;
|
||||
// export type CusRollover = z.infer<typeof CusRolloverSchema>;
|
||||
@@ -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
|
||||
// >;
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
29
shared/api/others/apiDiscount.ts
Normal file
29
shared/api/others/apiDiscount.ts
Normal file
@@ -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<typeof APIDiscountSchema>;
|
||||
export type APIDiscountList = z.infer<typeof APICusRewardsSchema>;
|
||||
@@ -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<typeof InvoiceResponseSchema>;
|
||||
export const APIInvoiceListSchema = z.array(APIInvoiceSchema);
|
||||
export type APIInvoice = z.infer<typeof APIInvoiceSchema>;
|
||||
@@ -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({
|
||||
@@ -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: {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<typeof AttachBodySchema>;
|
||||
export type ProductOptions = z.infer<typeof ProductOptions>;
|
||||
// export type AttachBody = z.infer<typeof AttachBodySchema>;
|
||||
// export type ProductOptions = z.infer<typeof ProductOptions>;
|
||||
|
||||
@@ -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<typeof CheckoutLineSchema>;
|
||||
// export type CheckoutLine = z.infer<typeof CheckoutLineSchema>;
|
||||
|
||||
@@ -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<typeof CusReferralResponseSchema>;
|
||||
@@ -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
|
||||
>;
|
||||
@@ -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<typeof CusResponseSchema>;
|
||||
export type CusProductResponse = z.infer<typeof CusProductResponseSchema>;
|
||||
@@ -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<typeof EntityResponseSchema>;
|
||||
|
||||
Reference in New Issue
Block a user