fix: send price:null when schedule plan customize removes base price

buildCustomizeBasePrice treated price=0 as falsy (!0 === true), silently
dropping the price override. The server then fell back to the original
plan price, causing free-first-phase schedules to show a non-zero total
and redirect to checkout instead of creating directly.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ayush Rodrigues
2026-05-14 12:31:18 +01:00
parent d72d75fc33
commit 152a749b18
2 changed files with 18 additions and 9 deletions

View File

@@ -76,7 +76,8 @@ export function buildCustomizeBasePrice({ items }: { items: ProductItem[] }) {
const priceItem = items.find(
(item) => item.price != null && !item.feature_id,
);
if (!priceItem?.price || !priceItem.interval) return undefined;
if (!priceItem || priceItem.price === 0) return null;
if (!priceItem.interval) return undefined;
return {
amount: priceItem.price,
interval: priceItem.interval,
@@ -100,10 +101,10 @@ export function buildCustomize({
if (!items) return undefined;
const planItems = buildCustomizeItems({ items, features });
const basePrice = buildCustomizeBasePrice({ items });
if (!planItems && !basePrice) return undefined;
if (!planItems && basePrice === undefined) return undefined;
return {
...(planItems ? { items: planItems } : {}),
...(basePrice ? { price: basePrice } : {}),
...(basePrice !== undefined ? { price: basePrice } : {}),
};
}

View File

@@ -87,10 +87,17 @@ describe("buildCustomizeBasePrice", () => {
expect(result!.interval).toBe("month");
});
test("returns undefined when no base price item exists", () => {
test("returns null when base price item was removed", () => {
const result = buildCustomizeBasePrice({ items: [featurePriceItem] });
expect(result).toBeUndefined();
expect(result).toBeNull();
});
test("returns null when price is zero (free)", () => {
const zeroPriceItem = { ...basePriceItem, price: 0 } as ProductItem;
const result = buildCustomizeBasePrice({ items: [zeroPriceItem] });
expect(result).toBeNull();
});
test("returns undefined when price item has no interval", () => {
@@ -118,7 +125,7 @@ describe("buildCustomizeBasePrice", () => {
test("ignores feature items that happen to have a price", () => {
const result = buildCustomizeBasePrice({ items: [featurePriceItem] });
expect(result).toBeUndefined();
expect(result).toBeNull();
});
});
@@ -207,11 +214,12 @@ describe("buildCustomize", () => {
expect(result).toBeUndefined();
});
test("returns items when only free features are present", () => {
test("returns items and price:null when base price was removed", () => {
const result = buildCustomize({ items: [freeFeatureItem], features });
expect(result).toBeDefined();
expect(result!.items).toBeDefined();
expect(result!.price).toBeNull();
});
test("returns price when only base price is customized", () => {
@@ -223,12 +231,12 @@ describe("buildCustomize", () => {
expect(result).not.toHaveProperty("items");
});
test("returns items when only feature items are customized", () => {
test("returns items and price:null when items have no base price", () => {
const result = buildCustomize({ items: [featurePriceItem], features });
expect(result).toBeDefined();
expect(result!.items).toBeDefined();
expect(result).not.toHaveProperty("price");
expect(result!.price).toBeNull();
});
test("returns both price and items when fully customized", () => {