fix: adding custom feature from dashboard, multi product attach

This commit is contained in:
John Yeo
2025-06-29 11:29:25 +01:00
parent 36b7521546
commit 54e6531da2
12 changed files with 82 additions and 8 deletions

View File

@@ -47,7 +47,11 @@ export const createStripeCusIfNotExists = async ({
expand: ["test_clock", "invoice_settings.default_payment_method"],
},
);
return stripeCus as Stripe.Customer;
if (!stripeCus.deleted) {
return stripeCus as Stripe.Customer;
} else {
createNew = true;
}
} catch (error) {
createNew = true;
}

View File

@@ -26,7 +26,8 @@ const getProductsForAttach = async ({
if (notNullish(product_ids)) {
let freeTrialProds = products.filter((prod) => notNullish(prod.free_trial));
if (freeTrialProds.length > 0) {
console.log("freeTrialProds", freeTrialProds);
if (freeTrialProds.length > 1) {
throw new RecaseError({
message:
"When providing product_ids, can't have multiple free trial products",

View File

@@ -23,7 +23,7 @@ import { hasPrepaidPrice } from "@/internal/products/prices/priceUtils/usagePric
import { attachParamToCusProducts } from "./convertAttachParams.js";
import { findPrepaidPrice } from "@/internal/products/prices/priceUtils/findPriceUtils.js";
const checkMultiProductErrors = async ({
const handleMultiProductErrors = async ({
attachParams,
}: {
attachParams: AttachParams;
@@ -291,7 +291,7 @@ export const getAttachBranch = async ({
}) => {
// 1. Multi product
if (notNullish(attachBody.product_ids)) {
await checkMultiProductErrors({ attachParams });
await handleMultiProductErrors({ attachParams });
return AttachBranch.MultiProduct;
}

View File

@@ -9,8 +9,6 @@ import { handleAttachErrors } from "./attachUtils/handleAttachErrors.js";
import { checkStripeConnections, createStripePrices } from "./attachRouter.js";
import { insertCustomItems } from "./attachUtils/insertCustomItems.js";
import { runAttachFunction } from "./attachUtils/getAttachFunction.js";
import RecaseError from "@/utils/errorUtils.js";
import { FeatureSchema } from "@autumn/shared";
export const handleAttach = async (req: any, res: any) =>
routeHandler({

View File

@@ -23,6 +23,7 @@ export const attachParamsToPreview = async ({
logger: any;
}) => {
// Handle existing product
const branch = await getAttachBranch({
req,
attachBody,

View File

@@ -239,10 +239,14 @@ export class CusProductService {
static async getByProductId({
db,
productId,
orgId,
env,
limit = 1,
}: {
db: DrizzleCli;
productId: string;
orgId: string;
env: AppEnv;
limit?: number;
}) {
let data = await db
@@ -252,7 +256,13 @@ export class CusProductService {
products,
eq(customerProducts.internal_product_id, products.internal_id),
)
.where(eq(products.id, productId))
.where(
and(
eq(products.id, productId),
eq(products.org_id, orgId),
eq(products.env, env),
),
)
.limit(1);
return data.map((d) => ({

View File

@@ -30,6 +30,13 @@ export const handleListProductsBeta = async (req: any, res: any) =>
})(),
]);
if (req.query.v1_schema === "true") {
res.status(200).json({
list: products,
});
return;
}
sortFullProducts({ products });
let batchResponse = [];

View File

@@ -121,6 +121,8 @@ export const handleUpdateProductDetails = async ({
const customersOnAllVersions = await CusProductService.getByProductId({
db,
productId: curProduct.id,
orgId: org.id,
env: curProduct.env as AppEnv,
});
if (productDetailsSame(curProduct, newProduct)) {

View File

@@ -4,6 +4,12 @@ import { Price, prices, Product } from "@autumn/shared";
import { and, eq, inArray } from "drizzle-orm";
export class PriceService {
static async get({ db, id }: { db: DrizzleCli; id: string }) {
return (await db.query.prices.findFirst({
where: eq(prices.id, id),
})) as Price;
}
static async getCustomInEntIds({
db,
entitlementIds,

View File

@@ -0,0 +1,43 @@
import { DrizzleCli } from "@/db/initDrizzle.js";
import { PriceService } from "../PriceService.js";
import { prices } from "@autumn/shared";
import { eq } from "drizzle-orm";
import { generateId } from "@/utils/genUtils.js";
import { Price, UsagePriceConfig } from "@autumn/shared";
export const copyPrice = async ({
db,
priceId,
usagePriceConfig,
isCustom,
}: {
db: DrizzleCli;
priceId: string;
usagePriceConfig?: Partial<UsagePriceConfig>;
isCustom?: boolean;
}) => {
let price = (await db.query.prices.findFirst({
where: eq(prices.id, priceId),
})) as Price;
let newPrice = structuredClone(price);
newPrice = {
...newPrice,
id: generateId("pr"),
created_at: Date.now(),
is_custom: isCustom || newPrice.is_custom,
};
if (usagePriceConfig) {
newPrice = {
...newPrice,
config: {
...newPrice.config,
...usagePriceConfig,
},
};
}
return newPrice;
};

View File

@@ -186,6 +186,7 @@ export const itemsAreSame = ({
item1: FeatureItemSchema.parse(item1),
item2: item2 as FeatureItem,
});
pricesChanged = false;
}

View File

@@ -29,7 +29,8 @@ export const FeatureItemSchema = ProductItemSchema.pick({
}
return num;
}),
})
.nullish(),
});
export type FeatureItem = z.infer<typeof FeatureItemSchema>;