415 lines
10 KiB
TypeScript
415 lines
10 KiB
TypeScript
// import { config } from "dotenv";
|
|
|
|
// config();
|
|
|
|
// import assert from "node:assert";
|
|
// import {
|
|
// AppEnv,
|
|
// CusProductStatus,
|
|
// cusProductToPrices,
|
|
// type Entity,
|
|
// type FullCusProduct,
|
|
// type FullCustomer,
|
|
// type Organization,
|
|
// } from "@autumn/shared";
|
|
|
|
// import type Stripe from "stripe";
|
|
// import { initDrizzle } from "@/db/initDrizzle.js";
|
|
// import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
|
// import { getStripeSchedules } from "@/external/stripe/stripeSubUtils.js";
|
|
// import { createSupabaseClient } from "@/external/supabaseUtils.js";
|
|
// import { CusService } from "@/internal/customers/CusService.js";
|
|
// import { OrgService } from "@/internal/orgs/OrgService.js";
|
|
// import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
|
// import { notNullish } from "@/utils/genUtils.js";
|
|
// import {
|
|
// getAllEntities,
|
|
// getAllFullCustomers,
|
|
// } from "@/utils/scriptUtils/getAll/getAllAutumnCustomers.js";
|
|
// import {
|
|
// getAllStripeSchedules,
|
|
// getAllStripeSubscriptions,
|
|
// } from "@/utils/scriptUtils/getAll/getAllStripeSubs.js";
|
|
// import { EntityService } from "./internal/api/entities/EntityService.js";
|
|
// import { getRelatedCusPrice } from "./internal/customers/cusProducts/cusEnts/cusEntUtils.js";
|
|
// import { checkCusSubCorrect } from "./utils/checkUtils/checkCustomerCorrect.js";
|
|
|
|
// const { db } = initDrizzle({ maxConnections: 5 });
|
|
|
|
// let orgSlugs = process.env.ORG_SLUGS!.split(",");
|
|
// const skipEmails = process.env.SKIP_EMAILS!.split(",");
|
|
// const skipIds = [
|
|
// "cus_2tXCCwC6iyiftgA6ndSo1Ubb2dx",
|
|
// "DxG668K7uDd0Vahk54YWjvCGVgf2",
|
|
// ];
|
|
|
|
// orgSlugs = ["athenahq"];
|
|
// // let customerId = null;
|
|
// const customerId = null;
|
|
|
|
// const getSingleCustomer = async ({
|
|
// stripeCli,
|
|
// customerId,
|
|
// orgId,
|
|
// env,
|
|
// }: {
|
|
// stripeCli: Stripe;
|
|
// customerId: string;
|
|
// orgId: string;
|
|
// env: AppEnv;
|
|
// }) => {
|
|
// const customers = [
|
|
// await CusService.getFull({
|
|
// db,
|
|
// idOrInternalId: customerId,
|
|
// orgId,
|
|
// env,
|
|
// }),
|
|
// ];
|
|
|
|
// const stripeCusId = customers[0].processor?.id;
|
|
// const stripeSubs = stripeCusId
|
|
// ? (
|
|
// await stripeCli.subscriptions.list({
|
|
// customer: stripeCusId,
|
|
// expand: ["data.discounts.coupon"],
|
|
// })
|
|
// ).data
|
|
// : [];
|
|
|
|
// // const stripeSubs = await getStripeSubs({
|
|
// // stripeCli,
|
|
// // subIds: customers[0].customer_products.flatMap(
|
|
// // (cp) => cp.subscription_ids || []
|
|
// // ),
|
|
// // });
|
|
|
|
// let scheduleIds = customers[0].customer_products.flatMap(
|
|
// (cp) => cp.scheduled_ids || [],
|
|
// );
|
|
|
|
// scheduleIds = Array.from(new Set(scheduleIds));
|
|
|
|
// const stripeSchedules = await getStripeSchedules({
|
|
// stripeCli,
|
|
// scheduleIds,
|
|
// });
|
|
|
|
// const entities = await EntityService.list({
|
|
// db,
|
|
// internalCustomerId: customers[0].internal_id,
|
|
// });
|
|
|
|
// return { customers, stripeSubs, stripeSchedules, entities };
|
|
// };
|
|
|
|
// const checkCustomerCorrect = async ({
|
|
// fullCus,
|
|
// subs,
|
|
// schedules,
|
|
// org,
|
|
// entities,
|
|
// }: {
|
|
// fullCus: FullCustomer;
|
|
// subs: Stripe.Subscription[];
|
|
// schedules: Stripe.SubscriptionSchedule[];
|
|
// org: Organization;
|
|
// entities: Entity[];
|
|
// }) => {
|
|
// if (skipIds.includes(fullCus.internal_id!)) return;
|
|
|
|
// if (skipEmails.some((skipEmail) => skipEmail === fullCus.email)) {
|
|
// return;
|
|
// }
|
|
|
|
// fullCus.entities = entities.filter(
|
|
// (entity) => entity.internal_customer_id === fullCus.internal_id,
|
|
// );
|
|
|
|
// // console.log(`Checking ${fullCus.email} (${fullCus.id})`);
|
|
// const cusProducts = fullCus.customer_products;
|
|
|
|
// await checkCusSubCorrect({
|
|
// db,
|
|
// fullCus,
|
|
// subs,
|
|
// schedules,
|
|
// org,
|
|
// env: AppEnv.Live,
|
|
// });
|
|
|
|
// for (const cusProduct of cusProducts) {
|
|
// if (!cusProduct.subscription_ids) continue;
|
|
|
|
// if (cusProduct.status === CusProductStatus.Scheduled) {
|
|
// // Check if there's a main product elsewhere
|
|
// const mainCusProd = cusProducts.find(
|
|
// (cp: FullCusProduct) =>
|
|
// cp.product.group === cusProduct.product.group &&
|
|
// cp.id !== cusProduct.id &&
|
|
// cp.status !== CusProductStatus.Scheduled &&
|
|
// (cusProduct.internal_entity_id
|
|
// ? cusProduct.internal_entity_id === cp.internal_entity_id
|
|
// : true),
|
|
// );
|
|
|
|
// assert(
|
|
// mainCusProd,
|
|
// `Found scheduled cus product with no main product (${cusProduct.product.name})`,
|
|
// );
|
|
// }
|
|
|
|
// if (
|
|
// !cusProduct.product.is_add_on &&
|
|
// cusProduct.status !== CusProductStatus.Scheduled
|
|
// ) {
|
|
// const group = cusProduct.product.group;
|
|
// const otherCusProd = cusProducts.find(
|
|
// (cp: FullCusProduct) =>
|
|
// cp.product.group === group &&
|
|
// cp.id !== cusProduct.id &&
|
|
// !cp.product.is_add_on &&
|
|
// cp.status !== CusProductStatus.Scheduled &&
|
|
// cp.internal_entity_id === cusProduct.internal_entity_id,
|
|
// );
|
|
|
|
// assert(
|
|
// !otherCusProd,
|
|
// `found two cus products from the same group: ${otherCusProd?.product.name} and ${cusProduct.product.name}`,
|
|
// );
|
|
// }
|
|
|
|
// const stripeSubs = subs.filter((sub: any) =>
|
|
// cusProduct.subscription_ids!.some((id: string) => id === sub.id),
|
|
// );
|
|
|
|
// assert(
|
|
// stripeSubs.length === cusProduct.subscription_ids!.length,
|
|
// "number of stripe subs should be the same as number of subscription ids",
|
|
// );
|
|
|
|
// // let subItems = stripeSubs.flatMap((sub: any) => sub.items.data);
|
|
|
|
// const prices = cusProductToPrices({ cusProduct });
|
|
|
|
// if (
|
|
// isOneOff(prices) ||
|
|
// isFreeProduct(prices) ||
|
|
// cusProduct.status === CusProductStatus.Scheduled
|
|
// ) {
|
|
// continue;
|
|
// }
|
|
|
|
// for (const cusEnt of cusProduct.customer_entitlements) {
|
|
// const cusPrice = getRelatedCusPrice(cusEnt, cusProduct.customer_prices);
|
|
|
|
// if (cusEnt.usage_allowed && !cusPrice) {
|
|
// assert.fail(
|
|
// `Feature ${cusEnt.feature_id} has usage allowed but no related cus price`,
|
|
// );
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // Other checks to perform
|
|
// };
|
|
|
|
// const checkCustomerHandleError = async ({
|
|
// fullCus,
|
|
// subs,
|
|
// org,
|
|
// schedules,
|
|
// entities,
|
|
// }: {
|
|
// fullCus: FullCustomer;
|
|
// subs: Stripe.Subscription[];
|
|
// org: Organization;
|
|
// schedules: Stripe.SubscriptionSchedule[];
|
|
// entities: Entity[];
|
|
// }) => {
|
|
// try {
|
|
// await checkCustomerCorrect({
|
|
// fullCus,
|
|
// subs,
|
|
// org,
|
|
// schedules,
|
|
// entities,
|
|
// });
|
|
|
|
// return undefined;
|
|
// } catch (error: any) {
|
|
// return {
|
|
// id: fullCus.id,
|
|
// name: fullCus.name,
|
|
// email: fullCus.email,
|
|
// error: error.message,
|
|
// };
|
|
// }
|
|
// };
|
|
|
|
// export const check = async () => {
|
|
// const env = AppEnv.Live;
|
|
// const sb = createSupabaseClient();
|
|
|
|
// const today = new Date().toISOString().slice(0, 16);
|
|
|
|
// for (const slug of orgSlugs) {
|
|
// const org = await OrgService.getBySlug({
|
|
// db,
|
|
// slug,
|
|
// });
|
|
|
|
// if (!org) {
|
|
// console.log(`Org ${slug} not found`);
|
|
// continue;
|
|
// }
|
|
|
|
// const fileName = `errors/${today}-${org.slug}.json`;
|
|
|
|
// const stripeCli = createStripeCli({
|
|
// org,
|
|
// env,
|
|
// });
|
|
|
|
// console.log("--------------------------------");
|
|
// console.log(`Running error check for ${org.name}`);
|
|
|
|
// let customers: FullCustomer[] = [];
|
|
// let stripeSubs: Stripe.Subscription[] = [];
|
|
// let stripeSchedules: Stripe.SubscriptionSchedule[] = [];
|
|
// let entities: Entity[] = [];
|
|
|
|
// if (customerId) {
|
|
// const res = await getSingleCustomer({
|
|
// stripeCli,
|
|
// customerId,
|
|
// orgId: org.id,
|
|
// env,
|
|
// });
|
|
|
|
// customers = res.customers;
|
|
// stripeSubs = res.stripeSubs;
|
|
// entities = res.entities;
|
|
// } else {
|
|
// const [customersRes, stripeSubsRes, stripeSchedulesRes, entitiesRes] =
|
|
// await Promise.all([
|
|
// getAllFullCustomers({
|
|
// db,
|
|
// orgId: org.id,
|
|
// env,
|
|
// }),
|
|
// getAllStripeSubscriptions({
|
|
// stripeCli,
|
|
// waitForSeconds: 1,
|
|
// }),
|
|
// getAllStripeSchedules({
|
|
// stripeCli,
|
|
// waitForSeconds: 1,
|
|
// }),
|
|
// getAllEntities({
|
|
// db,
|
|
// orgId: org.id,
|
|
// env,
|
|
// }),
|
|
// ]);
|
|
|
|
// customers = customersRes;
|
|
// stripeSubs = stripeSubsRes.subscriptions;
|
|
// stripeSchedules = stripeSchedulesRes.schedules;
|
|
// entities = entitiesRes;
|
|
// }
|
|
|
|
// const batchSize = 1;
|
|
// const allErrors = [];
|
|
// for (let i = 0; i < customers.length; i += batchSize) {
|
|
// const batch = customers.slice(i, i + batchSize);
|
|
|
|
// const batchCheck: any = [];
|
|
// for (const customer of batch) {
|
|
// batchCheck.push(
|
|
// checkCustomerHandleError({
|
|
// fullCus: customer,
|
|
// subs: stripeSubs,
|
|
// schedules: stripeSchedules,
|
|
// org,
|
|
// entities,
|
|
// }),
|
|
// );
|
|
// }
|
|
|
|
// let results = await Promise.all(batchCheck);
|
|
// results = results.filter(notNullish);
|
|
// allErrors.push(...results);
|
|
// }
|
|
|
|
// console.log(`Found ${allErrors.length} errors`);
|
|
|
|
// if (allErrors.length > 0 && customers.length > 1) {
|
|
// await sb.storage
|
|
// .from("autumn")
|
|
// .upload(fileName, JSON.stringify(allErrors, null, 2));
|
|
|
|
// if (allErrors.length > 0) {
|
|
// const slackBody = {
|
|
// text: `Error check for ${org.name}`,
|
|
// blocks: [
|
|
// {
|
|
// type: "section",
|
|
// text: {
|
|
// type: "mrkdwn",
|
|
// text: `*Error check for ${org.name}*: found ${allErrors.length} errors\nSee results at ${process.env.SUPABASE_URL}/storage/v1/object/public/autumn/${fileName}`,
|
|
// },
|
|
// },
|
|
// ],
|
|
// };
|
|
|
|
// await fetch(process.env.SLACK_WEBHOOK_URL!, {
|
|
// method: "POST",
|
|
// body: JSON.stringify(slackBody),
|
|
// });
|
|
// }
|
|
// } else {
|
|
// console.log(allErrors);
|
|
// }
|
|
// }
|
|
|
|
// console.log(
|
|
// `COMPLETED ERROR CHECK FOR ${new Date().toISOString().slice(0, 16)}`,
|
|
// );
|
|
|
|
// if (process.env.NODE_ENV === "production") {
|
|
// const slackBody = {
|
|
// text: `Completed error check for ${new Date().toISOString().slice(0, 16)}`,
|
|
// blocks: [
|
|
// {
|
|
// type: "section",
|
|
// text: {
|
|
// type: "mrkdwn",
|
|
// text: `Error check completed for ${new Date().toISOString().slice(0, 16)}`,
|
|
// },
|
|
// },
|
|
// ],
|
|
// };
|
|
|
|
// await fetch(process.env.SLACK_WEBHOOK_URL!, {
|
|
// method: "POST",
|
|
// body: JSON.stringify(slackBody),
|
|
// });
|
|
// }
|
|
// };
|
|
|
|
// check()
|
|
// .catch((error) => {
|
|
// console.error(error);
|
|
// process.exit(1);
|
|
// })
|
|
// .finally(() => {
|
|
// process.exit(0);
|
|
// });
|
|
|
|
import { initInfisical } from "./external/infisical/initInfisical.js";
|
|
|
|
await initInfisical();
|
|
|
|
await import("./scan/runScan.js");
|