@saasicat/nest 0.2.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.
Files changed (96) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +53 -0
  3. package/dist/admin/index.cjs +1283 -0
  4. package/dist/admin/index.d.cts +225 -0
  5. package/dist/admin/index.d.ts +225 -0
  6. package/dist/admin/index.js +58 -0
  7. package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
  8. package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
  9. package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
  10. package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
  11. package/dist/aggregation-CJ3qQf92.d.cts +288 -0
  12. package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
  13. package/dist/billing/index.cjs +5205 -0
  14. package/dist/billing/index.d.cts +1326 -0
  15. package/dist/billing/index.d.ts +1326 -0
  16. package/dist/billing/index.js +197 -0
  17. package/dist/catalog/index.cjs +6449 -0
  18. package/dist/catalog/index.d.cts +1073 -0
  19. package/dist/catalog/index.d.ts +1073 -0
  20. package/dist/catalog/index.js +158 -0
  21. package/dist/checkout-offer/index.cjs +807 -0
  22. package/dist/checkout-offer/index.d.cts +120 -0
  23. package/dist/checkout-offer/index.d.ts +120 -0
  24. package/dist/checkout-offer/index.js +20 -0
  25. package/dist/chunk-2FR6ZL7R.js +0 -0
  26. package/dist/chunk-2PNX2QL2.js +347 -0
  27. package/dist/chunk-2VNZYRDV.js +326 -0
  28. package/dist/chunk-7MLGHWKO.js +12 -0
  29. package/dist/chunk-ABB2CRAS.js +31 -0
  30. package/dist/chunk-AJ3EPELS.js +157 -0
  31. package/dist/chunk-E56W4U2P.js +90 -0
  32. package/dist/chunk-HRU3CP57.js +71 -0
  33. package/dist/chunk-L4SUWH5B.js +14 -0
  34. package/dist/chunk-M6CZQYPY.js +314 -0
  35. package/dist/chunk-MDIZUVIK.js +87 -0
  36. package/dist/chunk-MV6LW5UB.js +217 -0
  37. package/dist/chunk-NYLON2VC.js +6 -0
  38. package/dist/chunk-P6MYZMXQ.js +951 -0
  39. package/dist/chunk-Q53N43LQ.js +13 -0
  40. package/dist/chunk-QJVPRD3R.js +573 -0
  41. package/dist/chunk-RIGLQ4BV.js +3367 -0
  42. package/dist/chunk-SABTXESR.js +24 -0
  43. package/dist/chunk-SHUYVCID.js +6 -0
  44. package/dist/chunk-SYEVGPFC.js +686 -0
  45. package/dist/chunk-V7HUZ6UQ.js +5923 -0
  46. package/dist/chunk-WPUVJRIN.js +29 -0
  47. package/dist/chunk-XCZ3TTFK.js +22 -0
  48. package/dist/chunk-XSGU2Y2O.js +1611 -0
  49. package/dist/chunk-YNEW3L63.js +190 -0
  50. package/dist/chunk-YXGHPILG.js +614 -0
  51. package/dist/chunk-ZL4LLE2Z.js +465 -0
  52. package/dist/di-CcNeq9v-.d.cts +16 -0
  53. package/dist/di-CcNeq9v-.d.ts +16 -0
  54. package/dist/discovery/index.cjs +588 -0
  55. package/dist/discovery/index.d.cts +299 -0
  56. package/dist/discovery/index.d.ts +299 -0
  57. package/dist/discovery/index.js +49 -0
  58. package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
  59. package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
  60. package/dist/entitlement/index.cjs +805 -0
  61. package/dist/entitlement/index.d.cts +60 -0
  62. package/dist/entitlement/index.d.ts +60 -0
  63. package/dist/entitlement/index.js +57 -0
  64. package/dist/index.cjs +15918 -0
  65. package/dist/index.d.cts +85 -0
  66. package/dist/index.d.ts +85 -0
  67. package/dist/index.js +956 -0
  68. package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
  69. package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
  70. package/dist/platform/index.cjs +2772 -0
  71. package/dist/platform/index.d.cts +118 -0
  72. package/dist/platform/index.d.ts +118 -0
  73. package/dist/platform/index.js +37 -0
  74. package/dist/promo/index.cjs +995 -0
  75. package/dist/promo/index.d.cts +121 -0
  76. package/dist/promo/index.d.ts +121 -0
  77. package/dist/promo/index.js +58 -0
  78. package/dist/registration/index.cjs +1687 -0
  79. package/dist/registration/index.d.cts +444 -0
  80. package/dist/registration/index.d.ts +444 -0
  81. package/dist/registration/index.js +82 -0
  82. package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
  83. package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
  84. package/dist/service-DX8KbGXl.d.cts +109 -0
  85. package/dist/service-DX8KbGXl.d.ts +109 -0
  86. package/dist/subscription-contract/index.cjs +471 -0
  87. package/dist/subscription-contract/index.d.cts +17 -0
  88. package/dist/subscription-contract/index.d.ts +17 -0
  89. package/dist/subscription-contract/index.js +21 -0
  90. package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
  91. package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
  92. package/dist/testing/index.cjs +3942 -0
  93. package/dist/testing/index.d.cts +279 -0
  94. package/dist/testing/index.d.ts +279 -0
  95. package/dist/testing/index.js +1195 -0
  96. package/package.json +185 -0
@@ -0,0 +1,1326 @@
1
+ import { BillingCycle, FeatureKey, UpsellOfferResolver, BundleRepository, CatalogEntryRepository, UpsellOffer, PlanCatalog, PlanCatalogReadSink, PlanCatalogReadSnapshot, PlanCatalogImportSink, PlanCatalogImportReport, PlanId, PlanDef, QuotaKey, MarketingTopFeature, FeatureUiRegistry, MarketingProjectionRepository, BusinessTypeRepository, SubscriptionUsagePort, UsageSnapshotPort, NewContractLineItemData, TenantSubscriptionWritePort, SubscriptionBundleRepository, SubscriptionBundleView, SubscriptionBundleRecord, SubscriptionUsageRecord, OnboardingSelectionResponse, SubscriptionContractRepository, ConfiguratorSourcesLookup, ConfiguratorMarketingProvider, ConfiguratorCatalog, PlanRepository, BundleVersionRow } from '@saasicat/types';
2
+ export { BundleVersionFields, ChangeDirection, DiffResult, PlanVersionFields, VersionChange, VersionChangeDirection, classifyBundleVersionDiff, classifyPlanDiff } from '@saasicat/types';
3
+ import * as _nestjs_common from '@nestjs/common';
4
+ import { CanActivate, ExecutionContext, ExceptionFilter, ArgumentsHost, Type, DynamicModule, ForwardReference, Provider } from '@nestjs/common';
5
+ export { Type as NestType } from '@nestjs/common';
6
+ import { Reflector } from '@nestjs/core';
7
+ import { d as EntitlementService, L as LimitExceededError, b as EffectiveLimitsSnapshot, t as toEffectiveLimitsSnapshot } from '../aggregation-CJ3qQf92.cjs';
8
+ import { P as ProviderSpec } from '../di-CcNeq9v-.cjs';
9
+ import { S as SubscriptionContractService } from '../subscription-contract.service--cm47ZJJ.cjs';
10
+ import { f as PromoCodesService } from '../service-DX8KbGXl.cjs';
11
+ import { A as AdminAuditService } from '../admin-audit.service-9IqXMlZm.cjs';
12
+ import '../plan-resolution-CFCoUkrE.cjs';
13
+
14
+ /**
15
+ * Finds the next period boundary that lies strictly **after** `after`.
16
+ * Iterates from `startedAt` (fallback: `after`) by +1 cycle each time, until
17
+ * the result is greater than `after`.
18
+ */
19
+ declare function periodEndAfter(startedAt: Date | null, cycle: BillingCycle, after: Date): Date;
20
+ /**
21
+ * Returns the initial period window for a subscription
22
+ * (`currentPeriodStart`/`currentPeriodEnd`). `start` is `startedAt`,
23
+ * `end = start + 1 cycle`. We deliberately do not iterate — on a
24
+ * plan change / renewal cron run the value is actively reset.
25
+ */
26
+ declare function initialPeriodWindow(startedAt: Date, cycle: BillingCycle): {
27
+ start: Date;
28
+ end: Date;
29
+ };
30
+ /**
31
+ * Finds the earliest period boundary that lies **at least `minLeadDays` days**
32
+ * in the future. Used by the notification cron to determine the effective
33
+ * period for upcoming version changes:
34
+ *
35
+ * - `BillingCycle = YEARLY` with `currentPeriodEnd ∈ [+42d, +43d)`: trivial,
36
+ * effective date = currentPeriodEnd.
37
+ * - `BillingCycle = MONTHLY` with `currentPeriodEnd in 16d`: 16d < 42d → the
38
+ * function jumps to the period after next (≥ 42d lead time).
39
+ *
40
+ * Spec: ROADMAP §2 no. 3 (advance-warning period), §6.1 (time-based selection).
41
+ */
42
+ declare function periodEndWithMinLead(startedAt: Date | null, cycle: BillingCycle, now: Date, minLeadDays?: number): Date;
43
+
44
+ /** Generic shape of a PlanVersion snapshot for publish validation. */
45
+ interface PublishablePlanVersion {
46
+ id: string;
47
+ publishedAt: Date | null;
48
+ supersededAt: Date | null;
49
+ baseVersionId: string | null;
50
+ }
51
+ /**
52
+ * Structured publish error — the consumer maps it to the appropriate HTTP
53
+ * status code (NestJS: 400 / 404 / 409).
54
+ */
55
+ declare class PublishValidationError extends Error {
56
+ readonly code: 'CHANGE_NOTE_REQUIRED' | 'NOT_FOUND' | 'ALREADY_PUBLISHED' | 'NO_BASE_VERSION' | 'BASE_NOT_FOUND' | 'BASE_SUPERSEDED' | 'OPTIMISTIC_LOCK_CONFLICT';
57
+ constructor(code: 'CHANGE_NOTE_REQUIRED' | 'NOT_FOUND' | 'ALREADY_PUBLISHED' | 'NO_BASE_VERSION' | 'BASE_NOT_FOUND' | 'BASE_SUPERSEDED' | 'OPTIMISTIC_LOCK_CONFLICT', message: string);
58
+ }
59
+ /**
60
+ * Checks the `changeNote` requirement. Throws `CHANGE_NOTE_REQUIRED` if empty /
61
+ * only whitespace.
62
+ */
63
+ declare function assertChangeNote(changeNote: string | null | undefined): string;
64
+ /**
65
+ * Checks the preconditions a draft must satisfy before it may be published:
66
+ *
67
+ * - Exists (otherwise NOT_FOUND)
68
+ * - `publishedAt` is null (otherwise ALREADY_PUBLISHED)
69
+ * - `baseVersionId` is set (otherwise NO_BASE_VERSION)
70
+ *
71
+ * If `base` is passed, it additionally checks:
72
+ * - `base.supersededAt` is null (otherwise BASE_SUPERSEDED)
73
+ *
74
+ * `null` for `base` means "base lookup performed separately but not
75
+ * found" → BASE_NOT_FOUND.
76
+ */
77
+ declare function assertDraftPublishable(draft: PublishablePlanVersion | null, draftId: string): asserts draft is PublishablePlanVersion;
78
+ /**
79
+ * Checks that the base version the draft diffs against is still
80
+ * available (not superseded).
81
+ */
82
+ declare function assertBaseVersionFresh(base: PublishablePlanVersion | null, baseId: string, planContext: string): asserts base is PublishablePlanVersion;
83
+ /**
84
+ * Checks the result of the optimistic-lock update (`updateMany WHERE
85
+ * publishedAt IS NULL`): exactly 1 row expected. Otherwise a
86
+ * concurrent admin has already published the draft.
87
+ */
88
+ declare function assertOptimisticLockHeld(updateCount: number, draftId: string): void;
89
+
90
+ /**
91
+ * What the renewal cron should do with a subscription whose
92
+ * `pendingPlanVersionEffectiveAt` has been reached.
93
+ *
94
+ * - `ROLL_FORWARD`: pending becomes the new live version. Happens when
95
+ * either `nonRegressive=true` (platform guarantee: no regression)
96
+ * or `accepted=true` (the tenant has agreed to the change).
97
+ * - `CLEAR_PENDING`: pending is discarded. Happens when the
98
+ * pending version is regressive AND the tenant has **not** agreed
99
+ * by the effective date (variant B from roadmap §6.2: opt-in
100
+ * missed → no change).
101
+ * - `SKIP`: the sub has no pending version or the effective date is
102
+ * still in the future. (Should normally not be found by the cron
103
+ * filter at all — caught defensively here.)
104
+ */
105
+ type RenewalDecision = 'ROLL_FORWARD' | 'CLEAR_PENDING' | 'SKIP';
106
+ /** Input shape for `decideRenewal` (what the cron reads from the sub). */
107
+ interface RenewalSubInput {
108
+ pendingPlanVersionId: string | null;
109
+ pendingPlanVersionEffectiveAt: Date | null;
110
+ pendingPlanVersionAccepted: boolean;
111
+ /** `nonRegressive` from the referenced PlanVersion. */
112
+ pendingPlanVersionNonRegressive: boolean;
113
+ }
114
+ /**
115
+ * Decides what should happen to a subscription with a due pending version.
116
+ */
117
+ declare function decideRenewal(sub: RenewalSubInput, now: Date): RenewalDecision;
118
+ /**
119
+ * Returns the fields that must be reset in the subscription update after a
120
+ * `ROLL_FORWARD` or `CLEAR_PENDING`. The consumer inserts them
121
+ * into its Prisma `update.data` block.
122
+ */
123
+ declare function clearPendingPlanVersionFields(): {
124
+ pendingPlanVersionId: null;
125
+ pendingPlanVersionEffectiveAt: null;
126
+ pendingPlanVersionAccepted: false;
127
+ pendingPlanVersionAcceptedAt: null;
128
+ pendingPlanVersionAcceptedByUserId: null;
129
+ pendingPlanVersionNotifiedAt: null;
130
+ pendingPlanVersionReminderSentAt: null;
131
+ };
132
+ /** Input shape for `computeNextPeriod`. */
133
+ interface PeriodRollInput {
134
+ /** Subscription.currentPeriodEnd. NULL → no period active → SKIP. */
135
+ currentPeriodEnd: Date | null;
136
+ billingCycle: BillingCycle;
137
+ canceledAt: Date | null;
138
+ }
139
+ /** Result: the next period window or `null` (skip). */
140
+ interface NextPeriodWindow {
141
+ currentPeriodStart: Date;
142
+ currentPeriodEnd: Date;
143
+ }
144
+ /**
145
+ * Computes the next period window. `null` means: no action
146
+ * (either the period hasn't been reached yet, canceled, or NULL period).
147
+ *
148
+ * Logic (spec: SUPERADMIN_PLANS_DASHBOARD_TODO §2.2):
149
+ * - If `canceledAt` is set → SKIP.
150
+ * - If `currentPeriodEnd === null` → SKIP (trial / PENDING_SALES).
151
+ * - If `currentPeriodEnd > now` → SKIP (period still active).
152
+ * - Otherwise → start := old `currentPeriodEnd`, end := periodEndAfter(start).
153
+ */
154
+ declare function computeNextPeriod(sub: PeriodRollInput, now: Date): NextPeriodWindow | null;
155
+
156
+ declare const REQUIRE_FEATURE_KEY = "require-feature";
157
+ /**
158
+ * Tag handler with required feature keys.
159
+ *
160
+ * No value = public. Multiple values = logical OR.
161
+ */
162
+ declare const RequireFeature: (...features: FeatureKey[]) => _nestjs_common.CustomDecorator<string>;
163
+
164
+ interface FeatureGuardConfig {
165
+ /**
166
+ * Wraps `EntitlementService.computeLimits` in a tenant context.
167
+ * Consumers with RLS pass `runWithTenant(tenantId, fn)` here
168
+ * — so that the repository queries see the tenant in the DB session
169
+ * variables. Without RLS: omit the field, the default is identity.
170
+ */
171
+ tenantContextRunner?: <T>(tenantId: string, fn: () => Promise<T>) => Promise<T>;
172
+ /**
173
+ * Reads the platform role from the `request.user` object. Used for the
174
+ * SUPER_ADMIN bypass — platform support may help a tenant even
175
+ * when the feature is not in that tenant's plan.
176
+ *
177
+ * Default: `user.role ?? user.platformRole`.
178
+ */
179
+ userRoleResolver?: (user: unknown) => string | undefined;
180
+ /**
181
+ * Reads the `tenantId` from the request. Default:
182
+ * `request.tenantId ?? request.user?.tenantId`.
183
+ */
184
+ tenantIdResolver?: (request: unknown) => string | undefined;
185
+ }
186
+ declare const FEATURE_GUARD_CONFIG_TOKEN: unique symbol;
187
+
188
+ declare class FeatureGuard implements CanActivate {
189
+ private readonly reflector;
190
+ private readonly entitlements;
191
+ private readonly config;
192
+ private readonly upsellResolver;
193
+ private readonly logger;
194
+ constructor(reflector: Reflector, entitlements: EntitlementService, config?: FeatureGuardConfig | null, upsellResolver?: UpsellOfferResolver | null);
195
+ canActivate(context: ExecutionContext): Promise<boolean>;
196
+ /**
197
+ * Upsell response (#36): with a registered `UpsellOfferResolver` the 403
198
+ * becomes machine-readable (`FeatureNotLicensedBody`), so consumer UIs can
199
+ * render a purchase offer. Without a resolver the previous plain 403
200
+ * remains — no breaking change.
201
+ *
202
+ * Deliberately 403 + `code` field instead of 402 — rationale in
203
+ * `@saasicat/types` upsell.types.ts (402 is reserved / inconsistently
204
+ * supported; SPA interceptors must not treat the 403 as an auth error, the
205
+ * distinction runs through `code`).
206
+ */
207
+ private buildNotLicensedException;
208
+ /**
209
+ * A resolver error must not turn the semantically correct 403 into a 500 —
210
+ * degrade to empty offers and log.
211
+ */
212
+ private resolveOffersSafe;
213
+ }
214
+
215
+ declare const UPSELL_OFFER_RESOLVER_TOKEN: unique symbol;
216
+ /**
217
+ * Optional currency for the offers of the `CatalogBundleUpsellResolver`
218
+ * (ISO-4217 string, e.g. `'EUR'`). Without a provider: `'EUR'`.
219
+ */
220
+ declare const UPSELL_OFFER_CURRENCY_TOKEN: unique symbol;
221
+
222
+ declare class CatalogBundleUpsellResolver implements UpsellOfferResolver {
223
+ private readonly bundleRepo;
224
+ private readonly projectKey;
225
+ private readonly catalogEntryRepo;
226
+ private readonly currency;
227
+ constructor(bundleRepo: BundleRepository, projectKey: string, catalogEntryRepo?: CatalogEntryRepository | null, currency?: string | null);
228
+ resolveOffers(featureKeys: string[], tenantId: string): Promise<UpsellOffer[]>;
229
+ /**
230
+ * Uncovered dependencies of the missing features (#35) — feed into the
231
+ * coverage ranking. Without a CatalogEntryRepository: empty (ranking
232
+ * degrades to price).
233
+ */
234
+ private lookupUnmetRequires;
235
+ /** Only published-and-live AND marketed — non-marketed bundles are not an offer. */
236
+ private listLiveMarketedBundles;
237
+ private toOffer;
238
+ }
239
+
240
+ declare class LimitExceededFilter implements ExceptionFilter {
241
+ private readonly logger;
242
+ catch(exception: LimitExceededError, host: ArgumentsHost): void;
243
+ }
244
+
245
+ /**
246
+ * Schema validation error bundling all Ajv errors — one call returns
247
+ * the full list, no round-trip editing needed.
248
+ */
249
+ interface AjvErrorLike {
250
+ instancePath?: string;
251
+ message?: string;
252
+ schemaPath?: string;
253
+ }
254
+ declare class PlanCatalogValidationError extends Error {
255
+ readonly source: string;
256
+ readonly errors: AjvErrorLike[];
257
+ constructor(source: string, errors: AjvErrorLike[]);
258
+ }
259
+ interface LoadPlanCatalogOptions {
260
+ /**
261
+ * Absolute path or relative path (resolved against CWD).
262
+ */
263
+ path: string;
264
+ /**
265
+ * Optional: additional cross-field validations that the JSON schema
266
+ * cannot cover. Default: enable all (see validateConsistency).
267
+ */
268
+ crossFieldChecks?: boolean;
269
+ }
270
+ /**
271
+ * Loads + validates a saas.yaml file.
272
+ *
273
+ * Throws `PlanCatalogValidationError` on schema violations or
274
+ * cross-field violations. Throws `Error` on IO/YAML parse errors.
275
+ */
276
+ declare function loadPlanCatalogFromFile(opts: LoadPlanCatalogOptions): PlanCatalog;
277
+ /**
278
+ * Variant for tests / in-memory loading: takes YAML content as a string,
279
+ * `source` is only for error logging.
280
+ */
281
+ declare function loadPlanCatalogFromString(yamlContent: string, opts: {
282
+ source: string;
283
+ crossFieldChecks?: boolean;
284
+ }): PlanCatalog;
285
+
286
+ declare const PLAN_CATALOG_TOKEN: unique symbol;
287
+ declare const PLAN_CATALOG_READ_SINK_TOKEN: unique symbol;
288
+ interface PlanCatalogModuleOptions {
289
+ /** Build-time identity of the app. */
290
+ projectKey: string;
291
+ /**
292
+ * App-identity block (branding + version) from `config/saas.yaml#app`.
293
+ * Flows into `PLAN_CATALOG_TOKEN.app` and from there into the
294
+ * AdminPublicBoot endpoint + the AdminManifestConfig.
295
+ */
296
+ app?: PlanCatalog['app'];
297
+ currency: string;
298
+ vatRate: number;
299
+ /**
300
+ * App-wide marketing configuration (SPEC_V2 §6.5) — including the
301
+ * `availableLocales` pool. Flows into `PLAN_CATALOG_TOKEN.marketing`
302
+ * and from there into the admin manifest (`project.availableLocales`).
303
+ */
304
+ marketing?: PlanCatalog['marketing'];
305
+ /** App-specific adapter for DB reads. */
306
+ sink: ProviderSpec<PlanCatalogReadSink>;
307
+ /** Modules that must be visible in the DI scope (analogous to CatalogModule). */
308
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
309
+ extraProviders?: Provider[];
310
+ /** Defaults to `true`. */
311
+ global?: boolean;
312
+ }
313
+ declare class PlanCatalogModule {
314
+ static forRoot(options: PlanCatalogModuleOptions): DynamicModule;
315
+ /**
316
+ * Tests / in-memory setup: takes the catalog object directly, without a
317
+ * sink/DB lookup. For unit tests + test bootstraps.
318
+ */
319
+ static forRootWithCatalog(catalog: PlanCatalog, opts?: {
320
+ global?: boolean;
321
+ }): DynamicModule;
322
+ }
323
+
324
+ interface PlanCatalogBuildSettings {
325
+ projectKey: string;
326
+ /** App identity (branding + version) from `config/saas.yaml#app`. Optional. */
327
+ app?: PlanCatalog['app'];
328
+ currency: string;
329
+ vatRate: number;
330
+ /** App-wide marketing configuration (SPEC_V2 §6.5). Optional. */
331
+ marketing?: PlanCatalog['marketing'];
332
+ }
333
+ declare function buildPlanCatalogFromSnapshot(settings: PlanCatalogBuildSettings, snapshot: PlanCatalogReadSnapshot): PlanCatalog;
334
+
335
+ declare const PLAN_CATALOG_IMPORT_SINK_TOKEN: unique symbol;
336
+ declare class PlanCatalogImporterService {
337
+ private readonly sink;
338
+ constructor(sink: PlanCatalogImportSink);
339
+ /**
340
+ * Import directly from a YAML string. Apps pass the contents of the
341
+ * `saas.yaml` through via the HTTP body; CLI tools can read the file
342
+ * contents themselves beforehand.
343
+ *
344
+ * @throws on a schema failure of the YAML (validated first via the loader).
345
+ */
346
+ importFromYaml(yamlContent: string, opts?: {
347
+ crossFieldChecks?: boolean;
348
+ source?: string;
349
+ }): Promise<PlanCatalogImportReport>;
350
+ importCatalog(catalog: PlanCatalog): Promise<PlanCatalogImportReport>;
351
+ }
352
+
353
+ interface PlanCatalogImporterControllerConfig {
354
+ /**
355
+ * Class-level guards for `/admin/billing/plan-catalog/import`. Required
356
+ * when `controller` is set — pass `[]` explicitly for auth-free
357
+ * (only sensible in tests).
358
+ */
359
+ guards: Array<Type<CanActivate>>;
360
+ }
361
+ interface PlanCatalogImporterModuleOptions {
362
+ sink: ProviderSpec<PlanCatalogImportSink>;
363
+ controller?: PlanCatalogImporterControllerConfig;
364
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
365
+ extraProviders?: Provider[];
366
+ global?: boolean;
367
+ }
368
+ declare class PlanCatalogImporterModule {
369
+ static forRoot(options: PlanCatalogImporterModuleOptions): DynamicModule;
370
+ }
371
+
372
+ declare class PlanCatalogImportDto {
373
+ yamlContent: string;
374
+ crossFieldChecks?: boolean;
375
+ }
376
+ declare function buildPlanCatalogImporterController(guards: Array<Type<CanActivate>>): Type;
377
+
378
+ /**
379
+ * Finds a plan in the catalog. `undefined` if it does not exist.
380
+ */
381
+ declare function findPlan(catalog: PlanCatalog, planId: PlanId): PlanDef | undefined;
382
+ /**
383
+ * Like `findPlan`, but throws a typed error if the plan does not exist.
384
+ * For code paths where its absence is a bug (plan-change validation,
385
+ * subscription snapshot on creation).
386
+ */
387
+ declare function getPlanOrThrow(catalog: PlanCatalog, planId: PlanId): PlanDef;
388
+ /**
389
+ * Returns all marketed plans (`marketed: true` or undefined). Order as in the
390
+ * catalog. ENTERPRISE and other `marketed: false` plans are NOT included —
391
+ * these can only be activated via `ahp paket apply` / special contract and do
392
+ * not belong in self-service onboarding lists.
393
+ */
394
+ declare function getMarketedPlans(catalog: PlanCatalog): PlanDef[];
395
+ /**
396
+ * Net list price from the catalog. `null` when:
397
+ * - the plan does not exist
398
+ * - the plan is `marketed: false` (e.g. ENTERPRISE — special contract,
399
+ * no list price)
400
+ * - the plan has no price for the cycle (`monthlyNet`/`yearlyNet === null`)
401
+ */
402
+ declare function getPlanPriceNet(catalog: PlanCatalog, planId: PlanId, cycle: BillingCycle): number | null;
403
+ /**
404
+ * Gross list price from the catalog (net * (1 + vatRate/100)).
405
+ * `null` with the same rules as `getPlanPriceNet`. `vatRate` is optional;
406
+ * default: `catalog.vatRate`.
407
+ */
408
+ declare function getPlanPriceGross(catalog: PlanCatalog, planId: PlanId, cycle: BillingCycle, vatRate?: number): number | null;
409
+ /**
410
+ * Checks whether a plan directly contains a feature (without considering
411
+ * Bundles / subscription). For marketing lists, plan comparison tables.
412
+ *
413
+ * Note: this is NOT the entitlement check for a concrete tenant.
414
+ * The real entitlement check (`EntitlementService.computeLimits`) takes
415
+ * Bundle bookings and custom limits into account — this helper function is
416
+ * only the static plan definition.
417
+ */
418
+ declare function isFeatureInPlan(catalog: PlanCatalog, planId: PlanId, featureKey: FeatureKey): boolean;
419
+ /**
420
+ * Aggregated plan Quota for a key. Returns `undefined` when the plan or the
421
+ * key does not exist.
422
+ *
423
+ * `-1` is the catalog convention for "unlimited"; consumers must map that to
424
+ * `Number.POSITIVE_INFINITY` themselves if they compute with it.
425
+ */
426
+ declare function getPlanQuota(catalog: PlanCatalog, planId: PlanId, quotaKey: QuotaKey): number | undefined;
427
+ /**
428
+ * List of all feature keys declared in the catalog that are not
429
+ * `plannedOnly: true`. For UI lists that show bookable features.
430
+ */
431
+ declare function getActiveFeatureKeys(catalog: PlanCatalog): FeatureKey[];
432
+ /**
433
+ * Checks whether a feature is `plannedOnly: true`. If the feature is not
434
+ * declared in the catalog, the function returns `false` (conservative:
435
+ * unknown keys are not marked as "planned").
436
+ */
437
+ declare function isFeaturePlannedOnly(catalog: PlanCatalog, featureKey: FeatureKey): boolean;
438
+
439
+ declare const FEATURE_UI_REGISTRY_TOKEN: unique symbol;
440
+
441
+ interface MarketingFields {
442
+ displayLabel?: string;
443
+ description?: string;
444
+ visible?: boolean;
445
+ badge?: string;
446
+ topFeatures?: MarketingTopFeature[];
447
+ trialEnabled?: boolean;
448
+ trialDays?: number;
449
+ priceTag?: string | null;
450
+ ctaLabel?: string | null;
451
+ highlight?: boolean;
452
+ priority?: number;
453
+ }
454
+ interface PlanResponseEntry {
455
+ id: string;
456
+ name: string;
457
+ tagline: string;
458
+ monthlyNet: number | null;
459
+ yearlyNet: number | null;
460
+ popular: boolean;
461
+ quotas: Record<string, number>;
462
+ features: string[];
463
+ /** SPEC_V2 §11.1 M6 — optional, present if a MarketingProjection exists. */
464
+ marketing?: MarketingFields;
465
+ }
466
+ interface PublicBundleEntry {
467
+ bundleVersionId: string;
468
+ bundleKey: string;
469
+ label: string;
470
+ description: string | null;
471
+ features: string[];
472
+ quotas: Record<string, number>;
473
+ monthlyNet: string | null;
474
+ yearlyNet: string | null;
475
+ /**
476
+ * Uncovered feature dependencies (#35): union of the `requires` of the
477
+ * contained features minus the ones contained in the bundle itself. The
478
+ * configurator greys out the bundle if these keys are neither in the plan
479
+ * nor in the current selection. Empty without a CatalogEntryRepository
480
+ * (no requires data available).
481
+ */
482
+ requiresFeatures: string[];
483
+ marketing?: MarketingFields;
484
+ }
485
+ interface PublicBusinessTypeEntry {
486
+ businessTypeVersionId: string;
487
+ businessTypeKey: string;
488
+ label: string;
489
+ description: string | null;
490
+ monthlyNet: string | null;
491
+ yearlyNet: string | null;
492
+ bundleKeys: string[];
493
+ quotaOverrides: Record<string, number>;
494
+ marketing?: MarketingFields;
495
+ }
496
+ declare class PublicCatalogController {
497
+ private readonly planCatalog;
498
+ private readonly featureRegistry;
499
+ private readonly projectKey;
500
+ private readonly marketingRepo;
501
+ private readonly bundleRepo;
502
+ private readonly businessTypeRepo;
503
+ private readonly catalogEntryRepo;
504
+ constructor(planCatalog: PlanCatalog, featureRegistry: FeatureUiRegistry, projectKey?: string | null, marketingRepo?: MarketingProjectionRepository | null, bundleRepo?: BundleRepository | null, businessTypeRepo?: BusinessTypeRepository | null, catalogEntryRepo?: CatalogEntryRepository | null);
505
+ listPlans(lang?: string, localeParam?: string): Promise<PlanResponseEntry[]>;
506
+ listFeatureRegistry(): Promise<FeatureUiRegistry>;
507
+ /**
508
+ * SPEC_V2 §11.1 M6 Pack 2c — public catalog endpoint for bundles
509
+ * (stem list with live versions). If a `MarketingProjection` with
510
+ * `targetType=BUNDLE` + matching locale exists, it is merged in.
511
+ */
512
+ listBundles(lang?: string, localeParam?: string): Promise<PublicBundleEntry[]>;
513
+ /**
514
+ * `requires` data for `requiresFeatures` (#35) — comes from the
515
+ * FeatureCatalogEntries. Without a CatalogEntryRepository or on DB errors,
516
+ * an empty index: the auth-free endpoint must not break on this,
517
+ * `requiresFeatures` then stays conservatively empty.
518
+ */
519
+ private loadFeatureRequiresIndex;
520
+ /**
521
+ * SPEC_V2 §11.1 M6 Pack 2c — public catalog endpoint for BusinessTypes
522
+ * (stem list with live versions + bundle composition + marketing).
523
+ */
524
+ listBusinessTypes(lang?: string, localeParam?: string): Promise<PublicBusinessTypeEntry[]>;
525
+ private lookupMarketing;
526
+ private toBundleEntry;
527
+ }
528
+
529
+ interface PublicCatalogModuleOptions {
530
+ /** Required: consumer-specific FeatureUiRegistry. */
531
+ featureUiRegistry: FeatureUiRegistry;
532
+ /**
533
+ * SPEC_V2 §11.1 M6 Pack 2c — app identity (e.g. "clubapp"). Used
534
+ * for marketing lookups + bundles/business-types filter.
535
+ * Optional; if omitted, the new endpoints return empty lists.
536
+ */
537
+ projectKey?: string;
538
+ /**
539
+ * Optional. When set, `/billing/bundles` is active.
540
+ */
541
+ bundleRepository?: ProviderSpec<BundleRepository>;
542
+ /**
543
+ * Optional. When set, `/billing/business-types` is active.
544
+ */
545
+ businessTypeRepository?: ProviderSpec<BusinessTypeRepository>;
546
+ /**
547
+ * Optional. When set, marketing texts are merged into /billing/bundles
548
+ * and /billing/business-types (locale filter).
549
+ */
550
+ marketingRepository?: ProviderSpec<MarketingProjectionRepository>;
551
+ /**
552
+ * Optional (#13). When set (+ projectKey), `/billing/feature-registry`
553
+ * overlays the editable `FeatureCatalogEntry.icon` from the DB over the
554
+ * static registry.
555
+ */
556
+ catalogEntryRepository?: ProviderSpec<CatalogEntryRepository>;
557
+ /**
558
+ * Modules whose providers must be visible in the DI scope — typically
559
+ * `PrismaModule`/`PlatformAdaptersModule` for the repositories.
560
+ */
561
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
562
+ extraProviders?: Provider[];
563
+ }
564
+ declare class PublicCatalogModule {
565
+ static forRoot(options: PublicCatalogModuleOptions): DynamicModule;
566
+ }
567
+
568
+ declare const PUBLIC_CATALOG_PROJECT_KEY_TOKEN: unique symbol;
569
+ declare const PUBLIC_CATALOG_BUNDLE_REPOSITORY_TOKEN: unique symbol;
570
+ declare const PUBLIC_CATALOG_BUSINESS_TYPE_REPOSITORY_TOKEN: unique symbol;
571
+ declare const PUBLIC_CATALOG_MARKETING_REPOSITORY_TOKEN: unique symbol;
572
+
573
+ /**
574
+ * List of all auth guards the platform controller should iterate in order
575
+ * (analogous to `@UseGuards(JwtAuthGuard, TenantGuard, RolesGuard)`).
576
+ * Provided via `forRoot.authGuards` — can be an array of existing instances
577
+ * or a factory provider.
578
+ */
579
+ declare const TENANT_AUTH_GUARDS_TOKEN: unique symbol;
580
+ /**
581
+ * Resolver function `(req) => string` that extracts the `tenantId` from the
582
+ * request. Default: `req.user.tenantId`.
583
+ */
584
+ declare const TENANT_ID_RESOLVER_TOKEN: unique symbol;
585
+ /**
586
+ * Resolver function `(req) => string` that extracts the `userId` from the
587
+ * request. Default: `req.user.sub ?? req.user.id`.
588
+ */
589
+ declare const USER_ID_RESOLVER_TOKEN: unique symbol;
590
+ /**
591
+ * Resolver function `(req) => string` that extracts the user email from the
592
+ * request. Optional — used by the audit-log path to build the AdminActor
593
+ * (`{userId, email, source: 'web', context}`). Default: `req.user.email`.
594
+ * If the consumer's JWT does not carry an email, the resolver can return
595
+ * `null` — the audit-log path then falls back to `'unknown'`.
596
+ */
597
+ declare const USER_EMAIL_RESOLVER_TOKEN: unique symbol;
598
+ /**
599
+ * Resolver function `(req) => string` that extracts an audit context from the
600
+ * request (e.g. session ID, trace ID). Default: `req.headers['x-session-id']`
601
+ * or `'tenant-self-service'`.
602
+ */
603
+ declare const AUDIT_CONTEXT_RESOLVER_TOKEN: unique symbol;
604
+ /** Adapter token: consumer's `SubscriptionUsagePort` implementation. */
605
+ declare const SUBSCRIPTION_USAGE_PORT_TOKEN: unique symbol;
606
+ /** Adapter token: consumer's `UsageSnapshotPort` implementation. */
607
+ declare const USAGE_SNAPSHOT_PORT_TOKEN: unique symbol;
608
+ /** Adapter token: consumer's `TenantSubscriptionWritePort` implementation. */
609
+ declare const SUBSCRIPTION_WRITE_PORT_TOKEN: unique symbol;
610
+ type TenantIdResolver = (req: unknown) => string | null | undefined;
611
+ type UserIdResolver = (req: unknown) => string | null | undefined;
612
+ type UserEmailResolver = (req: unknown) => string | null | undefined;
613
+ type AuditContextResolver = (req: unknown) => string | null | undefined;
614
+ type AuthGuardList = ReadonlyArray<CanActivate>;
615
+ /**
616
+ * Optional adapter token: projects the new trial end of a change
617
+ * (app-specific trial logic, e.g. carry-over of the remaining time). Without a
618
+ * port, `PlanChangePreviewDto.projectedTrialEndsAt` stays `null` and the wizard
619
+ * falls back to the current trial end.
620
+ */
621
+ declare const TRIAL_PROJECTION_PORT_TOKEN: unique symbol;
622
+ interface TrialProjectionInput {
623
+ /** Current plan key of the subscription. */
624
+ currentPlan: string;
625
+ /** Target plan key of the change. */
626
+ targetPlan: string;
627
+ /** Current trial end (null = no trial). */
628
+ currentTrialEndsAt: Date | null;
629
+ /** Subscription status (e.g. 'TRIAL'/'ACTIVE'). */
630
+ status: string;
631
+ now: Date;
632
+ }
633
+ interface TrialProjectionPort {
634
+ /**
635
+ * Projected new trial end after the change. `null` if nothing changes or
636
+ * the target package does not support a trial.
637
+ */
638
+ projectTrialEndsAt(input: TrialProjectionInput): Promise<Date | null>;
639
+ }
640
+ /**
641
+ * Optional adapter token: provides due scheduled plan changes for the
642
+ * `PendingPlanMaterializationService`. Without a port, the service is not
643
+ * registered (materialization is opt-in).
644
+ */
645
+ declare const PENDING_PLAN_QUERY_PORT_TOKEN: unique symbol;
646
+ /** A due scheduled plan change — minimal for materialization. */
647
+ interface DuePendingPlanChange {
648
+ tenantId: string;
649
+ /** Target plan key of the scheduled change (`pendingPlan`). */
650
+ pendingPlan: string;
651
+ /** Target cycle (`pendingBillingCycle`); `null` → default MONTHLY. */
652
+ pendingBillingCycle: string | null;
653
+ }
654
+ interface PendingPlanQueryPort {
655
+ /**
656
+ * Returns all subscriptions with a due scheduled plan change:
657
+ * `pendingPlan != null AND pendingEffectiveAt <= now AND status != 'TRIAL'`.
658
+ * TRIAL is excluded — there the trial lifecycle drives the transition.
659
+ */
660
+ findDuePendingPlanChanges(now: Date): Promise<DuePendingPlanChange[]>;
661
+ }
662
+
663
+ declare class ComposedTenantAuthGuard implements CanActivate {
664
+ private readonly guards;
665
+ constructor(guards?: AuthGuardList | null);
666
+ canActivate(context: ExecutionContext): Promise<boolean>;
667
+ }
668
+
669
+ declare class TenantAdminGuard implements CanActivate {
670
+ canActivate(context: ExecutionContext): boolean;
671
+ }
672
+
673
+ interface SelfServiceBlockedPlans {
674
+ asTarget?: string[];
675
+ asSource?: string[];
676
+ }
677
+ declare const SELF_SERVICE_BLOCKED_PLANS_TOKEN: unique symbol;
678
+ /**
679
+ * Bundle counterpart (#37): `bundleKeys` lists bundles that are not
680
+ * bookable via self-service (only via sales/special contract).
681
+ * Applies in `SubscriptionBundlesService.addBundleToSubscription`
682
+ * (enforcement, 422) and in the bundle preview (blocker indication).
683
+ */
684
+ interface SelfServiceBlockedBundles {
685
+ bundleKeys?: string[];
686
+ }
687
+ declare const SELF_SERVICE_BLOCKED_BUNDLES_TOKEN: unique symbol;
688
+
689
+ interface ProrationDto {
690
+ daysRemainingInPeriod: number;
691
+ daysInPeriod: number;
692
+ periodStart: Date;
693
+ periodEnd: Date;
694
+ currentPriceNet: number;
695
+ targetPriceNet: number;
696
+ /** Prorated surcharge/credit until end of period. Negative = credit. */
697
+ prorataDeltaNet: number;
698
+ }
699
+ interface ProrationInput {
700
+ periodStart: Date;
701
+ periodEnd: Date;
702
+ now: Date;
703
+ /** Previous period price (bundle add: 0 — something is only added). */
704
+ currentPriceNet: number;
705
+ targetPriceNet: number;
706
+ }
707
+ declare function computeProration(input: ProrationInput): ProrationDto;
708
+
709
+ type PlanChangeType = 'UPGRADE' | 'DOWNGRADE' | 'CYCLE_CHANGE' | 'NOOP';
710
+ interface PlanSnapshotDto {
711
+ id: string;
712
+ name: string;
713
+ monthlyNet: number | null;
714
+ yearlyNet: number | null;
715
+ quotas: Record<string, number>;
716
+ features: string[];
717
+ }
718
+ interface LimitsCheckRow {
719
+ used: number;
720
+ currentMax: number;
721
+ targetMax: number;
722
+ exceeded: boolean;
723
+ }
724
+ interface PlanChangePreviewIssue {
725
+ code: string;
726
+ message: string;
727
+ }
728
+ interface PlanChangePreviewDto {
729
+ changeType: PlanChangeType;
730
+ current: {
731
+ plan: PlanSnapshotDto;
732
+ billingCycle: string;
733
+ };
734
+ target: {
735
+ plan: PlanSnapshotDto;
736
+ billingCycle: string;
737
+ };
738
+ /** For upgrade/NOOP: immediately (null). Otherwise period end. */
739
+ effectiveAt: Date | null;
740
+ isImmediate: boolean;
741
+ /**
742
+ * Projected new trial end after the change (app trial logic, e.g.
743
+ * carry-over of the remaining time). `null` if no TrialProjectionPort is
744
+ * configured, the subscription is not in a trial, or nothing changes.
745
+ * The wizard uses this to show "regulär ab Ende der Testphase".
746
+ */
747
+ projectedTrialEndsAt: Date | null;
748
+ proration: ProrationDto | null;
749
+ /** Map quotaKey → LimitsCheckRow across all quota dimensions from the current limit, target plan and usage. */
750
+ limitsCheck: Record<string, LimitsCheckRow>;
751
+ featuresLost: string[];
752
+ featuresGained: string[];
753
+ /** Hard prevention reasons — e.g. usage > target limit. */
754
+ blockers: PlanChangePreviewIssue[];
755
+ /** Non-blocking hints — e.g. feature loss. */
756
+ warnings: PlanChangePreviewIssue[];
757
+ }
758
+ interface PlanChangeContext {
759
+ /** Current period start time from the subscription, if present. */
760
+ currentPeriodStart: Date | null;
761
+ /** Current period end from the subscription, if present. */
762
+ currentPeriodEnd: Date | null;
763
+ /** TRIAL end, if status === 'TRIAL'. */
764
+ trialEndsAt: Date | null;
765
+ /** Subscription status (TRIAL/ACTIVE/...). */
766
+ status: string;
767
+ /** Current cycle of the subscription (for cycle-change classification). */
768
+ currentBillingCycle: string;
769
+ /** Current plan of the subscription. */
770
+ currentPlan: string;
771
+ /** Subscription start, if present (for the periodEndAfter fallback). */
772
+ startedAt: Date | null;
773
+ }
774
+ declare class PlanChangePreviewService {
775
+ private readonly catalog;
776
+ private readonly entitlements;
777
+ private readonly subscriptions;
778
+ private readonly usageSnapshot;
779
+ private readonly blockedPlans;
780
+ private readonly trialProjection;
781
+ constructor(catalog: PlanCatalog, entitlements: EntitlementService, subscriptions: SubscriptionUsagePort, usageSnapshot: UsageSnapshotPort, blockedPlans?: SelfServiceBlockedPlans | null, trialProjection?: TrialProjectionPort | null);
782
+ preview(tenantId: string, targetPlan: string, targetCycle: string, now?: Date): Promise<PlanChangePreviewDto>;
783
+ /** Like `preview`, but only the blocker list — for a server-side
784
+ * pre-check before the `changePlan` mutation (defense-in-depth). */
785
+ assertChangeAllowed(tenantId: string, targetPlan: string, targetCycle: string, now?: Date): Promise<PlanChangePreviewIssue[]>;
786
+ private classify;
787
+ /** Catalog order = rank. Non-marketed plans go to the end. */
788
+ private planRank;
789
+ private resolveEffectiveAt;
790
+ private computeProration;
791
+ }
792
+
793
+ /**
794
+ * Optional hook token: the platform `changePlan` path + the
795
+ * `PendingPlanMaterializationService` call the freeze after the plan mutation
796
+ * (analogous to `TrialProjectionPort`). Without a port nothing is frozen — the
797
+ * entitlements then stay catalog-/version-pinned as before.
798
+ */
799
+ declare const CONTRACT_FREEZE_PORT_TOKEN: unique symbol;
800
+ /** Adapter token: consumer-specific bundle/version data access. */
801
+ declare const CONTRACT_FREEZE_SOURCE_PORT_TOKEN: unique symbol;
802
+ /** Config token: the consumer's `projectKey` for the contract. */
803
+ declare const CONTRACT_FREEZE_PROJECT_KEY_TOKEN: unique symbol;
804
+ interface ContractFreezePort {
805
+ /**
806
+ * Freezes the agreed service at `effectiveFrom` as the new active
807
+ * `SubscriptionContract` (supersedes the previous one). Non-fatal for the
808
+ * caller — the plan change is already persisted.
809
+ */
810
+ freezeOnPlanChange(tenantId: string, newPlan: string, billingCycle: BillingCycle, effectiveFrom: Date): Promise<void>;
811
+ }
812
+ /** Frozen bundle line items + their version ids (trace). */
813
+ interface ContractFreezeBundleSnapshot {
814
+ lineItems: NewContractLineItemData[];
815
+ bundleVersionIds: string[];
816
+ }
817
+ /**
818
+ * Consumer-specific data access for the freeze: live PlanVersion id
819
+ * (trace) + booked bundles as contract line items. The generic freeze
820
+ * logic (plan line item from the catalog, snapshot, contract assembly) lives in
821
+ * the platform `SubscriptionContractFreezeService`.
822
+ */
823
+ interface ContractFreezeSourcePort {
824
+ /** Live (published, non-superseded) PlanVersion id of the target plan, or null. */
825
+ findLivePlanVersionId(planId: string): Promise<string | null>;
826
+ /**
827
+ * The tenant's active (non-terminated) bundle bookings as line items.
828
+ * `vatRate` is passed through so the gross price is computed consistently
829
+ * with the catalog VAT. Apps without a bundle schema return empty lists.
830
+ */
831
+ loadBookedBundles(tenantId: string, cycle: 'monthly' | 'yearly', vatRate: number): Promise<ContractFreezeBundleSnapshot>;
832
+ }
833
+
834
+ declare class PendingPlanMaterializationService {
835
+ private readonly query;
836
+ private readonly subscriptionWrite;
837
+ private readonly entitlements;
838
+ private readonly contractFreeze;
839
+ private readonly logger;
840
+ constructor(query: PendingPlanQueryPort, subscriptionWrite: TenantSubscriptionWritePort, entitlements: EntitlementService, contractFreeze?: ContractFreezePort | null);
841
+ materializeDuePlanChanges(now?: Date): Promise<{
842
+ applied: number;
843
+ }>;
844
+ private tryFreeze;
845
+ }
846
+
847
+ declare class SubscriptionContractFreezeService implements ContractFreezePort {
848
+ private readonly catalog;
849
+ private readonly entitlements;
850
+ private readonly contracts;
851
+ private readonly projectKey;
852
+ private readonly source;
853
+ constructor(catalog: PlanCatalog, entitlements: EntitlementService, contracts: SubscriptionContractService, projectKey: string, source: ContractFreezeSourcePort);
854
+ freezeOnPlanChange(tenantId: string, newPlan: string, billingCycle: BillingCycle, effectiveFrom: Date): Promise<void>;
855
+ }
856
+
857
+ declare function computeCarriedTrialEndsAt(currentTrialDays: number, newTrialDays: number, currentTrialEndsAt: Date, now: Date): Date;
858
+
859
+ declare class PreviewPlanChangeDto {
860
+ plan: string;
861
+ billingCycle: string;
862
+ }
863
+ declare class ChangePlanDto extends PreviewPlanChangeDto {
864
+ /** Immediate change (true) vs. change at period end (false/undefined). */
865
+ effectiveImmediately?: boolean;
866
+ }
867
+ declare class CancelSubscriptionDto {
868
+ /** true = cancel immediately (status CANCELED). false/undefined = at period end. */
869
+ immediately?: boolean;
870
+ }
871
+
872
+ declare class CompleteOnboardingSubscriptionDto {
873
+ plan: string;
874
+ billingCycle: string;
875
+ promoCode?: string;
876
+ /**
877
+ * Optional — UUIDs of the BundleVersions that should be booked
878
+ * together with the plan (P11.7.3). Per bundle, the platform
879
+ * default minimum term (12 months) is set. Bundles are added
880
+ * best-effort **after** the plan change — an error on an individual
881
+ * bundle (e.g. incompatible with the chosen plan) lands as a
882
+ * warning in the response, without rolling back the plan change.
883
+ */
884
+ bundleVersionIds?: string[];
885
+ }
886
+
887
+ interface SubscriptionBundleConfig {
888
+ /** Default minimum term in months on `add`. Default = 12. */
889
+ defaultMinimumTermMonths?: number;
890
+ }
891
+ interface AddBundleToSubscriptionInput {
892
+ subscriptionId: string;
893
+ bundleVersionId: string;
894
+ /** PlanKey of the current subscription for the plan-compatibility check. */
895
+ currentPlanKey: string;
896
+ /** Default = now (service time). */
897
+ startedAt?: Date;
898
+ /**
899
+ * Override for the minimum term (months). Default = config or
900
+ * 12. `0` explicitly means "no minimum term"
901
+ * (`minimumTermEndsAt = null`).
902
+ */
903
+ minimumTermMonths?: number;
904
+ }
905
+ interface CancelBundleFromSubscriptionInput {
906
+ subscriptionBundleId: string;
907
+ /** Default = now. */
908
+ canceledAt?: Date;
909
+ /**
910
+ * Period end of the subscription from which the cancellation could take effect.
911
+ * Effective date = `max(currentPeriodEnd, minimumTermEndsAt)`.
912
+ * If not set, `canceledAt` is interpreted as the period end
913
+ * (= immediate effect, provided the minimum term has already expired).
914
+ */
915
+ currentPeriodEnd?: Date;
916
+ }
917
+ declare class SubscriptionBundlesService {
918
+ private readonly repo;
919
+ private readonly bundles;
920
+ private readonly blockedBundles;
921
+ private readonly defaultMinTermMonths;
922
+ constructor(repo: SubscriptionBundleRepository, bundles: BundleRepository, config?: SubscriptionBundleConfig, blockedBundles?: SelfServiceBlockedBundles | null);
923
+ /** All bundle bookings of a subscription (for the "My Bundles" page). */
924
+ listForSubscription(subscriptionId: string): Promise<SubscriptionBundleView[]>;
925
+ addBundleToSubscription(input: AddBundleToSubscriptionInput): Promise<SubscriptionBundleRecord>;
926
+ cancelBundleFromSubscription(input: CancelBundleFromSubscriptionInput): Promise<SubscriptionBundleRecord>;
927
+ /**
928
+ * "Undo cancellation" — only as long as the cancellation is not yet effective
929
+ * (the bundle runs until `canceledEffectiveAt`). After that, re-booking is the way.
930
+ */
931
+ reactivateBundle(subscriptionBundleId: string): Promise<SubscriptionBundleRecord>;
932
+ }
933
+ /**
934
+ * Effective date of a bundle cancellation:
935
+ * `max(currentPeriodEnd, minimumTermEndsAt)` — missing values fall back to
936
+ * `canceledAt` (= immediate effect). Shared between the
937
+ * cancellation mutation and the preview (#37).
938
+ */
939
+ declare function resolveBundleCancelEffectiveAt(input: {
940
+ canceledAt: Date;
941
+ currentPeriodEnd: Date | null;
942
+ minimumTermEndsAt: Date | null;
943
+ }): Date;
944
+ /**
945
+ * Adds `months` to `date` and keeps the UTC day. Edge case
946
+ * 31.01 + 1 month → 28/29.02 (JS Date does this automatically by
947
+ * setMonth normalizing the day).
948
+ */
949
+ declare function addMonths(date: Date, months: number): Date;
950
+
951
+ interface RequestLike {
952
+ user?: {
953
+ tenantId?: string;
954
+ sub?: string;
955
+ id?: string;
956
+ };
957
+ }
958
+ interface UsageResponse {
959
+ plan: string;
960
+ effectivePlan: string;
961
+ billingCycle: string;
962
+ status: string;
963
+ isPilot: boolean;
964
+ pilotEndsAt: Date | null;
965
+ trialEndsAt: Date | null;
966
+ startedAt: Date | null;
967
+ currentPeriodStart: Date | null;
968
+ currentPeriodEnd: Date | null;
969
+ pendingPlan: string | null;
970
+ pendingBillingCycle: string | null;
971
+ pendingEffectiveAt: Date | null;
972
+ planVersion: SubscriptionUsageRecord['planVersion'];
973
+ pendingPlanVersion: SubscriptionUsageRecord['pendingPlanVersion'];
974
+ pendingPlanVersionEffectiveAt: Date | null;
975
+ pendingPlanVersionAccepted: boolean;
976
+ pendingPlanVersionAcceptedAt: Date | null;
977
+ limits: ReturnType<typeof toEffectiveLimitsSnapshot>;
978
+ usage: Record<string, number>;
979
+ /**
980
+ * P11.4 (METAMODELL §17a): Frozen package snapshot from the
981
+ * `CheckoutOffer` that was activated during onboarding. Read-only
982
+ * for the tenant self-service UI. `null` for subscriptions without
983
+ * a CheckoutOffer origin.
984
+ */
985
+ packageSnapshot: unknown | null;
986
+ /** P11.4: Optional reference to the originating CheckoutOffer. */
987
+ checkoutOfferId: string | null;
988
+ }
989
+ declare class TenantBillingController {
990
+ private readonly entitlements;
991
+ private readonly planPreview;
992
+ private readonly subscriptionUsage;
993
+ private readonly usageSnapshot;
994
+ private readonly subscriptionWrite;
995
+ private readonly tenantIdResolver;
996
+ private readonly userIdResolver;
997
+ private readonly blockedPlans;
998
+ private readonly promoCodes;
999
+ private readonly auditService;
1000
+ private readonly userEmailResolver;
1001
+ private readonly auditContextResolver;
1002
+ private readonly subscriptionBundles;
1003
+ private readonly contractFreeze;
1004
+ private readonly trialProjection;
1005
+ constructor(entitlements: EntitlementService, planPreview: PlanChangePreviewService, subscriptionUsage: SubscriptionUsagePort, usageSnapshot: UsageSnapshotPort, subscriptionWrite: TenantSubscriptionWritePort, tenantIdResolver?: TenantIdResolver | null, userIdResolver?: UserIdResolver | null, blockedPlans?: SelfServiceBlockedPlans | null, promoCodes?: PromoCodesService | null, auditService?: AdminAuditService | null, userEmailResolver?: UserEmailResolver | null, auditContextResolver?: AuditContextResolver | null, subscriptionBundles?: SubscriptionBundlesService | null, contractFreeze?: ContractFreezePort | null, trialProjection?: TrialProjectionPort | null);
1006
+ private readonly logger;
1007
+ getEntitlement(req: RequestLike): Promise<EffectiveLimitsSnapshot>;
1008
+ getUsage(req: RequestLike): Promise<UsageResponse>;
1009
+ previewPlanChange(req: RequestLike, dto: PreviewPlanChangeDto): Promise<PlanChangePreviewDto>;
1010
+ changePlan(req: RequestLike, dto: ChangePlanDto): Promise<{
1011
+ plan: string;
1012
+ billingCycle: string;
1013
+ immediate: boolean;
1014
+ pendingPlan?: undefined;
1015
+ pendingBillingCycle?: undefined;
1016
+ pendingEffectiveAt?: undefined;
1017
+ } | {
1018
+ plan: string;
1019
+ billingCycle: string;
1020
+ pendingPlan: string;
1021
+ pendingBillingCycle: string;
1022
+ pendingEffectiveAt: Date;
1023
+ immediate: boolean;
1024
+ }>;
1025
+ completeOnboardingSubscription(req: RequestLike, dto: CompleteOnboardingSubscriptionDto): Promise<OnboardingSelectionResponse>;
1026
+ acceptPendingPlanVersion(req: RequestLike): Promise<{
1027
+ accepted: boolean;
1028
+ acceptedAt: Date | null;
1029
+ effectiveAt: Date | null;
1030
+ idempotent: boolean;
1031
+ }>;
1032
+ cancelSubscription(req: RequestLike, dto: CancelSubscriptionDto): Promise<{
1033
+ canceledAt: Date | null;
1034
+ status: string;
1035
+ immediate: boolean;
1036
+ }>;
1037
+ private requireTenantId;
1038
+ private requireUserId;
1039
+ private buildActor;
1040
+ private resolveUserEmail;
1041
+ private toResponseRedemption;
1042
+ private collectPromoSkipReasons;
1043
+ /**
1044
+ * #18: contract freeze after the plan change — non-fatal, only outside of
1045
+ * the trial (during a trial the trial entitlements apply, not the booked plan;
1046
+ * the freeze happens on the transition to ACTIVE, i.e. on materialization).
1047
+ * Without a configured `contractFreeze` hook the call is a no-op.
1048
+ */
1049
+ private tryFreezeOnPlanChange;
1050
+ /**
1051
+ * Audit-log helper — writes best-effort, does not block the response path.
1052
+ * If `AdminAuditService` is not injected (e.g. a minimal deploy without
1053
+ * AdminModule), the call is silently discarded.
1054
+ */
1055
+ private auditLog;
1056
+ }
1057
+
1058
+ interface TenantBillingModuleOptions {
1059
+ /**
1060
+ * App guards in the order in which they should be executed
1061
+ * (e.g. `[JwtAuthGuard, TenantGuard]`). The platform combines them via
1062
+ * `ComposedTenantAuthGuard`. At least one guard is mandatory —
1063
+ * missing configuration leads to 403 (safe default).
1064
+ *
1065
+ * Variant 1: array of guard instances (e.g. via factory provider).
1066
+ * Variant 2: Pick<FactoryProvider, 'useFactory' | 'inject'> — apps
1067
+ * pass their guard classes through via `inject` and the factory builds
1068
+ * the array.
1069
+ */
1070
+ authGuards: ProviderSpec<ReadonlyArray<CanActivate>>;
1071
+ /** Adapter to the subscription display form (`GET /billing/usage`). */
1072
+ subscriptionUsagePort: ProviderSpec<SubscriptionUsagePort>;
1073
+ /** Adapter to usage counters of all `quotaKeys`. */
1074
+ usageSnapshotPort: ProviderSpec<UsageSnapshotPort>;
1075
+ /** Adapter for plan/add-on mutations (phase C). */
1076
+ subscriptionWritePort: ProviderSpec<TenantSubscriptionWritePort>;
1077
+ /**
1078
+ * Optional adapter that provides the projected new trial end of a change
1079
+ * (app trial logic, e.g. carry-over). Without a port,
1080
+ * `PlanChangePreviewDto.projectedTrialEndsAt` stays `null`.
1081
+ */
1082
+ trialProjectionPort?: ProviderSpec<TrialProjectionPort>;
1083
+ /**
1084
+ * Optional adapter that provides due scheduled plan changes (#19). If it is
1085
+ * passed, the module registers the `PendingPlanMaterializationService`
1086
+ * (exported) — the consumer triggers it via its own cron. Without a
1087
+ * port, the materialization stays disabled (lazy resolution as before).
1088
+ */
1089
+ pendingPlanQueryPort?: ProviderSpec<PendingPlanQueryPort>;
1090
+ /**
1091
+ * Optional contract freeze hook (#18). If it is configured, the
1092
+ * platform `changePlan` path (non-TRIAL) AND the materialization freeze
1093
+ * the agreed service after the plan mutation as a `SubscriptionContract`
1094
+ * (analogous to `trialProjectionPort`). Consumer-specific are only `projectKey`
1095
+ * + bundle/version data access (`sourcePort`); the contract logic is
1096
+ * generic. `subscriptionContractRepository` is the same repo that also goes
1097
+ * to `EntitlementModule.forRoot` — the freeze needs it in its own scope.
1098
+ */
1099
+ contractFreeze?: {
1100
+ projectKey: string;
1101
+ sourcePort: ProviderSpec<ContractFreezeSourcePort>;
1102
+ subscriptionContractRepository: ProviderSpec<SubscriptionContractRepository>;
1103
+ };
1104
+ /**
1105
+ * Plans that are not accepted as target/source via self-service
1106
+ * (typically ENTERPRISE → special contract). `null`/undefined = no blocks.
1107
+ */
1108
+ selfServiceBlockedPlans?: SelfServiceBlockedPlans;
1109
+ /** Optional tenant ID resolver. Default: `req.user.tenantId`. */
1110
+ tenantIdResolver?: TenantIdResolver;
1111
+ /** Optional user ID resolver. Default: `req.user.sub ?? req.user.id`. */
1112
+ userIdResolver?: UserIdResolver;
1113
+ /**
1114
+ * Optional email resolver for the audit log path. Default: `req.user.email`.
1115
+ * If the consumer JWT does not include the email, the resolver can
1116
+ * return `null` — the audit log then uses `'unknown'`.
1117
+ */
1118
+ userEmailResolver?: UserEmailResolver;
1119
+ /**
1120
+ * Optional audit context resolver (session ID / trace ID). Default:
1121
+ * `req.headers['x-session-id']` or `'tenant-self-service'`.
1122
+ */
1123
+ auditContextResolver?: AuditContextResolver;
1124
+ /**
1125
+ * Modules whose providers must be visible within this module.
1126
+ * Typical use case: the app's own `AuthModule`, so that the `JwtAuthGuard`
1127
+ * is injectable in the `authGuards` factory. Without this entry NestJS throws
1128
+ * `UnknownDependenciesException` for JwtAuthGuard.
1129
+ */
1130
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
1131
+ /**
1132
+ * Additional providers that are registered in the DynamicModule itself —
1133
+ * typically: the adapter classes referenced in `inject:[Adapter]` lists
1134
+ * (e.g. `PrismaSubscriptionUsagePort`). NestJS 11.1.19
1135
+ * resolves factory inject tokens only in the DynamicModule's own scope.
1136
+ */
1137
+ extraProviders?: Provider[];
1138
+ /** Register the module globally — default `false`. */
1139
+ global?: boolean;
1140
+ }
1141
+ declare class TenantBillingModule {
1142
+ static forRoot(options: TenantBillingModuleOptions): DynamicModule;
1143
+ }
1144
+
1145
+ /**
1146
+ * Builds the `ConfiguratorCatalog` (for onboarding step 3) from the
1147
+ * live `plan_versions` (SuperAdmin defines plans + prices) plus the
1148
+ * app-specific plan-marketing source (`ConfiguratorMarketingProvider`).
1149
+ *
1150
+ * Deliberately no DI on the marketing provider via token — the builder is
1151
+ * a pure-function-like class that consumers call directly (typically in an
1152
+ * app adapter that implements `RegistrationConfiguratorLookup.getCatalog()`).
1153
+ */
1154
+ declare class ConfiguratorCatalogBuilder {
1155
+ build(input: {
1156
+ sources: ConfiguratorSourcesLookup;
1157
+ marketing: ConfiguratorMarketingProvider;
1158
+ }): Promise<ConfiguratorCatalog>;
1159
+ }
1160
+
1161
+ declare const SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN: unique symbol;
1162
+ /** Optional config token; default = 12 months minimum term. */
1163
+ declare const SUBSCRIPTION_BUNDLE_CONFIG_TOKEN: unique symbol;
1164
+
1165
+ interface SubscriptionBundlePreviewIssue {
1166
+ code: string;
1167
+ message: string;
1168
+ }
1169
+ /** Subscription context — the controller reads it from the SubscriptionUsagePort. */
1170
+ interface SubscriptionBundlePreviewContext {
1171
+ subscriptionId: string;
1172
+ /** PlanKey of the current subscription (plan compatibility + redundancy source). */
1173
+ currentPlanKey: string;
1174
+ /** 'MONTHLY' | 'YEARLY' (port convention). */
1175
+ billingCycle: string;
1176
+ /** Subscription status (TRIAL/ACTIVE/...). No proration during TRIAL. */
1177
+ status: string;
1178
+ startedAt: Date | null;
1179
+ currentPeriodStart: Date | null;
1180
+ currentPeriodEnd: Date | null;
1181
+ }
1182
+ interface BundlePreviewSnapshot {
1183
+ bundleKey: string;
1184
+ label: string;
1185
+ bundleVersionId: string;
1186
+ features: string[];
1187
+ quotas: Record<string, number>;
1188
+ }
1189
+ /** AK-13: feature is already paid for elsewhere — double-payment hint. */
1190
+ interface RedundantFeatureHint {
1191
+ featureKey: string;
1192
+ coveredBy: 'PLAN' | 'BUNDLE';
1193
+ /** planKey or bundleKey of the covering source. */
1194
+ coveredByKey: string;
1195
+ }
1196
+ interface SubscriptionBundleAddPreviewDto {
1197
+ action: 'add';
1198
+ bundle: BundlePreviewSnapshot;
1199
+ billingCycle: string;
1200
+ /**
1201
+ * Prorated amount until period end. `null` during TRIAL (no paid
1202
+ * period yet) or without a list price for the cycle.
1203
+ */
1204
+ proration: ProrationDto | null;
1205
+ /** List price per follow-up period in the current cycle; null = no price maintained. */
1206
+ nextPeriodPriceNet: number | null;
1207
+ minimumTermMonths: number;
1208
+ /** Projected minimum-term end from `now`; null = no minimum term. */
1209
+ minimumTermEndsAt: Date | null;
1210
+ redundantFeatures: RedundantFeatureHint[];
1211
+ /**
1212
+ * requires-features (#35) that neither the plan nor active bundles nor the
1213
+ * bundle itself cover. Non-empty ⇒ blocker
1214
+ * BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED.
1215
+ */
1216
+ missingRequires: string[];
1217
+ blockers: SubscriptionBundlePreviewIssue[];
1218
+ warnings: SubscriptionBundlePreviewIssue[];
1219
+ }
1220
+ interface SubscriptionBundleCancelPreviewDto {
1221
+ action: 'cancel';
1222
+ subscriptionBundleId: string;
1223
+ bundle: BundlePreviewSnapshot;
1224
+ billingCycle: string;
1225
+ /** Effective date = max(currentPeriodEnd, minimumTermEndsAt). */
1226
+ effectiveAt: Date;
1227
+ /** Savings per period from the effective date; null = no price maintained. */
1228
+ nextPeriodSavingsNet: number | null;
1229
+ blockers: SubscriptionBundlePreviewIssue[];
1230
+ warnings: SubscriptionBundlePreviewIssue[];
1231
+ }
1232
+ declare class SubscriptionBundlePreviewService {
1233
+ private readonly subscriptionBundles;
1234
+ private readonly bundles;
1235
+ private readonly plans;
1236
+ private readonly catalogEntries;
1237
+ private readonly blockedBundles;
1238
+ private readonly defaultMinTermMonths;
1239
+ constructor(subscriptionBundles: SubscriptionBundleRepository, bundles: BundleRepository, plans?: PlanRepository | null, catalogEntries?: CatalogEntryRepository | null, blockedBundles?: SelfServiceBlockedBundles | null, config?: SubscriptionBundleConfig);
1240
+ previewAdd(ctx: SubscriptionBundlePreviewContext, input: {
1241
+ bundleVersionId: string;
1242
+ minimumTermMonths?: number;
1243
+ }, now?: Date): Promise<SubscriptionBundleAddPreviewDto>;
1244
+ previewCancel(ctx: SubscriptionBundlePreviewContext, input: {
1245
+ subscriptionBundleId: string;
1246
+ }, now?: Date): Promise<SubscriptionBundleCancelPreviewDto>;
1247
+ /** Bookability checks — same codes as `addBundleToSubscription` (422 path). */
1248
+ private collectBookabilityBlockers;
1249
+ /** Versions of the active bundle bookings (for redundancy + requires coverage). */
1250
+ private loadActiveBundleVersions;
1251
+ /** Features of the currently live PlanVersion state; empty without PlanRepository. */
1252
+ private resolvePlanFeatures;
1253
+ private collectRedundantFeatures;
1254
+ /**
1255
+ * requires of the new bundle that neither the bundle itself nor plan ∪
1256
+ * active bundles cover (#35). Empty without CatalogEntryRepository.
1257
+ */
1258
+ private collectMissingRequires;
1259
+ /** `projectKey` lives on the bundle stem, not on the version. */
1260
+ private resolveProjectKey;
1261
+ }
1262
+ /**
1263
+ * List price (net) for the billing cycle including plan-specific
1264
+ * pricing override (BundlePricingOverride with `planId`, without
1265
+ * `businessTypeKey`). null = no price maintained for the cycle.
1266
+ */
1267
+ declare function resolveBundlePriceNet(bundleVersion: BundleVersionRow, planKey: string, billingCycle: string): number | null;
1268
+
1269
+ interface SubscriptionBundleControllerOptions {
1270
+ extraGuards?: Array<Type<CanActivate>>;
1271
+ /**
1272
+ * Auth guard list analogous to `TenantBillingModule.forRoot.authGuards`.
1273
+ * Without this list, `ComposedTenantAuthGuard` blocks fail-closed.
1274
+ */
1275
+ authGuards?: ProviderSpec<ReadonlyArray<CanActivate>>;
1276
+ /**
1277
+ * Usage port for subscription lookup and current plan compatibility.
1278
+ * If not set, an imported module must export the token.
1279
+ */
1280
+ subscriptionUsagePort?: ProviderSpec<SubscriptionUsagePort>;
1281
+ /** Optional tenant-ID resolver. Default: `req.user.tenantId`. */
1282
+ tenantIdResolver?: TenantIdResolver;
1283
+ }
1284
+ interface SubscriptionBundleModuleOptions {
1285
+ subscriptionBundleRepository: ProviderSpec<SubscriptionBundleRepository>;
1286
+ /**
1287
+ * Optional — if omitted, `BUNDLE_REPOSITORY_TOKEN` is expected via
1288
+ * default inject from the DI scope (typically via an imported
1289
+ * `CatalogModule`).
1290
+ */
1291
+ bundleRepository?: ProviderSpec<BundleRepository>;
1292
+ /** Default minimum term (months). Default = 12. */
1293
+ defaultMinimumTermMonths?: number;
1294
+ /**
1295
+ * Self-service policy (#37): bundles that are only bookable via sales.
1296
+ * Applies in `addBundleToSubscription` (422 BUNDLE_NOT_SELF_SERVICE)
1297
+ * and in the preview (blocker).
1298
+ */
1299
+ selfServiceBlockedBundles?: SelfServiceBlockedBundles;
1300
+ /**
1301
+ * If set: the tenant self-service controller is mounted at
1302
+ * `/billing/subscription-bundles` (GET/POST/DELETE). The
1303
+ * `extraGuards` are applied in addition to the platform default
1304
+ * `ComposedTenantAuthGuard` (role/MFA guards).
1305
+ *
1306
+ * Prerequisite: the consumer has already registered `TenantBillingModule`
1307
+ * — the controller needs `SUBSCRIPTION_USAGE_PORT_TOKEN`
1308
+ * + `TENANT_ID_RESOLVER_TOKEN` in the same DI scope.
1309
+ */
1310
+ controller?: SubscriptionBundleControllerOptions;
1311
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
1312
+ extraProviders?: Provider[];
1313
+ global?: boolean;
1314
+ }
1315
+ declare class SubscriptionBundleModule {
1316
+ static forRoot(options: SubscriptionBundleModuleOptions): DynamicModule;
1317
+ }
1318
+
1319
+ /**
1320
+ * Factory analogous to `buildBundlesController` etc. — the consumer can pass
1321
+ * additional guards (e.g. RoleGuard) in addition to the platform default
1322
+ * `ComposedTenantAuthGuard`.
1323
+ */
1324
+ declare function buildTenantSubscriptionBundlesController(extraGuards?: Array<Type<CanActivate>>): Type;
1325
+
1326
+ export { AUDIT_CONTEXT_RESOLVER_TOKEN, type AddBundleToSubscriptionInput, type AjvErrorLike, type AuditContextResolver, type AuthGuardList, type BundlePreviewSnapshot, CONTRACT_FREEZE_PORT_TOKEN, CONTRACT_FREEZE_PROJECT_KEY_TOKEN, CONTRACT_FREEZE_SOURCE_PORT_TOKEN, type CancelBundleFromSubscriptionInput, CancelSubscriptionDto, CatalogBundleUpsellResolver, ChangePlanDto, CompleteOnboardingSubscriptionDto, ComposedTenantAuthGuard, ConfiguratorCatalogBuilder, type ContractFreezeBundleSnapshot, type ContractFreezePort, type ContractFreezeSourcePort, type DuePendingPlanChange, FEATURE_GUARD_CONFIG_TOKEN, FEATURE_UI_REGISTRY_TOKEN, FeatureGuard, type FeatureGuardConfig, LimitExceededFilter, type LimitsCheckRow, type LoadPlanCatalogOptions, type MarketingFields, type NextPeriodWindow, PENDING_PLAN_QUERY_PORT_TOKEN, PLAN_CATALOG_IMPORT_SINK_TOKEN, PLAN_CATALOG_READ_SINK_TOKEN, PLAN_CATALOG_TOKEN, PUBLIC_CATALOG_BUNDLE_REPOSITORY_TOKEN, PUBLIC_CATALOG_BUSINESS_TYPE_REPOSITORY_TOKEN, PUBLIC_CATALOG_MARKETING_REPOSITORY_TOKEN, PUBLIC_CATALOG_PROJECT_KEY_TOKEN, PendingPlanMaterializationService, type PendingPlanQueryPort, type PeriodRollInput, type PlanCatalogBuildSettings, PlanCatalogImportDto, type PlanCatalogImporterControllerConfig, PlanCatalogImporterModule, type PlanCatalogImporterModuleOptions, PlanCatalogImporterService, PlanCatalogModule, type PlanCatalogModuleOptions, PlanCatalogValidationError, type PlanChangeContext, type PlanChangePreviewDto, type PlanChangePreviewIssue, PlanChangePreviewService, type PlanChangeType, type PlanResponseEntry, type PlanSnapshotDto, PreviewPlanChangeDto, type ProrationDto, type ProrationInput, type PublicBundleEntry, type PublicBusinessTypeEntry, PublicCatalogController, PublicCatalogModule, type PublicCatalogModuleOptions, PublishValidationError, type PublishablePlanVersion, REQUIRE_FEATURE_KEY, type RedundantFeatureHint, type RenewalDecision, type RenewalSubInput, RequireFeature, SELF_SERVICE_BLOCKED_BUNDLES_TOKEN, SELF_SERVICE_BLOCKED_PLANS_TOKEN, SUBSCRIPTION_BUNDLE_CONFIG_TOKEN, SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN, SUBSCRIPTION_USAGE_PORT_TOKEN, SUBSCRIPTION_WRITE_PORT_TOKEN, type SelfServiceBlockedBundles, type SelfServiceBlockedPlans, type SubscriptionBundleAddPreviewDto, type SubscriptionBundleCancelPreviewDto, type SubscriptionBundleConfig, type SubscriptionBundleControllerOptions, SubscriptionBundleModule, type SubscriptionBundleModuleOptions, type SubscriptionBundlePreviewContext, type SubscriptionBundlePreviewIssue, SubscriptionBundlePreviewService, SubscriptionBundlesService, SubscriptionContractFreezeService, TENANT_AUTH_GUARDS_TOKEN, TENANT_ID_RESOLVER_TOKEN, TRIAL_PROJECTION_PORT_TOKEN, TenantAdminGuard, TenantBillingController, TenantBillingModule, type TenantBillingModuleOptions, type TenantIdResolver, type TrialProjectionInput, type TrialProjectionPort, UPSELL_OFFER_CURRENCY_TOKEN, UPSELL_OFFER_RESOLVER_TOKEN, USAGE_SNAPSHOT_PORT_TOKEN, USER_EMAIL_RESOLVER_TOKEN, USER_ID_RESOLVER_TOKEN, type UsageResponse, type UserEmailResolver, type UserIdResolver, addMonths, assertBaseVersionFresh, assertChangeNote, assertDraftPublishable, assertOptimisticLockHeld, buildPlanCatalogFromSnapshot, buildPlanCatalogImporterController, buildTenantSubscriptionBundlesController, clearPendingPlanVersionFields, computeCarriedTrialEndsAt, computeNextPeriod, computeProration, decideRenewal, findPlan, getActiveFeatureKeys, getMarketedPlans, getPlanOrThrow, getPlanPriceGross, getPlanPriceNet, getPlanQuota, initialPeriodWindow, isFeatureInPlan, isFeaturePlannedOnly, loadPlanCatalogFromFile, loadPlanCatalogFromString, periodEndAfter, periodEndWithMinLead, resolveBundleCancelEffectiveAt, resolveBundlePriceNet };