@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,1611 @@
1
+ import {
2
+ asProvider
3
+ } from "./chunk-SABTXESR.js";
4
+ import {
5
+ __name
6
+ } from "./chunk-SHUYVCID.js";
7
+
8
+ // src/registration/helpers.ts
9
+ import { createHash, randomInt, timingSafeEqual } from "crypto";
10
+ function generateOtpCode() {
11
+ return String(randomInt(1e5, 1e6));
12
+ }
13
+ __name(generateOtpCode, "generateOtpCode");
14
+ function hashOtpCode(code) {
15
+ return createHash("sha256").update(code).digest("hex");
16
+ }
17
+ __name(hashOtpCode, "hashOtpCode");
18
+ function verifyOtpCode(expectedHash, code) {
19
+ const actual = hashOtpCode(code);
20
+ if (actual.length !== expectedHash.length) {
21
+ return false;
22
+ }
23
+ try {
24
+ return timingSafeEqual(Buffer.from(actual, "hex"), Buffer.from(expectedHash, "hex"));
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ __name(verifyOtpCode, "verifyOtpCode");
30
+ function slugify(name) {
31
+ const transliterated = name.toLowerCase().replace(/ß/g, "ss").replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").normalize("NFKD").replace(/[̀-ͯ]/g, "");
32
+ const slug = transliterated.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
33
+ return slug || "verein";
34
+ }
35
+ __name(slugify, "slugify");
36
+
37
+ // src/registration/tokens.ts
38
+ var PENDING_REGISTRATION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol("PENDING_REGISTRATION_REPOSITORY");
39
+ var REGISTRATION_OTP_DELIVERY_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_OTP_DELIVERY");
40
+ var USER_ACCOUNT_LOOKUP_TOKEN = /* @__PURE__ */ Symbol("USER_ACCOUNT_LOOKUP");
41
+ var SLUG_AVAILABILITY_CHECK_TOKEN = /* @__PURE__ */ Symbol("SLUG_AVAILABILITY_CHECK");
42
+ var PASSWORD_HASHER_TOKEN = /* @__PURE__ */ Symbol("PASSWORD_HASHER");
43
+ var PLAN_CATALOG_LOOKUP_TOKEN = /* @__PURE__ */ Symbol("PLAN_CATALOG_LOOKUP");
44
+ var PAYMENT_PROVIDER_TOKEN = /* @__PURE__ */ Symbol("PAYMENT_PROVIDER");
45
+ var PAYMENT_EVENT_LOG_TOKEN = /* @__PURE__ */ Symbol("PAYMENT_EVENT_LOG");
46
+ var ACTIVATION_ORCHESTRATOR_TOKEN = /* @__PURE__ */ Symbol("ACTIVATION_ORCHESTRATOR");
47
+ var REGISTRATION_AUDIT_LOGGER_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_AUDIT_LOGGER");
48
+ var REGISTRATION_RESUME_TOKEN_SIGNER_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_RESUME_TOKEN_SIGNER");
49
+ var REGISTRATION_RESUME_DELIVERY_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_RESUME_DELIVERY");
50
+ var REGISTRATION_RESUME_BASE_URL_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_RESUME_BASE_URL");
51
+ var REGISTRATION_CONFIGURATOR_LOOKUP_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_CONFIGURATOR_LOOKUP");
52
+ var REGISTRATION_PROMO_PREVIEW_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_PROMO_PREVIEW");
53
+ var REGISTRATION_BUSINESS_TYPE_LOOKUP_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_BUSINESS_TYPE_LOOKUP");
54
+ var REGISTRATION_CONFIG_TOKEN = /* @__PURE__ */ Symbol("REGISTRATION_CONFIG");
55
+
56
+ // src/registration/pending-registration.service.ts
57
+ import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Optional } from "@nestjs/common";
58
+ import { REGISTRATION_RESUME_TTL_MINUTES } from "@saasicat/types";
59
+ import { OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, REGISTRATION_STEP_BY_STATUS } from "@saasicat/types";
60
+
61
+ // src/registration/pricing.ts
62
+ function round2(value) {
63
+ return Math.round(value * 100) / 100;
64
+ }
65
+ __name(round2, "round2");
66
+ function computeBreakdown(catalog, selection, promo) {
67
+ const model = catalog.models.find((m) => m.id === selection.modelId);
68
+ if (!model) {
69
+ throw new Error(`Unknown model: ${selection.modelId}`);
70
+ }
71
+ const modelMonthlyNet = round2(model.monthlyNet);
72
+ const effectiveQuotas = {
73
+ ...model.quotaBase
74
+ };
75
+ const subtotalMonthlyNet = modelMonthlyNet;
76
+ const factor = selection.billingCycle === "YEARLY" ? catalog.cycleDiscount : 1;
77
+ const subtotalNet = round2(subtotalMonthlyNet * factor);
78
+ const discountAmount = promo ? Math.min(round2(promo.discountAmount), subtotalNet) : 0;
79
+ const totalNet = Math.max(0, round2(subtotalNet - discountAmount));
80
+ const totalGross = round2(totalNet * (1 + catalog.vatRate / 100));
81
+ const yearlySavings = selection.billingCycle === "YEARLY" ? round2(subtotalMonthlyNet * 12 - subtotalMonthlyNet * catalog.cycleDiscount) : 0;
82
+ return {
83
+ cycle: selection.billingCycle,
84
+ effectiveQuotas,
85
+ modelMonthlyNet,
86
+ subtotalMonthlyNet,
87
+ subtotalNet,
88
+ discountAmount,
89
+ totalNet,
90
+ vatRate: catalog.vatRate,
91
+ totalGross,
92
+ yearlySavings,
93
+ appliedPromo: promo ? {
94
+ code: "",
95
+ label: promo.label,
96
+ percent: promo.percent
97
+ } : void 0
98
+ };
99
+ }
100
+ __name(computeBreakdown, "computeBreakdown");
101
+ function resolveModel(catalog, modelId) {
102
+ const model = catalog.models.find((m) => m.id === modelId);
103
+ if (!model) throw new Error(`Unknown model: ${modelId}`);
104
+ return {
105
+ planId: model.planId,
106
+ monthlyNet: model.monthlyNet,
107
+ yearlyNet: model.yearlyNet
108
+ };
109
+ }
110
+ __name(resolveModel, "resolveModel");
111
+
112
+ // src/registration/pending-registration.service.ts
113
+ function _ts_decorate(decorators, target, key, desc) {
114
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
115
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
116
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
117
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
118
+ }
119
+ __name(_ts_decorate, "_ts_decorate");
120
+ function _ts_metadata(k, v) {
121
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
122
+ }
123
+ __name(_ts_metadata, "_ts_metadata");
124
+ function _ts_param(paramIndex, decorator) {
125
+ return function(target, key) {
126
+ decorator(target, key, paramIndex);
127
+ };
128
+ }
129
+ __name(_ts_param, "_ts_param");
130
+ var PendingRegistrationService = class _PendingRegistrationService {
131
+ static {
132
+ __name(this, "PendingRegistrationService");
133
+ }
134
+ repo;
135
+ otpDelivery;
136
+ userLookup;
137
+ slugCheck;
138
+ passwordHasher;
139
+ planCatalog;
140
+ paymentProvider;
141
+ paymentEventLog;
142
+ activationOrchestrator;
143
+ audit;
144
+ resumeTokenSigner;
145
+ resumeDelivery;
146
+ resumeBaseUrl;
147
+ configuratorLookup;
148
+ promoPreview;
149
+ businessTypeLookup;
150
+ logger = new Logger(_PendingRegistrationService.name);
151
+ constructor(repo, otpDelivery, userLookup, slugCheck, passwordHasher, planCatalog, paymentProvider, paymentEventLog, activationOrchestrator, audit, resumeTokenSigner, resumeDelivery, resumeBaseUrl, configuratorLookup, promoPreview, businessTypeLookup) {
152
+ this.repo = repo;
153
+ this.otpDelivery = otpDelivery;
154
+ this.userLookup = userLookup;
155
+ this.slugCheck = slugCheck;
156
+ this.passwordHasher = passwordHasher;
157
+ this.planCatalog = planCatalog;
158
+ this.paymentProvider = paymentProvider;
159
+ this.paymentEventLog = paymentEventLog;
160
+ this.activationOrchestrator = activationOrchestrator;
161
+ this.audit = audit;
162
+ this.resumeTokenSigner = resumeTokenSigner;
163
+ this.resumeDelivery = resumeDelivery;
164
+ this.resumeBaseUrl = resumeBaseUrl;
165
+ this.configuratorLookup = configuratorLookup;
166
+ this.promoPreview = promoPreview;
167
+ this.businessTypeLookup = businessTypeLookup;
168
+ }
169
+ async record(eventType, pendingRegistrationId, context, metadata) {
170
+ try {
171
+ await this.audit.log({
172
+ eventType,
173
+ pendingRegistrationId,
174
+ context,
175
+ metadata
176
+ });
177
+ } catch (error) {
178
+ const message = error instanceof Error ? error.message : String(error);
179
+ this.logger.warn(`Audit-Log fehlgeschlagen (${eventType}): ${message}`);
180
+ }
181
+ }
182
+ /** Public signup plan list for step 3 (frontend `usePublicPlans()`). */
183
+ async listPublicPlans() {
184
+ return this.planCatalog.listPublicSignupPlans();
185
+ }
186
+ async selectPlan(input, context) {
187
+ const pending = await this.repo.findById(input.pendingRegistrationId);
188
+ if (!pending) {
189
+ throw new NotFoundException({
190
+ code: "PENDING_REGISTRATION_NOT_FOUND"
191
+ });
192
+ }
193
+ if (pending.expiresAt.getTime() < Date.now()) {
194
+ throw new BadRequestException({
195
+ code: "PENDING_REGISTRATION_EXPIRED"
196
+ });
197
+ }
198
+ if (pending.status !== "EMAIL_VERIFIED" && pending.status !== "PLAN_SELECTED" && pending.status !== "CHECKOUT_STARTED") {
199
+ throw new BadRequestException({
200
+ code: "INVALID_REGISTRATION_STATE"
201
+ });
202
+ }
203
+ const plan = await this.planCatalog.findPublicSignupPlan(input.planId);
204
+ if (!plan) {
205
+ throw new BadRequestException({
206
+ code: "PLAN_NOT_AVAILABLE"
207
+ });
208
+ }
209
+ const now = /* @__PURE__ */ new Date();
210
+ const expiresAt = new Date(now.getTime() + PENDING_ONBOARDING_TTL_DAYS * 24 * 60 * 60 * 1e3);
211
+ const updated = await this.repo.update(pending.id, {
212
+ status: "PLAN_SELECTED",
213
+ currentStep: 4,
214
+ selectedPlanId: plan.id,
215
+ expiresAt
216
+ });
217
+ await this.record("PLAN_SELECTED", updated.id, context, {
218
+ planId: plan.id
219
+ });
220
+ return {
221
+ pendingRegistrationId: updated.id,
222
+ status: updated.status,
223
+ nextStep: REGISTRATION_STEP_BY_STATUS[updated.status],
224
+ selectedPlanId: updated.selectedPlanId ?? plan.id
225
+ };
226
+ }
227
+ async startCheckout(input, context) {
228
+ const pending = await this.repo.findById(input.pendingRegistrationId);
229
+ if (!pending) {
230
+ throw new NotFoundException({
231
+ code: "PENDING_REGISTRATION_NOT_FOUND"
232
+ });
233
+ }
234
+ if (pending.expiresAt.getTime() < Date.now()) {
235
+ throw new BadRequestException({
236
+ code: "PENDING_REGISTRATION_EXPIRED"
237
+ });
238
+ }
239
+ if (pending.status !== "PLAN_SELECTED" && pending.status !== "CHECKOUT_STARTED") {
240
+ throw new BadRequestException({
241
+ code: "PLAN_NOT_SELECTED"
242
+ });
243
+ }
244
+ if (!pending.selectedPlanId) {
245
+ throw new BadRequestException({
246
+ code: "PLAN_NOT_SELECTED"
247
+ });
248
+ }
249
+ const plan = await this.planCatalog.findPublicSignupPlan(pending.selectedPlanId);
250
+ if (!plan) {
251
+ throw new BadRequestException({
252
+ code: "PLAN_NOT_AVAILABLE"
253
+ });
254
+ }
255
+ const session = await this.paymentProvider.createCheckoutSession({
256
+ pendingRegistrationId: pending.id,
257
+ planId: pending.selectedPlanId,
258
+ email: pending.email,
259
+ successUrl: input.successUrl,
260
+ cancelUrl: input.cancelUrl
261
+ });
262
+ const now = /* @__PURE__ */ new Date();
263
+ const expiresAt = new Date(now.getTime() + PENDING_CHECKOUT_TTL_DAYS * 24 * 60 * 60 * 1e3);
264
+ const updated = await this.repo.update(pending.id, {
265
+ status: "CHECKOUT_STARTED",
266
+ currentStep: 4,
267
+ checkoutSessionId: session.sessionId,
268
+ checkoutStartedAt: now,
269
+ expiresAt
270
+ });
271
+ await this.record("CHECKOUT_STARTED", updated.id, context, {
272
+ sessionId: session.sessionId,
273
+ provider: session.provider
274
+ });
275
+ return {
276
+ pendingRegistrationId: updated.id,
277
+ status: updated.status,
278
+ nextStep: REGISTRATION_STEP_BY_STATUS[updated.status],
279
+ checkoutSessionId: session.sessionId,
280
+ checkoutUrl: session.checkoutUrl
281
+ };
282
+ }
283
+ /**
284
+ * Processes a payment webhook idempotently:
285
+ * 1. `tryClaim` pins the event ID via a @unique INSERT. Duplicates are
286
+ * dropped silently (`ALREADY_PROCESSED`).
287
+ * 2. On status `SUCCEEDED` the PendingRegistration is resolved from the
288
+ * checkout session and the ActivationOrchestrator triggers the final
289
+ * user+tenant+subscription creation in a single transaction.
290
+ * 3. After activation the PendingRegistration is deleted.
291
+ *
292
+ * On errors in step 2/3 the EventLog entry remains — provider retries then
293
+ * run into ALREADY_PROCESSED. Operational repair is done manually (status
294
+ * inspection + cleanup).
295
+ */
296
+ async handlePaymentEvent(input, context) {
297
+ const claimed = await this.paymentEventLog.tryClaim(input.eventId, {
298
+ provider: input.provider,
299
+ sessionId: input.sessionId,
300
+ status: input.status,
301
+ rawPayload: input.rawPayload
302
+ });
303
+ if (!claimed) {
304
+ this.logger.warn(`Idempotenz: PaymentEvent ${input.eventId} bereits verarbeitet \u2014 Duplikat verworfen.`);
305
+ await this.record("PAYMENT_DUPLICATE_IGNORED", null, context, {
306
+ eventId: input.eventId,
307
+ sessionId: input.sessionId
308
+ });
309
+ return {
310
+ activated: false,
311
+ reason: "ALREADY_PROCESSED"
312
+ };
313
+ }
314
+ if (input.status !== "SUCCEEDED") {
315
+ await this.record("PAYMENT_FAILED", null, context, {
316
+ eventId: input.eventId,
317
+ sessionId: input.sessionId
318
+ });
319
+ return {
320
+ activated: false,
321
+ reason: "PAYMENT_NOT_SUCCEEDED"
322
+ };
323
+ }
324
+ if (!input.sessionId) {
325
+ return {
326
+ activated: false,
327
+ reason: "MISSING_SESSION_ID"
328
+ };
329
+ }
330
+ const pending = await this.repo.findByCheckoutSession(input.sessionId);
331
+ if (!pending) {
332
+ return {
333
+ activated: false,
334
+ reason: "PENDING_REGISTRATION_NOT_FOUND"
335
+ };
336
+ }
337
+ if (pending.status !== "CHECKOUT_STARTED" && pending.status !== "PLAN_SELECTED") {
338
+ return {
339
+ activated: false,
340
+ reason: "INVALID_STATE"
341
+ };
342
+ }
343
+ await this.record("PAYMENT_RECEIVED", pending.id, context, {
344
+ eventId: input.eventId,
345
+ sessionId: input.sessionId
346
+ });
347
+ const result = await this.activationOrchestrator.activate(pending);
348
+ await this.repo.delete(pending.id);
349
+ await this.record("ACTIVATION_COMPLETED", pending.id, context, {
350
+ userId: result.userId,
351
+ tenantId: result.tenantId,
352
+ subscriptionId: result.subscriptionId
353
+ });
354
+ this.logger.log(`Aktivierung erfolgreich: pending=${pending.id} \u2192 user=${result.userId} tenant=${result.tenantId} subscription=${result.subscriptionId}`);
355
+ return {
356
+ activated: true,
357
+ result
358
+ };
359
+ }
360
+ /**
361
+ * Deletes all PendingRegistration records with `expiresAt < now`.
362
+ * Full deletion (no anonymization path) is the simpler option: the schema
363
+ * holds `email`, `firstName`, `lastName`, `passwordHash` as NOT NULL, and
364
+ * the unique email is exactly what needs to become free again after expiry.
365
+ *
366
+ * The batch `limit` guards against memory spikes on large backlogs — the
367
+ * caller (cron) keeps calling until `moreAvailable === false`.
368
+ */
369
+ async runCleanup(now, limit = 500) {
370
+ if (limit <= 0) {
371
+ return {
372
+ deleted: 0,
373
+ moreAvailable: false
374
+ };
375
+ }
376
+ const expired = await this.repo.findExpired(now, limit);
377
+ for (const pending of expired) {
378
+ try {
379
+ await this.repo.delete(pending.id);
380
+ } catch (error) {
381
+ const message = error instanceof Error ? error.message : String(error);
382
+ this.logger.warn(`Cleanup: PendingRegistration ${pending.id} konnte nicht geloescht werden (${message}).`);
383
+ }
384
+ }
385
+ if (expired.length > 0) {
386
+ this.logger.log(`Cleanup: ${expired.length} abgelaufene PendingRegistrations geloescht.`);
387
+ }
388
+ return {
389
+ deleted: expired.length,
390
+ moreAvailable: expired.length >= limit
391
+ };
392
+ }
393
+ async start(input, context) {
394
+ const email = normalizeEmail(input.email);
395
+ if (await this.userLookup.hasActiveUser(email)) {
396
+ this.logger.log(`register/start fuer aktiven Account ignoriert (${email}).`);
397
+ await this.record("REGISTRATION_NEUTRAL_ACTIVE_USER", null, context);
398
+ return {
399
+ neutral: true
400
+ };
401
+ }
402
+ const existing = await this.repo.findByEmail(email);
403
+ if (existing) {
404
+ if (existing.expiresAt.getTime() < Date.now()) {
405
+ await this.repo.delete(existing.id);
406
+ await this.record("REGISTRATION_NEUTRAL_EXPIRED", existing.id, context);
407
+ } else if (existing.status === "PENDING_EMAIL_VERIFICATION") {
408
+ await this.regenerateOtp(existing);
409
+ await this.record("REGISTRATION_NEUTRAL_REPLAY", existing.id, context, {
410
+ status: existing.status
411
+ });
412
+ return {
413
+ neutral: true
414
+ };
415
+ } else {
416
+ if (this.resumeTokenSigner && this.resumeDelivery) {
417
+ await this.sendResumeLink(existing);
418
+ } else {
419
+ await this.regenerateOtp(existing);
420
+ }
421
+ await this.record("REGISTRATION_NEUTRAL_REPLAY", existing.id, context, {
422
+ status: existing.status
423
+ });
424
+ return {
425
+ neutral: true
426
+ };
427
+ }
428
+ }
429
+ const slug = await this.resolveSlug(input.tenantName, input.tenantSlug);
430
+ const passwordHash = await this.passwordHasher.hash(input.password);
431
+ const code = generateOtpCode();
432
+ const otpHash = hashOtpCode(code);
433
+ const now = /* @__PURE__ */ new Date();
434
+ const otpExpiresAt = new Date(now.getTime() + OTP_TTL_MINUTES * 60 * 1e3);
435
+ const expiresAt = new Date(now.getTime() + PENDING_EMAIL_TTL_HOURS * 60 * 60 * 1e3);
436
+ const pending = await this.repo.create({
437
+ tenantName: input.tenantName.trim(),
438
+ tenantSlug: slug,
439
+ salutation: nullable(input.salutation),
440
+ firstName: input.firstName.trim(),
441
+ lastName: input.lastName.trim(),
442
+ email,
443
+ passwordHash,
444
+ locale: input.locale ?? "de",
445
+ otpHash,
446
+ otpExpiresAt,
447
+ expiresAt
448
+ });
449
+ await this.deliverOtp(pending, code);
450
+ await this.record("REGISTRATION_STARTED", pending.id, context);
451
+ return {
452
+ neutral: true
453
+ };
454
+ }
455
+ async verifyOtp(emailRaw, code, context) {
456
+ const email = normalizeEmail(emailRaw);
457
+ const pending = await this.repo.findByEmail(email);
458
+ if (!pending) {
459
+ await this.record("OTP_VERIFY_FAILED", null, context, {
460
+ reason: "unknown_email"
461
+ });
462
+ throw new BadRequestException({
463
+ code: "OTP_INVALID"
464
+ });
465
+ }
466
+ if (pending.status !== "PENDING_EMAIL_VERIFICATION") {
467
+ return {
468
+ status: pending.status,
469
+ nextStep: REGISTRATION_STEP_BY_STATUS[pending.status],
470
+ pendingRegistrationId: pending.id
471
+ };
472
+ }
473
+ if (!pending.otpHash || !pending.otpExpiresAt) {
474
+ await this.record("OTP_VERIFY_FAILED", pending.id, context, {
475
+ reason: "no_otp_set"
476
+ });
477
+ throw new BadRequestException({
478
+ code: "OTP_INVALID"
479
+ });
480
+ }
481
+ if (pending.otpExpiresAt.getTime() < Date.now()) {
482
+ await this.record("OTP_VERIFY_FAILED", pending.id, context, {
483
+ reason: "expired"
484
+ });
485
+ throw new BadRequestException({
486
+ code: "OTP_EXPIRED"
487
+ });
488
+ }
489
+ const maxAttempts = resolveOtpVerifyMaxAttempts();
490
+ if (pending.otpAttemptCount >= maxAttempts) {
491
+ await this.record("OTP_VERIFY_FAILED", pending.id, context, {
492
+ reason: "locked"
493
+ });
494
+ throw new BadRequestException({
495
+ code: "OTP_LOCKED"
496
+ });
497
+ }
498
+ const attemptCount = await this.repo.incrementOtpAttemptCount(pending.id);
499
+ if (attemptCount > maxAttempts) {
500
+ await this.record("OTP_VERIFY_FAILED", pending.id, context, {
501
+ reason: "locked"
502
+ });
503
+ throw new BadRequestException({
504
+ code: "OTP_LOCKED"
505
+ });
506
+ }
507
+ if (!verifyOtpCode(pending.otpHash, code)) {
508
+ await this.record("OTP_VERIFY_FAILED", pending.id, context, {
509
+ reason: "wrong_code"
510
+ });
511
+ throw new BadRequestException({
512
+ code: "OTP_INVALID"
513
+ });
514
+ }
515
+ const now = /* @__PURE__ */ new Date();
516
+ const updated = await this.repo.update(pending.id, {
517
+ status: "EMAIL_VERIFIED",
518
+ currentStep: 3,
519
+ emailVerifiedAt: now,
520
+ otpHash: null,
521
+ otpExpiresAt: null,
522
+ expiresAt: new Date(now.getTime() + PENDING_ONBOARDING_TTL_DAYS * 24 * 60 * 60 * 1e3)
523
+ });
524
+ await this.record("OTP_VERIFIED", updated.id, context);
525
+ return {
526
+ status: updated.status,
527
+ nextStep: REGISTRATION_STEP_BY_STATUS[updated.status],
528
+ pendingRegistrationId: updated.id
529
+ };
530
+ }
531
+ async resendOtp(emailRaw, context) {
532
+ const email = normalizeEmail(emailRaw);
533
+ const pending = await this.repo.findByEmail(email);
534
+ if (!pending) {
535
+ return {
536
+ neutral: true
537
+ };
538
+ }
539
+ if (pending.status !== "PENDING_EMAIL_VERIFICATION") {
540
+ return {
541
+ neutral: true
542
+ };
543
+ }
544
+ await this.regenerateOtp(pending);
545
+ await this.record("OTP_RESEND_REQUESTED", pending.id, context);
546
+ return {
547
+ neutral: true
548
+ };
549
+ }
550
+ async regenerateOtp(pending) {
551
+ const now = /* @__PURE__ */ new Date();
552
+ const windowMs = OTP_RATE_LIMIT_WINDOW_MINUTES * 60 * 1e3;
553
+ const withinWindow = pending.lastOtpSentAt !== null && now.getTime() - pending.lastOtpSentAt.getTime() < windowMs;
554
+ if (withinWindow && pending.otpSendCount >= OTP_RATE_LIMIT_MAX_SENDS) {
555
+ this.logger.warn(`OTP-Rate-Limit erreicht fuer ${pending.email} \u2014 Send wird still verworfen.`);
556
+ return;
557
+ }
558
+ const code = generateOtpCode();
559
+ const otpHash = hashOtpCode(code);
560
+ const otpExpiresAt = new Date(now.getTime() + OTP_TTL_MINUTES * 60 * 1e3);
561
+ const otpSendCount = withinWindow ? pending.otpSendCount + 1 : 1;
562
+ await this.repo.update(pending.id, {
563
+ otpHash,
564
+ otpExpiresAt,
565
+ otpSendCount,
566
+ lastOtpSentAt: now,
567
+ // New code = fresh attempt budget. Overall throughput stays capped
568
+ // by the send rate limit above.
569
+ otpAttemptCount: 0
570
+ });
571
+ await this.deliverOtp(pending, code);
572
+ }
573
+ async deliverOtp(pending, code) {
574
+ try {
575
+ await this.otpDelivery.sendVerificationOtp({
576
+ to: pending.email,
577
+ code,
578
+ firstName: pending.firstName,
579
+ locale: pending.locale
580
+ });
581
+ } catch (error) {
582
+ const message = error instanceof Error ? error.message : String(error);
583
+ this.logger.error(`OTP-Mail an ${pending.email} fehlgeschlagen: ${message}`);
584
+ }
585
+ }
586
+ /**
587
+ * Creates a signed resume token (60 min TTL) and sends a magic link to the
588
+ * stored email. Mail send errors are logged but not escalated
589
+ * (account-enumeration protection).
590
+ */
591
+ async sendResumeLink(pending) {
592
+ if (!this.resumeTokenSigner || !this.resumeDelivery) {
593
+ return;
594
+ }
595
+ const token = await this.resumeTokenSigner.sign({
596
+ pendingRegistrationId: pending.id,
597
+ ttlMinutes: REGISTRATION_RESUME_TTL_MINUTES
598
+ });
599
+ const base = (this.resumeBaseUrl ?? "http://localhost").replace(/\/+$/, "");
600
+ const url = `${base}/login?resume=${encodeURIComponent(token)}`;
601
+ try {
602
+ await this.resumeDelivery.sendResumeEmail({
603
+ to: pending.email,
604
+ firstName: pending.firstName,
605
+ locale: pending.locale,
606
+ resumeUrl: url
607
+ });
608
+ } catch (error) {
609
+ const message = error instanceof Error ? error.message : String(error);
610
+ this.logger.error(`Resume-Mail an ${pending.email} fehlgeschlagen: ${message}`);
611
+ }
612
+ }
613
+ /**
614
+ * Resolves a resume token to the matching onboarding step. Called by the
615
+ * frontend from `/register?resume=<jwt>`. On an invalid token the method
616
+ * throws `RESUME_TOKEN_INVALID`.
617
+ *
618
+ * Also returns a public-safe snapshot of the PendingRegistration so the
619
+ * frontend can prefill completed steps with the existing data (first name /
620
+ * last name, organization name, plan selection ...).
621
+ */
622
+ async resumeWithToken(input, context) {
623
+ if (!this.resumeTokenSigner) {
624
+ throw new BadRequestException({
625
+ code: "RESUME_NOT_CONFIGURED"
626
+ });
627
+ }
628
+ let decoded;
629
+ try {
630
+ decoded = await this.resumeTokenSigner.verify(input.token);
631
+ } catch {
632
+ throw new BadRequestException({
633
+ code: "RESUME_TOKEN_INVALID"
634
+ });
635
+ }
636
+ const pending = await this.repo.findById(decoded.pendingRegistrationId);
637
+ if (!pending) {
638
+ throw new BadRequestException({
639
+ code: "RESUME_TOKEN_INVALID"
640
+ });
641
+ }
642
+ if (pending.expiresAt.getTime() < Date.now()) {
643
+ throw new BadRequestException({
644
+ code: "PENDING_REGISTRATION_EXPIRED"
645
+ });
646
+ }
647
+ await this.record("REGISTRATION_NEUTRAL_REPLAY", pending.id, context, {
648
+ via: "resume_token",
649
+ status: pending.status
650
+ });
651
+ return {
652
+ pendingRegistrationId: pending.id,
653
+ status: pending.status,
654
+ nextStep: REGISTRATION_STEP_BY_STATUS[pending.status],
655
+ snapshot: toSnapshot(pending)
656
+ };
657
+ }
658
+ /**
659
+ * Signs a short-lived resume token for an existing PendingRegistration —
660
+ * the login endpoint uses this to send the user into the wizard with
661
+ * prefill after successful password verification.
662
+ */
663
+ async signResumeToken(pendingRegistrationId) {
664
+ if (!this.resumeTokenSigner) return null;
665
+ return this.resumeTokenSigner.sign({
666
+ pendingRegistrationId
667
+ });
668
+ }
669
+ /* ─── Configurator (Step 3) ────────────────────────────────────────── */
670
+ /** Returns the (app-specific) configurator catalog. */
671
+ async getConfiguratorCatalog() {
672
+ if (!this.configuratorLookup) {
673
+ throw new BadRequestException({
674
+ code: "CONFIGURATOR_NOT_CONFIGURED"
675
+ });
676
+ }
677
+ return this.configuratorLookup.getCatalog();
678
+ }
679
+ /**
680
+ * Saves the configurator selection (model + cycle + promo) and returns the
681
+ * full price breakdown.
682
+ *
683
+ * Sets:
684
+ * - `status = PLAN_SELECTED`
685
+ * - `currentStep = 4`
686
+ * - `selectedPlanId = <planId from the chosen model>`
687
+ * - `configJson` / `billingCycle` / `appliedPromoCode`
688
+ * - `expiresAt = now + ONBOARDING_TTL`
689
+ */
690
+ async saveConfiguration(input, context) {
691
+ if (!this.configuratorLookup) {
692
+ throw new BadRequestException({
693
+ code: "CONFIGURATOR_NOT_CONFIGURED"
694
+ });
695
+ }
696
+ const pending = await this.repo.findById(input.pendingRegistrationId);
697
+ if (!pending) {
698
+ throw new NotFoundException({
699
+ code: "PENDING_REGISTRATION_NOT_FOUND"
700
+ });
701
+ }
702
+ if (pending.expiresAt.getTime() < Date.now()) {
703
+ throw new BadRequestException({
704
+ code: "PENDING_REGISTRATION_EXPIRED"
705
+ });
706
+ }
707
+ if (pending.status !== "EMAIL_VERIFIED" && pending.status !== "PLAN_SELECTED" && pending.status !== "CHECKOUT_STARTED") {
708
+ throw new BadRequestException({
709
+ code: "INVALID_REGISTRATION_STATE"
710
+ });
711
+ }
712
+ const catalog = await this.configuratorLookup.getCatalog();
713
+ const model = catalog.models.find((m) => m.id === input.selection.modelId);
714
+ if (!model) {
715
+ throw new BadRequestException({
716
+ code: "MODEL_NOT_AVAILABLE"
717
+ });
718
+ }
719
+ if (input.selection.businessTypeVersionId && this.businessTypeLookup) {
720
+ const view = await this.businessTypeLookup.findPublishedVersion(input.selection.businessTypeVersionId);
721
+ if (!view) {
722
+ throw new BadRequestException({
723
+ code: "BUSINESS_TYPE_NOT_AVAILABLE"
724
+ });
725
+ }
726
+ }
727
+ let promoEval;
728
+ if (input.selection.appliedPromoCode && this.promoPreview) {
729
+ const dryRun = computeBreakdown(catalog, {
730
+ ...input.selection,
731
+ appliedPromoCode: null
732
+ });
733
+ const previewResult = await this.promoPreview.preview({
734
+ code: input.selection.appliedPromoCode,
735
+ planId: model.planId,
736
+ billingCycle: input.selection.billingCycle,
737
+ subtotalGross: dryRun.totalGross,
738
+ email: pending.email
739
+ });
740
+ if (previewResult.valid && previewResult.discountAmount && previewResult.percent !== void 0 && previewResult.label) {
741
+ promoEval = {
742
+ discountAmount: previewResult.discountAmount,
743
+ percent: previewResult.percent,
744
+ label: previewResult.label
745
+ };
746
+ }
747
+ }
748
+ const breakdown = computeBreakdown(catalog, input.selection, promoEval);
749
+ if (promoEval) {
750
+ breakdown.appliedPromo = {
751
+ code: input.selection.appliedPromoCode,
752
+ label: promoEval.label,
753
+ percent: promoEval.percent
754
+ };
755
+ }
756
+ const now = /* @__PURE__ */ new Date();
757
+ const expiresAt = new Date(now.getTime() + PENDING_ONBOARDING_TTL_DAYS * 24 * 60 * 60 * 1e3);
758
+ const updated = await this.repo.update(pending.id, {
759
+ status: "PLAN_SELECTED",
760
+ currentStep: 4,
761
+ selectedPlanId: model.planId,
762
+ configJson: input.selection,
763
+ billingCycle: input.selection.billingCycle,
764
+ appliedPromoCode: input.selection.appliedPromoCode ?? null,
765
+ expiresAt
766
+ });
767
+ await this.record("PLAN_SELECTED", updated.id, context, {
768
+ modelId: model.id,
769
+ planId: model.planId,
770
+ cycle: input.selection.billingCycle,
771
+ hasPromo: !!promoEval
772
+ });
773
+ return {
774
+ pendingRegistrationId: updated.id,
775
+ status: updated.status,
776
+ nextStep: REGISTRATION_STEP_BY_STATUS[updated.status],
777
+ selection: input.selection,
778
+ breakdown
779
+ };
780
+ }
781
+ /**
782
+ * Validates a promo code against the currently stored configuration and
783
+ * returns a new breakdown. Does NOT modify the pending — for live preview
784
+ * in the frontend. If `null` is passed as `code`, the promo effect is
785
+ * removed.
786
+ */
787
+ async previewConfigPromo(pendingRegistrationId, code) {
788
+ if (!this.configuratorLookup) {
789
+ throw new BadRequestException({
790
+ code: "CONFIGURATOR_NOT_CONFIGURED"
791
+ });
792
+ }
793
+ const pending = await this.repo.findById(pendingRegistrationId);
794
+ if (!pending) {
795
+ throw new NotFoundException({
796
+ code: "PENDING_REGISTRATION_NOT_FOUND"
797
+ });
798
+ }
799
+ const config = pending.configJson;
800
+ if (!config) {
801
+ throw new BadRequestException({
802
+ code: "CONFIG_NOT_SAVED"
803
+ });
804
+ }
805
+ const catalog = await this.configuratorLookup.getCatalog();
806
+ const model = catalog.models.find((m) => m.id === config.modelId);
807
+ if (!model) {
808
+ throw new BadRequestException({
809
+ code: "MODEL_NOT_AVAILABLE"
810
+ });
811
+ }
812
+ let promoEval;
813
+ if (code && this.promoPreview) {
814
+ const dryRun = computeBreakdown(catalog, {
815
+ ...config,
816
+ appliedPromoCode: null
817
+ });
818
+ const previewResult = await this.promoPreview.preview({
819
+ code,
820
+ planId: model.planId,
821
+ billingCycle: config.billingCycle,
822
+ subtotalGross: dryRun.totalGross,
823
+ email: pending.email
824
+ });
825
+ if (previewResult.valid && previewResult.discountAmount && previewResult.percent !== void 0 && previewResult.label) {
826
+ promoEval = {
827
+ discountAmount: previewResult.discountAmount,
828
+ percent: previewResult.percent,
829
+ label: previewResult.label
830
+ };
831
+ }
832
+ }
833
+ const breakdown = computeBreakdown(catalog, config, promoEval);
834
+ if (promoEval && code) {
835
+ breakdown.appliedPromo = {
836
+ code,
837
+ label: promoEval.label,
838
+ percent: promoEval.percent
839
+ };
840
+ }
841
+ return breakdown;
842
+ }
843
+ /**
844
+ * Helper for step 3 resume: the frontend needs the current breakdown when
845
+ * the pending already has a configuration. Pure recomputation against the
846
+ * catalog (without promo validation, since promos should be freshly checked
847
+ * anyway).
848
+ */
849
+ async getCurrentBreakdown(pendingRegistrationId) {
850
+ if (!this.configuratorLookup) return null;
851
+ const pending = await this.repo.findById(pendingRegistrationId);
852
+ if (!pending || !pending.configJson) return null;
853
+ const catalog = await this.configuratorLookup.getCatalog();
854
+ return computeBreakdown(catalog, pending.configJson);
855
+ }
856
+ async resolveSlug(tenantName, requested) {
857
+ const requestedSlug = requested?.trim().toLowerCase() ?? "";
858
+ const base = requestedSlug.length > 0 ? requestedSlug : slugify(tenantName);
859
+ if (await this.slugCheck.isSlugAvailable(base)) {
860
+ return base;
861
+ }
862
+ for (let i = 2; i < 1e3; i++) {
863
+ const candidate = `${base}-${i}`;
864
+ if (await this.slugCheck.isSlugAvailable(candidate)) {
865
+ return candidate;
866
+ }
867
+ }
868
+ throw new Error(`Konnte keinen freien Slug fuer "${tenantName}" finden.`);
869
+ }
870
+ };
871
+ PendingRegistrationService = _ts_decorate([
872
+ Injectable(),
873
+ _ts_param(0, Inject(PENDING_REGISTRATION_REPOSITORY_TOKEN)),
874
+ _ts_param(1, Inject(REGISTRATION_OTP_DELIVERY_TOKEN)),
875
+ _ts_param(2, Inject(USER_ACCOUNT_LOOKUP_TOKEN)),
876
+ _ts_param(3, Inject(SLUG_AVAILABILITY_CHECK_TOKEN)),
877
+ _ts_param(4, Inject(PASSWORD_HASHER_TOKEN)),
878
+ _ts_param(5, Inject(PLAN_CATALOG_LOOKUP_TOKEN)),
879
+ _ts_param(6, Inject(PAYMENT_PROVIDER_TOKEN)),
880
+ _ts_param(7, Inject(PAYMENT_EVENT_LOG_TOKEN)),
881
+ _ts_param(8, Inject(ACTIVATION_ORCHESTRATOR_TOKEN)),
882
+ _ts_param(9, Inject(REGISTRATION_AUDIT_LOGGER_TOKEN)),
883
+ _ts_param(10, Optional()),
884
+ _ts_param(10, Inject(REGISTRATION_RESUME_TOKEN_SIGNER_TOKEN)),
885
+ _ts_param(11, Optional()),
886
+ _ts_param(11, Inject(REGISTRATION_RESUME_DELIVERY_TOKEN)),
887
+ _ts_param(12, Optional()),
888
+ _ts_param(12, Inject(REGISTRATION_RESUME_BASE_URL_TOKEN)),
889
+ _ts_param(13, Optional()),
890
+ _ts_param(13, Inject(REGISTRATION_CONFIGURATOR_LOOKUP_TOKEN)),
891
+ _ts_param(14, Optional()),
892
+ _ts_param(14, Inject(REGISTRATION_PROMO_PREVIEW_TOKEN)),
893
+ _ts_param(15, Optional()),
894
+ _ts_param(15, Inject(REGISTRATION_BUSINESS_TYPE_LOOKUP_TOKEN)),
895
+ _ts_metadata("design:type", Function),
896
+ _ts_metadata("design:paramtypes", [
897
+ typeof PendingRegistrationRepository === "undefined" ? Object : PendingRegistrationRepository,
898
+ typeof RegistrationOtpDelivery === "undefined" ? Object : RegistrationOtpDelivery,
899
+ typeof UserAccountLookup === "undefined" ? Object : UserAccountLookup,
900
+ typeof SlugAvailabilityCheck === "undefined" ? Object : SlugAvailabilityCheck,
901
+ typeof PasswordHasher === "undefined" ? Object : PasswordHasher,
902
+ typeof PlanCatalogLookup === "undefined" ? Object : PlanCatalogLookup,
903
+ typeof PaymentProvider === "undefined" ? Object : PaymentProvider,
904
+ typeof PaymentEventLog === "undefined" ? Object : PaymentEventLog,
905
+ typeof ActivationOrchestrator === "undefined" ? Object : ActivationOrchestrator,
906
+ typeof RegistrationAuditLogger === "undefined" ? Object : RegistrationAuditLogger,
907
+ typeof RegistrationResumeTokenSigner === "undefined" ? Object : RegistrationResumeTokenSigner,
908
+ typeof RegistrationResumeDelivery === "undefined" ? Object : RegistrationResumeDelivery,
909
+ String,
910
+ typeof RegistrationConfiguratorLookup === "undefined" ? Object : RegistrationConfiguratorLookup,
911
+ typeof RegistrationPromoPreview === "undefined" ? Object : RegistrationPromoPreview,
912
+ typeof RegistrationBusinessTypeLookup === "undefined" ? Object : RegistrationBusinessTypeLookup
913
+ ])
914
+ ], PendingRegistrationService);
915
+ function normalizeEmail(value) {
916
+ return value.trim().toLowerCase();
917
+ }
918
+ __name(normalizeEmail, "normalizeEmail");
919
+ function resolveOtpVerifyMaxAttempts() {
920
+ const raw = process.env.SAAS_PLATFORM_OTP_VERIFY_MAX_ATTEMPTS;
921
+ if (!raw) return OTP_VERIFY_MAX_ATTEMPTS;
922
+ const parsed = Number.parseInt(raw, 10);
923
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : OTP_VERIFY_MAX_ATTEMPTS;
924
+ }
925
+ __name(resolveOtpVerifyMaxAttempts, "resolveOtpVerifyMaxAttempts");
926
+ function toSnapshot(pending) {
927
+ return {
928
+ tenantName: pending.tenantName,
929
+ tenantSlug: pending.tenantSlug,
930
+ salutation: pending.salutation,
931
+ firstName: pending.firstName,
932
+ lastName: pending.lastName,
933
+ email: pending.email,
934
+ locale: pending.locale,
935
+ status: pending.status,
936
+ currentStep: pending.currentStep,
937
+ emailVerifiedAt: pending.emailVerifiedAt?.toISOString() ?? null,
938
+ selectedPlanId: pending.selectedPlanId,
939
+ config: pending.configJson,
940
+ billingCycle: pending.billingCycle,
941
+ appliedPromoCode: pending.appliedPromoCode,
942
+ checkoutSessionId: pending.checkoutSessionId
943
+ };
944
+ }
945
+ __name(toSnapshot, "toSnapshot");
946
+ function nullable(value) {
947
+ const trimmed = value?.trim();
948
+ return trimmed && trimmed.length > 0 ? trimmed : null;
949
+ }
950
+ __name(nullable, "nullable");
951
+
952
+ // src/registration/cleanup.cron.ts
953
+ import { Injectable as Injectable2, Logger as Logger2 } from "@nestjs/common";
954
+ import { Cron, CronExpression } from "@nestjs/schedule";
955
+ function _ts_decorate2(decorators, target, key, desc) {
956
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
957
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
958
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
959
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
960
+ }
961
+ __name(_ts_decorate2, "_ts_decorate");
962
+ function _ts_metadata2(k, v) {
963
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
964
+ }
965
+ __name(_ts_metadata2, "_ts_metadata");
966
+ var RegistrationCleanupCron = class _RegistrationCleanupCron {
967
+ static {
968
+ __name(this, "RegistrationCleanupCron");
969
+ }
970
+ service;
971
+ logger = new Logger2(_RegistrationCleanupCron.name);
972
+ constructor(service) {
973
+ this.service = service;
974
+ }
975
+ async runDaily() {
976
+ await this.runCleanup();
977
+ }
978
+ /**
979
+ * Public API: can be triggered manually from an admin endpoint /
980
+ * `/admin/registration/run-cleanup` (same pattern as
981
+ * `PromoCodeExpirer.run` in the consuming apps).
982
+ */
983
+ async runCleanup(now = /* @__PURE__ */ new Date(), batchSize = 500) {
984
+ let totalDeleted = 0;
985
+ let iterations = 0;
986
+ const MAX_ITERATIONS = 50;
987
+ while (iterations < MAX_ITERATIONS) {
988
+ const result = await this.service.runCleanup(now, batchSize);
989
+ totalDeleted += result.deleted;
990
+ iterations++;
991
+ if (!result.moreAvailable) {
992
+ break;
993
+ }
994
+ }
995
+ if (totalDeleted > 0) {
996
+ this.logger.log(`PendingRegistration-Cleanup abgeschlossen: ${totalDeleted} geloescht in ${iterations} Batches.`);
997
+ }
998
+ }
999
+ };
1000
+ _ts_decorate2([
1001
+ Cron("15 4 * * *", {
1002
+ timeZone: "Europe/Berlin"
1003
+ }),
1004
+ _ts_metadata2("design:type", Function),
1005
+ _ts_metadata2("design:paramtypes", []),
1006
+ _ts_metadata2("design:returntype", Promise)
1007
+ ], RegistrationCleanupCron.prototype, "runDaily", null);
1008
+ RegistrationCleanupCron = _ts_decorate2([
1009
+ Injectable2(),
1010
+ _ts_metadata2("design:type", Function),
1011
+ _ts_metadata2("design:paramtypes", [
1012
+ typeof PendingRegistrationService === "undefined" ? Object : PendingRegistrationService
1013
+ ])
1014
+ ], RegistrationCleanupCron);
1015
+
1016
+ // src/registration/ip-rate-limit.guard.ts
1017
+ import { HttpException, HttpStatus, Injectable as Injectable3 } from "@nestjs/common";
1018
+ function _ts_decorate3(decorators, target, key, desc) {
1019
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1020
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1021
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1022
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1023
+ }
1024
+ __name(_ts_decorate3, "_ts_decorate");
1025
+ function _ts_metadata3(k, v) {
1026
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1027
+ }
1028
+ __name(_ts_metadata3, "_ts_metadata");
1029
+ var BaseIpRateLimitGuard = class {
1030
+ static {
1031
+ __name(this, "BaseIpRateLimitGuard");
1032
+ }
1033
+ options;
1034
+ buckets = /* @__PURE__ */ new Map();
1035
+ constructor(options) {
1036
+ this.options = options;
1037
+ }
1038
+ canActivate(context) {
1039
+ if (process.env.SAAS_PLATFORM_SKIP_RATE_LIMITS === "1" && process.env.NODE_ENV !== "production") {
1040
+ return true;
1041
+ }
1042
+ const req = context.switchToHttp().getRequest();
1043
+ const key = ipFingerprint(req);
1044
+ if (this.exceeded(key)) {
1045
+ throw new HttpException({
1046
+ code: "RATE_LIMITED",
1047
+ reason: this.options.name,
1048
+ retryAfterSeconds: Math.ceil(this.options.windowMs / 1e3)
1049
+ }, HttpStatus.TOO_MANY_REQUESTS);
1050
+ }
1051
+ return true;
1052
+ }
1053
+ exceeded(key) {
1054
+ const now = Date.now();
1055
+ const bucket = this.buckets.get(key);
1056
+ if (!bucket || now - bucket.windowStart > this.options.windowMs) {
1057
+ this.buckets.set(key, {
1058
+ count: 1,
1059
+ windowStart: now
1060
+ });
1061
+ return false;
1062
+ }
1063
+ bucket.count += 1;
1064
+ return bucket.count > this.options.limit;
1065
+ }
1066
+ };
1067
+ BaseIpRateLimitGuard = _ts_decorate3([
1068
+ Injectable3(),
1069
+ _ts_metadata3("design:type", Function),
1070
+ _ts_metadata3("design:paramtypes", [
1071
+ Object
1072
+ ])
1073
+ ], BaseIpRateLimitGuard);
1074
+ function ipFingerprint(req) {
1075
+ const fwd = req.headers["x-forwarded-for"];
1076
+ const first = Array.isArray(fwd) ? fwd[0] : fwd?.split(",")[0]?.trim();
1077
+ return first || req.ip || "unknown";
1078
+ }
1079
+ __name(ipFingerprint, "ipFingerprint");
1080
+
1081
+ // src/registration/module.ts
1082
+ import { Module } from "@nestjs/common";
1083
+ function _ts_decorate4(decorators, target, key, desc) {
1084
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1085
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1086
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1087
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1088
+ }
1089
+ __name(_ts_decorate4, "_ts_decorate");
1090
+ var RegistrationModule = class _RegistrationModule {
1091
+ static {
1092
+ __name(this, "RegistrationModule");
1093
+ }
1094
+ static forRoot(options) {
1095
+ const providers = [
1096
+ ...options.extraProviders ?? [],
1097
+ asProvider(PENDING_REGISTRATION_REPOSITORY_TOKEN, options.pendingRegistrationRepository),
1098
+ asProvider(REGISTRATION_OTP_DELIVERY_TOKEN, options.otpDelivery),
1099
+ asProvider(USER_ACCOUNT_LOOKUP_TOKEN, options.userAccountLookup),
1100
+ asProvider(SLUG_AVAILABILITY_CHECK_TOKEN, options.slugAvailabilityCheck),
1101
+ asProvider(PASSWORD_HASHER_TOKEN, options.passwordHasher),
1102
+ asProvider(PLAN_CATALOG_LOOKUP_TOKEN, options.planCatalogLookup),
1103
+ asProvider(PAYMENT_PROVIDER_TOKEN, options.paymentProvider),
1104
+ asProvider(PAYMENT_EVENT_LOG_TOKEN, options.paymentEventLog),
1105
+ asProvider(ACTIVATION_ORCHESTRATOR_TOKEN, options.activationOrchestrator),
1106
+ asProvider(REGISTRATION_AUDIT_LOGGER_TOKEN, options.auditLogger),
1107
+ PendingRegistrationService
1108
+ ];
1109
+ if (options.resumeTokenSigner) {
1110
+ providers.push(asProvider(REGISTRATION_RESUME_TOKEN_SIGNER_TOKEN, options.resumeTokenSigner));
1111
+ }
1112
+ if (options.resumeDelivery) {
1113
+ providers.push(asProvider(REGISTRATION_RESUME_DELIVERY_TOKEN, options.resumeDelivery));
1114
+ }
1115
+ if (options.resumeBaseUrl !== void 0) {
1116
+ providers.push({
1117
+ provide: REGISTRATION_RESUME_BASE_URL_TOKEN,
1118
+ useValue: options.resumeBaseUrl
1119
+ });
1120
+ }
1121
+ if (options.configuratorLookup) {
1122
+ providers.push(asProvider(REGISTRATION_CONFIGURATOR_LOOKUP_TOKEN, options.configuratorLookup));
1123
+ }
1124
+ if (options.promoPreview) {
1125
+ providers.push(asProvider(REGISTRATION_PROMO_PREVIEW_TOKEN, options.promoPreview));
1126
+ }
1127
+ if (options.businessTypeLookup) {
1128
+ providers.push(asProvider(REGISTRATION_BUSINESS_TYPE_LOOKUP_TOKEN, options.businessTypeLookup));
1129
+ }
1130
+ const includeCleanupCron = options.includeCleanupCron ?? true;
1131
+ if (includeCleanupCron) {
1132
+ providers.push(RegistrationCleanupCron);
1133
+ }
1134
+ return {
1135
+ module: _RegistrationModule,
1136
+ global: options.global ?? false,
1137
+ imports: options.imports ?? [],
1138
+ providers,
1139
+ exports: includeCleanupCron ? [
1140
+ PendingRegistrationService,
1141
+ RegistrationCleanupCron
1142
+ ] : [
1143
+ PendingRegistrationService
1144
+ ]
1145
+ };
1146
+ }
1147
+ };
1148
+ RegistrationModule = _ts_decorate4([
1149
+ Module({})
1150
+ ], RegistrationModule);
1151
+
1152
+ // src/registration/dto/register-start.dto.ts
1153
+ import { Equals, IsEmail, IsIn, IsOptional, IsString, Matches, MaxLength, MinLength } from "class-validator";
1154
+ function _ts_decorate5(decorators, target, key, desc) {
1155
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1156
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1157
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1158
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1159
+ }
1160
+ __name(_ts_decorate5, "_ts_decorate");
1161
+ function _ts_metadata4(k, v) {
1162
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1163
+ }
1164
+ __name(_ts_metadata4, "_ts_metadata");
1165
+ var RegisterStartDto = class {
1166
+ static {
1167
+ __name(this, "RegisterStartDto");
1168
+ }
1169
+ /** Association/tenant name (required). */
1170
+ tenantName;
1171
+ /** Optional slug. If left empty, it is generated from `tenantName`. */
1172
+ tenantSlug;
1173
+ /** Salutation of the administrator (optional). */
1174
+ salutation;
1175
+ firstName;
1176
+ lastName;
1177
+ email;
1178
+ password;
1179
+ /** Terms / privacy / data processing — mandatory acceptance (must be `true`). */
1180
+ termsAccepted;
1181
+ locale;
1182
+ };
1183
+ _ts_decorate5([
1184
+ IsString(),
1185
+ MinLength(2),
1186
+ MaxLength(120),
1187
+ _ts_metadata4("design:type", String)
1188
+ ], RegisterStartDto.prototype, "tenantName", void 0);
1189
+ _ts_decorate5([
1190
+ IsOptional(),
1191
+ IsString(),
1192
+ Matches(/^[a-z0-9-]+$/, {
1193
+ message: "tenantSlug darf nur Kleinbuchstaben, Ziffern und Bindestriche enthalten."
1194
+ }),
1195
+ MaxLength(60),
1196
+ _ts_metadata4("design:type", String)
1197
+ ], RegisterStartDto.prototype, "tenantSlug", void 0);
1198
+ _ts_decorate5([
1199
+ IsOptional(),
1200
+ IsString(),
1201
+ MaxLength(40),
1202
+ _ts_metadata4("design:type", String)
1203
+ ], RegisterStartDto.prototype, "salutation", void 0);
1204
+ _ts_decorate5([
1205
+ IsString(),
1206
+ MinLength(1),
1207
+ MaxLength(60),
1208
+ _ts_metadata4("design:type", String)
1209
+ ], RegisterStartDto.prototype, "firstName", void 0);
1210
+ _ts_decorate5([
1211
+ IsString(),
1212
+ MinLength(1),
1213
+ MaxLength(60),
1214
+ _ts_metadata4("design:type", String)
1215
+ ], RegisterStartDto.prototype, "lastName", void 0);
1216
+ _ts_decorate5([
1217
+ IsEmail(),
1218
+ MaxLength(160),
1219
+ _ts_metadata4("design:type", String)
1220
+ ], RegisterStartDto.prototype, "email", void 0);
1221
+ _ts_decorate5([
1222
+ IsString(),
1223
+ MinLength(8),
1224
+ MaxLength(160),
1225
+ _ts_metadata4("design:type", String)
1226
+ ], RegisterStartDto.prototype, "password", void 0);
1227
+ _ts_decorate5([
1228
+ Equals(true, {
1229
+ message: "AGB, Datenschutzerklaerung und Auftragsverarbeitung muessen zugestimmt werden."
1230
+ }),
1231
+ _ts_metadata4("design:type", Boolean)
1232
+ ], RegisterStartDto.prototype, "termsAccepted", void 0);
1233
+ _ts_decorate5([
1234
+ IsOptional(),
1235
+ IsIn([
1236
+ "de",
1237
+ "tr"
1238
+ ]),
1239
+ _ts_metadata4("design:type", String)
1240
+ ], RegisterStartDto.prototype, "locale", void 0);
1241
+
1242
+ // src/registration/dto/verify-registration-otp.dto.ts
1243
+ import { IsEmail as IsEmail2, IsString as IsString2, Matches as Matches2 } from "class-validator";
1244
+ function _ts_decorate6(decorators, target, key, desc) {
1245
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1246
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1247
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1248
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1249
+ }
1250
+ __name(_ts_decorate6, "_ts_decorate");
1251
+ function _ts_metadata5(k, v) {
1252
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1253
+ }
1254
+ __name(_ts_metadata5, "_ts_metadata");
1255
+ var VerifyRegistrationOtpDto = class {
1256
+ static {
1257
+ __name(this, "VerifyRegistrationOtpDto");
1258
+ }
1259
+ email;
1260
+ otp;
1261
+ };
1262
+ _ts_decorate6([
1263
+ IsEmail2(),
1264
+ _ts_metadata5("design:type", String)
1265
+ ], VerifyRegistrationOtpDto.prototype, "email", void 0);
1266
+ _ts_decorate6([
1267
+ IsString2(),
1268
+ Matches2(/^\d{6}$/, {
1269
+ message: "OTP muss 6 Ziffern sein."
1270
+ }),
1271
+ _ts_metadata5("design:type", String)
1272
+ ], VerifyRegistrationOtpDto.prototype, "otp", void 0);
1273
+
1274
+ // src/registration/dto/resend-registration-otp.dto.ts
1275
+ import { IsEmail as IsEmail3 } from "class-validator";
1276
+ function _ts_decorate7(decorators, target, key, desc) {
1277
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1278
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1279
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1280
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1281
+ }
1282
+ __name(_ts_decorate7, "_ts_decorate");
1283
+ function _ts_metadata6(k, v) {
1284
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1285
+ }
1286
+ __name(_ts_metadata6, "_ts_metadata");
1287
+ var ResendRegistrationOtpDto = class {
1288
+ static {
1289
+ __name(this, "ResendRegistrationOtpDto");
1290
+ }
1291
+ email;
1292
+ };
1293
+ _ts_decorate7([
1294
+ IsEmail3(),
1295
+ _ts_metadata6("design:type", String)
1296
+ ], ResendRegistrationOtpDto.prototype, "email", void 0);
1297
+
1298
+ // src/registration/dto/select-plan.dto.ts
1299
+ import { IsString as IsString3, MaxLength as MaxLength2, MinLength as MinLength2 } from "class-validator";
1300
+ function _ts_decorate8(decorators, target, key, desc) {
1301
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1302
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1303
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1304
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1305
+ }
1306
+ __name(_ts_decorate8, "_ts_decorate");
1307
+ function _ts_metadata7(k, v) {
1308
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1309
+ }
1310
+ __name(_ts_metadata7, "_ts_metadata");
1311
+ var SelectRegistrationPlanDto = class {
1312
+ static {
1313
+ __name(this, "SelectRegistrationPlanDto");
1314
+ }
1315
+ pendingRegistrationId;
1316
+ planId;
1317
+ };
1318
+ _ts_decorate8([
1319
+ IsString3(),
1320
+ MinLength2(1),
1321
+ MaxLength2(80),
1322
+ _ts_metadata7("design:type", String)
1323
+ ], SelectRegistrationPlanDto.prototype, "pendingRegistrationId", void 0);
1324
+ _ts_decorate8([
1325
+ IsString3(),
1326
+ MinLength2(1),
1327
+ MaxLength2(40),
1328
+ _ts_metadata7("design:type", String)
1329
+ ], SelectRegistrationPlanDto.prototype, "planId", void 0);
1330
+
1331
+ // src/registration/dto/start-checkout.dto.ts
1332
+ import { IsString as IsString4, IsUrl, MaxLength as MaxLength3, MinLength as MinLength3 } from "class-validator";
1333
+ function _ts_decorate9(decorators, target, key, desc) {
1334
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1335
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1336
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1337
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1338
+ }
1339
+ __name(_ts_decorate9, "_ts_decorate");
1340
+ function _ts_metadata8(k, v) {
1341
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1342
+ }
1343
+ __name(_ts_metadata8, "_ts_metadata");
1344
+ var StartRegistrationCheckoutDto = class {
1345
+ static {
1346
+ __name(this, "StartRegistrationCheckoutDto");
1347
+ }
1348
+ pendingRegistrationId;
1349
+ successUrl;
1350
+ cancelUrl;
1351
+ };
1352
+ _ts_decorate9([
1353
+ IsString4(),
1354
+ MinLength3(1),
1355
+ MaxLength3(80),
1356
+ _ts_metadata8("design:type", String)
1357
+ ], StartRegistrationCheckoutDto.prototype, "pendingRegistrationId", void 0);
1358
+ _ts_decorate9([
1359
+ IsUrl({
1360
+ require_tld: false
1361
+ }),
1362
+ MaxLength3(500),
1363
+ _ts_metadata8("design:type", String)
1364
+ ], StartRegistrationCheckoutDto.prototype, "successUrl", void 0);
1365
+ _ts_decorate9([
1366
+ IsUrl({
1367
+ require_tld: false
1368
+ }),
1369
+ MaxLength3(500),
1370
+ _ts_metadata8("design:type", String)
1371
+ ], StartRegistrationCheckoutDto.prototype, "cancelUrl", void 0);
1372
+
1373
+ // src/registration/dto/payment-webhook.dto.ts
1374
+ import { IsIn as IsIn2, IsObject, IsOptional as IsOptional2, IsString as IsString5, MaxLength as MaxLength4, MinLength as MinLength4 } from "class-validator";
1375
+ function _ts_decorate10(decorators, target, key, desc) {
1376
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1377
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1378
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1379
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1380
+ }
1381
+ __name(_ts_decorate10, "_ts_decorate");
1382
+ function _ts_metadata9(k, v) {
1383
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1384
+ }
1385
+ __name(_ts_metadata9, "_ts_metadata");
1386
+ var PaymentWebhookDto = class {
1387
+ static {
1388
+ __name(this, "PaymentWebhookDto");
1389
+ }
1390
+ eventId;
1391
+ sessionId;
1392
+ provider;
1393
+ status;
1394
+ payload;
1395
+ };
1396
+ _ts_decorate10([
1397
+ IsString5(),
1398
+ MinLength4(1),
1399
+ MaxLength4(120),
1400
+ _ts_metadata9("design:type", String)
1401
+ ], PaymentWebhookDto.prototype, "eventId", void 0);
1402
+ _ts_decorate10([
1403
+ IsOptional2(),
1404
+ IsString5(),
1405
+ MaxLength4(120),
1406
+ _ts_metadata9("design:type", String)
1407
+ ], PaymentWebhookDto.prototype, "sessionId", void 0);
1408
+ _ts_decorate10([
1409
+ IsString5(),
1410
+ MaxLength4(40),
1411
+ _ts_metadata9("design:type", String)
1412
+ ], PaymentWebhookDto.prototype, "provider", void 0);
1413
+ _ts_decorate10([
1414
+ IsIn2([
1415
+ "SUCCEEDED",
1416
+ "FAILED"
1417
+ ]),
1418
+ _ts_metadata9("design:type", String)
1419
+ ], PaymentWebhookDto.prototype, "status", void 0);
1420
+ _ts_decorate10([
1421
+ IsOptional2(),
1422
+ IsObject(),
1423
+ _ts_metadata9("design:type", typeof Record === "undefined" ? Object : Record)
1424
+ ], PaymentWebhookDto.prototype, "payload", void 0);
1425
+
1426
+ // src/registration/dto/continue-registration.dto.ts
1427
+ import { IsString as IsString6, MaxLength as MaxLength5, MinLength as MinLength5 } from "class-validator";
1428
+ function _ts_decorate11(decorators, target, key, desc) {
1429
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1430
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1431
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1432
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1433
+ }
1434
+ __name(_ts_decorate11, "_ts_decorate");
1435
+ function _ts_metadata10(k, v) {
1436
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1437
+ }
1438
+ __name(_ts_metadata10, "_ts_metadata");
1439
+ var ContinueRegistrationDto = class {
1440
+ static {
1441
+ __name(this, "ContinueRegistrationDto");
1442
+ }
1443
+ token;
1444
+ };
1445
+ _ts_decorate11([
1446
+ IsString6(),
1447
+ MinLength5(10),
1448
+ MaxLength5(2e3),
1449
+ _ts_metadata10("design:type", String)
1450
+ ], ContinueRegistrationDto.prototype, "token", void 0);
1451
+
1452
+ // src/registration/dto/save-configuration.dto.ts
1453
+ import { IsIn as IsIn3, IsOptional as IsOptional3, IsString as IsString7, IsUUID, MaxLength as MaxLength6, MinLength as MinLength6, ValidateIf, ValidateNested } from "class-validator";
1454
+ import { Type } from "class-transformer";
1455
+ function _ts_decorate12(decorators, target, key, desc) {
1456
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1457
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1458
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1459
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1460
+ }
1461
+ __name(_ts_decorate12, "_ts_decorate");
1462
+ function _ts_metadata11(k, v) {
1463
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1464
+ }
1465
+ __name(_ts_metadata11, "_ts_metadata");
1466
+ var SaveRegistrationConfigSelectionDto = class {
1467
+ static {
1468
+ __name(this, "SaveRegistrationConfigSelectionDto");
1469
+ }
1470
+ modelId;
1471
+ billingCycle;
1472
+ appliedPromoCode;
1473
+ /**
1474
+ * SPEC_V2 §11.1 M5.3 — optional business-type choice. UUID of a
1475
+ * **published** BusinessTypeVersion. The service validates existence +
1476
+ * live status against `RegistrationBusinessTypeLookup`. `null` = no
1477
+ * choice (legacy Plan-only path).
1478
+ */
1479
+ businessTypeVersionId;
1480
+ /**
1481
+ * METAMODELL §17a — Bundle consistency. ID of the `CheckoutOffer` that was
1482
+ * created on the pricing page (`<app>/register?offer=<id>`). Passed
1483
+ * through unchanged in `configJson`; the ActivationOrchestrator freezes it
1484
+ * as `packageSnapshot` when the subscription is created and sets the offer
1485
+ * to `consumed`. `null` = direct registration without a website offer.
1486
+ */
1487
+ offerId;
1488
+ };
1489
+ _ts_decorate12([
1490
+ IsString7(),
1491
+ MinLength6(1),
1492
+ MaxLength6(40),
1493
+ _ts_metadata11("design:type", String)
1494
+ ], SaveRegistrationConfigSelectionDto.prototype, "modelId", void 0);
1495
+ _ts_decorate12([
1496
+ IsIn3([
1497
+ "MONTHLY",
1498
+ "YEARLY"
1499
+ ]),
1500
+ _ts_metadata11("design:type", String)
1501
+ ], SaveRegistrationConfigSelectionDto.prototype, "billingCycle", void 0);
1502
+ _ts_decorate12([
1503
+ IsOptional3(),
1504
+ IsString7(),
1505
+ MaxLength6(60),
1506
+ _ts_metadata11("design:type", Object)
1507
+ ], SaveRegistrationConfigSelectionDto.prototype, "appliedPromoCode", void 0);
1508
+ _ts_decorate12([
1509
+ IsOptional3(),
1510
+ ValidateIf((_o, value) => value !== null),
1511
+ IsUUID(),
1512
+ _ts_metadata11("design:type", Object)
1513
+ ], SaveRegistrationConfigSelectionDto.prototype, "businessTypeVersionId", void 0);
1514
+ _ts_decorate12([
1515
+ IsOptional3(),
1516
+ ValidateIf((_o, value) => value !== null),
1517
+ IsUUID(),
1518
+ _ts_metadata11("design:type", Object)
1519
+ ], SaveRegistrationConfigSelectionDto.prototype, "offerId", void 0);
1520
+ var SaveRegistrationConfigDto = class {
1521
+ static {
1522
+ __name(this, "SaveRegistrationConfigDto");
1523
+ }
1524
+ pendingRegistrationId;
1525
+ selection;
1526
+ };
1527
+ _ts_decorate12([
1528
+ IsString7(),
1529
+ MinLength6(1),
1530
+ MaxLength6(80),
1531
+ _ts_metadata11("design:type", String)
1532
+ ], SaveRegistrationConfigDto.prototype, "pendingRegistrationId", void 0);
1533
+ _ts_decorate12([
1534
+ ValidateNested(),
1535
+ Type(() => SaveRegistrationConfigSelectionDto),
1536
+ _ts_metadata11("design:type", typeof SaveRegistrationConfigSelectionDto === "undefined" ? Object : SaveRegistrationConfigSelectionDto)
1537
+ ], SaveRegistrationConfigDto.prototype, "selection", void 0);
1538
+
1539
+ // src/registration/dto/preview-promo.dto.ts
1540
+ import { IsOptional as IsOptional4, IsString as IsString8, MaxLength as MaxLength7, MinLength as MinLength7 } from "class-validator";
1541
+ function _ts_decorate13(decorators, target, key, desc) {
1542
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1543
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1544
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1545
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1546
+ }
1547
+ __name(_ts_decorate13, "_ts_decorate");
1548
+ function _ts_metadata12(k, v) {
1549
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1550
+ }
1551
+ __name(_ts_metadata12, "_ts_metadata");
1552
+ var PreviewRegistrationPromoDto = class {
1553
+ static {
1554
+ __name(this, "PreviewRegistrationPromoDto");
1555
+ }
1556
+ pendingRegistrationId;
1557
+ code;
1558
+ };
1559
+ _ts_decorate13([
1560
+ IsString8(),
1561
+ MinLength7(1),
1562
+ MaxLength7(80),
1563
+ _ts_metadata12("design:type", String)
1564
+ ], PreviewRegistrationPromoDto.prototype, "pendingRegistrationId", void 0);
1565
+ _ts_decorate13([
1566
+ IsOptional4(),
1567
+ IsString8(),
1568
+ MaxLength7(60),
1569
+ _ts_metadata12("design:type", String)
1570
+ ], PreviewRegistrationPromoDto.prototype, "code", void 0);
1571
+
1572
+ export {
1573
+ generateOtpCode,
1574
+ hashOtpCode,
1575
+ verifyOtpCode,
1576
+ slugify,
1577
+ PENDING_REGISTRATION_REPOSITORY_TOKEN,
1578
+ REGISTRATION_OTP_DELIVERY_TOKEN,
1579
+ USER_ACCOUNT_LOOKUP_TOKEN,
1580
+ SLUG_AVAILABILITY_CHECK_TOKEN,
1581
+ PASSWORD_HASHER_TOKEN,
1582
+ PLAN_CATALOG_LOOKUP_TOKEN,
1583
+ PAYMENT_PROVIDER_TOKEN,
1584
+ PAYMENT_EVENT_LOG_TOKEN,
1585
+ ACTIVATION_ORCHESTRATOR_TOKEN,
1586
+ REGISTRATION_AUDIT_LOGGER_TOKEN,
1587
+ REGISTRATION_RESUME_TOKEN_SIGNER_TOKEN,
1588
+ REGISTRATION_RESUME_DELIVERY_TOKEN,
1589
+ REGISTRATION_RESUME_BASE_URL_TOKEN,
1590
+ REGISTRATION_CONFIGURATOR_LOOKUP_TOKEN,
1591
+ REGISTRATION_PROMO_PREVIEW_TOKEN,
1592
+ REGISTRATION_BUSINESS_TYPE_LOOKUP_TOKEN,
1593
+ REGISTRATION_CONFIG_TOKEN,
1594
+ computeBreakdown,
1595
+ resolveModel,
1596
+ PendingRegistrationService,
1597
+ CronExpression,
1598
+ RegistrationCleanupCron,
1599
+ BaseIpRateLimitGuard,
1600
+ RegistrationModule,
1601
+ RegisterStartDto,
1602
+ VerifyRegistrationOtpDto,
1603
+ ResendRegistrationOtpDto,
1604
+ SelectRegistrationPlanDto,
1605
+ StartRegistrationCheckoutDto,
1606
+ PaymentWebhookDto,
1607
+ ContinueRegistrationDto,
1608
+ SaveRegistrationConfigSelectionDto,
1609
+ SaveRegistrationConfigDto,
1610
+ PreviewRegistrationPromoDto
1611
+ };