@saasicat/nest 1.0.0-rc.2 → 1.0.0-rc.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +44 -8
  2. package/dist/.build-stamp +1 -1
  3. package/dist/_entries.cjs +10214 -4796
  4. package/dist/admin/index.d.cts +5 -4
  5. package/dist/admin/index.d.ts +5 -4
  6. package/dist/admin/index.js +13 -9
  7. package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
  8. package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
  9. package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
  10. package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
  11. package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
  12. package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
  13. package/dist/billing/index.d.cts +448 -108
  14. package/dist/billing/index.d.ts +448 -108
  15. package/dist/billing/index.js +108 -41
  16. package/dist/catalog/index.d.cts +74 -45
  17. package/dist/catalog/index.d.ts +74 -45
  18. package/dist/catalog/index.js +21 -13
  19. package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
  20. package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
  21. package/dist/checkout-offer/index.d.cts +17 -22
  22. package/dist/checkout-offer/index.d.ts +17 -22
  23. package/dist/checkout-offer/index.js +21 -7
  24. package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
  25. package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
  26. package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
  27. package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
  28. package/dist/chunk-34XVPR6P.js +222 -0
  29. package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
  30. package/dist/chunk-3RLWML2S.js +896 -0
  31. package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
  32. package/dist/chunk-55QEDAMX.js +946 -0
  33. package/dist/chunk-5CKC7O52.js +140 -0
  34. package/dist/chunk-6PJE7EWO.js +185 -0
  35. package/dist/chunk-7NJKVC5X.js +353 -0
  36. package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
  37. package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
  38. package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
  39. package/dist/chunk-FEQZCWUL.js +174 -0
  40. package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
  41. package/dist/chunk-G7RQO2XO.js +0 -0
  42. package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
  43. package/dist/chunk-GZTL64QK.js +7 -0
  44. package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
  45. package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
  46. package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
  47. package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
  48. package/dist/chunk-KPEMTBOP.js +26 -0
  49. package/dist/chunk-LF6J4YYN.js +301 -0
  50. package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
  51. package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
  52. package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
  53. package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
  54. package/dist/chunk-QPVDCKYS.js +702 -0
  55. package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
  56. package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
  57. package/dist/chunk-SZ7RFPXA.js +10 -0
  58. package/dist/chunk-TBBZWZQT.js +98 -0
  59. package/dist/chunk-TOEFN7DN.js +295 -0
  60. package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
  61. package/dist/chunk-WOEJ6K7M.js +55 -0
  62. package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
  63. package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
  64. package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
  65. package/dist/discovery/index.d.cts +2 -2
  66. package/dist/discovery/index.d.ts +2 -2
  67. package/dist/discovery/index.js +6 -6
  68. package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
  69. package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
  70. package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
  71. package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
  72. package/dist/entitlement/index.d.cts +27 -4
  73. package/dist/entitlement/index.d.ts +27 -4
  74. package/dist/entitlement/index.js +13 -8
  75. package/dist/index.d.cts +209 -28
  76. package/dist/index.d.ts +209 -28
  77. package/dist/index.js +285 -163
  78. package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
  79. package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
  80. package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
  81. package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
  82. package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
  83. package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
  84. package/dist/payments/index.cjs +4 -0
  85. package/dist/payments/index.d.cts +187 -0
  86. package/dist/payments/index.d.ts +187 -0
  87. package/dist/payments/index.js +141 -0
  88. package/dist/payments.module-BdUq9ot_.d.cts +57 -0
  89. package/dist/payments.module-CQGv_VG7.d.ts +57 -0
  90. package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
  91. package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
  92. package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
  93. package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
  94. package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
  95. package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
  96. package/dist/platform/index.d.cts +52 -23
  97. package/dist/platform/index.d.ts +52 -23
  98. package/dist/platform/index.js +57 -36
  99. package/dist/promo/index.d.cts +23 -8
  100. package/dist/promo/index.d.ts +23 -8
  101. package/dist/promo/index.js +15 -7
  102. package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
  103. package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
  104. package/dist/promo.service-BYBu6dy3.d.cts +215 -0
  105. package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
  106. package/dist/registration/index.d.cts +104 -47
  107. package/dist/registration/index.d.ts +104 -47
  108. package/dist/registration/index.js +15 -9
  109. package/dist/subscriber/index.cjs +4 -0
  110. package/dist/subscriber/index.d.cts +28 -0
  111. package/dist/subscriber/index.d.ts +28 -0
  112. package/dist/subscriber/index.js +16 -0
  113. package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
  114. package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
  115. package/dist/subscription-contract/index.d.cts +4 -2
  116. package/dist/subscription-contract/index.d.ts +4 -2
  117. package/dist/subscription-contract/index.js +13 -5
  118. package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
  119. package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
  120. package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
  121. package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
  122. package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
  123. package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
  124. package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
  125. package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
  126. package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
  127. package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
  128. package/dist/testing/index.d.cts +44 -17
  129. package/dist/testing/index.d.ts +44 -17
  130. package/dist/testing/index.js +189 -40
  131. package/package.json +24 -4
  132. package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
  133. package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
  134. package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
  135. package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
  136. package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
  137. package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
  138. package/dist/chunk-57V6ZTI6.js +0 -22
  139. package/dist/chunk-BSK6YBLI.js +0 -87
  140. package/dist/chunk-DQKCK7DX.js +0 -217
  141. package/dist/chunk-VXEYLNIB.js +0 -632
  142. package/dist/chunk-WUDYIPYH.js +0 -715
  143. package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
  144. package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
  145. package/dist/promo.service-BJbKAmw3.d.cts +0 -112
  146. package/dist/promo.service-BJbKAmw3.d.ts +0 -112
  147. package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
  148. package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
  149. package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
  150. package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
  151. /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
  152. /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
  153. /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
  154. /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
@@ -0,0 +1,812 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { d as AuthGuardList, a as TrialProjectionPort, P as PendingPlanQueryPort, T as TenantIdResolver, b as UserIdResolver, U as UserEmailResolver, A as AuditContextResolver } from './tenant-billing.tokens-G-1xOlMr.cjs';
3
+ import { BillingCycle, CancellationNoticePeriods, SubscriptionUsagePort, UsageSnapshotPort, SelfServiceBlockedPlans, SubscriptionBundleRepository, TenantSubscriptionWritePort, BundleRepository, SubscriptionBundleView, SubscriptionBundleRecord, PlanRepository, CatalogEntryRepository, SubscriptionUsageRecord, OnboardingSelectionResponse } from '@saasicat/core';
4
+ import { c as EntitlementService, a as EffectiveLimitsSnapshot, t as toEffectiveLimitsSnapshot } from './aggregation-OpnhwzF5.cjs';
5
+ import { d as ContractFreezePort, j as SelfServiceBlockedBundles } from './tenant-billing.module-ClbjzocC.cjs';
6
+ import { P as PlanCatalogSource } from './plan-catalog-source-DWe-BGY1.cjs';
7
+ import { a as PromoCodesService } from './promo.service-BYBu6dy3.cjs';
8
+ import { A as AdminAuditService } from './admin-audit.service-4P3Djdxk.cjs';
9
+
10
+ interface CancellationInput {
11
+ /** When the customer declared it. */
12
+ now: Date;
13
+ /** End of the period they are in, if the subscription has one. */
14
+ currentPeriodEnd: Date | null;
15
+ /** End of what was committed to. Null on a subscription with no term. */
16
+ minimumTermUntil: Date | null;
17
+ /** Needed only to find the following period end when notice has passed. */
18
+ billingCycle: BillingCycle;
19
+ /** Days before the term end after which a cancellation is too late. */
20
+ noticePeriodDays: number;
21
+ /**
22
+ * The day of the month the subscription is billed on.
23
+ *
24
+ * Only the hard cut reads it, and only then does it matter — but there it
25
+ * matters in money. A declaration made after the notice window lands one
26
+ * period past the term end, and computing that step from the term end alone
27
+ * takes its day from a date that may already have been clamped: an
28
+ * anchor-31 subscription whose term ends 28 February was cut to 28 March
29
+ * rather than 31 March, three days short of the period the customer had
30
+ * just been charged for.
31
+ */
32
+ billingAnchorDay?: number | null;
33
+ }
34
+ interface CancellationDecision {
35
+ /** When the cancellation takes effect. */
36
+ effectiveAt: Date;
37
+ /** The term end it was measured against. */
38
+ termEndsAt: Date;
39
+ /** True when the notice window had already closed. */
40
+ afterNoticeDeadline: boolean;
41
+ /** The moment after which a cancellation lands one period later. */
42
+ noticeDeadline: Date | null;
43
+ }
44
+ /**
45
+ * Decides when a cancellation declared at `now` takes effect.
46
+ *
47
+ * Never returns a date in the past: a term that has already ended means the
48
+ * cancellation lands immediately, which is what a customer outside any
49
+ * commitment should get.
50
+ */
51
+ declare function decideCancellation(input: CancellationInput): CancellationDecision;
52
+
53
+ /**
54
+ * No notice at all, named so it is not a bare `{}` at a call site.
55
+ *
56
+ * Not a configuration default: `config/saas.yaml` requires both rhythms and an
57
+ * application that omits them does not boot. This is what lets a class be
58
+ * constructed directly — in a test that does not exercise a notice period —
59
+ * without restating a term. `TenantBillingModule` always provides the real one.
60
+ */
61
+ declare const NO_NOTICE_PERIOD: CancellationNoticePeriods;
62
+ /**
63
+ * The notice a subscription on `billingCycle` is owed.
64
+ *
65
+ * The two rhythms are read apart rather than one falling back to the other:
66
+ * they are separate numbers because real contracts set them apart, and
67
+ * inferring one from the other would be inventing a term.
68
+ *
69
+ * `config/saas.yaml` requires both, so there is nothing to default here. A
70
+ * number that is not written down is not a zero — it is a question the operator
71
+ * has to answer before the application starts.
72
+ */
73
+ declare function noticeDaysFor(periods: CancellationNoticePeriods, billingCycle: string): number;
74
+ /**
75
+ * The fields a cancellation is decided from. Narrow on purpose: this file
76
+ * decides dates and knows nothing about persistence.
77
+ */
78
+ interface CancellableSubscription {
79
+ status: string;
80
+ billingCycle: BillingCycle;
81
+ currentPeriodEnd: Date | null;
82
+ minimumTermUntil: Date | null;
83
+ trialEndsAt: Date | null;
84
+ /** See `CancellationInput.billingAnchorDay`. */
85
+ billingAnchorDay?: number | null;
86
+ }
87
+ /**
88
+ * Decides a cancellation from a subscription, which is the only correct way to
89
+ * build the input above: reading the four date fields at a call site loses the
90
+ * fifth fact, and there is no type that notices.
91
+ *
92
+ * That fifth fact is whether the subscription commits to anything. A trial does
93
+ * not. It has a period end like every other subscription — the repository's own
94
+ * fixture sets one — and treating that as a term makes the two rules a customer
95
+ * meets disagree: a plan change during a trial takes effect at once because
96
+ * there is nothing to protect, while a cancellation was measured against a term
97
+ * that does not exist.
98
+ *
99
+ * The consequence was not a date a few weeks out. With a notice period
100
+ * configured, a trial ending in five days is already past its deadline, so the
101
+ * cancellation landed one BILLING CYCLE after the trial — a customer ending a
102
+ * yearly-cycle trial bought a year by cancelling it. The window exists so that a
103
+ * term cannot be left at the last moment; a trial has no term to leave.
104
+ *
105
+ * What a trial does have is an end, and the cancellation lands there: nothing is
106
+ * billed for the remainder and the customer keeps what they were given. Ending
107
+ * it on the spot would take the trial away as the price of saying they do not
108
+ * want to convert.
109
+ */
110
+ declare function decideCancellationFor(sub: CancellableSubscription, now: Date, noticePeriodDays: number): CancellationDecision;
111
+
112
+ declare class ComposedTenantAuthGuard implements CanActivate {
113
+ private readonly guards;
114
+ constructor(guards?: AuthGuardList | null);
115
+ canActivate(context: ExecutionContext): Promise<boolean>;
116
+ }
117
+
118
+ declare class TenantAdminGuard implements CanActivate {
119
+ canActivate(context: ExecutionContext): boolean;
120
+ }
121
+
122
+ interface ProrationDto {
123
+ daysRemainingInPeriod: number;
124
+ daysInPeriod: number;
125
+ periodStart: Date;
126
+ periodEnd: Date;
127
+ currentPriceNet: number;
128
+ targetPriceNet: number;
129
+ /**
130
+ * What the change costs for the rest of the period, never below zero.
131
+ *
132
+ * The raw arithmetic goes negative when the target is cheaper than what is
133
+ * running — after a price reduction, an upgrade can arrive at a negative
134
+ * number. That is not a credit: this platform does not pay money back, and
135
+ * a negative charge carried into an invoice is a refund nobody agreed to.
136
+ * It is a free upgrade, and `isFree` is how a page says so.
137
+ */
138
+ prorataDeltaNet: number;
139
+ /**
140
+ * The unclamped result, kept because the page has something to say about it.
141
+ *
142
+ * Dropping it would make "free" indistinguishable from "costs exactly
143
+ * nothing", and those read differently to someone deciding.
144
+ */
145
+ rawDeltaNet: number;
146
+ /**
147
+ * True when the arithmetic asked for less than nothing.
148
+ *
149
+ * Strictly less: a change that costs exactly zero — equal prices, or a
150
+ * remaining fraction that rounds the difference away — is not a free
151
+ * upgrade, it is a change with no price difference. `rawDeltaNet` exists
152
+ * above precisely so a page can tell those apart, and a flag that merges
153
+ * them takes that back.
154
+ */
155
+ isFree: boolean;
156
+ }
157
+ interface ProrationInput {
158
+ periodStart: Date;
159
+ periodEnd: Date;
160
+ now: Date;
161
+ /** Previous period price (bundle add: 0 — something is only added). */
162
+ currentPriceNet: number;
163
+ targetPriceNet: number;
164
+ }
165
+ declare function computeProration(input: ProrationInput): ProrationDto;
166
+
167
+ type PlanChangeType = 'UPGRADE' | 'DOWNGRADE' | 'CYCLE_CHANGE' | 'NOOP';
168
+ /** Where the target plan sits against the running one in the catalog order. */
169
+ type PlanDirection = 'UP' | 'DOWN' | 'SAME';
170
+ /**
171
+ * Whether the target billing period is longer, shorter or the same.
172
+ *
173
+ * Its own answer, deliberately. `changeType` collapses "a better plan" and "a
174
+ * shorter commitment" into one word, and the two have opposite consequences: a
175
+ * higher plan may start today, a shorter period may not — it would end a term
176
+ * the customer is still inside. Asked as one question, moving from a yearly
177
+ * STARTER to a monthly PRO reads as `UPGRADE`, applies immediately, and ends
178
+ * the yearly commitment early. That is the case this split exists for.
179
+ */
180
+ type CycleDirection = 'LONGER' | 'SHORTER' | 'SAME';
181
+ interface PlanSnapshotDto {
182
+ id: string;
183
+ name: string;
184
+ monthlyNet: number | null;
185
+ yearlyNet: number | null;
186
+ quotas: Record<string, number>;
187
+ features: string[];
188
+ }
189
+ interface LimitsCheckRow {
190
+ used: number;
191
+ currentMax: number;
192
+ targetMax: number;
193
+ exceeded: boolean;
194
+ }
195
+ interface PlanChangePreviewIssue {
196
+ code: string;
197
+ /**
198
+ * English display text, and the last rung of the ladder rather than the
199
+ * first: `resolveErrorMessage` prefers the reader's catalogue and reaches
200
+ * this only for a code nobody has translated. It stays on the wire because
201
+ * a blocker that renders as an empty line leaves someone with a disabled
202
+ * button and no reason.
203
+ */
204
+ message: string;
205
+ /**
206
+ * The values the sentence needs, beside it rather than inside it.
207
+ *
208
+ * Without these a client holding the code still cannot rebuild the
209
+ * sentence — it would have to parse English prose for the numbers. Every
210
+ * template in the shipped catalogues names only what appears here, and
211
+ * `preview-issues-are-translatable.test.js` holds that.
212
+ */
213
+ params?: Record<string, string | number>;
214
+ }
215
+ interface PlanChangePreviewDto {
216
+ changeType: PlanChangeType;
217
+ /**
218
+ * The two answers `changeType` collapses into one.
219
+ *
220
+ * A page needs them apart to explain a deferred upgrade: the plan went up,
221
+ * the period got shorter, and it is the second that decided the date.
222
+ */
223
+ planDirection: PlanDirection;
224
+ cycleDirection: CycleDirection;
225
+ current: {
226
+ plan: PlanSnapshotDto;
227
+ billingCycle: string;
228
+ };
229
+ target: {
230
+ plan: PlanSnapshotDto;
231
+ billingCycle: string;
232
+ };
233
+ /** For upgrade/NOOP: immediately (null). Otherwise period end. */
234
+ effectiveAt: Date | null;
235
+ isImmediate: boolean;
236
+ /**
237
+ * Projected new trial end after the change (app trial logic, e.g.
238
+ * carry-over of the remaining time). `null` if no TrialProjectionPort is
239
+ * configured, the subscription is not in a trial, or nothing changes.
240
+ * The wizard uses this to show "regular from the end of the trial".
241
+ */
242
+ projectedTrialEndsAt: Date | null;
243
+ proration: ProrationDto | null;
244
+ /** Map quotaKey → LimitsCheckRow across all quota dimensions from the current limit, target plan and usage. */
245
+ limitsCheck: Record<string, LimitsCheckRow>;
246
+ featuresLost: string[];
247
+ featuresGained: string[];
248
+ /** Hard prevention reasons — e.g. usage > target limit. */
249
+ blockers: PlanChangePreviewIssue[];
250
+ /** Non-blocking hints — e.g. feature loss. */
251
+ warnings: PlanChangePreviewIssue[];
252
+ }
253
+ interface PlanChangeContext {
254
+ /** Current period start time from the subscription, if present. */
255
+ currentPeriodStart: Date | null;
256
+ /** Current period end from the subscription, if present. */
257
+ currentPeriodEnd: Date | null;
258
+ /**
259
+ * End of what was committed to, which can outlast the period.
260
+ *
261
+ * They coincide until a notice period pushes one past the other. A change
262
+ * scheduled to the period end alone would then materialise inside the
263
+ * commitment this rule exists to protect — the customer keeps the plan they
264
+ * are bound to for eleven months and loses it in the twelfth.
265
+ */
266
+ minimumTermUntil: Date | null;
267
+ /** TRIAL end, if status === 'TRIAL'. */
268
+ trialEndsAt: Date | null;
269
+ /**
270
+ * The cancellation, because it decides what a change may still do.
271
+ *
272
+ * A cancellation was measured against the term of the cycle it was declared
273
+ * under, so that cycle cannot move while it is outstanding. The route
274
+ * refuses such a change; without the same answer here, a reader is walked
275
+ * through the whole wizard — the acknowledgement included — and meets the
276
+ * refusal only when they press confirm.
277
+ */
278
+ canceledAt: Date | null;
279
+ canceledEffectiveAt: Date | null;
280
+ /** Subscription status (TRIAL/ACTIVE/...). */
281
+ status: string;
282
+ /** Current cycle of the subscription (for cycle-change classification). */
283
+ currentBillingCycle: string;
284
+ /** Current plan of the subscription. */
285
+ currentPlan: string;
286
+ /** Subscription start, if present (for the periodEndAfter fallback). */
287
+ startedAt: Date | null;
288
+ }
289
+ declare class PlanChangePreviewService {
290
+ private readonly catalogs;
291
+ private readonly entitlements;
292
+ private readonly subscriptions;
293
+ private readonly usageSnapshot;
294
+ private readonly blockedPlans;
295
+ private readonly trialProjection;
296
+ private readonly subscriptionBundles;
297
+ constructor(catalogs: PlanCatalogSource, entitlements: EntitlementService, subscriptions: SubscriptionUsagePort, usageSnapshot: UsageSnapshotPort, blockedPlans?: SelfServiceBlockedPlans | null, trialProjection?: TrialProjectionPort | null, subscriptionBundles?: SubscriptionBundleRepository | null);
298
+ preview(tenantId: string, targetPlan: string, targetCycle: string, now?: Date): Promise<PlanChangePreviewDto>;
299
+ /** Like `preview`, but only the blocker list — for a server-side
300
+ * pre-check before the `changePlan` mutation (defense-in-depth). */
301
+ assertChangeAllowed(tenantId: string, targetPlan: string, targetCycle: string, now?: Date): Promise<PlanChangePreviewIssue[]>;
302
+ /** Catalog order decides which plan is higher; equal keys are `SAME`. */
303
+ private planDirection;
304
+ /**
305
+ * YEARLY is the longer commitment; anything else is compared against it.
306
+ *
307
+ * Written as a comparison rather than a pair of equality checks so a third
308
+ * cycle — quarterly is the one that keeps being asked for — orders itself
309
+ * instead of falling into `SAME` and quietly becoming immediate.
310
+ */
311
+ private cycleDirection;
312
+ /**
313
+ * Active bookings whose own rhythm would not fit `targetCycle`.
314
+ *
315
+ * Reads the booking's stored rhythm, not the plan's: a booking with none
316
+ * follows the plan and therefore fits any plan by construction. Empty
317
+ * without the bundle module, which is a consumer that has no bookings at
318
+ * all rather than one whose bookings are being ignored.
319
+ */
320
+ private bookingsOutlastingCycle;
321
+ private classify;
322
+ /** Catalog order = rank. Non-marketed plans go to the end. */
323
+ private planRank;
324
+ private resolveEffectiveAt;
325
+ private computeProration;
326
+ }
327
+
328
+ declare class PendingPlanMaterializationService {
329
+ private readonly query;
330
+ private readonly subscriptionWrite;
331
+ private readonly entitlements;
332
+ private readonly contractFreeze;
333
+ private readonly logger;
334
+ constructor(query: PendingPlanQueryPort, subscriptionWrite: TenantSubscriptionWritePort, entitlements: EntitlementService, contractFreeze?: ContractFreezePort | null);
335
+ materializeDuePlanChanges(now?: Date): Promise<{
336
+ applied: number;
337
+ }>;
338
+ private tryFreeze;
339
+ }
340
+
341
+ declare class PreviewPlanChangeDto {
342
+ plan: string;
343
+ billingCycle: string;
344
+ }
345
+ /**
346
+ * A plan change carries what to change to, never when.
347
+ *
348
+ * It used to carry `effectiveImmediately`, and the route honoured it — so a
349
+ * direct call could take the immediate path and end a term the customer was
350
+ * inside. When a change lands follows from the plan direction, the cycle
351
+ * direction and the minimum term, all of which the server knows and the caller
352
+ * does not.
353
+ */
354
+ declare class ChangePlanDto extends PreviewPlanChangeDto {
355
+ }
356
+ /**
357
+ * A cancellation carries nothing.
358
+ *
359
+ * It used to carry `immediately`, and honouring it let a tenant end a term they
360
+ * were still inside — the one thing this route may not do. A cancellation is a
361
+ * declaration; when it lands is decided from the minimum term and the notice
362
+ * period, not asked for. Ending a contract on the spot is an operator's act and
363
+ * goes through the operator's own path.
364
+ *
365
+ * Kept as an empty class rather than deleted so `whitelist` still strips a body
366
+ * from a client that has not been updated, instead of the field reaching a
367
+ * handler that would ignore it silently.
368
+ */
369
+ declare class CancelSubscriptionDto {
370
+ /**
371
+ * The effective date the page showed before the customer confirmed.
372
+ *
373
+ * Optional, and checked rather than trusted: if the server's own decision
374
+ * differs, the request is refused with the new date so the page can ask
375
+ * again. The window is small and the consequence is not — a dialog opened
376
+ * before a notice deadline and confirmed after it promises January 2027 and
377
+ * would deliver January 2028.
378
+ *
379
+ * This is the caller telling the server what only the caller knows: what
380
+ * the human actually agreed to.
381
+ */
382
+ expectedEffectiveAt?: string;
383
+ }
384
+
385
+ declare class CompleteOnboardingSubscriptionDto {
386
+ plan: string;
387
+ billingCycle: string;
388
+ promoCode?: string;
389
+ /**
390
+ * Optional — UUIDs of the BundleVersions that should be booked
391
+ * together with the plan (P11.7.3). Per bundle, the platform
392
+ * default minimum term (none) is set. Bundles are added
393
+ * best-effort **after** the plan change — an error on an individual
394
+ * bundle (e.g. incompatible with the chosen plan) lands as a
395
+ * warning in the response, without rolling back the plan change.
396
+ */
397
+ bundleVersionIds?: string[];
398
+ }
399
+
400
+ interface SubscriptionBundleConfig {
401
+ /**
402
+ * Default minimum term in months on `add`. Default = 0 — no commitment,
403
+ * so an add-on can be cancelled to its own period end. An operator who
404
+ * wants one configures it.
405
+ */
406
+ defaultMinimumTermMonths?: number;
407
+ }
408
+ interface AddBundleToSubscriptionInput {
409
+ subscriptionId: string;
410
+ bundleVersionId: string;
411
+ /** PlanKey of the current subscription for the plan-compatibility check. */
412
+ currentPlanKey: string;
413
+ /** Default = now (service time). */
414
+ startedAt?: Date;
415
+ /**
416
+ * Override for the minimum term (months). Default = config or
417
+ * 12. `0` explicitly means "no minimum term"
418
+ * (`minimumTermEndsAt = null`).
419
+ */
420
+ minimumTermMonths?: number;
421
+ /**
422
+ * When the parent subscription ends, or null while it runs on.
423
+ *
424
+ * A bundle cannot commit past the subscription that pays for it. With a
425
+ * cancellation outstanding, a twelve-month default term on a subscription
426
+ * ending in three weeks binds a customer to something that has three weeks
427
+ * left to give — and once the parent ends, entitlement resolution grants
428
+ * nothing through it.
429
+ *
430
+ * Required rather than optional: a caller that omits it commits the
431
+ * customer for longer than the contract can deliver, and the omission is
432
+ * invisible.
433
+ */
434
+ parentEndsAt: Date | null;
435
+ /**
436
+ * The plan's rhythm, its current period end, and the day it is billed on.
437
+ *
438
+ * A bundle runs in step with the plan that pays for it: its periods end on
439
+ * the plan's day, and its first one is short. Aligned here, at booking,
440
+ * rather than trimmed when the plan ends — a trim means somebody was
441
+ * committed to more than they received, and then owed the difference.
442
+ *
443
+ * The plan's cycle is also the ceiling for the bundle's: a bundle may run
444
+ * in a shorter rhythm, never a longer one.
445
+ */
446
+ planCycle: BillingCycle;
447
+ planPeriodEnd: Date | null;
448
+ planAnchorDay: number | null;
449
+ /** The bundle's own rhythm. Defaults to the plan's. */
450
+ billingCycle?: BillingCycle;
451
+ }
452
+ interface CancelBundleFromSubscriptionInput {
453
+ subscriptionBundleId: string;
454
+ /** Default = now. */
455
+ canceledAt?: Date;
456
+ /**
457
+ * Period end of the subscription from which the cancellation could take effect.
458
+ * Effective date = `max(currentPeriodEnd, minimumTermEndsAt)`.
459
+ * If not set, `canceledAt` is interpreted as the period end
460
+ * (= immediate effect, provided the minimum term has already expired).
461
+ */
462
+ currentPeriodEnd?: Date;
463
+ /**
464
+ * When the parent subscription ends, or null while it runs on.
465
+ *
466
+ * A bundle cannot be held past the plan that pays for it, and the term was
467
+ * written at booking — before any cancellation declared since. Reading the
468
+ * boundary here is what makes that harmless: no clamp at insert time can
469
+ * see a cancellation that had not happened yet.
470
+ */
471
+ parentEndsAt: Date | null;
472
+ }
473
+ declare class SubscriptionBundlesService {
474
+ private readonly repo;
475
+ private readonly bundles;
476
+ private readonly blockedBundles;
477
+ private readonly defaultMinTermMonths;
478
+ constructor(repo: SubscriptionBundleRepository, bundles: BundleRepository, config?: SubscriptionBundleConfig, blockedBundles?: SelfServiceBlockedBundles | null);
479
+ /** All bundle bookings of a subscription (for the "My Bundles" page). */
480
+ /**
481
+ * The tenant's bookings, with the price each one is actually billed at.
482
+ *
483
+ * `planKey` is required because a price is not a property of a bundle
484
+ * alone: a `BundlePricingOverride` can set a different one per plan, and
485
+ * the rhythm decides which of the two figures applies. Returning the base
486
+ * monthly price regardless — which this did until 2026-08-27 — puts a
487
+ * number on the tenant's screen that nobody is charged, and on a yearly
488
+ * booking it was out by whatever the yearly price is.
489
+ */
490
+ listForSubscription(subscriptionId: string, planKey: string, planCycle: string): Promise<SubscriptionBundleView[]>;
491
+ /**
492
+ * List prices for the given bundle versions, in both rhythms, resolved for
493
+ * one plan.
494
+ *
495
+ * The public catalogue cannot answer this: it has no tenant and therefore
496
+ * no plan, so it serves the base prices and a bundle priced only through an
497
+ * override reads as having no price at all. A tenant UI that treated those
498
+ * fields as final hid such a bundle behind "not available in this rhythm"
499
+ * while the booking would have gone through.
500
+ */
501
+ resolvePricesFor(planKey: string, bundleVersionIds: string[]): Promise<Record<string, {
502
+ monthlyNet: number | null;
503
+ yearlyNet: number | null;
504
+ }>>;
505
+ addBundleToSubscription(input: AddBundleToSubscriptionInput): Promise<SubscriptionBundleRecord>;
506
+ cancelBundleFromSubscription(input: CancelBundleFromSubscriptionInput): Promise<SubscriptionBundleRecord>;
507
+ /**
508
+ * "Undo cancellation" — only as long as the cancellation is not yet effective
509
+ * (the bundle runs until `canceledEffectiveAt`). After that, re-booking is the way.
510
+ */
511
+ reactivateBundle(subscriptionBundleId: string): Promise<SubscriptionBundleRecord>;
512
+ }
513
+ /**
514
+ * Effective date of a bundle cancellation:
515
+ * `max(currentPeriodEnd, minimumTermEndsAt)` — missing values fall back to
516
+ * `canceledAt` (= immediate effect). Shared between the
517
+ * cancellation mutation and the preview (#37).
518
+ */
519
+ declare function resolveBundleCancelEffectiveAt(input: {
520
+ canceledAt: Date;
521
+ currentPeriodEnd: Date | null;
522
+ minimumTermEndsAt: Date | null;
523
+ /** When the parent subscription ends, or null while it runs on. */
524
+ parentEndsAt: Date | null;
525
+ }): Date;
526
+ /**
527
+ * Adds `months` to `date` and keeps the UTC day. Edge case
528
+ * 31.01 + 1 month → 28/29.02 (JS Date does this automatically by
529
+ * setMonth normalizing the day).
530
+ */
531
+ declare function addMonths(date: Date, months: number): Date;
532
+ declare function clampToParent(ownTermEndsAt: Date | null, parentEndsAt: Date | null): Date | null;
533
+
534
+ interface SubscriptionBundlePreviewIssue {
535
+ code: string;
536
+ message: string;
537
+ /**
538
+ * The values the sentence names, beside it rather than inside it — the same
539
+ * contract `PlanChangePreviewIssue` carries, so one consumer mechanism
540
+ * resolves the issues of both previews.
541
+ */
542
+ params?: Record<string, string | number>;
543
+ }
544
+ /** Subscription context — the controller reads it from the SubscriptionUsagePort. */
545
+ interface SubscriptionBundlePreviewContext {
546
+ subscriptionId: string;
547
+ /** PlanKey of the current subscription (plan compatibility + redundancy source). */
548
+ currentPlanKey: string;
549
+ /** 'MONTHLY' | 'YEARLY' (port convention). */
550
+ billingCycle: string;
551
+ /** Subscription status (TRIAL/ACTIVE/...). No proration during TRIAL. */
552
+ status: string;
553
+ startedAt: Date | null;
554
+ currentPeriodStart: Date | null;
555
+ currentPeriodEnd: Date | null;
556
+ /**
557
+ * When the parent subscription ends, or null while it runs on.
558
+ *
559
+ * The dialog states the date the booking will be committed to, and the
560
+ * mutation caps that date at the parent's end. A preview that does not cap
561
+ * it describes a different contract from the one that is written.
562
+ */
563
+ parentEndsAt: Date | null;
564
+ /**
565
+ * The day of the month the plan is billed on, where it is known.
566
+ *
567
+ * The bundle's periods land on this day, so the preview needs the same
568
+ * value the booking uses. Reading it from the plan's period end instead
569
+ * would hand on a date a short month has already clamped, and the quoted
570
+ * first period would then differ from the one written.
571
+ */
572
+ planAnchorDay?: number | null;
573
+ }
574
+ interface BundlePreviewSnapshot {
575
+ bundleKey: string;
576
+ label: string;
577
+ bundleVersionId: string;
578
+ features: string[];
579
+ quotas: Record<string, number>;
580
+ }
581
+ /** AK-13: feature is already paid for elsewhere — double-payment hint. */
582
+ interface RedundantFeatureHint {
583
+ featureKey: string;
584
+ coveredBy: 'PLAN' | 'BUNDLE';
585
+ /** planKey or bundleKey of the covering source. */
586
+ coveredByKey: string;
587
+ }
588
+ interface SubscriptionBundleAddPreviewDto {
589
+ action: 'add';
590
+ bundle: BundlePreviewSnapshot;
591
+ billingCycle: string;
592
+ /**
593
+ * Prorated amount until period end. `null` during TRIAL (no paid
594
+ * period yet) or without a list price for the cycle.
595
+ */
596
+ proration: ProrationDto | null;
597
+ /** List price per follow-up period in the current cycle; null = no price maintained. */
598
+ nextPeriodPriceNet: number | null;
599
+ minimumTermMonths: number;
600
+ /** Projected minimum-term end from `now`; null = no minimum term. */
601
+ minimumTermEndsAt: Date | null;
602
+ /**
603
+ * End of the first billing period, on the plan's billing day.
604
+ *
605
+ * Shorter than a full cycle in the usual case, and charged pro rata for
606
+ * exactly that stretch (`proration`). Null where the plan has no period to
607
+ * align to — a trial, or a subscription not yet started.
608
+ */
609
+ firstPeriodEnd: Date | null;
610
+ /**
611
+ * The day the bundle ends because the plan does, or null while the plan
612
+ * runs on.
613
+ *
614
+ * Ending with the plan is not a cancellation: no notice is needed, and the
615
+ * period the bundle is in when it happens is not credited. The alignment
616
+ * exists so that day is a period boundary — this field is what remains
617
+ * when the plan is already ending on a day the bundle has been paid past.
618
+ */
619
+ endsWithPlanAt: Date | null;
620
+ redundantFeatures: RedundantFeatureHint[];
621
+ /**
622
+ * requires-features (#35) that neither the plan nor active bundles nor the
623
+ * bundle itself cover. Non-empty ⇒ blocker
624
+ * BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED.
625
+ */
626
+ missingRequires: string[];
627
+ blockers: SubscriptionBundlePreviewIssue[];
628
+ warnings: SubscriptionBundlePreviewIssue[];
629
+ }
630
+ interface SubscriptionBundleCancelPreviewDto {
631
+ action: 'cancel';
632
+ subscriptionBundleId: string;
633
+ bundle: BundlePreviewSnapshot;
634
+ billingCycle: string;
635
+ /** Effective date = max(currentPeriodEnd, minimumTermEndsAt). */
636
+ effectiveAt: Date;
637
+ /** Savings per period from the effective date; null = no price maintained. */
638
+ nextPeriodSavingsNet: number | null;
639
+ blockers: SubscriptionBundlePreviewIssue[];
640
+ warnings: SubscriptionBundlePreviewIssue[];
641
+ }
642
+ declare class SubscriptionBundlePreviewService {
643
+ private readonly subscriptionBundles;
644
+ private readonly bundles;
645
+ private readonly plans;
646
+ private readonly catalogEntries;
647
+ private readonly blockedBundles;
648
+ private readonly defaultMinTermMonths;
649
+ constructor(subscriptionBundles: SubscriptionBundleRepository, bundles: BundleRepository, plans?: PlanRepository | null, catalogEntries?: CatalogEntryRepository | null, blockedBundles?: SelfServiceBlockedBundles | null, config?: SubscriptionBundleConfig);
650
+ previewAdd(ctx: SubscriptionBundlePreviewContext, input: {
651
+ bundleVersionId: string;
652
+ minimumTermMonths?: number;
653
+ /** The bundle's own rhythm. Defaults to the plan's, as the booking does. */
654
+ billingCycle?: BillingCycle;
655
+ }, now?: Date): Promise<SubscriptionBundleAddPreviewDto>;
656
+ previewCancel(ctx: SubscriptionBundlePreviewContext, input: {
657
+ subscriptionBundleId: string;
658
+ }, now?: Date): Promise<SubscriptionBundleCancelPreviewDto>;
659
+ /** Bookability checks — same codes as `addBundleToSubscription` (422 path). */
660
+ private collectBookabilityBlockers;
661
+ /** Versions of the active bundle bookings (for redundancy + requires coverage). */
662
+ private loadActiveBundleVersions;
663
+ /** Features of the currently live PlanVersion state; empty without PlanRepository. */
664
+ private resolvePlanFeatures;
665
+ private collectRedundantFeatures;
666
+ /**
667
+ * requires of the new bundle that neither the bundle itself nor plan ∪
668
+ * active bundles cover (#35). Empty without CatalogEntryRepository.
669
+ */
670
+ private collectMissingRequires;
671
+ }
672
+
673
+ interface RequestLike {
674
+ user?: {
675
+ tenantId?: string;
676
+ sub?: string;
677
+ id?: string;
678
+ };
679
+ }
680
+ interface UsageResponse {
681
+ plan: string;
682
+ effectivePlan: string;
683
+ billingCycle: string;
684
+ status: string;
685
+ isPilot: boolean;
686
+ pilotEndsAt: Date | null;
687
+ trialEndsAt: Date | null;
688
+ startedAt: Date | null;
689
+ currentPeriodStart: Date | null;
690
+ currentPeriodEnd: Date | null;
691
+ pendingPlan: string | null;
692
+ pendingBillingCycle: string | null;
693
+ pendingEffectiveAt: Date | null;
694
+ planVersion: SubscriptionUsageRecord['planVersion'];
695
+ pendingPlanVersion: SubscriptionUsageRecord['pendingPlanVersion'];
696
+ pendingPlanVersionEffectiveAt: Date | null;
697
+ pendingPlanVersionAccepted: boolean;
698
+ pendingPlanVersionAcceptedAt: Date | null;
699
+ /** When a cancellation was declared. Null while none was. */
700
+ canceledAt: Date | null;
701
+ /** When it lands. A tenant keeps everything until then. */
702
+ canceledEffectiveAt: Date | null;
703
+ /**
704
+ * What cancelling right now would do — the date, and why it is that date.
705
+ *
706
+ * A projection, not a state: it is recomputed on every read, and it says
707
+ * nothing about whether a cancellation exists. `canceledEffectiveAt` above
708
+ * is the one that does.
709
+ */
710
+ cancellation: CancellationDecision;
711
+ limits: ReturnType<typeof toEffectiveLimitsSnapshot>;
712
+ usage: Record<string, number>;
713
+ /**
714
+ * P11.4: Frozen package snapshot from the
715
+ * `CheckoutOffer` that was activated during onboarding. Read-only
716
+ * for the tenant self-service UI. `null` for subscriptions without
717
+ * a CheckoutOffer origin.
718
+ */
719
+ packageSnapshot: unknown | null;
720
+ /** P11.4: Optional reference to the originating CheckoutOffer. */
721
+ checkoutOfferId: string | null;
722
+ }
723
+ declare class TenantBillingController {
724
+ private readonly entitlements;
725
+ private readonly planPreview;
726
+ private readonly subscriptionUsage;
727
+ private readonly usageSnapshot;
728
+ private readonly subscriptionWrite;
729
+ private readonly tenantIdResolver;
730
+ private readonly userIdResolver;
731
+ private readonly blockedPlans;
732
+ private readonly promoCodes;
733
+ private readonly auditService;
734
+ private readonly userEmailResolver;
735
+ private readonly auditContextResolver;
736
+ private readonly subscriptionBundles;
737
+ private readonly contractFreeze;
738
+ private readonly trialProjection;
739
+ private readonly cancellationNoticeDays;
740
+ 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, cancellationNoticeDays?: CancellationNoticePeriods);
741
+ private readonly logger;
742
+ getEntitlement(req: RequestLike): Promise<EffectiveLimitsSnapshot>;
743
+ getUsage(req: RequestLike): Promise<UsageResponse>;
744
+ previewPlanChange(req: RequestLike, dto: PreviewPlanChangeDto): Promise<PlanChangePreviewDto>;
745
+ changePlan(req: RequestLike, dto: ChangePlanDto): Promise<{
746
+ plan: string;
747
+ billingCycle: string;
748
+ immediate: boolean;
749
+ pendingPlan?: undefined;
750
+ pendingBillingCycle?: undefined;
751
+ pendingEffectiveAt?: undefined;
752
+ } | {
753
+ plan: string;
754
+ billingCycle: string;
755
+ pendingPlan: string;
756
+ pendingBillingCycle: string;
757
+ pendingEffectiveAt: Date;
758
+ immediate: boolean;
759
+ }>;
760
+ completeOnboardingSubscription(req: RequestLike, dto: CompleteOnboardingSubscriptionDto): Promise<OnboardingSelectionResponse>;
761
+ acceptPendingPlanVersion(req: RequestLike): Promise<{
762
+ accepted: boolean;
763
+ acceptedAt: Date | null;
764
+ effectiveAt: Date | null;
765
+ idempotent: boolean;
766
+ }>;
767
+ cancelSubscription(req: RequestLike, dto: CancelSubscriptionDto): Promise<{
768
+ canceledAt: Date | null;
769
+ canceledEffectiveAt: Date | null;
770
+ status: string;
771
+ termEndsAt: null;
772
+ noticeDeadline: null;
773
+ afterNoticeDeadline: null;
774
+ alreadyCanceled: boolean;
775
+ } | {
776
+ canceledAt: Date | null;
777
+ canceledEffectiveAt: Date | null;
778
+ status: string;
779
+ termEndsAt: Date;
780
+ noticeDeadline: Date | null;
781
+ afterNoticeDeadline: boolean;
782
+ alreadyCanceled: boolean;
783
+ }>;
784
+ /**
785
+ * What a cancellation declared at `now` would do. One method, because the
786
+ * page states the date and this route applies it, and two constructions of
787
+ * the same input are two chances to disagree about a rule the customer
788
+ * meets once.
789
+ */
790
+ private projectCancellation;
791
+ private requireTenantId;
792
+ private requireUserId;
793
+ private buildActor;
794
+ private resolveUserEmail;
795
+ private toResponseRedemption;
796
+ private collectPromoSkipReasons;
797
+ /**
798
+ * #18: contract freeze after the plan change — non-fatal, only outside of
799
+ * the trial (during a trial the trial entitlements apply, not the booked plan;
800
+ * the freeze happens on the transition to ACTIVE, i.e. on materialization).
801
+ * Without a configured `contractFreeze` hook the call is a no-op.
802
+ */
803
+ private tryFreezeOnPlanChange;
804
+ /**
805
+ * Audit-log helper — writes best-effort, does not block the response path.
806
+ * If `AdminAuditService` is not injected (e.g. a minimal deploy without
807
+ * AdminModule), the call is silently discarded.
808
+ */
809
+ private auditLog;
810
+ }
811
+
812
+ export { type AddBundleToSubscriptionInput as A, type BundlePreviewSnapshot as B, type CancelBundleFromSubscriptionInput as C, clampToParent as D, computeProration as E, decideCancellation as F, decideCancellationFor as G, noticeDaysFor as H, resolveBundleCancelEffectiveAt as I, type LimitsCheckRow as L, NO_NOTICE_PERIOD as N, PendingPlanMaterializationService as P, type RedundantFeatureHint as R, type SubscriptionBundleAddPreviewDto as S, TenantAdminGuard as T, type UsageResponse as U, CancelSubscriptionDto as a, type CancellableSubscription as b, type CancellationDecision as c, type CancellationInput as d, ChangePlanDto as e, CompleteOnboardingSubscriptionDto as f, ComposedTenantAuthGuard as g, type CycleDirection as h, type PlanChangeContext as i, type PlanChangePreviewDto as j, type PlanChangePreviewIssue as k, PlanChangePreviewService as l, type PlanChangeType as m, type PlanDirection as n, type PlanSnapshotDto as o, PreviewPlanChangeDto as p, type ProrationDto as q, type ProrationInput as r, type SubscriptionBundleCancelPreviewDto as s, type SubscriptionBundleConfig as t, type SubscriptionBundlePreviewContext as u, type SubscriptionBundlePreviewIssue as v, SubscriptionBundlePreviewService as w, SubscriptionBundlesService as x, TenantBillingController as y, addMonths as z };