diff --git a/server/src/internal/customers/attach/attachFunctions/upgradeSameIntFlow/handleUpgradeSameInt.ts b/server/src/internal/customers/attach/attachFunctions/upgradeSameIntFlow/handleUpgradeSameInt.ts index 9a9b3cb18..188676d9e 100644 --- a/server/src/internal/customers/attach/attachFunctions/upgradeSameIntFlow/handleUpgradeSameInt.ts +++ b/server/src/internal/customers/attach/attachFunctions/upgradeSameIntFlow/handleUpgradeSameInt.ts @@ -67,7 +67,10 @@ export const handleUpgradeSameInterval = async ({ disableFreeTrial: config.disableTrial, carryExistingUsages: config.carryUsage, carryOverTrial: config.carryTrial, - anchorToUnix: stripeSubs[0].current_period_end * 1000, + anchorToUnix: + stripeSubs.length > 0 + ? stripeSubs[0].current_period_end * 1000 + : undefined, logger, }); diff --git a/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts b/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts index 6f82f5948..59954f423 100644 --- a/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts +++ b/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts @@ -26,6 +26,7 @@ import { import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js"; import { Decimal } from "decimal.js"; import { intervalsAreSame } from "../attachUtils/getAttachConfig.js"; +import { isFreeProduct } from "@/internal/products/productUtils.js"; const getNextCycleAt = ({ prices, @@ -107,9 +108,10 @@ export const getUpgradeProductPreview = async ({ // Get prorated amounts for new product const newProduct = attachParamsToProduct({ attachParams }); const intervalsSame = intervalsAreSame({ attachParams }); - const anchorToUnix = intervalsSame - ? stripeSubs[0].current_period_end * 1000 - : undefined; + const anchorToUnix = + intervalsSame && stripeSubs.length > 0 + ? stripeSubs[0].current_period_end * 1000 + : undefined; const newPreviewItems = await getItemsForNewProduct({ newProduct, @@ -122,21 +124,30 @@ export const getUpgradeProductPreview = async ({ }); 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({ - newProduct, - attachParams, - interval: attachParams.freeTrial ? undefined : lastInterval, - logger, - }); + let dueNextCycle = undefined; + if (!isFreeProduct(newProduct.prices)) { + const nextCycleAt = getNextCycleAt({ + prices: newProduct.prices, + 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]; @@ -175,7 +186,7 @@ export const getUpgradeProductPreview = async ({ if (branch == AttachBranch.UpdatePrepaidQuantity) { 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, ); } @@ -197,10 +208,7 @@ export const getUpgradeProductPreview = async ({ return { currency: attachParams.org.default_currency, due_today: dueToday, - due_next_cycle: { - line_items: nextCycleItems, - due_at: nextCycleAt.next_cycle_at, - }, + due_next_cycle: dueNextCycle, options, }; diff --git a/server/src/internal/customers/handlers/handleUpdateEntitlement.ts b/server/src/internal/customers/handlers/handleUpdateEntitlement.ts index 55b8ce169..c34c5a742 100644 --- a/server/src/internal/customers/handlers/handleUpdateEntitlement.ts +++ b/server/src/internal/customers/handlers/handleUpdateEntitlement.ts @@ -2,11 +2,7 @@ import { handleRequestError } from "@/utils/errorUtils.js"; import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js"; import RecaseError from "@/utils/errorUtils.js"; -import { - ErrCode, - FullCustomerEntitlement, - FullCusEntWithProduct, -} from "@autumn/shared"; +import { ErrCode, FullCustomerEntitlement } from "@autumn/shared"; import { Decimal } from "decimal.js"; import { StatusCodes } from "http-status-codes"; import { adjustAllowance } from "@/trigger/adjustAllowance.js"; diff --git a/server/src/internal/products/product-items/compareItemUtils.ts b/server/src/internal/products/product-items/compareItemUtils.ts index d9a372a78..ec66b0f6f 100644 --- a/server/src/internal/products/product-items/compareItemUtils.ts +++ b/server/src/internal/products/product-items/compareItemUtils.ts @@ -173,15 +173,30 @@ export const itemsAreSame = ({ // 1. If feature item let same = false; let pricesChanged = false; + if (isFeatureItem(item1)) { + if (!isFeatureItem(item2)) { + return { + same: false, + pricesChanged: true, + }; + } + same = featureItemsAreSame({ item1: FeatureItemSchema.parse(item1), - item2: FeatureItemSchema.parse(item2), + item2: item2 as FeatureItem, }); pricesChanged = false; } if (isFeaturePriceItem(item1)) { + if (!isFeaturePriceItem(item2)) { + return { + same: false, + pricesChanged: true, + }; + } + const { same: same_, pricesChanged: pricesChanged_ } = featurePriceItemsAreSame({ item1: FeaturePriceItemSchema.parse(item1), diff --git a/server/src/internal/products/product-items/mapToItem.ts b/server/src/internal/products/product-items/mapToItem.ts index fc2772e2c..c787512fb 100644 --- a/server/src/internal/products/product-items/mapToItem.ts +++ b/server/src/internal/products/product-items/mapToItem.ts @@ -76,6 +76,7 @@ export const toFeaturePriceItem = ({ ent.feature.config?.usage_type || ProductItemFeatureType.SingleUse, included_usage: ent.allowance, + interval: billingToItemInterval(config.interval!), price: null, diff --git a/shared/utils/displayUtils.ts b/shared/utils/displayUtils.ts index 073960ea6..45b7985bf 100644 --- a/shared/utils/displayUtils.ts +++ b/shared/utils/displayUtils.ts @@ -91,7 +91,7 @@ export const getFeatureInvoiceDescription = ({ }) => { const { singular, plural } = getSingularAndPlural({ feature }); - const usageStr = numberWithCommas(usage); + const usageStr = numberWithCommas(Math.ceil(usage)); let result = ""; diff --git a/vite/src/app/layout.tsx b/vite/src/app/layout.tsx index 209473f08..de0cb15a1 100644 --- a/vite/src/app/layout.tsx +++ b/vite/src/app/layout.tsx @@ -21,11 +21,10 @@ import { AutumnProvider } from "autumn-js/react"; import { useAuth } from "@clerk/clerk-react"; export function MainLayout() { + const env = useEnv(); const { isLoaded: isUserLoaded, user } = useUser(); const { organization: org } = useOrganization(); - const { setActive } = useOrganizationList(); const { getToken } = useAuth(); - const env = useEnv(); const { pathname } = useLocation(); const navigate = useNavigate(); @@ -85,13 +84,6 @@ export function MainLayout() { return ; } - // // 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")) { return (