fix: updating product with free features
This commit is contained in:
@@ -67,7 +67,10 @@ export const handleUpgradeSameInterval = async ({
|
|||||||
disableFreeTrial: config.disableTrial,
|
disableFreeTrial: config.disableTrial,
|
||||||
carryExistingUsages: config.carryUsage,
|
carryExistingUsages: config.carryUsage,
|
||||||
carryOverTrial: config.carryTrial,
|
carryOverTrial: config.carryTrial,
|
||||||
anchorToUnix: stripeSubs[0].current_period_end * 1000,
|
anchorToUnix:
|
||||||
|
stripeSubs.length > 0
|
||||||
|
? stripeSubs[0].current_period_end * 1000
|
||||||
|
: undefined,
|
||||||
logger,
|
logger,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
|
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||||
import { Decimal } from "decimal.js";
|
import { Decimal } from "decimal.js";
|
||||||
import { intervalsAreSame } from "../attachUtils/getAttachConfig.js";
|
import { intervalsAreSame } from "../attachUtils/getAttachConfig.js";
|
||||||
|
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||||
|
|
||||||
const getNextCycleAt = ({
|
const getNextCycleAt = ({
|
||||||
prices,
|
prices,
|
||||||
@@ -107,9 +108,10 @@ export const getUpgradeProductPreview = async ({
|
|||||||
// Get prorated amounts for new product
|
// Get prorated amounts for new product
|
||||||
const newProduct = attachParamsToProduct({ attachParams });
|
const newProduct = attachParamsToProduct({ attachParams });
|
||||||
const intervalsSame = intervalsAreSame({ attachParams });
|
const intervalsSame = intervalsAreSame({ attachParams });
|
||||||
const anchorToUnix = intervalsSame
|
const anchorToUnix =
|
||||||
? stripeSubs[0].current_period_end * 1000
|
intervalsSame && stripeSubs.length > 0
|
||||||
: undefined;
|
? stripeSubs[0].current_period_end * 1000
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const newPreviewItems = await getItemsForNewProduct({
|
const newPreviewItems = await getItemsForNewProduct({
|
||||||
newProduct,
|
newProduct,
|
||||||
@@ -122,21 +124,30 @@ export const getUpgradeProductPreview = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const lastInterval = getLastInterval({ prices: newProduct.prices });
|
const lastInterval = getLastInterval({ prices: newProduct.prices });
|
||||||
const nextCycleAt = getNextCycleAt({
|
|
||||||
prices: newProduct.prices,
|
|
||||||
stripeSubs,
|
|
||||||
willCycleReset: !intervalsSame,
|
|
||||||
interval: lastInterval,
|
|
||||||
now,
|
|
||||||
freeTrial: attachParams.freeTrial,
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextCycleItems = await getItemsForNewProduct({
|
let dueNextCycle = undefined;
|
||||||
newProduct,
|
if (!isFreeProduct(newProduct.prices)) {
|
||||||
attachParams,
|
const nextCycleAt = getNextCycleAt({
|
||||||
interval: attachParams.freeTrial ? undefined : lastInterval,
|
prices: newProduct.prices,
|
||||||
logger,
|
stripeSubs,
|
||||||
});
|
willCycleReset: !intervalsSame,
|
||||||
|
interval: lastInterval,
|
||||||
|
now,
|
||||||
|
freeTrial: attachParams.freeTrial,
|
||||||
|
});
|
||||||
|
|
||||||
|
let nextCycleItems = await getItemsForNewProduct({
|
||||||
|
newProduct,
|
||||||
|
attachParams,
|
||||||
|
interval: attachParams.freeTrial ? undefined : lastInterval,
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
dueNextCycle = {
|
||||||
|
line_items: nextCycleItems,
|
||||||
|
due_at: nextCycleAt.next_cycle_at,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let items = [...curPreviewItems, ...newPreviewItems];
|
let items = [...curPreviewItems, ...newPreviewItems];
|
||||||
|
|
||||||
@@ -175,7 +186,7 @@ export const getUpgradeProductPreview = async ({
|
|||||||
|
|
||||||
if (branch == AttachBranch.UpdatePrepaidQuantity) {
|
if (branch == AttachBranch.UpdatePrepaidQuantity) {
|
||||||
items = items.filter((item) => item.usage_model == UsageModel.Prepaid);
|
items = items.filter((item) => item.usage_model == UsageModel.Prepaid);
|
||||||
nextCycleItems = nextCycleItems.filter(
|
dueNextCycle!.line_items = dueNextCycle!.line_items.filter(
|
||||||
(item) => item.usage_model == UsageModel.Prepaid,
|
(item) => item.usage_model == UsageModel.Prepaid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -197,10 +208,7 @@ export const getUpgradeProductPreview = async ({
|
|||||||
return {
|
return {
|
||||||
currency: attachParams.org.default_currency,
|
currency: attachParams.org.default_currency,
|
||||||
due_today: dueToday,
|
due_today: dueToday,
|
||||||
due_next_cycle: {
|
due_next_cycle: dueNextCycle,
|
||||||
line_items: nextCycleItems,
|
|
||||||
due_at: nextCycleAt.next_cycle_at,
|
|
||||||
},
|
|
||||||
|
|
||||||
options,
|
options,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import { handleRequestError } from "@/utils/errorUtils.js";
|
|||||||
|
|
||||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||||
import RecaseError from "@/utils/errorUtils.js";
|
import RecaseError from "@/utils/errorUtils.js";
|
||||||
import {
|
import { ErrCode, FullCustomerEntitlement } from "@autumn/shared";
|
||||||
ErrCode,
|
|
||||||
FullCustomerEntitlement,
|
|
||||||
FullCusEntWithProduct,
|
|
||||||
} from "@autumn/shared";
|
|
||||||
import { Decimal } from "decimal.js";
|
import { Decimal } from "decimal.js";
|
||||||
import { StatusCodes } from "http-status-codes";
|
import { StatusCodes } from "http-status-codes";
|
||||||
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
|
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
|
||||||
|
|||||||
@@ -173,15 +173,30 @@ export const itemsAreSame = ({
|
|||||||
// 1. If feature item
|
// 1. If feature item
|
||||||
let same = false;
|
let same = false;
|
||||||
let pricesChanged = false;
|
let pricesChanged = false;
|
||||||
|
|
||||||
if (isFeatureItem(item1)) {
|
if (isFeatureItem(item1)) {
|
||||||
|
if (!isFeatureItem(item2)) {
|
||||||
|
return {
|
||||||
|
same: false,
|
||||||
|
pricesChanged: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
same = featureItemsAreSame({
|
same = featureItemsAreSame({
|
||||||
item1: FeatureItemSchema.parse(item1),
|
item1: FeatureItemSchema.parse(item1),
|
||||||
item2: FeatureItemSchema.parse(item2),
|
item2: item2 as FeatureItem,
|
||||||
});
|
});
|
||||||
pricesChanged = false;
|
pricesChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFeaturePriceItem(item1)) {
|
if (isFeaturePriceItem(item1)) {
|
||||||
|
if (!isFeaturePriceItem(item2)) {
|
||||||
|
return {
|
||||||
|
same: false,
|
||||||
|
pricesChanged: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const { same: same_, pricesChanged: pricesChanged_ } =
|
const { same: same_, pricesChanged: pricesChanged_ } =
|
||||||
featurePriceItemsAreSame({
|
featurePriceItemsAreSame({
|
||||||
item1: FeaturePriceItemSchema.parse(item1),
|
item1: FeaturePriceItemSchema.parse(item1),
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export const toFeaturePriceItem = ({
|
|||||||
ent.feature.config?.usage_type || ProductItemFeatureType.SingleUse,
|
ent.feature.config?.usage_type || ProductItemFeatureType.SingleUse,
|
||||||
|
|
||||||
included_usage: ent.allowance,
|
included_usage: ent.allowance,
|
||||||
|
|
||||||
interval: billingToItemInterval(config.interval!),
|
interval: billingToItemInterval(config.interval!),
|
||||||
|
|
||||||
price: null,
|
price: null,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const getFeatureInvoiceDescription = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { singular, plural } = getSingularAndPlural({ feature });
|
const { singular, plural } = getSingularAndPlural({ feature });
|
||||||
|
|
||||||
const usageStr = numberWithCommas(usage);
|
const usageStr = numberWithCommas(Math.ceil(usage));
|
||||||
|
|
||||||
let result = "";
|
let result = "";
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,10 @@ import { AutumnProvider } from "autumn-js/react";
|
|||||||
import { useAuth } from "@clerk/clerk-react";
|
import { useAuth } from "@clerk/clerk-react";
|
||||||
|
|
||||||
export function MainLayout() {
|
export function MainLayout() {
|
||||||
|
const env = useEnv();
|
||||||
const { isLoaded: isUserLoaded, user } = useUser();
|
const { isLoaded: isUserLoaded, user } = useUser();
|
||||||
const { organization: org } = useOrganization();
|
const { organization: org } = useOrganization();
|
||||||
const { setActive } = useOrganizationList();
|
|
||||||
const { getToken } = useAuth();
|
const { getToken } = useAuth();
|
||||||
const env = useEnv();
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -85,13 +84,6 @@ export function MainLayout() {
|
|||||||
return <RedirectToSignIn />;
|
return <RedirectToSignIn />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 1. If not org, and memberships > 0, set org active
|
|
||||||
// if (!org && user.organizationMemberships.length > 0 && setActive) {
|
|
||||||
// setActive({
|
|
||||||
// organization: user.organizationMemberships[0].organization.id,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!org && !pathname.includes("/onboarding")) {
|
if (!org && !pathname.includes("/onboarding")) {
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
|
|||||||
Reference in New Issue
Block a user