From 515d46f010712893e904de36396520552ff4b278 Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Tue, 27 Jan 2026 16:19:58 -0800 Subject: [PATCH] fix payment data integrity --- apps/backend/src/lib/payments.test.tsx | 58 ++++++++++++++++++++++++++ apps/backend/src/lib/payments.tsx | 7 ++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/apps/backend/src/lib/payments.test.tsx b/apps/backend/src/lib/payments.test.tsx index ca61dca576..d65664de04 100644 --- a/apps/backend/src/lib/payments.test.tsx +++ b/apps/backend/src/lib/payments.test.tsx @@ -1182,6 +1182,64 @@ describe('getSubscriptions - defaults behavior', () => { expect(ids).toContain('freeUngrouped'); }); + it('includes include-by-default product when only inactive subscription exists in line', async () => { + const tenancy = createMockTenancy({ + items: {}, + productLines: { g1: { displayName: 'G1', customerType: 'user' } }, + products: { + freeG1: { + displayName: 'Free', + productLineId: 'g1', + customerType: 'user', + freeTrial: undefined, + serverOnly: false, + stackable: false, + prices: 'include-by-default', + includedItems: {}, + isAddOnTo: false, + }, + paidG1: { + displayName: 'Paid', + productLineId: 'g1', + customerType: 'user', + freeTrial: undefined, + serverOnly: false, + stackable: false, + prices: {}, + includedItems: {}, + isAddOnTo: false, + }, + }, + }); + + const prisma = createMockPrisma({ + subscription: { + findMany: async () => [{ + id: 'sub-1', + productId: 'paidG1', + product: tenancy.config.payments.products['paidG1'], + quantity: 1, + currentPeriodStart: new Date('2025-01-01T00:00:00.000Z'), + currentPeriodEnd: new Date('2025-02-01T00:00:00.000Z'), + cancelAtPeriodEnd: false, + status: 'canceled', + createdAt: new Date('2025-01-01T00:00:00.000Z'), + stripeSubscriptionId: null, + }], + }, + } as any); + + const subs = await getSubscriptions({ + prisma, + tenancy, + customerType: 'user', + customerId: 'user-1', + }); + + const ids = subs.map(s => s.productId); + expect(ids).toContain('freeG1'); + }); + it('throws error when multiple include-by-default products exist in same line', async () => { const tenancy = createMockTenancy({ items: {}, diff --git a/apps/backend/src/lib/payments.tsx b/apps/backend/src/lib/payments.tsx index 3f426217e0..f9445ec4fe 100644 --- a/apps/backend/src/lib/payments.tsx +++ b/apps/backend/src/lib/payments.tsx @@ -332,7 +332,7 @@ export async function getSubscriptions(options: { const productLinesWithDbSubscriptions = new Set(); for (const s of dbSubscriptions) { const product = s.product as yup.InferType; - subscriptions.push({ + const subscription: Subscription = { id: s.id, productId: s.productId, product, @@ -343,8 +343,9 @@ export async function getSubscriptions(options: { status: s.status, createdAt: s.createdAt, stripeSubscriptionId: s.stripeSubscriptionId, - }); - if (product.productLineId !== undefined) { + }; + subscriptions.push(subscription); + if (product.productLineId !== undefined && isActiveSubscription(subscription)) { productLinesWithDbSubscriptions.add(product.productLineId); } }