import "dotenv/config"; import { BillingInterval, type Customer, type FullProduct, } from "@autumn/shared"; import { addDays, addHours, addMinutes, addMonths, addWeeks, format, } from "date-fns"; import type { Stripe } from "stripe"; import { timeout } from "./genUtils.js"; const STRIPE_TEST_CLOCK_TIMING = 20000; // 30s export const deleteAllStripeProducts = async ({ stripeCli, }: { stripeCli: Stripe; }) => { const stripeProds = await stripeCli.products.list({ limit: 100, }); const batchSize = 10; for (let i = 0; i < stripeProds.data.length; i += batchSize) { const batch = stripeProds.data.slice(i, i + batchSize); await Promise.all( batch.map(async (prod) => { console.log("Deleting stripe product", prod.id); try { await stripeCli.products.del(prod.id); } catch (_error) { await stripeCli.products.update(prod.id, { active: false, }); } }), ); console.log("Deleted", i, "of", stripeProds.data.length); } }; export const deleteAllStripeTestClocks = async ({ stripeCli, }: { stripeCli: Stripe; }) => { const stripeTestClocks = await stripeCli.testHelpers.testClocks.list({ limit: 100, }); const batchSize = 10; for (let i = 0; i < stripeTestClocks.data.length; i += batchSize) { const batch = stripeTestClocks.data.slice(i, i + batchSize); await Promise.all( batch.map(async (clock) => stripeCli.testHelpers.testClocks.del(clock.id), ), ); } }; export const deleteStripeProduct = async ({ stripeCli, product, }: { stripeCli: Stripe; product: FullProduct; }) => { try { await stripeCli.products.retrieve(product.processor!.id); } catch (_error) { return; } for (const price of product.prices!) { const config = price.config as any; if (config.stripe_price_id) { const stripePrice = await stripeCli.prices.retrieve( config.stripe_price_id, ); await stripeCli.prices.update(config.stripe_price_id, { active: false, }); // Delete default product try { await stripeCli.products.del(stripePrice.product as string); } catch (_error) { await stripeCli.products.update(stripePrice.product as string, { active: false, }); } } if (config.stripe_meter_id) { await stripeCli.billing.meters.deactivate(config.stripe_meter_id); } } if (product.processor) { // console.log("Stripe product", stripeProd.active, stripeProd.id); const stripeProdId = product.processor.id; try { await stripeCli.products.del(stripeProdId); } catch (_error) { await stripeCli.products.update(stripeProdId, { active: false, }); } } }; export const checkMeteredEventSummary = async ({ stripeCli, meterId, customerId, }: { stripeCli: Stripe; meterId: string; customerId: string; }) => { const summaries = await stripeCli.billing.meters.listEventSummaries(meterId, { customer: customerId, start_time: Math.floor(Date.now() / 1000), end_time: Math.floor(addMonths(new Date(), 1).getTime() / 1000), }); return summaries; }; export const advanceTestClock = async ({ stripeCli, testClockId, startingFrom, numberOfDays, numberOfWeeks, numberOfHours, numberOfMonths, advanceTo, waitForSeconds, }: { stripeCli: Stripe; testClockId: string; numberOfDays?: number; startingFrom?: Date; numberOfWeeks?: number; numberOfHours?: number; numberOfMonths?: number; advanceTo?: number; waitForSeconds?: number; }) => { if (!startingFrom) { startingFrom = new Date(); } // Stack all time units - they accumulate from startingFrom let targetDate = startingFrom; if (numberOfMonths) { targetDate = addMonths(targetDate, numberOfMonths); } if (numberOfWeeks) { targetDate = addWeeks(targetDate, numberOfWeeks); } if (numberOfDays) { targetDate = addDays(targetDate, numberOfDays); } if (numberOfHours) { targetDate = addHours(targetDate, numberOfHours); } // Only use calculated targetDate if we actually had time params if (numberOfMonths || numberOfWeeks || numberOfDays || numberOfHours) { advanceTo = targetDate.getTime(); } if (!advanceTo) { advanceTo = addMinutes(addMonths(startingFrom, 1), 10).getTime(); } console.log(" - Advancing to: ", format(advanceTo, "dd MMM yyyy HH:mm:ss")); await stripeCli.testHelpers.testClocks.advance(testClockId, { frozen_time: Math.floor(advanceTo / 1000), }); await timeout( waitForSeconds ? waitForSeconds * 1000 : STRIPE_TEST_CLOCK_TIMING, ); return advanceTo; }; export const waitForMeterUpdate = async () => { const timeoutSeconds = 160; for (let i = 0; i < timeoutSeconds; i += 10) { console.log(` - ${i} / ${timeoutSeconds}`); await timeout(10000); } }; export const advanceClockForInvoice = async ({ stripeCli, testClockId, waitForMeterUpdate = false, numberOfDays, startingFrom, }: { stripeCli: Stripe; testClockId: string; waitForMeterUpdate?: boolean; numberOfDays?: number; startingFrom?: Date; }) => { let advanceTo: number; if (!startingFrom) { startingFrom = new Date(); } if (numberOfDays) { advanceTo = addDays(startingFrom, numberOfDays).getTime(); } else { advanceTo = addMonths(startingFrom, 1).getTime(); } // advanceTo = subHours(addMonths(Date.now(), 1), 1).getTime(); await stripeCli.testHelpers.testClocks.advance(testClockId, { frozen_time: Math.ceil(advanceTo / 1000), }); console.log( " - advanceClockForInvoice (1): ", format(advanceTo, "dd MMM yyyy HH:mm:ss"), ); if (waitForMeterUpdate) { const timeoutSeconds = 200; for (let i = 0; i < timeoutSeconds; i += 10) { console.log(` - ${i} / ${timeoutSeconds}`); await timeout(10000); } } else { await timeout(STRIPE_TEST_CLOCK_TIMING); } // const advanceTo2 = addHours(new Date(advanceTo), 30).getTime(); const advanceTo2 = addDays(new Date(advanceTo), 4).getTime(); await stripeCli.testHelpers.testClocks.advance(testClockId, { frozen_time: Math.floor(advanceTo2 / 1000), }); console.log( " - advanceClockForInvoice (2): ", format(advanceTo2, "dd MMM yyyy HH:mm:ss"), ); await timeout(STRIPE_TEST_CLOCK_TIMING); return advanceTo2; }; export const advanceMonths = async ({ stripeCli, testClockId, numberOfMonths, }: { stripeCli: Stripe; testClockId: string; numberOfMonths: number; }) => { let advanceTo = new Date(); for (let i = 0; i < numberOfMonths; i += 1) { advanceTo = addMonths(advanceTo, 1); console.log( " - Advancing to: ", format(advanceTo, "dd MMM yyyy HH:mm:ss"), ); try { await stripeCli.testHelpers.testClocks.advance(testClockId, { frozen_time: Math.floor(advanceTo.getTime() / 1000), }); } catch (error: any) { console.log(" - Advance clock: ", error.message); await timeout(10000); await stripeCli.testHelpers.testClocks.advance(testClockId, { frozen_time: Math.floor(advanceTo.getTime() / 1000), }); } await timeout(15000); } }; // Check billing meter event summary export const checkBillingMeterEventSummary = async ({ stripeCli, startTime, stripeMeterId, stripeCustomerId, }: { stripeCli: Stripe; startTime: Date; stripeMeterId: string; stripeCustomerId: string; }) => { const endTime = addMonths(startTime, 1); const event = await stripeCli.billing.meters.listEventSummaries( stripeMeterId, { limit: 100, start_time: Math.round(startTime.getTime() / 1000), end_time: Math.round(endTime.getTime() / 1000), customer: stripeCustomerId, }, ); if (event.data.length === 0) { return null; } else { return event.data[0]; } }; export const getDiscount = async ({ stripeCli, customer, stripeId, }: { stripeCli: Stripe; customer?: Customer; stripeId?: string; }): Promise< (Stripe.Discount & { source: { coupon: Stripe.Coupon } }) | null > => { const stripeCustomer: any = await stripeCli.customers.retrieve( stripeId || customer!.processor!.id, { expand: ["discount.source.coupon"], }, ); return stripeCustomer.discount; }; export const stripeToAutumnInterval = ({ interval, intervalCount, }: { interval: string; intervalCount: number; }) => { if (interval === "month" && intervalCount === 1) { return BillingInterval.Month; } if (interval === "month" && intervalCount === 3) { return BillingInterval.Quarter; } if (interval === "month" && intervalCount === 6) { return BillingInterval.SemiAnnual; } if ( (interval === "month" && intervalCount === 12) || (interval === "year" && intervalCount === 1) ) { return BillingInterval.Year; } }; export const subItemToAutumnInterval = (item: Stripe.SubscriptionItem) => { return { interval: item.price.recurring?.interval as BillingInterval, intervalCount: item.price.recurring?.interval_count || 1, }; // return stripeToAutumnInterval({ // interval: item.price.recurring?.interval!, // intervalCount: item.price.recurring?.interval_count!, // }); };