diff --git a/server/src/external/stripe/webhookHandlers/handleInvoiceCreated/handleUsagePrices.ts b/server/src/external/stripe/webhookHandlers/handleInvoiceCreated/handleUsagePrices.ts index 1bc3d8c9e..2bbc8cfaf 100644 --- a/server/src/external/stripe/webhookHandlers/handleInvoiceCreated/handleUsagePrices.ts +++ b/server/src/external/stripe/webhookHandlers/handleInvoiceCreated/handleUsagePrices.ts @@ -129,21 +129,6 @@ export const handleUsagePrices = async ({ let ent = relatedCusEnt.entitlement; - let rolloverUpdate = getRolloverUpdates({ - cusEnt: relatedCusEnt, - nextResetAt: usageSub.current_period_end * 1000, - }); - - // console.log( - // "Rollover update received in handleUsagePrices:", - // rolloverUpdate.toInsert.map((rollover) => ({ - // id: rollover.id, - // balance: rollover.balance, - // entities: rollover.entities.map((entity) => `${entity.id}: ${entity.balance}`).join(", "), - // expires_at: rollover.expires_at ? new Date(rollover.expires_at).toISOString() : null, - // })) - // ); - let resetBalancesUpdate = getResetBalancesUpdate({ cusEnt: relatedCusEnt, allowance: ent.interval == EntInterval.Lifetime ? 0 : ent.allowance!, @@ -161,6 +146,11 @@ export const handleUsagePrices = async ({ }, }); + let rolloverUpdate = getRolloverUpdates({ + cusEnt: relatedCusEnt, + nextResetAt: usageSub.current_period_end * 1000, + }); + if (rolloverUpdate?.toInsert && rolloverUpdate.toInsert.length > 0) { await RolloverService.insert({ db, diff --git a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts index e3c0eff95..be62a6a41 100644 --- a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts +++ b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/balancesToFeatureResponse.ts @@ -3,6 +3,7 @@ import { CusEntResponse, CusEntResponseSchema, CusEntResponseV2, + CusRollover, Feature, FeatureType, FullCustomerEntitlement, @@ -66,7 +67,9 @@ export const featuresToObject = ({ usageLimit = undefined; } - featureObject[featureId] = { + // console.log(`Feature ${featureId} list:`, relatedEnts); + + let cusFeature: CusEntResponseV2 = { id: featureId, name: feature.name, type: featureType, @@ -79,7 +82,6 @@ export const featuresToObject = ({ next_reset_at: getEarliestNextResetAt(relatedEnts), interval: relatedEnts.length == 1 ? relatedEnts[0].interval : "multiple", overage_allowed: relatedEnts.some((e) => e.overage_allowed), - // rollovers: relatedEnts.flatMap((e) => e.rollovers), breakdown: !unlimited && relatedEnts.length > 1 ? relatedEnts.map((e) => ({ @@ -96,7 +98,13 @@ export const featuresToObject = ({ credit_amount: s.credit_amount, })) : undefined, + + rollovers: relatedEnts + .flatMap((e) => e.rollovers) + .filter(notNullish) as CusRollover[], }; + + featureObject[featureId] = cusFeature; } return featureObject; diff --git a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusBalances.ts b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusBalances.ts index 26aa94996..4e7c94645 100644 --- a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusBalances.ts +++ b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusBalances.ts @@ -69,6 +69,100 @@ export const getV1EntitlementsRes = ({ return res; }; +export const getRolloverFields = ({ + cusEnt, + entityId, +}: { + cusEnt: FullCustomerEntitlement; + entityId?: string; +}) => { + let hasRollover = notNullish(cusEnt.entitlement.rollover); + if (!hasRollover) { + return null; + } + + if (cusEnt.entitlement.entity_feature_id) { + if (entityId) { + return cusEnt.rollovers.reduce( + (acc, rollover) => { + if (rollover.entities[entityId]) { + return { + balance: acc.balance + rollover.entities[entityId].balance, + usage: acc.usage + rollover.entities[entityId].usage, + rollovers: [ + ...acc.rollovers, + { + balance: rollover.entities[entityId].balance, + usage: rollover.entities[entityId].usage, + expires_at: rollover.expires_at, + }, + ], + }; + } + return acc; + }, + { + balance: 0, + usage: 0, + rollovers: [] as any[], + } + ); + } else { + return cusEnt.rollovers.reduce( + (acc, rollover) => { + let newBalance = 0; + let newUsage = 0; + + for (const entityId in rollover.entities) { + newBalance += rollover.entities[entityId].balance; + newUsage += rollover.entities[entityId].usage; + } + + return { + balance: acc.balance + newBalance, + usage: acc.usage + newUsage, + rollovers: [ + ...acc.rollovers, + { + balance: newBalance, + usage: newUsage, + expires_at: rollover.expires_at, + }, + ], + }; + }, + { + balance: 0, + usage: 0, + rollovers: [] as any[], + } + ); + } + } else { + return cusEnt.rollovers.reduce( + (acc, rollover) => { + return { + balance: acc.balance + rollover.balance, + usage: acc.usage + rollover.usage, + rollovers: [ + ...acc.rollovers, + { + balance: rollover.balance, + usage: rollover.usage, + expires_at: rollover.expires_at, + }, + ], + }; + }, + { + balance: 0, + usage: 0, + rollovers: [] as any[], + } + ); + } +}; + // IMPORTANT FUNCTION export const getCusBalances = async ({ cusEntsWithCusProduct, @@ -170,11 +264,6 @@ export const getCusBalances = async ({ }); data[key].balance += balance || 0; - // let totalRolloverBalance = cusEnt.rollovers.reduce((acc, rollover) => { - // return acc + (rollover.balance || 0); - // }, 0); - - // data[key].balance += totalRolloverBalance; data[key].adjustment += adjustment || 0; let total = @@ -188,6 +277,17 @@ export const getCusBalances = async ({ data[key].total += total; data[key].unused += unused || 0; + let rollover = getRolloverFields({ + cusEnt, + entityId: entity?.id, + }); + + if (rollover) { + data[key].balance += rollover.balance; + data[key].total += rollover.balance + rollover.usage; + data[key].rollovers = rollover.rollovers; + } + if (org.config.api_version >= BREAK_API_VERSION) { if ( !data[key].next_reset_at || @@ -261,5 +361,9 @@ export const getCusBalances = async ({ }); } + // if (org.api_version == APIVersion.v1) { + + // } + return balances as CusFeatureBalance[]; }; diff --git a/shared/models/cusModels/cusResModels/cusFeatureResponse.ts b/shared/models/cusModels/cusResModels/cusFeatureResponse.ts index eeaaaea08..1caae1a8c 100644 --- a/shared/models/cusModels/cusResModels/cusFeatureResponse.ts +++ b/shared/models/cusModels/cusResModels/cusFeatureResponse.ts @@ -2,6 +2,11 @@ import { z } from "zod"; import { EntInterval } from "../../productModels/entModels/entEnums.js"; import { ProductItemFeatureType } from "../../productV2Models/productItemModels/productItemModels.js"; +export const CusRolloverSchema = z.object({ + balance: z.number(), + expires_at: z.number(), +}); + export const CusEntResponseSchema = z.object({ feature_id: z.string(), interval: z.nativeEnum(EntInterval).nullish(), @@ -12,6 +17,7 @@ export const CusEntResponseSchema = z.object({ next_reset_at: z.number().nullish(), overage_allowed: z.boolean().nullish(), usage_limit: z.number().nullish(), + rollovers: z.array(CusRolloverSchema).nullish(), }); export const CoreCusFeatureResponseSchema = z.object({ @@ -31,7 +37,7 @@ export const CoreCusFeatureResponseSchema = z.object({ usage: z.number().nullish(), included_usage: z.number().nullish(), next_reset_at: z.number().nullish(), - }), + }) ) .nullish(), credit_schema: z @@ -39,11 +45,12 @@ export const CoreCusFeatureResponseSchema = z.object({ z.object({ feature_id: z.string(), credit_amount: z.number(), - }), + }) ) .nullish(), usage_limit: z.number().nullish(), + rollovers: z.array(CusRolloverSchema).nullish(), }); export const CusEntResponseV2Schema = z @@ -68,3 +75,4 @@ export const CheckResponseSchema = z export type CusEntResponse = z.infer; export type CusEntResponseV2 = z.infer; export type CheckResponse = z.infer; +export type CusRollover = z.infer;