@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.
- package/LICENSE +202 -0
- package/README.md +53 -0
- package/dist/admin/index.cjs +1283 -0
- package/dist/admin/index.d.cts +225 -0
- package/dist/admin/index.d.ts +225 -0
- package/dist/admin/index.js +58 -0
- package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
- package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
- package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
- package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
- package/dist/aggregation-CJ3qQf92.d.cts +288 -0
- package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
- package/dist/billing/index.cjs +5205 -0
- package/dist/billing/index.d.cts +1326 -0
- package/dist/billing/index.d.ts +1326 -0
- package/dist/billing/index.js +197 -0
- package/dist/catalog/index.cjs +6449 -0
- package/dist/catalog/index.d.cts +1073 -0
- package/dist/catalog/index.d.ts +1073 -0
- package/dist/catalog/index.js +158 -0
- package/dist/checkout-offer/index.cjs +807 -0
- package/dist/checkout-offer/index.d.cts +120 -0
- package/dist/checkout-offer/index.d.ts +120 -0
- package/dist/checkout-offer/index.js +20 -0
- package/dist/chunk-2FR6ZL7R.js +0 -0
- package/dist/chunk-2PNX2QL2.js +347 -0
- package/dist/chunk-2VNZYRDV.js +326 -0
- package/dist/chunk-7MLGHWKO.js +12 -0
- package/dist/chunk-ABB2CRAS.js +31 -0
- package/dist/chunk-AJ3EPELS.js +157 -0
- package/dist/chunk-E56W4U2P.js +90 -0
- package/dist/chunk-HRU3CP57.js +71 -0
- package/dist/chunk-L4SUWH5B.js +14 -0
- package/dist/chunk-M6CZQYPY.js +314 -0
- package/dist/chunk-MDIZUVIK.js +87 -0
- package/dist/chunk-MV6LW5UB.js +217 -0
- package/dist/chunk-NYLON2VC.js +6 -0
- package/dist/chunk-P6MYZMXQ.js +951 -0
- package/dist/chunk-Q53N43LQ.js +13 -0
- package/dist/chunk-QJVPRD3R.js +573 -0
- package/dist/chunk-RIGLQ4BV.js +3367 -0
- package/dist/chunk-SABTXESR.js +24 -0
- package/dist/chunk-SHUYVCID.js +6 -0
- package/dist/chunk-SYEVGPFC.js +686 -0
- package/dist/chunk-V7HUZ6UQ.js +5923 -0
- package/dist/chunk-WPUVJRIN.js +29 -0
- package/dist/chunk-XCZ3TTFK.js +22 -0
- package/dist/chunk-XSGU2Y2O.js +1611 -0
- package/dist/chunk-YNEW3L63.js +190 -0
- package/dist/chunk-YXGHPILG.js +614 -0
- package/dist/chunk-ZL4LLE2Z.js +465 -0
- package/dist/di-CcNeq9v-.d.cts +16 -0
- package/dist/di-CcNeq9v-.d.ts +16 -0
- package/dist/discovery/index.cjs +588 -0
- package/dist/discovery/index.d.cts +299 -0
- package/dist/discovery/index.d.ts +299 -0
- package/dist/discovery/index.js +49 -0
- package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
- package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
- package/dist/entitlement/index.cjs +805 -0
- package/dist/entitlement/index.d.cts +60 -0
- package/dist/entitlement/index.d.ts +60 -0
- package/dist/entitlement/index.js +57 -0
- package/dist/index.cjs +15918 -0
- package/dist/index.d.cts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +956 -0
- package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
- package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
- package/dist/platform/index.cjs +2772 -0
- package/dist/platform/index.d.cts +118 -0
- package/dist/platform/index.d.ts +118 -0
- package/dist/platform/index.js +37 -0
- package/dist/promo/index.cjs +995 -0
- package/dist/promo/index.d.cts +121 -0
- package/dist/promo/index.d.ts +121 -0
- package/dist/promo/index.js +58 -0
- package/dist/registration/index.cjs +1687 -0
- package/dist/registration/index.d.cts +444 -0
- package/dist/registration/index.d.ts +444 -0
- package/dist/registration/index.js +82 -0
- package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
- package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
- package/dist/service-DX8KbGXl.d.cts +109 -0
- package/dist/service-DX8KbGXl.d.ts +109 -0
- package/dist/subscription-contract/index.cjs +471 -0
- package/dist/subscription-contract/index.d.cts +17 -0
- package/dist/subscription-contract/index.d.ts +17 -0
- package/dist/subscription-contract/index.js +21 -0
- package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
- package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
- package/dist/testing/index.cjs +3942 -0
- package/dist/testing/index.d.cts +279 -0
- package/dist/testing/index.d.ts +279 -0
- package/dist/testing/index.js +1195 -0
- 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
|
+
};
|