fix: free + trial, is now not activated after higher tier cancels
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
valkey:
|
||||
image: docker.io/bitnami/valkey:8.0
|
||||
image: docker.io/valkey/valkey:8.0
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- VALKEY_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
services:
|
||||
valkey:
|
||||
image: docker.io/bitnami/valkey:8.0
|
||||
image: docker.io/valkey/valkey:8.0
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- VALKEY_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
valkey:
|
||||
image: docker.io/bitnami/valkey:8.0
|
||||
image: docker.io/valkey/valkey:8.0
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- VALKEY_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import dotenv from "dotenv";
|
||||
import {
|
||||
CustomerEntitlement,
|
||||
FullCusEntWithProduct,
|
||||
ResetCusEnt,
|
||||
} from "@autumn/shared";
|
||||
import { CusEntService } from "./internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import { format } from "date-fns";
|
||||
import { CronJob } from "cron";
|
||||
import type { CustomerEntitlement, ResetCusEnt } from "@autumn/shared";
|
||||
import { UTCDate } from "@date-fns/utc";
|
||||
import { initDrizzle } from "./db/initDrizzle.js";
|
||||
import { CronJob } from "cron";
|
||||
import { format } from "date-fns";
|
||||
import dotenv from "dotenv";
|
||||
import { resetCustomerEntitlement } from "./cron/cronUtils.js";
|
||||
import { runProductCron } from "./cron/productCron/runProductCron.js";
|
||||
import { initDrizzle } from "./db/initDrizzle.js";
|
||||
import { CusEntService } from "./internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import { OrgService } from "./internal/orgs/OrgService.js";
|
||||
import { notNullish } from "./utils/genUtils.js";
|
||||
|
||||
@@ -45,9 +42,9 @@ export const cronTask = async () => {
|
||||
);
|
||||
}
|
||||
|
||||
let results = await Promise.all(batchResets);
|
||||
const results = await Promise.all(batchResets);
|
||||
|
||||
let toUpsert = results.filter(notNullish);
|
||||
const toUpsert = results.filter(notNullish);
|
||||
await CusEntService.upsert({
|
||||
db,
|
||||
data: toUpsert as CustomerEntitlement[],
|
||||
@@ -68,17 +65,22 @@ export const cronTask = async () => {
|
||||
// await client.end();
|
||||
};
|
||||
|
||||
const job = new CronJob(
|
||||
const main = async () => {
|
||||
await Promise.all([
|
||||
// cronTask(),
|
||||
runProductCron(),
|
||||
]);
|
||||
};
|
||||
|
||||
new CronJob(
|
||||
"* * * * *", // Run every minute
|
||||
function () {
|
||||
cronTask();
|
||||
},
|
||||
main,
|
||||
null, // onComplete
|
||||
true, // start immediately
|
||||
"UTC", // timezone (adjust as needed)
|
||||
);
|
||||
|
||||
cronTask();
|
||||
main();
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
console.log("Received SIGTERM signal, closing database connection...");
|
||||
|
||||
67
server/src/cron/productCron/runProductCron.ts
Normal file
67
server/src/cron/productCron/runProductCron.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { customerPrices, customerProducts, customers } from "@autumn/shared";
|
||||
import { and, eq, gt, isNotNull, notExists, sql } from "drizzle-orm";
|
||||
import { db } from "@/db/initDrizzle.js";
|
||||
|
||||
export const runProductCron = async () => {
|
||||
console.log("Running product cron");
|
||||
// Get customer_products that have 0 customer_prices, and trial_ends_at is not null, and trial_ends_at > now
|
||||
const results = await db
|
||||
.select()
|
||||
.from(customerProducts)
|
||||
.innerJoin(
|
||||
customers,
|
||||
eq(customerProducts.internal_customer_id, customers.internal_id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
// No customer_prices exist for this customer_product
|
||||
notExists(
|
||||
db
|
||||
.select()
|
||||
.from(customerPrices)
|
||||
.where(eq(customerPrices.customer_product_id, customerProducts.id)),
|
||||
),
|
||||
// trial_ends_at is not null
|
||||
isNotNull(customerProducts.trial_ends_at),
|
||||
// trial_ends_at > now (comparing epoch timestamps in milliseconds)
|
||||
gt(
|
||||
customerProducts.trial_ends_at,
|
||||
sql`(EXTRACT(EPOCH FROM NOW()) * 1000)::bigint`,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Found ${results.length} customer products with no prices and active trials`,
|
||||
);
|
||||
|
||||
const uniqueOrgIds = [...new Set(results.map((r) => r.customers.org_id))];
|
||||
console.log("Unique org IDs:", uniqueOrgIds);
|
||||
|
||||
// for (const result of results) {
|
||||
// console.log(
|
||||
// `Customer ${result.customers.id}, Org: ${result.customers.org_id}, product: ${result.customer_products.product_id}`,
|
||||
// );
|
||||
// }
|
||||
// const expireCusProduct = async (customerProduct: CustomerProduct) => {
|
||||
// await CusProductService.update({
|
||||
// db,
|
||||
// cusProductId: customerProduct.id,
|
||||
// updates: {
|
||||
// status: CusProductStatus.Expired,
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
|
||||
// const batchSize = 50;
|
||||
// for (let i = 0; i < results.length; i += batchSize) {
|
||||
// const batch = results.slice(i, i + batchSize);
|
||||
// const batchExpires = batch.map((cusProduct) =>
|
||||
// expireCusProduct(cusProduct),
|
||||
// );
|
||||
// await Promise.all(batchExpires);
|
||||
// console.log(`Expired batch of ${batch.length} customer products`);
|
||||
// }
|
||||
|
||||
return results;
|
||||
};
|
||||
@@ -1,26 +1,25 @@
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import {
|
||||
activateFutureProduct,
|
||||
activateDefaultProduct,
|
||||
cancelCusProductSubscriptions,
|
||||
} from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import { cusProductToPrices } from "@autumn/shared";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import {
|
||||
FullCusProduct,
|
||||
APIVersion,
|
||||
AttachScenario,
|
||||
BillingType,
|
||||
CusProductStatus,
|
||||
AttachScenario,
|
||||
cusProductToPrices,
|
||||
type FullCusProduct,
|
||||
} from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { getCusPaymentMethod } from "../../stripeCusUtils.js";
|
||||
import { webhookToAttachParams } from "../../webhookUtils/webhookUtils.js";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { createUsageInvoice } from "@/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoice.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import {
|
||||
activateDefaultProduct,
|
||||
activateFutureProduct,
|
||||
} from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getCusPaymentMethod } from "../../stripeCusUtils.js";
|
||||
import { webhookToAttachParams } from "../../webhookUtils/webhookUtils.js";
|
||||
|
||||
export const handleCusProductDeleted = async ({
|
||||
req,
|
||||
@@ -137,13 +136,13 @@ export const handleCusProductDeleted = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
let cusProducts = await CusProductService.list({
|
||||
const cusProducts = await CusProductService.list({
|
||||
db,
|
||||
internalCustomerId: cusProduct.customer!.internal_id,
|
||||
internalCustomerId: cusProduct.internal_customer_id,
|
||||
inStatuses: [CusProductStatus.Active, CusProductStatus.PastDue],
|
||||
});
|
||||
|
||||
let { curMainProduct } = getExistingCusProducts({
|
||||
const { curMainProduct } = getExistingCusProducts({
|
||||
product: cusProduct.product,
|
||||
cusProducts,
|
||||
});
|
||||
@@ -154,12 +153,4 @@ export const handleCusProductDeleted = async ({
|
||||
fullCus,
|
||||
curCusProduct: curMainProduct || undefined,
|
||||
});
|
||||
|
||||
// await cancelCusProductSubscriptions({
|
||||
// cusProduct,
|
||||
// org,
|
||||
// env,
|
||||
// excludeIds: [subscription.id],
|
||||
// logger,
|
||||
// });
|
||||
};
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import Stripe from "stripe";
|
||||
import { AttachScenario, CusProductStatus } from "@autumn/shared";
|
||||
import { createStripeCli } from "../../utils.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { activateFutureProduct } from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { cusProductToProduct } from "@autumn/shared";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import {
|
||||
AttachScenario,
|
||||
CusProductStatus,
|
||||
cusProductToProduct,
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { activateFutureProduct } from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { createStripeCli } from "../../utils.js";
|
||||
|
||||
export const handleSchedulePhaseCompleted = async ({
|
||||
req,
|
||||
@@ -127,9 +129,4 @@ export const handleSchedulePhaseCompleted = async ({
|
||||
);
|
||||
}
|
||||
}
|
||||
// const currentPhase = schedule.phases.find(
|
||||
// (phase) =>
|
||||
// phase.start_date <= Math.floor(now / 1000) &&
|
||||
// phase.end_date > Math.floor(now / 1000)
|
||||
// );
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ACTIVE_STATUSES,
|
||||
APIVersion,
|
||||
type AppEnv,
|
||||
AttachScenario,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
type FixedPriceConfig,
|
||||
type FullCusProduct,
|
||||
type FullCustomer,
|
||||
type FullProduct,
|
||||
type Organization,
|
||||
PriceType,
|
||||
type Subscription,
|
||||
@@ -31,7 +33,6 @@ import { notNullish, nullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { handleAddProduct } from "../attach/attachFunctions/addProductFlow/handleAddProduct.js";
|
||||
import { newCusToAttachParams } from "../attach/attachUtils/attachParams/convertToParams.js";
|
||||
import { getDefaultAttachConfig } from "../attach/attachUtils/getAttachConfig.js";
|
||||
import { initStripeCusAndProducts } from "../handlers/handleCreateCustomer.js";
|
||||
import { CusProductService, RELEVANT_STATUSES } from "./CusProductService.js";
|
||||
import { getRelatedCusEnt } from "./cusPrices/cusPriceUtils.js";
|
||||
@@ -133,6 +134,7 @@ export const getDefaultProduct = async ({
|
||||
return defaultProd;
|
||||
};
|
||||
|
||||
// This function is only used in cancellation flows
|
||||
export const activateDefaultProduct = async ({
|
||||
req,
|
||||
productGroup,
|
||||
@@ -153,44 +155,38 @@ export const activateDefaultProduct = async ({
|
||||
});
|
||||
|
||||
// Look for a paid default trial first, then fall back to free default
|
||||
let defaultProd = defaultProducts.find(
|
||||
const defaultProd: FullProduct | undefined = defaultProducts.find(
|
||||
(p) =>
|
||||
p.group === productGroup && isDefaultTrialFullProduct({ product: p }),
|
||||
p.group === productGroup && !isDefaultTrialFullProduct({ product: p }),
|
||||
);
|
||||
|
||||
const defaultableProducts = {
|
||||
free: defaultProducts.filter(
|
||||
(p) => p.group === productGroup && isFreeProduct(p.prices),
|
||||
),
|
||||
paid: defaultProducts.filter(
|
||||
(p) =>
|
||||
p.group === productGroup && isDefaultTrialFullProduct({ product: p }),
|
||||
),
|
||||
};
|
||||
// const defaultableProducts = {
|
||||
// free: defaultProducts.filter(
|
||||
// (p) => p.group === productGroup && isFreeProduct(p.prices),
|
||||
// ),
|
||||
// paid: defaultProducts.filter(
|
||||
// (p) =>
|
||||
// p.group === productGroup && isDefaultTrialFullProduct({ product: p }),
|
||||
// ),
|
||||
// };
|
||||
|
||||
// console.log("Found defaultable products:", {
|
||||
// free: defaultableProducts.free.map((p) => p.name),
|
||||
// paid: defaultableProducts.paid.map((p) => p.name),
|
||||
// });
|
||||
// if (defaultableProducts.paid.length > 0) {
|
||||
// defaultProd = defaultableProducts.paid[0];
|
||||
// } else if (defaultableProducts.free.length > 0) {
|
||||
// defaultProd = defaultableProducts.free[0];
|
||||
// }
|
||||
|
||||
if (defaultableProducts.paid.length > 0) {
|
||||
defaultProd = defaultableProducts.paid[0];
|
||||
} else if (defaultableProducts.free.length > 0) {
|
||||
defaultProd = defaultableProducts.free[0];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (!defaultProd) return false;
|
||||
|
||||
if (curCusProduct?.internal_product_id == defaultProd.internal_id) {
|
||||
if (curCusProduct?.internal_product_id === defaultProd.internal_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const defaultIsFree = isFreeProduct(defaultProd.prices);
|
||||
const isDefaultTrial = isDefaultTrialFullProduct({ product: defaultProd });
|
||||
|
||||
// Initialize Stripe customer and products if needed (for paid non-trial products)
|
||||
if (!defaultIsFree && !isDefaultTrial) {
|
||||
if (!defaultIsFree) {
|
||||
await initStripeCusAndProducts({
|
||||
db,
|
||||
org,
|
||||
@@ -201,83 +197,87 @@ export const activateDefaultProduct = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (!isDefaultTrial) {
|
||||
const existingDefaultProduct = fullCus.customer_products.find(
|
||||
(cp) =>
|
||||
cp.product.internal_id === defaultProd!.internal_id &&
|
||||
(cp.status === CusProductStatus.Active ||
|
||||
cp.status === CusProductStatus.PastDue ||
|
||||
cp.status === CusProductStatus.Trialing),
|
||||
// If default is already active, skip
|
||||
const existingDefaultProduct = fullCus.customer_products.find(
|
||||
(cp) =>
|
||||
cp.product.id === defaultProd?.id && ACTIVE_STATUSES.includes(cp.status),
|
||||
);
|
||||
|
||||
if (existingDefaultProduct) {
|
||||
logger.info(
|
||||
`Default product ${defaultProd?.name} already exists for customer`,
|
||||
);
|
||||
|
||||
if (existingDefaultProduct) {
|
||||
logger.info(
|
||||
`Default product ${defaultProd!.name} already exists for customer`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
await handleAddProduct({
|
||||
req,
|
||||
attachParams: newCusToAttachParams({
|
||||
req,
|
||||
newCus: fullCus,
|
||||
products: [defaultProd],
|
||||
stripeCli,
|
||||
}),
|
||||
});
|
||||
|
||||
// await createFullCusProduct({
|
||||
// db,
|
||||
// attachParams: {
|
||||
// org,
|
||||
// customer,
|
||||
// product: defaultProd,
|
||||
// prices: defaultProd.prices,
|
||||
// entitlements: defaultProd.entitlements,
|
||||
// freeTrial: defaultProd.free_trial || null,
|
||||
// optionsList: [],
|
||||
// entities: [],
|
||||
// features: [],
|
||||
// replaceables: [],
|
||||
// },
|
||||
// scenario: AttachScenario.New,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// console.log(` ✅ activated default product: ${defaultProd.group}`);
|
||||
return true;
|
||||
} else if (isDefaultTrial && defaultableProducts.free.length > 0) {
|
||||
defaultProd = defaultableProducts.free[0];
|
||||
|
||||
// Check if the free default product already exists to prevent duplicates
|
||||
const existingFreeProduct = fullCus.customer_products.find(
|
||||
(cp) =>
|
||||
cp.product.internal_id === defaultProd!.internal_id &&
|
||||
(cp.status === CusProductStatus.Active ||
|
||||
cp.status === CusProductStatus.PastDue),
|
||||
);
|
||||
|
||||
if (existingFreeProduct) {
|
||||
logger.info(
|
||||
`Free default product ${defaultProd!.name} already exists for customer`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
await handleAddProduct({
|
||||
req,
|
||||
attachParams: newCusToAttachParams({
|
||||
req,
|
||||
newCus: fullCus,
|
||||
products: [defaultProd],
|
||||
stripeCli,
|
||||
}),
|
||||
config: getDefaultAttachConfig(),
|
||||
});
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
await handleAddProduct({
|
||||
req,
|
||||
attachParams: newCusToAttachParams({
|
||||
req,
|
||||
newCus: fullCus,
|
||||
products: [defaultProd],
|
||||
stripeCli,
|
||||
}),
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
// if (!isDefaultTrial) {
|
||||
// const existingDefaultProduct = fullCus.customer_products.find(
|
||||
// (cp) =>
|
||||
// cp.product.internal_id === defaultProd?.internal_id &&
|
||||
// ACTIVE_STATUSES.includes(cp.status),
|
||||
// );
|
||||
|
||||
// if (existingDefaultProduct) {
|
||||
// logger.info(
|
||||
// `Default product ${defaultProd?.name} already exists for customer`,
|
||||
// );
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// await handleAddProduct({
|
||||
// req,
|
||||
// attachParams: newCusToAttachParams({
|
||||
// req,
|
||||
// newCus: fullCus,
|
||||
// products: [defaultProd],
|
||||
// stripeCli,
|
||||
// }),
|
||||
// });
|
||||
|
||||
// return true;
|
||||
// } else if (isDefaultTrial && defaultableProducts.free.length > 0) {
|
||||
// defaultProd = defaultableProducts.free[0];
|
||||
|
||||
// // Check if the free default product already exists to prevent duplicates
|
||||
// const existingFreeProduct = fullCus.customer_products.find(
|
||||
// (cp) =>
|
||||
// cp.product.internal_id === defaultProd!.internal_id &&
|
||||
// (cp.status === CusProductStatus.Active ||
|
||||
// cp.status === CusProductStatus.PastDue),
|
||||
// );
|
||||
|
||||
// if (existingFreeProduct) {
|
||||
// logger.info(
|
||||
// `Free default product ${defaultProd?.name} already exists for customer`,
|
||||
// );
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// await handleAddProduct({
|
||||
// req,
|
||||
// attachParams: newCusToAttachParams({
|
||||
// req,
|
||||
// newCus: fullCus,
|
||||
// products: [defaultProd],
|
||||
// stripeCli,
|
||||
// }),
|
||||
// config: getDefaultAttachConfig(),
|
||||
// });
|
||||
|
||||
// return true;
|
||||
// }
|
||||
};
|
||||
|
||||
export const expireAndActivate = async ({
|
||||
|
||||
@@ -196,51 +196,5 @@ export const createNewCustomer = async ({
|
||||
}
|
||||
}
|
||||
|
||||
// if (nonFreeProds.length > 0) {
|
||||
// const stripeCli = createStripeCli({ org, env });
|
||||
|
||||
// await initStripeCusAndProducts({
|
||||
// db,
|
||||
// org,
|
||||
// env,
|
||||
// customer: newCustomer,
|
||||
// products: nonFreeProds,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// await handleAddProduct({
|
||||
// req,
|
||||
// attachParams: newCusToAttachParams({
|
||||
// req,
|
||||
// newCus: newCustomer as FullCustomer,
|
||||
// products: nonFreeProds,
|
||||
// stripeCli,
|
||||
// freeTrial: defaultPaidTrialProd?.free_trial || null,
|
||||
// }),
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (!defaultPaidTrialProd) {
|
||||
// for (const product of freeProds) {
|
||||
// await createFullCusProduct({
|
||||
// db,
|
||||
// attachParams: newCusToInsertParams({
|
||||
// req,
|
||||
// newCus: newCustomer,
|
||||
// product,
|
||||
// }),
|
||||
// nextResetAt,
|
||||
// anchorToUnix: org.config.anchor_start_of_month
|
||||
// ? getNextStartOfMonthUnix({
|
||||
// interval: BillingInterval.Month,
|
||||
// intervalCount: 1,
|
||||
// })
|
||||
// : undefined,
|
||||
// scenario: AttachScenario.New,
|
||||
// logger,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
return newCustomer;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FullProduct, Price, ProductV2 } from "@autumn/shared";
|
||||
import type { FullProduct, Price, ProductV2 } from "@autumn/shared";
|
||||
import { pricesOnlyOneOff } from "../prices/priceUtils.js";
|
||||
import { isFeatureItem } from "../product-items/productItemUtils/getItemType.js";
|
||||
import { isFreeProduct } from "../productUtils.js";
|
||||
@@ -57,6 +57,9 @@ export const isDefaultTrialFullProduct = ({
|
||||
product: FullProduct;
|
||||
skipDefault?: boolean;
|
||||
}) => {
|
||||
// If it's free + trial, also consider it default trial
|
||||
if (isFreeProduct(product.prices) && product.free_trial) return true;
|
||||
|
||||
return (
|
||||
product.free_trial &&
|
||||
!product.free_trial?.card_required &&
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import type { Autumn } from "autumn-js";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import type { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { deleteCusCache } from "@/internal/customers/cusCache/updateCachedCus.js";
|
||||
import {
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
createStripeCustomer,
|
||||
} from "../../external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "../../external/stripe/utils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
|
||||
export const createCusInStripe = async ({
|
||||
customer,
|
||||
@@ -188,7 +188,7 @@ export const initCustomerV2 = async ({
|
||||
attachPm,
|
||||
withTestClock = true,
|
||||
}: {
|
||||
autumn: Autumn;
|
||||
autumn: Autumn | AutumnInt;
|
||||
customerId: string;
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
@@ -201,7 +201,7 @@ export const initCustomerV2 = async ({
|
||||
const fingerprint_ = "";
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
|
||||
let testClockId;
|
||||
let testClockId: string | undefined;
|
||||
|
||||
if (withTestClock) {
|
||||
const testClock = await stripeCli.testHelpers.testClocks.create({
|
||||
@@ -220,7 +220,8 @@ export const initCustomerV2 = async ({
|
||||
// 2. Create customer
|
||||
try {
|
||||
await autumn.customers.delete(customerId);
|
||||
} catch (error) {}
|
||||
} catch (_error) {}
|
||||
|
||||
await autumn.customers.create({
|
||||
id: customerId,
|
||||
name,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import "dotenv/config";
|
||||
|
||||
import { timeout } from "./genUtils.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { BillingInterval, Customer, FullProduct } from "@autumn/shared";
|
||||
import {
|
||||
BillingInterval,
|
||||
type Customer,
|
||||
type FullProduct,
|
||||
} from "@autumn/shared";
|
||||
import {
|
||||
addDays,
|
||||
addHours,
|
||||
@@ -12,9 +14,11 @@ import {
|
||||
format,
|
||||
} from "date-fns";
|
||||
import puppeteer from "puppeteer-core";
|
||||
import Browserbase from "@browserbasehq/sdk";
|
||||
import type { Stripe } from "stripe";
|
||||
import { timeout } from "./genUtils.js";
|
||||
|
||||
const STRIPE_TEST_CLOCK_TIMING = 20000; // 30s
|
||||
|
||||
import { Hyperbrowser } from "@hyperbrowser/sdk";
|
||||
|
||||
const client = new Hyperbrowser({
|
||||
@@ -186,7 +190,7 @@ export const deleteStripeProduct = async ({
|
||||
|
||||
if (product.processor) {
|
||||
// console.log("Stripe product", stripeProd.active, stripeProd.id);
|
||||
let stripeProdId = product.processor.id;
|
||||
const stripeProdId = product.processor.id;
|
||||
try {
|
||||
await stripeCli.products.del(stripeProdId);
|
||||
} catch (error) {
|
||||
@@ -260,7 +264,7 @@ export const advanceTestClock = async ({
|
||||
}
|
||||
|
||||
console.log(" - Advancing to: ", format(advanceTo, "dd MMM yyyy HH:mm:ss"));
|
||||
await stripeCli.testHelpers.testClocks.advance(testClockId, {
|
||||
const res = await stripeCli.testHelpers.testClocks.advance(testClockId, {
|
||||
frozen_time: Math.floor(advanceTo / 1000),
|
||||
});
|
||||
|
||||
@@ -384,7 +388,7 @@ export const checkBillingMeterEventSummary = async ({
|
||||
stripeMeterId: string;
|
||||
stripeCustomerId: string;
|
||||
}) => {
|
||||
let endTime = addMonths(startTime, 1);
|
||||
const endTime = addMonths(startTime, 1);
|
||||
const event = await stripeCli.billing.meters.listEventSummaries(
|
||||
stripeMeterId,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user