Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
124 changes: 123 additions & 1 deletion apps/backend/src/lib/payments.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { PrismaClientTransaction } from '@/prisma-client';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getItemQuantityForCustomer, validatePurchaseSession } from './payments';
import { getItemQuantityForCustomer, getSubscriptions, validatePurchaseSession } from './payments';
import type { Tenancy } from './tenancies';

function createMockPrisma(overrides: Partial<PrismaClientTransaction> = {}): PrismaClientTransaction {
Expand Down Expand Up @@ -717,6 +717,38 @@ describe('getItemQuantityForCustomer - subscriptions', () => {
expect(qty).toBe(8);
vi.useRealTimers();
});

it('ungrouped include-by-default provides item quantity without db subscription', async () => {
const now = new Date('2025-02-10T00:00:00.000Z');
vi.setSystemTime(now);
const itemId = 'defaultItemUngrouped';

const tenancy = createMockTenancy({
items: { [itemId]: { displayName: 'UDF', customerType: 'user' } },
catalogs: {},
products: {
offFreeUngrouped: {
displayName: 'Free Ungrouped',
catalogId: undefined,
customerType: 'user',
freeTrial: undefined,
serverOnly: false,
stackable: false,
prices: 'include-by-default',
includedItems: { [itemId]: { quantity: 5, repeat: 'never', expires: 'when-purchase-expires' } },
isAddOnTo: false,
},
},
});

const prisma = createMockPrisma({
subscription: { findMany: async () => [] },
} as any);

const qty = await getItemQuantityForCustomer({ prisma, tenancy, itemId, customerId: 'u1', customerType: 'user' });
expect(qty).toBe(5);
vi.useRealTimers();
});
});


Expand Down Expand Up @@ -933,3 +965,93 @@ describe('combined sources - one-time purchases + manual changes + subscriptions
});
});


describe('getSubscriptions - defaults behavior', () => {
it('includes ungrouped include-by-default offers in subscriptions', async () => {
const tenancy = createMockTenancy({
items: {},
catalogs: {},
products: {
freeUngrouped: {
displayName: 'Free',
catalogId: undefined,
customerType: 'custom',
freeTrial: undefined,
serverOnly: false,
stackable: false,
prices: 'include-by-default',
includedItems: {},
isAddOnTo: false,
},
paidUngrouped: {
displayName: 'Paid',
catalogId: undefined,
customerType: 'custom',
freeTrial: undefined,
serverOnly: false,
stackable: false,
prices: {},
includedItems: {},
isAddOnTo: false,
},
},
});

const prisma = createMockPrisma({
subscription: { findMany: async () => [] },
} as any);

const subs = await getSubscriptions({
prisma,
tenancy,
customerType: 'custom',
customerId: 'c-1',
});

const ids = subs.map(s => s.productId);
expect(ids).toContain('freeUngrouped');
});

it('throws error when multiple include-by-default offers exist in same group', async () => {
const tenancy = createMockTenancy({
items: {},
catalogs: { g1: { displayName: 'G1' } },
products: {
g1FreeA: {
displayName: 'Free A',
catalogId: 'g1',
customerType: 'custom',
freeTrial: undefined,
serverOnly: false,
stackable: false,
prices: 'include-by-default',
includedItems: {},
isAddOnTo: false,
},
g1FreeB: {
displayName: 'Free B',
catalogId: 'g1',
customerType: 'custom',
freeTrial: undefined,
serverOnly: false,
stackable: false,
prices: 'include-by-default',
includedItems: {},
isAddOnTo: false,
},
},
});

const prisma = createMockPrisma({
subscription: { findMany: async () => [] },
} as any);

await getSubscriptions({
prisma,
tenancy,
customerType: 'custom',
customerId: 'c-1',
});
});
});

17 changes: 17 additions & 0 deletions apps/backend/src/lib/payments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,23 @@ export async function getSubscriptions(options: {
}
}

const ungroupedDefaults = typedEntries(products).filter(([id, product]) => (
product.catalogId === undefined && product.prices === "include-by-default" && !subscriptions.some((s) => s.productId === id)
));
for (const [productId, product] of ungroupedDefaults) {
subscriptions.push({
id: null,
productId,
product,
quantity: 1,
currentPeriodStart: DEFAULT_PRODUCT_START_DATE,
currentPeriodEnd: null,
status: SubscriptionStatus.active,
createdAt: DEFAULT_PRODUCT_START_DATE,
stripeSubscriptionId: null,
});
}

return subscriptions;
}

Expand Down