@saasicat/nest 1.0.0-rc.8 → 1.0.0-rc.9

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.
Files changed (35) hide show
  1. package/dist/.build-stamp +1 -1
  2. package/dist/_entries.cjs +56 -17
  3. package/dist/billing/index.d.cts +20 -5
  4. package/dist/billing/index.d.ts +20 -5
  5. package/dist/billing/index.js +7 -5
  6. package/dist/{chunk-IW5XGOEU.js → chunk-5LE5AZUP.js} +1 -1
  7. package/dist/{chunk-7JEXCHMU.js → chunk-6FQ3G4IF.js} +1 -1
  8. package/dist/{chunk-GHIECNF3.js → chunk-FVD4TGER.js} +10 -3
  9. package/dist/{chunk-UUFUSKX2.js → chunk-H7SF4EUR.js} +2 -0
  10. package/dist/{chunk-JNONSUP3.js → chunk-HFQNSOIU.js} +1 -1
  11. package/dist/{chunk-DQW3DHSD.js → chunk-KBRELZT4.js} +1 -1
  12. package/dist/{chunk-2BNZK22P.js → chunk-OI7P6ZED.js} +7 -6
  13. package/dist/{chunk-TJ5E42WX.js → chunk-W67WBRW5.js} +46 -18
  14. package/dist/{define-saasicat-DVKyc8oT.d.ts → define-saasicat-DKyoOmEC.d.ts} +1 -1
  15. package/dist/{define-saasicat-BrlUxP9g.d.cts → define-saasicat-j-CoHjoG.d.cts} +1 -1
  16. package/dist/{enforce-quota.interceptor-DEAyTI-x.d.ts → enforce-quota.interceptor-CLPZKiJC.d.ts} +2 -2
  17. package/dist/{enforce-quota.interceptor-DvuVXX_y.d.cts → enforce-quota.interceptor-CLS6xJ-E.d.cts} +2 -2
  18. package/dist/entitlement/index.js +3 -3
  19. package/dist/index.d.cts +6 -6
  20. package/dist/index.d.ts +6 -6
  21. package/dist/index.js +11 -9
  22. package/dist/{module-options-5-kVLwuv.d.cts → module-options-BDb8Tam7.d.cts} +8 -1
  23. package/dist/{module-options-DVmppjem.d.ts → module-options-DF3H6q3A.d.ts} +8 -1
  24. package/dist/platform/index.d.cts +7 -7
  25. package/dist/platform/index.d.ts +7 -7
  26. package/dist/platform/index.js +7 -7
  27. package/dist/promo/index.js +3 -3
  28. package/dist/{tenant-billing.controller-DElYt3Aa.d.cts → tenant-billing.controller-BqYJjjRi.d.cts} +105 -3
  29. package/dist/{tenant-billing.controller-CVDMTGx6.d.ts → tenant-billing.controller-DhNqJDoU.d.ts} +105 -3
  30. package/dist/{tenant-billing.module-BVXDucZj.d.cts → tenant-billing.module-D96e9tYP.d.cts} +2 -137
  31. package/dist/{tenant-billing.module-C7rgy1jA.d.ts → tenant-billing.module-ZBfERe3L.d.ts} +2 -137
  32. package/dist/testing/index.d.cts +4 -4
  33. package/dist/testing/index.d.ts +4 -4
  34. package/dist/testing/index.js +7 -7
  35. package/package.json +3 -3
package/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- 89d783a28230b3a9b77ff0cb7706f96197daa84bc359f5d1d8f0d643d33beffc
1
+ aec99b9d672b86bd7310c62f4ec0587e8708c675222a1792a640af85a8cdd6b2
package/dist/_entries.cjs CHANGED
@@ -1639,6 +1639,7 @@ __export(billing_exports, {
1639
1639
  FEATURE_GUARD_MARKER: () => FEATURE_GUARD_MARKER,
1640
1640
  FeatureGuard: () => FeatureGuard,
1641
1641
  LimitExceededFilter: () => LimitExceededFilter,
1642
+ NO_NOTICE_PERIOD: () => NO_NOTICE_PERIOD,
1642
1643
  PENDING_PLAN_QUERY_PORT_TOKEN: () => PENDING_PLAN_QUERY_PORT_TOKEN,
1643
1644
  PLAN_CATALOG_IMPORT_SINK_TOKEN: () => PLAN_CATALOG_IMPORT_SINK_TOKEN,
1644
1645
  PLAN_CATALOG_READ_SINK_TOKEN: () => PLAN_CATALOG_READ_SINK_TOKEN,
@@ -1954,9 +1955,12 @@ function decideCancellation(input) {
1954
1955
  };
1955
1956
  }
1956
1957
  __name(decideCancellation, "decideCancellation");
1958
+ var NO_NOTICE_PERIOD = {
1959
+ monthly: 0,
1960
+ yearly: 0
1961
+ };
1957
1962
  function noticeDaysFor(periods, billingCycle) {
1958
- if (!periods) return 0;
1959
- return (billingCycle === "YEARLY" ? periods.yearly : periods.monthly) ?? 0;
1963
+ return billingCycle === "YEARLY" ? periods.yearly : periods.monthly;
1960
1964
  }
1961
1965
  __name(noticeDaysFor, "noticeDaysFor");
1962
1966
  function decideCancellationFor(sub, now, noticePeriodDays) {
@@ -2144,6 +2148,7 @@ function buildPlanCatalogFromSnapshot(settings, snapshot) {
2144
2148
  app: settings.app,
2145
2149
  currency: settings.currency,
2146
2150
  vatRate: settings.vatRate,
2151
+ tenantBilling: settings.tenantBilling,
2147
2152
  ...settings.marketing ? {
2148
2153
  marketing: settings.marketing
2149
2154
  } : {},
@@ -2183,6 +2188,7 @@ var PlanCatalogModule = class _PlanCatalogModule {
2183
2188
  app: options.app,
2184
2189
  currency: options.currency,
2185
2190
  vatRate: options.vatRate,
2191
+ tenantBilling: options.tenantBilling,
2186
2192
  marketing: options.marketing
2187
2193
  }, snapshot);
2188
2194
  }, "useFactory"),
@@ -3174,6 +3180,13 @@ var import_js_yaml = __toESM(require("js-yaml"), 1);
3174
3180
  var import__ = __toESM(require("ajv/dist/2020.js"), 1);
3175
3181
  var import_ajv_formats = __toESM(require("ajv-formats"), 1);
3176
3182
  var import_spec = require("@saasicat/spec");
3183
+ function locate(error) {
3184
+ const segments = (error.instancePath ?? "").split("/").filter(Boolean);
3185
+ const missing = error.params?.missingProperty;
3186
+ if (missing) segments.push(missing);
3187
+ return segments.length > 0 ? segments.join(".") : "(document root)";
3188
+ }
3189
+ __name(locate, "locate");
3177
3190
  var PLAN_CATALOG_UNREADABLE_ERROR = "PlanCatalogUnreadableError";
3178
3191
  var PlanCatalogValidationError = class extends Error {
3179
3192
  static {
@@ -3182,7 +3195,7 @@ var PlanCatalogValidationError = class extends Error {
3182
3195
  source;
3183
3196
  errors;
3184
3197
  constructor(source, errors) {
3185
- const messages = errors.map((e) => `${e.instancePath || "/"}: ${e.message ?? "unknown"}`).join("\n ");
3198
+ const messages = errors.map((e) => `${locate(e)}: ${e.message ?? "unknown"}`).join("\n ");
3186
3199
  super(`Plan catalog validation failed for ${source}:
3187
3200
  ${messages}`), this.source = source, this.errors = errors;
3188
3201
  this.name = "PlanCatalogValidationError";
@@ -6128,7 +6141,7 @@ var TenantBillingController = class _TenantBillingController {
6128
6141
  contractFreeze;
6129
6142
  trialProjection;
6130
6143
  cancellationNoticeDays;
6131
- constructor(entitlements, planPreview, subscriptionUsage, usageSnapshot, subscriptionWrite, tenantIdResolver = null, userIdResolver = null, blockedPlans = null, promoCodes = null, auditService = null, userEmailResolver = null, auditContextResolver = null, subscriptionBundles = null, contractFreeze = null, trialProjection = null, cancellationNoticeDays = {}) {
6144
+ constructor(entitlements, planPreview, subscriptionUsage, usageSnapshot, subscriptionWrite, tenantIdResolver = null, userIdResolver = null, blockedPlans = null, promoCodes = null, auditService = null, userEmailResolver = null, auditContextResolver = null, subscriptionBundles = null, contractFreeze = null, trialProjection = null, cancellationNoticeDays = NO_NOTICE_PERIOD) {
6132
6145
  this.entitlements = entitlements;
6133
6146
  this.planPreview = planPreview;
6134
6147
  this.subscriptionUsage = subscriptionUsage;
@@ -6951,6 +6964,7 @@ TenantBillingController = _ts_decorate35([
6951
6964
 
6952
6965
  // src/billing/tenant-billing.module.ts
6953
6966
  var import_common35 = require("@nestjs/common");
6967
+ var import_spec2 = require("@saasicat/spec");
6954
6968
  function _ts_decorate36(decorators, target, key, desc) {
6955
6969
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6956
6970
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -6958,6 +6972,27 @@ function _ts_decorate36(decorators, target, key, desc) {
6958
6972
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6959
6973
  }
6960
6974
  __name(_ts_decorate36, "_ts_decorate");
6975
+ function settingFromCatalog(catalog, key) {
6976
+ const settings = catalog.tenantBilling;
6977
+ const value = settings?.[key];
6978
+ if (value === void 0) {
6979
+ throw new Error(`The plan catalogue carries no tenantBilling.${key}. It is a required section of config/saas.yaml and the loader refuses a file without it, so a catalogue that reaches here without one was built in code: check the \`dbCatalog\` block, which forwards \`tenantBilling\` from that same file, or the object passed to \`planCatalog\`.`);
6980
+ }
6981
+ return value;
6982
+ }
6983
+ __name(settingFromCatalog, "settingFromCatalog");
6984
+ var MOVED_TO_THE_FILE = Object.keys(import_spec2.planCatalogSchema.properties?.tenantBilling?.properties ?? // Refuses to guess rather than falling back to an empty list: an empty
6985
+ // one would turn this guard off without saying so.
6986
+ (() => {
6987
+ throw new Error("plan-catalog.schema.json declares no tenantBilling properties \u2014 @saasicat/spec and @saasicat/nest are out of step.");
6988
+ })());
6989
+ function assertNoMovedOptions(options) {
6990
+ const given = options;
6991
+ const passed = MOVED_TO_THE_FILE.filter((key) => given[key] !== void 0);
6992
+ if (passed.length === 0) return;
6993
+ throw new Error(`TenantBillingModule.forRoot() no longer accepts ${passed.join(" and ")} \u2014 this moved to config/saas.yaml under \`tenantBilling:\`. Move the value there (both members of each setting are required) and delete it here. The file is the one place these live, so a fallback here would be the second home that change exists to close. See docs/guides/upgrade-to-1.0.md.`);
6994
+ }
6995
+ __name(assertNoMovedOptions, "assertNoMovedOptions");
6961
6996
  var TenantBillingModule = class _TenantBillingModule {
6962
6997
  static {
6963
6998
  __name(this, "TenantBillingModule");
@@ -6972,18 +7007,20 @@ var TenantBillingModule = class _TenantBillingModule {
6972
7007
  TenantAdminGuard,
6973
7008
  PlanChangePreviewService
6974
7009
  ];
6975
- if (options.cancellationNoticeDays) {
6976
- providers.push({
6977
- provide: CANCELLATION_NOTICE_DAYS_TOKEN,
6978
- useValue: options.cancellationNoticeDays
6979
- });
6980
- }
6981
- if (options.selfServiceBlockedPlans) {
6982
- providers.push({
6983
- provide: SELF_SERVICE_BLOCKED_PLANS_TOKEN,
6984
- useValue: options.selfServiceBlockedPlans
6985
- });
6986
- }
7010
+ assertNoMovedOptions(options);
7011
+ providers.push({
7012
+ provide: CANCELLATION_NOTICE_DAYS_TOKEN,
7013
+ useFactory: /* @__PURE__ */ __name((catalog) => settingFromCatalog(catalog, "cancellationNoticeDays"), "useFactory"),
7014
+ inject: [
7015
+ PLAN_CATALOG_TOKEN
7016
+ ]
7017
+ }, {
7018
+ provide: SELF_SERVICE_BLOCKED_PLANS_TOKEN,
7019
+ useFactory: /* @__PURE__ */ __name((catalog) => settingFromCatalog(catalog, "selfServiceBlockedPlans"), "useFactory"),
7020
+ inject: [
7021
+ PLAN_CATALOG_TOKEN
7022
+ ]
7023
+ });
6987
7024
  if (options.subscriptionBundleRepository) {
6988
7025
  providers.push(asProvider(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN, options.subscriptionBundleRepository));
6989
7026
  }
@@ -14569,7 +14606,7 @@ var RULE_SPECS = [
14569
14606
  id: "catalog.identity-or-sink",
14570
14607
  when: /* @__PURE__ */ __name((c) => !c.options.planCatalog, "when"),
14571
14608
  assert: /* @__PURE__ */ __name((c) => Boolean(c.adapters.planCatalogReadSink && c.options.dbCatalog), "assert"),
14572
- message: "no plan catalogue is reachable. Either set `planCatalog` (the quickstart YAML path) or, for DB hydration, BOTH a `planCatalogReadSink` (via `adapters` or `persistence`) AND `dbCatalog` ({ app, currency, vatRate }). Without the identity the app boots with a silently empty catalogue."
14609
+ message: "no plan catalogue is reachable. Either set `planCatalog` (the quickstart YAML path) or, for DB hydration, BOTH a `planCatalogReadSink` (via `adapters` or `persistence`) AND `dbCatalog` ({ app, currency, vatRate, tenantBilling }). Without the identity the app boots with a silently empty catalogue."
14573
14610
  },
14574
14611
  {
14575
14612
  id: "catalog.app-is-named",
@@ -16220,6 +16257,7 @@ function composePlanCatalog(options, sink) {
16220
16257
  app: dbCatalog.app,
16221
16258
  currency: dbCatalog.currency,
16222
16259
  vatRate: dbCatalog.vatRate,
16260
+ tenantBilling: dbCatalog.tenantBilling,
16223
16261
  marketing: dbCatalog.marketing,
16224
16262
  sink,
16225
16263
  imports: options.imports
@@ -18659,6 +18697,7 @@ __export(src_exports, {
18659
18697
  MarketingSettingsService: () => MarketingSettingsService,
18660
18698
  MfaGuard: () => MfaGuard,
18661
18699
  MfaService: () => MfaService,
18700
+ NO_NOTICE_PERIOD: () => NO_NOTICE_PERIOD,
18662
18701
  PASSWORD_HASHER_TOKEN: () => PASSWORD_HASHER_TOKEN,
18663
18702
  PAYMENT_EVENT_LOG_TOKEN: () => PAYMENT_EVENT_LOG_TOKEN,
18664
18703
  PAYMENT_PROVIDER_TOKEN: () => PAYMENT_PROVIDER_TOKEN,
@@ -1,18 +1,18 @@
1
1
  import { BillingCycle, BundleVersionRow, FeatureKey, UpsellOfferResolver, BundleRepository, CatalogEntryRepository, UpsellOffer, PlanCatalog, PlanCatalogReadSink, PlanCatalogReadSnapshot, PlanCatalogImportSink, PlanCatalogImportReport, PlanId, PlanDef, QuotaKey, MarketingTopFeature, FeatureUiRegistry, MarketingProjectionRepository, ConfiguratorSourcesLookup, ConfiguratorMarketingProvider, ConfiguratorCatalog } from '@saasicat/core';
2
- export { BundleVersionFields, ChangeDirection, DiffResult, PlanVersionFields, VersionChange, VersionChangeDirection, classifyBundleVersionDiff, classifyPlanDiff } from '@saasicat/core';
3
- import { F as FEATURE_GUARD_MARKER, j as ContractFreezePort, k as ContractFreezeSourcePort } from '../tenant-billing.module-BVXDucZj.cjs';
4
- export { A as AUDIT_CONTEXT_RESOLVER_TOKEN, a as AuditContextResolver, b as AuthGuardList, C as CANCELLATION_NOTICE_DAYS_TOKEN, c as CONTRACT_FREEZE_PORT_TOKEN, d as CONTRACT_FREEZE_SOURCE_PORT_TOKEN, e as CancellableSubscription, f as CancellationDecision, g as CancellationInput, h as CancellationNoticePeriods, i as ContractFreezeBundleSnapshot, D as DuePendingPlanChange, M as MarkedFeatureGuard, P as PENDING_PLAN_QUERY_PORT_TOKEN, l as PendingPlanQueryPort, S as SELF_SERVICE_BLOCKED_BUNDLES_TOKEN, m as SELF_SERVICE_BLOCKED_PLANS_TOKEN, n as SUBSCRIPTION_USAGE_PORT_TOKEN, o as SUBSCRIPTION_WRITE_PORT_TOKEN, p as SelfServiceBlockedBundles, q as SelfServiceBlockedPlans, r as SubscriptionBundleControllerOptions, s as SubscriptionBundleModule, t as SubscriptionBundleModuleOptions, T as TENANT_AUTH_GUARDS_TOKEN, u as TENANT_ID_RESOLVER_TOKEN, v as TRIAL_PROJECTION_PORT_TOKEN, w as TenantBillingModule, x as TenantBillingModuleOptions, y as TenantIdResolver, z as TrialProjectionInput, B as TrialProjectionPort, U as USAGE_SNAPSHOT_PORT_TOKEN, E as USER_EMAIL_RESOLVER_TOKEN, G as USER_ID_RESOLVER_TOKEN, H as UserEmailResolver, I as UserIdResolver, J as decideCancellation, K as decideCancellationFor, L as isPlatformFeatureGuard, N as noticeDaysFor } from '../tenant-billing.module-BVXDucZj.cjs';
2
+ export { BundleVersionFields, CancellationNoticePeriods, ChangeDirection, DiffResult, PlanVersionFields, SelfServiceBlockedPlans, VersionChange, VersionChangeDirection, classifyBundleVersionDiff, classifyPlanDiff } from '@saasicat/core';
3
+ export { A as AddBundleToSubscriptionInput, B as BundlePreviewSnapshot, C as CancelBundleFromSubscriptionInput, a as CancelSubscriptionDto, b as CancellableSubscription, c as CancellationDecision, d as CancellationInput, e as ChangePlanDto, f as CompleteOnboardingSubscriptionDto, g as ComposedTenantAuthGuard, h as CycleDirection, L as LimitsCheckRow, N as NO_NOTICE_PERIOD, P as PendingPlanMaterializationService, i as PlanChangeContext, j as PlanChangePreviewDto, k as PlanChangePreviewIssue, l as PlanChangePreviewService, m as PlanChangeType, n as PlanDirection, o as PlanSnapshotDto, p as PreviewPlanChangeDto, q as ProrationDto, r as ProrationInput, R as RedundantFeatureHint, S as SubscriptionBundleAddPreviewDto, s as SubscriptionBundleCancelPreviewDto, t as SubscriptionBundleConfig, u as SubscriptionBundlePreviewContext, v as SubscriptionBundlePreviewIssue, w as SubscriptionBundlePreviewService, x as SubscriptionBundlesService, T as TenantAdminGuard, y as TenantBillingController, U as UsageResponse, z as addMonths, D as clampToParent, E as computeProration, F as decideCancellation, G as decideCancellationFor, H as noticeDaysFor, I as resolveBundleCancelEffectiveAt } from '../tenant-billing.controller-BqYJjjRi.cjs';
4
+ import { F as FEATURE_GUARD_MARKER, f as ContractFreezePort, g as ContractFreezeSourcePort } from '../tenant-billing.module-D96e9tYP.cjs';
5
+ export { A as AUDIT_CONTEXT_RESOLVER_TOKEN, a as AuditContextResolver, b as AuthGuardList, C as CANCELLATION_NOTICE_DAYS_TOKEN, c as CONTRACT_FREEZE_PORT_TOKEN, d as CONTRACT_FREEZE_SOURCE_PORT_TOKEN, e as ContractFreezeBundleSnapshot, D as DuePendingPlanChange, M as MarkedFeatureGuard, P as PENDING_PLAN_QUERY_PORT_TOKEN, h as PendingPlanQueryPort, S as SELF_SERVICE_BLOCKED_BUNDLES_TOKEN, i as SELF_SERVICE_BLOCKED_PLANS_TOKEN, j as SUBSCRIPTION_USAGE_PORT_TOKEN, k as SUBSCRIPTION_WRITE_PORT_TOKEN, l as SelfServiceBlockedBundles, m as SubscriptionBundleControllerOptions, n as SubscriptionBundleModule, o as SubscriptionBundleModuleOptions, T as TENANT_AUTH_GUARDS_TOKEN, p as TENANT_ID_RESOLVER_TOKEN, q as TRIAL_PROJECTION_PORT_TOKEN, r as TenantBillingModule, s as TenantBillingModuleOptions, t as TenantIdResolver, u as TrialProjectionInput, v as TrialProjectionPort, U as USAGE_SNAPSHOT_PORT_TOKEN, w as USER_EMAIL_RESOLVER_TOKEN, x as USER_ID_RESOLVER_TOKEN, y as UserEmailResolver, z as UserIdResolver, B as isPlatformFeatureGuard } from '../tenant-billing.module-D96e9tYP.cjs';
5
6
  import * as _nestjs_common from '@nestjs/common';
6
7
  import { CanActivate, ExecutionContext, ArgumentsHost, Type, DynamicModule, ForwardReference, Provider } from '@nestjs/common';
7
8
  export { Type as NestType } from '@nestjs/common';
8
9
  import { Reflector, BaseExceptionFilter } from '@nestjs/core';
9
10
  import { c as EntitlementService } from '../aggregation-Bsg1g9Q0.cjs';
10
11
  import { P as ProviderSpec } from '../di-CcNeq9v-.cjs';
11
- export { A as AddBundleToSubscriptionInput, B as BundlePreviewSnapshot, C as CancelBundleFromSubscriptionInput, a as CancelSubscriptionDto, b as ChangePlanDto, c as CompleteOnboardingSubscriptionDto, d as ComposedTenantAuthGuard, e as CycleDirection, L as LimitsCheckRow, P as PendingPlanMaterializationService, f as PlanChangeContext, g as PlanChangePreviewDto, h as PlanChangePreviewIssue, i as PlanChangePreviewService, j as PlanChangeType, k as PlanDirection, l as PlanSnapshotDto, m as PreviewPlanChangeDto, n as ProrationDto, o as ProrationInput, R as RedundantFeatureHint, S as SubscriptionBundleAddPreviewDto, p as SubscriptionBundleCancelPreviewDto, q as SubscriptionBundleConfig, r as SubscriptionBundlePreviewContext, s as SubscriptionBundlePreviewIssue, t as SubscriptionBundlePreviewService, u as SubscriptionBundlesService, T as TenantAdminGuard, v as TenantBillingController, U as UsageResponse, w as addMonths, x as clampToParent, y as computeProration, z as resolveBundleCancelEffectiveAt } from '../tenant-billing.controller-DElYt3Aa.cjs';
12
12
  import { S as SubscriptionContractService } from '../subscription-contract.service-hD87MKyg.cjs';
13
- import '../plan-resolution-CwUy_OqC.cjs';
14
13
  import '../promo.service-BJbKAmw3.cjs';
15
14
  import '../admin-audit.service-4P3Djdxk.cjs';
15
+ import '../plan-resolution-CwUy_OqC.cjs';
16
16
 
17
17
  /**
18
18
  * One period onwards, on `anchorDay` where the month has one.
@@ -550,6 +550,9 @@ interface AjvErrorLike {
550
550
  instancePath?: string;
551
551
  message?: string;
552
552
  schemaPath?: string;
553
+ params?: {
554
+ missingProperty?: string;
555
+ };
553
556
  }
554
557
  /**
555
558
  * `Error.name` for a document that could not be read as a catalog at all.
@@ -603,6 +606,12 @@ interface PlanCatalogModuleOptions {
603
606
  app: PlanCatalog['app'];
604
607
  currency: string;
605
608
  vatRate: number;
609
+ /**
610
+ * Commercial self-service settings from `config/saas.yaml#tenantBilling`.
611
+ * The read sink loads plans and features; settings are never in the
612
+ * database, so they can only come from the file.
613
+ */
614
+ tenantBilling: PlanCatalog['tenantBilling'];
606
615
  /**
607
616
  * App-wide marketing configuration — including the
608
617
  * `availableLocales` pool. Flows into `PLAN_CATALOG_TOKEN.marketing`
@@ -633,6 +642,12 @@ interface PlanCatalogBuildSettings {
633
642
  app: PlanCatalog['app'];
634
643
  currency: string;
635
644
  vatRate: number;
645
+ /**
646
+ * Commercial self-service settings from `config/saas.yaml#tenantBilling`.
647
+ * Required, like the two above: the database carries plans and features,
648
+ * never the settings, so this can only come from the file.
649
+ */
650
+ tenantBilling: PlanCatalog['tenantBilling'];
636
651
  /** App-wide marketing configuration. Optional. */
637
652
  marketing?: PlanCatalog['marketing'];
638
653
  }
@@ -1,18 +1,18 @@
1
1
  import { BillingCycle, BundleVersionRow, FeatureKey, UpsellOfferResolver, BundleRepository, CatalogEntryRepository, UpsellOffer, PlanCatalog, PlanCatalogReadSink, PlanCatalogReadSnapshot, PlanCatalogImportSink, PlanCatalogImportReport, PlanId, PlanDef, QuotaKey, MarketingTopFeature, FeatureUiRegistry, MarketingProjectionRepository, ConfiguratorSourcesLookup, ConfiguratorMarketingProvider, ConfiguratorCatalog } from '@saasicat/core';
2
- export { BundleVersionFields, ChangeDirection, DiffResult, PlanVersionFields, VersionChange, VersionChangeDirection, classifyBundleVersionDiff, classifyPlanDiff } from '@saasicat/core';
3
- import { F as FEATURE_GUARD_MARKER, j as ContractFreezePort, k as ContractFreezeSourcePort } from '../tenant-billing.module-C7rgy1jA.js';
4
- export { A as AUDIT_CONTEXT_RESOLVER_TOKEN, a as AuditContextResolver, b as AuthGuardList, C as CANCELLATION_NOTICE_DAYS_TOKEN, c as CONTRACT_FREEZE_PORT_TOKEN, d as CONTRACT_FREEZE_SOURCE_PORT_TOKEN, e as CancellableSubscription, f as CancellationDecision, g as CancellationInput, h as CancellationNoticePeriods, i as ContractFreezeBundleSnapshot, D as DuePendingPlanChange, M as MarkedFeatureGuard, P as PENDING_PLAN_QUERY_PORT_TOKEN, l as PendingPlanQueryPort, S as SELF_SERVICE_BLOCKED_BUNDLES_TOKEN, m as SELF_SERVICE_BLOCKED_PLANS_TOKEN, n as SUBSCRIPTION_USAGE_PORT_TOKEN, o as SUBSCRIPTION_WRITE_PORT_TOKEN, p as SelfServiceBlockedBundles, q as SelfServiceBlockedPlans, r as SubscriptionBundleControllerOptions, s as SubscriptionBundleModule, t as SubscriptionBundleModuleOptions, T as TENANT_AUTH_GUARDS_TOKEN, u as TENANT_ID_RESOLVER_TOKEN, v as TRIAL_PROJECTION_PORT_TOKEN, w as TenantBillingModule, x as TenantBillingModuleOptions, y as TenantIdResolver, z as TrialProjectionInput, B as TrialProjectionPort, U as USAGE_SNAPSHOT_PORT_TOKEN, E as USER_EMAIL_RESOLVER_TOKEN, G as USER_ID_RESOLVER_TOKEN, H as UserEmailResolver, I as UserIdResolver, J as decideCancellation, K as decideCancellationFor, L as isPlatformFeatureGuard, N as noticeDaysFor } from '../tenant-billing.module-C7rgy1jA.js';
2
+ export { BundleVersionFields, CancellationNoticePeriods, ChangeDirection, DiffResult, PlanVersionFields, SelfServiceBlockedPlans, VersionChange, VersionChangeDirection, classifyBundleVersionDiff, classifyPlanDiff } from '@saasicat/core';
3
+ export { A as AddBundleToSubscriptionInput, B as BundlePreviewSnapshot, C as CancelBundleFromSubscriptionInput, a as CancelSubscriptionDto, b as CancellableSubscription, c as CancellationDecision, d as CancellationInput, e as ChangePlanDto, f as CompleteOnboardingSubscriptionDto, g as ComposedTenantAuthGuard, h as CycleDirection, L as LimitsCheckRow, N as NO_NOTICE_PERIOD, P as PendingPlanMaterializationService, i as PlanChangeContext, j as PlanChangePreviewDto, k as PlanChangePreviewIssue, l as PlanChangePreviewService, m as PlanChangeType, n as PlanDirection, o as PlanSnapshotDto, p as PreviewPlanChangeDto, q as ProrationDto, r as ProrationInput, R as RedundantFeatureHint, S as SubscriptionBundleAddPreviewDto, s as SubscriptionBundleCancelPreviewDto, t as SubscriptionBundleConfig, u as SubscriptionBundlePreviewContext, v as SubscriptionBundlePreviewIssue, w as SubscriptionBundlePreviewService, x as SubscriptionBundlesService, T as TenantAdminGuard, y as TenantBillingController, U as UsageResponse, z as addMonths, D as clampToParent, E as computeProration, F as decideCancellation, G as decideCancellationFor, H as noticeDaysFor, I as resolveBundleCancelEffectiveAt } from '../tenant-billing.controller-DhNqJDoU.js';
4
+ import { F as FEATURE_GUARD_MARKER, f as ContractFreezePort, g as ContractFreezeSourcePort } from '../tenant-billing.module-ZBfERe3L.js';
5
+ export { A as AUDIT_CONTEXT_RESOLVER_TOKEN, a as AuditContextResolver, b as AuthGuardList, C as CANCELLATION_NOTICE_DAYS_TOKEN, c as CONTRACT_FREEZE_PORT_TOKEN, d as CONTRACT_FREEZE_SOURCE_PORT_TOKEN, e as ContractFreezeBundleSnapshot, D as DuePendingPlanChange, M as MarkedFeatureGuard, P as PENDING_PLAN_QUERY_PORT_TOKEN, h as PendingPlanQueryPort, S as SELF_SERVICE_BLOCKED_BUNDLES_TOKEN, i as SELF_SERVICE_BLOCKED_PLANS_TOKEN, j as SUBSCRIPTION_USAGE_PORT_TOKEN, k as SUBSCRIPTION_WRITE_PORT_TOKEN, l as SelfServiceBlockedBundles, m as SubscriptionBundleControllerOptions, n as SubscriptionBundleModule, o as SubscriptionBundleModuleOptions, T as TENANT_AUTH_GUARDS_TOKEN, p as TENANT_ID_RESOLVER_TOKEN, q as TRIAL_PROJECTION_PORT_TOKEN, r as TenantBillingModule, s as TenantBillingModuleOptions, t as TenantIdResolver, u as TrialProjectionInput, v as TrialProjectionPort, U as USAGE_SNAPSHOT_PORT_TOKEN, w as USER_EMAIL_RESOLVER_TOKEN, x as USER_ID_RESOLVER_TOKEN, y as UserEmailResolver, z as UserIdResolver, B as isPlatformFeatureGuard } from '../tenant-billing.module-ZBfERe3L.js';
5
6
  import * as _nestjs_common from '@nestjs/common';
6
7
  import { CanActivate, ExecutionContext, ArgumentsHost, Type, DynamicModule, ForwardReference, Provider } from '@nestjs/common';
7
8
  export { Type as NestType } from '@nestjs/common';
8
9
  import { Reflector, BaseExceptionFilter } from '@nestjs/core';
9
10
  import { c as EntitlementService } from '../aggregation-53yv4-rr.js';
10
11
  import { P as ProviderSpec } from '../di-CcNeq9v-.js';
11
- export { A as AddBundleToSubscriptionInput, B as BundlePreviewSnapshot, C as CancelBundleFromSubscriptionInput, a as CancelSubscriptionDto, b as ChangePlanDto, c as CompleteOnboardingSubscriptionDto, d as ComposedTenantAuthGuard, e as CycleDirection, L as LimitsCheckRow, P as PendingPlanMaterializationService, f as PlanChangeContext, g as PlanChangePreviewDto, h as PlanChangePreviewIssue, i as PlanChangePreviewService, j as PlanChangeType, k as PlanDirection, l as PlanSnapshotDto, m as PreviewPlanChangeDto, n as ProrationDto, o as ProrationInput, R as RedundantFeatureHint, S as SubscriptionBundleAddPreviewDto, p as SubscriptionBundleCancelPreviewDto, q as SubscriptionBundleConfig, r as SubscriptionBundlePreviewContext, s as SubscriptionBundlePreviewIssue, t as SubscriptionBundlePreviewService, u as SubscriptionBundlesService, T as TenantAdminGuard, v as TenantBillingController, U as UsageResponse, w as addMonths, x as clampToParent, y as computeProration, z as resolveBundleCancelEffectiveAt } from '../tenant-billing.controller-CVDMTGx6.js';
12
12
  import { S as SubscriptionContractService } from '../subscription-contract.service-hD87MKyg.js';
13
- import '../plan-resolution-CwUy_OqC.js';
14
13
  import '../promo.service-BJbKAmw3.js';
15
14
  import '../admin-audit.service-4P3Djdxk.js';
15
+ import '../plan-resolution-CwUy_OqC.js';
16
16
 
17
17
  /**
18
18
  * One period onwards, on `anchorDay` where the month has one.
@@ -550,6 +550,9 @@ interface AjvErrorLike {
550
550
  instancePath?: string;
551
551
  message?: string;
552
552
  schemaPath?: string;
553
+ params?: {
554
+ missingProperty?: string;
555
+ };
553
556
  }
554
557
  /**
555
558
  * `Error.name` for a document that could not be read as a catalog at all.
@@ -603,6 +606,12 @@ interface PlanCatalogModuleOptions {
603
606
  app: PlanCatalog['app'];
604
607
  currency: string;
605
608
  vatRate: number;
609
+ /**
610
+ * Commercial self-service settings from `config/saas.yaml#tenantBilling`.
611
+ * The read sink loads plans and features; settings are never in the
612
+ * database, so they can only come from the file.
613
+ */
614
+ tenantBilling: PlanCatalog['tenantBilling'];
606
615
  /**
607
616
  * App-wide marketing configuration — including the
608
617
  * `availableLocales` pool. Flows into `PLAN_CATALOG_TOKEN.marketing`
@@ -633,6 +642,12 @@ interface PlanCatalogBuildSettings {
633
642
  app: PlanCatalog['app'];
634
643
  currency: string;
635
644
  vatRate: number;
645
+ /**
646
+ * Commercial self-service settings from `config/saas.yaml#tenantBilling`.
647
+ * Required, like the two above: the database carries plans and features,
648
+ * never the settings, so this can only come from the file.
649
+ */
650
+ tenantBilling: PlanCatalog['tenantBilling'];
636
651
  /** App-wide marketing configuration. Optional. */
637
652
  marketing?: PlanCatalog['marketing'];
638
653
  }
@@ -24,7 +24,7 @@ import {
24
24
  decideRenewal,
25
25
  loadPlanCatalogFromFile,
26
26
  loadPlanCatalogFromString
27
- } from "../chunk-GHIECNF3.js";
27
+ } from "../chunk-FVD4TGER.js";
28
28
  import {
29
29
  AddSubscriptionBundleDto,
30
30
  BILLING_FEATURE_UI_REGISTRY_TOKEN,
@@ -38,6 +38,7 @@ import {
38
38
  ComposedTenantAuthGuard,
39
39
  DEFAULT_BUNDLE_MINIMUM_TERM_MONTHS,
40
40
  FEATURE_GUARD_MARKER,
41
+ NO_NOTICE_PERIOD,
41
42
  PUBLIC_CATALOG_BUNDLE_REPOSITORY_TOKEN,
42
43
  PUBLIC_CATALOG_MARKETING_REPOSITORY_TOKEN,
43
44
  PendingPlanMaterializationService,
@@ -78,8 +79,8 @@ import {
78
79
  resolveBundleCancelEffectiveAt,
79
80
  resolvePlanAnchorDay,
80
81
  retreatOneCycle
81
- } from "../chunk-TJ5E42WX.js";
82
- import "../chunk-7JEXCHMU.js";
82
+ } from "../chunk-W67WBRW5.js";
83
+ import "../chunk-6FQ3G4IF.js";
83
84
  import "../chunk-F7P2X2DR.js";
84
85
  import {
85
86
  AUDIT_CONTEXT_RESOLVER_TOKEN,
@@ -104,7 +105,7 @@ import "../chunk-YSC46BGM.js";
104
105
  import {
105
106
  SUBSCRIPTION_BUNDLE_CONFIG_TOKEN,
106
107
  SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN
107
- } from "../chunk-IW5XGOEU.js";
108
+ } from "../chunk-5LE5AZUP.js";
108
109
  import {
109
110
  PLAN_CATALOG_READ_SINK_TOKEN,
110
111
  PLAN_CATALOG_TOKEN,
@@ -119,7 +120,7 @@ import {
119
120
  getPlanQuota,
120
121
  isFeatureInPlan,
121
122
  isFeaturePlannedOnly
122
- } from "../chunk-UUFUSKX2.js";
123
+ } from "../chunk-H7SF4EUR.js";
123
124
  import "../chunk-VBRYPU7V.js";
124
125
  import "../chunk-57V6ZTI6.js";
125
126
  import "../chunk-OUDSQ3PU.js";
@@ -147,6 +148,7 @@ export {
147
148
  FEATURE_GUARD_MARKER,
148
149
  FeatureGuard,
149
150
  LimitExceededFilter,
151
+ NO_NOTICE_PERIOD,
150
152
  PENDING_PLAN_QUERY_PORT_TOKEN,
151
153
  PLAN_CATALOG_IMPORT_SINK_TOKEN,
152
154
  PLAN_CATALOG_READ_SINK_TOKEN,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PLAN_CATALOG_TOKEN,
3
3
  isFeaturePlannedOnly
4
- } from "./chunk-UUFUSKX2.js";
4
+ } from "./chunk-H7SF4EUR.js";
5
5
  import {
6
6
  SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
7
7
  } from "./chunk-VBRYPU7V.js";
@@ -3,7 +3,7 @@ import {
3
3
  computeIncludedVat,
4
4
  getPlanPriceGross,
5
5
  round2
6
- } from "./chunk-UUFUSKX2.js";
6
+ } from "./chunk-H7SF4EUR.js";
7
7
  import {
8
8
  __name
9
9
  } from "./chunk-SHUYVCID.js";
@@ -4,11 +4,11 @@ import {
4
4
  PUBLIC_CATALOG_CATALOG_ENTRY_REPOSITORY_TOKEN,
5
5
  REQUIRE_FEATURE_KEY,
6
6
  periodEndAfter
7
- } from "./chunk-TJ5E42WX.js";
7
+ } from "./chunk-W67WBRW5.js";
8
8
  import {
9
9
  EntitlementService,
10
10
  isLimitExceededError
11
- } from "./chunk-IW5XGOEU.js";
11
+ } from "./chunk-5LE5AZUP.js";
12
12
  import {
13
13
  ENTITLEMENT_SERVICE_TOKEN
14
14
  } from "./chunk-OUDSQ3PU.js";
@@ -401,6 +401,13 @@ import yaml from "js-yaml";
401
401
  import Ajv2020 from "ajv/dist/2020.js";
402
402
  import addFormats from "ajv-formats";
403
403
  import { planCatalogSchema } from "@saasicat/spec";
404
+ function locate(error) {
405
+ const segments = (error.instancePath ?? "").split("/").filter(Boolean);
406
+ const missing = error.params?.missingProperty;
407
+ if (missing) segments.push(missing);
408
+ return segments.length > 0 ? segments.join(".") : "(document root)";
409
+ }
410
+ __name(locate, "locate");
404
411
  var PLAN_CATALOG_UNREADABLE_ERROR = "PlanCatalogUnreadableError";
405
412
  var PlanCatalogValidationError = class extends Error {
406
413
  static {
@@ -409,7 +416,7 @@ var PlanCatalogValidationError = class extends Error {
409
416
  source;
410
417
  errors;
411
418
  constructor(source, errors) {
412
- const messages = errors.map((e) => `${e.instancePath || "/"}: ${e.message ?? "unknown"}`).join("\n ");
419
+ const messages = errors.map((e) => `${locate(e)}: ${e.message ?? "unknown"}`).join("\n ");
413
420
  super(`Plan catalog validation failed for ${source}:
414
421
  ${messages}`), this.source = source, this.errors = errors;
415
422
  this.name = "PlanCatalogValidationError";
@@ -62,6 +62,7 @@ function buildPlanCatalogFromSnapshot(settings, snapshot) {
62
62
  app: settings.app,
63
63
  currency: settings.currency,
64
64
  vatRate: settings.vatRate,
65
+ tenantBilling: settings.tenantBilling,
65
66
  ...settings.marketing ? {
66
67
  marketing: settings.marketing
67
68
  } : {},
@@ -102,6 +103,7 @@ var PlanCatalogModule = class _PlanCatalogModule {
102
103
  app: options.app,
103
104
  currency: options.currency,
104
105
  vatRate: options.vatRate,
106
+ tenantBilling: options.tenantBilling,
105
107
  marketing: options.marketing
106
108
  }, snapshot);
107
109
  }, "useFactory"),
@@ -8,7 +8,7 @@ import {
8
8
  PROMO_SUBSCRIPTION_LOOKUP_TOKEN,
9
9
  PROMO_TRANSACTION_RUNNER_TOKEN,
10
10
  PromoCodesService
11
- } from "./chunk-7JEXCHMU.js";
11
+ } from "./chunk-6FQ3G4IF.js";
12
12
  import {
13
13
  AdminAuditService
14
14
  } from "./chunk-O2J2HDXA.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  EntitlementService,
3
3
  SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN
4
- } from "./chunk-IW5XGOEU.js";
4
+ } from "./chunk-5LE5AZUP.js";
5
5
  import {
6
6
  SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
7
7
  } from "./chunk-VBRYPU7V.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PromoCodesModule
3
- } from "./chunk-JNONSUP3.js";
3
+ } from "./chunk-HFQNSOIU.js";
4
4
  import {
5
5
  SubscriptionContractModule
6
6
  } from "./chunk-L4VVZ4O5.js";
@@ -20,7 +20,7 @@ import {
20
20
  SubscriptionBundleModule,
21
21
  TenantBillingModule,
22
22
  isPlatformFeatureGuard
23
- } from "./chunk-TJ5E42WX.js";
23
+ } from "./chunk-W67WBRW5.js";
24
24
  import {
25
25
  CatalogModule
26
26
  } from "./chunk-DFQETRBL.js";
@@ -35,15 +35,15 @@ import {
35
35
  } from "./chunk-XBYAFEOR.js";
36
36
  import {
37
37
  EntitlementModule
38
- } from "./chunk-DQW3DHSD.js";
38
+ } from "./chunk-KBRELZT4.js";
39
39
  import {
40
40
  LimitExceededError,
41
41
  cancellationHasLanded
42
- } from "./chunk-IW5XGOEU.js";
42
+ } from "./chunk-5LE5AZUP.js";
43
43
  import {
44
44
  PLAN_CATALOG_TOKEN,
45
45
  PlanCatalogModule
46
- } from "./chunk-UUFUSKX2.js";
46
+ } from "./chunk-H7SF4EUR.js";
47
47
  import {
48
48
  codedError
49
49
  } from "./chunk-57V6ZTI6.js";
@@ -541,7 +541,7 @@ var RULE_SPECS = [
541
541
  id: "catalog.identity-or-sink",
542
542
  when: /* @__PURE__ */ __name((c) => !c.options.planCatalog, "when"),
543
543
  assert: /* @__PURE__ */ __name((c) => Boolean(c.adapters.planCatalogReadSink && c.options.dbCatalog), "assert"),
544
- message: "no plan catalogue is reachable. Either set `planCatalog` (the quickstart YAML path) or, for DB hydration, BOTH a `planCatalogReadSink` (via `adapters` or `persistence`) AND `dbCatalog` ({ app, currency, vatRate }). Without the identity the app boots with a silently empty catalogue."
544
+ message: "no plan catalogue is reachable. Either set `planCatalog` (the quickstart YAML path) or, for DB hydration, BOTH a `planCatalogReadSink` (via `adapters` or `persistence`) AND `dbCatalog` ({ app, currency, vatRate, tenantBilling }). Without the identity the app boots with a silently empty catalogue."
545
545
  },
546
546
  {
547
547
  id: "catalog.app-is-named",
@@ -1615,6 +1615,7 @@ function composePlanCatalog(options, sink) {
1615
1615
  app: dbCatalog.app,
1616
1616
  currency: dbCatalog.currency,
1617
1617
  vatRate: dbCatalog.vatRate,
1618
+ tenantBilling: dbCatalog.tenantBilling,
1618
1619
  marketing: dbCatalog.marketing,
1619
1620
  sink,
1620
1621
  imports: options.imports
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PromoCodesService
3
- } from "./chunk-7JEXCHMU.js";
3
+ } from "./chunk-6FQ3G4IF.js";
4
4
  import {
5
5
  SubscriptionContractService
6
6
  } from "./chunk-F7P2X2DR.js";
@@ -30,13 +30,13 @@ import {
30
30
  SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN,
31
31
  cancellationHasLanded,
32
32
  toEffectiveLimitsSnapshot
33
- } from "./chunk-IW5XGOEU.js";
33
+ } from "./chunk-5LE5AZUP.js";
34
34
  import {
35
35
  PLAN_CATALOG_TOKEN,
36
36
  findPlan,
37
37
  getMarketedPlans,
38
38
  getPlanPriceNet
39
- } from "./chunk-UUFUSKX2.js";
39
+ } from "./chunk-H7SF4EUR.js";
40
40
  import {
41
41
  SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
42
42
  } from "./chunk-VBRYPU7V.js";
@@ -265,9 +265,12 @@ function decideCancellation(input) {
265
265
  };
266
266
  }
267
267
  __name(decideCancellation, "decideCancellation");
268
+ var NO_NOTICE_PERIOD = {
269
+ monthly: 0,
270
+ yearly: 0
271
+ };
268
272
  function noticeDaysFor(periods, billingCycle) {
269
- if (!periods) return 0;
270
- return (billingCycle === "YEARLY" ? periods.yearly : periods.monthly) ?? 0;
273
+ return billingCycle === "YEARLY" ? periods.yearly : periods.monthly;
271
274
  }
272
275
  __name(noticeDaysFor, "noticeDaysFor");
273
276
  function decideCancellationFor(sub, now, noticePeriodDays) {
@@ -1889,7 +1892,7 @@ var TenantBillingController = class _TenantBillingController {
1889
1892
  contractFreeze;
1890
1893
  trialProjection;
1891
1894
  cancellationNoticeDays;
1892
- constructor(entitlements, planPreview, subscriptionUsage, usageSnapshot, subscriptionWrite, tenantIdResolver = null, userIdResolver = null, blockedPlans = null, promoCodes = null, auditService = null, userEmailResolver = null, auditContextResolver = null, subscriptionBundles = null, contractFreeze = null, trialProjection = null, cancellationNoticeDays = {}) {
1895
+ constructor(entitlements, planPreview, subscriptionUsage, usageSnapshot, subscriptionWrite, tenantIdResolver = null, userIdResolver = null, blockedPlans = null, promoCodes = null, auditService = null, userEmailResolver = null, auditContextResolver = null, subscriptionBundles = null, contractFreeze = null, trialProjection = null, cancellationNoticeDays = NO_NOTICE_PERIOD) {
1893
1896
  this.entitlements = entitlements;
1894
1897
  this.planPreview = planPreview;
1895
1898
  this.subscriptionUsage = subscriptionUsage;
@@ -2712,6 +2715,7 @@ TenantBillingController = _ts_decorate12([
2712
2715
 
2713
2716
  // src/billing/tenant-billing.module.ts
2714
2717
  import { Module as Module2 } from "@nestjs/common";
2718
+ import { planCatalogSchema } from "@saasicat/spec";
2715
2719
  function _ts_decorate13(decorators, target, key, desc) {
2716
2720
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2717
2721
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -2719,6 +2723,27 @@ function _ts_decorate13(decorators, target, key, desc) {
2719
2723
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2720
2724
  }
2721
2725
  __name(_ts_decorate13, "_ts_decorate");
2726
+ function settingFromCatalog(catalog, key) {
2727
+ const settings = catalog.tenantBilling;
2728
+ const value = settings?.[key];
2729
+ if (value === void 0) {
2730
+ throw new Error(`The plan catalogue carries no tenantBilling.${key}. It is a required section of config/saas.yaml and the loader refuses a file without it, so a catalogue that reaches here without one was built in code: check the \`dbCatalog\` block, which forwards \`tenantBilling\` from that same file, or the object passed to \`planCatalog\`.`);
2731
+ }
2732
+ return value;
2733
+ }
2734
+ __name(settingFromCatalog, "settingFromCatalog");
2735
+ var MOVED_TO_THE_FILE = Object.keys(planCatalogSchema.properties?.tenantBilling?.properties ?? // Refuses to guess rather than falling back to an empty list: an empty
2736
+ // one would turn this guard off without saying so.
2737
+ (() => {
2738
+ throw new Error("plan-catalog.schema.json declares no tenantBilling properties \u2014 @saasicat/spec and @saasicat/nest are out of step.");
2739
+ })());
2740
+ function assertNoMovedOptions(options) {
2741
+ const given = options;
2742
+ const passed = MOVED_TO_THE_FILE.filter((key) => given[key] !== void 0);
2743
+ if (passed.length === 0) return;
2744
+ throw new Error(`TenantBillingModule.forRoot() no longer accepts ${passed.join(" and ")} \u2014 this moved to config/saas.yaml under \`tenantBilling:\`. Move the value there (both members of each setting are required) and delete it here. The file is the one place these live, so a fallback here would be the second home that change exists to close. See docs/guides/upgrade-to-1.0.md.`);
2745
+ }
2746
+ __name(assertNoMovedOptions, "assertNoMovedOptions");
2722
2747
  var TenantBillingModule = class _TenantBillingModule {
2723
2748
  static {
2724
2749
  __name(this, "TenantBillingModule");
@@ -2733,18 +2758,20 @@ var TenantBillingModule = class _TenantBillingModule {
2733
2758
  TenantAdminGuard,
2734
2759
  PlanChangePreviewService
2735
2760
  ];
2736
- if (options.cancellationNoticeDays) {
2737
- providers.push({
2738
- provide: CANCELLATION_NOTICE_DAYS_TOKEN,
2739
- useValue: options.cancellationNoticeDays
2740
- });
2741
- }
2742
- if (options.selfServiceBlockedPlans) {
2743
- providers.push({
2744
- provide: SELF_SERVICE_BLOCKED_PLANS_TOKEN,
2745
- useValue: options.selfServiceBlockedPlans
2746
- });
2747
- }
2761
+ assertNoMovedOptions(options);
2762
+ providers.push({
2763
+ provide: CANCELLATION_NOTICE_DAYS_TOKEN,
2764
+ useFactory: /* @__PURE__ */ __name((catalog) => settingFromCatalog(catalog, "cancellationNoticeDays"), "useFactory"),
2765
+ inject: [
2766
+ PLAN_CATALOG_TOKEN
2767
+ ]
2768
+ }, {
2769
+ provide: SELF_SERVICE_BLOCKED_PLANS_TOKEN,
2770
+ useFactory: /* @__PURE__ */ __name((catalog) => settingFromCatalog(catalog, "selfServiceBlockedPlans"), "useFactory"),
2771
+ inject: [
2772
+ PLAN_CATALOG_TOKEN
2773
+ ]
2774
+ });
2748
2775
  if (options.subscriptionBundleRepository) {
2749
2776
  providers.push(asProvider(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN, options.subscriptionBundleRepository));
2750
2777
  }
@@ -3514,6 +3541,7 @@ export {
3514
3541
  bundleCycleFitsPlan,
3515
3542
  computeNextBundlePeriod,
3516
3543
  decideCancellation,
3544
+ NO_NOTICE_PERIOD,
3517
3545
  noticeDaysFor,
3518
3546
  decideCancellationFor,
3519
3547
  FEATURE_GUARD_MARKER,
@@ -3,7 +3,7 @@ import { ExecutionContext, DynamicModule } from '@nestjs/common';
3
3
  import { Reflector } from '@nestjs/core';
4
4
  import { SuperAdminProvisioningPort, SetupStatusResponse, SetupRequest, SetupResult, SetupConfirmMfaRequest, SetupConfirmMfaResponse } from '@saasicat/core';
5
5
  import { h as MfaService } from './admin-stats.service-TYiSUwIR.js';
6
- import { e as SaaSiCatModuleOptions } from './module-options-DVmppjem.js';
6
+ import { e as SaaSiCatModuleOptions } from './module-options-DF3H6q3A.js';
7
7
 
8
8
  /**
9
9
  * Shared metadata key for endpoints that intentionally run without application
@@ -3,7 +3,7 @@ import { ExecutionContext, DynamicModule } from '@nestjs/common';
3
3
  import { Reflector } from '@nestjs/core';
4
4
  import { SuperAdminProvisioningPort, SetupStatusResponse, SetupRequest, SetupResult, SetupConfirmMfaRequest, SetupConfirmMfaResponse } from '@saasicat/core';
5
5
  import { h as MfaService } from './admin-stats.service-DfKupywR.cjs';
6
- import { e as SaaSiCatModuleOptions } from './module-options-5-kVLwuv.cjs';
6
+ import { e as SaaSiCatModuleOptions } from './module-options-BDb8Tam7.cjs';
7
7
 
8
8
  /**
9
9
  * Shared metadata key for endpoints that intentionally run without application