@saasicat/nest 0.8.0 → 0.10.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 (95) hide show
  1. package/dist/_entries.cjs +17678 -0
  2. package/dist/admin/index.cjs +4 -1274
  3. package/dist/admin/index.d.cts +10 -171
  4. package/dist/admin/index.d.ts +10 -171
  5. package/dist/admin/index.js +13 -4
  6. package/dist/admin-resources.module-BZN_yBFj.d.cts +40 -0
  7. package/dist/admin-resources.module-Cms8MY6h.d.ts +40 -0
  8. package/dist/admin-stats.module-15ac5KY3.d.ts +201 -0
  9. package/dist/admin-stats.module-v4SVvWzj.d.cts +201 -0
  10. package/dist/billing/index.cjs +4 -5128
  11. package/dist/billing/index.d.cts +8 -719
  12. package/dist/billing/index.d.ts +8 -719
  13. package/dist/billing/index.js +7 -10
  14. package/dist/catalog/index.cjs +4 -5624
  15. package/dist/catalog/index.d.cts +7 -427
  16. package/dist/catalog/index.d.ts +7 -427
  17. package/dist/catalog/index.js +14 -12
  18. package/dist/catalog.module-BKObceo0.d.ts +103 -0
  19. package/dist/catalog.module-Dw2Hrbyu.d.cts +103 -0
  20. package/dist/checkout-offer/index.cjs +4 -807
  21. package/dist/checkout-offer/index.d.cts +5 -84
  22. package/dist/checkout-offer/index.d.ts +5 -84
  23. package/dist/checkout-offer/index.js +3 -2
  24. package/dist/checkout-offer.module-Y0hDOVrq.d.ts +85 -0
  25. package/dist/checkout-offer.module-ZYF5IsDY.d.cts +85 -0
  26. package/dist/{chunk-5DDCSGZC.js → chunk-33TP26MR.js} +144 -356
  27. package/dist/{chunk-CK54YKVK.js → chunk-75JABGZK.js} +16 -16
  28. package/dist/{chunk-HRU3CP57.js → chunk-BOADBIGB.js} +1 -1
  29. package/dist/{chunk-ZLUGQE5D.js → chunk-E7AJ42AB.js} +8 -8
  30. package/dist/{chunk-6LP7CDS2.js → chunk-F3VV6EXG.js} +18 -13
  31. package/dist/{chunk-PLDHI6NA.js → chunk-FVT7KTUI.js} +258 -3
  32. package/dist/{chunk-RZU4HIAN.js → chunk-GBDUH6DO.js} +26 -23
  33. package/dist/{chunk-K3OPNVFR.js → chunk-HBFU5AN3.js} +5 -9
  34. package/dist/chunk-HWGIMNWR.js +0 -0
  35. package/dist/chunk-IRWZC3GC.js +755 -0
  36. package/dist/chunk-L4KTQ6CL.js +20 -0
  37. package/dist/{chunk-T5SXXAKG.js → chunk-LZHLGUQI.js} +1 -1
  38. package/dist/{chunk-YHHE4KDA.js → chunk-PUXLPUVH.js} +351 -21
  39. package/dist/chunk-TXDH4FUA.js +211 -0
  40. package/dist/chunk-UF53DWWR.js +0 -0
  41. package/dist/{chunk-AJ3EPELS.js → chunk-V34AA7C7.js} +1 -1
  42. package/dist/chunk-WJ6BMDL3.js +313 -0
  43. package/dist/{chunk-M6CZQYPY.js → chunk-Z3FWN3HJ.js} +2 -2
  44. package/dist/discovery/index.cjs +4 -588
  45. package/dist/discovery/index.js +3 -3
  46. package/dist/{enforce-quota.interceptor-cv7r80VM.d.cts → enforce-quota.interceptor-BhkX5nq2.d.cts} +127 -12
  47. package/dist/{enforce-quota.interceptor-Dj1Kusr5.d.ts → enforce-quota.interceptor-CcqZc0Yu.d.ts} +127 -12
  48. package/dist/entitlement/index.cjs +4 -780
  49. package/dist/entitlement/index.d.cts +3 -1
  50. package/dist/entitlement/index.d.ts +3 -1
  51. package/dist/entitlement/index.js +2 -3
  52. package/dist/index.cjs +4 -14975
  53. package/dist/index.d.cts +22 -54
  54. package/dist/index.d.ts +22 -54
  55. package/dist/index.js +88 -369
  56. package/dist/module-CSN8j8Tp.d.cts +39 -0
  57. package/dist/module-DgYJur6f.d.ts +39 -0
  58. package/dist/plan-versions.service-BB36Q1bg.d.ts +329 -0
  59. package/dist/plan-versions.service-CUR2Whyr.d.cts +329 -0
  60. package/dist/platform/index.cjs +4 -2779
  61. package/dist/platform/index.d.cts +50 -8
  62. package/dist/platform/index.d.ts +50 -8
  63. package/dist/platform/index.js +116 -12
  64. package/dist/promo/index.cjs +4 -995
  65. package/dist/promo/index.d.cts +48 -46
  66. package/dist/promo/index.d.ts +48 -46
  67. package/dist/promo/index.js +10 -2
  68. package/dist/rate-limit.guard-dntqAlfZ.d.cts +19 -0
  69. package/dist/rate-limit.guard-dntqAlfZ.d.ts +19 -0
  70. package/dist/registration/index.cjs +4 -1655
  71. package/dist/registration/index.js +1 -1
  72. package/dist/setup.service-BPwJ7My_.d.cts +45 -0
  73. package/dist/setup.service-D2mrdDDB.d.ts +45 -0
  74. package/dist/subscription-bundle-preview.service-BkJXHAeP.d.cts +438 -0
  75. package/dist/subscription-bundle-preview.service-DBbrE_cw.d.ts +438 -0
  76. package/dist/subscription-bundles.module-BQmoozm5.d.ts +291 -0
  77. package/dist/subscription-bundles.module-DCpV-Pb5.d.cts +291 -0
  78. package/dist/subscription-contract/index.cjs +4 -471
  79. package/dist/subscription-contract/index.d.cts +5 -13
  80. package/dist/subscription-contract/index.d.ts +5 -13
  81. package/dist/subscription-contract.module-CG9fPKAr.d.cts +14 -0
  82. package/dist/subscription-contract.module-CVebZzHR.d.ts +14 -0
  83. package/dist/testing/index.cjs +4 -3756
  84. package/dist/testing/index.d.cts +9 -4
  85. package/dist/testing/index.d.ts +9 -4
  86. package/dist/testing/index.js +13 -7
  87. package/package.json +5 -5
  88. package/dist/admin-manifest.config-DyrQNT7M.d.cts +0 -10
  89. package/dist/admin-manifest.config-DyrQNT7M.d.ts +0 -10
  90. package/dist/admin-manifest.service-Cp6uwvmt.d.cts +0 -33
  91. package/dist/admin-manifest.service-UGgzR1x-.d.ts +0 -33
  92. package/dist/chunk-52UTQRD3.js +0 -326
  93. package/dist/chunk-N3WANKSA.js +0 -20
  94. package/dist/chunk-Q53N43LQ.js +0 -13
  95. /package/dist/{chunk-2FR6ZL7R.js → chunk-DBZGV3HC.js} +0 -0
@@ -36,7 +36,7 @@ import {
36
36
  resolveModel,
37
37
  slugify,
38
38
  verifyOtpCode
39
- } from "../chunk-CK54YKVK.js";
39
+ } from "../chunk-75JABGZK.js";
40
40
  import "../chunk-SABTXESR.js";
41
41
  import "../chunk-SHUYVCID.js";
42
42
  export {
@@ -0,0 +1,45 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { SuperAdminProvisioningPort, SetupStatusResponse, SetupRequest, SetupResult, SetupConfirmMfaRequest, SetupConfirmMfaResponse } from '@saasicat/types';
3
+ import { P as ProviderSpec } from './di-CcNeq9v-.cjs';
4
+ import { j as MfaService } from './admin-stats.module-v4SVvWzj.cjs';
5
+
6
+ interface SetupModuleOptions {
7
+ /**
8
+ * App adapter for the existence check + creation of the first SUPER_ADMIN. A
9
+ * full `UserManagementPort` adapter (CLI) satisfies this contract as well, so
10
+ * it can be reused.
11
+ */
12
+ provisioningPort: ProviderSpec<SuperAdminProvisioningPort>;
13
+ /** Env-var name of the setup token. Default `SETUP_TOKEN`. */
14
+ setupTokenEnvVar?: string;
15
+ /** Authenticator issuer for MFA enrollment. Default `SuperAdmin`. */
16
+ mfaIssuer?: string;
17
+ /** Register the module globally — default `false`. */
18
+ global?: boolean;
19
+ }
20
+ declare class SetupModule {
21
+ static forRoot(options: SetupModuleOptions): DynamicModule;
22
+ }
23
+
24
+ declare const SETUP_PROVISIONING_PORT_TOKEN: unique symbol;
25
+ declare const SETUP_CONFIG_TOKEN: unique symbol;
26
+ interface SetupConfig {
27
+ /** Env-var name of the setup token. Without the var set, setup is disabled. */
28
+ setupTokenEnvVar: string;
29
+ /** Authenticator issuer for the MFA enrollment in the wizard. */
30
+ mfaIssuer: string;
31
+ }
32
+
33
+ declare class SetupService {
34
+ private readonly users;
35
+ private readonly config;
36
+ private readonly mfa;
37
+ constructor(users: SuperAdminProvisioningPort, config: SetupConfig, mfa: MfaService);
38
+ status(): Promise<SetupStatusResponse>;
39
+ setup(req: SetupRequest): Promise<SetupResult>;
40
+ confirmMfa(req: SetupConfirmMfaRequest): Promise<SetupConfirmMfaResponse>;
41
+ private assertToken;
42
+ private assertNoSuperAdmin;
43
+ }
44
+
45
+ export { SetupService as S, SETUP_CONFIG_TOKEN as a, SETUP_PROVISIONING_PORT_TOKEN as b, type SetupConfig as c, SetupModule as d, type SetupModuleOptions as e };
@@ -0,0 +1,45 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { SuperAdminProvisioningPort, SetupStatusResponse, SetupRequest, SetupResult, SetupConfirmMfaRequest, SetupConfirmMfaResponse } from '@saasicat/types';
3
+ import { P as ProviderSpec } from './di-CcNeq9v-.js';
4
+ import { j as MfaService } from './admin-stats.module-15ac5KY3.js';
5
+
6
+ interface SetupModuleOptions {
7
+ /**
8
+ * App adapter for the existence check + creation of the first SUPER_ADMIN. A
9
+ * full `UserManagementPort` adapter (CLI) satisfies this contract as well, so
10
+ * it can be reused.
11
+ */
12
+ provisioningPort: ProviderSpec<SuperAdminProvisioningPort>;
13
+ /** Env-var name of the setup token. Default `SETUP_TOKEN`. */
14
+ setupTokenEnvVar?: string;
15
+ /** Authenticator issuer for MFA enrollment. Default `SuperAdmin`. */
16
+ mfaIssuer?: string;
17
+ /** Register the module globally — default `false`. */
18
+ global?: boolean;
19
+ }
20
+ declare class SetupModule {
21
+ static forRoot(options: SetupModuleOptions): DynamicModule;
22
+ }
23
+
24
+ declare const SETUP_PROVISIONING_PORT_TOKEN: unique symbol;
25
+ declare const SETUP_CONFIG_TOKEN: unique symbol;
26
+ interface SetupConfig {
27
+ /** Env-var name of the setup token. Without the var set, setup is disabled. */
28
+ setupTokenEnvVar: string;
29
+ /** Authenticator issuer for the MFA enrollment in the wizard. */
30
+ mfaIssuer: string;
31
+ }
32
+
33
+ declare class SetupService {
34
+ private readonly users;
35
+ private readonly config;
36
+ private readonly mfa;
37
+ constructor(users: SuperAdminProvisioningPort, config: SetupConfig, mfa: MfaService);
38
+ status(): Promise<SetupStatusResponse>;
39
+ setup(req: SetupRequest): Promise<SetupResult>;
40
+ confirmMfa(req: SetupConfirmMfaRequest): Promise<SetupConfirmMfaResponse>;
41
+ private assertToken;
42
+ private assertNoSuperAdmin;
43
+ }
44
+
45
+ export { SetupService as S, SETUP_CONFIG_TOKEN as a, SETUP_PROVISIONING_PORT_TOKEN as b, type SetupConfig as c, SetupModule as d, type SetupModuleOptions as e };
@@ -0,0 +1,438 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { b as AuthGuardList, m as SelfServiceBlockedPlans, w as TrialProjectionPort, h as PendingPlanQueryPort, f as ContractFreezePort, l as SelfServiceBlockedBundles, u as TenantIdResolver, B as UserIdResolver, z as UserEmailResolver, a as AuditContextResolver } from './subscription-bundles.module-DCpV-Pb5.cjs';
3
+ import { PlanCatalog, SubscriptionUsagePort, UsageSnapshotPort, TenantSubscriptionWritePort, SubscriptionBundleRepository, BundleRepository, SubscriptionBundleView, SubscriptionBundleRecord, SubscriptionUsageRecord, OnboardingSelectionResponse, PlanRepository, CatalogEntryRepository, BundleVersionRow } from '@saasicat/types';
4
+ import { c as EntitlementService, a as EffectiveLimitsSnapshot, t as toEffectiveLimitsSnapshot } from './aggregation-CfI0bOsQ.cjs';
5
+ import { f as PromoCodesService } from './service-DX8KbGXl.cjs';
6
+ import { A as AdminAuditService } from './admin-audit.service-9IqXMlZm.cjs';
7
+
8
+ declare class ComposedTenantAuthGuard implements CanActivate {
9
+ private readonly guards;
10
+ constructor(guards?: AuthGuardList | null);
11
+ canActivate(context: ExecutionContext): Promise<boolean>;
12
+ }
13
+
14
+ declare class TenantAdminGuard implements CanActivate {
15
+ canActivate(context: ExecutionContext): boolean;
16
+ }
17
+
18
+ interface ProrationDto {
19
+ daysRemainingInPeriod: number;
20
+ daysInPeriod: number;
21
+ periodStart: Date;
22
+ periodEnd: Date;
23
+ currentPriceNet: number;
24
+ targetPriceNet: number;
25
+ /** Prorated surcharge/credit until end of period. Negative = credit. */
26
+ prorataDeltaNet: number;
27
+ }
28
+ interface ProrationInput {
29
+ periodStart: Date;
30
+ periodEnd: Date;
31
+ now: Date;
32
+ /** Previous period price (bundle add: 0 — something is only added). */
33
+ currentPriceNet: number;
34
+ targetPriceNet: number;
35
+ }
36
+ declare function computeProration(input: ProrationInput): ProrationDto;
37
+
38
+ type PlanChangeType = 'UPGRADE' | 'DOWNGRADE' | 'CYCLE_CHANGE' | 'NOOP';
39
+ interface PlanSnapshotDto {
40
+ id: string;
41
+ name: string;
42
+ monthlyNet: number | null;
43
+ yearlyNet: number | null;
44
+ quotas: Record<string, number>;
45
+ features: string[];
46
+ }
47
+ interface LimitsCheckRow {
48
+ used: number;
49
+ currentMax: number;
50
+ targetMax: number;
51
+ exceeded: boolean;
52
+ }
53
+ interface PlanChangePreviewIssue {
54
+ code: string;
55
+ message: string;
56
+ }
57
+ interface PlanChangePreviewDto {
58
+ changeType: PlanChangeType;
59
+ current: {
60
+ plan: PlanSnapshotDto;
61
+ billingCycle: string;
62
+ };
63
+ target: {
64
+ plan: PlanSnapshotDto;
65
+ billingCycle: string;
66
+ };
67
+ /** For upgrade/NOOP: immediately (null). Otherwise period end. */
68
+ effectiveAt: Date | null;
69
+ isImmediate: boolean;
70
+ /**
71
+ * Projected new trial end after the change (app trial logic, e.g.
72
+ * carry-over of the remaining time). `null` if no TrialProjectionPort is
73
+ * configured, the subscription is not in a trial, or nothing changes.
74
+ * The wizard uses this to show "regulär ab Ende der Testphase".
75
+ */
76
+ projectedTrialEndsAt: Date | null;
77
+ proration: ProrationDto | null;
78
+ /** Map quotaKey → LimitsCheckRow across all quota dimensions from the current limit, target plan and usage. */
79
+ limitsCheck: Record<string, LimitsCheckRow>;
80
+ featuresLost: string[];
81
+ featuresGained: string[];
82
+ /** Hard prevention reasons — e.g. usage > target limit. */
83
+ blockers: PlanChangePreviewIssue[];
84
+ /** Non-blocking hints — e.g. feature loss. */
85
+ warnings: PlanChangePreviewIssue[];
86
+ }
87
+ interface PlanChangeContext {
88
+ /** Current period start time from the subscription, if present. */
89
+ currentPeriodStart: Date | null;
90
+ /** Current period end from the subscription, if present. */
91
+ currentPeriodEnd: Date | null;
92
+ /** TRIAL end, if status === 'TRIAL'. */
93
+ trialEndsAt: Date | null;
94
+ /** Subscription status (TRIAL/ACTIVE/...). */
95
+ status: string;
96
+ /** Current cycle of the subscription (for cycle-change classification). */
97
+ currentBillingCycle: string;
98
+ /** Current plan of the subscription. */
99
+ currentPlan: string;
100
+ /** Subscription start, if present (for the periodEndAfter fallback). */
101
+ startedAt: Date | null;
102
+ }
103
+ declare class PlanChangePreviewService {
104
+ private readonly catalog;
105
+ private readonly entitlements;
106
+ private readonly subscriptions;
107
+ private readonly usageSnapshot;
108
+ private readonly blockedPlans;
109
+ private readonly trialProjection;
110
+ constructor(catalog: PlanCatalog, entitlements: EntitlementService, subscriptions: SubscriptionUsagePort, usageSnapshot: UsageSnapshotPort, blockedPlans?: SelfServiceBlockedPlans | null, trialProjection?: TrialProjectionPort | null);
111
+ preview(tenantId: string, targetPlan: string, targetCycle: string, now?: Date): Promise<PlanChangePreviewDto>;
112
+ /** Like `preview`, but only the blocker list — for a server-side
113
+ * pre-check before the `changePlan` mutation (defense-in-depth). */
114
+ assertChangeAllowed(tenantId: string, targetPlan: string, targetCycle: string, now?: Date): Promise<PlanChangePreviewIssue[]>;
115
+ private classify;
116
+ /** Catalog order = rank. Non-marketed plans go to the end. */
117
+ private planRank;
118
+ private resolveEffectiveAt;
119
+ private computeProration;
120
+ }
121
+
122
+ declare class PendingPlanMaterializationService {
123
+ private readonly query;
124
+ private readonly subscriptionWrite;
125
+ private readonly entitlements;
126
+ private readonly contractFreeze;
127
+ private readonly logger;
128
+ constructor(query: PendingPlanQueryPort, subscriptionWrite: TenantSubscriptionWritePort, entitlements: EntitlementService, contractFreeze?: ContractFreezePort | null);
129
+ materializeDuePlanChanges(now?: Date): Promise<{
130
+ applied: number;
131
+ }>;
132
+ private tryFreeze;
133
+ }
134
+
135
+ declare class PreviewPlanChangeDto {
136
+ plan: string;
137
+ billingCycle: string;
138
+ }
139
+ declare class ChangePlanDto extends PreviewPlanChangeDto {
140
+ /** Immediate change (true) vs. change at period end (false/undefined). */
141
+ effectiveImmediately?: boolean;
142
+ }
143
+ declare class CancelSubscriptionDto {
144
+ /** true = cancel immediately (status CANCELED). false/undefined = at period end. */
145
+ immediately?: boolean;
146
+ }
147
+
148
+ declare class CompleteOnboardingSubscriptionDto {
149
+ plan: string;
150
+ billingCycle: string;
151
+ promoCode?: string;
152
+ /**
153
+ * Optional — UUIDs of the BundleVersions that should be booked
154
+ * together with the plan (P11.7.3). Per bundle, the platform
155
+ * default minimum term (12 months) is set. Bundles are added
156
+ * best-effort **after** the plan change — an error on an individual
157
+ * bundle (e.g. incompatible with the chosen plan) lands as a
158
+ * warning in the response, without rolling back the plan change.
159
+ */
160
+ bundleVersionIds?: string[];
161
+ }
162
+
163
+ interface SubscriptionBundleConfig {
164
+ /** Default minimum term in months on `add`. Default = 12. */
165
+ defaultMinimumTermMonths?: number;
166
+ }
167
+ interface AddBundleToSubscriptionInput {
168
+ subscriptionId: string;
169
+ bundleVersionId: string;
170
+ /** PlanKey of the current subscription for the plan-compatibility check. */
171
+ currentPlanKey: string;
172
+ /** Default = now (service time). */
173
+ startedAt?: Date;
174
+ /**
175
+ * Override for the minimum term (months). Default = config or
176
+ * 12. `0` explicitly means "no minimum term"
177
+ * (`minimumTermEndsAt = null`).
178
+ */
179
+ minimumTermMonths?: number;
180
+ }
181
+ interface CancelBundleFromSubscriptionInput {
182
+ subscriptionBundleId: string;
183
+ /** Default = now. */
184
+ canceledAt?: Date;
185
+ /**
186
+ * Period end of the subscription from which the cancellation could take effect.
187
+ * Effective date = `max(currentPeriodEnd, minimumTermEndsAt)`.
188
+ * If not set, `canceledAt` is interpreted as the period end
189
+ * (= immediate effect, provided the minimum term has already expired).
190
+ */
191
+ currentPeriodEnd?: Date;
192
+ }
193
+ declare class SubscriptionBundlesService {
194
+ private readonly repo;
195
+ private readonly bundles;
196
+ private readonly blockedBundles;
197
+ private readonly defaultMinTermMonths;
198
+ constructor(repo: SubscriptionBundleRepository, bundles: BundleRepository, config?: SubscriptionBundleConfig, blockedBundles?: SelfServiceBlockedBundles | null);
199
+ /** All bundle bookings of a subscription (for the "My Bundles" page). */
200
+ listForSubscription(subscriptionId: string): Promise<SubscriptionBundleView[]>;
201
+ addBundleToSubscription(input: AddBundleToSubscriptionInput): Promise<SubscriptionBundleRecord>;
202
+ cancelBundleFromSubscription(input: CancelBundleFromSubscriptionInput): Promise<SubscriptionBundleRecord>;
203
+ /**
204
+ * "Undo cancellation" — only as long as the cancellation is not yet effective
205
+ * (the bundle runs until `canceledEffectiveAt`). After that, re-booking is the way.
206
+ */
207
+ reactivateBundle(subscriptionBundleId: string): Promise<SubscriptionBundleRecord>;
208
+ }
209
+ /**
210
+ * Effective date of a bundle cancellation:
211
+ * `max(currentPeriodEnd, minimumTermEndsAt)` — missing values fall back to
212
+ * `canceledAt` (= immediate effect). Shared between the
213
+ * cancellation mutation and the preview (#37).
214
+ */
215
+ declare function resolveBundleCancelEffectiveAt(input: {
216
+ canceledAt: Date;
217
+ currentPeriodEnd: Date | null;
218
+ minimumTermEndsAt: Date | null;
219
+ }): Date;
220
+ /**
221
+ * Adds `months` to `date` and keeps the UTC day. Edge case
222
+ * 31.01 + 1 month → 28/29.02 (JS Date does this automatically by
223
+ * setMonth normalizing the day).
224
+ */
225
+ declare function addMonths(date: Date, months: number): Date;
226
+
227
+ interface RequestLike {
228
+ user?: {
229
+ tenantId?: string;
230
+ sub?: string;
231
+ id?: string;
232
+ };
233
+ }
234
+ interface UsageResponse {
235
+ plan: string;
236
+ effectivePlan: string;
237
+ billingCycle: string;
238
+ status: string;
239
+ isPilot: boolean;
240
+ pilotEndsAt: Date | null;
241
+ trialEndsAt: Date | null;
242
+ startedAt: Date | null;
243
+ currentPeriodStart: Date | null;
244
+ currentPeriodEnd: Date | null;
245
+ pendingPlan: string | null;
246
+ pendingBillingCycle: string | null;
247
+ pendingEffectiveAt: Date | null;
248
+ planVersion: SubscriptionUsageRecord['planVersion'];
249
+ pendingPlanVersion: SubscriptionUsageRecord['pendingPlanVersion'];
250
+ pendingPlanVersionEffectiveAt: Date | null;
251
+ pendingPlanVersionAccepted: boolean;
252
+ pendingPlanVersionAcceptedAt: Date | null;
253
+ limits: ReturnType<typeof toEffectiveLimitsSnapshot>;
254
+ usage: Record<string, number>;
255
+ /**
256
+ * P11.4 (METAMODELL §17a): Frozen package snapshot from the
257
+ * `CheckoutOffer` that was activated during onboarding. Read-only
258
+ * for the tenant self-service UI. `null` for subscriptions without
259
+ * a CheckoutOffer origin.
260
+ */
261
+ packageSnapshot: unknown | null;
262
+ /** P11.4: Optional reference to the originating CheckoutOffer. */
263
+ checkoutOfferId: string | null;
264
+ }
265
+ declare class TenantBillingController {
266
+ private readonly entitlements;
267
+ private readonly planPreview;
268
+ private readonly subscriptionUsage;
269
+ private readonly usageSnapshot;
270
+ private readonly subscriptionWrite;
271
+ private readonly tenantIdResolver;
272
+ private readonly userIdResolver;
273
+ private readonly blockedPlans;
274
+ private readonly promoCodes;
275
+ private readonly auditService;
276
+ private readonly userEmailResolver;
277
+ private readonly auditContextResolver;
278
+ private readonly subscriptionBundles;
279
+ private readonly contractFreeze;
280
+ private readonly trialProjection;
281
+ 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);
282
+ private readonly logger;
283
+ getEntitlement(req: RequestLike): Promise<EffectiveLimitsSnapshot>;
284
+ getUsage(req: RequestLike): Promise<UsageResponse>;
285
+ previewPlanChange(req: RequestLike, dto: PreviewPlanChangeDto): Promise<PlanChangePreviewDto>;
286
+ changePlan(req: RequestLike, dto: ChangePlanDto): Promise<{
287
+ plan: string;
288
+ billingCycle: string;
289
+ immediate: boolean;
290
+ pendingPlan?: undefined;
291
+ pendingBillingCycle?: undefined;
292
+ pendingEffectiveAt?: undefined;
293
+ } | {
294
+ plan: string;
295
+ billingCycle: string;
296
+ pendingPlan: string;
297
+ pendingBillingCycle: string;
298
+ pendingEffectiveAt: Date;
299
+ immediate: boolean;
300
+ }>;
301
+ completeOnboardingSubscription(req: RequestLike, dto: CompleteOnboardingSubscriptionDto): Promise<OnboardingSelectionResponse>;
302
+ acceptPendingPlanVersion(req: RequestLike): Promise<{
303
+ accepted: boolean;
304
+ acceptedAt: Date | null;
305
+ effectiveAt: Date | null;
306
+ idempotent: boolean;
307
+ }>;
308
+ cancelSubscription(req: RequestLike, dto: CancelSubscriptionDto): Promise<{
309
+ canceledAt: Date | null;
310
+ status: string;
311
+ immediate: boolean;
312
+ }>;
313
+ private requireTenantId;
314
+ private requireUserId;
315
+ private buildActor;
316
+ private resolveUserEmail;
317
+ private toResponseRedemption;
318
+ private collectPromoSkipReasons;
319
+ /**
320
+ * #18: contract freeze after the plan change — non-fatal, only outside of
321
+ * the trial (during a trial the trial entitlements apply, not the booked plan;
322
+ * the freeze happens on the transition to ACTIVE, i.e. on materialization).
323
+ * Without a configured `contractFreeze` hook the call is a no-op.
324
+ */
325
+ private tryFreezeOnPlanChange;
326
+ /**
327
+ * Audit-log helper — writes best-effort, does not block the response path.
328
+ * If `AdminAuditService` is not injected (e.g. a minimal deploy without
329
+ * AdminModule), the call is silently discarded.
330
+ */
331
+ private auditLog;
332
+ }
333
+
334
+ interface SubscriptionBundlePreviewIssue {
335
+ code: string;
336
+ message: string;
337
+ }
338
+ /** Subscription context — the controller reads it from the SubscriptionUsagePort. */
339
+ interface SubscriptionBundlePreviewContext {
340
+ subscriptionId: string;
341
+ /** PlanKey of the current subscription (plan compatibility + redundancy source). */
342
+ currentPlanKey: string;
343
+ /** 'MONTHLY' | 'YEARLY' (port convention). */
344
+ billingCycle: string;
345
+ /** Subscription status (TRIAL/ACTIVE/...). No proration during TRIAL. */
346
+ status: string;
347
+ startedAt: Date | null;
348
+ currentPeriodStart: Date | null;
349
+ currentPeriodEnd: Date | null;
350
+ }
351
+ interface BundlePreviewSnapshot {
352
+ bundleKey: string;
353
+ label: string;
354
+ bundleVersionId: string;
355
+ features: string[];
356
+ quotas: Record<string, number>;
357
+ }
358
+ /** AK-13: feature is already paid for elsewhere — double-payment hint. */
359
+ interface RedundantFeatureHint {
360
+ featureKey: string;
361
+ coveredBy: 'PLAN' | 'BUNDLE';
362
+ /** planKey or bundleKey of the covering source. */
363
+ coveredByKey: string;
364
+ }
365
+ interface SubscriptionBundleAddPreviewDto {
366
+ action: 'add';
367
+ bundle: BundlePreviewSnapshot;
368
+ billingCycle: string;
369
+ /**
370
+ * Prorated amount until period end. `null` during TRIAL (no paid
371
+ * period yet) or without a list price for the cycle.
372
+ */
373
+ proration: ProrationDto | null;
374
+ /** List price per follow-up period in the current cycle; null = no price maintained. */
375
+ nextPeriodPriceNet: number | null;
376
+ minimumTermMonths: number;
377
+ /** Projected minimum-term end from `now`; null = no minimum term. */
378
+ minimumTermEndsAt: Date | null;
379
+ redundantFeatures: RedundantFeatureHint[];
380
+ /**
381
+ * requires-features (#35) that neither the plan nor active bundles nor the
382
+ * bundle itself cover. Non-empty ⇒ blocker
383
+ * BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED.
384
+ */
385
+ missingRequires: string[];
386
+ blockers: SubscriptionBundlePreviewIssue[];
387
+ warnings: SubscriptionBundlePreviewIssue[];
388
+ }
389
+ interface SubscriptionBundleCancelPreviewDto {
390
+ action: 'cancel';
391
+ subscriptionBundleId: string;
392
+ bundle: BundlePreviewSnapshot;
393
+ billingCycle: string;
394
+ /** Effective date = max(currentPeriodEnd, minimumTermEndsAt). */
395
+ effectiveAt: Date;
396
+ /** Savings per period from the effective date; null = no price maintained. */
397
+ nextPeriodSavingsNet: number | null;
398
+ blockers: SubscriptionBundlePreviewIssue[];
399
+ warnings: SubscriptionBundlePreviewIssue[];
400
+ }
401
+ declare class SubscriptionBundlePreviewService {
402
+ private readonly subscriptionBundles;
403
+ private readonly bundles;
404
+ private readonly plans;
405
+ private readonly catalogEntries;
406
+ private readonly blockedBundles;
407
+ private readonly defaultMinTermMonths;
408
+ constructor(subscriptionBundles: SubscriptionBundleRepository, bundles: BundleRepository, plans?: PlanRepository | null, catalogEntries?: CatalogEntryRepository | null, blockedBundles?: SelfServiceBlockedBundles | null, config?: SubscriptionBundleConfig);
409
+ previewAdd(ctx: SubscriptionBundlePreviewContext, input: {
410
+ bundleVersionId: string;
411
+ minimumTermMonths?: number;
412
+ }, now?: Date): Promise<SubscriptionBundleAddPreviewDto>;
413
+ previewCancel(ctx: SubscriptionBundlePreviewContext, input: {
414
+ subscriptionBundleId: string;
415
+ }, now?: Date): Promise<SubscriptionBundleCancelPreviewDto>;
416
+ /** Bookability checks — same codes as `addBundleToSubscription` (422 path). */
417
+ private collectBookabilityBlockers;
418
+ /** Versions of the active bundle bookings (for redundancy + requires coverage). */
419
+ private loadActiveBundleVersions;
420
+ /** Features of the currently live PlanVersion state; empty without PlanRepository. */
421
+ private resolvePlanFeatures;
422
+ private collectRedundantFeatures;
423
+ /**
424
+ * requires of the new bundle that neither the bundle itself nor plan ∪
425
+ * active bundles cover (#35). Empty without CatalogEntryRepository.
426
+ */
427
+ private collectMissingRequires;
428
+ /** `projectKey` lives on the bundle stem, not on the version. */
429
+ private resolveProjectKey;
430
+ }
431
+ /**
432
+ * List price (net) for the billing cycle including plan-specific
433
+ * pricing override (BundlePricingOverride with `planId`). null = no price
434
+ * maintained for the cycle.
435
+ */
436
+ declare function resolveBundlePriceNet(bundleVersion: BundleVersionRow, planKey: string, billingCycle: string): number | null;
437
+
438
+ export { type AddBundleToSubscriptionInput as A, type BundlePreviewSnapshot as B, type CancelBundleFromSubscriptionInput as C, type LimitsCheckRow as L, PendingPlanMaterializationService as P, type RedundantFeatureHint as R, type SubscriptionBundleAddPreviewDto as S, TenantAdminGuard as T, type UsageResponse as U, CancelSubscriptionDto as a, ChangePlanDto as b, CompleteOnboardingSubscriptionDto as c, ComposedTenantAuthGuard as d, type PlanChangeContext as e, type PlanChangePreviewDto as f, type PlanChangePreviewIssue as g, PlanChangePreviewService as h, type PlanChangeType as i, type PlanSnapshotDto as j, PreviewPlanChangeDto as k, type ProrationDto as l, type ProrationInput as m, type SubscriptionBundleCancelPreviewDto as n, type SubscriptionBundleConfig as o, type SubscriptionBundlePreviewContext as p, type SubscriptionBundlePreviewIssue as q, SubscriptionBundlePreviewService as r, SubscriptionBundlesService as s, TenantBillingController as t, addMonths as u, computeProration as v, resolveBundleCancelEffectiveAt as w, resolveBundlePriceNet as x };