Files
cfw-autumn/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts
2025-09-02 23:44:24 -07:00

159 lines
4.1 KiB
TypeScript

import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
import {
BillingInterval,
BillingType,
FullCusProduct,
FullProduct,
intervalsDifferent,
intervalsSame,
} from "@autumn/shared";
import Stripe from "stripe";
import { getContUseInvoiceItems } from "./getContUseInvoiceItems.js";
import { findPriceInStripeItems } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import { cusProductToPrices } from "@autumn/shared";
import { attachParamsToProduct } from "../convertAttachParams.js";
import { subToAutumnInterval } from "@/external/stripe/utils.js";
import { intervalsAreSame } from "../getAttachConfig.js";
import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
export const filterContUsageProrations = async ({
sub,
stripeCli,
curCusProduct,
newProduct,
logger,
}: {
sub: Stripe.Subscription;
stripeCli: Stripe;
curCusProduct: FullCusProduct;
newProduct: FullProduct;
logger: any;
}) => {
const curPrices = cusProductToPrices({
cusProduct: curCusProduct,
});
let allPrices = [...curPrices, ...newProduct.prices];
// const upcomingLines = await stripeCli.invoices.listUpcomingLines({
// subscription: sub.id,
// });
const pendingItems = await stripeCli.invoiceItems.list({
pending: true,
customer: sub.customer as string,
});
const intervalSet = subToAutumnInterval(sub);
for (const item of pendingItems.data) {
// console.log("LINE ITEM:", item);
if (!item.proration) continue;
let price = findPriceInStripeItems({
prices: allPrices,
lineItem: item,
billingType: BillingType.InArrearProrated,
});
if (!price) continue;
logger.info(
`Deleting ii: ${item.description} - ${item.amount / 100} (${intervalSet.interval}, ${intervalSet.intervalCount})`
);
await stripeCli.invoiceItems.del(
item.id
// @ts-ignore -- Stripe types are not correct
// item.parent.subscription_item_details.invoice_item
);
}
};
export const createAndFilterContUseItems = async ({
attachParams,
curMainProduct,
// stripeSubs,
sub,
logger,
interval,
intervalCount,
}: {
attachParams: AttachParams;
curMainProduct: FullCusProduct;
// stripeSubs: Stripe.Subscription[];
sub: Stripe.Subscription;
logger: any;
interval?: BillingInterval;
intervalCount?: number;
}) => {
const { stripeCli, customer, org } = attachParams;
const product = attachParamsToProduct({ attachParams });
// const sameIntervals = intervalsAreSame({ attachParams });
const now = attachParams.now || Date.now();
// if (!sameIntervals) {
// return { newItems: [], oldItems: [], replaceables: [] };
// }
let { newItems, oldItems, replaceables } = await getContUseInvoiceItems({
attachParams,
cusProduct: curMainProduct!,
sub,
logger,
});
await filterContUsageProrations({
sub,
stripeCli,
curCusProduct: curMainProduct,
newProduct: product,
logger,
});
const items = [...oldItems, ...newItems];
const curPrices = cusProductToPrices({
cusProduct: curMainProduct,
});
for (const item of items) {
if (!item.amount || item.amount === 0) {
continue;
}
let price =
product.prices.find((p) => p.id === item.price_id) ||
curPrices.find((p) => p.id === item.price_id);
if (
interval &&
price?.config &&
intervalsDifferent({
// price?.config.interval !== interval
intervalA: price?.config,
intervalB: { interval, intervalCount },
})
) {
continue;
}
logger.info(
`Adding invoice item: ${item.description}, ${item.description}, interval: ${interval}`
);
const { start, end } = subToPeriodStartEnd({ sub });
await stripeCli.invoiceItems.create({
customer: customer.processor?.id!,
amount: Math.round(item.amount * 100),
description: item.description,
currency: org.default_currency || "usd",
subscription: sub.id,
period: {
start: Math.floor(now / 1000),
end: end,
},
});
}
return { newItems, oldItems, replaceables };
};