239 lines
5.6 KiB
TypeScript
239 lines
5.6 KiB
TypeScript
import { CusService } from "@/internal/customers/CusService.js";
|
|
import { ProductService } from "@/internal/products/ProductService.js";
|
|
import { isDefaultTrialFullProduct } from "@/internal/products/productUtils/classifyProduct.js";
|
|
import { AppEnv, ProcessorType } from "@autumn/shared";
|
|
import { generateId } from "@/utils/genUtils.js";
|
|
import { newCusToAttachParams } from "@/internal/customers/attach/attachUtils/attachParams/convertToParams.js";
|
|
import { handleAddProduct } from "@/internal/customers/attach/attachFunctions/addProductFlow/handleAddProduct.js";
|
|
import Stripe from "stripe";
|
|
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
|
import { DrizzleCli } from "@/db/initDrizzle.js";
|
|
import { attachPmToCus } from "@/external/stripe/stripeCusUtils.js";
|
|
|
|
export async function manuallyAttachDefaultTrial({
|
|
customerId,
|
|
stripeCli,
|
|
autumn,
|
|
db,
|
|
org,
|
|
env,
|
|
testClockID,
|
|
autumnJs,
|
|
attachPm = "",
|
|
group,
|
|
}: {
|
|
customerId: string;
|
|
stripeCli: Stripe;
|
|
autumn: AutumnInt;
|
|
db: DrizzleCli;
|
|
org: any;
|
|
env: AppEnv;
|
|
testClockID?: string;
|
|
autumnJs: any;
|
|
attachPm?: "success" | "fail" | "";
|
|
group?: string;
|
|
}) {
|
|
try {
|
|
const existingCustomer = await CusService.get({
|
|
db,
|
|
idOrInternalId: customerId,
|
|
orgId: org.id,
|
|
env,
|
|
});
|
|
if (existingCustomer) {
|
|
// Delete via API to clean up properly
|
|
await autumnJs.customers.delete(customerId);
|
|
}
|
|
} catch (error) {
|
|
// Ignore if customer doesn't exist
|
|
console.log("Customer doesn't exist, skipping delete", error);
|
|
}
|
|
|
|
// Step 2: Manually create customer in DB (following createNewCustomer.ts logic)
|
|
const customerData = {
|
|
id: customerId,
|
|
name: customerId,
|
|
email: `${customerId}@example.com`,
|
|
metadata: {},
|
|
internal_id: generateId("cus"),
|
|
org_id: org.id,
|
|
created_at: Date.now(),
|
|
env,
|
|
};
|
|
|
|
const newCustomer = await CusService.insert({
|
|
db,
|
|
data: customerData,
|
|
});
|
|
|
|
if (!newCustomer) {
|
|
throw new Error("Failed to create customer");
|
|
}
|
|
|
|
// Step 3: Get default products (following createNewCustomer.ts logic)
|
|
const allDefaultProds = await ProductService.listDefault({
|
|
db,
|
|
orgId: org.id,
|
|
env,
|
|
});
|
|
|
|
// Filter by group if specified
|
|
const defaultProds = group
|
|
? allDefaultProds.filter((p) => p.group === group)
|
|
: allDefaultProds;
|
|
|
|
const defaultPaidTrialProd = defaultProds.find((p) =>
|
|
isDefaultTrialFullProduct({ product: p }),
|
|
);
|
|
|
|
let customer = newCustomer;
|
|
|
|
if (defaultPaidTrialProd) {
|
|
// Step 4: Create Stripe customer with test clock
|
|
const stripeCustomer = await stripeCli.customers.create({
|
|
email: `${customerId}@example.com`,
|
|
test_clock: testClockID ? testClockID : undefined,
|
|
});
|
|
|
|
// Step 5: Update customer with Stripe processor info (BEFORE attachPmToCus)
|
|
await CusService.update({
|
|
db,
|
|
internalCusId: newCustomer.internal_id,
|
|
update: {
|
|
processor: {
|
|
type: ProcessorType.Stripe,
|
|
id: stripeCustomer.id,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Update local customer object
|
|
customer = {
|
|
...newCustomer,
|
|
processor: {
|
|
id: stripeCustomer.id,
|
|
type: "stripe",
|
|
},
|
|
} as any;
|
|
|
|
if (attachPm && testClockID) {
|
|
await attachPmToCus({
|
|
customer: customer,
|
|
org: org,
|
|
env: env,
|
|
db: db,
|
|
testClockId: testClockID,
|
|
willFail: attachPm === "fail",
|
|
});
|
|
}
|
|
|
|
// Step 6: Manually attach the default trial product (following createNewCustomer.ts logic)
|
|
const req = {
|
|
db,
|
|
org,
|
|
env,
|
|
orgId: org.id,
|
|
logtail: console,
|
|
logger: console,
|
|
} as any;
|
|
|
|
await handleAddProduct({
|
|
req,
|
|
attachParams: newCusToAttachParams({
|
|
req,
|
|
newCus: customer as any,
|
|
products: [defaultPaidTrialProd],
|
|
stripeCli,
|
|
freeTrial: defaultPaidTrialProd.free_trial || null,
|
|
}),
|
|
});
|
|
|
|
return customer;
|
|
}
|
|
}
|
|
|
|
export async function cleanupQueueAndCache() {
|
|
try {
|
|
const { QueueManager } = await import("@/queue/QueueManager.js");
|
|
const queueInstance = await QueueManager.getInstance();
|
|
|
|
// Access private properties to close connections
|
|
if ((queueInstance as any).queue) {
|
|
await (queueInstance as any).queue.close();
|
|
}
|
|
if ((queueInstance as any).backupQueue) {
|
|
await (queueInstance as any).backupQueue.close();
|
|
}
|
|
if ((queueInstance as any).mainConnection) {
|
|
await (queueInstance as any).mainConnection.quit();
|
|
}
|
|
if ((queueInstance as any).backupConnection) {
|
|
await (queueInstance as any).backupConnection.quit();
|
|
}
|
|
} catch (error) {
|
|
// Ignore cleanup errors
|
|
}
|
|
|
|
try {
|
|
const { CacheManager } = await import("@/external/caching/CacheManager.js");
|
|
const cacheInstance = await CacheManager.getInstance();
|
|
if ((cacheInstance as any).connection) {
|
|
await (cacheInstance as any).connection.quit();
|
|
}
|
|
} catch (error) {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
|
|
export async function flipDefaultState({
|
|
id,
|
|
autumn,
|
|
state,
|
|
}: {
|
|
id: string;
|
|
autumn: AutumnInt;
|
|
state: boolean;
|
|
}) {
|
|
try {
|
|
let productExists = await autumn.products.get(id);
|
|
if (productExists) {
|
|
await autumn.products.update(id, {
|
|
is_default: state,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.log(`Product ${id} doesn't exist, skipping update`);
|
|
}
|
|
}
|
|
|
|
export async function flipDefaultStates({
|
|
currentCase,
|
|
autumn,
|
|
}: {
|
|
currentCase: number;
|
|
autumn: AutumnInt;
|
|
}) {
|
|
let total = 4;
|
|
|
|
// Now flip all products from 0 to total-1, only current case should be true
|
|
for (let i = 0; i < total; i++) {
|
|
const id = `defaultTrial${i}_pro`;
|
|
const state = i === currentCase; // Only the current case is true
|
|
await flipDefaultState({
|
|
id,
|
|
autumn,
|
|
state,
|
|
});
|
|
}
|
|
|
|
for (let i = 0; i < total; i++) {
|
|
const id = `defaultTrial${i}_free`;
|
|
const state = i === currentCase; // Only the current case is true
|
|
await flipDefaultState({
|
|
id,
|
|
autumn,
|
|
state,
|
|
});
|
|
}
|
|
}
|