feat: added api / openapi models to shared folder
This commit is contained in:
@@ -88,6 +88,7 @@ const handleSpecialErrorCases = (
|
||||
logger.warn(
|
||||
`ATTACH ZOD ERROR (${ctx.org?.slug || "unknown"}): ${formattedError}`,
|
||||
);
|
||||
|
||||
return c.json(
|
||||
{
|
||||
message: formattedError,
|
||||
@@ -172,18 +173,35 @@ export const errorMiddleware = (err: Error, c: Context<HonoEnv>) => {
|
||||
if (err instanceof ZodError) {
|
||||
const formattedError = formatZodError(err);
|
||||
|
||||
logger.error(
|
||||
`ZOD ERROR (${ctx.org?.slug || "unknown"}): ${formattedError}`,
|
||||
);
|
||||
// 1. If it's validation error
|
||||
if (c.get("validated")) {
|
||||
logger.error(
|
||||
`INTERNAL ZOD ERROR (${ctx.org?.slug || "unknown"}): ${formattedError}`,
|
||||
);
|
||||
logger.error(err);
|
||||
|
||||
return c.json(
|
||||
{
|
||||
message: formattedError,
|
||||
code: ErrCode.InvalidInputs,
|
||||
env: ctx.env,
|
||||
},
|
||||
400,
|
||||
);
|
||||
return c.json(
|
||||
{
|
||||
message: formattedError,
|
||||
code: ErrCode.InvalidInputs,
|
||||
env: ctx.env,
|
||||
},
|
||||
500,
|
||||
);
|
||||
} else {
|
||||
logger.warn(
|
||||
`ZOD ERROR (${ctx.org?.slug || "unknown"}): ${formattedError}`,
|
||||
);
|
||||
|
||||
return c.json(
|
||||
{
|
||||
message: formattedError,
|
||||
code: ErrCode.InvalidInputs,
|
||||
env: ctx.env,
|
||||
},
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Handle unknown errors
|
||||
|
||||
@@ -75,6 +75,8 @@ export function createRoute<
|
||||
}
|
||||
|
||||
const wrappedHandler = async (c: ValidatedContext<HonoEnv, Body, Query>) => {
|
||||
c.set("validated", true);
|
||||
|
||||
if (opts.withTx) {
|
||||
const db = c.get("ctx").db;
|
||||
|
||||
|
||||
@@ -26,5 +26,5 @@ export type RequestContext = {
|
||||
export type AutumnContext = RequestContext;
|
||||
|
||||
export type HonoEnv = {
|
||||
Variables: { ctx: AutumnContext };
|
||||
Variables: { ctx: AutumnContext; validated: boolean };
|
||||
};
|
||||
|
||||
@@ -177,7 +177,7 @@ if (process.env.NODE_ENV === "development") {
|
||||
console.log(`Master ${process.pid} is running`);
|
||||
console.log("Number of CPUs", numCPUs);
|
||||
|
||||
const numWorkers = 7;
|
||||
const numWorkers = 5;
|
||||
|
||||
for (let i = 0; i < numWorkers; i++) {
|
||||
cluster.fork();
|
||||
|
||||
@@ -8,6 +8,7 @@ import { attachRouter } from "../customers/attach/attachRouter.js";
|
||||
import { handleSetupPayment } from "../customers/attach/handleSetupPayment.js";
|
||||
import cancelRouter from "../customers/cancel/cancelRouter.js";
|
||||
import { cusRouter } from "../customers/cusRouter.js";
|
||||
import { handleCreateBillingPortal } from "../customers/handlers/handleCreateBillingPortal.js";
|
||||
import { featureRouter } from "../features/featureRouter.js";
|
||||
import { internalFeatureRouter } from "../features/internalFeatureRouter.js";
|
||||
import { migrationRouter } from "../migrations/migrationRouter.js";
|
||||
@@ -59,6 +60,7 @@ apiRouter.use("/check", checkRouter);
|
||||
apiRouter.use("/events", eventsRouter);
|
||||
apiRouter.use("/track", eventsRouter);
|
||||
apiRouter.post("/setup_payment", handleSetupPayment);
|
||||
apiRouter.post("/billing_portal", handleCreateBillingPortal);
|
||||
|
||||
// Analytics
|
||||
apiRouter.use("/query", analyticsRouter);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import type Stripe from "stripe";
|
||||
import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { toSuccessUrl } from "@/internal/orgs/orgUtils/convertOrgUtils.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import Stripe from "stripe";
|
||||
|
||||
const createDefaultBillingPortalConfiguration = async (stripeCli: Stripe) => {
|
||||
try {
|
||||
@@ -48,8 +48,8 @@ export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
res,
|
||||
action: "create_billing_portal",
|
||||
handler: async (req: any, res: any) => {
|
||||
const customerId = req.params.customer_id;
|
||||
let returnUrl = req.body.return_url;
|
||||
const customerId = req.params.customer_id || req.body.customer_id;
|
||||
const returnUrl = req.body.return_url;
|
||||
|
||||
const [org, customer] = await Promise.all([
|
||||
OrgService.getFromReq(req),
|
||||
@@ -116,8 +116,7 @@ export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
|
||||
// Check if the error is due to missing default configuration
|
||||
if (
|
||||
error.message &&
|
||||
error.message.includes("default configuration has not been created")
|
||||
error.message?.includes("default configuration has not been created")
|
||||
) {
|
||||
try {
|
||||
// Create a default billing portal configuration
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import "dotenv/config";
|
||||
|
||||
import { orgRouter } from "./orgs/orgRouter.js";
|
||||
import { Router } from "express";
|
||||
import { userRouter } from "./users/userRouter.js";
|
||||
import { withAuth, withOrgAuth } from "../middleware/authMiddleware.js";
|
||||
import { internalFeatureRouter } from "./features/internalFeatureRouter.js";
|
||||
import { productRouter } from "./products/internalProductRouter.js";
|
||||
import { devRouter } from "./dev/devRouter.js";
|
||||
import { cusRouter } from "./customers/internalCusRouter.js";
|
||||
import { onboardingRouter } from "./orgs/onboarding/onboardingRouter.js";
|
||||
import { handlePostOrg } from "./orgs/handlers/handlePostOrg.js";
|
||||
import { withAdminAuth } from "./admin/withAdminAuth.js";
|
||||
import { adminRouter } from "./admin/adminRouter.js";
|
||||
import { autumnHandler } from "autumn-js/express";
|
||||
import { Autumn } from "autumn-js";
|
||||
import { analyticsRouter } from "./analytics/internalAnalyticsRouter.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { InvoiceService } from "./invoices/InvoiceService.js";
|
||||
import { autumnHandler } from "autumn-js/express";
|
||||
import { Router } from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { withAuth, withOrgAuth } from "../middleware/authMiddleware.js";
|
||||
import { adminRouter } from "./admin/adminRouter.js";
|
||||
import { withAdminAuth } from "./admin/withAdminAuth.js";
|
||||
import { analyticsRouter } from "./analytics/internalAnalyticsRouter.js";
|
||||
import { trmnlRouter } from "./api/trmnl/trmnlRouter.js";
|
||||
import { trmnlAuthMiddleware } from "@/middleware/trmnlAuthMiddleware.js";
|
||||
import { cusRouter } from "./customers/internalCusRouter.js";
|
||||
import { devRouter } from "./dev/devRouter.js";
|
||||
import { internalFeatureRouter } from "./features/internalFeatureRouter.js";
|
||||
import { InvoiceService } from "./invoices/InvoiceService.js";
|
||||
import { handlePostOrg } from "./orgs/handlers/handlePostOrg.js";
|
||||
import { onboardingRouter } from "./orgs/onboarding/onboardingRouter.js";
|
||||
import { orgRouter } from "./orgs/orgRouter.js";
|
||||
import { productRouter } from "./products/internalProductRouter.js";
|
||||
import { viewsRouter } from "./saved-views/savedViewsRouter.js";
|
||||
import { userRouter } from "./users/userRouter.js";
|
||||
|
||||
const mainRouter: Router = Router();
|
||||
|
||||
@@ -53,8 +52,8 @@ mainRouter.use(
|
||||
"/invoices/hosted_invoice_url/:invoiceId",
|
||||
limiter,
|
||||
async (req: any, res: any) => {
|
||||
let invoiceId = req.params.invoiceId;
|
||||
let invoice = await InvoiceService.get({
|
||||
const invoiceId = req.params.invoiceId;
|
||||
const invoice = await InvoiceService.get({
|
||||
db: req.db,
|
||||
id: invoiceId,
|
||||
});
|
||||
@@ -62,13 +61,15 @@ mainRouter.use(
|
||||
if (!invoice) return res.status(404).json({ error: "Invoice not found" });
|
||||
|
||||
try {
|
||||
let org = invoice.customer.org;
|
||||
let env = invoice.customer.env;
|
||||
let stripeCli = createStripeCli({
|
||||
const org = invoice.customer.org;
|
||||
const env = invoice.customer.env;
|
||||
const stripeCli = createStripeCli({
|
||||
org,
|
||||
env,
|
||||
});
|
||||
let stripeInvoice = await stripeCli.invoices.retrieve(invoice.stripe_id);
|
||||
const stripeInvoice = await stripeCli.invoices.retrieve(
|
||||
invoice.stripe_id,
|
||||
);
|
||||
|
||||
if (stripeInvoice.status == "draft") {
|
||||
return res
|
||||
@@ -108,7 +109,7 @@ mainRouter.use(
|
||||
withOrgAuth,
|
||||
autumnHandler({
|
||||
autumn: (req: any) => {
|
||||
let client = new Autumn({
|
||||
const client = new Autumn({
|
||||
url: "http://localhost:8080/v1",
|
||||
headers: {
|
||||
cookie: req.headers.cookie,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type FullProduct,
|
||||
type Price,
|
||||
ProductAlreadyExistsError,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
@@ -34,7 +35,7 @@ export const disableCurrentDefault = async ({
|
||||
// freeTrial,
|
||||
}: {
|
||||
req: AutumnContext;
|
||||
newProduct: CreateProductV2Params;
|
||||
newProduct: CreateProductV2Params | ProductV2;
|
||||
// items: ProductItem[];
|
||||
// freeTrial: FreeTrial;
|
||||
}) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
CreateProductV2ParamsSchema,
|
||||
type FreeTrial,
|
||||
mapToProductV2,
|
||||
notNullish,
|
||||
ProductNotFoundError,
|
||||
ProductV2Schema,
|
||||
type ProductV2,
|
||||
RecaseError,
|
||||
UpdateProductQuerySchema,
|
||||
UpdateProductSchema,
|
||||
@@ -37,8 +37,6 @@ export const handleUpdateProductV2 = createRoute({
|
||||
|
||||
const { db, org, env, features, logger } = ctx;
|
||||
const { version, upsert, disable_version } = c.req.valid("query");
|
||||
// const { productId } = req.params;
|
||||
// const { orgId, env, logger, db } = req;
|
||||
|
||||
const [fullProduct, rewardPrograms, _defaultProds] = await Promise.all([
|
||||
ProductService.getFull({
|
||||
@@ -64,11 +62,6 @@ export const handleUpdateProductV2 = createRoute({
|
||||
|
||||
if (!fullProduct) throw new ProductNotFoundError({ productId: productId });
|
||||
|
||||
// // How to go into another route handler?
|
||||
// if (upsert === "true") await handleCreateProduct(c);
|
||||
|
||||
// Start a transaction?
|
||||
|
||||
const cusProductsCurVersion =
|
||||
await CusProductService.getByInternalProductId({
|
||||
db,
|
||||
@@ -80,14 +73,17 @@ export const handleUpdateProductV2 = createRoute({
|
||||
features,
|
||||
});
|
||||
|
||||
const cusProductExists = cusProductsCurVersion.length > 0;
|
||||
const newFreeTrial = body.free_trial as FreeTrial | undefined;
|
||||
const newProductV2: ProductV2 = {
|
||||
...curProductV2,
|
||||
...body,
|
||||
items: body.items || [],
|
||||
free_trial: newFreeTrial || curProductV2.free_trial || undefined,
|
||||
};
|
||||
|
||||
await disableCurrentDefault({
|
||||
req: ctx,
|
||||
newProduct: CreateProductV2ParamsSchema.parse({
|
||||
...fullProduct,
|
||||
...body,
|
||||
}),
|
||||
newProduct: newProductV2,
|
||||
});
|
||||
|
||||
await handleUpdateProductDetails({
|
||||
@@ -103,12 +99,8 @@ export const handleUpdateProductV2 = createRoute({
|
||||
|
||||
const itemsExist = notNullish(body.items);
|
||||
|
||||
const cusProductExists = cusProductsCurVersion.length > 0;
|
||||
if (cusProductExists && itemsExist) {
|
||||
const newProductV2 = ProductV2Schema.parse({
|
||||
...body,
|
||||
items: body.items || [],
|
||||
});
|
||||
|
||||
if (disable_version === "true") {
|
||||
throw new RecaseError({
|
||||
message: "Cannot auto save product as there are existing customers",
|
||||
@@ -116,7 +108,7 @@ export const handleUpdateProductV2 = createRoute({
|
||||
}
|
||||
|
||||
const { itemsSame, freeTrialsSame } = productsAreSame({
|
||||
newProductV2,
|
||||
newProductV2: newProductV2,
|
||||
curProductV1: fullProduct,
|
||||
features,
|
||||
});
|
||||
@@ -126,7 +118,7 @@ export const handleUpdateProductV2 = createRoute({
|
||||
if (!productSame) {
|
||||
const newProduct = await handleVersionProductV2({
|
||||
ctx,
|
||||
newProductV2,
|
||||
newProductV2: newProductV2,
|
||||
latestProduct: fullProduct,
|
||||
org,
|
||||
env,
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import {
|
||||
ErrCode,
|
||||
Feature,
|
||||
FullProduct,
|
||||
type Feature,
|
||||
type FullProduct,
|
||||
OnDecrease,
|
||||
OnIncrease,
|
||||
ProductItem,
|
||||
ProductV2,
|
||||
type ProductItem,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
import { mapToProductItems } from "../productV2Utils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { freeTrialsAreSame } from "../free-trials/freeTrialUtils.js";
|
||||
import {
|
||||
findSimilarItem,
|
||||
itemsAreSame,
|
||||
} from "../product-items/compareItemUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { freeTrialsAreSame } from "../free-trials/freeTrialUtils.js";
|
||||
import {
|
||||
isFeaturePriceItem,
|
||||
isPriceItem,
|
||||
} from "../product-items/productItemUtils/getItemType.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { itemToPriceOrTiers } from "../product-items/productItemUtils.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
import { getResetUsage } from "../product-items/productItemUtils/itemToPriceAndEnt.js";
|
||||
import { itemToPriceOrTiers } from "../product-items/productItemUtils.js";
|
||||
import { mapToProductItems } from "../productV2Utils.js";
|
||||
|
||||
const sanitizeItems = ({
|
||||
items,
|
||||
@@ -31,7 +29,7 @@ const sanitizeItems = ({
|
||||
features: Feature[];
|
||||
}) => {
|
||||
return items.map((item) => {
|
||||
let priceData = itemToPriceOrTiers({ item });
|
||||
const priceData = itemToPriceOrTiers({ item });
|
||||
const newItem = {
|
||||
...item,
|
||||
reset_usage_when_enabled: getResetUsage({
|
||||
@@ -124,7 +122,7 @@ export const productsAreSame = ({
|
||||
}
|
||||
|
||||
for (const item of items1) {
|
||||
let similarItem = findSimilarItem({
|
||||
const similarItem = findSimilarItem({
|
||||
item,
|
||||
items: items2,
|
||||
});
|
||||
@@ -157,7 +155,7 @@ export const productsAreSame = ({
|
||||
}
|
||||
|
||||
for (const item of items2) {
|
||||
let similarItem = findSimilarItem({
|
||||
const similarItem = findSimilarItem({
|
||||
item,
|
||||
items: items1,
|
||||
});
|
||||
@@ -173,10 +171,10 @@ export const productsAreSame = ({
|
||||
}
|
||||
|
||||
// Compare free trial
|
||||
let freeTrial1 = curProductV1?.free_trial || curProductV2?.free_trial;
|
||||
let freeTrial2 = newProductV1?.free_trial || newProductV2?.free_trial;
|
||||
const freeTrial1 = curProductV1?.free_trial || curProductV2?.free_trial;
|
||||
const freeTrial2 = newProductV1?.free_trial || newProductV2?.free_trial;
|
||||
|
||||
let freeTrialsSame = freeTrialsAreSame({
|
||||
const freeTrialsSame = freeTrialsAreSame({
|
||||
ft1: freeTrial1,
|
||||
ft2: freeTrial2,
|
||||
});
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { AppEnv, Organization } from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import Stripe from "stripe";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { addPrefixToProducts } from "../utils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { defaultApiVersion } from "tests/constants.js";
|
||||
import { runMigrationTest } from "./runMigrationTest.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import type { AppEnv, Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { defaultApiVersion } from "tests/constants.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
let wordsItem = constructArrearItem({
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { addPrefixToProducts } from "../utils.js";
|
||||
import { runMigrationTest } from "./runMigrationTest.js";
|
||||
|
||||
const wordsItem = constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
});
|
||||
|
||||
export let pro = constructProduct({
|
||||
export const pro = constructProduct({
|
||||
items: [wordsItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
let newWordsItem = constructArrearItem({
|
||||
const newWordsItem = constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 120100,
|
||||
});
|
||||
|
||||
let proWithTrial = constructProduct({
|
||||
const proWithTrial = constructProduct({
|
||||
items: [newWordsItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
@@ -40,13 +41,13 @@ let proWithTrial = constructProduct({
|
||||
const testCase = "migrations4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing migration for pro -> pro with trial (should not start trial)`)}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: defaultApiVersion });
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: defaultApiVersion });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
@@ -83,7 +84,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing migration for pro -> pro wi
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async function () {
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
@@ -95,7 +96,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing migration for pro -> pro wi
|
||||
});
|
||||
});
|
||||
|
||||
it("should update product to new version", async function () {
|
||||
it("should update product to new version", async () => {
|
||||
proWithTrial.version = 2;
|
||||
await autumn.products.update(pro.id, {
|
||||
items: proWithTrial.items,
|
||||
@@ -103,8 +104,8 @@ describe(`${chalk.yellowBright(`${testCase}: Testing migration for pro -> pro wi
|
||||
});
|
||||
});
|
||||
|
||||
it("should attach track usage and get correct balance", async function () {
|
||||
let wordsUsage = 120000;
|
||||
it("should attach track usage and get correct balance", async () => {
|
||||
const wordsUsage = 120000;
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
value: wordsUsage,
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
import { expect } from "chai";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
type AppEnv,
|
||||
BillingInterval,
|
||||
Organization,
|
||||
ProductV2,
|
||||
type Organization,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import Stripe from "stripe";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import type Stripe from "stripe";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { addPrefixToProducts } from "../utils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { replaceItems } from "../utils.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
import runUpdateEntsTest from "../updateEnts/expectUpdateEnts.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import runUpdateEntsTest from "../updateEnts/expectUpdateEnts.js";
|
||||
import { addPrefixToProducts, replaceItems } from "../utils.js";
|
||||
|
||||
export let pro = constructProduct({
|
||||
export const pro = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
trial: true,
|
||||
@@ -31,13 +29,13 @@ export let pro = constructProduct({
|
||||
const testCase = "newVersion2";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing attach new version for trial product`)}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
@@ -74,7 +72,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing attach new version for tria
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async function () {
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
@@ -86,11 +84,11 @@ describe(`${chalk.yellowBright(`${testCase}: Testing attach new version for tria
|
||||
});
|
||||
});
|
||||
|
||||
let usage = 50000;
|
||||
const usage = 50000;
|
||||
let newPro: ProductV2;
|
||||
it("should update product to new version", async function () {
|
||||
it("should update product to new version", async () => {
|
||||
newPro = structuredClone(pro);
|
||||
let newItems = replaceItems({
|
||||
const newItems = replaceItems({
|
||||
items: pro.items,
|
||||
interval: BillingInterval.Month,
|
||||
newItem: constructPriceItem({
|
||||
@@ -109,7 +107,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing attach new version for tria
|
||||
|
||||
return;
|
||||
|
||||
it("should attach pro v2", async function () {
|
||||
it("should attach pro v2", async () => {
|
||||
await runUpdateEntsTest({
|
||||
autumn,
|
||||
stripeCli,
|
||||
|
||||
@@ -48,7 +48,7 @@ describe(`${chalk.yellowBright(`contUse/${testCase}: Testing entities, prorate n
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
let curUnix = new Date().getTime();
|
||||
let curUnix = Date.now();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
type AppEnv,
|
||||
OnDecrease,
|
||||
OnIncrease,
|
||||
Organization,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import Stripe from "stripe";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { addPrefixToProducts } from "../../attach/utils.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths, addWeeks } from "date-fns";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import type Stripe from "stripe";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectSubQuantityCorrect } from "tests/utils/expectUtils/expectContUseUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { addPrefixToProducts } from "../../attach/utils.js";
|
||||
|
||||
let userItem = constructArrearProratedItem({
|
||||
const userItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 50,
|
||||
includedUsage: 1,
|
||||
@@ -34,7 +34,7 @@ let userItem = constructArrearProratedItem({
|
||||
},
|
||||
});
|
||||
|
||||
export let pro = constructProduct({
|
||||
export const pro = constructProduct({
|
||||
items: [userItem],
|
||||
type: "pro",
|
||||
});
|
||||
@@ -42,8 +42,8 @@ export let pro = constructProduct({
|
||||
const testCase = "entity3";
|
||||
|
||||
describe(`${chalk.yellowBright(`contUse/${testCase}: Testing replaceables deleted at end of cycle`)}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
@@ -85,7 +85,7 @@ describe(`${chalk.yellowBright(`contUse/${testCase}: Testing replaceables delete
|
||||
});
|
||||
|
||||
let usage = 0;
|
||||
let firstEntities = [
|
||||
const firstEntities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "test",
|
||||
@@ -103,7 +103,7 @@ describe(`${chalk.yellowBright(`contUse/${testCase}: Testing replaceables delete
|
||||
},
|
||||
];
|
||||
|
||||
it("should create three entities, then attach pro", async function () {
|
||||
it("should create three entities, then attach pro", async () => {
|
||||
await autumn.entities.create(customerId, firstEntities);
|
||||
usage += firstEntities.length;
|
||||
|
||||
@@ -124,7 +124,7 @@ describe(`${chalk.yellowBright(`contUse/${testCase}: Testing replaceables delete
|
||||
});
|
||||
});
|
||||
|
||||
it("should delete 2 entities and have no new invoice", async function () {
|
||||
it("should delete 2 entities and have no new invoice", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
@@ -148,12 +148,12 @@ describe(`${chalk.yellowBright(`contUse/${testCase}: Testing replaceables delete
|
||||
itemQuantity: usage - numReplaceables,
|
||||
});
|
||||
|
||||
let customer = await autumn.customers.get(customerId);
|
||||
let invoices = customer.invoices!;
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices!;
|
||||
expect(invoices.length).to.equal(1);
|
||||
});
|
||||
|
||||
it("should advance clock to next cycle and have correct invoice", async function () {
|
||||
it("should advance clock to next cycle and have correct invoice", async () => {
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
@@ -168,7 +168,7 @@ describe(`${chalk.yellowBright(`contUse/${testCase}: Testing replaceables delete
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
|
||||
let basePrice = getBasePrice({ product: pro });
|
||||
const basePrice = getBasePrice({ product: pro });
|
||||
expect(invoices.length).to.equal(2);
|
||||
expect(invoices[0].total).to.equal(basePrice); // 0 entities
|
||||
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
import Stripe from "stripe";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
AppEnv,
|
||||
type AppEnv,
|
||||
AttachBranch,
|
||||
CreateEntity,
|
||||
type CreateEntity,
|
||||
CusProductStatus,
|
||||
FeatureOptions,
|
||||
Organization,
|
||||
ProductV2,
|
||||
type FeatureOptions,
|
||||
type Organization,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import {
|
||||
getAttachTotal,
|
||||
getCurrentOptions,
|
||||
} from "tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { expectInvoicesCorrect } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { expectFeaturesCorrect } from "tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { notNullish, timeout, toSnakeCase } from "@/utils/genUtils.js";
|
||||
import { expectSubItemsCorrect } from "tests/utils/expectUtils/expectSubUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
|
||||
import type { AttachParams, Customer } from "autumn-js";
|
||||
import { expect } from "chai";
|
||||
import { completeCheckoutForm } from "../stripeUtils.js";
|
||||
import { AttachParams, Customer } from "autumn-js";
|
||||
import { isFreeProductV2 } from "@/internal/products/productUtils/classifyProduct.js";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import type Stripe from "stripe";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
import { expectFeaturesCorrect } from "tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import {
|
||||
expectInvoicesCorrect,
|
||||
expectProductAttached,
|
||||
} from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { getCurrentOptions } from "tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import type { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { isFreeProductV2 } from "@/internal/products/productUtils/classifyProduct.js";
|
||||
import { timeout, toSnakeCase } from "@/utils/genUtils.js";
|
||||
import { completeCheckoutForm } from "../stripeUtils.js";
|
||||
|
||||
export const attachAndExpectCorrect = async ({
|
||||
autumn,
|
||||
@@ -135,9 +131,9 @@ export const attachAndExpectCorrect = async ({
|
||||
|
||||
const productCount = customer.products.reduce((acc: number, p: any) => {
|
||||
if (
|
||||
product.group == p.group &&
|
||||
product.group === p.group &&
|
||||
!p.is_add_on &&
|
||||
(entityId ? p.entity_id == entityId : true)
|
||||
(entityId ? p.entity_id === entityId : true)
|
||||
) {
|
||||
return acc + 1;
|
||||
} else return acc;
|
||||
@@ -145,7 +141,7 @@ export const attachAndExpectCorrect = async ({
|
||||
|
||||
const branch = preview.branch;
|
||||
|
||||
if (branch == AttachBranch.Downgrade) {
|
||||
if (branch === AttachBranch.Downgrade) {
|
||||
expect(
|
||||
productCount,
|
||||
`customer should only have 2 products (from this group: ${product.group})`,
|
||||
@@ -162,15 +158,15 @@ export const attachAndExpectCorrect = async ({
|
||||
product,
|
||||
entityId,
|
||||
status:
|
||||
preview.branch == AttachBranch.Downgrade
|
||||
preview.branch === AttachBranch.Downgrade
|
||||
? CusProductStatus.Scheduled
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const skipInvoiceCheck =
|
||||
(preview.branch == AttachBranch.UpdatePrepaidQuantity &&
|
||||
checkoutRes.total == 0) ||
|
||||
preview.branch == AttachBranch.Downgrade;
|
||||
(preview.branch === AttachBranch.UpdatePrepaidQuantity &&
|
||||
checkoutRes.total === 0) ||
|
||||
preview.branch === AttachBranch.Downgrade;
|
||||
|
||||
const freeProduct = isFreeProductV2({ product });
|
||||
if (!skipInvoiceCheck && !freeProduct) {
|
||||
@@ -183,7 +179,7 @@ export const attachAndExpectCorrect = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (!skipFeatureCheck && branch !== AttachBranch.Downgrade) {
|
||||
if (!skipFeatureCheck && branch === AttachBranch.Downgrade) {
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product,
|
||||
@@ -195,7 +191,7 @@ export const attachAndExpectCorrect = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (branch == AttachBranch.OneOff) {
|
||||
if (branch === AttachBranch.OneOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Node environment variables
|
||||
NODE_ENV="development"
|
||||
|
||||
# Development environment variables
|
||||
DEV_DATABASE_URL="postgres://user:password@localhost:5432/dev_db"
|
||||
35
shared/api/common/customerData.ts
Normal file
35
shared/api/common/customerData.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const CustomerDataSchema = z
|
||||
.object({
|
||||
name: z.string().nullish().meta({
|
||||
description: "Customer's name",
|
||||
example: "John Doe",
|
||||
}),
|
||||
email: z.string().nullish().meta({
|
||||
description: "Customer's email address",
|
||||
example: "john@example.com",
|
||||
}),
|
||||
fingerprint: z.string().nullish().meta({
|
||||
description:
|
||||
"Unique identifier (eg, serial number) to detect duplicate customers and prevent free trial abuse",
|
||||
example: "fp_123abc",
|
||||
}),
|
||||
metadata: z
|
||||
.record(z.any(), z.any())
|
||||
.nullish()
|
||||
.meta({
|
||||
description: "Additional metadata for the customer",
|
||||
example: { company: "Acme Inc" },
|
||||
}),
|
||||
stripe_id: z.string().nullish().meta({
|
||||
description: "Stripe customer ID if you already have one",
|
||||
example: "cus_stripe123",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CustomerData",
|
||||
description: "Customer data for creating or updating a customer",
|
||||
});
|
||||
|
||||
export type CustomerData = z.infer<typeof CustomerDataSchema>;
|
||||
19
shared/api/common/entityData.ts
Normal file
19
shared/api/common/entityData.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const EntityDataSchema = z
|
||||
.object({
|
||||
feature_id: z.string().meta({
|
||||
description: "The feature ID that this entity is associated with",
|
||||
example: "seats",
|
||||
}),
|
||||
name: z.string().optional().meta({
|
||||
description: "Name of the entity",
|
||||
example: "Team Alpha",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "EntityData",
|
||||
description: "Entity data for creating an entity",
|
||||
});
|
||||
|
||||
export type EntityData = z.infer<typeof EntityDataSchema>;
|
||||
@@ -3,6 +3,8 @@ import { CreateFreeTrialSchema } from "@models/productModels/freeTrialModels/fre
|
||||
import { ProductItemSchema } from "@models/productV2Models/productItemModels/productItemModels.js";
|
||||
import { z } from "zod/v4";
|
||||
import { notNullish } from "../../utils/utils.js";
|
||||
import { CustomerDataSchema } from "../common/customerData.js";
|
||||
import { EntityDataSchema } from "../common/entityData.js";
|
||||
|
||||
export const ProductOptions = z.object({
|
||||
product_id: z.string(),
|
||||
@@ -18,19 +20,18 @@ export const ExtAttachBodySchema = z.object({
|
||||
.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: CustomerDataSchema.nullish().describe(
|
||||
"Customer data if using attach to auto create customer",
|
||||
),
|
||||
|
||||
entity_id: z.string().nullish(),
|
||||
entity_data: z.any().nullish(),
|
||||
entity_data: EntityDataSchema.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(),
|
||||
free_trial: z.boolean().optional(),
|
||||
|
||||
// Others
|
||||
success_url: z.string().optional(),
|
||||
|
||||
147
shared/api/core/checkModels.ts
Normal file
147
shared/api/core/checkModels.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { EntityDataSchema } from "@api/common/entityData.js";
|
||||
import { APIProductSchema } from "@api/products/apiProduct.js";
|
||||
import { z } from "zod/v4";
|
||||
import { CustomerDataSchema } from "../common/customerData.js";
|
||||
import { CoreCusFeatureSchema } from "../customers/components/apiCusFeature.js";
|
||||
|
||||
// Check Feature Enums
|
||||
export const CheckFeatureScenarioSchema = z
|
||||
.enum(["usage_limit", "feature_flag"])
|
||||
.meta({
|
||||
id: "CheckFeatureScenario",
|
||||
description: "Scenario type for feature check",
|
||||
});
|
||||
|
||||
export const ProductScenarioSchema = z
|
||||
.enum([
|
||||
"scheduled",
|
||||
"active",
|
||||
"new",
|
||||
"renew",
|
||||
"upgrade",
|
||||
"downgrade",
|
||||
"cancel",
|
||||
])
|
||||
.meta({
|
||||
id: "ProductScenario",
|
||||
description: "Scenario type for product attachment",
|
||||
});
|
||||
|
||||
// Check Feature Schemas
|
||||
export const CheckParamsSchema = z
|
||||
.object({
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer to check",
|
||||
example: "cus_123",
|
||||
}),
|
||||
feature_id: z.string().optional().meta({
|
||||
description: "The ID of the feature to check access for",
|
||||
example: "api_calls",
|
||||
}),
|
||||
product_id: z.string().optional().meta({
|
||||
description: "The ID of the product to check",
|
||||
example: "pro_plan",
|
||||
}),
|
||||
entity_id: z.string().optional().meta({
|
||||
description: "The ID of the entity (optional)",
|
||||
example: "entity_123",
|
||||
}),
|
||||
customer_data: CustomerDataSchema.optional().meta({
|
||||
description:
|
||||
"Customer data to create or update the customer if they don't exist",
|
||||
}),
|
||||
required_balance: z.number().optional().meta({
|
||||
description: "The required balance for the check",
|
||||
example: 1,
|
||||
}),
|
||||
send_event: z.boolean().optional().meta({
|
||||
description: "Whether to send a usage event if allowed",
|
||||
example: true,
|
||||
}),
|
||||
with_preview: z.boolean().optional().meta({
|
||||
description: "Whether to include preview information in the response",
|
||||
example: true,
|
||||
}),
|
||||
entity_data: EntityDataSchema.optional().meta({
|
||||
description: "Entity data to create the entity if it doesn't exist",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CheckParams",
|
||||
description: "Parameters for checking feature or product access",
|
||||
});
|
||||
|
||||
// Check Feature Preview Schemas
|
||||
export const CheckFeaturePreviewSchema = z
|
||||
.object({
|
||||
scenario: CheckFeatureScenarioSchema.meta({
|
||||
description: "The scenario type for this feature preview",
|
||||
example: "usage_limit",
|
||||
}),
|
||||
title: z.string().meta({
|
||||
description: "Title for the preview message",
|
||||
example: "Usage Limit Reached",
|
||||
}),
|
||||
message: z.string().meta({
|
||||
description: "Detailed message explaining the check result",
|
||||
example: "You've reached your usage limit. Upgrade to continue.",
|
||||
}),
|
||||
feature_id: z.string().meta({
|
||||
description: "The ID of the feature",
|
||||
example: "api_calls",
|
||||
}),
|
||||
feature_name: z.string().meta({
|
||||
description: "The name of the feature",
|
||||
example: "API Calls",
|
||||
}),
|
||||
products: z.array(APIProductSchema).meta({
|
||||
description: "Available products that include this feature",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CheckFeaturePreview",
|
||||
description: "Preview information for a feature check",
|
||||
});
|
||||
|
||||
export const CheckResultSchema = z
|
||||
.object({
|
||||
allowed: z.boolean().meta({
|
||||
description: "Whether the customer is allowed to use the feature",
|
||||
example: true,
|
||||
}),
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
feature_id: z.string().meta({
|
||||
description: "The ID of the feature checked",
|
||||
example: "api_calls",
|
||||
}),
|
||||
entity_id: z.string().nullish().meta({
|
||||
description: "The ID of the entity (if provided)",
|
||||
example: "entity_123",
|
||||
}),
|
||||
required_balance: z.number().meta({
|
||||
description: "The required balance for this check",
|
||||
example: 1,
|
||||
}),
|
||||
code: z.string().meta({
|
||||
description: "Response code indicating the result",
|
||||
example: "allowed",
|
||||
}),
|
||||
preview: CheckFeaturePreviewSchema.optional().meta({
|
||||
description: "Preview information if with_preview was true",
|
||||
}),
|
||||
})
|
||||
.extend(CoreCusFeatureSchema.shape)
|
||||
.meta({
|
||||
id: "CheckResult",
|
||||
description: "Result of a feature check",
|
||||
});
|
||||
|
||||
// Export Types
|
||||
export type CheckParams = z.infer<typeof CheckParamsSchema>;
|
||||
export type CheckResponse = z.infer<typeof CheckResultSchema>;
|
||||
export type CheckFeatureScenario = z.infer<typeof CheckFeatureScenarioSchema>;
|
||||
// export type CheckFeaturePreview = z.infer<typeof CheckFeaturePreviewSchema>;
|
||||
export type ProductScenario = z.infer<typeof ProductScenarioSchema>;
|
||||
210
shared/api/core/checkProductModels.ts
Normal file
210
shared/api/core/checkProductModels.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { z } from "zod/v4";
|
||||
import { CustomerDataSchema } from "../common/customerData.js";
|
||||
import { EntityDataSchema } from "../common/entityData.js";
|
||||
import { ProductScenarioSchema } from "./checkModels.js";
|
||||
|
||||
// Check Product Schemas
|
||||
export const CheckProductParamsSchema = z
|
||||
.object({
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
product_id: z.string().meta({
|
||||
description: "The ID of the product to check",
|
||||
example: "pro_plan",
|
||||
}),
|
||||
entity_id: z.string().optional().meta({
|
||||
description: "The ID of the entity (optional)",
|
||||
example: "entity_123",
|
||||
}),
|
||||
customer_data: CustomerDataSchema.optional().meta({
|
||||
description:
|
||||
"Customer data to create or update the customer if they don't exist",
|
||||
}),
|
||||
entity_data: EntityDataSchema.optional().meta({
|
||||
description: "Entity data to create the entity if it doesn't exist",
|
||||
}),
|
||||
with_preview: z.boolean().optional().meta({
|
||||
description: "Whether to include preview information in the response",
|
||||
example: true,
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CheckProductParams",
|
||||
description: "Parameters for checking product availability",
|
||||
});
|
||||
|
||||
export const CheckProductPreviewItemSchema = z
|
||||
.object({
|
||||
price: z.string().meta({
|
||||
description: "Formatted price string",
|
||||
example: "$10.00",
|
||||
}),
|
||||
description: z.string().meta({
|
||||
description: "Description of the item",
|
||||
example: "Base subscription",
|
||||
}),
|
||||
usage_model: z.enum(["prepaid", "pay_per_use"]).optional().meta({
|
||||
description: "The usage model for this item",
|
||||
example: "prepaid",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CheckProductPreviewItem",
|
||||
description: "Individual item in product preview",
|
||||
});
|
||||
|
||||
export const CheckProductPreviewOptionSchema = z
|
||||
.object({
|
||||
feature_id: z.string().meta({
|
||||
description: "The ID of the feature",
|
||||
example: "api_calls",
|
||||
}),
|
||||
feature_name: z.string().meta({
|
||||
description: "The name of the feature",
|
||||
example: "API Calls",
|
||||
}),
|
||||
billing_units: z.number().meta({
|
||||
description: "Number of billing units",
|
||||
example: 1000,
|
||||
}),
|
||||
price: z.number().optional().meta({
|
||||
description: "Price per billing unit",
|
||||
example: 0.01,
|
||||
}),
|
||||
tiers: z
|
||||
.array(
|
||||
z.object({
|
||||
to: z.union([z.number(), z.string()]).meta({
|
||||
description: "Upper limit of this tier (can be 'inf' for infinite)",
|
||||
example: 1000,
|
||||
}),
|
||||
amount: z.number().meta({
|
||||
description: "Price amount for this tier",
|
||||
example: 10,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.meta({
|
||||
description: "Tiered pricing structure",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CheckProductPreviewOption",
|
||||
description: "Feature option in product preview",
|
||||
});
|
||||
|
||||
export const CheckProductPreviewSchema = z
|
||||
.object({
|
||||
scenario: ProductScenarioSchema.meta({
|
||||
description: "The scenario type for this product preview",
|
||||
example: "upgrade",
|
||||
}),
|
||||
product_id: z.string().meta({
|
||||
description: "The ID of the product",
|
||||
example: "pro_plan",
|
||||
}),
|
||||
product_name: z.string().meta({
|
||||
description: "The name of the product",
|
||||
example: "Pro Plan",
|
||||
}),
|
||||
recurring: z.boolean().meta({
|
||||
description: "Whether the product is recurring",
|
||||
example: true,
|
||||
}),
|
||||
error_on_attach: z.boolean().optional().meta({
|
||||
description: "Whether there would be an error attaching this product",
|
||||
example: false,
|
||||
}),
|
||||
next_cycle_at: z.number().optional().meta({
|
||||
description: "Timestamp of the next billing cycle",
|
||||
example: 1717000000000,
|
||||
}),
|
||||
current_product_name: z.string().optional().meta({
|
||||
description: "Name of the customer's current product",
|
||||
example: "Basic Plan",
|
||||
}),
|
||||
items: z.array(CheckProductPreviewItemSchema).optional().meta({
|
||||
description: "Individual items in the product",
|
||||
}),
|
||||
options: z.array(CheckProductPreviewOptionSchema).optional().meta({
|
||||
description: "Feature options available in the product",
|
||||
}),
|
||||
due_today: z
|
||||
.object({
|
||||
price: z.number().meta({
|
||||
description: "Amount due today",
|
||||
example: 10,
|
||||
}),
|
||||
currency: z.string().meta({
|
||||
description: "Currency code",
|
||||
example: "usd",
|
||||
}),
|
||||
})
|
||||
.optional()
|
||||
.meta({
|
||||
description: "Payment due today",
|
||||
}),
|
||||
due_next_cycle: z
|
||||
.object({
|
||||
price: z.number().meta({
|
||||
description: "Amount due next cycle",
|
||||
example: 50,
|
||||
}),
|
||||
currency: z.string().meta({
|
||||
description: "Currency code",
|
||||
example: "usd",
|
||||
}),
|
||||
})
|
||||
.optional()
|
||||
.meta({
|
||||
description: "Payment due in the next cycle",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CheckProductPreview",
|
||||
description: "Preview information for a product check",
|
||||
});
|
||||
|
||||
export const CheckProductResultSchema = z
|
||||
.object({
|
||||
allowed: z.boolean().meta({
|
||||
description: "Whether the customer can attach the product",
|
||||
example: true,
|
||||
}),
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
product_id: z.string().meta({
|
||||
description: "The ID of the product",
|
||||
example: "pro_plan",
|
||||
}),
|
||||
entity_id: z.string().optional().meta({
|
||||
description: "The ID of the entity (if provided)",
|
||||
example: "entity_123",
|
||||
}),
|
||||
status: z.string().optional().meta({
|
||||
description: "Status code for the check result",
|
||||
example: "upgrade_available",
|
||||
}),
|
||||
preview: CheckProductPreviewSchema.optional().meta({
|
||||
description: "Preview information if with_preview was true",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "CheckProductResult",
|
||||
description: "Result of a product check",
|
||||
});
|
||||
|
||||
export type CheckProductParams = z.infer<typeof CheckProductParamsSchema>;
|
||||
export type CheckProductResult = z.infer<typeof CheckProductResultSchema>;
|
||||
export type CheckProductPreview = z.infer<typeof CheckProductPreviewSchema>;
|
||||
export type CheckProductPreviewItem = z.infer<
|
||||
typeof CheckProductPreviewItemSchema
|
||||
>;
|
||||
export type CheckProductPreviewOption = z.infer<
|
||||
typeof CheckProductPreviewOptionSchema
|
||||
>;
|
||||
@@ -1,4 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
import { CustomerDataSchema } from "../common/customerData.js";
|
||||
import { EntityDataSchema } from "../common/entityData.js";
|
||||
|
||||
// Cancel Schemas
|
||||
export const CancelBodySchema = z
|
||||
@@ -57,10 +59,9 @@ export const TrackParamsSchema = z
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
customer_data: z.any().nullish().meta({
|
||||
customer_data: CustomerDataSchema.nullish().meta({
|
||||
description:
|
||||
"Customer data to create or update the customer if they don't exist",
|
||||
example: { name: "John Doe", email: "john@example.com" },
|
||||
}),
|
||||
event_name: z.string().nonempty().optional().meta({
|
||||
description: "The name of the event to track",
|
||||
@@ -71,10 +72,13 @@ export const TrackParamsSchema = z
|
||||
"The ID of the feature (alternative to event_name for usage events)",
|
||||
example: "api_calls",
|
||||
}),
|
||||
properties: z.record(z.string(), z.any()).nullish().meta({
|
||||
description: "Additional properties for the event",
|
||||
example: { endpoint: "/api/users" },
|
||||
}),
|
||||
properties: z
|
||||
.record(z.string(), z.any())
|
||||
.nullish()
|
||||
.meta({
|
||||
description: "Additional properties for the event",
|
||||
example: { endpoint: "/api/users" },
|
||||
}),
|
||||
timestamp: z.number().nullish().meta({
|
||||
description: "Unix timestamp in milliseconds when the event occurred",
|
||||
example: 1717000000000,
|
||||
@@ -88,16 +92,16 @@ export const TrackParamsSchema = z
|
||||
example: 1,
|
||||
}),
|
||||
set_usage: z.boolean().nullish().meta({
|
||||
description: "Whether to set the usage to this value instead of increment",
|
||||
description:
|
||||
"Whether to set the usage to this value instead of increment",
|
||||
example: false,
|
||||
}),
|
||||
entity_id: z.string().nullish().meta({
|
||||
description: "The ID of the entity this event is associated with",
|
||||
example: "entity_123",
|
||||
}),
|
||||
entity_data: z.any().nullish().meta({
|
||||
entity_data: EntityDataSchema.nullish().meta({
|
||||
description: "Data for creating the entity if it doesn't exist",
|
||||
example: { name: "Team Alpha" },
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
@@ -174,9 +178,80 @@ export const QueryResultSchema = z
|
||||
description: "Result of an analytics query",
|
||||
});
|
||||
|
||||
export const SetupPaymentParamsSchema = z
|
||||
.object({
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
success_url: z.string().optional().meta({
|
||||
description: "URL to redirect to after successful payment setup",
|
||||
example: "https://example.com/success",
|
||||
}),
|
||||
checkout_session_params: z.record(z.any(), z.any()).optional().meta({
|
||||
description: "Additional parameters for the checkout session",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "SetupPaymentParams",
|
||||
description: "Parameters for setting up a payment method",
|
||||
});
|
||||
|
||||
export const SetupPaymentResultSchema = z
|
||||
.object({
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
url: z.string().meta({
|
||||
description: "URL to the payment setup page",
|
||||
example: "https://checkout.stripe.com/...",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "SetupPaymentResult",
|
||||
description: "Result of setting up a payment method",
|
||||
});
|
||||
|
||||
export const BillingPortalParamsSchema = z
|
||||
.object({
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
return_url: z.string().optional().meta({
|
||||
description:
|
||||
"URL to return to after exiting the billing portal. Must include http:// or https://",
|
||||
example: "https://example.com/dashboard",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "BillingPortalParams",
|
||||
description: "Parameters for accessing the billing portal",
|
||||
});
|
||||
|
||||
export const BillingPortalResultSchema = z
|
||||
.object({
|
||||
customer_id: z.string().meta({
|
||||
description: "The ID of the customer",
|
||||
example: "cus_123",
|
||||
}),
|
||||
url: z.string().meta({
|
||||
description: "URL to the billing portal",
|
||||
example: "https://billing.stripe.com/...",
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
id: "BillingPortalResult",
|
||||
description: "Result of creating a billing portal session",
|
||||
});
|
||||
|
||||
export type CancelBody = z.infer<typeof CancelBodySchema>;
|
||||
export type CancelResult = z.infer<typeof CancelResultSchema>;
|
||||
export type TrackParams = z.infer<typeof TrackParamsSchema>;
|
||||
export type TrackResult = z.infer<typeof TrackResultSchema>;
|
||||
export type QueryParams = z.infer<typeof QueryParamsSchema>;
|
||||
export type QueryResult = z.infer<typeof QueryResultSchema>;
|
||||
export type SetupPaymentParams = z.infer<typeof SetupPaymentParamsSchema>;
|
||||
export type BillingPortalParams = z.infer<typeof BillingPortalParamsSchema>;
|
||||
export type BillingPortalResult = z.infer<typeof BillingPortalResultSchema>;
|
||||
|
||||
@@ -4,96 +4,152 @@ import {
|
||||
ExtAttachBodySchema,
|
||||
ExtCheckoutParamsSchema,
|
||||
} from "@api/models.js";
|
||||
import { CheckParamsSchema, CheckResultSchema } from "./checkModels.js";
|
||||
import {
|
||||
BillingPortalParamsSchema,
|
||||
BillingPortalResultSchema,
|
||||
CancelBodySchema,
|
||||
CancelResultSchema,
|
||||
QueryParamsSchema,
|
||||
QueryResultSchema,
|
||||
SetupPaymentParamsSchema,
|
||||
SetupPaymentResultSchema,
|
||||
TrackParamsSchema,
|
||||
TrackResultSchema,
|
||||
} from "./coreOpModels.js";
|
||||
|
||||
export const coreOps = {
|
||||
"/core": {
|
||||
"/attach": {
|
||||
post: {
|
||||
summary: "Attach Product",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: ExtAttachBodySchema },
|
||||
},
|
||||
"/attach": {
|
||||
post: {
|
||||
summary: "Attach Product",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: ExtAttachBodySchema },
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: AttachResultSchema } },
|
||||
},
|
||||
},
|
||||
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 } },
|
||||
},
|
||||
},
|
||||
"/checkout": {
|
||||
post: {
|
||||
summary: "Checkout",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: { "application/json": { schema: ExtCheckoutParamsSchema } },
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: CheckoutResponseSchema } },
|
||||
},
|
||||
},
|
||||
},
|
||||
"/cancel": {
|
||||
post: {
|
||||
summary: "Cancel Product",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: CancelBodySchema },
|
||||
},
|
||||
},
|
||||
"/cancel": {
|
||||
post: {
|
||||
summary: "Cancel Product",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: CancelBodySchema },
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: CancelResultSchema } },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: CancelResultSchema } },
|
||||
},
|
||||
},
|
||||
},
|
||||
"/track": {
|
||||
post: {
|
||||
summary: "Track Event",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: TrackParamsSchema },
|
||||
},
|
||||
},
|
||||
"/track": {
|
||||
post: {
|
||||
summary: "Track Event",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: TrackParamsSchema },
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: TrackResultSchema } },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: TrackResultSchema } },
|
||||
},
|
||||
},
|
||||
},
|
||||
"/query": {
|
||||
post: {
|
||||
summary: "Query Analytics",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: QueryParamsSchema },
|
||||
},
|
||||
},
|
||||
"/query": {
|
||||
post: {
|
||||
summary: "Query Analytics",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: QueryParamsSchema },
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: QueryResultSchema } },
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: QueryResultSchema } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/check": {
|
||||
post: {
|
||||
summary: "Check Feature Access",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: CheckParamsSchema },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: CheckResultSchema } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/setup_payment": {
|
||||
post: {
|
||||
summary: "Setup Payment Method",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: SetupPaymentParamsSchema },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: { "application/json": { schema: SetupPaymentResultSchema } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/billing_portal": {
|
||||
post: {
|
||||
summary: "Create Billing Portal Session",
|
||||
tags: ["core"],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": { schema: BillingPortalParamsSchema },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: {
|
||||
"application/json": { schema: BillingPortalResultSchema },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -14,38 +14,43 @@ export const APITrialsUsedSchema = z.object({
|
||||
fingerprint: z.string().nullish(),
|
||||
});
|
||||
|
||||
export const APICustomerSchema = z.object({
|
||||
// Internal fields
|
||||
autumn_id: 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",
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
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),
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
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(),
|
||||
})
|
||||
.meta({
|
||||
id: "Customer",
|
||||
description: "Customer object returned by the API",
|
||||
});
|
||||
|
||||
export type APICustomer = z.infer<typeof APICustomerSchema>;
|
||||
|
||||
@@ -66,18 +66,7 @@ export const APICusFeatureSchema = z
|
||||
})
|
||||
.extend(CoreCusFeatureSchema.shape);
|
||||
|
||||
export const CheckResultSchema = 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(CoreCusFeatureSchema.shape);
|
||||
|
||||
export type CusEntResponse = z.infer<typeof CusEntResponseSchema>;
|
||||
export type CusEntResponseV2 = z.infer<typeof APICusFeatureSchema>;
|
||||
export type CheckResponse = z.infer<typeof CheckResultSchema>;
|
||||
|
||||
export type CusRollover = z.infer<typeof CusRolloverSchema>;
|
||||
|
||||
@@ -5,7 +5,7 @@ export const APICusProductSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().nullable(),
|
||||
group: z.string().nullable(),
|
||||
status: z.enum(["active", "expired", "scheduled"]),
|
||||
status: z.enum(["active", "expired", "scheduled", "trialing"]),
|
||||
|
||||
canceled_at: z.number().nullish(),
|
||||
started_at: z.number(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod/v4";
|
||||
import { EntityDataSchema } from "../common/entityData.js";
|
||||
|
||||
// Create Customer Params (based on handlePostCustomer logic)
|
||||
export const CreateCustomerParamsSchema = z
|
||||
@@ -28,7 +29,6 @@ export const CreateCustomerParamsSchema = z
|
||||
example: "John Doe",
|
||||
}),
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: "not a valid email address" })
|
||||
.or(z.literal(""))
|
||||
.nullish()
|
||||
@@ -41,10 +41,14 @@ export const CreateCustomerParamsSchema = z
|
||||
"Unique identifier (eg, serial number) to detect duplicate customers and prevent free trial abuse",
|
||||
example: "fp_123abc",
|
||||
}),
|
||||
metadata: z.record(z.any(), z.any()).default({}).nullish().meta({
|
||||
description: "Additional metadata for the customer",
|
||||
example: { company: "Acme Inc" },
|
||||
}),
|
||||
metadata: z
|
||||
.record(z.any(), z.any())
|
||||
.default({})
|
||||
.nullish()
|
||||
.meta({
|
||||
description: "Additional metadata for the customer",
|
||||
example: { company: "Acme Inc" },
|
||||
}),
|
||||
stripe_id: z.string().nullish().meta({
|
||||
description: "Stripe customer ID if you already have one",
|
||||
example: "cus_stripe123",
|
||||
@@ -53,9 +57,8 @@ export const CreateCustomerParamsSchema = z
|
||||
description: "Entity ID to associate with the customer",
|
||||
example: "entity_123",
|
||||
}),
|
||||
entity_data: z.any().nullish().meta({
|
||||
entity_data: EntityDataSchema.nullish().meta({
|
||||
description: "Data for creating an entity",
|
||||
example: { name: "Team Alpha", feature_id: "seats" },
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
@@ -105,11 +108,14 @@ export const UpdateCustomerParamsSchema = z
|
||||
"Unique identifier (eg, serial number) to detect duplicate customers",
|
||||
example: "fp_123abc",
|
||||
}),
|
||||
metadata: z.record(z.any(), z.any()).nullish().meta({
|
||||
description:
|
||||
"Additional metadata for the customer (set individual keys to null to delete them)",
|
||||
example: { company: "Acme Inc" },
|
||||
}),
|
||||
metadata: z
|
||||
.record(z.any(), z.any())
|
||||
.nullish()
|
||||
.meta({
|
||||
description:
|
||||
"Additional metadata for the customer (set individual keys to null to delete them)",
|
||||
example: { company: "Acme Inc" },
|
||||
}),
|
||||
stripe_id: z.string().nullish().meta({
|
||||
description: "Stripe customer ID",
|
||||
example: "cus_stripe123",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { CusExpand } from "@models/cusModels/cusExpand.js";
|
||||
import { z } from "zod/v4";
|
||||
import { SuccessResponseSchema } from "../common/commonResponses.js";
|
||||
import { APICustomerSchema } from "./apiCustomer.js";
|
||||
import {
|
||||
CreateCustomerParamsSchema,
|
||||
ListCustomersResponseSchema,
|
||||
UpdateCustomerParamsSchema,
|
||||
} from "./customerOpModels.js";
|
||||
import { SuccessResponseSchema } from "../common/commonResponses.js";
|
||||
|
||||
export const customerOps = {
|
||||
"/customers": {
|
||||
@@ -57,7 +58,7 @@ export const customerOps = {
|
||||
customer_id: z.string(),
|
||||
}),
|
||||
query: z.object({
|
||||
expand: z.string().optional(),
|
||||
expand: z.array(z.enum(CusExpand)).optional(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { EntityResponseSchema } from "@models/cusModels/entityModels/entityResModels.js";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
export const APIEntitySchema = EntityResponseSchema;
|
||||
export const APIEntitySchema = EntityResponseSchema.meta({
|
||||
id: "Entity",
|
||||
description: "Entity object returned by the API",
|
||||
});
|
||||
export type APIEntity = z.infer<typeof APIEntitySchema>;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EntityExpand } from "@models/cusModels/entityModels/entityExpand.js";
|
||||
import { z } from "zod/v4";
|
||||
import { SuccessResponseSchema } from "../common/commonResponses.js";
|
||||
import { APIEntitySchema } from "./apiEntity.js";
|
||||
import { CreateEntityParamsSchema } from "./entityOpModels.js";
|
||||
import { SuccessResponseSchema } from "../common/commonResponses.js";
|
||||
|
||||
const EntityListResponseSchema = z
|
||||
.object({
|
||||
@@ -13,28 +14,6 @@ const EntityListResponseSchema = z
|
||||
|
||||
export const entityOps = {
|
||||
"/customers/{customer_id}/entities": {
|
||||
get: {
|
||||
summary: "List Entities",
|
||||
tags: ["entities"],
|
||||
requestParams: {
|
||||
path: z.object({
|
||||
customer_id: z.string(),
|
||||
}),
|
||||
query: z.object({
|
||||
expand: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "200 OK",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: EntityListResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
post: {
|
||||
summary: "Create Entity",
|
||||
tags: ["entities"],
|
||||
@@ -66,7 +45,7 @@ export const entityOps = {
|
||||
entity_id: z.string(),
|
||||
}),
|
||||
query: z.object({
|
||||
expand: z.string().optional(),
|
||||
expand: z.array(z.enum(EntityExpand)).optional(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
|
||||
@@ -7,27 +7,32 @@ export enum APIFeatureType {
|
||||
CreditSystem = "credit_system",
|
||||
}
|
||||
|
||||
export const APIFeatureSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().nullish(),
|
||||
type: z.nativeEnum(APIFeatureType),
|
||||
display: z
|
||||
.object({
|
||||
singular: z.string(),
|
||||
plural: z.string(),
|
||||
})
|
||||
.nullish(),
|
||||
export const APIFeatureSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string().nullish(),
|
||||
type: z.nativeEnum(APIFeatureType),
|
||||
display: z
|
||||
.object({
|
||||
singular: z.string(),
|
||||
plural: z.string(),
|
||||
})
|
||||
.nullish(),
|
||||
|
||||
credit_schema: z
|
||||
.array(
|
||||
z.object({
|
||||
metered_feature_id: z.string(),
|
||||
credit_cost: z.number(),
|
||||
}),
|
||||
)
|
||||
.nullish(),
|
||||
credit_schema: z
|
||||
.array(
|
||||
z.object({
|
||||
metered_feature_id: z.string(),
|
||||
credit_cost: z.number(),
|
||||
}),
|
||||
)
|
||||
.nullish(),
|
||||
|
||||
archived: z.boolean().nullish(),
|
||||
});
|
||||
archived: z.boolean().nullish(),
|
||||
})
|
||||
.meta({
|
||||
id: "Feature",
|
||||
description: "Feature object returned by the API",
|
||||
});
|
||||
|
||||
export type APIFeature = z.infer<typeof APIFeatureSchema>;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Core
|
||||
export * from "./core/attachModels.js";
|
||||
export * from "./core/checkModels.js";
|
||||
export * from "./core/checkoutModels.js";
|
||||
export * from "./core/coreOpModels.js";
|
||||
export * from "./core/coreOpenApi.js";
|
||||
export * from "./core/coreOpModels.js";
|
||||
|
||||
// Customers
|
||||
|
||||
@@ -15,8 +16,8 @@ export * from "./customers/customersOpenApi.js";
|
||||
|
||||
// Entities
|
||||
export * from "./entities/apiEntity.js";
|
||||
export * from "./entities/entityOpModels.js";
|
||||
export * from "./entities/entitiesOpenApi.js";
|
||||
export * from "./entities/entityOpModels.js";
|
||||
|
||||
// Features
|
||||
export * from "./features/apiFeature.js";
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import "dotenv/config";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { AppEnv } from "@models/genModels/genEnums.js";
|
||||
import yaml from "yaml";
|
||||
import { z } from "zod/v4";
|
||||
import { createDocument } from "zod-openapi";
|
||||
import { CustomerDataSchema } from "./common/customerData.js";
|
||||
import { EntityDataSchema } from "./common/entityData.js";
|
||||
import { coreOps } from "./core/coreOpenApi.js";
|
||||
import { customerOps } from "./customers/customersOpenApi.js";
|
||||
import { entityOps } from "./entities/entitiesOpenApi.js";
|
||||
@@ -36,12 +39,14 @@ const document = createDocument({
|
||||
.object({
|
||||
message: z.string(),
|
||||
code: z.string(),
|
||||
env: z.nativeEnum(AppEnv),
|
||||
env: z.enum(AppEnv),
|
||||
})
|
||||
.meta({
|
||||
id: "AutumnError",
|
||||
description: "An error that occurred in the API",
|
||||
}),
|
||||
customerData: CustomerDataSchema,
|
||||
entityData: EntityDataSchema,
|
||||
},
|
||||
securitySchemes: {
|
||||
secretKey: {
|
||||
@@ -64,15 +69,24 @@ const document = createDocument({
|
||||
// Export to YAML file during build
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
try {
|
||||
// Export as JSON (YAML export has issues with zod schemas)
|
||||
// Convert to JSON first to strip out Zod schemas and function references
|
||||
const jsonStr = JSON.stringify(document, null, 2);
|
||||
writeFileSync("./openapi.json", jsonStr, "utf8");
|
||||
console.log("OpenAPI document exported to openapi.json");
|
||||
|
||||
// TODO: Fix YAML export - currently fails with "Tag not resolved for Function value"
|
||||
// const yamlContent = yaml.stringify(document);
|
||||
// writeFileSync("./openapi.yaml", yamlContent, "utf8");
|
||||
// console.log("OpenAPI document exported to openapi.yaml");
|
||||
// Convert JSON to YAML (this avoids function serialization issues)
|
||||
const jsonObj = JSON.parse(jsonStr);
|
||||
const yamlContent = yaml.stringify(jsonObj);
|
||||
|
||||
if (process.env.STAINLESS_PATH) {
|
||||
writeFileSync(
|
||||
`${process.env.STAINLESS_PATH}/openapi.yml`,
|
||||
yamlContent,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`OpenAPI document exported to ${process.env.STAINLESS_PATH}/openapi.yml`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to export OpenAPI document:", error);
|
||||
}
|
||||
|
||||
@@ -28,27 +28,29 @@ export const CreateProductV2ParamsSchema = z
|
||||
description: "Create Product",
|
||||
});
|
||||
|
||||
export const UpdateProductV2ParamsSchema = CreateProductV2ParamsSchema.extend({
|
||||
id: z.string().nonempty().regex(idRegex).optional(),
|
||||
name: z
|
||||
.string()
|
||||
.refine((val) => val.length > 0, {
|
||||
message: "name must be a non-empty string",
|
||||
})
|
||||
.optional(),
|
||||
export const UpdateProductV2ParamsSchema = z
|
||||
.object({
|
||||
id: z.string().nonempty().regex(idRegex).optional(),
|
||||
name: z
|
||||
.string()
|
||||
.refine((val) => val.length > 0, {
|
||||
message: "name must be a non-empty string",
|
||||
})
|
||||
.optional(),
|
||||
|
||||
is_add_on: z.boolean().optional(),
|
||||
is_default: z.boolean().optional(),
|
||||
version: z.number().optional(),
|
||||
group: z.string().optional(),
|
||||
archived: z.boolean().optional(),
|
||||
is_add_on: z.boolean().optional(),
|
||||
is_default: z.boolean().optional(),
|
||||
version: z.number().optional(),
|
||||
group: z.string().optional(),
|
||||
archived: z.boolean().optional(),
|
||||
|
||||
// items: z.array(CreateProductItemParamsSchema).optional(),
|
||||
free_trial: CreateFreeTrialSchema.nullish(),
|
||||
}).meta({
|
||||
id: "UpdateProductParams",
|
||||
description: "Update Product",
|
||||
});
|
||||
items: z.array(CreateProductItemParamsSchema).optional(),
|
||||
free_trial: CreateFreeTrialSchema.nullish(),
|
||||
})
|
||||
.meta({
|
||||
id: "UpdateProductParams",
|
||||
description: "Update Product",
|
||||
});
|
||||
|
||||
export const UpdateProductQuerySchema = z.object({
|
||||
version: z.string().optional(),
|
||||
|
||||
@@ -27,7 +27,6 @@ export * from "./models/attachModels/attachEnums/AttachFunction.js";
|
||||
|
||||
// Attach Models
|
||||
export * from "./models/attachModels/attachPreviewModels.js";
|
||||
export * from "./models/attachModels/checkoutModels.js";
|
||||
export * from "./models/authModels/membership.js";
|
||||
export * from "./models/chatResultModels/chatResultFeature.js";
|
||||
export * from "./models/chatResultModels/chatResultFeature.js";
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// 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";
|
||||
|
||||
// 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 type CheckoutLine = z.infer<typeof CheckoutLineSchema>;
|
||||
@@ -3,7 +3,7 @@ import { FreeTrialDuration } from "./freeTrialEnums.js";
|
||||
|
||||
export const FreeTrialSchema = z.object({
|
||||
id: z.string(),
|
||||
duration: z.nativeEnum(FreeTrialDuration),
|
||||
duration: z.enum(FreeTrialDuration),
|
||||
length: z.number(),
|
||||
unique_fingerprint: z.boolean(),
|
||||
|
||||
|
||||
@@ -46,23 +46,6 @@ export const UpdateProductSchema = z.object({
|
||||
archived: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const FrontendProductSchema = ProductSchema.omit({
|
||||
org_id: true,
|
||||
created_at: true,
|
||||
env: true,
|
||||
processor: true,
|
||||
}).extend({
|
||||
isActive: z.boolean(),
|
||||
prices: z.array(PriceSchema),
|
||||
entitlements: z.array(
|
||||
EntitlementSchema.extend({
|
||||
feature: FeatureSchema,
|
||||
}),
|
||||
),
|
||||
free_trial: FreeTrialSchema,
|
||||
options: z.any(),
|
||||
});
|
||||
|
||||
export const FullProductSchema = ProductSchema.extend({
|
||||
prices: z.array(PriceSchema),
|
||||
entitlements: z.array(
|
||||
@@ -84,7 +67,6 @@ export type ProductCounts = {
|
||||
};
|
||||
|
||||
export type Product = z.infer<typeof ProductSchema>;
|
||||
export type FrontendProduct = z.infer<typeof FrontendProductSchema>;
|
||||
export type FullProduct = z.infer<typeof FullProductSchema>;
|
||||
export type CreateProduct = z.infer<typeof CreateProductSchema>;
|
||||
export type UpdateProduct = z.infer<typeof UpdateProductSchema>;
|
||||
|
||||
19426
shared/openapi.json
19426
shared/openapi.json
File diff suppressed because it is too large
Load Diff
@@ -1,799 +0,0 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Autumn API
|
||||
version: 1.2.0
|
||||
servers:
|
||||
- url: https://api.useautumn.com
|
||||
description: Production server
|
||||
security:
|
||||
- secretKey: []
|
||||
paths:
|
||||
/products:
|
||||
post:
|
||||
summary: Create Product
|
||||
tags:
|
||||
- products
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateProductParams"
|
||||
responses:
|
||||
"200":
|
||||
description: 200 OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Product"
|
||||
patch:
|
||||
summary: Update Product
|
||||
tags:
|
||||
- products
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdateProductParams"
|
||||
responses:
|
||||
"200":
|
||||
description: 200 OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Product"
|
||||
components:
|
||||
schemas:
|
||||
CreateProductParams:
|
||||
description: Create Product
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z0-9_-]+$
|
||||
name:
|
||||
type: string
|
||||
is_add_on:
|
||||
default: false
|
||||
type: boolean
|
||||
is_default:
|
||||
default: false
|
||||
type: boolean
|
||||
version:
|
||||
type: number
|
||||
group:
|
||||
default: ""
|
||||
type: string
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
feature_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
feature_type:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- single_use
|
||||
- continuous_use
|
||||
- boolean
|
||||
- static
|
||||
- type: "null"
|
||||
included_usage:
|
||||
anyOf:
|
||||
- anyOf:
|
||||
- type: number
|
||||
- type: string
|
||||
const: inf
|
||||
- type: "null"
|
||||
interval:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- minute
|
||||
- hour
|
||||
- day
|
||||
- week
|
||||
- month
|
||||
- quarter
|
||||
- semi_annual
|
||||
- year
|
||||
- type: "null"
|
||||
interval_count:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
entity_feature_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
usage_model:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- prepaid
|
||||
- pay_per_use
|
||||
- type: "null"
|
||||
price:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
tiers:
|
||||
anyOf:
|
||||
- type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
to:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: string
|
||||
const: inf
|
||||
amount:
|
||||
type: number
|
||||
required:
|
||||
- to
|
||||
- amount
|
||||
- type: "null"
|
||||
billing_units:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
usage_limit:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
reset_usage_when_enabled:
|
||||
anyOf:
|
||||
- type: boolean
|
||||
- type: "null"
|
||||
config:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
on_increase:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- bill_immediately
|
||||
- prorate_immediately
|
||||
- prorate_next_cycle
|
||||
- bill_next_cycle
|
||||
- type: "null"
|
||||
on_decrease:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- prorate
|
||||
- prorate_immediately
|
||||
- prorate_next_cycle
|
||||
- none
|
||||
- no_prorations
|
||||
- type: "null"
|
||||
rollover:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
max:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
duration:
|
||||
default: month
|
||||
type: string
|
||||
enum:
|
||||
- month
|
||||
- forever
|
||||
length:
|
||||
type: number
|
||||
required:
|
||||
- max
|
||||
- length
|
||||
- type: "null"
|
||||
- type: "null"
|
||||
created_at:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
entitlement_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
price_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
price_config:
|
||||
anyOf:
|
||||
- {}
|
||||
- type: "null"
|
||||
free_trial:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
length:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: number
|
||||
unique_fingerprint:
|
||||
default: false
|
||||
type: boolean
|
||||
duration:
|
||||
default: day
|
||||
type: string
|
||||
enum:
|
||||
- day
|
||||
- month
|
||||
- year
|
||||
card_required:
|
||||
default: true
|
||||
type: boolean
|
||||
required:
|
||||
- length
|
||||
- type: "null"
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
UpdateProductParams:
|
||||
description: Update Product
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z0-9_-]+$
|
||||
name:
|
||||
type: string
|
||||
is_add_on:
|
||||
type: boolean
|
||||
is_default:
|
||||
type: boolean
|
||||
version:
|
||||
type: number
|
||||
group:
|
||||
type: string
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
feature_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
feature_type:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- single_use
|
||||
- continuous_use
|
||||
- boolean
|
||||
- static
|
||||
- type: "null"
|
||||
included_usage:
|
||||
anyOf:
|
||||
- anyOf:
|
||||
- type: number
|
||||
- type: string
|
||||
const: inf
|
||||
- type: "null"
|
||||
interval:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- minute
|
||||
- hour
|
||||
- day
|
||||
- week
|
||||
- month
|
||||
- quarter
|
||||
- semi_annual
|
||||
- year
|
||||
- type: "null"
|
||||
interval_count:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
entity_feature_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
usage_model:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- prepaid
|
||||
- pay_per_use
|
||||
- type: "null"
|
||||
price:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
tiers:
|
||||
anyOf:
|
||||
- type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
to:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: string
|
||||
const: inf
|
||||
amount:
|
||||
type: number
|
||||
required:
|
||||
- to
|
||||
- amount
|
||||
- type: "null"
|
||||
billing_units:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
usage_limit:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
reset_usage_when_enabled:
|
||||
anyOf:
|
||||
- type: boolean
|
||||
- type: "null"
|
||||
config:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
on_increase:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- bill_immediately
|
||||
- prorate_immediately
|
||||
- prorate_next_cycle
|
||||
- bill_next_cycle
|
||||
- type: "null"
|
||||
on_decrease:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- prorate
|
||||
- prorate_immediately
|
||||
- prorate_next_cycle
|
||||
- none
|
||||
- no_prorations
|
||||
- type: "null"
|
||||
rollover:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
max:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
duration:
|
||||
default: month
|
||||
type: string
|
||||
enum:
|
||||
- month
|
||||
- forever
|
||||
length:
|
||||
type: number
|
||||
required:
|
||||
- max
|
||||
- length
|
||||
- type: "null"
|
||||
- type: "null"
|
||||
created_at:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
entitlement_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
price_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
price_config:
|
||||
anyOf:
|
||||
- {}
|
||||
- type: "null"
|
||||
free_trial:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
length:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: number
|
||||
unique_fingerprint:
|
||||
default: false
|
||||
type: boolean
|
||||
duration:
|
||||
default: day
|
||||
type: string
|
||||
enum:
|
||||
- day
|
||||
- month
|
||||
- year
|
||||
card_required:
|
||||
default: true
|
||||
type: boolean
|
||||
required:
|
||||
- length
|
||||
- type: "null"
|
||||
archived:
|
||||
type: boolean
|
||||
Product:
|
||||
description: A product
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: The ID of the product you set when creating the product
|
||||
example: pro_plan
|
||||
type: string
|
||||
name:
|
||||
description: The name of the product
|
||||
example: Pro Plan
|
||||
type: string
|
||||
group:
|
||||
description: The group of the product
|
||||
example: product_set_1
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
env:
|
||||
description: The environment of the product
|
||||
example: production
|
||||
type: string
|
||||
enum:
|
||||
- sandbox
|
||||
- live
|
||||
is_add_on:
|
||||
description: Whether the product is an add-on and can be purchased alongside
|
||||
other products
|
||||
example: true
|
||||
type: boolean
|
||||
is_default:
|
||||
description: Whether the product is the default product
|
||||
example: true
|
||||
type: boolean
|
||||
archived:
|
||||
description: Whether this product has been archived and is no longer available
|
||||
example: false
|
||||
type: boolean
|
||||
version:
|
||||
description: The version of the product
|
||||
example: 1
|
||||
type: number
|
||||
created_at:
|
||||
description: The timestamp of when the product was created in milliseconds since
|
||||
epoch
|
||||
example: 1759247877000
|
||||
type: number
|
||||
items:
|
||||
description: Array of product items that define the features and pricing
|
||||
example:
|
||||
- feature_id: <string>
|
||||
feature_type: single_use
|
||||
included_usage: 123
|
||||
interval: <string>
|
||||
usage_model: prepaid
|
||||
price: 123
|
||||
billing_units: 123
|
||||
entity_feature_id: <string>
|
||||
reset_usage_when_enabled: true
|
||||
tiers:
|
||||
- to: 123
|
||||
amount: 123
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ProductItem"
|
||||
free_trial:
|
||||
description: Free trial configuration for this product, if available
|
||||
example:
|
||||
duration: <string>
|
||||
length: 123
|
||||
unique_fingerprint: true
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
duration:
|
||||
type: string
|
||||
enum:
|
||||
- day
|
||||
- month
|
||||
- year
|
||||
length:
|
||||
type: number
|
||||
unique_fingerprint:
|
||||
type: boolean
|
||||
card_required:
|
||||
type: boolean
|
||||
trial_available:
|
||||
default: true
|
||||
anyOf:
|
||||
- type: boolean
|
||||
- type: "null"
|
||||
required:
|
||||
- duration
|
||||
- length
|
||||
- unique_fingerprint
|
||||
- card_required
|
||||
- trial_available
|
||||
additionalProperties: false
|
||||
- type: "null"
|
||||
base_variant_id:
|
||||
description: ID of the base variant this product is derived from
|
||||
example: var_1234567890abcdef
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
scenario:
|
||||
description: Scenario context for when this product is used in attach flows
|
||||
example: upgrade
|
||||
type: string
|
||||
enum:
|
||||
- scheduled
|
||||
- active
|
||||
- new
|
||||
- renew
|
||||
- upgrade
|
||||
- downgrade
|
||||
- cancel
|
||||
- expired
|
||||
properties:
|
||||
description: Additional properties and metadata for the product
|
||||
example:
|
||||
is_free: false
|
||||
is_one_off: false
|
||||
interval_group: monthly
|
||||
has_trial: true
|
||||
updateable: true
|
||||
type: object
|
||||
properties:
|
||||
is_free:
|
||||
description: True if the product has no base price or usage prices
|
||||
example: false
|
||||
type: boolean
|
||||
is_one_off:
|
||||
description: True if the product only contains a one-time price
|
||||
example: false
|
||||
type: boolean
|
||||
interval_group:
|
||||
description: The billing interval group for recurring products (e.g., 'monthly',
|
||||
'yearly')
|
||||
example: monthly
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
has_trial:
|
||||
description: True if the product includes a free trial
|
||||
example: true
|
||||
anyOf:
|
||||
- type: boolean
|
||||
- type: "null"
|
||||
updateable:
|
||||
description: True if the product can be updated after creation (only applicable
|
||||
if there are prepaid recurring prices)
|
||||
example: true
|
||||
anyOf:
|
||||
- type: boolean
|
||||
- type: "null"
|
||||
required:
|
||||
- is_free
|
||||
- is_one_off
|
||||
additionalProperties: false
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- group
|
||||
- env
|
||||
- is_add_on
|
||||
- is_default
|
||||
- archived
|
||||
- version
|
||||
- created_at
|
||||
- items
|
||||
- free_trial
|
||||
- base_variant_id
|
||||
additionalProperties: false
|
||||
ProductItem:
|
||||
description: A product item that defines a feature
|
||||
example:
|
||||
feature_id: feature_1
|
||||
feature_type: single_use
|
||||
included_usage: 123
|
||||
interval: monthly
|
||||
usage_model: prepaid
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- feature
|
||||
- priced_feature
|
||||
- price
|
||||
- type: "null"
|
||||
feature_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
feature_type:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- single_use
|
||||
- continuous_use
|
||||
- boolean
|
||||
- static
|
||||
- type: "null"
|
||||
feature:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- boolean
|
||||
- single_use
|
||||
- continuous_use
|
||||
- credit_system
|
||||
display:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
singular:
|
||||
type: string
|
||||
plural:
|
||||
type: string
|
||||
required:
|
||||
- singular
|
||||
- plural
|
||||
additionalProperties: false
|
||||
- type: "null"
|
||||
credit_schema:
|
||||
anyOf:
|
||||
- type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
metered_feature_id:
|
||||
type: string
|
||||
credit_cost:
|
||||
type: number
|
||||
required:
|
||||
- metered_feature_id
|
||||
- credit_cost
|
||||
additionalProperties: false
|
||||
- type: "null"
|
||||
archived:
|
||||
anyOf:
|
||||
- type: boolean
|
||||
- type: "null"
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
additionalProperties: false
|
||||
- type: "null"
|
||||
included_usage:
|
||||
anyOf:
|
||||
- anyOf:
|
||||
- type: number
|
||||
- type: string
|
||||
const: inf
|
||||
- type: "null"
|
||||
interval:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- minute
|
||||
- hour
|
||||
- day
|
||||
- week
|
||||
- month
|
||||
- quarter
|
||||
- semi_annual
|
||||
- year
|
||||
- type: "null"
|
||||
interval_count:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
price:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
tiers:
|
||||
anyOf:
|
||||
- type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
to:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: string
|
||||
const: inf
|
||||
amount:
|
||||
type: number
|
||||
required:
|
||||
- to
|
||||
- amount
|
||||
additionalProperties: false
|
||||
- type: "null"
|
||||
usage_model:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- prepaid
|
||||
- pay_per_use
|
||||
- type: "null"
|
||||
billing_units:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
reset_usage_when_enabled:
|
||||
anyOf:
|
||||
- type: boolean
|
||||
- type: "null"
|
||||
quantity:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
next_cycle_quantity:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
entity_feature_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
display:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
primary_text:
|
||||
type: string
|
||||
secondary_text:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
required:
|
||||
- primary_text
|
||||
additionalProperties: false
|
||||
- type: "null"
|
||||
additionalProperties: false
|
||||
AutumnError:
|
||||
description: An error that occurred in the API
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
code:
|
||||
type: string
|
||||
env:
|
||||
type: string
|
||||
enum:
|
||||
- sandbox
|
||||
- live
|
||||
required:
|
||||
- message
|
||||
- code
|
||||
- env
|
||||
additionalProperties: false
|
||||
securitySchemes:
|
||||
secretKey:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
@@ -14,6 +14,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build:tsc": "tsc && bun api/openapi.ts",
|
||||
"api": "bun api/openapi.ts",
|
||||
"build": "bun build ./index.ts --outdir dist --format esm --target bun --external zod",
|
||||
"dev": "bunx nodemon --ext ts --ignore dist --exec \"bun run build && bun run dev:dts\"",
|
||||
"dev:dts": "tsc --emitDeclarationOnly --outDir dist --project tsconfig.json",
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import ProductSidebar from "@/views/products/product/ProductSidebar";
|
||||
import LoadingScreen from "@/views/general/LoadingScreen";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
Customer,
|
||||
Entity,
|
||||
Feature,
|
||||
ProductItem,
|
||||
ProductV2,
|
||||
} from "@autumn/shared";
|
||||
import { useAxiosSWR } from "@/services/useAxiosSwr";
|
||||
import { CustomToaster } from "@/components/general/CustomToaster";
|
||||
import { ManageProduct } from "@/views/products/product/ManageProduct";
|
||||
import { ProductContext } from "@/views/products/product/ProductContext";
|
||||
import { type ProductItem, type ProductV2 } from "@autumn/shared";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Link, useParams, useSearchParams } from "react-router";
|
||||
import ErrorScreen from "@/views/general/ErrorScreen";
|
||||
import { ProductOptions } from "./ProductOptions";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { CustomerProductBreadcrumbs } from "./components/CustomerProductBreadcrumbs";
|
||||
import { FrontendProduct, useAttachState } from "./hooks/useAttachState";
|
||||
import { sortProductItems } from "@/utils/productUtils";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
import { CustomToaster } from "@/components/general/CustomToaster";
|
||||
import { useOrg } from "@/hooks/common/useOrg";
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
import { useCusProductQuery } from "./hooks/useCusProductQuery";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { notNullish } from "@/utils/genUtils";
|
||||
import { sortProductItems } from "@/utils/productUtils";
|
||||
import ErrorScreen from "@/views/general/ErrorScreen";
|
||||
import LoadingScreen from "@/views/general/LoadingScreen";
|
||||
import { ManageProduct } from "@/views/products/product/ManageProduct";
|
||||
import { ProductContext } from "@/views/products/product/ProductContext";
|
||||
import ProductSidebar from "@/views/products/product/ProductSidebar";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
import { CustomerProductBreadcrumbs } from "./components/CustomerProductBreadcrumbs";
|
||||
import { useAttachState } from "./hooks/useAttachState";
|
||||
import { useCusProductQuery } from "./hooks/useCusProductQuery";
|
||||
import { ProductOptions } from "./ProductOptions";
|
||||
|
||||
interface OptionValue {
|
||||
feature_id: string;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FeatureOptions, ProductV2 } from "@autumn/shared";
|
||||
import type { FeatureOptions, ProductV2 } from "@autumn/shared";
|
||||
|
||||
export type FrontendProduct = ProductV2 & {
|
||||
isActive: boolean;
|
||||
options: FeatureOptions[];
|
||||
isCanceled: boolean;
|
||||
};
|
||||
// export type FrontendProduct = ProductV2 & {
|
||||
// isActive: boolean;
|
||||
// options: FeatureOptions[];
|
||||
// isCanceled: boolean;
|
||||
// };
|
||||
|
||||
export const getAttachBody = ({
|
||||
customerId,
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import {
|
||||
type AttachPreview,
|
||||
type FeatureOptions,
|
||||
type FullCusProduct,
|
||||
isCanceled,
|
||||
type ProductItem,
|
||||
type ProductV2,
|
||||
UsageModel,
|
||||
} from "@autumn/shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { notNullish } from "@/utils/genUtils";
|
||||
import { isFeatureItem } from "@/utils/product/getItemType";
|
||||
import { isOneOffProduct } from "@/utils/product/priceUtils";
|
||||
import { sortProductItems } from "@/utils/productUtils";
|
||||
import {
|
||||
AttachPreview,
|
||||
CusProduct,
|
||||
FeatureOptions,
|
||||
FullCusProduct,
|
||||
isCanceled,
|
||||
ProductItem,
|
||||
ProductV2,
|
||||
UsageModel,
|
||||
} from "@autumn/shared";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type FrontendProduct = ProductV2 & {
|
||||
isActive: boolean;
|
||||
options: FeatureOptions[];
|
||||
isCanceled: boolean;
|
||||
};
|
||||
// export type FrontendProduct = ProductV2 & {
|
||||
// isActive: boolean;
|
||||
// options: FeatureOptions[];
|
||||
// isCanceled: boolean;
|
||||
// };
|
||||
|
||||
export enum AttachCase {
|
||||
AddOn = "Add On",
|
||||
@@ -31,7 +30,7 @@ export enum AttachCase {
|
||||
const productHasPrepaid = (items: ProductItem[]) => {
|
||||
return items.some(
|
||||
(item) =>
|
||||
item.usage_model == UsageModel.Prepaid && notNullish(item.interval),
|
||||
item.usage_model === UsageModel.Prepaid && notNullish(item.interval),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user