224 lines
4.3 KiB
TypeScript
224 lines
4.3 KiB
TypeScript
import {
|
|
type AppEnv,
|
|
customerProducts,
|
|
type CreateReward,
|
|
type CreateRewardProgram,
|
|
isUsagePrice,
|
|
} from "@autumn/shared";
|
|
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
|
import type { AutumnInt } from "@/external/autumn/autumnCli.js";
|
|
import { ProductService } from "@/internal/products/ProductService.js";
|
|
import { eq } from "drizzle-orm";
|
|
|
|
export const createProduct = async ({
|
|
db,
|
|
orgId,
|
|
env,
|
|
autumn,
|
|
product,
|
|
prefix,
|
|
createInStripe,
|
|
}: {
|
|
db: DrizzleCli;
|
|
orgId: string;
|
|
env: AppEnv;
|
|
autumn: AutumnInt;
|
|
product: any;
|
|
prefix?: string;
|
|
createInStripe?: boolean;
|
|
}) => {
|
|
try {
|
|
const products = await ProductService.listFull({
|
|
db,
|
|
orgId,
|
|
env,
|
|
returnAll: true,
|
|
inIds: [product.id],
|
|
});
|
|
|
|
// customer_products_internal_product_id_fkey has no ON DELETE CASCADE.
|
|
// Wipe referencing rows first so the product delete succeeds.
|
|
for (const prod of products) {
|
|
await db
|
|
.delete(customerProducts)
|
|
.where(eq(customerProducts.internal_product_id, prod.internal_id));
|
|
}
|
|
|
|
const batchDelete = [];
|
|
for (const prod of products) {
|
|
batchDelete.push(
|
|
ProductService.deleteByInternalId({
|
|
db,
|
|
internalId: prod.internal_id,
|
|
orgId,
|
|
env,
|
|
}),
|
|
);
|
|
}
|
|
|
|
await Promise.all(batchDelete);
|
|
} catch (error) {
|
|
console.error("Error deleting product", error);
|
|
// Ignore deletion errors (might have customers attached)
|
|
}
|
|
|
|
const clone = structuredClone(product);
|
|
if (typeof clone.items === "object") {
|
|
clone.items = Object.values(clone.items);
|
|
}
|
|
|
|
if (prefix) {
|
|
clone.id = `${prefix}_${clone.id}`;
|
|
clone.name = `${clone.name} ${prefix}`;
|
|
}
|
|
|
|
if (createInStripe === false) {
|
|
clone.create_in_stripe = false;
|
|
}
|
|
|
|
try {
|
|
await autumn.products.create(clone);
|
|
} catch (error: any) {
|
|
// If product already exists (race condition), silently continue
|
|
if (
|
|
error?.message?.includes("already exists") ||
|
|
error?.message?.includes("duplicate") ||
|
|
error?.code === "PRODUCT_EXISTS"
|
|
) {
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const createProducts = async ({
|
|
db,
|
|
orgId,
|
|
env,
|
|
autumn,
|
|
products,
|
|
prefix,
|
|
customerId,
|
|
createInStripe,
|
|
}: {
|
|
db: DrizzleCli;
|
|
orgId: string;
|
|
env: AppEnv;
|
|
autumn: AutumnInt;
|
|
products: any[];
|
|
prefix?: string;
|
|
customerId?: string;
|
|
createInStripe?: boolean;
|
|
}) => {
|
|
if (customerId) {
|
|
try {
|
|
await autumn.customers.delete(customerId);
|
|
} catch (error) {}
|
|
}
|
|
|
|
const batchCreate = [];
|
|
for (const product of products) {
|
|
batchCreate.push(
|
|
createProduct({ db, orgId, env, autumn, product, prefix, createInStripe }),
|
|
);
|
|
}
|
|
|
|
await Promise.all(batchCreate);
|
|
};
|
|
|
|
export const createReward = async ({
|
|
db,
|
|
orgId,
|
|
env,
|
|
autumn,
|
|
reward,
|
|
productId,
|
|
onlyUsage = false,
|
|
}: {
|
|
db: DrizzleCli;
|
|
orgId: string;
|
|
env: AppEnv;
|
|
autumn: AutumnInt;
|
|
reward: CreateReward;
|
|
productId?: string;
|
|
onlyUsage?: boolean;
|
|
}) => {
|
|
// Only fetch product if we need usage prices
|
|
if (onlyUsage && productId) {
|
|
const fullProduct = await ProductService.getFull({
|
|
db,
|
|
orgId,
|
|
env,
|
|
idOrInternalId: productId,
|
|
});
|
|
|
|
const usagePrices = fullProduct?.prices.filter((price) =>
|
|
isUsagePrice({ price }),
|
|
);
|
|
|
|
reward.discount_config!.price_ids = usagePrices?.map((price) => price.id);
|
|
} else if (productId) {
|
|
const fullProduct = await ProductService.getFull({
|
|
db,
|
|
orgId,
|
|
env,
|
|
idOrInternalId: productId,
|
|
});
|
|
|
|
reward.discount_config!.price_ids =
|
|
fullProduct?.prices.map((p) => p.id) ?? [];
|
|
}
|
|
|
|
try {
|
|
await autumn.rewards.delete(reward.id);
|
|
} catch (error) {}
|
|
|
|
await autumn.rewards.create(reward);
|
|
};
|
|
|
|
export const createReferralProgram = async ({
|
|
db,
|
|
orgId,
|
|
env,
|
|
autumn,
|
|
reward,
|
|
rewardProgram,
|
|
productId,
|
|
onlyUsage = false,
|
|
}: {
|
|
db: DrizzleCli;
|
|
orgId: string;
|
|
env: AppEnv;
|
|
autumn: AutumnInt;
|
|
reward: CreateReward;
|
|
rewardProgram: CreateRewardProgram;
|
|
productId?: string;
|
|
onlyUsage?: boolean;
|
|
}) => {
|
|
// Create reward first
|
|
await createReward({
|
|
db,
|
|
orgId,
|
|
env,
|
|
autumn,
|
|
reward,
|
|
productId,
|
|
onlyUsage,
|
|
});
|
|
|
|
// Create referral program (will fail if already exists, but that's ok)
|
|
try {
|
|
await autumn.rewardPrograms.create(rewardProgram);
|
|
} catch (error: any) {
|
|
// If program already exists (race condition), silently continue
|
|
if (
|
|
error?.message?.includes("already exists") ||
|
|
error?.message?.includes("duplicate") ||
|
|
error?.code === "REWARD_PROGRAM_EXISTS"
|
|
) {
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
};
|