238 lines
5.0 KiB
TypeScript
238 lines
5.0 KiB
TypeScript
import {
|
|
type AppEnv,
|
|
AttachBranch,
|
|
type AttachPreview,
|
|
CusProductStatus,
|
|
cusProductToPrices,
|
|
type FullCusProduct,
|
|
type FullCustomer,
|
|
type Organization,
|
|
type ProductV2,
|
|
} from "@autumn/shared";
|
|
import { expect } from "chai";
|
|
import { addHours } from "date-fns";
|
|
import type Stripe from "stripe";
|
|
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
|
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
|
import type { AutumnInt } from "@/external/autumn/autumnCli.js";
|
|
import { findStripePriceFromPrices } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
|
|
import { getStripeSchedules } from "@/external/stripe/stripeSubUtils.js";
|
|
import { CusService } from "@/internal/customers/CusService.js";
|
|
import { isV4Usage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
|
import { isFreeProductV2 } from "@/internal/products/productUtils/classifyProduct.js";
|
|
import { hoursToFinalizeInvoice } from "../constants.js";
|
|
import { advanceTestClock } from "../stripeUtils.js";
|
|
import { expectProductAttached } from "./expectProductAttached.js";
|
|
import { expectSubItemsCorrect } from "./expectSubUtils.js";
|
|
|
|
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,
|
|
isCanceled: true,
|
|
});
|
|
|
|
// const { fullCus } = await expectSubItemsCorrect({
|
|
// stripeCli,
|
|
// customerId,
|
|
// product: curProduct,
|
|
// db,
|
|
// org,
|
|
// env,
|
|
// subCanceled: isFreeProductV2({ product: newProduct }),
|
|
// 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,
|
|
status: CusProductStatus.Scheduled,
|
|
});
|
|
|
|
await expectSubToBeCorrect({
|
|
db,
|
|
customerId,
|
|
org,
|
|
env,
|
|
shouldBeCanceled: newProductIsFree,
|
|
});
|
|
|
|
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++;
|
|
} else {
|
|
expect(stripePrice).to.exist;
|
|
}
|
|
}
|
|
|
|
expect(
|
|
stripePrices.length,
|
|
"number of schedule items equivalent to number of autumn prices",
|
|
).to.equal(autumnPrices.length - missingUsageCount);
|
|
};
|