@saasicat/nest 1.0.0-rc.2 → 1.0.0-rc.21
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.
- package/README.md +44 -8
- package/dist/.build-stamp +1 -1
- package/dist/_entries.cjs +10214 -4796
- package/dist/admin/index.d.cts +5 -4
- package/dist/admin/index.d.ts +5 -4
- package/dist/admin/index.js +13 -9
- package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
- package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
- package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
- package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
- package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
- package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
- package/dist/billing/index.d.cts +448 -108
- package/dist/billing/index.d.ts +448 -108
- package/dist/billing/index.js +108 -41
- package/dist/catalog/index.d.cts +74 -45
- package/dist/catalog/index.d.ts +74 -45
- package/dist/catalog/index.js +21 -13
- package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
- package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
- package/dist/checkout-offer/index.d.cts +17 -22
- package/dist/checkout-offer/index.d.ts +17 -22
- package/dist/checkout-offer/index.js +21 -7
- package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
- package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
- package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
- package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
- package/dist/chunk-34XVPR6P.js +222 -0
- package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
- package/dist/chunk-3RLWML2S.js +896 -0
- package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
- package/dist/chunk-55QEDAMX.js +946 -0
- package/dist/chunk-5CKC7O52.js +140 -0
- package/dist/chunk-6PJE7EWO.js +185 -0
- package/dist/chunk-7NJKVC5X.js +353 -0
- package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
- package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
- package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
- package/dist/chunk-FEQZCWUL.js +174 -0
- package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
- package/dist/chunk-G7RQO2XO.js +0 -0
- package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
- package/dist/chunk-GZTL64QK.js +7 -0
- package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
- package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
- package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
- package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
- package/dist/chunk-KPEMTBOP.js +26 -0
- package/dist/chunk-LF6J4YYN.js +301 -0
- package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
- package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
- package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
- package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
- package/dist/chunk-QPVDCKYS.js +702 -0
- package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
- package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
- package/dist/chunk-SZ7RFPXA.js +10 -0
- package/dist/chunk-TBBZWZQT.js +98 -0
- package/dist/chunk-TOEFN7DN.js +295 -0
- package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
- package/dist/chunk-WOEJ6K7M.js +55 -0
- package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
- package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
- package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
- package/dist/discovery/index.d.cts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/discovery/index.js +6 -6
- package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
- package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
- package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
- package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
- package/dist/entitlement/index.d.cts +27 -4
- package/dist/entitlement/index.d.ts +27 -4
- package/dist/entitlement/index.js +13 -8
- package/dist/index.d.cts +209 -28
- package/dist/index.d.ts +209 -28
- package/dist/index.js +285 -163
- package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
- package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
- package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
- package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
- package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
- package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
- package/dist/payments/index.cjs +4 -0
- package/dist/payments/index.d.cts +187 -0
- package/dist/payments/index.d.ts +187 -0
- package/dist/payments/index.js +141 -0
- package/dist/payments.module-BdUq9ot_.d.cts +57 -0
- package/dist/payments.module-CQGv_VG7.d.ts +57 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
- package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
- package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
- package/dist/platform/index.d.cts +52 -23
- package/dist/platform/index.d.ts +52 -23
- package/dist/platform/index.js +57 -36
- package/dist/promo/index.d.cts +23 -8
- package/dist/promo/index.d.ts +23 -8
- package/dist/promo/index.js +15 -7
- package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
- package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
- package/dist/promo.service-BYBu6dy3.d.cts +215 -0
- package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
- package/dist/registration/index.d.cts +104 -47
- package/dist/registration/index.d.ts +104 -47
- package/dist/registration/index.js +15 -9
- package/dist/subscriber/index.cjs +4 -0
- package/dist/subscriber/index.d.cts +28 -0
- package/dist/subscriber/index.d.ts +28 -0
- package/dist/subscriber/index.js +16 -0
- package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
- package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
- package/dist/subscription-contract/index.d.cts +4 -2
- package/dist/subscription-contract/index.d.ts +4 -2
- package/dist/subscription-contract/index.js +13 -5
- package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
- package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
- package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
- package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
- package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
- package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
- package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
- package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
- package/dist/testing/index.d.cts +44 -17
- package/dist/testing/index.d.ts +44 -17
- package/dist/testing/index.js +189 -40
- package/package.json +24 -4
- package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
- package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
- package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
- package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
- package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
- package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
- package/dist/chunk-57V6ZTI6.js +0 -22
- package/dist/chunk-BSK6YBLI.js +0 -87
- package/dist/chunk-DQKCK7DX.js +0 -217
- package/dist/chunk-VXEYLNIB.js +0 -632
- package/dist/chunk-WUDYIPYH.js +0 -715
- package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
- package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
- package/dist/promo.service-BJbKAmw3.d.cts +0 -112
- package/dist/promo.service-BJbKAmw3.d.ts +0 -112
- package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
- package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
- package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
- package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
- /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
- /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
- /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
- /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FEATURE_GUARD_MARKER,
|
|
3
|
+
PLAN_CATALOG_UNREADABLE_ERROR,
|
|
3
4
|
PUBLIC_CATALOG_BUNDLE_REPOSITORY_TOKEN,
|
|
4
5
|
PUBLIC_CATALOG_CATALOG_ENTRY_REPOSITORY_TOKEN,
|
|
5
|
-
|
|
6
|
+
PlanCatalogValidationError,
|
|
6
7
|
REQUIRE_FEATURE_KEY,
|
|
8
|
+
loadPlanCatalogFromString,
|
|
7
9
|
periodEndAfter
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-M47BNEY2.js";
|
|
11
|
+
import {
|
|
12
|
+
EnforceMfa
|
|
13
|
+
} from "./chunk-34XVPR6P.js";
|
|
9
14
|
import {
|
|
10
15
|
EntitlementService,
|
|
11
16
|
isLimitExceededError
|
|
12
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-G6EZWECL.js";
|
|
13
18
|
import {
|
|
14
19
|
ENTITLEMENT_SERVICE_TOKEN
|
|
15
20
|
} from "./chunk-OUDSQ3PU.js";
|
|
16
21
|
import {
|
|
17
22
|
asProvider
|
|
18
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-J7NJRK3K.js";
|
|
19
24
|
import {
|
|
20
25
|
__name
|
|
21
26
|
} from "./chunk-SHUYVCID.js";
|
|
@@ -71,6 +76,8 @@ __name(assertOptimisticLockHeld, "assertOptimisticLockHeld");
|
|
|
71
76
|
function decideRenewal(sub, now) {
|
|
72
77
|
if (!sub.pendingPlanVersionId || !sub.pendingPlanVersionEffectiveAt) return "SKIP";
|
|
73
78
|
if (sub.pendingPlanVersionEffectiveAt > now) return "SKIP";
|
|
79
|
+
const landedAt = sub.canceledEffectiveAt ?? sub.canceledAt;
|
|
80
|
+
if (landedAt !== null && landedAt <= now) return "SKIP";
|
|
74
81
|
if (sub.pendingPlanVersionNonRegressive || sub.pendingPlanVersionAccepted) {
|
|
75
82
|
return "ROLL_FORWARD";
|
|
76
83
|
}
|
|
@@ -90,14 +97,16 @@ function clearPendingPlanVersionFields() {
|
|
|
90
97
|
}
|
|
91
98
|
__name(clearPendingPlanVersionFields, "clearPendingPlanVersionFields");
|
|
92
99
|
function computeNextPeriod(sub, now) {
|
|
93
|
-
if (sub.canceledAt !== null) return null;
|
|
94
100
|
if (sub.currentPeriodEnd === null) return null;
|
|
95
101
|
if (sub.currentPeriodEnd > now) return null;
|
|
102
|
+
const landedAt = sub.canceledEffectiveAt ?? sub.canceledAt;
|
|
103
|
+
if (landedAt !== null && landedAt <= now) return null;
|
|
96
104
|
const newStart = sub.currentPeriodEnd;
|
|
97
|
-
const newEnd = periodEndAfter(newStart, sub.billingCycle, now);
|
|
105
|
+
const newEnd = periodEndAfter(newStart, sub.billingCycle, now, sub.billingAnchorDay ?? void 0);
|
|
98
106
|
return {
|
|
99
107
|
currentPeriodStart: newStart,
|
|
100
|
-
currentPeriodEnd: newEnd
|
|
108
|
+
currentPeriodEnd: newEnd,
|
|
109
|
+
minimumTermUntil: newEnd
|
|
101
110
|
};
|
|
102
111
|
}
|
|
103
112
|
__name(computeNextPeriod, "computeNextPeriod");
|
|
@@ -261,12 +270,10 @@ var CatalogBundleUpsellResolver = class {
|
|
|
261
270
|
__name(this, "CatalogBundleUpsellResolver");
|
|
262
271
|
}
|
|
263
272
|
bundleRepo;
|
|
264
|
-
projectKey;
|
|
265
273
|
catalogEntryRepo;
|
|
266
274
|
currency;
|
|
267
|
-
constructor(bundleRepo,
|
|
275
|
+
constructor(bundleRepo, catalogEntryRepo = null, currency = null) {
|
|
268
276
|
this.bundleRepo = bundleRepo;
|
|
269
|
-
this.projectKey = projectKey;
|
|
270
277
|
this.catalogEntryRepo = catalogEntryRepo;
|
|
271
278
|
this.currency = currency;
|
|
272
279
|
}
|
|
@@ -296,15 +303,12 @@ var CatalogBundleUpsellResolver = class {
|
|
|
296
303
|
*/
|
|
297
304
|
async lookupUnmetRequires(featureKeys) {
|
|
298
305
|
if (!this.catalogEntryRepo) return [];
|
|
299
|
-
const rows = await this.catalogEntryRepo.listFeatures({
|
|
300
|
-
projectKey: this.projectKey
|
|
301
|
-
});
|
|
306
|
+
const rows = await this.catalogEntryRepo.listFeatures({});
|
|
302
307
|
return collectUnsatisfiedRequires(featureKeys, buildFeatureRequiresIndex(rows));
|
|
303
308
|
}
|
|
304
309
|
/** Only published-and-live AND marketed — non-marketed bundles are not an offer. */
|
|
305
310
|
async listLiveMarketedBundles() {
|
|
306
311
|
const stems = await this.bundleRepo.list({
|
|
307
|
-
projectKey: this.projectKey,
|
|
308
312
|
excludeDeleted: true
|
|
309
313
|
});
|
|
310
314
|
const out = [];
|
|
@@ -327,15 +331,13 @@ var CatalogBundleUpsellResolver = class {
|
|
|
327
331
|
CatalogBundleUpsellResolver = _ts_decorate2([
|
|
328
332
|
Injectable2(),
|
|
329
333
|
_ts_param2(0, Inject2(PUBLIC_CATALOG_BUNDLE_REPOSITORY_TOKEN)),
|
|
330
|
-
_ts_param2(1,
|
|
334
|
+
_ts_param2(1, Optional2()),
|
|
335
|
+
_ts_param2(1, Inject2(PUBLIC_CATALOG_CATALOG_ENTRY_REPOSITORY_TOKEN)),
|
|
331
336
|
_ts_param2(2, Optional2()),
|
|
332
|
-
_ts_param2(2, Inject2(
|
|
333
|
-
_ts_param2(3, Optional2()),
|
|
334
|
-
_ts_param2(3, Inject2(UPSELL_OFFER_CURRENCY_TOKEN)),
|
|
337
|
+
_ts_param2(2, Inject2(UPSELL_OFFER_CURRENCY_TOKEN)),
|
|
335
338
|
_ts_metadata2("design:type", Function),
|
|
336
339
|
_ts_metadata2("design:paramtypes", [
|
|
337
340
|
typeof BundleRepository === "undefined" ? Object : BundleRepository,
|
|
338
|
-
String,
|
|
339
341
|
Object,
|
|
340
342
|
Object
|
|
341
343
|
])
|
|
@@ -398,82 +400,6 @@ LimitExceededFilter = _ts_decorate3([
|
|
|
398
400
|
Catch()
|
|
399
401
|
], LimitExceededFilter);
|
|
400
402
|
|
|
401
|
-
// src/billing/plan-catalog-loader.ts
|
|
402
|
-
import { readFileSync } from "fs";
|
|
403
|
-
import { resolve as resolvePath } from "path";
|
|
404
|
-
import yaml from "js-yaml";
|
|
405
|
-
import Ajv2020 from "ajv/dist/2020.js";
|
|
406
|
-
import addFormats from "ajv-formats";
|
|
407
|
-
import { planCatalogSchema } from "@saasicat/spec";
|
|
408
|
-
var PlanCatalogValidationError = class extends Error {
|
|
409
|
-
static {
|
|
410
|
-
__name(this, "PlanCatalogValidationError");
|
|
411
|
-
}
|
|
412
|
-
source;
|
|
413
|
-
errors;
|
|
414
|
-
constructor(source, errors) {
|
|
415
|
-
const messages = errors.map((e) => `${e.instancePath || "/"}: ${e.message ?? "unknown"}`).join("\n ");
|
|
416
|
-
super(`Plan catalog validation failed for ${source}:
|
|
417
|
-
${messages}`), this.source = source, this.errors = errors;
|
|
418
|
-
this.name = "PlanCatalogValidationError";
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
function loadPlanCatalogFromFile(opts) {
|
|
422
|
-
const absolutePath = resolvePath(opts.path);
|
|
423
|
-
const raw = readFileSync(absolutePath, "utf-8");
|
|
424
|
-
return loadPlanCatalogFromString(raw, {
|
|
425
|
-
source: absolutePath,
|
|
426
|
-
crossFieldChecks: opts.crossFieldChecks ?? true
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
__name(loadPlanCatalogFromFile, "loadPlanCatalogFromFile");
|
|
430
|
-
function loadPlanCatalogFromString(yamlContent, opts) {
|
|
431
|
-
const parsed = yaml.load(yamlContent);
|
|
432
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
433
|
-
throw new Error(`The YAML content of ${opts.source} is not an object`);
|
|
434
|
-
}
|
|
435
|
-
const ajv = new Ajv2020({
|
|
436
|
-
strict: false,
|
|
437
|
-
allErrors: true
|
|
438
|
-
});
|
|
439
|
-
addFormats.default(ajv);
|
|
440
|
-
const validate = ajv.compile(planCatalogSchema);
|
|
441
|
-
if (!validate(parsed)) {
|
|
442
|
-
throw new PlanCatalogValidationError(opts.source, validate.errors ?? []);
|
|
443
|
-
}
|
|
444
|
-
const catalog = parsed;
|
|
445
|
-
if (opts.crossFieldChecks ?? true) {
|
|
446
|
-
validateConsistency(catalog, opts.source);
|
|
447
|
-
}
|
|
448
|
-
return catalog;
|
|
449
|
-
}
|
|
450
|
-
__name(loadPlanCatalogFromString, "loadPlanCatalogFromString");
|
|
451
|
-
function validateConsistency(catalog, source) {
|
|
452
|
-
const errors = [];
|
|
453
|
-
const declaredFeatureKeys = new Set((catalog.features ?? []).map((f) => f.key));
|
|
454
|
-
const planIds = /* @__PURE__ */ new Set();
|
|
455
|
-
for (const plan of catalog.plans ?? []) {
|
|
456
|
-
if (planIds.has(plan.id)) {
|
|
457
|
-
errors.push(`plans[].id: duplicate plan id "${plan.id}"`);
|
|
458
|
-
}
|
|
459
|
-
planIds.add(plan.id);
|
|
460
|
-
if (catalog.features) {
|
|
461
|
-
for (const fk of plan.features) {
|
|
462
|
-
if (!declaredFeatureKeys.has(fk)) {
|
|
463
|
-
errors.push(`plans[id=${plan.id}].features: Unknown featureKey "${fk}" \u2014 not declared in catalog.features`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
if (errors.length > 0) {
|
|
469
|
-
const err = new Error(`Plan catalog consistency error in ${source}:
|
|
470
|
-
${errors.join("\n ")}`);
|
|
471
|
-
err.name = "PlanCatalogConsistencyError";
|
|
472
|
-
throw err;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
__name(validateConsistency, "validateConsistency");
|
|
476
|
-
|
|
477
403
|
// src/billing/plan-catalog-importer.service.ts
|
|
478
404
|
import { Inject as Inject3, Injectable as Injectable3 } from "@nestjs/common";
|
|
479
405
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
@@ -529,7 +455,6 @@ var PlanCatalogImporterService = class {
|
|
|
529
455
|
};
|
|
530
456
|
for (const feature of catalog.features ?? []) {
|
|
531
457
|
const result = await this.sink.upsertFeatureCatalogEntry({
|
|
532
|
-
projectKey: catalog.projectKey,
|
|
533
458
|
featureKey: feature.key,
|
|
534
459
|
label: feature.label,
|
|
535
460
|
icon: feature.icon,
|
|
@@ -541,7 +466,6 @@ var PlanCatalogImporterService = class {
|
|
|
541
466
|
}
|
|
542
467
|
for (const plan of catalog.plans ?? []) {
|
|
543
468
|
const planResult = await this.sink.upsertPlan({
|
|
544
|
-
projectKey: catalog.projectKey,
|
|
545
469
|
planKey: plan.id,
|
|
546
470
|
label: plan.name ?? plan.id,
|
|
547
471
|
description: plan.tagline ?? null
|
|
@@ -552,13 +476,17 @@ var PlanCatalogImporterService = class {
|
|
|
552
476
|
report.warnings.push(`Plan '${plan.id}' has no monthlyNet \u2014 skipped (on-request plans need a manual plan version).`);
|
|
553
477
|
continue;
|
|
554
478
|
}
|
|
479
|
+
if (plan.yearlyNet === null || plan.yearlyNet === void 0) {
|
|
480
|
+
report.warnings.push(`Plan '${plan.id}' has no yearlyNet \u2014 skipped (a plan version needs a yearly price; set one in the catalog or publish the version manually).`);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
555
483
|
const versionResult = await this.sink.upsertPlanVersion({
|
|
556
484
|
planKey: plan.id,
|
|
557
485
|
version: 1,
|
|
558
486
|
features: plan.features,
|
|
559
487
|
quotas: plan.quotas,
|
|
560
488
|
monthlyNet: plan.monthlyNet.toFixed(2),
|
|
561
|
-
yearlyNet:
|
|
489
|
+
yearlyNet: plan.yearlyNet.toFixed(2),
|
|
562
490
|
marketed: plan.marketed ?? true,
|
|
563
491
|
publish: true,
|
|
564
492
|
changeNote: IMPORT_CHANGE_NOTE
|
|
@@ -582,8 +510,9 @@ PlanCatalogImporterService = _ts_decorate4([
|
|
|
582
510
|
import { Module } from "@nestjs/common";
|
|
583
511
|
|
|
584
512
|
// src/billing/plan-catalog-importer.controller.ts
|
|
585
|
-
import { Body, Controller, Inject as Inject4, Post, UseGuards } from "@nestjs/common";
|
|
513
|
+
import { BadRequestException, Body, Controller, Inject as Inject4, Post, UseGuards } from "@nestjs/common";
|
|
586
514
|
import { IsBoolean, IsOptional, IsString, MinLength } from "class-validator";
|
|
515
|
+
import { CATALOG_ERROR_CODES } from "@saasicat/core";
|
|
587
516
|
function _ts_decorate5(decorators, target, key, desc) {
|
|
588
517
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
589
518
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -618,6 +547,15 @@ _ts_decorate5([
|
|
|
618
547
|
IsBoolean(),
|
|
619
548
|
_ts_metadata4("design:type", Boolean)
|
|
620
549
|
], PlanCatalogImportDto.prototype, "crossFieldChecks", void 0);
|
|
550
|
+
var UNREADABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
551
|
+
"YAMLException",
|
|
552
|
+
"YAMLParseError",
|
|
553
|
+
PLAN_CATALOG_UNREADABLE_ERROR
|
|
554
|
+
]);
|
|
555
|
+
function isUnreadableDocument(error) {
|
|
556
|
+
return error instanceof Error && UNREADABLE_ERROR_NAMES.has(error.name);
|
|
557
|
+
}
|
|
558
|
+
__name(isUnreadableDocument, "isUnreadableDocument");
|
|
621
559
|
function buildPlanCatalogImporterController(guards) {
|
|
622
560
|
let GeneratedPlanCatalogImporterController = class GeneratedPlanCatalogImporterController {
|
|
623
561
|
static {
|
|
@@ -627,14 +565,45 @@ function buildPlanCatalogImporterController(guards) {
|
|
|
627
565
|
constructor(service) {
|
|
628
566
|
this.service = service;
|
|
629
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* A body that does not parse or does not validate is the caller's
|
|
570
|
+
* mistake, so it answers 400.
|
|
571
|
+
*
|
|
572
|
+
* Without this it answered 500: the loader throws a plain `Error` for
|
|
573
|
+
* unparseable YAML and a `PlanCatalogValidationError` for a schema or
|
|
574
|
+
* cross-field violation, and neither is an HTTP exception. A caller
|
|
575
|
+
* cannot tell a bad upload from a broken server that way, and the one
|
|
576
|
+
* they can fix is the one that looked unfixable.
|
|
577
|
+
*/
|
|
630
578
|
async import(dto) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
579
|
+
try {
|
|
580
|
+
return await this.service.importFromYaml(dto.yamlContent, {
|
|
581
|
+
crossFieldChecks: dto.crossFieldChecks
|
|
582
|
+
});
|
|
583
|
+
} catch (error) {
|
|
584
|
+
if (error instanceof PlanCatalogValidationError) {
|
|
585
|
+
throw new BadRequestException({
|
|
586
|
+
code: CATALOG_ERROR_CODES.PLAN_CATALOG_INVALID,
|
|
587
|
+
message: error.message,
|
|
588
|
+
params: {
|
|
589
|
+
message: error.message,
|
|
590
|
+
errors: error.errors
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
if (isUnreadableDocument(error)) {
|
|
595
|
+
throw new BadRequestException({
|
|
596
|
+
code: CATALOG_ERROR_CODES.PLAN_CATALOG_UNREADABLE,
|
|
597
|
+
message: error.message
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
throw error;
|
|
601
|
+
}
|
|
634
602
|
}
|
|
635
603
|
};
|
|
636
604
|
_ts_decorate5([
|
|
637
605
|
Post("import"),
|
|
606
|
+
EnforceMfa(),
|
|
638
607
|
_ts_param4(0, Body()),
|
|
639
608
|
_ts_metadata4("design:type", Function),
|
|
640
609
|
_ts_metadata4("design:paramtypes", [
|
|
@@ -724,7 +693,6 @@ var ConfiguratorCatalogBuilder = class {
|
|
|
724
693
|
m
|
|
725
694
|
]));
|
|
726
695
|
return {
|
|
727
|
-
cycleDiscount: marketing.getCycleDiscount(),
|
|
728
696
|
currency: marketing.getCurrency(),
|
|
729
697
|
vatRate: marketing.getVatRate(),
|
|
730
698
|
models: planRows.filter((row) => row.marketed).map((row) => buildModel(row, planMarketingByPlanId)).filter((m) => m !== null)
|
|
@@ -779,9 +747,6 @@ export {
|
|
|
779
747
|
FeatureGuard,
|
|
780
748
|
CatalogBundleUpsellResolver,
|
|
781
749
|
LimitExceededFilter,
|
|
782
|
-
PlanCatalogValidationError,
|
|
783
|
-
loadPlanCatalogFromFile,
|
|
784
|
-
loadPlanCatalogFromString,
|
|
785
750
|
PLAN_CATALOG_IMPORT_SINK_TOKEN,
|
|
786
751
|
PlanCatalogImporterService,
|
|
787
752
|
PlanCatalogImportDto,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/admin/admin.tokens.ts
|
|
2
|
+
var MFA_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/MfaPort");
|
|
3
|
+
var AUDIT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/AuditPort");
|
|
4
|
+
var RLS_BYPASS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/RlsBypassPort");
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
MFA_PORT_TOKEN,
|
|
8
|
+
AUDIT_PORT_TOKEN,
|
|
9
|
+
RLS_BYPASS_PORT_TOKEN
|
|
10
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertTaxRatePercent,
|
|
3
|
+
grossFromNet
|
|
4
|
+
} from "./chunk-6PJE7EWO.js";
|
|
5
|
+
import {
|
|
6
|
+
__name
|
|
7
|
+
} from "./chunk-SHUYVCID.js";
|
|
8
|
+
|
|
9
|
+
// src/billing/plan-helpers.ts
|
|
10
|
+
import { BILLING_ERROR_CODES } from "@saasicat/core";
|
|
11
|
+
function findPlan(catalog, planId) {
|
|
12
|
+
return (catalog.plans ?? []).find((p) => p.id === planId);
|
|
13
|
+
}
|
|
14
|
+
__name(findPlan, "findPlan");
|
|
15
|
+
function getPlanOrThrow(catalog, planId) {
|
|
16
|
+
const plan = findPlan(catalog, planId);
|
|
17
|
+
if (!plan) {
|
|
18
|
+
throw new Error(`Plan "${planId}" is not in the catalog`);
|
|
19
|
+
}
|
|
20
|
+
return plan;
|
|
21
|
+
}
|
|
22
|
+
__name(getPlanOrThrow, "getPlanOrThrow");
|
|
23
|
+
function getMarketedPlans(catalog) {
|
|
24
|
+
return (catalog.plans ?? []).filter((p) => p.marketed !== false);
|
|
25
|
+
}
|
|
26
|
+
__name(getMarketedPlans, "getMarketedPlans");
|
|
27
|
+
function getPlanPriceNet(catalog, planId, cycle) {
|
|
28
|
+
const plan = findPlan(catalog, planId);
|
|
29
|
+
return plan ? listPriceNet(plan, cycle) : null;
|
|
30
|
+
}
|
|
31
|
+
__name(getPlanPriceNet, "getPlanPriceNet");
|
|
32
|
+
function listPriceNet(plan, cycle) {
|
|
33
|
+
if (plan.marketed === false) return null;
|
|
34
|
+
const net = cycle === "YEARLY" ? plan.yearlyNet : plan.monthlyNet;
|
|
35
|
+
return net ?? null;
|
|
36
|
+
}
|
|
37
|
+
__name(listPriceNet, "listPriceNet");
|
|
38
|
+
function isPlanNotSoldInCycle(plan, cycle) {
|
|
39
|
+
if (plan.marketed === false) return false;
|
|
40
|
+
return (cycle === "YEARLY" ? plan.yearlyNet : plan.monthlyNet) == null;
|
|
41
|
+
}
|
|
42
|
+
__name(isPlanNotSoldInCycle, "isPlanNotSoldInCycle");
|
|
43
|
+
function planNotSoldInCycle(plan, cycle) {
|
|
44
|
+
const planName = plan.name ?? plan.id;
|
|
45
|
+
return {
|
|
46
|
+
code: BILLING_ERROR_CODES.PLAN_NOT_SOLD_IN_CYCLE,
|
|
47
|
+
message: `${planName} has no price for this billing rhythm and cannot be booked in it.`,
|
|
48
|
+
params: {
|
|
49
|
+
planName,
|
|
50
|
+
planKey: plan.id,
|
|
51
|
+
billingCycle: cycle
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
__name(planNotSoldInCycle, "planNotSoldInCycle");
|
|
56
|
+
function getPlanPriceGross(catalog, planId, cycle, vatRate) {
|
|
57
|
+
const net = getPlanPriceNet(catalog, planId, cycle);
|
|
58
|
+
if (net === null) return null;
|
|
59
|
+
const rate = vatRate ?? catalog.vatRate;
|
|
60
|
+
assertTaxRatePercent(vatRate === void 0 ? "catalog.vatRate" : "vatRate", rate);
|
|
61
|
+
return grossFromNet(net, rate);
|
|
62
|
+
}
|
|
63
|
+
__name(getPlanPriceGross, "getPlanPriceGross");
|
|
64
|
+
function isFeatureInPlan(catalog, planId, featureKey) {
|
|
65
|
+
const plan = findPlan(catalog, planId);
|
|
66
|
+
if (!plan) return false;
|
|
67
|
+
return plan.features.includes(featureKey);
|
|
68
|
+
}
|
|
69
|
+
__name(isFeatureInPlan, "isFeatureInPlan");
|
|
70
|
+
function getPlanQuota(catalog, planId, quotaKey) {
|
|
71
|
+
const plan = findPlan(catalog, planId);
|
|
72
|
+
return plan?.quotas[quotaKey];
|
|
73
|
+
}
|
|
74
|
+
__name(getPlanQuota, "getPlanQuota");
|
|
75
|
+
function getActiveFeatureKeys(catalog) {
|
|
76
|
+
return (catalog.features ?? []).filter((f) => !f.plannedOnly).map((f) => f.key);
|
|
77
|
+
}
|
|
78
|
+
__name(getActiveFeatureKeys, "getActiveFeatureKeys");
|
|
79
|
+
function isFeaturePlannedOnly(catalog, featureKey) {
|
|
80
|
+
const def = (catalog.features ?? []).find((f) => f.key === featureKey);
|
|
81
|
+
return def?.plannedOnly === true;
|
|
82
|
+
}
|
|
83
|
+
__name(isFeaturePlannedOnly, "isFeaturePlannedOnly");
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
findPlan,
|
|
87
|
+
getPlanOrThrow,
|
|
88
|
+
getMarketedPlans,
|
|
89
|
+
getPlanPriceNet,
|
|
90
|
+
listPriceNet,
|
|
91
|
+
isPlanNotSoldInCycle,
|
|
92
|
+
planNotSoldInCycle,
|
|
93
|
+
getPlanPriceGross,
|
|
94
|
+
isFeatureInPlan,
|
|
95
|
+
getPlanQuota,
|
|
96
|
+
getActiveFeatureKeys,
|
|
97
|
+
isFeaturePlannedOnly
|
|
98
|
+
};
|