@rpcbase/db 0.122.0 → 0.123.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billingStorage.d.ts","sourceRoot":"","sources":["../src/billingStorage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,
|
|
1
|
+
{"version":3,"file":"billingStorage.d.ts","sourceRoot":"","sources":["../src/billingStorage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA8D/C,eAAO,MAAM,0BAA0B,GAAI,QAAQ;IACjD,GAAG,EAAE,YAAY,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB,KAAG,cAsEH,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { b, a, c, g, d, e, f } from "./can-B4pD_pnR.js";
|
|
|
3
3
|
import mongoose, { Schema as Schema$1, Types } from "mongoose";
|
|
4
4
|
import { default as default2 } from "mongoose";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { SUBSCRIPTION_STATUSES, SUBSCRIPTION_INTERVAL_UNITS, SUBSCRIPTION_TYPES, SUBSCRIPTION_SCOPES, SUBSCRIPTION_EVENT_TYPES } from "@rpcbase/billing";
|
|
6
|
+
import { SUBSCRIPTION_STATUSES, SUBSCRIPTION_INTERVAL_UNITS, SUBSCRIPTION_TYPES, SUBSCRIPTION_SCOPES, SUBSCRIPTION_EVENT_TYPES, SUBSCRIPTION_CHANGE_DIRECTIONS } from "@rpcbase/billing";
|
|
7
7
|
import { timingSafeEqual, createHmac } from "node:crypto";
|
|
8
8
|
import { w as withLocalizedStringFallback } from "./index-DrIoUXc2.js";
|
|
9
9
|
import { E, a as a2, L, b as b2, e as e2, m, r, z as z2, c as c2, d as d2, f as f2 } from "./index-DrIoUXc2.js";
|
|
@@ -173,16 +173,15 @@ const ZRBTenantSubscriptionType = z.enum(SUBSCRIPTION_TYPES);
|
|
|
173
173
|
const ZRBTenantSubscriptionScope = z.enum(SUBSCRIPTION_SCOPES);
|
|
174
174
|
const ZRBTenantSubscriptionEventType = z.enum(SUBSCRIPTION_EVENT_TYPES);
|
|
175
175
|
const ZRBTenantSubscriptionEventSource = z.enum(["admin", "system", "webhook", "user"]);
|
|
176
|
-
const ZRBTenantSubscriptionChangeDirection = z.enum(
|
|
177
|
-
const
|
|
178
|
-
tenantId: z.string(),
|
|
179
|
-
subscriptionId: z.string(),
|
|
180
|
-
type: ZRBTenantSubscriptionEventType,
|
|
176
|
+
const ZRBTenantSubscriptionChangeDirection = z.enum(SUBSCRIPTION_CHANGE_DIRECTIONS);
|
|
177
|
+
const subscriptionEventBaseSchema = z.object({
|
|
178
|
+
tenantId: z.string().trim().min(1),
|
|
179
|
+
subscriptionId: z.string().trim().min(1),
|
|
181
180
|
occurredAt: z.date(),
|
|
182
181
|
effectiveAt: z.date(),
|
|
183
|
-
subscriptionType: ZRBTenantSubscriptionType
|
|
182
|
+
subscriptionType: ZRBTenantSubscriptionType,
|
|
184
183
|
parentSubscriptionId: z.string().optional(),
|
|
185
|
-
scope: ZRBTenantSubscriptionScope
|
|
184
|
+
scope: ZRBTenantSubscriptionScope,
|
|
186
185
|
scopeId: z.string().optional(),
|
|
187
186
|
fromPlanKey: z.string().optional(),
|
|
188
187
|
toPlanKey: z.string().optional(),
|
|
@@ -198,7 +197,7 @@ const ZRBTenantSubscriptionEvent = z.object({
|
|
|
198
197
|
toPriceId: z.string().optional(),
|
|
199
198
|
billingAnchor: z.date().optional(),
|
|
200
199
|
supersedesEventId: z.string().optional(),
|
|
201
|
-
idempotencyKey: z.string().
|
|
200
|
+
idempotencyKey: z.string().trim().min(1),
|
|
202
201
|
direction: ZRBTenantSubscriptionChangeDirection.optional(),
|
|
203
202
|
actorUserId: z.string().optional(),
|
|
204
203
|
source: ZRBTenantSubscriptionEventSource.optional(),
|
|
@@ -208,6 +207,65 @@ const ZRBTenantSubscriptionEvent = z.object({
|
|
|
208
207
|
providerPayload: z.unknown().optional(),
|
|
209
208
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
210
209
|
});
|
|
210
|
+
const subscriptionCreatedEventSchema = subscriptionEventBaseSchema.extend({
|
|
211
|
+
type: z.literal("created"),
|
|
212
|
+
toPlanKey: z.string().trim().min(1),
|
|
213
|
+
toStatus: ZRBTenantSubscriptionStatus,
|
|
214
|
+
toModules: z.array(z.string()),
|
|
215
|
+
toIntervalUnit: ZRBTenantSubscriptionIntervalUnit,
|
|
216
|
+
toIntervalCount: z.number().int().min(1),
|
|
217
|
+
billingAnchor: z.date()
|
|
218
|
+
});
|
|
219
|
+
const subscriptionPlanChangedEventSchema = subscriptionEventBaseSchema.extend({
|
|
220
|
+
type: z.literal("plan_changed"),
|
|
221
|
+
fromPlanKey: z.string().trim().min(1),
|
|
222
|
+
toPlanKey: z.string().trim().min(1),
|
|
223
|
+
fromModules: z.array(z.string()),
|
|
224
|
+
toModules: z.array(z.string()),
|
|
225
|
+
fromIntervalUnit: ZRBTenantSubscriptionIntervalUnit,
|
|
226
|
+
toIntervalUnit: ZRBTenantSubscriptionIntervalUnit,
|
|
227
|
+
fromIntervalCount: z.number().int().min(1),
|
|
228
|
+
toIntervalCount: z.number().int().min(1)
|
|
229
|
+
});
|
|
230
|
+
const subscriptionStatusChangedEventSchema = subscriptionEventBaseSchema.extend({
|
|
231
|
+
type: z.literal("status_changed"),
|
|
232
|
+
fromStatus: ZRBTenantSubscriptionStatus,
|
|
233
|
+
toStatus: ZRBTenantSubscriptionStatus
|
|
234
|
+
});
|
|
235
|
+
const subscriptionResumedEventSchema = subscriptionEventBaseSchema.extend({
|
|
236
|
+
type: z.literal("resumed"),
|
|
237
|
+
fromStatus: ZRBTenantSubscriptionStatus,
|
|
238
|
+
toStatus: z.literal("active")
|
|
239
|
+
});
|
|
240
|
+
const subscriptionCanceledEventSchema = subscriptionEventBaseSchema.extend({
|
|
241
|
+
type: z.literal("canceled"),
|
|
242
|
+
fromStatus: ZRBTenantSubscriptionStatus,
|
|
243
|
+
toStatus: z.literal("canceled")
|
|
244
|
+
});
|
|
245
|
+
const subscriptionRenewedEventSchema = subscriptionEventBaseSchema.extend({
|
|
246
|
+
type: z.literal("renewed"),
|
|
247
|
+
fromStatus: ZRBTenantSubscriptionStatus,
|
|
248
|
+
toStatus: ZRBTenantSubscriptionStatus
|
|
249
|
+
});
|
|
250
|
+
const ZRBTenantSubscriptionEvent = z.discriminatedUnion("type", [subscriptionCreatedEventSchema, subscriptionPlanChangedEventSchema, subscriptionStatusChangedEventSchema, subscriptionResumedEventSchema, subscriptionCanceledEventSchema, subscriptionRenewedEventSchema]).superRefine((event, ctx) => {
|
|
251
|
+
if (event.subscriptionType === "addon" && !event.parentSubscriptionId?.trim()) {
|
|
252
|
+
ctx.addIssue({
|
|
253
|
+
code: "custom",
|
|
254
|
+
message: "parentSubscriptionId is required for add-on subscriptions",
|
|
255
|
+
path: ["parentSubscriptionId"]
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (event.scope !== "tenant" && !event.scopeId?.trim()) {
|
|
259
|
+
ctx.addIssue({
|
|
260
|
+
code: "custom",
|
|
261
|
+
message: "scopeId is required for non-tenant subscriptions",
|
|
262
|
+
path: ["scopeId"]
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
const requiredForEventTypes = (...types) => function requiredForEventType() {
|
|
267
|
+
return Boolean(this.type && types.includes(this.type));
|
|
268
|
+
};
|
|
211
269
|
const RBTenantSubscriptionEventSchema = new Schema$1({
|
|
212
270
|
tenantId: {
|
|
213
271
|
type: String,
|
|
@@ -235,54 +293,72 @@ const RBTenantSubscriptionEventSchema = new Schema$1({
|
|
|
235
293
|
},
|
|
236
294
|
subscriptionType: {
|
|
237
295
|
type: String,
|
|
296
|
+
required: true,
|
|
238
297
|
enum: ZRBTenantSubscriptionType.options
|
|
239
298
|
},
|
|
240
299
|
parentSubscriptionId: {
|
|
241
|
-
type: String
|
|
300
|
+
type: String,
|
|
301
|
+
required() {
|
|
302
|
+
return this.subscriptionType === "addon";
|
|
303
|
+
}
|
|
242
304
|
},
|
|
243
305
|
scope: {
|
|
244
306
|
type: String,
|
|
307
|
+
required: true,
|
|
245
308
|
enum: ZRBTenantSubscriptionScope.options
|
|
246
309
|
},
|
|
247
310
|
scopeId: {
|
|
248
|
-
type: String
|
|
311
|
+
type: String,
|
|
312
|
+
required() {
|
|
313
|
+
return Boolean(this.scope && this.scope !== "tenant");
|
|
314
|
+
}
|
|
249
315
|
},
|
|
250
316
|
fromPlanKey: {
|
|
251
|
-
type: String
|
|
317
|
+
type: String,
|
|
318
|
+
required: requiredForEventTypes("plan_changed")
|
|
252
319
|
},
|
|
253
320
|
toPlanKey: {
|
|
254
|
-
type: String
|
|
321
|
+
type: String,
|
|
322
|
+
required: requiredForEventTypes("created", "plan_changed")
|
|
255
323
|
},
|
|
256
324
|
fromStatus: {
|
|
257
325
|
type: String,
|
|
326
|
+
required: requiredForEventTypes("status_changed", "resumed", "canceled", "renewed"),
|
|
258
327
|
enum: ZRBTenantSubscriptionStatus.options
|
|
259
328
|
},
|
|
260
329
|
toStatus: {
|
|
261
330
|
type: String,
|
|
331
|
+
required: requiredForEventTypes("created", "status_changed", "resumed", "canceled", "renewed"),
|
|
262
332
|
enum: ZRBTenantSubscriptionStatus.options
|
|
263
333
|
},
|
|
264
334
|
fromModules: {
|
|
265
335
|
type: [String],
|
|
336
|
+
required: requiredForEventTypes("plan_changed"),
|
|
266
337
|
default: void 0
|
|
267
338
|
},
|
|
268
339
|
toModules: {
|
|
269
340
|
type: [String],
|
|
341
|
+
required: requiredForEventTypes("created", "plan_changed"),
|
|
270
342
|
default: void 0
|
|
271
343
|
},
|
|
272
344
|
fromIntervalUnit: {
|
|
273
345
|
type: String,
|
|
346
|
+
required: requiredForEventTypes("plan_changed"),
|
|
274
347
|
enum: ZRBTenantSubscriptionIntervalUnit.options
|
|
275
348
|
},
|
|
276
349
|
toIntervalUnit: {
|
|
277
350
|
type: String,
|
|
351
|
+
required: requiredForEventTypes("created", "plan_changed"),
|
|
278
352
|
enum: ZRBTenantSubscriptionIntervalUnit.options
|
|
279
353
|
},
|
|
280
354
|
fromIntervalCount: {
|
|
281
355
|
type: Number,
|
|
356
|
+
required: requiredForEventTypes("plan_changed"),
|
|
282
357
|
min: 1
|
|
283
358
|
},
|
|
284
359
|
toIntervalCount: {
|
|
285
360
|
type: Number,
|
|
361
|
+
required: requiredForEventTypes("created", "plan_changed"),
|
|
286
362
|
min: 1
|
|
287
363
|
},
|
|
288
364
|
fromPriceId: {
|
|
@@ -292,13 +368,15 @@ const RBTenantSubscriptionEventSchema = new Schema$1({
|
|
|
292
368
|
type: String
|
|
293
369
|
},
|
|
294
370
|
billingAnchor: {
|
|
295
|
-
type: Date
|
|
371
|
+
type: Date,
|
|
372
|
+
required: requiredForEventTypes("created")
|
|
296
373
|
},
|
|
297
374
|
supersedesEventId: {
|
|
298
375
|
type: String
|
|
299
376
|
},
|
|
300
377
|
idempotencyKey: {
|
|
301
|
-
type: String
|
|
378
|
+
type: String,
|
|
379
|
+
required: true
|
|
302
380
|
},
|
|
303
381
|
direction: {
|
|
304
382
|
type: String,
|
|
@@ -338,14 +416,20 @@ RBTenantSubscriptionEventSchema.index({
|
|
|
338
416
|
providerEventId: 1
|
|
339
417
|
}, {
|
|
340
418
|
unique: true,
|
|
341
|
-
|
|
419
|
+
partialFilterExpression: {
|
|
420
|
+
provider: {
|
|
421
|
+
$type: "string"
|
|
422
|
+
},
|
|
423
|
+
providerEventId: {
|
|
424
|
+
$type: "string"
|
|
425
|
+
}
|
|
426
|
+
}
|
|
342
427
|
});
|
|
343
428
|
RBTenantSubscriptionEventSchema.index({
|
|
344
429
|
tenantId: 1,
|
|
345
430
|
idempotencyKey: 1
|
|
346
431
|
}, {
|
|
347
|
-
unique: true
|
|
348
|
-
sparse: true
|
|
432
|
+
unique: true
|
|
349
433
|
});
|
|
350
434
|
const ZRBTenantSubscriptionProjection = z.object({
|
|
351
435
|
tenantId: z.string(),
|
|
@@ -2243,26 +2327,16 @@ const getTransactionSession = (transaction) => {
|
|
|
2243
2327
|
};
|
|
2244
2328
|
const toSubscriptionEvent = (document) => {
|
|
2245
2329
|
const parsed = ZRBTenantSubscriptionEvent.parse(document);
|
|
2330
|
+
if (!document._id) throw new Error("Persisted subscription event is missing its id");
|
|
2246
2331
|
return {
|
|
2247
2332
|
...parsed,
|
|
2248
|
-
id:
|
|
2333
|
+
id: String(document._id)
|
|
2249
2334
|
};
|
|
2250
2335
|
};
|
|
2251
|
-
const getExistingEventQuery = (event, tenantId) => {
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
idempotencyKey: event.idempotencyKey
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2258
|
-
if (event.provider && event.providerEventId) {
|
|
2259
|
-
return {
|
|
2260
|
-
provider: event.provider,
|
|
2261
|
-
providerEventId: event.providerEventId
|
|
2262
|
-
};
|
|
2263
|
-
}
|
|
2264
|
-
return null;
|
|
2265
|
-
};
|
|
2336
|
+
const getExistingEventQuery = (event, tenantId) => ({
|
|
2337
|
+
tenantId,
|
|
2338
|
+
idempotencyKey: event.idempotencyKey
|
|
2339
|
+
});
|
|
2266
2340
|
const isDuplicateKeyError = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === 11e3);
|
|
2267
2341
|
const toProjectionDocument = (projection) => ({
|
|
2268
2342
|
tenantId: projection.tenantId,
|
|
@@ -2308,16 +2382,14 @@ const createTenantBillingStorage = (params) => {
|
|
|
2308
2382
|
session
|
|
2309
2383
|
})),
|
|
2310
2384
|
createEvent: async (event, transaction) => {
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
const existing = await findExistingEvent(existingQuery, transaction);
|
|
2314
|
-
if (existing) return existing;
|
|
2385
|
+
if (event.tenantId !== params.tenantId) {
|
|
2386
|
+
throw new Error("Billing event tenantId does not match the storage tenant");
|
|
2315
2387
|
}
|
|
2388
|
+
const existingQuery = getExistingEventQuery(event, params.tenantId);
|
|
2389
|
+
const existing = await findExistingEvent(existingQuery, transaction);
|
|
2390
|
+
if (existing) return existing;
|
|
2316
2391
|
const EventModel = await models.get("RBTenantSubscriptionEvent", params.ctx);
|
|
2317
|
-
const eventDocument = ZRBTenantSubscriptionEvent.parse(
|
|
2318
|
-
...event,
|
|
2319
|
-
tenantId: params.tenantId
|
|
2320
|
-
});
|
|
2392
|
+
const eventDocument = ZRBTenantSubscriptionEvent.parse(event);
|
|
2321
2393
|
const session = getTransactionSession(transaction);
|
|
2322
2394
|
try {
|
|
2323
2395
|
const documents = await EventModel.create([eventDocument], session ? {
|
|
@@ -2327,10 +2399,10 @@ const createTenantBillingStorage = (params) => {
|
|
|
2327
2399
|
if (!document) throw new Error("Subscription event was not persisted");
|
|
2328
2400
|
return toSubscriptionEvent(document);
|
|
2329
2401
|
} catch (error) {
|
|
2330
|
-
if (!
|
|
2331
|
-
const
|
|
2332
|
-
if (!
|
|
2333
|
-
return
|
|
2402
|
+
if (!isDuplicateKeyError(error)) throw error;
|
|
2403
|
+
const duplicate = await findExistingEvent(existingQuery, transaction);
|
|
2404
|
+
if (!duplicate) throw error;
|
|
2405
|
+
return duplicate;
|
|
2334
2406
|
}
|
|
2335
2407
|
},
|
|
2336
2408
|
listEvents: async (tenantId, subscriptionId, transaction) => {
|