@rpcbase/billing 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ import { SubscriptionIntervalUnit } from './types';
2
+ export declare const addBillingInterval: (value: Date, intervalUnit: SubscriptionIntervalUnit, intervalCount?: number) => Date;
3
+ export declare const resolveBillingPeriod: (params: {
4
+ billingAnchor: Date;
5
+ intervalUnit: SubscriptionIntervalUnit;
6
+ intervalCount?: number;
7
+ asOf: Date;
8
+ }) => {
9
+ start: Date;
10
+ end: Date;
11
+ };
12
+ //# sourceMappingURL=calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../src/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AA8BvD,eAAO,MAAM,kBAAkB,GAC7B,OAAO,IAAI,EACX,cAAc,wBAAwB,EACtC,sBAAiB,KAChB,IAYF,CAAA;AAiBD,eAAO,MAAM,oBAAoB,GAAI,QAAQ;IAC3C,aAAa,EAAE,IAAI,CAAA;IACnB,YAAY,EAAE,wBAAwB,CAAA;IACtC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,IAAI,CAAA;CACX,KAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAyB3B,CAAA"}
@@ -0,0 +1,7 @@
1
+ export * from './calendar';
2
+ export * from './proration';
3
+ export * from './projection';
4
+ export * from './segments';
5
+ export * from './storage';
6
+ export * from './types';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,332 @@
1
+ const assertValidDate$2 = (value, field) => {
2
+ const date = new Date(value);
3
+ if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
4
+ return date;
5
+ };
6
+ const normalizeIntervalCount = (value) => {
7
+ if (!Number.isInteger(value) || value < 1) throw new Error("intervalCount must be a positive integer");
8
+ return value;
9
+ };
10
+ const getDaysInUtcMonth = (year, month) => new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
11
+ const createClampedUtcDate = (source, year, month) => {
12
+ const day = Math.min(source.getUTCDate(), getDaysInUtcMonth(year, month));
13
+ return new Date(Date.UTC(year, month, day, source.getUTCHours(), source.getUTCMinutes(), source.getUTCSeconds(), source.getUTCMilliseconds()));
14
+ };
15
+ const addBillingInterval = (value, intervalUnit, intervalCount = 1) => {
16
+ const date = assertValidDate$2(value, "value");
17
+ const count = normalizeIntervalCount(intervalCount);
18
+ if (intervalUnit === "year") {
19
+ return createClampedUtcDate(date, date.getUTCFullYear() + count, date.getUTCMonth());
20
+ }
21
+ const absoluteMonth = date.getUTCFullYear() * 12 + date.getUTCMonth() + count;
22
+ const year = Math.floor(absoluteMonth / 12);
23
+ const month = (absoluteMonth % 12 + 12) % 12;
24
+ return createClampedUtcDate(date, year, month);
25
+ };
26
+ const getEstimatedPeriodIndex = (anchor, asOf, intervalUnit, intervalCount) => {
27
+ if (asOf.getTime() <= anchor.getTime()) return 0;
28
+ const rawDifference = intervalUnit === "year" ? asOf.getUTCFullYear() - anchor.getUTCFullYear() : (asOf.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + asOf.getUTCMonth() - anchor.getUTCMonth();
29
+ return Math.max(0, Math.floor(rawDifference / intervalCount));
30
+ };
31
+ const resolveBillingPeriod = (params) => {
32
+ const anchor = assertValidDate$2(params.billingAnchor, "billingAnchor");
33
+ const asOf = assertValidDate$2(params.asOf, "asOf");
34
+ const intervalCount = normalizeIntervalCount(params.intervalCount ?? 1);
35
+ let periodIndex = getEstimatedPeriodIndex(anchor, asOf, params.intervalUnit, intervalCount);
36
+ let start = addBillingInterval(anchor, params.intervalUnit, Math.max(1, periodIndex * intervalCount));
37
+ if (periodIndex === 0) start = new Date(anchor);
38
+ while (start.getTime() > asOf.getTime() && periodIndex > 0) {
39
+ periodIndex -= 1;
40
+ start = periodIndex === 0 ? new Date(anchor) : addBillingInterval(anchor, params.intervalUnit, periodIndex * intervalCount);
41
+ }
42
+ let end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount);
43
+ while (end.getTime() <= asOf.getTime()) {
44
+ periodIndex += 1;
45
+ start = new Date(end);
46
+ end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount);
47
+ }
48
+ return {
49
+ start,
50
+ end
51
+ };
52
+ };
53
+ const assertValidDate$1 = (value, field) => {
54
+ const date = new Date(value);
55
+ if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
56
+ return date;
57
+ };
58
+ const calculateProratedAmount = (params) => {
59
+ if (!Number.isSafeInteger(params.unitAmount) || params.unitAmount < 0) {
60
+ throw new Error("unitAmount must be a non-negative safe integer");
61
+ }
62
+ const segmentStart = assertValidDate$1(params.segmentStart, "segmentStart");
63
+ const segmentEnd = assertValidDate$1(params.segmentEnd, "segmentEnd");
64
+ const periodStart = assertValidDate$1(params.periodStart, "periodStart");
65
+ const periodEnd = assertValidDate$1(params.periodEnd, "periodEnd");
66
+ const periodDuration = periodEnd.getTime() - periodStart.getTime();
67
+ if (periodDuration <= 0) throw new Error("Billing period end must be after its start");
68
+ const overlapStart = Math.max(segmentStart.getTime(), periodStart.getTime());
69
+ const overlapEnd = Math.min(segmentEnd.getTime(), periodEnd.getTime());
70
+ const overlapDuration = Math.max(0, overlapEnd - overlapStart);
71
+ return Math.round(params.unitAmount * overlapDuration / periodDuration);
72
+ };
73
+ const assertValidDate = (value, field) => {
74
+ const date = new Date(value);
75
+ if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
76
+ return date;
77
+ };
78
+ const compareEvents = (left, right) => {
79
+ const effectiveDelta = left.effectiveAt.getTime() - right.effectiveAt.getTime();
80
+ if (effectiveDelta !== 0) return effectiveDelta;
81
+ const occurredDelta = left.occurredAt.getTime() - right.occurredAt.getTime();
82
+ if (occurredDelta !== 0) return occurredDelta;
83
+ return (left.id ?? "").localeCompare(right.id ?? "");
84
+ };
85
+ const sortSubscriptionEvents = (events) => events.map((event) => ({
86
+ ...event,
87
+ occurredAt: assertValidDate(event.occurredAt, "occurredAt"),
88
+ effectiveAt: assertValidDate(event.effectiveAt, "effectiveAt"),
89
+ billingAnchor: event.billingAnchor ? assertValidDate(event.billingAnchor, "billingAnchor") : void 0
90
+ })).sort(compareEvents);
91
+ const getEffectiveEvents = (events) => {
92
+ const ordered = sortSubscriptionEvents(events);
93
+ const supersededIds = new Set(ordered.map((event) => event.supersedesEventId).filter((eventId) => Boolean(eventId)));
94
+ const inferredSupersededEvents = /* @__PURE__ */ new Set();
95
+ for (const cancellation of ordered) {
96
+ if (cancellation.type !== "canceled" || cancellation.effectiveAt.getTime() <= cancellation.occurredAt.getTime() || cancellation.id && supersededIds.has(cancellation.id)) {
97
+ continue;
98
+ }
99
+ const laterResume = ordered.find((candidate) => candidate.type === "resumed" && candidate.occurredAt.getTime() > cancellation.occurredAt.getTime() && candidate.effectiveAt.getTime() <= cancellation.effectiveAt.getTime());
100
+ if (laterResume) inferredSupersededEvents.add(cancellation);
101
+ }
102
+ return ordered.filter((event) => !(event.id && supersededIds.has(event.id)) && !inferredSupersededEvents.has(event));
103
+ };
104
+ const assertSingleSubscription = (events) => {
105
+ const subscriptionIds = new Set(events.map((event) => event.subscriptionId));
106
+ if (subscriptionIds.size > 1) throw new Error("Events from multiple subscriptions cannot be projected together");
107
+ return events[0]?.subscriptionId ?? "";
108
+ };
109
+ const getStatusForEvent = (event, currentStatus) => {
110
+ if (event.toStatus) return event.toStatus;
111
+ if (event.type === "created" || event.type === "resumed") return "active";
112
+ if (event.type === "canceled") return "canceled";
113
+ return currentStatus;
114
+ };
115
+ const projectSubscription = (events, options = {}) => {
116
+ if (events.length === 0) return null;
117
+ const subscriptionId = assertSingleSubscription(events);
118
+ const ordered = getEffectiveEvents(events);
119
+ const asOf = assertValidDate(options.asOf ?? /* @__PURE__ */ new Date(), "asOf");
120
+ const applicable = ordered.filter((event) => event.effectiveAt.getTime() <= asOf.getTime());
121
+ if (applicable.length === 0) return null;
122
+ const firstEvent = applicable[0];
123
+ let type = firstEvent.subscriptionType ?? "primary";
124
+ let parentSubscriptionId = firstEvent.parentSubscriptionId ?? null;
125
+ let scope = firstEvent.scope ?? "tenant";
126
+ let scopeId = firstEvent.scopeId ?? null;
127
+ let planKey = "";
128
+ let priceId = null;
129
+ let status = "active";
130
+ let intervalUnit = firstEvent.toIntervalUnit ?? "month";
131
+ let intervalCount = firstEvent.toIntervalCount ?? 1;
132
+ let modules = [];
133
+ let billingAnchor = firstEvent.billingAnchor ?? firstEvent.effectiveAt;
134
+ let provider = null;
135
+ let canceledAt = null;
136
+ for (const event of applicable) {
137
+ type = event.subscriptionType ?? type;
138
+ parentSubscriptionId = event.parentSubscriptionId ?? parentSubscriptionId;
139
+ scope = event.scope ?? scope;
140
+ scopeId = event.scopeId ?? scopeId;
141
+ planKey = event.toPlanKey ?? planKey;
142
+ priceId = event.toPriceId ?? priceId;
143
+ status = getStatusForEvent(event, status);
144
+ intervalUnit = event.toIntervalUnit ?? intervalUnit;
145
+ intervalCount = event.toIntervalCount ?? intervalCount;
146
+ modules = event.toModules ? [...event.toModules] : modules;
147
+ billingAnchor = event.billingAnchor ?? billingAnchor;
148
+ provider = event.provider ?? provider;
149
+ if (event.type === "canceled") {
150
+ canceledAt = new Date(event.effectiveAt);
151
+ } else if (event.type === "created" || event.type === "resumed") {
152
+ canceledAt = null;
153
+ }
154
+ }
155
+ if (!planKey) return null;
156
+ if (!Number.isInteger(intervalCount) || intervalCount < 1) {
157
+ throw new Error("Subscription intervalCount must be a positive integer");
158
+ }
159
+ const futureEvents = ordered.filter((event) => event.effectiveAt.getTime() > asOf.getTime());
160
+ const scheduledCancellation = futureEvents.find((event) => event.type === "canceled") ?? null;
161
+ const scheduledPlanEvent = futureEvents.find((event) => event.type === "plan_changed" && event.toPlanKey) ?? null;
162
+ const scheduledPlan = scheduledPlanEvent?.toPlanKey ? {
163
+ planKey: scheduledPlanEvent.toPlanKey,
164
+ effectiveAt: new Date(scheduledPlanEvent.effectiveAt)
165
+ } : null;
166
+ const periodAsOf = canceledAt && canceledAt.getTime() <= asOf.getTime() ? new Date(canceledAt.getTime() - 1) : asOf;
167
+ const period = resolveBillingPeriod({
168
+ billingAnchor,
169
+ intervalUnit,
170
+ intervalCount,
171
+ asOf: periodAsOf
172
+ });
173
+ return {
174
+ subscriptionId,
175
+ type,
176
+ parentSubscriptionId,
177
+ scope,
178
+ scopeId,
179
+ planKey,
180
+ priceId,
181
+ status,
182
+ intervalUnit,
183
+ intervalCount,
184
+ modules,
185
+ billingAnchor: new Date(billingAnchor),
186
+ currentPeriodStart: period.start,
187
+ currentPeriodEnd: period.end,
188
+ cancelAtPeriodEnd: status !== "canceled" && Boolean(scheduledCancellation),
189
+ cancelAt: scheduledCancellation ? new Date(scheduledCancellation.effectiveAt) : canceledAt ? new Date(canceledAt) : null,
190
+ canceledAt: canceledAt ? new Date(canceledAt) : null,
191
+ scheduledPlan,
192
+ provider
193
+ };
194
+ };
195
+ const getSubscriptionTransitionDates = (events, params) => {
196
+ assertSingleSubscription(events);
197
+ const after = assertValidDate(params.after, "after");
198
+ const before = assertValidDate(params.before, "before");
199
+ return [...new Set(getEffectiveEvents(events).map((event) => event.effectiveAt.getTime()).filter((time) => time > after.getTime() && time < before.getTime()))].sort((left, right) => left - right).map((time) => new Date(time));
200
+ };
201
+ const DEFAULT_BILLABLE_STATUSES = ["active"];
202
+ const assertValidWindow = (from, to) => {
203
+ const normalizedFrom = new Date(from);
204
+ const normalizedTo = new Date(to);
205
+ if (Number.isNaN(normalizedFrom.getTime()) || Number.isNaN(normalizedTo.getTime())) {
206
+ throw new Error("Billing segment window must contain valid dates");
207
+ }
208
+ if (normalizedTo.getTime() <= normalizedFrom.getTime()) {
209
+ throw new Error("Billing segment window end must be after its start");
210
+ }
211
+ return {
212
+ from: normalizedFrom,
213
+ to: normalizedTo
214
+ };
215
+ };
216
+ const buildBillableSegments = (events, params) => {
217
+ if (events.length === 0) return [];
218
+ const window = assertValidWindow(params.from, params.to);
219
+ const billableStatuses = new Set(params.billableStatuses ?? DEFAULT_BILLABLE_STATUSES);
220
+ const transitionDates = getSubscriptionTransitionDates(events, {
221
+ after: window.from,
222
+ before: window.to
223
+ });
224
+ const segments = [];
225
+ let cursor = new Date(window.from);
226
+ while (cursor.getTime() < window.to.getTime()) {
227
+ const projection = projectSubscription(events, {
228
+ asOf: cursor
229
+ });
230
+ const nextTransition = transitionDates.find((date) => date.getTime() > cursor.getTime()) ?? null;
231
+ const nextPeriodEnd = projection && projection.currentPeriodEnd.getTime() > cursor.getTime() ? projection.currentPeriodEnd : null;
232
+ const candidateTimes = [window.to.getTime(), nextTransition?.getTime() ?? Number.POSITIVE_INFINITY, nextPeriodEnd?.getTime() ?? Number.POSITIVE_INFINITY];
233
+ const nextTime = Math.min(...candidateTimes);
234
+ if (!Number.isFinite(nextTime) || nextTime <= cursor.getTime()) {
235
+ throw new Error("Unable to advance while building billing segments");
236
+ }
237
+ const next = new Date(nextTime);
238
+ if (projection && billableStatuses.has(projection.status)) {
239
+ segments.push({
240
+ subscriptionId: projection.subscriptionId,
241
+ planKey: projection.planKey,
242
+ priceId: projection.priceId,
243
+ status: projection.status,
244
+ modules: [...projection.modules],
245
+ start: new Date(cursor),
246
+ end: next,
247
+ periodStart: new Date(projection.currentPeriodStart),
248
+ periodEnd: new Date(projection.currentPeriodEnd)
249
+ });
250
+ }
251
+ cursor = next;
252
+ }
253
+ return segments;
254
+ };
255
+ const runWithOptionalTransaction = async (storage, operation) => {
256
+ if (!storage.runInTransaction) return operation();
257
+ return storage.runInTransaction((transaction) => operation(transaction));
258
+ };
259
+ const buildStoredProjection = (params) => {
260
+ if (!params.latestEvent.id) throw new Error("Persisted subscription events must have an id");
261
+ return {
262
+ ...params.projection,
263
+ tenantId: params.tenantId,
264
+ latestEventId: params.latestEvent.id,
265
+ latestEventAt: new Date(params.latestEvent.occurredAt),
266
+ projectedAt: new Date(params.projectedAt)
267
+ };
268
+ };
269
+ const projectAndStore = async (params) => {
270
+ const events = await params.storage.listEvents(params.tenantId, params.subscriptionId, params.transaction);
271
+ const projection = projectSubscription(events, {
272
+ asOf: params.asOf
273
+ });
274
+ if (!projection) return null;
275
+ const latestEvent = [...events].sort((left, right) => {
276
+ const occurredDelta = right.occurredAt.getTime() - left.occurredAt.getTime();
277
+ if (occurredDelta !== 0) return occurredDelta;
278
+ return (right.id ?? "").localeCompare(left.id ?? "");
279
+ })[0];
280
+ if (!latestEvent) return null;
281
+ const storedProjection = buildStoredProjection({
282
+ tenantId: params.tenantId,
283
+ projection,
284
+ latestEvent,
285
+ projectedAt: params.asOf
286
+ });
287
+ await params.storage.upsertProjection(storedProjection, params.transaction);
288
+ return storedProjection;
289
+ };
290
+ const appendSubscriptionEvent = async (params) => runWithOptionalTransaction(params.storage, async (transaction) => {
291
+ const event = await params.storage.createEvent(params.event, transaction);
292
+ const projection = await projectAndStore({
293
+ storage: params.storage,
294
+ tenantId: params.tenantId,
295
+ subscriptionId: event.subscriptionId,
296
+ asOf: params.asOf ?? /* @__PURE__ */ new Date(),
297
+ transaction
298
+ });
299
+ return {
300
+ event,
301
+ projection
302
+ };
303
+ });
304
+ const rebuildSubscriptionProjection = async (params) => runWithOptionalTransaction(params.storage, (transaction) => projectAndStore({
305
+ storage: params.storage,
306
+ tenantId: params.tenantId,
307
+ subscriptionId: params.subscriptionId,
308
+ asOf: params.asOf ?? /* @__PURE__ */ new Date(),
309
+ transaction
310
+ }));
311
+ const SUBSCRIPTION_STATUSES = ["trialing", "active", "past_due", "paused", "canceled", "expired"];
312
+ const SUBSCRIPTION_INTERVAL_UNITS = ["month", "year"];
313
+ const SUBSCRIPTION_TYPES = ["primary", "addon"];
314
+ const SUBSCRIPTION_SCOPES = ["tenant", "workspace", "shop", "custom"];
315
+ const SUBSCRIPTION_EVENT_TYPES = ["created", "plan_changed", "status_changed", "resumed", "canceled", "renewed"];
316
+ export {
317
+ SUBSCRIPTION_EVENT_TYPES,
318
+ SUBSCRIPTION_INTERVAL_UNITS,
319
+ SUBSCRIPTION_SCOPES,
320
+ SUBSCRIPTION_STATUSES,
321
+ SUBSCRIPTION_TYPES,
322
+ addBillingInterval,
323
+ appendSubscriptionEvent,
324
+ buildBillableSegments,
325
+ calculateProratedAmount,
326
+ getSubscriptionTransitionDates,
327
+ projectSubscription,
328
+ rebuildSubscriptionProjection,
329
+ resolveBillingPeriod,
330
+ sortSubscriptionEvents
331
+ };
332
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/calendar.ts","../src/proration.ts","../src/projection.ts","../src/segments.ts","../src/storage.ts","../src/types.ts"],"sourcesContent":["import type { SubscriptionIntervalUnit } from \"./types\"\n\n\nconst assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nconst normalizeIntervalCount = (value: number): number => {\n if (!Number.isInteger(value) || value < 1) throw new Error(\"intervalCount must be a positive integer\")\n return value\n}\n\nconst getDaysInUtcMonth = (year: number, month: number): number =>\n new Date(Date.UTC(year, month + 1, 0)).getUTCDate()\n\nconst createClampedUtcDate = (source: Date, year: number, month: number): Date => {\n const day = Math.min(source.getUTCDate(), getDaysInUtcMonth(year, month))\n return new Date(Date.UTC(\n year,\n month,\n day,\n source.getUTCHours(),\n source.getUTCMinutes(),\n source.getUTCSeconds(),\n source.getUTCMilliseconds(),\n ))\n}\n\nexport const addBillingInterval = (\n value: Date,\n intervalUnit: SubscriptionIntervalUnit,\n intervalCount = 1,\n): Date => {\n const date = assertValidDate(value, \"value\")\n const count = normalizeIntervalCount(intervalCount)\n\n if (intervalUnit === \"year\") {\n return createClampedUtcDate(date, date.getUTCFullYear() + count, date.getUTCMonth())\n }\n\n const absoluteMonth = date.getUTCFullYear() * 12 + date.getUTCMonth() + count\n const year = Math.floor(absoluteMonth / 12)\n const month = ((absoluteMonth % 12) + 12) % 12\n return createClampedUtcDate(date, year, month)\n}\n\nconst getEstimatedPeriodIndex = (\n anchor: Date,\n asOf: Date,\n intervalUnit: SubscriptionIntervalUnit,\n intervalCount: number,\n): number => {\n if (asOf.getTime() <= anchor.getTime()) return 0\n\n const rawDifference = intervalUnit === \"year\"\n ? asOf.getUTCFullYear() - anchor.getUTCFullYear()\n : (asOf.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + asOf.getUTCMonth() - anchor.getUTCMonth()\n\n return Math.max(0, Math.floor(rawDifference / intervalCount))\n}\n\nexport const resolveBillingPeriod = (params: {\n billingAnchor: Date\n intervalUnit: SubscriptionIntervalUnit\n intervalCount?: number\n asOf: Date\n}): { start: Date; end: Date } => {\n const anchor = assertValidDate(params.billingAnchor, \"billingAnchor\")\n const asOf = assertValidDate(params.asOf, \"asOf\")\n const intervalCount = normalizeIntervalCount(params.intervalCount ?? 1)\n\n let periodIndex = getEstimatedPeriodIndex(anchor, asOf, params.intervalUnit, intervalCount)\n let start = addBillingInterval(anchor, params.intervalUnit, Math.max(1, periodIndex * intervalCount))\n\n if (periodIndex === 0) start = new Date(anchor)\n\n while (start.getTime() > asOf.getTime() && periodIndex > 0) {\n periodIndex -= 1\n start = periodIndex === 0\n ? new Date(anchor)\n : addBillingInterval(anchor, params.intervalUnit, periodIndex * intervalCount)\n }\n\n let end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount)\n while (end.getTime() <= asOf.getTime()) {\n periodIndex += 1\n start = new Date(end)\n end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount)\n }\n\n return { start, end }\n}\n","const assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nexport const calculateProratedAmount = (params: {\n unitAmount: number\n segmentStart: Date\n segmentEnd: Date\n periodStart: Date\n periodEnd: Date\n}): number => {\n if (!Number.isSafeInteger(params.unitAmount) || params.unitAmount < 0) {\n throw new Error(\"unitAmount must be a non-negative safe integer\")\n }\n\n const segmentStart = assertValidDate(params.segmentStart, \"segmentStart\")\n const segmentEnd = assertValidDate(params.segmentEnd, \"segmentEnd\")\n const periodStart = assertValidDate(params.periodStart, \"periodStart\")\n const periodEnd = assertValidDate(params.periodEnd, \"periodEnd\")\n const periodDuration = periodEnd.getTime() - periodStart.getTime()\n if (periodDuration <= 0) throw new Error(\"Billing period end must be after its start\")\n\n const overlapStart = Math.max(segmentStart.getTime(), periodStart.getTime())\n const overlapEnd = Math.min(segmentEnd.getTime(), periodEnd.getTime())\n const overlapDuration = Math.max(0, overlapEnd - overlapStart)\n\n return Math.round(params.unitAmount * overlapDuration / periodDuration)\n}\n","import { resolveBillingPeriod } from \"./calendar\"\nimport type {\n ScheduledPlanChange,\n SubscriptionEvent,\n SubscriptionProjection,\n SubscriptionStatus,\n} from \"./types\"\n\n\nconst assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nconst compareEvents = (left: SubscriptionEvent, right: SubscriptionEvent): number => {\n const effectiveDelta = left.effectiveAt.getTime() - right.effectiveAt.getTime()\n if (effectiveDelta !== 0) return effectiveDelta\n\n const occurredDelta = left.occurredAt.getTime() - right.occurredAt.getTime()\n if (occurredDelta !== 0) return occurredDelta\n\n return (left.id ?? \"\").localeCompare(right.id ?? \"\")\n}\n\nexport const sortSubscriptionEvents = (events: readonly SubscriptionEvent[]): SubscriptionEvent[] =>\n events\n .map((event) => ({\n ...event,\n occurredAt: assertValidDate(event.occurredAt, \"occurredAt\"),\n effectiveAt: assertValidDate(event.effectiveAt, \"effectiveAt\"),\n billingAnchor: event.billingAnchor\n ? assertValidDate(event.billingAnchor, \"billingAnchor\")\n : undefined,\n }))\n .sort(compareEvents)\n\nconst getEffectiveEvents = (events: readonly SubscriptionEvent[]): SubscriptionEvent[] => {\n const ordered = sortSubscriptionEvents(events)\n const supersededIds = new Set(\n ordered\n .map((event) => event.supersedesEventId)\n .filter((eventId): eventId is string => Boolean(eventId)),\n )\n const inferredSupersededEvents = new Set<SubscriptionEvent>()\n\n for (const cancellation of ordered) {\n if (\n cancellation.type !== \"canceled\"\n || cancellation.effectiveAt.getTime() <= cancellation.occurredAt.getTime()\n || (cancellation.id && supersededIds.has(cancellation.id))\n ) {\n continue\n }\n\n const laterResume = ordered.find((candidate) =>\n candidate.type === \"resumed\"\n && candidate.occurredAt.getTime() > cancellation.occurredAt.getTime()\n && candidate.effectiveAt.getTime() <= cancellation.effectiveAt.getTime(),\n )\n\n if (laterResume) inferredSupersededEvents.add(cancellation)\n }\n\n return ordered.filter((event) =>\n !(event.id && supersededIds.has(event.id))\n && !inferredSupersededEvents.has(event),\n )\n}\n\nconst assertSingleSubscription = (events: readonly SubscriptionEvent[]): string => {\n const subscriptionIds = new Set(events.map((event) => event.subscriptionId))\n if (subscriptionIds.size > 1) throw new Error(\"Events from multiple subscriptions cannot be projected together\")\n return events[0]?.subscriptionId ?? \"\"\n}\n\nconst getStatusForEvent = (\n event: SubscriptionEvent,\n currentStatus: SubscriptionStatus,\n): SubscriptionStatus => {\n if (event.toStatus) return event.toStatus\n if (event.type === \"created\" || event.type === \"resumed\") return \"active\"\n if (event.type === \"canceled\") return \"canceled\"\n return currentStatus\n}\n\nexport const projectSubscription = (\n events: readonly SubscriptionEvent[],\n options: { asOf?: Date } = {},\n): SubscriptionProjection | null => {\n if (events.length === 0) return null\n\n const subscriptionId = assertSingleSubscription(events)\n const ordered = getEffectiveEvents(events)\n const asOf = assertValidDate(options.asOf ?? new Date(), \"asOf\")\n const applicable = ordered.filter((event) => event.effectiveAt.getTime() <= asOf.getTime())\n if (applicable.length === 0) return null\n\n const firstEvent = applicable[0]\n let type = firstEvent.subscriptionType ?? \"primary\"\n let parentSubscriptionId = firstEvent.parentSubscriptionId ?? null\n let scope = firstEvent.scope ?? \"tenant\"\n let scopeId = firstEvent.scopeId ?? null\n let planKey = \"\"\n let priceId: string | null = null\n let status: SubscriptionStatus = \"active\"\n let intervalUnit = firstEvent.toIntervalUnit ?? \"month\"\n let intervalCount = firstEvent.toIntervalCount ?? 1\n let modules: string[] = []\n let billingAnchor = firstEvent.billingAnchor ?? firstEvent.effectiveAt\n let provider: string | null = null\n let canceledAt: Date | null = null\n\n for (const event of applicable) {\n type = event.subscriptionType ?? type\n parentSubscriptionId = event.parentSubscriptionId ?? parentSubscriptionId\n scope = event.scope ?? scope\n scopeId = event.scopeId ?? scopeId\n planKey = event.toPlanKey ?? planKey\n priceId = event.toPriceId ?? priceId\n status = getStatusForEvent(event, status)\n intervalUnit = event.toIntervalUnit ?? intervalUnit\n intervalCount = event.toIntervalCount ?? intervalCount\n modules = event.toModules ? [...event.toModules] : modules\n billingAnchor = event.billingAnchor ?? billingAnchor\n provider = event.provider ?? provider\n\n if (event.type === \"canceled\") {\n canceledAt = new Date(event.effectiveAt)\n } else if (event.type === \"created\" || event.type === \"resumed\") {\n canceledAt = null\n }\n }\n\n if (!planKey) return null\n if (!Number.isInteger(intervalCount) || intervalCount < 1) {\n throw new Error(\"Subscription intervalCount must be a positive integer\")\n }\n\n const futureEvents = ordered.filter((event) => event.effectiveAt.getTime() > asOf.getTime())\n const scheduledCancellation = futureEvents.find((event) => event.type === \"canceled\") ?? null\n const scheduledPlanEvent = futureEvents.find((event) => event.type === \"plan_changed\" && event.toPlanKey) ?? null\n const scheduledPlan: ScheduledPlanChange | null = scheduledPlanEvent?.toPlanKey\n ? {\n planKey: scheduledPlanEvent.toPlanKey,\n effectiveAt: new Date(scheduledPlanEvent.effectiveAt),\n }\n : null\n\n const periodAsOf = canceledAt && canceledAt.getTime() <= asOf.getTime()\n ? new Date(canceledAt.getTime() - 1)\n : asOf\n const period = resolveBillingPeriod({\n billingAnchor,\n intervalUnit,\n intervalCount,\n asOf: periodAsOf,\n })\n\n return {\n subscriptionId,\n type,\n parentSubscriptionId,\n scope,\n scopeId,\n planKey,\n priceId,\n status,\n intervalUnit,\n intervalCount,\n modules,\n billingAnchor: new Date(billingAnchor),\n currentPeriodStart: period.start,\n currentPeriodEnd: period.end,\n cancelAtPeriodEnd: status !== \"canceled\" && Boolean(scheduledCancellation),\n cancelAt: scheduledCancellation\n ? new Date(scheduledCancellation.effectiveAt)\n : canceledAt\n ? new Date(canceledAt)\n : null,\n canceledAt: canceledAt ? new Date(canceledAt) : null,\n scheduledPlan,\n provider,\n }\n}\n\nexport const getSubscriptionTransitionDates = (\n events: readonly SubscriptionEvent[],\n params: { after: Date; before: Date },\n): Date[] => {\n assertSingleSubscription(events)\n const after = assertValidDate(params.after, \"after\")\n const before = assertValidDate(params.before, \"before\")\n\n return [...new Set(\n getEffectiveEvents(events)\n .map((event) => event.effectiveAt.getTime())\n .filter((time) => time > after.getTime() && time < before.getTime()),\n )]\n .sort((left, right) => left - right)\n .map((time) => new Date(time))\n}\n","import { getSubscriptionTransitionDates, projectSubscription } from \"./projection\"\nimport type {\n BillableSubscriptionSegment,\n SubscriptionEvent,\n SubscriptionStatus,\n} from \"./types\"\n\n\nconst DEFAULT_BILLABLE_STATUSES: readonly SubscriptionStatus[] = [\"active\"]\n\nconst assertValidWindow = (from: Date, to: Date): { from: Date; to: Date } => {\n const normalizedFrom = new Date(from)\n const normalizedTo = new Date(to)\n if (Number.isNaN(normalizedFrom.getTime()) || Number.isNaN(normalizedTo.getTime())) {\n throw new Error(\"Billing segment window must contain valid dates\")\n }\n if (normalizedTo.getTime() <= normalizedFrom.getTime()) {\n throw new Error(\"Billing segment window end must be after its start\")\n }\n return { from: normalizedFrom, to: normalizedTo }\n}\n\nexport const buildBillableSegments = (\n events: readonly SubscriptionEvent[],\n params: {\n from: Date\n to: Date\n billableStatuses?: readonly SubscriptionStatus[]\n },\n): BillableSubscriptionSegment[] => {\n if (events.length === 0) return []\n\n const window = assertValidWindow(params.from, params.to)\n const billableStatuses = new Set(params.billableStatuses ?? DEFAULT_BILLABLE_STATUSES)\n const transitionDates = getSubscriptionTransitionDates(events, {\n after: window.from,\n before: window.to,\n })\n const segments: BillableSubscriptionSegment[] = []\n let cursor = new Date(window.from)\n\n while (cursor.getTime() < window.to.getTime()) {\n const projection = projectSubscription(events, { asOf: cursor })\n const nextTransition = transitionDates.find((date) => date.getTime() > cursor.getTime()) ?? null\n const nextPeriodEnd = projection && projection.currentPeriodEnd.getTime() > cursor.getTime()\n ? projection.currentPeriodEnd\n : null\n const candidateTimes = [\n window.to.getTime(),\n nextTransition?.getTime() ?? Number.POSITIVE_INFINITY,\n nextPeriodEnd?.getTime() ?? Number.POSITIVE_INFINITY,\n ]\n const nextTime = Math.min(...candidateTimes)\n\n if (!Number.isFinite(nextTime) || nextTime <= cursor.getTime()) {\n throw new Error(\"Unable to advance while building billing segments\")\n }\n\n const next = new Date(nextTime)\n if (projection && billableStatuses.has(projection.status)) {\n segments.push({\n subscriptionId: projection.subscriptionId,\n planKey: projection.planKey,\n priceId: projection.priceId,\n status: projection.status,\n modules: [...projection.modules],\n start: new Date(cursor),\n end: next,\n periodStart: new Date(projection.currentPeriodStart),\n periodEnd: new Date(projection.currentPeriodEnd),\n })\n }\n\n cursor = next\n }\n\n return segments\n}\n","import { projectSubscription } from \"./projection\"\nimport type {\n StoredSubscriptionProjection,\n SubscriptionEvent,\n SubscriptionProjection,\n} from \"./types\"\n\n\nexport type BillingStorageTransaction = unknown\n\nexport type BillingStorage = {\n runInTransaction?: <T>(operation: (transaction: BillingStorageTransaction) => Promise<T>) => Promise<T>\n createEvent: (\n event: SubscriptionEvent,\n transaction?: BillingStorageTransaction,\n ) => Promise<SubscriptionEvent>\n listEvents: (\n tenantId: string,\n subscriptionId: string,\n transaction?: BillingStorageTransaction,\n ) => Promise<SubscriptionEvent[]>\n upsertProjection: (\n projection: StoredSubscriptionProjection,\n transaction?: BillingStorageTransaction,\n ) => Promise<void>\n}\n\nconst runWithOptionalTransaction = async <T>(\n storage: BillingStorage,\n operation: (transaction?: BillingStorageTransaction) => Promise<T>,\n): Promise<T> => {\n if (!storage.runInTransaction) return operation()\n return storage.runInTransaction((transaction) => operation(transaction))\n}\n\nconst buildStoredProjection = (params: {\n tenantId: string\n projection: SubscriptionProjection\n latestEvent: SubscriptionEvent\n projectedAt: Date\n}): StoredSubscriptionProjection => {\n if (!params.latestEvent.id) throw new Error(\"Persisted subscription events must have an id\")\n\n return {\n ...params.projection,\n tenantId: params.tenantId,\n latestEventId: params.latestEvent.id,\n latestEventAt: new Date(params.latestEvent.occurredAt),\n projectedAt: new Date(params.projectedAt),\n }\n}\n\nconst projectAndStore = async (params: {\n storage: BillingStorage\n tenantId: string\n subscriptionId: string\n asOf: Date\n transaction?: BillingStorageTransaction\n}): Promise<StoredSubscriptionProjection | null> => {\n const events = await params.storage.listEvents(\n params.tenantId,\n params.subscriptionId,\n params.transaction,\n )\n const projection = projectSubscription(events, { asOf: params.asOf })\n if (!projection) return null\n\n const latestEvent = [...events].sort((left, right) => {\n const occurredDelta = right.occurredAt.getTime() - left.occurredAt.getTime()\n if (occurredDelta !== 0) return occurredDelta\n return (right.id ?? \"\").localeCompare(left.id ?? \"\")\n })[0]\n if (!latestEvent) return null\n\n const storedProjection = buildStoredProjection({\n tenantId: params.tenantId,\n projection,\n latestEvent,\n projectedAt: params.asOf,\n })\n await params.storage.upsertProjection(storedProjection, params.transaction)\n return storedProjection\n}\n\nexport const appendSubscriptionEvent = async (params: {\n storage: BillingStorage\n tenantId: string\n event: SubscriptionEvent\n asOf?: Date\n}): Promise<{\n event: SubscriptionEvent\n projection: StoredSubscriptionProjection | null\n}> => runWithOptionalTransaction(params.storage, async (transaction) => {\n const event = await params.storage.createEvent(params.event, transaction)\n const projection = await projectAndStore({\n storage: params.storage,\n tenantId: params.tenantId,\n subscriptionId: event.subscriptionId,\n asOf: params.asOf ?? new Date(),\n transaction,\n })\n\n return { event, projection }\n})\n\nexport const rebuildSubscriptionProjection = async (params: {\n storage: BillingStorage\n tenantId: string\n subscriptionId: string\n asOf?: Date\n}): Promise<StoredSubscriptionProjection | null> => runWithOptionalTransaction(\n params.storage,\n (transaction) => projectAndStore({\n storage: params.storage,\n tenantId: params.tenantId,\n subscriptionId: params.subscriptionId,\n asOf: params.asOf ?? new Date(),\n transaction,\n }),\n)\n","export const SUBSCRIPTION_STATUSES = [\n \"trialing\",\n \"active\",\n \"past_due\",\n \"paused\",\n \"canceled\",\n \"expired\",\n] as const\n\nexport type SubscriptionStatus = typeof SUBSCRIPTION_STATUSES[number]\n\nexport const SUBSCRIPTION_INTERVAL_UNITS = [\"month\", \"year\"] as const\n\nexport type SubscriptionIntervalUnit = typeof SUBSCRIPTION_INTERVAL_UNITS[number]\n\nexport const SUBSCRIPTION_TYPES = [\"primary\", \"addon\"] as const\n\nexport type SubscriptionType = typeof SUBSCRIPTION_TYPES[number]\n\nexport const SUBSCRIPTION_SCOPES = [\"tenant\", \"workspace\", \"shop\", \"custom\"] as const\n\nexport type SubscriptionScope = typeof SUBSCRIPTION_SCOPES[number]\n\nexport const SUBSCRIPTION_EVENT_TYPES = [\n \"created\",\n \"plan_changed\",\n \"status_changed\",\n \"resumed\",\n \"canceled\",\n \"renewed\",\n] as const\n\nexport type SubscriptionEventType = typeof SUBSCRIPTION_EVENT_TYPES[number]\n\nexport type SubscriptionEvent = {\n id?: string\n tenantId?: string\n subscriptionId: string\n type: SubscriptionEventType\n occurredAt: Date\n effectiveAt: Date\n subscriptionType?: SubscriptionType\n parentSubscriptionId?: string\n scope?: SubscriptionScope\n scopeId?: string\n fromPlanKey?: string\n toPlanKey?: string\n fromStatus?: SubscriptionStatus\n toStatus?: SubscriptionStatus\n fromModules?: string[]\n toModules?: string[]\n fromIntervalUnit?: SubscriptionIntervalUnit\n toIntervalUnit?: SubscriptionIntervalUnit\n fromIntervalCount?: number\n toIntervalCount?: number\n fromPriceId?: string\n toPriceId?: string\n billingAnchor?: Date\n supersedesEventId?: string\n idempotencyKey?: string\n actorUserId?: string\n source?: \"admin\" | \"system\" | \"webhook\" | \"user\"\n reason?: string\n provider?: string\n providerEventId?: string\n metadata?: Record<string, unknown>\n}\n\nexport type ScheduledPlanChange = {\n planKey: string\n effectiveAt: Date\n}\n\nexport type SubscriptionProjection = {\n subscriptionId: string\n type: SubscriptionType\n parentSubscriptionId: string | null\n scope: SubscriptionScope\n scopeId: string | null\n planKey: string\n priceId: string | null\n status: SubscriptionStatus\n intervalUnit: SubscriptionIntervalUnit\n intervalCount: number\n modules: string[]\n billingAnchor: Date\n currentPeriodStart: Date\n currentPeriodEnd: Date\n cancelAtPeriodEnd: boolean\n cancelAt: Date | null\n canceledAt: Date | null\n scheduledPlan: ScheduledPlanChange | null\n provider: string | null\n}\n\nexport type BillableSubscriptionSegment = {\n subscriptionId: string\n planKey: string\n priceId: string | null\n status: SubscriptionStatus\n modules: string[]\n start: Date\n end: Date\n periodStart: Date\n periodEnd: Date\n}\n\nexport type StoredSubscriptionProjection = SubscriptionProjection & {\n tenantId: string\n latestEventId: string\n latestEventAt: Date\n projectedAt: Date\n}\n"],"names":["assertValidDate","value","field","date","Date","Number","isNaN","getTime","Error","normalizeIntervalCount","isInteger","getDaysInUtcMonth","year","month","UTC","getUTCDate","createClampedUtcDate","source","day","Math","min","getUTCHours","getUTCMinutes","getUTCSeconds","getUTCMilliseconds","addBillingInterval","intervalUnit","intervalCount","count","getUTCFullYear","getUTCMonth","absoluteMonth","floor","getEstimatedPeriodIndex","anchor","asOf","rawDifference","max","resolveBillingPeriod","params","billingAnchor","periodIndex","start","end","calculateProratedAmount","isSafeInteger","unitAmount","segmentStart","segmentEnd","periodStart","periodEnd","periodDuration","overlapStart","overlapEnd","overlapDuration","round","compareEvents","left","right","effectiveDelta","effectiveAt","occurredDelta","occurredAt","id","localeCompare","sortSubscriptionEvents","events","map","event","undefined","sort","getEffectiveEvents","ordered","supersededIds","Set","supersedesEventId","filter","eventId","Boolean","inferredSupersededEvents","cancellation","type","has","laterResume","find","candidate","add","assertSingleSubscription","subscriptionIds","subscriptionId","size","getStatusForEvent","currentStatus","toStatus","projectSubscription","options","length","applicable","firstEvent","subscriptionType","parentSubscriptionId","scope","scopeId","planKey","priceId","status","toIntervalUnit","toIntervalCount","modules","provider","canceledAt","toPlanKey","toPriceId","toModules","futureEvents","scheduledCancellation","scheduledPlanEvent","scheduledPlan","periodAsOf","period","currentPeriodStart","currentPeriodEnd","cancelAtPeriodEnd","cancelAt","getSubscriptionTransitionDates","after","before","time","DEFAULT_BILLABLE_STATUSES","assertValidWindow","from","to","normalizedFrom","normalizedTo","buildBillableSegments","window","billableStatuses","transitionDates","segments","cursor","projection","nextTransition","nextPeriodEnd","candidateTimes","POSITIVE_INFINITY","nextTime","isFinite","next","push","runWithOptionalTransaction","storage","operation","runInTransaction","transaction","buildStoredProjection","latestEvent","tenantId","latestEventId","latestEventAt","projectedAt","projectAndStore","listEvents","storedProjection","upsertProjection","appendSubscriptionEvent","createEvent","rebuildSubscriptionProjection","SUBSCRIPTION_STATUSES","SUBSCRIPTION_INTERVAL_UNITS","SUBSCRIPTION_TYPES","SUBSCRIPTION_SCOPES","SUBSCRIPTION_EVENT_TYPES"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { SubscriptionEvent, SubscriptionProjection } from './types';
2
+ export declare const sortSubscriptionEvents: (events: readonly SubscriptionEvent[]) => SubscriptionEvent[];
3
+ export declare const projectSubscription: (events: readonly SubscriptionEvent[], options?: {
4
+ asOf?: Date;
5
+ }) => SubscriptionProjection | null;
6
+ export declare const getSubscriptionTransitionDates: (events: readonly SubscriptionEvent[], params: {
7
+ after: Date;
8
+ before: Date;
9
+ }) => Date[];
10
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iBAAiB,EACjB,sBAAsB,EAEvB,MAAM,SAAS,CAAA;AAmBhB,eAAO,MAAM,sBAAsB,GAAI,QAAQ,SAAS,iBAAiB,EAAE,KAAG,iBAAiB,EAUvE,CAAA;AAmDxB,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,SAAS,iBAAiB,EAAE,EACpC,UAAS;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAO,KAC5B,sBAAsB,GAAG,IA+F3B,CAAA;AAED,eAAO,MAAM,8BAA8B,GACzC,QAAQ,SAAS,iBAAiB,EAAE,EACpC,QAAQ;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,IAAI,CAAA;CAAE,KACpC,IAAI,EAYN,CAAA"}
@@ -0,0 +1,8 @@
1
+ export declare const calculateProratedAmount: (params: {
2
+ unitAmount: number;
3
+ segmentStart: Date;
4
+ segmentEnd: Date;
5
+ periodStart: Date;
6
+ periodEnd: Date;
7
+ }) => number;
8
+ //# sourceMappingURL=proration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proration.d.ts","sourceRoot":"","sources":["../src/proration.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,uBAAuB,GAAI,QAAQ;IAC9C,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,IAAI,CAAA;IAClB,UAAU,EAAE,IAAI,CAAA;IAChB,WAAW,EAAE,IAAI,CAAA;IACjB,SAAS,EAAE,IAAI,CAAA;CAChB,KAAG,MAiBH,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { BillableSubscriptionSegment, SubscriptionEvent, SubscriptionStatus } from './types';
2
+ export declare const buildBillableSegments: (events: readonly SubscriptionEvent[], params: {
3
+ from: Date;
4
+ to: Date;
5
+ billableStatuses?: readonly SubscriptionStatus[];
6
+ }) => BillableSubscriptionSegment[];
7
+ //# sourceMappingURL=segments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segments.d.ts","sourceRoot":"","sources":["../src/segments.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,2BAA2B,EAC3B,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,SAAS,CAAA;AAiBhB,eAAO,MAAM,qBAAqB,GAChC,QAAQ,SAAS,iBAAiB,EAAE,EACpC,QAAQ;IACN,IAAI,EAAE,IAAI,CAAA;IACV,EAAE,EAAE,IAAI,CAAA;IACR,gBAAgB,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;CACjD,KACA,2BAA2B,EAgD7B,CAAA"}
@@ -0,0 +1,24 @@
1
+ import { StoredSubscriptionProjection, SubscriptionEvent } from './types';
2
+ export type BillingStorageTransaction = unknown;
3
+ export type BillingStorage = {
4
+ runInTransaction?: <T>(operation: (transaction: BillingStorageTransaction) => Promise<T>) => Promise<T>;
5
+ createEvent: (event: SubscriptionEvent, transaction?: BillingStorageTransaction) => Promise<SubscriptionEvent>;
6
+ listEvents: (tenantId: string, subscriptionId: string, transaction?: BillingStorageTransaction) => Promise<SubscriptionEvent[]>;
7
+ upsertProjection: (projection: StoredSubscriptionProjection, transaction?: BillingStorageTransaction) => Promise<void>;
8
+ };
9
+ export declare const appendSubscriptionEvent: (params: {
10
+ storage: BillingStorage;
11
+ tenantId: string;
12
+ event: SubscriptionEvent;
13
+ asOf?: Date;
14
+ }) => Promise<{
15
+ event: SubscriptionEvent;
16
+ projection: StoredSubscriptionProjection | null;
17
+ }>;
18
+ export declare const rebuildSubscriptionProjection: (params: {
19
+ storage: BillingStorage;
20
+ tenantId: string;
21
+ subscriptionId: string;
22
+ asOf?: Date;
23
+ }) => Promise<StoredSubscriptionProjection | null>;
24
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,4BAA4B,EAC5B,iBAAiB,EAElB,MAAM,SAAS,CAAA;AAGhB,MAAM,MAAM,yBAAyB,GAAG,OAAO,CAAA;AAE/C,MAAM,MAAM,cAAc,GAAG;IAC3B,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,WAAW,EAAE,yBAAyB,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACvG,WAAW,EAAE,CACX,KAAK,EAAE,iBAAiB,EACxB,WAAW,CAAC,EAAE,yBAAyB,KACpC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC/B,UAAU,EAAE,CACV,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,WAAW,CAAC,EAAE,yBAAyB,KACpC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;IACjC,gBAAgB,EAAE,CAChB,UAAU,EAAE,4BAA4B,EACxC,WAAW,CAAC,EAAE,yBAAyB,KACpC,OAAO,CAAC,IAAI,CAAC,CAAA;CACnB,CAAA;AA2DD,eAAO,MAAM,uBAAuB,GAAU,QAAQ;IACpD,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,iBAAiB,CAAA;IACxB,IAAI,CAAC,EAAE,IAAI,CAAA;CACZ,KAAG,OAAO,CAAC;IACV,KAAK,EAAE,iBAAiB,CAAA;IACxB,UAAU,EAAE,4BAA4B,GAAG,IAAI,CAAA;CAChD,CAWC,CAAA;AAEF,eAAO,MAAM,6BAA6B,GAAU,QAAQ;IAC1D,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,IAAI,CAAA;CACZ,KAAG,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAS9C,CAAA"}
@@ -0,0 +1,86 @@
1
+ export declare const SUBSCRIPTION_STATUSES: readonly ["trialing", "active", "past_due", "paused", "canceled", "expired"];
2
+ export type SubscriptionStatus = typeof SUBSCRIPTION_STATUSES[number];
3
+ export declare const SUBSCRIPTION_INTERVAL_UNITS: readonly ["month", "year"];
4
+ export type SubscriptionIntervalUnit = typeof SUBSCRIPTION_INTERVAL_UNITS[number];
5
+ export declare const SUBSCRIPTION_TYPES: readonly ["primary", "addon"];
6
+ export type SubscriptionType = typeof SUBSCRIPTION_TYPES[number];
7
+ export declare const SUBSCRIPTION_SCOPES: readonly ["tenant", "workspace", "shop", "custom"];
8
+ export type SubscriptionScope = typeof SUBSCRIPTION_SCOPES[number];
9
+ export declare const SUBSCRIPTION_EVENT_TYPES: readonly ["created", "plan_changed", "status_changed", "resumed", "canceled", "renewed"];
10
+ export type SubscriptionEventType = typeof SUBSCRIPTION_EVENT_TYPES[number];
11
+ export type SubscriptionEvent = {
12
+ id?: string;
13
+ tenantId?: string;
14
+ subscriptionId: string;
15
+ type: SubscriptionEventType;
16
+ occurredAt: Date;
17
+ effectiveAt: Date;
18
+ subscriptionType?: SubscriptionType;
19
+ parentSubscriptionId?: string;
20
+ scope?: SubscriptionScope;
21
+ scopeId?: string;
22
+ fromPlanKey?: string;
23
+ toPlanKey?: string;
24
+ fromStatus?: SubscriptionStatus;
25
+ toStatus?: SubscriptionStatus;
26
+ fromModules?: string[];
27
+ toModules?: string[];
28
+ fromIntervalUnit?: SubscriptionIntervalUnit;
29
+ toIntervalUnit?: SubscriptionIntervalUnit;
30
+ fromIntervalCount?: number;
31
+ toIntervalCount?: number;
32
+ fromPriceId?: string;
33
+ toPriceId?: string;
34
+ billingAnchor?: Date;
35
+ supersedesEventId?: string;
36
+ idempotencyKey?: string;
37
+ actorUserId?: string;
38
+ source?: "admin" | "system" | "webhook" | "user";
39
+ reason?: string;
40
+ provider?: string;
41
+ providerEventId?: string;
42
+ metadata?: Record<string, unknown>;
43
+ };
44
+ export type ScheduledPlanChange = {
45
+ planKey: string;
46
+ effectiveAt: Date;
47
+ };
48
+ export type SubscriptionProjection = {
49
+ subscriptionId: string;
50
+ type: SubscriptionType;
51
+ parentSubscriptionId: string | null;
52
+ scope: SubscriptionScope;
53
+ scopeId: string | null;
54
+ planKey: string;
55
+ priceId: string | null;
56
+ status: SubscriptionStatus;
57
+ intervalUnit: SubscriptionIntervalUnit;
58
+ intervalCount: number;
59
+ modules: string[];
60
+ billingAnchor: Date;
61
+ currentPeriodStart: Date;
62
+ currentPeriodEnd: Date;
63
+ cancelAtPeriodEnd: boolean;
64
+ cancelAt: Date | null;
65
+ canceledAt: Date | null;
66
+ scheduledPlan: ScheduledPlanChange | null;
67
+ provider: string | null;
68
+ };
69
+ export type BillableSubscriptionSegment = {
70
+ subscriptionId: string;
71
+ planKey: string;
72
+ priceId: string | null;
73
+ status: SubscriptionStatus;
74
+ modules: string[];
75
+ start: Date;
76
+ end: Date;
77
+ periodStart: Date;
78
+ periodEnd: Date;
79
+ };
80
+ export type StoredSubscriptionProjection = SubscriptionProjection & {
81
+ tenantId: string;
82
+ latestEventId: string;
83
+ latestEventAt: Date;
84
+ projectedAt: Date;
85
+ };
86
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@rpcbase/billing",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "wireit",
19
+ "test": "wireit",
20
+ "release": "wireit"
21
+ },
22
+ "wireit": {
23
+ "build": {
24
+ "command": "../../node_modules/.bin/vite build",
25
+ "files": [
26
+ "src/**/*",
27
+ "../../tsconfig.json",
28
+ "../../scripts/tsconfig.pkg.json",
29
+ "./tsconfig.json",
30
+ "./vite.config.js",
31
+ "../../scripts/vite.config-pkg.js"
32
+ ],
33
+ "output": [
34
+ "dist/"
35
+ ]
36
+ },
37
+ "build-watch": {
38
+ "command": "../../node_modules/.bin/vite build --watch",
39
+ "service": {
40
+ "readyWhen": {
41
+ "lineMatches": "^(?:\\u001b\\[[0-9;]*m)*built in .*\\.(?:\\u001b\\[[0-9;]*m)*$"
42
+ }
43
+ }
44
+ },
45
+ "test": {
46
+ "command": "../../node_modules/.bin/vitest run --config ../../pkg/test/src/vitest.config.mjs --passWithNoTests",
47
+ "files": [
48
+ "src/**/*",
49
+ "tsconfig.json",
50
+ "../../scripts/tsconfig.pkg.json"
51
+ ],
52
+ "dependencies": []
53
+ },
54
+ "release": {
55
+ "command": "../../scripts/publish.js",
56
+ "dependencies": [
57
+ "build"
58
+ ],
59
+ "files": [
60
+ "../../scripts/publish.js",
61
+ "package.json",
62
+ "dist/**/*"
63
+ ],
64
+ "output": [
65
+ "publish-output.txt"
66
+ ],
67
+ "env": {
68
+ "NPM_RELEASE_CHANNEL": {
69
+ "external": true
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }