Files
cfw-autumn/server/tests/utils/expectUtils/expectScheduleUtils.ts
2025-06-22 08:00:36 +01:00

229 lines
4.9 KiB
TypeScript

import { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { getStripeSchedules } from "@/external/stripe/stripeSubUtils.js";
import { findStripePriceFromPrices } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
import { CusService } from "@/internal/customers/CusService.js";
import { isV4Usage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
import {
AppEnv,
AttachBranch,
AttachPreview,
FullCusProduct,
FullCustomer,
Organization,
ProductV2,
} from "@autumn/shared";
import { expect } from "chai";
import Stripe from "stripe";
import { expectSubItemsCorrect } from "./expectSubUtils.js";
import { expectProductAttached } from "./expectProductAttached.js";
import { isFreeProductV2 } from "@/internal/products/productUtils/classifyProduct.js";
import { advanceTestClock } from "../stripeUtils.js";
import { hoursToFinalizeInvoice } from "../constants.js";
import { addHours } from "date-fns";
export const expectNextCycleCorrect = async ({
autumn,
preview,
stripeCli,
customerId,
testClockId,
product,
db,
org,
env,
advanceClock = true,
}: {
autumn: AutumnInt;
preview: AttachPreview;
stripeCli: Stripe;
customerId: string;
testClockId: string;
product: ProductV2;
db: DrizzleCli;
org: Organization;
env: AppEnv;
advanceClock?: boolean;
}) => {
if (advanceClock) {
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
preview!.due_next_cycle.due_at,
hoursToFinalizeInvoice,
).getTime(),
});
}
const customer = await autumn.customers.get(customerId);
expectProductAttached({
customer,
product,
});
await expectSubItemsCorrect({
stripeCli,
customerId,
product,
db,
org,
env,
});
};
export const expectDowngradeCorrect = async ({
customerId,
curProduct,
newProduct,
autumn,
stripeCli,
db,
org,
env,
}: {
customerId: string;
curProduct: ProductV2;
newProduct: ProductV2;
autumn: AutumnInt;
stripeCli: Stripe;
db: DrizzleCli;
org: Organization;
env: AppEnv;
}) => {
const preview = await autumn.attachPreview({
customer_id: customerId,
product_id: newProduct.id,
});
await autumn.attach({
customer_id: customerId,
product_id: newProduct.id,
});
const customer = await autumn.customers.get(customerId);
const productCount = customer.products.reduce((acc: number, product: any) => {
if (product.group == curProduct.group) {
return acc + 1;
} else return acc;
}, 0);
expect(
productCount,
"customer should only have 2 products (from this group)",
).to.equal(2);
expectProductAttached({
customer,
product: curProduct,
});
const { fullCus } = await expectSubItemsCorrect({
stripeCli,
customerId,
product: curProduct,
db,
org,
env,
isCanceled: true,
});
const newProductIsFree = isFreeProductV2({ product: newProduct });
if (newProductIsFree) {
let res = await stripeCli.subscriptionSchedules.list({
customer: fullCus.processor?.id,
});
let data = res.data.filter((s) => s.status != "canceled");
expect(data.length, "should have no sub schedules").to.equal(0);
await expectSubScheduleCorrect({
stripeCli,
customerId,
productId: newProduct.id,
db,
org,
env,
});
expectProductAttached({
customer,
product: newProduct,
});
}
expect(preview.branch).to.equal(AttachBranch.Downgrade);
return {
preview,
};
};
export const expectSubScheduleCorrect = async ({
stripeCli,
customerId,
productId,
db,
org,
env,
fullCus,
}: {
stripeCli: Stripe;
customerId: string;
productId: string;
db: DrizzleCli;
org: Organization;
env: AppEnv;
fullCus?: FullCustomer;
}) => {
// 1. Check schedule
if (!fullCus) {
fullCus = await CusService.getFull({
db,
idOrInternalId: customerId,
orgId: org.id,
env,
});
}
const cusProduct = fullCus.customer_products.find(
(cp: FullCusProduct) => cp.product.id == productId,
)!;
const scheduleSets = await getStripeSchedules({
stripeCli,
scheduleIds: cusProduct?.scheduled_ids || [],
});
const stripePrices = scheduleSets.flatMap(
(schedule) => schedule?.prices || [],
);
const autumnPrices = cusProductToPrices({ cusProduct });
let missingUsageCount = 0;
for (const autumnPrice of autumnPrices) {
const stripePrice = findStripePriceFromPrices({
stripePrices,
autumnPrice,
});
if (isV4Usage({ price: autumnPrice!, cusProduct })) {
missingUsageCount++;
continue;
} else {
expect(stripePrice).to.exist;
}
}
expect(
stripePrices.length,
"number of schedule items equivalent to number of autumn prices",
).to.equal(autumnPrices.length - missingUsageCount);
};