working on trials
This commit is contained in:
@@ -81,11 +81,11 @@ export const handleSubscriptionUpdated = async ({
|
||||
status: subStatusMap[subscription.status] || CusProductStatus.Unknown,
|
||||
canceled_at: canceled ? canceledAt : null,
|
||||
collection_method: fullSub.collection_method as CollectionMethod,
|
||||
trial_ends_at:
|
||||
previousAttributes.status === "trialing" &&
|
||||
subscription.status === "active"
|
||||
? null
|
||||
: undefined,
|
||||
// trial_ends_at:
|
||||
// previousAttributes.status === "trialing" &&
|
||||
// subscription.status === "active"
|
||||
// ? null
|
||||
// : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ import { getCustomerSub } from "../../attachUtils/convertAttachParams.js";
|
||||
import { paramsToSubItems } from "../../mergeUtils/paramsToSubItems.js";
|
||||
import { updateStripeSub2 } from "../upgradeFlow/updateStripeSub2.js";
|
||||
import { subToNewSchedule } from "../../mergeUtils/subToNewSchedule.js";
|
||||
import { isTrialing } from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import { formatUnixToDate } from "@/utils/genUtils.js";
|
||||
|
||||
export const handlePaidProduct = async ({
|
||||
req,
|
||||
@@ -69,19 +71,42 @@ export const handlePaidProduct = async ({
|
||||
let subscriptions: Stripe.Subscription[] = [];
|
||||
|
||||
// Only merge if no free trials
|
||||
let mergeCusProduct = undefined;
|
||||
if (!config.disableMerge && !freeTrial) {
|
||||
mergeCusProduct = cusProducts?.find((cp) =>
|
||||
products.some((p) => p.group == cp.product.group)
|
||||
);
|
||||
}
|
||||
|
||||
const mergeSub = await getCustomerSub({ attachParams });
|
||||
// if (!config.disableMerge && !freeTrial) {
|
||||
// mergeCusProduct = cusProducts?.find((cp) =>
|
||||
// products.some((p) => p.group == cp.product.group)
|
||||
// );
|
||||
// }
|
||||
|
||||
const { sub: mergeSub, cusProduct: mergeCusProduct } = await getCustomerSub({
|
||||
attachParams,
|
||||
});
|
||||
|
||||
let sub: Stripe.Subscription | null = null;
|
||||
let schedule: Stripe.SubscriptionSchedule | null = null;
|
||||
let trialEndsAt = undefined;
|
||||
|
||||
console.log("Merge sub:", mergeSub?.id);
|
||||
console.log("Merge cus product:", mergeCusProduct?.product.id);
|
||||
console.log(
|
||||
"Trial ends at:",
|
||||
formatUnixToDate(mergeCusProduct?.trial_ends_at || 0)
|
||||
);
|
||||
|
||||
throw new Error("test");
|
||||
|
||||
// 1. If merge sub
|
||||
if (mergeSub) {
|
||||
if (mergeCusProduct?.free_trial) {
|
||||
if (isTrialing({ cusProduct: mergeCusProduct, now: attachParams.now })) {
|
||||
attachParams.freeTrial = mergeCusProduct.free_trial;
|
||||
trialEndsAt = mergeCusProduct.trial_ends_at;
|
||||
} else {
|
||||
attachParams.freeTrial = null;
|
||||
trialEndsAt = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. If merged sub is canceled, also add to current schedule
|
||||
const newItemSet = await paramsToSubItems({
|
||||
req,
|
||||
@@ -194,6 +219,7 @@ export const handlePaidProduct = async ({
|
||||
anchorToUnix,
|
||||
carryExistingUsages: config.carryUsage,
|
||||
scenario: AttachScenario.New,
|
||||
trialEndsAt: trialEndsAt || undefined,
|
||||
logger,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -104,11 +104,19 @@ export const handleScheduleFunction2 = async ({
|
||||
if (currentPhaseIndex == newItems.phases.length - 1) {
|
||||
console.log(`NO SUBSEQUENT PHASES, RELEASING SCHEDULE`);
|
||||
await stripeCli.subscriptionSchedules.release(schedule!.id);
|
||||
await CusProductService.updateByStripeScheduledId({
|
||||
db: req.db,
|
||||
stripeScheduledId: schedule!.id,
|
||||
updates: { scheduled_ids: [] },
|
||||
});
|
||||
|
||||
await CusProductService.update({
|
||||
db: req.db,
|
||||
cusProductId: curCusProduct!.id,
|
||||
updates: {
|
||||
scheduled_ids: [],
|
||||
canceled: true,
|
||||
canceled_at: Date.now(),
|
||||
ended_at: expectedEnd * 1000,
|
||||
},
|
||||
});
|
||||
schedule = undefined;
|
||||
@@ -126,6 +134,7 @@ export const handleScheduleFunction2 = async ({
|
||||
cusProductId: curCusProduct!.id,
|
||||
updates: {
|
||||
scheduled_ids: [schedule!.id],
|
||||
canceled_at: Date.now(),
|
||||
canceled: true,
|
||||
ended_at: expectedEnd * 1000,
|
||||
},
|
||||
|
||||
@@ -26,14 +26,9 @@ import { getStripeSubItems2 } from "@/external/stripe/stripeSubUtils/getStripeSu
|
||||
import { updateStripeSub2 } from "./updateStripeSub2.js";
|
||||
import { getEarliestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
|
||||
import { paramsToSubItems } from "../../mergeUtils/paramsToSubItems.js";
|
||||
import { paramsToScheduleItems } from "../../mergeUtils/paramsToScheduleItems.js";
|
||||
import { updateCurSchedule } from "../../mergeUtils/updateCurSchedule.js";
|
||||
import {
|
||||
getCurrentPhaseIndex,
|
||||
logPhases,
|
||||
} from "../../mergeUtils/phaseUtils/phaseUtils.js";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { shouldCancelSub } from "./upgradeFlowUtils.js";
|
||||
import { handleUpgradeFlowSchedule } from "./handleUpgradeFlowSchedule.js";
|
||||
|
||||
export const handleUpgradeFlow = async ({
|
||||
req,
|
||||
@@ -65,7 +60,7 @@ export const handleUpgradeFlow = async ({
|
||||
|
||||
const newItemSet = await paramsToSubItems({
|
||||
req,
|
||||
sub: curSub!,
|
||||
sub: curSub,
|
||||
attachParams,
|
||||
config,
|
||||
});
|
||||
@@ -90,9 +85,12 @@ export const handleUpgradeFlow = async ({
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel sub...
|
||||
let canceled = false;
|
||||
if (shouldCancelSub({ sub: curSub!, newSubItems: subItems })) {
|
||||
// SCENARIO 1, NO SUB:
|
||||
if (!curSub) {
|
||||
console.log("UPGRADE FLOW, NO SUB (FROM CANCEL MAYBE...?)");
|
||||
// Do something about current sub...
|
||||
} else if (shouldCancelSub({ sub: curSub!, newSubItems: subItems })) {
|
||||
console.log(
|
||||
`UPGRADE FLOW, CANCELLING SUB ${curSub!.id}, PRORATE: ${config.proration}`
|
||||
);
|
||||
@@ -106,8 +104,8 @@ export const handleUpgradeFlow = async ({
|
||||
},
|
||||
});
|
||||
} else if (subItems.length > 0) {
|
||||
console.log(`UPGRADE FLOW, UPDATING SUB ${curSub!.id}`);
|
||||
itemSet.subItems = subItems;
|
||||
console.log("New sub items:", subItems);
|
||||
|
||||
const res = await updateStripeSub2({
|
||||
req,
|
||||
@@ -119,45 +117,14 @@ export const handleUpgradeFlow = async ({
|
||||
});
|
||||
|
||||
const schedule = await paramsToCurSubSchedule({ attachParams });
|
||||
|
||||
// Add to schedule?
|
||||
if (schedule) {
|
||||
console.log("CUR ITEMS:");
|
||||
await logPhases({
|
||||
phases: schedule.phases as any,
|
||||
db: req.db,
|
||||
});
|
||||
|
||||
const currentPhaseIndex = getCurrentPhaseIndex({
|
||||
await handleUpgradeFlowSchedule({
|
||||
req,
|
||||
attachParams,
|
||||
config,
|
||||
schedule,
|
||||
now: attachParams.now,
|
||||
curSub,
|
||||
});
|
||||
|
||||
const nextPhaseIndex = currentPhaseIndex + 1;
|
||||
|
||||
if (nextPhaseIndex < schedule.phases.length) {
|
||||
const newItems = await paramsToScheduleItems({
|
||||
req,
|
||||
schedule,
|
||||
attachParams,
|
||||
config,
|
||||
billingPeriodEnd: schedule?.phases?.[nextPhaseIndex]?.start_date,
|
||||
});
|
||||
|
||||
console.log("NEW ITEMS:");
|
||||
await logPhases({
|
||||
phases: newItems.phases,
|
||||
db: req.db,
|
||||
});
|
||||
|
||||
await updateCurSchedule({
|
||||
req,
|
||||
attachParams,
|
||||
schedule,
|
||||
newPhases: newItems.phases,
|
||||
sub: curSub!,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
attachParams.replaceables = res.replaceables || [];
|
||||
@@ -227,9 +194,3 @@ export const handleUpgradeFlow = async ({
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// const newSubItems = await removeCurCusProductItems({
|
||||
// sub: curSub,
|
||||
// cusProduct: curCusProduct!,
|
||||
// subItems: itemSet.subItems,
|
||||
// });
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AttachConfig } from "@autumn/shared";
|
||||
|
||||
import Stripe from "stripe";
|
||||
import { paramsToScheduleItems } from "../../mergeUtils/paramsToScheduleItems.js";
|
||||
import {
|
||||
logPhases,
|
||||
getCurrentPhaseIndex,
|
||||
} from "../../mergeUtils/phaseUtils/phaseUtils.js";
|
||||
import { updateCurSchedule } from "../../mergeUtils/updateCurSchedule.js";
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import {
|
||||
ACTIVE_STATUSES,
|
||||
CusProductService,
|
||||
} from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { attachParamsToCurCusProduct } from "../../attachUtils/convertAttachParams.js";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
|
||||
export const handleUpgradeFlowSchedule = async ({
|
||||
req,
|
||||
attachParams,
|
||||
config,
|
||||
schedule,
|
||||
curSub,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
attachParams: AttachParams;
|
||||
config: AttachConfig;
|
||||
schedule: Stripe.SubscriptionSchedule;
|
||||
curSub: Stripe.Subscription;
|
||||
}) => {
|
||||
console.log(`UPGRADE FLOW, UPDATING SCHEDULE ${schedule.id}`);
|
||||
const { stripeCli, customer, prices } = attachParams;
|
||||
const curCusProduct = attachParamsToCurCusProduct({ attachParams });
|
||||
|
||||
// console.log("CUR ITEMS:");
|
||||
// await logPhases({
|
||||
// phases: schedule.phases as any,
|
||||
// db: req.db,
|
||||
// });
|
||||
|
||||
const currentPhaseIndex = getCurrentPhaseIndex({
|
||||
schedule,
|
||||
now: attachParams.now,
|
||||
});
|
||||
|
||||
const nextPhaseIndex = currentPhaseIndex + 1;
|
||||
|
||||
if (currentPhaseIndex == -1 || nextPhaseIndex >= schedule.phases.length)
|
||||
return;
|
||||
|
||||
const newItems = await paramsToScheduleItems({
|
||||
req,
|
||||
schedule,
|
||||
attachParams,
|
||||
config,
|
||||
billingPeriodEnd: schedule?.phases?.[nextPhaseIndex]?.start_date,
|
||||
});
|
||||
|
||||
// Should release schedule...
|
||||
const newCurPhaseIndex = getCurrentPhaseIndex({
|
||||
schedule: { phases: newItems.phases } as any,
|
||||
now: attachParams.now,
|
||||
});
|
||||
|
||||
// If there are no subsequent phases, release schedule...
|
||||
// Example: mergedUpgrade4.test.ts, mergedCancel2.test.ts
|
||||
// pro, pro -> free, pro -> premium, pro (need to cancel initial schedule)
|
||||
if (newCurPhaseIndex == newItems.phases.length - 1) {
|
||||
console.log(`NO SUBSEQUENT PHASES, RELEASING SCHEDULE`);
|
||||
await stripeCli.subscriptionSchedules.release(schedule!.id);
|
||||
await CusProductService.updateByStripeScheduledId({
|
||||
db: req.db,
|
||||
stripeScheduledId: schedule!.id,
|
||||
updates: { scheduled_ids: [] },
|
||||
});
|
||||
|
||||
// Should we cancel the sub...?
|
||||
// If all other products are canceled, and new product is free, cancel the sub...
|
||||
const shouldCancelSub =
|
||||
customer.customer_products
|
||||
.filter(
|
||||
(cp) =>
|
||||
cp.id !== curCusProduct?.id &&
|
||||
cp.subscription_ids?.includes(curSub.id) &&
|
||||
ACTIVE_STATUSES.includes(cp.status)
|
||||
)
|
||||
.every((cp) => cp.canceled) && isFreeProduct(prices);
|
||||
|
||||
if (shouldCancelSub) {
|
||||
console.log(`UPGRADE FLOW, CANCELING SUB: ${curSub.id}`);
|
||||
await stripeCli.subscriptions.update(curSub.id, {
|
||||
cancel_at_period_end: true,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await logPhases({
|
||||
phases: newItems.phases,
|
||||
db: req.db,
|
||||
});
|
||||
|
||||
await updateCurSchedule({
|
||||
req,
|
||||
attachParams,
|
||||
schedule,
|
||||
newPhases: newItems.phases,
|
||||
sub: curSub!,
|
||||
});
|
||||
};
|
||||
@@ -40,15 +40,13 @@ export const updateStripeSub2 = async ({
|
||||
});
|
||||
}
|
||||
|
||||
let trialEnd = config.disableTrial
|
||||
? undefined
|
||||
: freeTrialToStripeTimestamp({
|
||||
freeTrial: attachParams.freeTrial,
|
||||
now: attachParams.now,
|
||||
});
|
||||
|
||||
console.log("Sub items:", sanitizeSubItems(itemSet.subItems));
|
||||
console.log("Invoice items:", itemSet.invoiceItems);
|
||||
let trialEnd =
|
||||
config.disableTrial || config.carryTrial
|
||||
? undefined
|
||||
: freeTrialToStripeTimestamp({
|
||||
freeTrial: attachParams.freeTrial,
|
||||
now: attachParams.now,
|
||||
});
|
||||
|
||||
// 1. Update subscription
|
||||
let updatedSub = await stripeCli.subscriptions.update(curSub.id, {
|
||||
|
||||
@@ -34,7 +34,7 @@ export const priceToUnusedPreviewItem = ({
|
||||
org?: Organization;
|
||||
}) => {
|
||||
now = now || Date.now();
|
||||
const onTrial = isTrialing(cusProduct);
|
||||
const onTrial = isTrialing({ cusProduct, now });
|
||||
|
||||
// 1. Get price from stripe items
|
||||
const subItem = findStripeItemForPrice({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getExistingCusProducts } from "../../cusProducts/cusProductUtils/getExi
|
||||
import { CusProductStatus } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { cusProductToProduct } from "../../cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { isTrialing } from "../../cusProducts/cusProductUtils.js";
|
||||
|
||||
export const attachParamsToCurCusProduct = ({
|
||||
attachParams,
|
||||
@@ -118,17 +119,34 @@ export const getCustomerSub = async ({
|
||||
return 0;
|
||||
});
|
||||
|
||||
const subId = cusProducts.flatMap((cp) => cp.subscription_ids || [])?.[0];
|
||||
// const subId = cusProducts.flatMap((cp) => cp.subscription_ids || [])?.[0];
|
||||
const cusProduct = cusProducts.find(
|
||||
(cp) => cp.subscription_ids && cp.subscription_ids.length > 0
|
||||
);
|
||||
|
||||
if (!subId) {
|
||||
return undefined;
|
||||
if (!cusProduct) return { sub: undefined, cusProduct: undefined };
|
||||
const subId = cusProduct.subscription_ids![0];
|
||||
|
||||
// If there's only one customer product on sub, and it's still trialing, return undefined, because should just replace sub.
|
||||
const curCusProduct = attachParamsToCurCusProduct({ attachParams });
|
||||
const cusProductsOnSub = cusProducts.filter(
|
||||
(cp) =>
|
||||
cp.subscription_ids?.includes(cusProduct.subscription_ids![0]) &&
|
||||
curCusProduct?.id == cp.id
|
||||
);
|
||||
|
||||
if (
|
||||
cusProductsOnSub.length === 1 &&
|
||||
isTrialing({ cusProduct, now: attachParams.now })
|
||||
) {
|
||||
return { sub: undefined, cusProduct: undefined };
|
||||
}
|
||||
|
||||
const sub = await stripeCli.subscriptions.retrieve(subId, {
|
||||
expand: ["items.data.price.tiers"],
|
||||
});
|
||||
|
||||
return sub;
|
||||
return { sub, cusProduct };
|
||||
};
|
||||
|
||||
export const paramsToCurSub = async ({
|
||||
|
||||
@@ -288,7 +288,7 @@ const getChangeProductBranch = async ({
|
||||
let isUpgrade = isProductUpgrade({ prices1: curPrices, prices2: newPrices });
|
||||
|
||||
if (isUpgrade) {
|
||||
if (isTrialing(curMainProduct!)) {
|
||||
if (isTrialing({ cusProduct: curMainProduct!, now: attachParams.now })) {
|
||||
return AttachBranch.MainIsTrial;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,15 +172,6 @@ export const runAttachFunction = async ({
|
||||
});
|
||||
}
|
||||
|
||||
// // 1. Cancel future schedule before creating a new one...
|
||||
// await deleteCurrentScheduledProduct({
|
||||
// req,
|
||||
// org,
|
||||
// attachParams,
|
||||
// attachFunc: attachFunction,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// 2. If main is trial, cancel it...
|
||||
if (branch == AttachBranch.MainIsTrial) {
|
||||
await CusProductService.update({
|
||||
@@ -271,3 +262,12 @@ export const runAttachFunction = async ({
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// // 1. Cancel future schedule before creating a new one...
|
||||
// await deleteCurrentScheduledProduct({
|
||||
// req,
|
||||
// org,
|
||||
// attachParams,
|
||||
// attachFunc: attachFunction,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
import { getMergeCusProduct } from "../attachFunctions/addProductFlow/getMergeCusProduct.js";
|
||||
import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
|
||||
import { formatUnixToDate } from "@/utils/genUtils.js";
|
||||
import { isTrialing } from "../../cusProducts/cusProductUtils.js";
|
||||
|
||||
const getNextCycleItems = async ({
|
||||
newProduct,
|
||||
@@ -35,6 +37,7 @@ const getNextCycleItems = async ({
|
||||
withPrepaid,
|
||||
logger,
|
||||
config,
|
||||
trialEnds,
|
||||
}: {
|
||||
newProduct: FullProduct;
|
||||
attachParams: AttachParams;
|
||||
@@ -43,6 +46,7 @@ const getNextCycleItems = async ({
|
||||
withPrepaid?: boolean;
|
||||
logger: any;
|
||||
config: AttachConfig;
|
||||
trialEnds?: number | null;
|
||||
}) => {
|
||||
// 1. If one off, return null
|
||||
if (branch == AttachBranch.OneOff) return null;
|
||||
@@ -50,11 +54,15 @@ const getNextCycleItems = async ({
|
||||
// 2. If free trial
|
||||
let nextCycleAt = undefined;
|
||||
if (attachParams.freeTrial) {
|
||||
nextCycleAt =
|
||||
freeTrialToStripeTimestamp({
|
||||
freeTrial: attachParams.freeTrial,
|
||||
now: attachParams.now,
|
||||
})! * 1000;
|
||||
if (trialEnds) {
|
||||
nextCycleAt = trialEnds;
|
||||
} else {
|
||||
nextCycleAt =
|
||||
freeTrialToStripeTimestamp({
|
||||
freeTrial: attachParams.freeTrial,
|
||||
now: attachParams.now,
|
||||
})! * 1000;
|
||||
}
|
||||
} else if (anchorToUnix) {
|
||||
// Yearly one
|
||||
const largestInterval = getLargestInterval({ prices: newProduct.prices });
|
||||
@@ -113,17 +121,35 @@ export const getNewProductPreview = async ({
|
||||
// products: [newProduct],
|
||||
// config,
|
||||
// });
|
||||
const mergeSub = await getCustomerSub({ attachParams });
|
||||
const { sub: mergeSub, cusProduct: mergeCusProduct } = await getCustomerSub({
|
||||
attachParams,
|
||||
});
|
||||
|
||||
// console.log("Merge sub:", mergeSub);
|
||||
// console.log("Merge sub:", mergeSub?.id);
|
||||
// console.log("Merge cus product:", mergeCusProduct?.product.id);
|
||||
// console.log(
|
||||
// "Trial ends at:",
|
||||
// formatUnixToDate(mergeCusProduct?.trial_ends_at || 0)
|
||||
// );
|
||||
|
||||
let trialEnds = undefined;
|
||||
if (mergeSub) {
|
||||
const { start } = subToPeriodStartEnd({ sub: mergeSub });
|
||||
if (mergeCusProduct?.free_trial) {
|
||||
// 1. If still on trial
|
||||
if (isTrialing({ cusProduct: mergeCusProduct, now: attachParams.now })) {
|
||||
trialEnds = mergeCusProduct.trial_ends_at;
|
||||
attachParams.freeTrial = mergeCusProduct.free_trial;
|
||||
} else {
|
||||
attachParams.freeTrial = null;
|
||||
}
|
||||
}
|
||||
|
||||
const smallestInterval = getSmallestInterval({
|
||||
prices: newProduct.prices,
|
||||
excludeOneOff: true,
|
||||
});
|
||||
|
||||
if (smallestInterval) {
|
||||
anchorToUnix = addBillingIntervalUnix({
|
||||
unixTimestamp: start * 1000,
|
||||
@@ -154,6 +180,7 @@ export const getNewProductPreview = async ({
|
||||
withPrepaid,
|
||||
logger,
|
||||
config,
|
||||
trialEnds,
|
||||
});
|
||||
|
||||
// console.log("Due next cycle", dueNextCycle);
|
||||
|
||||
@@ -60,7 +60,7 @@ const getNextCycleAt = ({
|
||||
if (
|
||||
branch == AttachBranch.NewVersion &&
|
||||
curCusProduct &&
|
||||
isTrialing(curCusProduct)
|
||||
isTrialing({ cusProduct: curCusProduct, now })
|
||||
) {
|
||||
return {
|
||||
next_cycle_at: curCusProduct.trial_ends_at,
|
||||
|
||||
@@ -102,17 +102,6 @@ export const paramsToSubItems = async ({
|
||||
}) => {
|
||||
const { logger } = req;
|
||||
let curSubItems = sub?.items.data || [];
|
||||
// if (scheduleSet) {
|
||||
// const scheduleItems = scheduleSet.schedule.phases[0].items.map((item) => ({
|
||||
// id: item.price,
|
||||
// price: {
|
||||
// id: item.price,
|
||||
// },
|
||||
// quantity: item.quantity,
|
||||
// }));
|
||||
|
||||
// curSubItems = scheduleItems as any;
|
||||
// }
|
||||
|
||||
const itemSet = await getStripeSubItems2({
|
||||
attachParams,
|
||||
@@ -127,13 +116,6 @@ export const paramsToSubItems = async ({
|
||||
curSubItems,
|
||||
});
|
||||
|
||||
// console.log("New sub items:");
|
||||
// await logPhaseItems({
|
||||
// db: req.db,
|
||||
// items: newSubItems as any,
|
||||
// });
|
||||
// console.log("--------------------------------");
|
||||
|
||||
const allCusProducts = attachParams.customer.customer_products;
|
||||
|
||||
// 3. Remove items related to cus products to remove
|
||||
|
||||
@@ -23,6 +23,7 @@ import { handleRenewProduct } from "../attach/attachFunctions/handleRenewProduct
|
||||
import { getDefaultAttachConfig } from "../attach/attachUtils/getAttachConfig.js";
|
||||
import { handleScheduleFunction2 } from "../attach/attachFunctions/scheduleFlow/handleScheduleFlow2.js";
|
||||
import { handleUpgradeFlow } from "../attach/attachFunctions/upgradeFlow/handleUpgradeFlow.js";
|
||||
import { activateDefaultProduct } from "../cusProducts/cusProductUtils.js";
|
||||
|
||||
export const handleCancelProduct = async ({
|
||||
req,
|
||||
@@ -181,6 +182,14 @@ export const handleCancelProduct = async ({
|
||||
: ProrationBehavior.None,
|
||||
},
|
||||
});
|
||||
|
||||
// Activate default product
|
||||
await activateDefaultProduct({
|
||||
req,
|
||||
productGroup: cusProduct.product.group,
|
||||
fullCus,
|
||||
curCusProduct: cusProduct,
|
||||
});
|
||||
return;
|
||||
|
||||
// Expire product immediately
|
||||
|
||||
@@ -579,8 +579,16 @@ export const searchCusProducts = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const isTrialing = (cusProduct: FullCusProduct) => {
|
||||
return cusProduct.trial_ends_at && cusProduct.trial_ends_at > Date.now();
|
||||
export const isTrialing = ({
|
||||
cusProduct,
|
||||
now,
|
||||
}: {
|
||||
cusProduct: FullCusProduct;
|
||||
now?: number;
|
||||
}) => {
|
||||
return (
|
||||
cusProduct.trial_ends_at && cusProduct.trial_ends_at > (now || Date.now())
|
||||
);
|
||||
};
|
||||
|
||||
export const getMainCusProduct = async ({
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { FullCusProduct } from "@autumn/shared";
|
||||
|
||||
export const isCanceled = ({ cusProduct }: { cusProduct: FullCusProduct }) => {
|
||||
return cusProduct.canceled;
|
||||
};
|
||||
@@ -161,8 +161,8 @@ export const cusProductToSchedule = async ({
|
||||
}
|
||||
);
|
||||
|
||||
if (schedule.status == "canceled") {
|
||||
return null;
|
||||
if (schedule.status == "canceled" || schedule.status == "released") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return schedule;
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
subYears,
|
||||
} from "date-fns";
|
||||
import { UTCDate } from "@date-fns/utc";
|
||||
import { formatUnixToDate, formatUnixToDateTime } from "@/utils/genUtils.js";
|
||||
import { formatUnixToDateTime } from "@/utils/genUtils.js";
|
||||
|
||||
export const subtractBillingIntervalUnix = ({
|
||||
unixTimestamp,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isProductUpgrade,
|
||||
} from "../../productUtils.js";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { isCanceled } from "@/internal/customers/cusProducts/cusProductUtils/classifyCusProduct.js";
|
||||
|
||||
export const getAttachScenario = ({
|
||||
fullCus,
|
||||
@@ -19,6 +20,7 @@ export const getAttachScenario = ({
|
||||
let { curMainProduct, curScheduledProduct } = getExistingCusProducts({
|
||||
product: fullProduct,
|
||||
cusProducts: fullCus?.customer_products || [],
|
||||
internalEntityId: fullCus?.entity?.internal_id,
|
||||
});
|
||||
|
||||
if (!curMainProduct || fullProduct.is_add_on) return AttachScenario.New;
|
||||
@@ -29,7 +31,7 @@ export const getAttachScenario = ({
|
||||
|
||||
// 1. If current product is the same as the product, return active
|
||||
if (curMainProduct?.product.id == fullProduct.id) {
|
||||
if (curMainProduct.canceled_at != null) {
|
||||
if (isCanceled({ cusProduct: curMainProduct })) {
|
||||
return AttachScenario.Renew;
|
||||
} else return AttachScenario.Active;
|
||||
}
|
||||
|
||||
185
server/tests/core/cancel/cancel2.ts
Normal file
185
server/tests/core/cancel/cancel2.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
priceToInvoiceAmount,
|
||||
} from "@autumn/shared";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructArrearProratedItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import {
|
||||
addPrefixToProducts,
|
||||
getBasePrice,
|
||||
} from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { addWeeks } from "date-fns";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { isPrepaidPrice } from "@shared/utils/productUtils/priceUtils.js";
|
||||
import { isContUsePrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
||||
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
const wordsUsage = 300000;
|
||||
const ops = [
|
||||
{
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "cancel2";
|
||||
describe(`${chalk.yellowBright("cancel2: Testing cancel at period end (with usage)")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should run operations", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
try {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Operation failed: ${op.product.id}, index: ${index}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should track usage cancel, advance test clock and have correct invoice", async function () {
|
||||
const cus1 = await autumn.customers.get(customerId);
|
||||
const prod = cus1.products.find((p) => p.id === premium.id);
|
||||
const proration = {
|
||||
start: prod?.current_period_start!,
|
||||
end: prod?.current_period_end!,
|
||||
};
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: wordsUsage,
|
||||
});
|
||||
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
cancel_immediately: false,
|
||||
});
|
||||
|
||||
await advanceToNextInvoice({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
});
|
||||
|
||||
const wordsAmount = await getExpectedInvoiceTotal({
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
onlyIncludeArrear: true,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: wordsUsage,
|
||||
},
|
||||
],
|
||||
stripeCli,
|
||||
customerId,
|
||||
productId: premium.id,
|
||||
expectExpired: true,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const prods = cus.products.filter((p) => p.group == premium.group);
|
||||
expect(prods.length).to.equal(0);
|
||||
|
||||
expect(cus.invoices.length).to.equal(2);
|
||||
expect(cus.invoices[0].total).to.equal(wordsAmount);
|
||||
});
|
||||
});
|
||||
161
server/tests/core/cancel/cancel3.ts
Normal file
161
server/tests/core/cancel/cancel3.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
priceToInvoiceAmount,
|
||||
} from "@autumn/shared";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructArrearProratedItem,
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import {
|
||||
addPrefixToProducts,
|
||||
getBasePrice,
|
||||
} from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { addWeeks } from "date-fns";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { isPrepaidPrice } from "@shared/utils/productUtils/priceUtils.js";
|
||||
import { isContUsePrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
||||
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
let free = constructProduct({
|
||||
id: "free",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
}),
|
||||
],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
product: free,
|
||||
results: [{ product: free, status: CusProductStatus.Active }],
|
||||
skipSubCheck: true,
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "cancel3";
|
||||
describe(`${chalk.yellowBright("cancel3: Cancelling free product")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [free],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should run operations", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
try {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
skipSubCheck: op.skipSubCheck,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Operation failed: ${op.product.id}, index: ${index}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should track usage cancel, advance test clock and have correct invoice", async function () {
|
||||
const cus1 = await autumn.customers.get(customerId);
|
||||
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
cancel_immediately: true,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const prods = cus.products.filter((p) => p.group == free.group);
|
||||
expect(prods.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
152
server/tests/core/cancel/cancel4.ts
Normal file
152
server/tests/core/cancel/cancel4.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
let addOn = constructProduct({
|
||||
id: "free_add_on",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Credits })],
|
||||
type: "free",
|
||||
isAddOn: true,
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
skipSubCheck: true,
|
||||
},
|
||||
{
|
||||
product: addOn,
|
||||
results: [
|
||||
{ product: premium, status: CusProductStatus.Active },
|
||||
{ product: addOn, status: CusProductStatus.Active },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "cancel4";
|
||||
describe(`${chalk.yellowBright("cancel4: Cancelling free add on product")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [premium, addOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [premium, addOn],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should run operations", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
try {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
skipSubCheck: op.skipSubCheck,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Operation failed: ${op.product.id}, index: ${index}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should track usage cancel, advance test clock and have correct invoice", async function () {
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
cancel_immediately: true,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
expectProductAttached({
|
||||
customer: cus,
|
||||
product: premium,
|
||||
});
|
||||
|
||||
const products = cus.products.filter((p) => p.group == addOn.group);
|
||||
expect(products.length).to.equal(1);
|
||||
});
|
||||
});
|
||||
200
server/tests/core/cancel/mergedCancel1.test.ts
Normal file
200
server/tests/core/cancel/mergedCancel1.test.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
AttachBranch,
|
||||
AttachScenario,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
skipSubCheck: true,
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const cancels = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
shouldBeCanceled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const renewals = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
},
|
||||
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedCancel1";
|
||||
describe(`${chalk.yellowBright("mergedCancel1: Merged cancel")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should run operations", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
try {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
skipSubCheck: op.skipSubCheck,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Operation failed: ${op.product.id}, index: ${index}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should track usage cancel, advance test clock and have correct invoice", async function () {
|
||||
for (const cancel of cancels) {
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: cancel.product.id,
|
||||
entity_id: cancel.entityId,
|
||||
cancel_immediately: false,
|
||||
});
|
||||
|
||||
await expectSubToBeCorrect({
|
||||
db,
|
||||
customerId,
|
||||
org,
|
||||
env,
|
||||
shouldBeCanceled: cancel.shouldBeCanceled,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("should renew both entities", async function () {
|
||||
for (const renewal of renewals) {
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: renewal.product.id,
|
||||
entity_id: renewal.entityId,
|
||||
});
|
||||
|
||||
expect(checkout.product.scenario).to.equal(AttachScenario.Renew);
|
||||
expect(checkout.total).to.equal(0);
|
||||
|
||||
const attach = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: renewal.product.id,
|
||||
entity_id: renewal.entityId,
|
||||
});
|
||||
|
||||
await expectSubToBeCorrect({
|
||||
db,
|
||||
customerId,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
160
server/tests/core/cancel/mergedCancel2.test.ts
Normal file
160
server/tests/core/cancel/mergedCancel2.test.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
|
||||
// Premium, Premium
|
||||
// Cancel End, Cancel Immediately
|
||||
// Results: Canceled sub
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const cancels = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
cancelImmediately: true,
|
||||
shouldBeCanceled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedCancel2";
|
||||
describe(`${chalk.yellowBright("mergedCancel2: Testing cancel immediately")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should run operations", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
try {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Operation failed: ${op.product.id}, index: ${index}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should track usage cancel, advance test clock and have correct invoice", async function () {
|
||||
for (const cancel of cancels) {
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: cancel.product.id,
|
||||
entity_id: cancel.entityId,
|
||||
cancel_immediately: cancel.cancelImmediately ?? false,
|
||||
});
|
||||
|
||||
await expectSubToBeCorrect({
|
||||
db,
|
||||
customerId,
|
||||
org,
|
||||
env,
|
||||
shouldBeCanceled: cancel.shouldBeCanceled,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
165
server/tests/core/cancel/mergedCancel3.test.ts
Normal file
165
server/tests/core/cancel/mergedCancel3.test.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
AttachScenario,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
|
||||
// Premium, Premium
|
||||
// Cancel Immediately, Cancel Immediately
|
||||
// Results: No sub
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const cancels = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
cancelImmediately: true,
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
cancelImmediately: true,
|
||||
shouldBeCanceled: true,
|
||||
skipSubCheck: true,
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedCancel3";
|
||||
describe(`${chalk.yellowBright("mergedCancel3: Testing cancel immediately")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should run operations", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
try {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Operation failed: ${op.product.id}, index: ${index}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should track usage cancel, advance test clock and have correct invoice", async function () {
|
||||
for (const cancel of cancels) {
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: cancel.product.id,
|
||||
entity_id: cancel.entityId,
|
||||
cancel_immediately: cancel.cancelImmediately ?? false,
|
||||
});
|
||||
|
||||
if (cancel.skipSubCheck) continue;
|
||||
|
||||
await expectSubToBeCorrect({
|
||||
db,
|
||||
customerId,
|
||||
org,
|
||||
env,
|
||||
shouldBeCanceled: cancel.shouldBeCanceled,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,11 @@ import { expect } from "chai";
|
||||
import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
|
||||
// OPERATIONS:
|
||||
// Premium, Premium
|
||||
// Pro, Pro
|
||||
// Premium, Premium
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
|
||||
@@ -24,7 +24,12 @@ import { expect } from "chai";
|
||||
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
// OPERATIONS:
|
||||
// Premium
|
||||
// Free
|
||||
// Free, Premium
|
||||
// Free, Pro
|
||||
|
||||
let free = constructProduct({
|
||||
id: "free",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
|
||||
@@ -22,7 +22,10 @@ import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { expect } from "chai";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
// OPERATIONS:
|
||||
// Pro, Pro
|
||||
// Free, Premium
|
||||
|
||||
let free = constructProduct({
|
||||
id: "free",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
|
||||
@@ -25,7 +25,10 @@ import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { addMonths } from "date-fns";
|
||||
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
// OPERATIONS:
|
||||
// PremiumAnnual, Premium
|
||||
// PremiumAnnual, Pro
|
||||
|
||||
let premiumAnnual = constructProduct({
|
||||
id: "premiumAnnual",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
|
||||
@@ -24,7 +24,10 @@ import { expect } from "chai";
|
||||
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
// OPERATIONS:
|
||||
// Premium, Premium
|
||||
// Free, Free
|
||||
// Pro, Free
|
||||
|
||||
let pro = constructProduct({
|
||||
id: "pro",
|
||||
|
||||
@@ -21,7 +21,12 @@ import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUti
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
// OPERATIONS:
|
||||
// Growth, Growth
|
||||
// Free
|
||||
// Pro
|
||||
// Premium
|
||||
// Free
|
||||
|
||||
let pro = constructProduct({
|
||||
id: "pro",
|
||||
|
||||
154
server/tests/merged/trial/mergedTrial1.test.ts
Normal file
154
server/tests/merged/trial/mergedTrial1.test.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { addDays } from "date-fns";
|
||||
|
||||
// Premium, Premium
|
||||
// Cancel End, Cancel Immediately
|
||||
// Results: Canceled sub
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
// {
|
||||
// entityId: "2",
|
||||
// product: premium,
|
||||
// results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
// },
|
||||
];
|
||||
|
||||
const testCase = "mergedTrial1";
|
||||
describe(`${chalk.yellowBright("mergedTrial1: Testing trial")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach first trial, and advance clock past trial", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
entity_id: "1",
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addDays(new Date(), 2).getTime(),
|
||||
});
|
||||
|
||||
const entity1 = await autumn.entities.get(customerId, "1");
|
||||
const premium1 = entity1.products.find((p: any) => p.id == premium.id);
|
||||
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
entity_id: "2",
|
||||
});
|
||||
|
||||
const nextCycle = checkout.next_cycle;
|
||||
expect(nextCycle?.starts_at);
|
||||
expect(nextCycle?.starts_at).to.approximately(
|
||||
premium1?.current_period_end,
|
||||
60000
|
||||
); // 1 min
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
entity_id: "2",
|
||||
});
|
||||
|
||||
const entity2 = await autumn.entities.get(customerId, "2");
|
||||
const premium2 = entity2.products.find((p: any) => p.id == premium.id);
|
||||
expect(premium2?.status).to.equal(CusProductStatus.Trialing);
|
||||
expect(premium2?.current_period_end).to.approximately(
|
||||
premium1?.current_period_end,
|
||||
60000
|
||||
); // 1 min
|
||||
});
|
||||
});
|
||||
150
server/tests/merged/trial/mergedTrial2.test.ts
Normal file
150
server/tests/merged/trial/mergedTrial2.test.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { addDays } from "date-fns";
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
// {
|
||||
// entityId: "2",
|
||||
// product: premium,
|
||||
// results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
// },
|
||||
];
|
||||
|
||||
const testCase = "mergedTrial2";
|
||||
describe(`${chalk.yellowBright("mergedTrial2: Testing add second trial product after first trial ends")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach first trial, and advance clock past trial", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
entity_id: "1",
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addDays(new Date(), 8).getTime(),
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
const entity1 = await autumn.entities.get(customerId, "1");
|
||||
const premium1 = entity1.products.find((p: any) => p.id == premium.id);
|
||||
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
entity_id: "2",
|
||||
});
|
||||
|
||||
const nextCycle = checkout.next_cycle;
|
||||
expect(nextCycle?.starts_at);
|
||||
expect(nextCycle?.starts_at).to.approximately(
|
||||
premium1?.current_period_end,
|
||||
60000
|
||||
); // 1 min
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
entity_id: "2",
|
||||
});
|
||||
|
||||
const entity2 = await autumn.entities.get(customerId, "2");
|
||||
const premium2 = entity2.products.find((p: any) => p.id == premium.id);
|
||||
expect(premium2?.status).to.equal(CusProductStatus.Trialing);
|
||||
expect(premium2?.current_period_end).to.approximately(
|
||||
premium1?.current_period_end,
|
||||
60000
|
||||
); // 1 min
|
||||
});
|
||||
});
|
||||
143
server/tests/merged/trial/trial1.test.ts
Normal file
143
server/tests/merged/trial/trial1.test.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { addDays } from "date-fns";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
|
||||
// Premium, Premium
|
||||
// Cancel End, Cancel Immediately
|
||||
// Results: Canceled sub
|
||||
|
||||
let pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
let premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Trialing }],
|
||||
},
|
||||
// {
|
||||
// entityId: "2",
|
||||
// product: premium,
|
||||
// results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
// },
|
||||
];
|
||||
|
||||
const testCase = "trial1";
|
||||
describe(`${chalk.yellowBright("trial1: Testing trial")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [pro, premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach first trial, and advance clock past trial", async function () {
|
||||
for (const op of ops) {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
status: CusProductStatus.Trialing,
|
||||
});
|
||||
});
|
||||
|
||||
it("should advance test clock to before trial ends and attach premium", async function () {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addDays(new Date(), 2).getTime(),
|
||||
});
|
||||
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
});
|
||||
|
||||
expect(checkout.next_cycle?.starts_at).to.be.approximately(
|
||||
addDays(curUnix, 7).getTime(),
|
||||
60000
|
||||
);
|
||||
|
||||
expect(checkout.total).to.equal(0);
|
||||
});
|
||||
});
|
||||
163
server/tests/merged/upgrade/mergedUpgrade4.test.ts
Normal file
163
server/tests/merged/upgrade/mergedUpgrade4.test.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import {
|
||||
APIVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
|
||||
// OPERATIONS:
|
||||
// Pro, Pro
|
||||
// Free
|
||||
// Premium
|
||||
|
||||
let pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
let free = constructProduct({
|
||||
id: "free",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
const growth = constructProduct({
|
||||
id: "growth",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "growth",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "1",
|
||||
product: free,
|
||||
results: [
|
||||
{ product: pro, status: CusProductStatus.Active },
|
||||
{ product: free, status: CusProductStatus.Scheduled },
|
||||
],
|
||||
},
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedUpgrade4";
|
||||
describe(`${chalk.yellowBright("mergedUpgrade4: Upgrading when there's a cancel")}`, () => {
|
||||
let customerId = testCase;
|
||||
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, free, premium, growth],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: autumnJs,
|
||||
products: [pro, free, premium, growth],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should run operations", async function () {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entities,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
|
||||
for (const result of op.results) {
|
||||
const entity = await autumn.entities.get(customerId, op.entityId);
|
||||
expectProductAttached({
|
||||
customer: entity,
|
||||
product: result.product,
|
||||
status: result.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user