@saasicat/core 1.0.0-rc.2 → 1.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +1017 -50
- package/dist/index.d.cts +2721 -477
- package/dist/index.d.ts +2721 -477
- package/dist/index.js +970 -49
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -20,49 +20,95 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES: () => ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES,
|
|
23
24
|
AUTH_ERROR_CODES: () => AUTH_ERROR_CODES,
|
|
24
25
|
BILLING_ERROR_CODES: () => BILLING_ERROR_CODES,
|
|
26
|
+
BUNDLE_PRICE_LOOKUP_LIMIT: () => BUNDLE_PRICE_LOOKUP_LIMIT,
|
|
27
|
+
CATALOGUE_KEYS: () => CATALOGUE_KEYS,
|
|
25
28
|
CATALOG_ERROR_CODES: () => CATALOG_ERROR_CODES,
|
|
26
29
|
CONTRACT_ERROR_CODES: () => CONTRACT_ERROR_CODES,
|
|
27
30
|
ERROR_MESSAGES_DE: () => ERROR_MESSAGES_DE,
|
|
28
31
|
ERROR_MESSAGES_EN: () => ERROR_MESSAGES_EN,
|
|
29
32
|
FEATURE_NOT_LICENSED: () => FEATURE_NOT_LICENSED,
|
|
33
|
+
ForeignPaymentMethodReferenceError: () => ForeignPaymentMethodReferenceError,
|
|
34
|
+
LEGAL_IDENTITY_FIELDS: () => LEGAL_IDENTITY_FIELDS,
|
|
35
|
+
MARKETING_PRIORITY_MAX: () => MARKETING_PRIORITY_MAX,
|
|
36
|
+
MARKETING_PRIORITY_MIN: () => MARKETING_PRIORITY_MIN,
|
|
30
37
|
OTP_RATE_LIMIT_MAX_SENDS: () => OTP_RATE_LIMIT_MAX_SENDS,
|
|
31
38
|
OTP_RATE_LIMIT_WINDOW_MINUTES: () => OTP_RATE_LIMIT_WINDOW_MINUTES,
|
|
32
39
|
OTP_TTL_MINUTES: () => OTP_TTL_MINUTES,
|
|
33
40
|
OTP_VERIFY_MAX_ATTEMPTS: () => OTP_VERIFY_MAX_ATTEMPTS,
|
|
34
41
|
PASSWORD_RESET_TTL_MINUTES: () => PASSWORD_RESET_TTL_MINUTES,
|
|
42
|
+
PAYMENT_ERROR_CODES: () => PAYMENT_ERROR_CODES,
|
|
43
|
+
PAYMENT_METHOD_TYPES: () => PAYMENT_METHOD_TYPES,
|
|
35
44
|
PENDING_CHECKOUT_TTL_DAYS: () => PENDING_CHECKOUT_TTL_DAYS,
|
|
36
45
|
PENDING_EMAIL_TTL_HOURS: () => PENDING_EMAIL_TTL_HOURS,
|
|
37
46
|
PENDING_ONBOARDING_TTL_DAYS: () => PENDING_ONBOARDING_TTL_DAYS,
|
|
38
47
|
PLATFORM_ERROR_CODES: () => PLATFORM_ERROR_CODES,
|
|
39
48
|
PROMO_ERROR_CODES: () => PROMO_ERROR_CODES,
|
|
49
|
+
PaymentCallbackRejectedError: () => PaymentCallbackRejectedError,
|
|
40
50
|
PersistenceCapabilityError: () => PersistenceCapabilityError,
|
|
41
51
|
PlatformUserExistsError: () => PlatformUserExistsError,
|
|
42
52
|
REGISTRATION_ERROR_CODES: () => REGISTRATION_ERROR_CODES,
|
|
43
53
|
REGISTRATION_RESUME_TTL_MINUTES: () => REGISTRATION_RESUME_TTL_MINUTES,
|
|
44
54
|
REGISTRATION_STEP_BY_STATUS: () => REGISTRATION_STEP_BY_STATUS,
|
|
55
|
+
SETTINGS_ERROR_CODES: () => SETTINGS_ERROR_CODES,
|
|
45
56
|
SETUP_ERROR_CODES: () => SETUP_ERROR_CODES,
|
|
57
|
+
SUBSCRIBER_ERROR_CODES: () => SUBSCRIBER_ERROR_CODES,
|
|
46
58
|
applyPromo: () => applyPromo,
|
|
47
59
|
assertPersistenceCapabilities: () => assertPersistenceCapabilities,
|
|
48
60
|
buildActivePlanVersionWhere: () => buildActivePlanVersionWhere,
|
|
49
61
|
buildActiveVersionWhere: () => buildActiveVersionWhere,
|
|
50
62
|
buildFeatureRequiresIndex: () => buildFeatureRequiresIndex,
|
|
63
|
+
bundleDraftDefaults: () => bundleDraftDefaults,
|
|
64
|
+
bundleStemDefaults: () => bundleStemDefaults,
|
|
65
|
+
canonicalJson: () => canonicalJson,
|
|
51
66
|
classifyBundleVersionDiff: () => classifyBundleVersionDiff,
|
|
67
|
+
classifyIssuerChange: () => classifyIssuerChange,
|
|
52
68
|
classifyPlanDiff: () => classifyPlanDiff,
|
|
53
69
|
collectUnsatisfiedRequires: () => collectUnsatisfiedRequires,
|
|
70
|
+
contractPartiesOf: () => contractPartiesOf,
|
|
54
71
|
coverageExcludingSelf: () => coverageExcludingSelf,
|
|
72
|
+
definedFields: () => definedFields,
|
|
73
|
+
diffSettings: () => diffSettings,
|
|
74
|
+
formatCustomerNumber: () => formatCustomerNumber,
|
|
55
75
|
formatErrorMessage: () => formatErrorMessage,
|
|
76
|
+
identityCorrectionDelta: () => identityCorrectionDelta,
|
|
56
77
|
isBundleRedundant: () => isBundleRedundant,
|
|
78
|
+
isForeignPaymentMethodReferenceError: () => isForeignPaymentMethodReferenceError,
|
|
79
|
+
isPaymentCallbackRejectedError: () => isPaymentCallbackRejectedError,
|
|
57
80
|
isPlatformUserExistsError: () => isPlatformUserExistsError,
|
|
58
81
|
isVersionEditable: () => isVersionEditable,
|
|
82
|
+
issuerIdentityOf: () => issuerIdentityOf,
|
|
83
|
+
keepOneRecommended: () => keepOneRecommended,
|
|
59
84
|
missingRequiresFor: () => missingRequiresFor,
|
|
85
|
+
movedIdentityFields: () => movedIdentityFields,
|
|
60
86
|
pickActivePromo: () => pickActivePromo,
|
|
87
|
+
planCatalogSettingsOf: () => planCatalogSettingsOf,
|
|
88
|
+
previousUtcDay: () => previousUtcDay,
|
|
61
89
|
promoStatus: () => promoStatus,
|
|
90
|
+
promotionOnPrice: () => promotionOnPrice,
|
|
91
|
+
readQuotaRecord: () => readQuotaRecord,
|
|
92
|
+
readQuotaValue: () => readQuotaValue,
|
|
93
|
+
recordedIssuerIdentity: () => recordedIssuerIdentity,
|
|
94
|
+
refuseForeignPaymentMethodReference: () => refuseForeignPaymentMethodReference,
|
|
62
95
|
resolveBundleAvailability: () => resolveBundleAvailability,
|
|
63
96
|
resolveErrorMessage: () => resolveErrorMessage,
|
|
97
|
+
sameLegalIdentity: () => sameLegalIdentity,
|
|
64
98
|
selectChargeableBundles: () => selectChargeableBundles,
|
|
65
|
-
|
|
99
|
+
settingsSubtreeOf: () => settingsSubtreeOf,
|
|
100
|
+
startOfUtcDay: () => startOfUtcDay,
|
|
101
|
+
subscriberFromRegistration: () => subscriberFromRegistration,
|
|
102
|
+
subscriberPaymentMethodColumns: () => subscriberPaymentMethodColumns,
|
|
103
|
+
toBundleStemRow: () => toBundleStemRow,
|
|
104
|
+
toContractLineItemRecord: () => toContractLineItemRecord,
|
|
105
|
+
toPlanRow: () => toPlanRow,
|
|
106
|
+
toPlanVersionRow: () => toPlanVersionRow,
|
|
107
|
+
toRunningContractIssuer: () => toRunningContractIssuer,
|
|
108
|
+
toSubscriberCorrectionRecord: () => toSubscriberCorrectionRecord,
|
|
109
|
+
toSubscriberPaymentMethodRecord: () => toSubscriberPaymentMethodRecord,
|
|
110
|
+
toSubscriberRecord: () => toSubscriberRecord,
|
|
111
|
+
toSubscriptionContractRecord: () => toSubscriptionContractRecord
|
|
66
112
|
});
|
|
67
113
|
module.exports = __toCommonJS(index_exports);
|
|
68
114
|
|
|
@@ -70,6 +116,11 @@ module.exports = __toCommonJS(index_exports);
|
|
|
70
116
|
function startOfUtcDay(date) {
|
|
71
117
|
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
72
118
|
}
|
|
119
|
+
function previousUtcDay(value) {
|
|
120
|
+
const result = new Date(value);
|
|
121
|
+
result.setUTCDate(result.getUTCDate() - 1);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
73
124
|
function buildActivePlanVersionWhere(asOf, options = {}) {
|
|
74
125
|
const asOfDayStart = startOfUtcDay(asOf);
|
|
75
126
|
const validityWindow = [
|
|
@@ -86,6 +137,57 @@ function buildActivePlanVersionWhere(asOf, options = {}) {
|
|
|
86
137
|
}
|
|
87
138
|
var buildActiveVersionWhere = buildActivePlanVersionWhere;
|
|
88
139
|
|
|
140
|
+
// src/bundle-draft-defaults.ts
|
|
141
|
+
function bundleDraftDefaults(data) {
|
|
142
|
+
return {
|
|
143
|
+
baseVersionId: data.baseVersionId ?? null,
|
|
144
|
+
features: data.features,
|
|
145
|
+
quotas: data.quotas ?? {},
|
|
146
|
+
compatibility: data.compatibility ?? {},
|
|
147
|
+
pricingOverrides: data.pricingOverrides ?? [],
|
|
148
|
+
monthlyNet: data.monthlyNet ?? null,
|
|
149
|
+
yearlyNet: data.yearlyNet ?? null,
|
|
150
|
+
marketed: data.marketed ?? true,
|
|
151
|
+
changeNote: data.changeNote ?? "",
|
|
152
|
+
createdByUserId: data.createdByUserId ?? null
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function bundleStemDefaults(data) {
|
|
156
|
+
return {
|
|
157
|
+
bundleKey: data.bundleKey,
|
|
158
|
+
label: data.label,
|
|
159
|
+
description: data.description ?? null,
|
|
160
|
+
icon: data.icon ?? null,
|
|
161
|
+
sortOrder: data.sortOrder ?? 0,
|
|
162
|
+
i18n: data.i18n ?? {}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function toBundleStemRow(row) {
|
|
166
|
+
return {
|
|
167
|
+
id: row.id,
|
|
168
|
+
bundleKey: row.bundleKey,
|
|
169
|
+
label: row.label,
|
|
170
|
+
description: row.description,
|
|
171
|
+
icon: row.icon,
|
|
172
|
+
sortOrder: row.sortOrder,
|
|
173
|
+
i18n: row.i18n !== null && typeof row.i18n === "object" && !Array.isArray(row.i18n) ? row.i18n : {},
|
|
174
|
+
createdAt: row.createdAt.toISOString(),
|
|
175
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
176
|
+
deletedAt: row.deletedAt?.toISOString() ?? null
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function definedFields(data, keys) {
|
|
180
|
+
const patch = {};
|
|
181
|
+
for (const key of keys) {
|
|
182
|
+
if (data[key] !== void 0) patch[key] = data[key];
|
|
183
|
+
}
|
|
184
|
+
return patch;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/catalog-entry.types.ts
|
|
188
|
+
var MARKETING_PRIORITY_MIN = 0;
|
|
189
|
+
var MARKETING_PRIORITY_MAX = 1e4;
|
|
190
|
+
|
|
89
191
|
// src/promotion.types.ts
|
|
90
192
|
function promoStatus(promo, today = /* @__PURE__ */ new Date()) {
|
|
91
193
|
const from = new Date(promo.validFrom);
|
|
@@ -99,27 +201,33 @@ function pickActivePromo(promotions, targetKey, locale, cycle, today = /* @__PUR
|
|
|
99
201
|
return matches[0] ?? null;
|
|
100
202
|
}
|
|
101
203
|
function applyPromo(promo, basePrice) {
|
|
204
|
+
const result = boundedPromo(promo, basePrice);
|
|
205
|
+
return result && result.discounted < result.original ? result : null;
|
|
206
|
+
}
|
|
207
|
+
function boundedPromo(promo, basePrice) {
|
|
102
208
|
if (!promo || basePrice === null || basePrice === void 0) return null;
|
|
103
209
|
if (promo.type === "percent" && typeof promo.value === "number") {
|
|
210
|
+
const pct = withinBounds(promo.value, 100);
|
|
104
211
|
return {
|
|
105
212
|
kind: "percent",
|
|
106
|
-
discounted: Math.round(basePrice * (100 -
|
|
213
|
+
discounted: Math.round(basePrice * (100 - pct)) / 100,
|
|
107
214
|
original: basePrice,
|
|
108
|
-
pct
|
|
215
|
+
pct
|
|
109
216
|
};
|
|
110
217
|
}
|
|
111
218
|
if (promo.type === "amount" && typeof promo.value === "number") {
|
|
219
|
+
const saved = withinBounds(promo.value, basePrice);
|
|
112
220
|
return {
|
|
113
221
|
kind: "amount",
|
|
114
|
-
discounted: Math.
|
|
222
|
+
discounted: Math.round((basePrice - saved) * 100) / 100,
|
|
115
223
|
original: basePrice,
|
|
116
|
-
saved
|
|
224
|
+
saved
|
|
117
225
|
};
|
|
118
226
|
}
|
|
119
|
-
if (promo.type === "intro" && typeof promo.value === "object") {
|
|
227
|
+
if (promo.type === "intro" && typeof promo.value === "object" && promo.value !== null) {
|
|
120
228
|
return {
|
|
121
229
|
kind: "intro",
|
|
122
|
-
discounted: promo.value.price,
|
|
230
|
+
discounted: withinBounds(promo.value.price, basePrice),
|
|
123
231
|
original: basePrice,
|
|
124
232
|
months: promo.value.months
|
|
125
233
|
};
|
|
@@ -129,26 +237,38 @@ function applyPromo(promo, basePrice) {
|
|
|
129
237
|
}
|
|
130
238
|
return null;
|
|
131
239
|
}
|
|
240
|
+
function withinBounds(value, upper) {
|
|
241
|
+
return Math.min(Math.max(value, 0), Math.max(upper, 0));
|
|
242
|
+
}
|
|
243
|
+
function promotionOnPrice(promotions, targetKey, locale, cycle, basePrice, today = /* @__PURE__ */ new Date(), targetType = "PLAN") {
|
|
244
|
+
const promotion = pickActivePromo(promotions, targetKey, locale, cycle, today, targetType);
|
|
245
|
+
const result = applyPromo(promotion, basePrice);
|
|
246
|
+
return promotion && result ? { promotion, result } : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/quota-value.ts
|
|
250
|
+
function readQuotaValue(value) {
|
|
251
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
252
|
+
if (typeof value !== "string") return null;
|
|
253
|
+
if (value.trim() === "") return null;
|
|
254
|
+
const parsed = Number(value);
|
|
255
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
256
|
+
}
|
|
257
|
+
var UNCOUNTABLE = -1;
|
|
258
|
+
function readQuotaRecord(value) {
|
|
259
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
|
|
260
|
+
const quotas = {};
|
|
261
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
262
|
+
quotas[key] = readQuotaValue(entry) ?? UNCOUNTABLE;
|
|
263
|
+
}
|
|
264
|
+
return quotas;
|
|
265
|
+
}
|
|
132
266
|
|
|
133
267
|
// src/version-diff.ts
|
|
134
268
|
function classifyPlanDiff(oldV, newV) {
|
|
135
269
|
const changes = [];
|
|
136
270
|
appendFeatureChanges(changes, oldV.features, newV.features);
|
|
137
|
-
|
|
138
|
-
appendNumberChange(
|
|
139
|
-
changes,
|
|
140
|
-
"maxVehicles",
|
|
141
|
-
oldV.maxVehicles,
|
|
142
|
-
newV.maxVehicles,
|
|
143
|
-
"higherIsBetter"
|
|
144
|
-
);
|
|
145
|
-
appendNumberChange(
|
|
146
|
-
changes,
|
|
147
|
-
"maxStorageGb",
|
|
148
|
-
oldV.maxStorageGb,
|
|
149
|
-
newV.maxStorageGb,
|
|
150
|
-
"higherIsBetter"
|
|
151
|
-
);
|
|
271
|
+
appendQuotaChanges(changes, oldV.quotas, newV.quotas);
|
|
152
272
|
appendDecimalChange(changes, "monthlyNet", oldV.monthlyNet, newV.monthlyNet, "lowerIsBetter");
|
|
153
273
|
appendDecimalChange(changes, "yearlyNet", oldV.yearlyNet, newV.yearlyNet, "lowerIsBetter");
|
|
154
274
|
return buildResult(changes);
|
|
@@ -195,11 +315,6 @@ function appendFeatureChanges(out, oldFeatures, newFeatures) {
|
|
|
195
315
|
});
|
|
196
316
|
}
|
|
197
317
|
}
|
|
198
|
-
function appendNumberChange(out, field, oldValue, newValue, polarity) {
|
|
199
|
-
if (oldValue === newValue) return;
|
|
200
|
-
const direction = directionFromCmp(newValue - oldValue, polarity);
|
|
201
|
-
out.push({ field, oldValue, newValue, direction });
|
|
202
|
-
}
|
|
203
318
|
function appendDecimalChange(out, field, oldValue, newValue, polarity) {
|
|
204
319
|
const oldNum = toNumber(oldValue);
|
|
205
320
|
const newNum = toNumber(newValue);
|
|
@@ -230,16 +345,31 @@ function buildResult(changes) {
|
|
|
230
345
|
return { nonRegressive, changes };
|
|
231
346
|
}
|
|
232
347
|
function appendQuotaChanges(out, oldQuotas, newQuotas) {
|
|
348
|
+
const at = (quotas, key) => Object.hasOwn(quotas, key) ? quotas[key] : 0;
|
|
233
349
|
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldQuotas), ...Object.keys(newQuotas)]);
|
|
234
350
|
for (const key of allKeys) {
|
|
235
|
-
const
|
|
236
|
-
const
|
|
351
|
+
const oldRaw = at(oldQuotas, key);
|
|
352
|
+
const newRaw = at(newQuotas, key);
|
|
353
|
+
if (oldRaw === newRaw) continue;
|
|
354
|
+
const oldValue = toQuotaNumber(oldRaw);
|
|
355
|
+
const newValue = toQuotaNumber(newRaw);
|
|
237
356
|
if (oldValue === newValue) continue;
|
|
238
|
-
|
|
239
|
-
|
|
357
|
+
out.push({
|
|
358
|
+
field: `quotas.${key}`,
|
|
359
|
+
oldValue: reportableQuota(oldRaw, oldValue),
|
|
360
|
+
newValue: reportableQuota(newRaw, newValue),
|
|
361
|
+
direction: directionFromQuotaCmp(oldValue, newValue)
|
|
362
|
+
});
|
|
240
363
|
}
|
|
241
364
|
}
|
|
365
|
+
function toQuotaNumber(value) {
|
|
366
|
+
return readQuotaValue(value) ?? Number.NaN;
|
|
367
|
+
}
|
|
368
|
+
function reportableQuota(raw, asNumber) {
|
|
369
|
+
return Number.isFinite(asNumber) ? asNumber : String(raw);
|
|
370
|
+
}
|
|
242
371
|
function directionFromQuotaCmp(oldValue, newValue) {
|
|
372
|
+
if (!Number.isFinite(oldValue) || !Number.isFinite(newValue)) return "REGRESSION";
|
|
243
373
|
if (oldValue === -1 && newValue !== -1) return "REGRESSION";
|
|
244
374
|
if (newValue === -1 && oldValue !== -1) return "IMPROVEMENT";
|
|
245
375
|
return newValue > oldValue ? "IMPROVEMENT" : "REGRESSION";
|
|
@@ -267,6 +397,60 @@ function appendNullableDecimalChange(out, field, oldValue, newValue, polarity) {
|
|
|
267
397
|
appendDecimalChange(out, field, oldValue, newValue, polarity);
|
|
268
398
|
}
|
|
269
399
|
|
|
400
|
+
// src/settings-subtree.ts
|
|
401
|
+
var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
|
|
402
|
+
"schemaVersion",
|
|
403
|
+
"features",
|
|
404
|
+
"plans"
|
|
405
|
+
]);
|
|
406
|
+
function planCatalogSettingsOf(catalog) {
|
|
407
|
+
return settingsSubtreeOf(catalog);
|
|
408
|
+
}
|
|
409
|
+
function settingsSubtreeOf(catalog) {
|
|
410
|
+
const settings = {};
|
|
411
|
+
for (const [key, value] of Object.entries(catalog)) {
|
|
412
|
+
if (CATALOGUE_KEYS.has(key)) continue;
|
|
413
|
+
if (value === void 0) continue;
|
|
414
|
+
settings[key] = value;
|
|
415
|
+
}
|
|
416
|
+
return settings;
|
|
417
|
+
}
|
|
418
|
+
function canonicalJson(value) {
|
|
419
|
+
const json = JSON.stringify(value, (_key, member) => {
|
|
420
|
+
if (member && typeof member === "object" && !Array.isArray(member)) {
|
|
421
|
+
const sorted = {};
|
|
422
|
+
for (const name of Object.keys(member).sort()) {
|
|
423
|
+
sorted[name] = member[name];
|
|
424
|
+
}
|
|
425
|
+
return sorted;
|
|
426
|
+
}
|
|
427
|
+
return member;
|
|
428
|
+
});
|
|
429
|
+
return json ?? "undefined";
|
|
430
|
+
}
|
|
431
|
+
function isPlainObject(value) {
|
|
432
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
433
|
+
}
|
|
434
|
+
function diffSettings(before, after) {
|
|
435
|
+
const differences = [];
|
|
436
|
+
collectDifferences(before, after, [], differences);
|
|
437
|
+
return differences.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
438
|
+
}
|
|
439
|
+
function isObjectOrAbsent(value) {
|
|
440
|
+
return value === void 0 || isPlainObject(value);
|
|
441
|
+
}
|
|
442
|
+
function collectDifferences(before, after, path, out) {
|
|
443
|
+
if (isObjectOrAbsent(before) && isObjectOrAbsent(after) && (before || after)) {
|
|
444
|
+
const names = /* @__PURE__ */ new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})]);
|
|
445
|
+
for (const name of names) {
|
|
446
|
+
collectDifferences(before?.[name], after?.[name], [...path, name], out);
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (canonicalJson(before) === canonicalJson(after)) return;
|
|
451
|
+
out.push({ path: path.join("."), before, after });
|
|
452
|
+
}
|
|
453
|
+
|
|
270
454
|
// src/ports/persistence-ports.types.ts
|
|
271
455
|
var PersistenceCapabilityError = class extends Error {
|
|
272
456
|
constructor(missing, requiredBy) {
|
|
@@ -337,6 +521,8 @@ var CATALOG_ERROR_CODES = {
|
|
|
337
521
|
BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED",
|
|
338
522
|
BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION",
|
|
339
523
|
BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE",
|
|
524
|
+
BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE",
|
|
525
|
+
BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "BUNDLE_VERSION_NOT_PRICED_FOR_PLAN",
|
|
340
526
|
BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED",
|
|
341
527
|
BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED",
|
|
342
528
|
BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID",
|
|
@@ -355,6 +541,12 @@ var CATALOG_ERROR_CODES = {
|
|
|
355
541
|
FEATURE_NOT_FOUND: "FEATURE_NOT_FOUND",
|
|
356
542
|
QUOTA_NOT_FOUND: "QUOTA_NOT_FOUND",
|
|
357
543
|
PROMOTION_NOT_FOUND: "PROMOTION_NOT_FOUND",
|
|
544
|
+
/**
|
|
545
|
+
* A promotion's value is not one its type takes: a percentage above 0 and
|
|
546
|
+
* at most 100, an amount above 0, an intro price of at least 0 for a whole
|
|
547
|
+
* number of months, or a whole number of free months.
|
|
548
|
+
*/
|
|
549
|
+
PROMOTION_VALUE_INVALID: "PROMOTION_VALUE_INVALID",
|
|
358
550
|
MARKETING_PROJECTION_NOT_FOUND: "MARKETING_PROJECTION_NOT_FOUND",
|
|
359
551
|
// ── already exists ──
|
|
360
552
|
PLAN_ALREADY_EXISTS: "PLAN_ALREADY_EXISTS",
|
|
@@ -366,7 +558,12 @@ var CATALOG_ERROR_CODES = {
|
|
|
366
558
|
// ── discovery ──
|
|
367
559
|
QUOTA_NOT_IN_DISCOVERY_SNAPSHOT: "QUOTA_NOT_IN_DISCOVERY_SNAPSHOT",
|
|
368
560
|
DISCOVERY_STATUS_TRANSITION_INVALID: "DISCOVERY_STATUS_TRANSITION_INVALID",
|
|
369
|
-
DISCOVERY_NOT_INITIALIZED: "DISCOVERY_NOT_INITIALIZED"
|
|
561
|
+
DISCOVERY_NOT_INITIALIZED: "DISCOVERY_NOT_INITIALIZED",
|
|
562
|
+
// ── catalogue import ──
|
|
563
|
+
/** The uploaded document is not a plan catalog — unparseable, or not an object. */
|
|
564
|
+
PLAN_CATALOG_UNREADABLE: "PLAN_CATALOG_UNREADABLE",
|
|
565
|
+
/** It parsed, and then failed the schema or a cross-field rule. */
|
|
566
|
+
PLAN_CATALOG_INVALID: "PLAN_CATALOG_INVALID"
|
|
370
567
|
};
|
|
371
568
|
var BILLING_ERROR_CODES = {
|
|
372
569
|
/**
|
|
@@ -378,6 +575,8 @@ var BILLING_ERROR_CODES = {
|
|
|
378
575
|
BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED",
|
|
379
576
|
BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN",
|
|
380
577
|
BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE",
|
|
578
|
+
BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN",
|
|
579
|
+
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN",
|
|
381
580
|
SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED",
|
|
382
581
|
SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED",
|
|
383
582
|
SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE",
|
|
@@ -392,8 +591,91 @@ var BILLING_ERROR_CODES = {
|
|
|
392
591
|
PLAN_NOT_IN_CATALOG: "PLAN_NOT_IN_CATALOG",
|
|
393
592
|
/** Plan exists but cannot be booked via self-service. */
|
|
394
593
|
PLAN_NOT_SELF_SERVICE: "PLAN_NOT_SELF_SERVICE",
|
|
594
|
+
/**
|
|
595
|
+
* Plan carries no price for the requested billing cycle, so it is not sold
|
|
596
|
+
* in it: a plan without a yearly price is a monthly plan.
|
|
597
|
+
*/
|
|
598
|
+
PLAN_NOT_SOLD_IN_CYCLE: "PLAN_NOT_SOLD_IN_CYCLE",
|
|
395
599
|
/** Plan change refused. Carries `blockers[]` with their own codes. */
|
|
396
600
|
PLAN_CHANGE_BLOCKED: "PLAN_CHANGE_BLOCKED",
|
|
601
|
+
/**
|
|
602
|
+
* The subscription moved between the read a request was decided on and the
|
|
603
|
+
* write it attempted, so nothing was written. The caller reloads and asks
|
|
604
|
+
* again.
|
|
605
|
+
*/
|
|
606
|
+
SUBSCRIPTION_CHANGED: "SUBSCRIPTION_CHANGED",
|
|
607
|
+
/**
|
|
608
|
+
* The tenant has no subscription to act on.
|
|
609
|
+
*
|
|
610
|
+
* `SUBSCRIPTION_NOT_FOUND` states the same fact on the read routes. Both
|
|
611
|
+
* are already on the wire and a code is renamed only deliberately, so both
|
|
612
|
+
* are named here rather than one being dropped behind a consumer's back.
|
|
613
|
+
*/
|
|
614
|
+
NO_SUBSCRIPTION: "NO_SUBSCRIPTION",
|
|
615
|
+
/**
|
|
616
|
+
* The cancellation date the reader was shown is no longer the one the rules
|
|
617
|
+
* return, so the confirmation is refused rather than silently applied.
|
|
618
|
+
* Carries the recomputed dates, so the page can re-ask instead of guessing.
|
|
619
|
+
*/
|
|
620
|
+
CANCELLATION_TERMS_CHANGED: "CANCELLATION_TERMS_CHANGED",
|
|
621
|
+
// ── the preview routes' own blockers and warnings ──
|
|
622
|
+
//
|
|
623
|
+
// These travel inside a 200 response, in `blockers[]` and `warnings[]`, not
|
|
624
|
+
// as thrown errors — but they are read by a person and so belong in the same
|
|
625
|
+
// catalogue. Each carries `params` with the values its sentence names. Two
|
|
626
|
+
// routes emit them: the plan-change preview and the bundle preview.
|
|
627
|
+
//
|
|
628
|
+
// Three of them used to construct their code from the subject:
|
|
629
|
+
// `VEHICLES_OVER_TARGET`, `ENTERPRISE_LOCKED`, `PRO_NOT_SELF_SERVICE`. That
|
|
630
|
+
// made the set grow with every quota and plan an installation defines, so no
|
|
631
|
+
// catalogue could be complete against it and no guard could check one. The
|
|
632
|
+
// subject is a parameter now.
|
|
633
|
+
/** The subscription has ended; its plan can no longer be changed. */
|
|
634
|
+
SUBSCRIPTION_ENDED: "SUBSCRIPTION_ENDED",
|
|
635
|
+
/** An active special contract blocks self-service plan changes. */
|
|
636
|
+
PLAN_LOCKED: "PLAN_LOCKED",
|
|
637
|
+
/** Current usage of one quota exceeds what the target plan allows. */
|
|
638
|
+
QUOTA_OVER_TARGET: "QUOTA_OVER_TARGET",
|
|
639
|
+
/** The change drops features the tenant has today. */
|
|
640
|
+
FEATURE_LOST: "FEATURE_LOST",
|
|
641
|
+
FEATURES_LOST: "FEATURES_LOST",
|
|
642
|
+
/** Target plan and cycle already match what is in place. */
|
|
643
|
+
NO_CHANGE: "NO_CHANGE",
|
|
644
|
+
/** A shorter cycle cannot start inside the term already running. */
|
|
645
|
+
CYCLE_SHORTENS_AT_TERM_END: "CYCLE_SHORTENS_AT_TERM_END",
|
|
646
|
+
/** A cancelled subscription cannot change its billing cycle. */
|
|
647
|
+
CANCELLATION_LOCKS_THE_CYCLE: "CANCELLATION_LOCKS_THE_CYCLE",
|
|
648
|
+
/**
|
|
649
|
+
* A bundle the tenant already holds runs past the cycle they are moving to.
|
|
650
|
+
*
|
|
651
|
+
* Its own code rather than `BUNDLE_CYCLE_EXCEEDS_PLAN`, which states the
|
|
652
|
+
* same rule about a booking that has not been made yet. The two need
|
|
653
|
+
* different sentences: this one can name the day the obstacle lifts and
|
|
654
|
+
* tell the reader to cancel the booking, and that advice is wrong for
|
|
655
|
+
* someone who is only about to book. One template cannot serve both.
|
|
656
|
+
*/
|
|
657
|
+
BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE",
|
|
658
|
+
/**
|
|
659
|
+
* Features of the previewed bundle are already covered by the plan or by
|
|
660
|
+
* another booked bundle. A warning rather than a blocker: paying twice is
|
|
661
|
+
* the customer's decision, and the preview only has to say so first.
|
|
662
|
+
*/
|
|
663
|
+
REDUNDANT_FEATURES: "REDUNDANT_FEATURES",
|
|
664
|
+
/**
|
|
665
|
+
* A booking's minimum term outlasts the period being cancelled, so the
|
|
666
|
+
* cancellation takes effect at the end of the term, not of the period.
|
|
667
|
+
*/
|
|
668
|
+
MINIMUM_TERM_BINDS: "MINIMUM_TERM_BINDS",
|
|
669
|
+
/**
|
|
670
|
+
* The previewed bundle requires features that neither the plan nor an
|
|
671
|
+
* active booking provides.
|
|
672
|
+
*
|
|
673
|
+
* The same string is a `StrictModeWarningCode` in `bundle.types.ts`, where
|
|
674
|
+
* it names the catalogue-authoring reading of the rule and travels with its
|
|
675
|
+
* own message. This declaration is the booking preview's blocker, which a
|
|
676
|
+
* tenant reads and therefore needs a shipped text for.
|
|
677
|
+
*/
|
|
678
|
+
BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED",
|
|
397
679
|
NO_PENDING_PLAN_VERSION: "NO_PENDING_PLAN_VERSION",
|
|
398
680
|
ONBOARDING_CREATE_FAILED: "ONBOARDING_CREATE_FAILED",
|
|
399
681
|
BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS",
|
|
@@ -413,20 +695,64 @@ var CONTRACT_ERROR_CODES = {
|
|
|
413
695
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED",
|
|
414
696
|
CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE: "CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE",
|
|
415
697
|
CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED: "CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED",
|
|
698
|
+
CHECKOUT_OFFER_PLAN_NOT_OFFERED: "CHECKOUT_OFFER_PLAN_NOT_OFFERED",
|
|
699
|
+
CHECKOUT_OFFER_BUNDLE_NOT_OFFERED: "CHECKOUT_OFFER_BUNDLE_NOT_OFFERED",
|
|
700
|
+
CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED: "CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED",
|
|
701
|
+
CHECKOUT_OFFER_PRICE_NOT_CURRENT: "CHECKOUT_OFFER_PRICE_NOT_CURRENT",
|
|
416
702
|
SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED: "SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED",
|
|
417
703
|
SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED",
|
|
418
704
|
SUBSCRIPTION_CONTRACT_INVALID_DATE: "SUBSCRIPTION_CONTRACT_INVALID_DATE",
|
|
419
705
|
SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "SUBSCRIPTION_CONTRACT_INVALID_WINDOW",
|
|
706
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH",
|
|
707
|
+
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT",
|
|
708
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH",
|
|
709
|
+
/**
|
|
710
|
+
* The lines do not add up to a total the contract states, counted as often
|
|
711
|
+
* as each falls due in one period. `field` names the total.
|
|
712
|
+
*/
|
|
713
|
+
SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP",
|
|
714
|
+
/** Something that takes money off states a negative amount. */
|
|
715
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE",
|
|
420
716
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START",
|
|
421
717
|
// ── stage 3 ──
|
|
422
718
|
CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND",
|
|
423
719
|
CHECKOUT_OFFER_EXPIRED: "CHECKOUT_OFFER_EXPIRED",
|
|
424
720
|
CHECKOUT_OFFER_ALREADY_CONSUMED: "CHECKOUT_OFFER_ALREADY_CONSUMED",
|
|
425
721
|
CHECKOUT_OFFER_NOT_CONSUMED: "CHECKOUT_OFFER_NOT_CONSUMED",
|
|
722
|
+
/**
|
|
723
|
+
* The offer changed between the checks of `conclude` and the transaction
|
|
724
|
+
* that consumed it, so the contract checked is not the one the offer now
|
|
725
|
+
* describes. Nothing was written; load the offer and conclude it again.
|
|
726
|
+
*/
|
|
727
|
+
CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED",
|
|
426
728
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND",
|
|
427
729
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT",
|
|
428
730
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED"
|
|
429
731
|
};
|
|
732
|
+
var SUBSCRIBER_ERROR_CODES = {
|
|
733
|
+
/**
|
|
734
|
+
* A contract, a plan change or a booking for a tenant that has no
|
|
735
|
+
* subscriber. Nothing is agreed or charged without the party to it.
|
|
736
|
+
* Carries `tenantId`.
|
|
737
|
+
*/
|
|
738
|
+
SUBSCRIBER_REQUIRED: "SUBSCRIBER_REQUIRED",
|
|
739
|
+
/** The tenant already has a live subscriber. Carries `tenantId`. */
|
|
740
|
+
SUBSCRIBER_ALREADY_EXISTS: "SUBSCRIBER_ALREADY_EXISTS",
|
|
741
|
+
SUBSCRIBER_NOT_FOUND: "SUBSCRIBER_NOT_FOUND",
|
|
742
|
+
SUBSCRIBER_LEGAL_NAME_REQUIRED: "SUBSCRIBER_LEGAL_NAME_REQUIRED",
|
|
743
|
+
/**
|
|
744
|
+
* A detail that has a form — the country, the invoice email — is not in it,
|
|
745
|
+
* or one sign-up requires — the billing address — is missing. Carries `field`.
|
|
746
|
+
*/
|
|
747
|
+
SUBSCRIBER_DETAIL_INVALID: "SUBSCRIBER_DETAIL_INVALID",
|
|
748
|
+
/** A contact change named a field of the legal identity. Carries `field`. */
|
|
749
|
+
SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "SUBSCRIBER_IDENTITY_NOT_A_CONTACT",
|
|
750
|
+
SUBSCRIBER_CORRECTION_REASON_REQUIRED: "SUBSCRIBER_CORRECTION_REASON_REQUIRED",
|
|
751
|
+
SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "SUBSCRIBER_CORRECTION_ACTOR_REQUIRED",
|
|
752
|
+
SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "SUBSCRIBER_CORRECTION_CHANGES_NOTHING",
|
|
753
|
+
/** The operator declared another legal entity: that is a transfer, not an edit. */
|
|
754
|
+
SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER"
|
|
755
|
+
};
|
|
430
756
|
var REGISTRATION_ERROR_CODES = {
|
|
431
757
|
PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND",
|
|
432
758
|
PENDING_REGISTRATION_EXPIRED: "PENDING_REGISTRATION_EXPIRED",
|
|
@@ -452,6 +778,12 @@ var AUTH_ERROR_CODES = {
|
|
|
452
778
|
/** Neither `tenantId` nor `userId` could be resolved from the request. */
|
|
453
779
|
TENANT_CONTEXT_MISSING: "TENANT_CONTEXT_MISSING",
|
|
454
780
|
TENANT_ADMIN_REQUIRED: "TENANT_ADMIN_REQUIRED",
|
|
781
|
+
/**
|
|
782
|
+
* The tenant's billing area — its payment method, and later its invoices
|
|
783
|
+
* and account — needs the billing permission, which the application maps
|
|
784
|
+
* to its roles and the tenant's administrator holds by default.
|
|
785
|
+
*/
|
|
786
|
+
BILLING_PERMISSION_REQUIRED: "BILLING_PERMISSION_REQUIRED",
|
|
455
787
|
SUPER_ADMIN_REQUIRED: "SUPER_ADMIN_REQUIRED",
|
|
456
788
|
/** TOTP MFA has never been set up for this user. */
|
|
457
789
|
MFA_NOT_SET_UP: "MFA_NOT_SET_UP",
|
|
@@ -479,6 +811,26 @@ var PROMO_ERROR_CODES = {
|
|
|
479
811
|
PROMO_WOULD_PRODUCE_ZERO_INVOICE: "PROMO_WOULD_PRODUCE_ZERO_INVOICE",
|
|
480
812
|
PROMO_MAX_REDEMPTIONS_LOWERED: "PROMO_MAX_REDEMPTIONS_LOWERED"
|
|
481
813
|
};
|
|
814
|
+
var PAYMENT_ERROR_CODES = {
|
|
815
|
+
/**
|
|
816
|
+
* No gateway account takes new payment methods:
|
|
817
|
+
* `config/saas.yaml#payments.newPaymentMethods` names none.
|
|
818
|
+
*/
|
|
819
|
+
PAYMENTS_NOT_CONFIGURED: "PAYMENTS_NOT_CONFIGURED",
|
|
820
|
+
/** A callback arrived for an account `config/saas.yaml#payments.accounts` does not name. Carries `account`. */
|
|
821
|
+
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "PAYMENT_GATEWAY_ACCOUNT_UNKNOWN",
|
|
822
|
+
/** A callback the gateway did not send: its signature does not verify. */
|
|
823
|
+
PAYMENT_CALLBACK_REJECTED: "PAYMENT_CALLBACK_REJECTED",
|
|
824
|
+
/**
|
|
825
|
+
* A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
|
|
826
|
+
* does not name. Carries `field`.
|
|
827
|
+
*/
|
|
828
|
+
PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED"
|
|
829
|
+
};
|
|
830
|
+
var SETTINGS_ERROR_CODES = {
|
|
831
|
+
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
832
|
+
SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND"
|
|
833
|
+
};
|
|
482
834
|
var PLATFORM_ERROR_CODES = {
|
|
483
835
|
...SETUP_ERROR_CODES,
|
|
484
836
|
...AUTH_ERROR_CODES,
|
|
@@ -486,7 +838,10 @@ var PLATFORM_ERROR_CODES = {
|
|
|
486
838
|
...CATALOG_ERROR_CODES,
|
|
487
839
|
...BILLING_ERROR_CODES,
|
|
488
840
|
...CONTRACT_ERROR_CODES,
|
|
489
|
-
...
|
|
841
|
+
...SUBSCRIBER_ERROR_CODES,
|
|
842
|
+
...REGISTRATION_ERROR_CODES,
|
|
843
|
+
...PAYMENT_ERROR_CODES,
|
|
844
|
+
...SETTINGS_ERROR_CODES
|
|
490
845
|
};
|
|
491
846
|
|
|
492
847
|
// src/errors.ts
|
|
@@ -565,6 +920,19 @@ function selectChargeableBundles(planFeatures, selectedBundles) {
|
|
|
565
920
|
}
|
|
566
921
|
}
|
|
567
922
|
|
|
923
|
+
// src/payment-gateway.types.ts
|
|
924
|
+
var PAYMENT_CALLBACK_REJECTED = "PAYMENT_CALLBACK_REJECTED";
|
|
925
|
+
var PaymentCallbackRejectedError = class extends Error {
|
|
926
|
+
code = PAYMENT_CALLBACK_REJECTED;
|
|
927
|
+
constructor(reason) {
|
|
928
|
+
super(`Payment callback rejected: ${reason}`);
|
|
929
|
+
this.name = "PaymentCallbackRejectedError";
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
function isPaymentCallbackRejectedError(err) {
|
|
933
|
+
return err instanceof Error && err.code === PAYMENT_CALLBACK_REJECTED;
|
|
934
|
+
}
|
|
935
|
+
|
|
568
936
|
// src/registration.types.ts
|
|
569
937
|
var PENDING_EMAIL_TTL_HOURS = 72;
|
|
570
938
|
var PENDING_ONBOARDING_TTL_DAYS = 14;
|
|
@@ -584,6 +952,442 @@ var REGISTRATION_STEP_BY_STATUS = {
|
|
|
584
952
|
};
|
|
585
953
|
var REGISTRATION_RESUME_TTL_MINUTES = 60;
|
|
586
954
|
|
|
955
|
+
// src/plan-mapping.ts
|
|
956
|
+
function toPlanRow(row) {
|
|
957
|
+
return {
|
|
958
|
+
id: row.id,
|
|
959
|
+
planKey: row.planKey,
|
|
960
|
+
label: row.label,
|
|
961
|
+
description: row.description,
|
|
962
|
+
icon: row.icon,
|
|
963
|
+
sortOrder: row.sortOrder,
|
|
964
|
+
createdAt: row.createdAt.toISOString(),
|
|
965
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
966
|
+
deletedAt: toIsoOrNull(row.deletedAt)
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function toPlanVersionRow(row, planKey, fields) {
|
|
970
|
+
const mapped = {
|
|
971
|
+
id: row.id,
|
|
972
|
+
version: row.version,
|
|
973
|
+
baseVersionId: row.baseVersionId,
|
|
974
|
+
planId: planKey,
|
|
975
|
+
features: toFeatureKeys(row.features),
|
|
976
|
+
quotas: toQuotaEffects(row.quotas),
|
|
977
|
+
monthlyNet: String(row.monthlyNet),
|
|
978
|
+
yearlyNet: String(row.yearlyNet),
|
|
979
|
+
marketed: row.marketed,
|
|
980
|
+
publishedAt: toIsoOrNull(row.publishedAt),
|
|
981
|
+
supersededAt: toIsoOrNull(row.supersededAt),
|
|
982
|
+
publishedChanges: Array.isArray(row.publishedChanges) ? row.publishedChanges : null,
|
|
983
|
+
changeNote: row.changeNote,
|
|
984
|
+
nonRegressive: row.nonRegressive,
|
|
985
|
+
validFrom: fields.validityWindows ? toIsoOrNull(row.validFrom) : null,
|
|
986
|
+
validUntil: fields.validityWindows ? toIsoOrNull(row.validUntil) : null,
|
|
987
|
+
createdByUserId: row.createdByUserId,
|
|
988
|
+
publishedByUserId: row.publishedByUserId,
|
|
989
|
+
createdAt: row.createdAt.toISOString(),
|
|
990
|
+
updatedAt: row.updatedAt.toISOString()
|
|
991
|
+
};
|
|
992
|
+
if (fields.endsAt) {
|
|
993
|
+
mapped.endsAt = toIsoOrNull(row.endsAt);
|
|
994
|
+
}
|
|
995
|
+
return mapped;
|
|
996
|
+
}
|
|
997
|
+
function toIsoOrNull(value) {
|
|
998
|
+
return value ? value.toISOString() : null;
|
|
999
|
+
}
|
|
1000
|
+
function toFeatureKeys(value) {
|
|
1001
|
+
if (!Array.isArray(value)) return [];
|
|
1002
|
+
return value.filter((entry) => typeof entry === "string");
|
|
1003
|
+
}
|
|
1004
|
+
function toQuotaEffects(value) {
|
|
1005
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
1006
|
+
return {};
|
|
1007
|
+
}
|
|
1008
|
+
const quotas = {};
|
|
1009
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1010
|
+
quotas[key] = readQuotaValue(entry) ?? Number.NaN;
|
|
1011
|
+
}
|
|
1012
|
+
return quotas;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/legal-identity.ts
|
|
1016
|
+
var LEGAL_IDENTITY_FIELDS = [
|
|
1017
|
+
"legalName",
|
|
1018
|
+
"vatId",
|
|
1019
|
+
"taxNumber"
|
|
1020
|
+
];
|
|
1021
|
+
function sameLegalIdentity(before, after) {
|
|
1022
|
+
return LEGAL_IDENTITY_FIELDS.every((field) => before[field] === after[field]);
|
|
1023
|
+
}
|
|
1024
|
+
function movedIdentityFields(before, after) {
|
|
1025
|
+
return LEGAL_IDENTITY_FIELDS.filter((field) => before[field] !== after[field]);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/issuer-identity.ts
|
|
1029
|
+
function issuerIdentityOf(issuer) {
|
|
1030
|
+
if (!issuer) return null;
|
|
1031
|
+
return identityOf({
|
|
1032
|
+
legalName: issuer.legalName,
|
|
1033
|
+
vatId: issuer.vatId,
|
|
1034
|
+
taxNumber: issuer.taxNumber
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
function recordedIssuerIdentity(settings) {
|
|
1038
|
+
const issuer = settings?.issuer;
|
|
1039
|
+
if (issuer === null || typeof issuer !== "object" || Array.isArray(issuer)) return null;
|
|
1040
|
+
return identityOf(issuer);
|
|
1041
|
+
}
|
|
1042
|
+
function identityOf(source) {
|
|
1043
|
+
const legalName = textOrNull(source.legalName);
|
|
1044
|
+
if (legalName === null) return null;
|
|
1045
|
+
return {
|
|
1046
|
+
legalName,
|
|
1047
|
+
vatId: textOrNull(source.vatId),
|
|
1048
|
+
taxNumber: textOrNull(source.taxNumber)
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function classifyIssuerChange(recorded, issuer) {
|
|
1052
|
+
const current = issuerIdentityOf(issuer);
|
|
1053
|
+
if (!recorded)
|
|
1054
|
+
return current ? { kind: "first-naming", identity: current } : { kind: "none-named" };
|
|
1055
|
+
if (current && sameLegalIdentity(recorded, current))
|
|
1056
|
+
return { kind: "unchanged", identity: current };
|
|
1057
|
+
if (!current) {
|
|
1058
|
+
const moved2 = LEGAL_IDENTITY_FIELDS.filter((field) => recorded[field] !== null);
|
|
1059
|
+
return {
|
|
1060
|
+
kind: "undeclared",
|
|
1061
|
+
recorded,
|
|
1062
|
+
current: null,
|
|
1063
|
+
moved: moved2,
|
|
1064
|
+
fault: { kind: "names-no-issuer" }
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
const moved = movedIdentityFields(recorded, current);
|
|
1068
|
+
const fault = faultIn(issuer?.correctionOf, recorded, moved);
|
|
1069
|
+
if (fault) return { kind: "undeclared", recorded, current, moved, fault };
|
|
1070
|
+
const declaration = issuer?.correctionOf;
|
|
1071
|
+
return { kind: "corrected", recorded, current, moved, reason: declaration.reason };
|
|
1072
|
+
}
|
|
1073
|
+
function faultIn(declaration, recorded, moved) {
|
|
1074
|
+
if (!declaration) return { kind: "absent" };
|
|
1075
|
+
for (const field of LEGAL_IDENTITY_FIELDS) {
|
|
1076
|
+
if (!Object.prototype.hasOwnProperty.call(declaration, field)) continue;
|
|
1077
|
+
const declared = textOrNull(declaration[field]);
|
|
1078
|
+
if (declared !== recorded[field]) {
|
|
1079
|
+
return { kind: "names-another-value", field, declared, recorded: recorded[field] };
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
for (const field of moved) {
|
|
1083
|
+
if (Object.prototype.hasOwnProperty.call(declaration, field)) continue;
|
|
1084
|
+
return { kind: "leaves-a-field-out", field, recorded: recorded[field] };
|
|
1085
|
+
}
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
function textOrNull(value) {
|
|
1089
|
+
if (typeof value !== "string") return null;
|
|
1090
|
+
const trimmed = value.trim();
|
|
1091
|
+
return trimmed === "" ? null : trimmed;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/subscriber-mapping.ts
|
|
1095
|
+
function formatCustomerNumber(prefix, sequence) {
|
|
1096
|
+
return `${prefix}${sequence}`;
|
|
1097
|
+
}
|
|
1098
|
+
function toSubscriberRecord(row, tenantId) {
|
|
1099
|
+
return {
|
|
1100
|
+
id: row.id,
|
|
1101
|
+
customerNumber: formatCustomerNumber(row.customerNumberPrefix, row.customerSequence),
|
|
1102
|
+
legalName: row.legalName,
|
|
1103
|
+
addressLine1: row.addressLine1,
|
|
1104
|
+
addressLine2: row.addressLine2,
|
|
1105
|
+
postalCode: row.postalCode,
|
|
1106
|
+
city: row.city,
|
|
1107
|
+
country: row.country,
|
|
1108
|
+
vatId: row.vatId,
|
|
1109
|
+
taxNumber: row.taxNumber,
|
|
1110
|
+
invoiceEmail: row.invoiceEmail,
|
|
1111
|
+
tenantId,
|
|
1112
|
+
migrated: row.migrated,
|
|
1113
|
+
createdAt: row.createdAt,
|
|
1114
|
+
updatedAt: row.updatedAt
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function toSubscriberCorrectionRecord(row) {
|
|
1118
|
+
return {
|
|
1119
|
+
id: row.id,
|
|
1120
|
+
subscriberId: row.subscriberId,
|
|
1121
|
+
previous: toIdentityValues(row.previous),
|
|
1122
|
+
corrected: toIdentityValues(row.corrected),
|
|
1123
|
+
reason: row.reason,
|
|
1124
|
+
correctedBy: row.correctedBy,
|
|
1125
|
+
correctedAt: row.correctedAt
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function identityCorrectionDelta(current, corrected) {
|
|
1129
|
+
const previous = {};
|
|
1130
|
+
const next = {};
|
|
1131
|
+
for (const field of LEGAL_IDENTITY_FIELDS) {
|
|
1132
|
+
const value = corrected[field];
|
|
1133
|
+
if (value === void 0 || value === current[field]) continue;
|
|
1134
|
+
previous[field] = current[field];
|
|
1135
|
+
next[field] = value;
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
previous,
|
|
1139
|
+
corrected: next
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
function contractPartiesOf(subscriber, issuer) {
|
|
1143
|
+
const party = {
|
|
1144
|
+
customerNumber: subscriber.customerNumber,
|
|
1145
|
+
legalName: subscriber.legalName,
|
|
1146
|
+
vatId: subscriber.vatId,
|
|
1147
|
+
taxNumber: subscriber.taxNumber,
|
|
1148
|
+
addressLine1: subscriber.addressLine1,
|
|
1149
|
+
addressLine2: subscriber.addressLine2,
|
|
1150
|
+
postalCode: subscriber.postalCode,
|
|
1151
|
+
city: subscriber.city,
|
|
1152
|
+
country: subscriber.country
|
|
1153
|
+
};
|
|
1154
|
+
return {
|
|
1155
|
+
subscriberId: subscriber.id,
|
|
1156
|
+
subscriber: party,
|
|
1157
|
+
issuer: issuer ? issuerPartyOf(issuer) : null
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function issuerPartyOf(issuer) {
|
|
1161
|
+
const identity = issuerIdentityOf(issuer);
|
|
1162
|
+
return {
|
|
1163
|
+
legalName: identity?.legalName ?? "",
|
|
1164
|
+
vatId: identity?.vatId ?? null,
|
|
1165
|
+
taxNumber: identity?.taxNumber ?? null,
|
|
1166
|
+
// Settled like the three above rather than copied verbatim: one copy
|
|
1167
|
+
// whose halves were settled differently is a row that reads two ways,
|
|
1168
|
+
// and a city written with a trailing space is not another city.
|
|
1169
|
+
addressLine1: settledText(issuer.addressLine1),
|
|
1170
|
+
addressLine2: settledText(issuer.addressLine2),
|
|
1171
|
+
postalCode: settledText(issuer.postalCode),
|
|
1172
|
+
city: settledText(issuer.city),
|
|
1173
|
+
country: settledText(issuer.country)
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function settledText(value) {
|
|
1177
|
+
if (typeof value !== "string") return null;
|
|
1178
|
+
const trimmed = value.trim();
|
|
1179
|
+
return trimmed === "" ? null : trimmed;
|
|
1180
|
+
}
|
|
1181
|
+
function subscriberFromRegistration(pending) {
|
|
1182
|
+
return {
|
|
1183
|
+
legalName: pending.tenantName,
|
|
1184
|
+
invoiceEmail: pending.email,
|
|
1185
|
+
addressLine1: pending.addressLine1,
|
|
1186
|
+
addressLine2: pending.addressLine2,
|
|
1187
|
+
postalCode: pending.postalCode,
|
|
1188
|
+
city: pending.city,
|
|
1189
|
+
country: pending.country,
|
|
1190
|
+
vatId: pending.vatId,
|
|
1191
|
+
taxNumber: pending.taxNumber
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function toIdentityValues(value) {
|
|
1195
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
|
|
1196
|
+
const source = value;
|
|
1197
|
+
const values = {};
|
|
1198
|
+
for (const field of LEGAL_IDENTITY_FIELDS) {
|
|
1199
|
+
if (!Object.prototype.hasOwnProperty.call(source, field)) continue;
|
|
1200
|
+
const entry = source[field];
|
|
1201
|
+
values[field] = typeof entry === "string" ? entry : null;
|
|
1202
|
+
}
|
|
1203
|
+
return values;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/subscriber-payment-method-mapping.ts
|
|
1207
|
+
var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
|
|
1208
|
+
var STATUSES = ["ACTIVE", "REPLACED"];
|
|
1209
|
+
function toSubscriberPaymentMethodRecord(row) {
|
|
1210
|
+
return {
|
|
1211
|
+
id: row.id,
|
|
1212
|
+
subscriberId: row.subscriberId,
|
|
1213
|
+
gatewayAccount: row.gatewayAccount,
|
|
1214
|
+
provider: row.provider,
|
|
1215
|
+
customerRef: row.customerRef,
|
|
1216
|
+
paymentMethodRef: row.paymentMethodRef,
|
|
1217
|
+
type: oneOf(PAYMENT_METHOD_TYPES, row.type, "type", row.id),
|
|
1218
|
+
brand: row.brand,
|
|
1219
|
+
last4: row.last4,
|
|
1220
|
+
expiryMonth: row.expiryMonth,
|
|
1221
|
+
expiryYear: row.expiryYear,
|
|
1222
|
+
country: row.country,
|
|
1223
|
+
bankCode: row.bankCode,
|
|
1224
|
+
mandateReference: row.mandateReference,
|
|
1225
|
+
status: oneOf(STATUSES, row.status, "status", row.id),
|
|
1226
|
+
confirmedAt: row.confirmedAt,
|
|
1227
|
+
replacedAt: row.replacedAt,
|
|
1228
|
+
createdAt: row.createdAt
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
function subscriberPaymentMethodColumns(data) {
|
|
1232
|
+
return {
|
|
1233
|
+
subscriberId: data.subscriberId,
|
|
1234
|
+
gatewayAccount: data.gatewayAccount,
|
|
1235
|
+
provider: data.provider,
|
|
1236
|
+
customerRef: data.customerRef,
|
|
1237
|
+
paymentMethodRef: data.paymentMethodRef,
|
|
1238
|
+
type: data.type,
|
|
1239
|
+
brand: data.brand,
|
|
1240
|
+
last4: data.last4,
|
|
1241
|
+
expiryMonth: data.expiryMonth,
|
|
1242
|
+
expiryYear: data.expiryYear,
|
|
1243
|
+
country: data.country,
|
|
1244
|
+
bankCode: data.bankCode,
|
|
1245
|
+
mandateReference: data.mandateReference,
|
|
1246
|
+
confirmedAt: data.confirmedAt
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function oneOf(allowed, value, column, id) {
|
|
1250
|
+
const match = allowed.find((entry) => entry === value);
|
|
1251
|
+
if (match === void 0) {
|
|
1252
|
+
throw new Error(
|
|
1253
|
+
`subscriber_payment_methods row '${id}' holds ${column} '${value}', which is none of ${allowed.join(", ")}.`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
return match;
|
|
1257
|
+
}
|
|
1258
|
+
var FOREIGN_PAYMENT_METHOD_REFERENCE = "FOREIGN_PAYMENT_METHOD_REFERENCE";
|
|
1259
|
+
var ForeignPaymentMethodReferenceError = class extends Error {
|
|
1260
|
+
constructor(gatewayAccount, paymentMethodRef) {
|
|
1261
|
+
super(
|
|
1262
|
+
`Payment method '${paymentMethodRef}' of account '${gatewayAccount}' belongs to another subscriber. A reference belongs to exactly one, so it is not handed out.`
|
|
1263
|
+
);
|
|
1264
|
+
this.gatewayAccount = gatewayAccount;
|
|
1265
|
+
this.paymentMethodRef = paymentMethodRef;
|
|
1266
|
+
this.name = "ForeignPaymentMethodReferenceError";
|
|
1267
|
+
}
|
|
1268
|
+
gatewayAccount;
|
|
1269
|
+
paymentMethodRef;
|
|
1270
|
+
code = FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
1271
|
+
};
|
|
1272
|
+
function isForeignPaymentMethodReferenceError(error) {
|
|
1273
|
+
return error instanceof Error && error.code === FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
1274
|
+
}
|
|
1275
|
+
function refuseForeignPaymentMethodReference(recorded, subscriberId) {
|
|
1276
|
+
if (recorded.subscriberId === subscriberId) return;
|
|
1277
|
+
throw new ForeignPaymentMethodReferenceError(
|
|
1278
|
+
recorded.gatewayAccount,
|
|
1279
|
+
recorded.paymentMethodRef
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// src/subscription-contract-mapping.ts
|
|
1284
|
+
function toSubscriptionContractRecord(row, lineItems) {
|
|
1285
|
+
return {
|
|
1286
|
+
id: row.id,
|
|
1287
|
+
tenantId: row.tenantId,
|
|
1288
|
+
subscriberId: row.subscriberId,
|
|
1289
|
+
subscriber: toSubscriberParty(row.subscriberSnapshot),
|
|
1290
|
+
issuer: isPlainObject2(row.issuerSnapshot) ? toIssuerParty(row.issuerSnapshot) : null,
|
|
1291
|
+
partiesMigrated: row.partiesMigrated,
|
|
1292
|
+
status: row.status,
|
|
1293
|
+
effectiveFrom: row.effectiveFrom,
|
|
1294
|
+
effectiveUntil: row.effectiveUntil,
|
|
1295
|
+
originalOfferId: row.originalOfferId,
|
|
1296
|
+
originalPlanVersionId: row.originalPlanVersionId,
|
|
1297
|
+
originalBundleVersionIds: toStringArray(row.originalBundleVersionIds),
|
|
1298
|
+
entitlementSnapshot: isPlainObject2(row.entitlementSnapshot) ? row.entitlementSnapshot : null,
|
|
1299
|
+
priceSnapshot: row.priceSnapshot,
|
|
1300
|
+
promotionSnapshots: toUnknownArray(row.promotionSnapshots),
|
|
1301
|
+
promoCodeSnapshots: toUnknownArray(row.promoCodeSnapshots),
|
|
1302
|
+
termsSnapshot: toRecordOrNull(row.termsSnapshot),
|
|
1303
|
+
lineItems: lineItems.map(toContractLineItemRecord),
|
|
1304
|
+
createdAt: row.createdAt,
|
|
1305
|
+
updatedAt: row.updatedAt
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
function toContractLineItemRecord(row) {
|
|
1309
|
+
return {
|
|
1310
|
+
id: row.id,
|
|
1311
|
+
contractId: row.contractId,
|
|
1312
|
+
kind: row.kind,
|
|
1313
|
+
sourceKey: row.sourceKey,
|
|
1314
|
+
sourceVersionId: row.sourceVersionId,
|
|
1315
|
+
titleSnapshot: row.titleSnapshot,
|
|
1316
|
+
descriptionSnapshot: row.descriptionSnapshot,
|
|
1317
|
+
quantity: row.quantity,
|
|
1318
|
+
unit: row.unit,
|
|
1319
|
+
priceNet: Number(row.priceNet),
|
|
1320
|
+
priceGross: Number(row.priceGross),
|
|
1321
|
+
billingCycle: row.billingCycle,
|
|
1322
|
+
currency: row.currency,
|
|
1323
|
+
taxRate: Number(row.taxRate),
|
|
1324
|
+
taxAmount: Number(row.taxAmount),
|
|
1325
|
+
minimumTermUntil: row.minimumTermUntil,
|
|
1326
|
+
featuresSnapshot: toStringArray(row.featuresSnapshot),
|
|
1327
|
+
quotaEffectsSnapshot: toQuotaEffects2(row.quotaEffectsSnapshot),
|
|
1328
|
+
metadata: toRecordOrNull(row.metadata),
|
|
1329
|
+
createdAt: row.createdAt
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
function toSubscriberParty(value) {
|
|
1333
|
+
const source = isPlainObject2(value) ? value : {};
|
|
1334
|
+
return {
|
|
1335
|
+
customerNumber: stringOr(source.customerNumber, ""),
|
|
1336
|
+
...toIssuerParty(source)
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
function toRunningContractIssuer(row) {
|
|
1340
|
+
const issuer = isPlainObject2(row.issuerSnapshot) ? row.issuerSnapshot : null;
|
|
1341
|
+
return {
|
|
1342
|
+
id: row.id,
|
|
1343
|
+
tenantId: row.tenantId,
|
|
1344
|
+
issuerLegalName: issuer ? stringOrNull(issuer.legalName) : null,
|
|
1345
|
+
effectiveFrom: row.effectiveFrom
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function toIssuerParty(value) {
|
|
1349
|
+
const source = value;
|
|
1350
|
+
return {
|
|
1351
|
+
legalName: stringOr(source.legalName, ""),
|
|
1352
|
+
vatId: stringOrNull(source.vatId),
|
|
1353
|
+
taxNumber: stringOrNull(source.taxNumber),
|
|
1354
|
+
addressLine1: stringOrNull(source.addressLine1),
|
|
1355
|
+
addressLine2: stringOrNull(source.addressLine2),
|
|
1356
|
+
postalCode: stringOrNull(source.postalCode),
|
|
1357
|
+
city: stringOrNull(source.city),
|
|
1358
|
+
country: stringOrNull(source.country)
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function stringOr(value, fallback) {
|
|
1362
|
+
return typeof value === "string" ? value : fallback;
|
|
1363
|
+
}
|
|
1364
|
+
function stringOrNull(value) {
|
|
1365
|
+
return typeof value === "string" ? value : null;
|
|
1366
|
+
}
|
|
1367
|
+
function isPlainObject2(value) {
|
|
1368
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1369
|
+
}
|
|
1370
|
+
function toUnknownArray(value) {
|
|
1371
|
+
return Array.isArray(value) ? value : [];
|
|
1372
|
+
}
|
|
1373
|
+
function toRecordOrNull(value) {
|
|
1374
|
+
return isPlainObject2(value) ? value : null;
|
|
1375
|
+
}
|
|
1376
|
+
function toStringArray(value) {
|
|
1377
|
+
if (!Array.isArray(value)) return [];
|
|
1378
|
+
return value.filter((entry) => typeof entry === "string");
|
|
1379
|
+
}
|
|
1380
|
+
function toQuotaEffects2(value) {
|
|
1381
|
+
return isPlainObject2(value) ? readQuotaRecord(value) : {};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/subscription-contract.types.ts
|
|
1385
|
+
var BUNDLE_PRICE_LOOKUP_LIMIT = 200;
|
|
1386
|
+
var ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES = [
|
|
1387
|
+
"active",
|
|
1388
|
+
"scheduled"
|
|
1389
|
+
];
|
|
1390
|
+
|
|
587
1391
|
// src/version-editability.ts
|
|
588
1392
|
function isVersionEditable(v, now = /* @__PURE__ */ new Date()) {
|
|
589
1393
|
if (v.publishedAt === null) {
|
|
@@ -611,6 +1415,17 @@ function isVersionEditable(v, now = /* @__PURE__ */ new Date()) {
|
|
|
611
1415
|
return { editable: true, reason: "pre-active" };
|
|
612
1416
|
}
|
|
613
1417
|
|
|
1418
|
+
// src/recommended-plan.ts
|
|
1419
|
+
function keepOneRecommended(plans, inRequestedLocale) {
|
|
1420
|
+
const recommended = plans.filter((plan) => plan.highlight);
|
|
1421
|
+
if (recommended.length === 0) return null;
|
|
1422
|
+
const winner = recommended.find((plan) => inRequestedLocale.has(plan.planKey)) ?? recommended[0];
|
|
1423
|
+
for (const plan of recommended) {
|
|
1424
|
+
if (plan !== winner) plan.highlight = false;
|
|
1425
|
+
}
|
|
1426
|
+
return winner;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
614
1429
|
// src/error-messages.ts
|
|
615
1430
|
var ERROR_MESSAGES_EN = {
|
|
616
1431
|
// ── setup ──
|
|
@@ -624,6 +1439,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
624
1439
|
NO_TENANT_ASSIGNED: "No tenant assigned",
|
|
625
1440
|
TENANT_CONTEXT_MISSING: "No tenant ID found on the request",
|
|
626
1441
|
TENANT_ADMIN_REQUIRED: "This action requires the TENANT_ADMIN role.",
|
|
1442
|
+
BILLING_PERMISSION_REQUIRED: "This part of the billing area requires the billing permission.",
|
|
627
1443
|
SUPER_ADMIN_REQUIRED: "Only the SUPER_ADMIN role is allowed",
|
|
628
1444
|
MFA_NOT_SET_UP: "Run the MFA setup via the CLI first.",
|
|
629
1445
|
MFA_REQUIRED: "TOTP code required in the X-Mfa-Code header.",
|
|
@@ -655,6 +1471,8 @@ var ERROR_MESSAGES_EN = {
|
|
|
655
1471
|
BUNDLE_VERSION_SUPERSEDED: "BundleVersion '{bundleVersionId}' has been superseded by a newer version.",
|
|
656
1472
|
BUNDLE_VERSION_REGRESSION: "This bundle version is regressive (feature removed / quota lowered / price raised). Publishing requires an explicit `forceRegressive: true` (UI confirmation dialog with MFA).",
|
|
657
1473
|
BUNDLE_VERSION_ZERO_PRICE: "A bundle version cannot be published with an explicit price of 0.00 (guard against seed placeholders). For free bundles leave it null, or set allowZeroPrice.",
|
|
1474
|
+
BUNDLE_VERSION_NO_PRICE: "This bundle version cannot be published without a price: neither a base price nor any plan override resolves one.",
|
|
1475
|
+
BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "This bundle is offered to plan '{planKey}', which is sold {billingCycle}, and no {billingCycle} price resolves for it \u2014 neither a base price nor a plan override.",
|
|
658
1476
|
BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "Discard is not implemented in the current repository. Implement BundleRepository.deleteDraft.",
|
|
659
1477
|
BUNDLE_VERSION_VALID_FROM_REQUIRED: "Published bundle versions must keep a validFrom. Set a new future date, or edit the draft before publishing.",
|
|
660
1478
|
BUNDLE_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' is not a valid date",
|
|
@@ -668,22 +1486,28 @@ var ERROR_MESSAGES_EN = {
|
|
|
668
1486
|
PLAN_VERSION_NOT_FOUND: "PlanVersion '{versionId}' not found",
|
|
669
1487
|
BUNDLE_NOT_FOUND: "Bundle '{bundleId}' not found",
|
|
670
1488
|
BUNDLE_VERSION_NOT_FOUND: "BundleVersion '{bundleVersionId}' not found",
|
|
671
|
-
FEATURE_NOT_FOUND: "Feature '{featureKey}' not found
|
|
672
|
-
QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found
|
|
1489
|
+
FEATURE_NOT_FOUND: "Feature '{featureKey}' not found",
|
|
1490
|
+
QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found",
|
|
673
1491
|
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' not found",
|
|
1492
|
+
PROMOTION_VALUE_INVALID: "The value of a '{type}' promotion is not one its type takes: a percentage above 0 and at most 100, an amount above 0, an intro price of at least 0 for a whole number of months, or a whole number of free months.",
|
|
674
1493
|
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' not found",
|
|
675
|
-
PLAN_ALREADY_EXISTS: "Plan '{planKey}' already exists
|
|
676
|
-
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' already exists
|
|
1494
|
+
PLAN_ALREADY_EXISTS: "Plan '{planKey}' already exists",
|
|
1495
|
+
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' already exists",
|
|
677
1496
|
MARKETING_PROJECTION_ALREADY_EXISTS: "Marketing projection for {targetType}/{targetVersionId}/{locale} already exists \u2014 use PATCH to edit it",
|
|
678
1497
|
PLAN_DRAFT_ALREADY_EXISTS: "Plan '{planKey}' already has a draft version v{draftVersion}; publish or discard it first",
|
|
679
1498
|
BUNDLE_DRAFT_ALREADY_EXISTS: "Bundle '{bundleKey}' already has a draft version v{draftVersion}; publish or discard it first",
|
|
680
1499
|
QUOTA_NOT_IN_DISCOVERY_SNAPSHOT: "Quota '{quotaKey}' is not in the discovery snapshot \u2014 a quota that is not declared in code cannot be approved",
|
|
681
1500
|
DISCOVERY_STATUS_TRANSITION_INVALID: "Transition '{from}' \u2192 '{to}' is not allowed",
|
|
682
1501
|
DISCOVERY_NOT_INITIALIZED: "Approval requires a discovery snapshot \u2014 discovery is not initialised (#25)",
|
|
1502
|
+
// ── catalogue import ──
|
|
1503
|
+
PLAN_CATALOG_UNREADABLE: "That file is not a plan catalog \u2014 it could not be read as one. Check that it is the YAML your app declares its plans in.",
|
|
1504
|
+
PLAN_CATALOG_INVALID: "The plan catalog was read, and then rejected: {message}",
|
|
683
1505
|
// ── billing ──
|
|
684
1506
|
BUNDLE_ALREADY_SUBSCRIBED: "Subscription '{subscriptionId}' has already actively booked this bundle.",
|
|
685
1507
|
BUNDLE_INCOMPATIBLE_WITH_PLAN: "BundleVersion '{bundleVersionId}' is not compatible with plan '{planKey}'. Allowed: [{allowedPlanKeys}].",
|
|
686
1508
|
BUNDLE_NOT_SELF_SERVICE: "Bundle '{bundleKey}' is only activated via a special contract. Please contact the contract manager.",
|
|
1509
|
+
BUNDLE_CYCLE_EXCEEDS_PLAN: "A yearly bundle cannot run beside a monthly plan: it would still be committed on every day the plan could end.",
|
|
1510
|
+
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "This bundle has no {billingCycle} price for the {planKey} plan, so it cannot be booked from here.",
|
|
687
1511
|
SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' is already cancelled.",
|
|
688
1512
|
SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' is not cancelled.",
|
|
689
1513
|
SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "Cancellation already in effect \u2014 book the bundle again.",
|
|
@@ -692,9 +1516,25 @@ var ERROR_MESSAGES_EN = {
|
|
|
692
1516
|
SUBSCRIPTION_BUNDLE_NOT_FOUND: "SubscriptionBundle '{subscriptionBundleId}' not found",
|
|
693
1517
|
TENANT_NOT_FOUND: "Tenant {slug} not found",
|
|
694
1518
|
NO_ACTIVE_PLAN_VERSION: "No plan version for {planId} active as of {asOf} \u2014 neither the validFrom window is satisfied nor is a latest-live version available.",
|
|
695
|
-
PLAN_NOT_IN_CATALOG: 'Plan "{planKey}" is not in the catalog
|
|
696
|
-
PLAN_NOT_SELF_SERVICE: "{
|
|
1519
|
+
PLAN_NOT_IN_CATALOG: 'Plan "{planKey}" is not in the catalog',
|
|
1520
|
+
PLAN_NOT_SELF_SERVICE: "{planName} is only activated via a special contract. Please contact the contract manager.",
|
|
1521
|
+
PLAN_NOT_SOLD_IN_CYCLE: "{planName} has no price for this billing rhythm and cannot be booked in it.",
|
|
697
1522
|
PLAN_CHANGE_BLOCKED: "Plan change during onboarding is blocked.",
|
|
1523
|
+
SUBSCRIPTION_CHANGED: "This subscription changed while the request was being decided. Reload it.",
|
|
1524
|
+
NO_SUBSCRIPTION: "This tenant has no subscription to cancel.",
|
|
1525
|
+
CANCELLATION_TERMS_CHANGED: "The effective date changed since it was shown. Confirm the new one.",
|
|
1526
|
+
SUBSCRIPTION_ENDED: "This subscription has ended. Its plan can no longer be changed.",
|
|
1527
|
+
PLAN_LOCKED: "Active {planName} special contract \u2014 please contact the contract manager to change plans.",
|
|
1528
|
+
QUOTA_OVER_TARGET: "Current usage {used} exceeds the target limit {targetMax} ({quotaKey}) in the {planName} plan. Please reduce usage first.",
|
|
1529
|
+
FEATURE_LOST: "Switching means losing access to one feature. Existing data is retained and never deleted \u2014 upgrading again unlocks it.",
|
|
1530
|
+
FEATURES_LOST: "Switching means losing access to {count} features. Existing data is retained and never deleted \u2014 upgrading again unlocks it.",
|
|
1531
|
+
NO_CHANGE: "Target plan and billing cycle already match the current state.",
|
|
1532
|
+
CANCELLATION_LOCKS_THE_CYCLE: "This subscription is cancelled, so its billing cycle cannot change. The plan can \u2014 keep the current cycle to change it today.",
|
|
1533
|
+
CYCLE_SHORTENS_AT_TERM_END: "A monthly {planName} cannot start inside the yearly term you are in. The upgrade takes effect when that term ends; to have it today, keep the yearly cycle.",
|
|
1534
|
+
BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "A yearly bundle is booked until {until}. A monthly plan cannot carry it \u2014 cancel the bundle first, or keep the yearly cycle.",
|
|
1535
|
+
REDUNDANT_FEATURES: "The plan or another booked bundle already includes {count} of the features in this bundle \u2014 booking it pays for them twice.",
|
|
1536
|
+
MINIMUM_TERM_BINDS: "The minimum term extends beyond the end of the period \u2014 the cancellation only takes effect when the minimum term ends.",
|
|
1537
|
+
BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "The bundle requires [{features}] \u2014 present neither in the plan nor in the active bundles.",
|
|
698
1538
|
NO_PENDING_PLAN_VERSION: "There is no pending plan version awaiting confirmation.",
|
|
699
1539
|
ONBOARDING_CREATE_FAILED: "The account could not be created. Please try again.",
|
|
700
1540
|
BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "Exactly one of bundleVersionId (add preview) or subscriptionBundleId (cancel preview) must be given.",
|
|
@@ -704,7 +1544,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
704
1544
|
// ── promo ──
|
|
705
1545
|
PROMO_CODE_NOT_FOUND: "Code not found",
|
|
706
1546
|
PROMO_CODE_ALREADY_EXISTS: "The code already exists.",
|
|
707
|
-
PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
1547
|
+
PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions, or checkouts holding one \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
708
1548
|
PROMO_CODE_NOT_REDEEMABLE: "Code cannot be redeemed: {reason}",
|
|
709
1549
|
PROMO_CODE_FORMAT_INVALID: 'The code may only contain upper-case letters, digits, "-" and "_" (4\u201332 characters).',
|
|
710
1550
|
PROMO_PERCENT_OUT_OF_RANGE: "The percentage must be between 0 and 100.",
|
|
@@ -722,18 +1562,39 @@ var ERROR_MESSAGES_EN = {
|
|
|
722
1562
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Every selected bundle version requires a frozen bundle line item.",
|
|
723
1563
|
CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE: "At least one bundle version from the checkout offer is no longer bookable.",
|
|
724
1564
|
CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED: "The selected plan does not cover all feature dependencies: [{missingRequires}] are missing from the plan + selected bundles.",
|
|
1565
|
+
CHECKOUT_OFFER_PLAN_NOT_OFFERED: "Plan '{planKey}' is not offered with a {billingCycle} price at the moment.",
|
|
1566
|
+
CHECKOUT_OFFER_BUNDLE_NOT_OFFERED: "Bundle version '{bundleVersionId}' cannot be added to this offer ({reason}).",
|
|
1567
|
+
CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED: "The promo code cannot be applied to this offer ({reason}).",
|
|
1568
|
+
CHECKOUT_OFFER_PRICE_NOT_CURRENT: "The prices of checkout offer '{offerId}' no longer match the catalogue; create a new offer.",
|
|
725
1569
|
SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED: "A subscription contract requires at least one line item.",
|
|
726
1570
|
SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "A subscription contract requires exactly one plan base item.",
|
|
727
1571
|
SUBSCRIPTION_CONTRACT_INVALID_DATE: "{field} must be a valid date.",
|
|
728
1572
|
SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "effectiveUntil must be after effectiveFrom.",
|
|
1573
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "A line item's taxAmount must be exactly priceGross minus priceNet.",
|
|
1574
|
+
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "A subscription contract states a tax rate of {taxRate} at {field}, which is not a percentage: a rate lies from 0 to 100, and a value between 0 and 1 is refused as a fraction.",
|
|
729
1575
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil must be after the effectiveFrom of the contract.",
|
|
1576
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "A line item must be booked in the currency its contract was priced in.",
|
|
1577
|
+
SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "The line items of a subscription contract add up to {lines} for {field}, but the contract states {stated}.",
|
|
1578
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "A subscription contract states a discount of {amount} at {field}; a discount takes money off and is never negative.",
|
|
730
1579
|
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' not found",
|
|
731
1580
|
CHECKOUT_OFFER_EXPIRED: "Checkout offer '{offerId}' has expired and cannot be {action}",
|
|
732
1581
|
CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
|
|
733
1582
|
CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' must be consumed before the contract is created",
|
|
1583
|
+
CHECKOUT_OFFER_CHANGED: "Checkout offer '{offerId}' changed while it was being concluded. Load it again.",
|
|
734
1584
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
|
|
735
1585
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
|
|
736
1586
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' is already closed",
|
|
1587
|
+
// ── subscriber ──
|
|
1588
|
+
SUBSCRIBER_REQUIRED: "Tenant '{tenantId}' has no subscriber. Nothing is agreed or charged without the party to it: create the tenant's subscriber first.",
|
|
1589
|
+
SUBSCRIBER_ALREADY_EXISTS: "Tenant '{tenantId}' already has a subscriber.",
|
|
1590
|
+
SUBSCRIBER_NOT_FOUND: "Subscriber '{subscriberId}' not found",
|
|
1591
|
+
SUBSCRIBER_LEGAL_NAME_REQUIRED: "A subscriber needs its legal name.",
|
|
1592
|
+
SUBSCRIBER_DETAIL_INVALID: "The subscriber's {field} is not valid.",
|
|
1593
|
+
SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} is part of the subscriber's legal identity and changes only as a correction, with a reason.",
|
|
1594
|
+
SUBSCRIBER_CORRECTION_REASON_REQUIRED: "A correction of the legal identity needs a reason.",
|
|
1595
|
+
SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "A correction of the legal identity has to say who makes it.",
|
|
1596
|
+
SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "The correction changes nothing: every value it names is already recorded.",
|
|
1597
|
+
SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "Another legal entity taking over a subscriber is a transfer, not a correction, and cannot be recorded as an edit.",
|
|
737
1598
|
// ── registration ──
|
|
738
1599
|
PENDING_REGISTRATION_NOT_FOUND: "This registration could not be found. It may have already been completed or discarded.",
|
|
739
1600
|
PENDING_REGISTRATION_EXPIRED: "This registration has expired. Please start again.",
|
|
@@ -749,8 +1610,15 @@ var ERROR_MESSAGES_EN = {
|
|
|
749
1610
|
PLAN_NOT_AVAILABLE: "This plan is not available.",
|
|
750
1611
|
PLAN_NOT_SELECTED: "Please select a plan first.",
|
|
751
1612
|
MODEL_NOT_AVAILABLE: "This option is not available.",
|
|
1613
|
+
// ── payments ──
|
|
1614
|
+
PAYMENTS_NOT_CONFIGURED: "Payment methods cannot be set up here yet.",
|
|
1615
|
+
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "No payment gateway account is configured under '{account}'.",
|
|
1616
|
+
PAYMENT_CALLBACK_REJECTED: "The payment callback could not be verified.",
|
|
1617
|
+
PAYMENT_RETURN_URL_NOT_ALLOWED: "The {field} leads to a site this installation does not return to.",
|
|
752
1618
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
753
|
-
[FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan."
|
|
1619
|
+
[FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan.",
|
|
1620
|
+
// ── settings ──
|
|
1621
|
+
SETTINGS_CHANGE_NOT_FOUND: "No recorded settings change has this id."
|
|
754
1622
|
};
|
|
755
1623
|
function formatErrorMessage(template, params = {}) {
|
|
756
1624
|
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
@@ -781,6 +1649,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
781
1649
|
NO_TENANT_ASSIGNED: "Kein Mandant zugeordnet",
|
|
782
1650
|
TENANT_CONTEXT_MISSING: "Am Request wurde keine Mandanten-ID gefunden",
|
|
783
1651
|
TENANT_ADMIN_REQUIRED: "Diese Aktion erfordert die Rolle TENANT_ADMIN.",
|
|
1652
|
+
BILLING_PERMISSION_REQUIRED: "Dieser Teil der Abrechnung erfordert die Abrechnungs-Berechtigung.",
|
|
784
1653
|
SUPER_ADMIN_REQUIRED: "Nur die Rolle SUPER_ADMIN ist zugelassen",
|
|
785
1654
|
MFA_NOT_SET_UP: "Richte MFA zuerst \xFCber die CLI ein.",
|
|
786
1655
|
MFA_REQUIRED: "TOTP-Code im Header X-Mfa-Code erforderlich.",
|
|
@@ -812,6 +1681,8 @@ var ERROR_MESSAGES_DE = {
|
|
|
812
1681
|
BUNDLE_VERSION_SUPERSEDED: "BundleVersion '{bundleVersionId}' wurde durch eine neuere Version abgel\xF6st.",
|
|
813
1682
|
BUNDLE_VERSION_REGRESSION: "Diese Bundle-Version ist regressiv (Feature entfernt / Quota gesenkt / Preis erh\xF6ht). Das Ver\xF6ffentlichen erfordert ein ausdr\xFCckliches `forceRegressive: true` (Best\xE4tigungsdialog in der UI mit MFA).",
|
|
814
1683
|
BUNDLE_VERSION_ZERO_PRICE: "Eine Bundle-Version l\xE4sst sich nicht mit einem ausdr\xFCcklichen Preis von 0,00 ver\xF6ffentlichen (Schutz vor Seed-Platzhaltern). Kostenlose Bundles l\xE4sst du auf null, oder du setzt allowZeroPrice.",
|
|
1684
|
+
BUNDLE_VERSION_NO_PRICE: "Diese Bundle-Version kann ohne Preis nicht ver\xF6ffentlicht werden: weder ein Grundpreis noch eine Plan-\xDCberschreibung ergibt einen.",
|
|
1685
|
+
BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "Dieses Bundle wird dem Plan '{planKey}' angeboten, der {billingCycle} verkauft wird, und daf\xFCr ergibt sich kein {billingCycle} Preis \u2014 weder als Grundpreis noch als Plan-\xDCberschreibung.",
|
|
815
1686
|
BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "Das Verwerfen ist im aktuellen Repository nicht implementiert. Implementiere BundleRepository.deleteDraft.",
|
|
816
1687
|
BUNDLE_VERSION_VALID_FROM_REQUIRED: "Ver\xF6ffentlichte Bundle-Versionen m\xFCssen ein validFrom behalten. Setze ein neues Datum in der Zukunft, oder bearbeite den Entwurf vor dem Ver\xF6ffentlichen.",
|
|
817
1688
|
BUNDLE_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' ist kein g\xFCltiges Datum",
|
|
@@ -825,22 +1696,28 @@ var ERROR_MESSAGES_DE = {
|
|
|
825
1696
|
PLAN_VERSION_NOT_FOUND: "PlanVersion '{versionId}' nicht gefunden",
|
|
826
1697
|
BUNDLE_NOT_FOUND: "Bundle '{bundleId}' nicht gefunden",
|
|
827
1698
|
BUNDLE_VERSION_NOT_FOUND: "BundleVersion '{bundleVersionId}' nicht gefunden",
|
|
828
|
-
FEATURE_NOT_FOUND: "Feature '{featureKey}'
|
|
829
|
-
QUOTA_NOT_FOUND: "Quota '{quotaKey}'
|
|
1699
|
+
FEATURE_NOT_FOUND: "Feature '{featureKey}' nicht gefunden",
|
|
1700
|
+
QUOTA_NOT_FOUND: "Quota '{quotaKey}' nicht gefunden",
|
|
830
1701
|
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' nicht gefunden",
|
|
1702
|
+
PROMOTION_VALUE_INVALID: "Der Wert einer Aktion vom Typ '{type}' passt nicht zu ihrem Typ: ein Prozentsatz \xFCber 0 und h\xF6chstens 100, ein Betrag \xFCber 0, ein Einf\xFChrungspreis ab 0 f\xFCr eine ganze Zahl von Monaten oder eine ganze Zahl von Gratismonaten.",
|
|
831
1703
|
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' nicht gefunden",
|
|
832
|
-
PLAN_ALREADY_EXISTS: "Plan '{planKey}' gibt es
|
|
833
|
-
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' gibt es
|
|
1704
|
+
PLAN_ALREADY_EXISTS: "Plan '{planKey}' gibt es bereits",
|
|
1705
|
+
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' gibt es bereits",
|
|
834
1706
|
MARKETING_PROJECTION_ALREADY_EXISTS: "F\xFCr {targetType}/{targetVersionId}/{locale} gibt es bereits eine Marketing-Projektion \u2014 bearbeite sie per PATCH",
|
|
835
1707
|
PLAN_DRAFT_ALREADY_EXISTS: "Plan '{planKey}' hat bereits die Entwurfsversion v{draftVersion}; ver\xF6ffentliche oder verwirf sie zuerst",
|
|
836
1708
|
BUNDLE_DRAFT_ALREADY_EXISTS: "Bundle '{bundleKey}' hat bereits die Entwurfsversion v{draftVersion}; ver\xF6ffentliche oder verwirf sie zuerst",
|
|
837
1709
|
QUOTA_NOT_IN_DISCOVERY_SNAPSHOT: "Quota '{quotaKey}' steht nicht im Discovery-Snapshot \u2014 eine Quota, die im Code nicht deklariert ist, l\xE4sst sich nicht freigeben",
|
|
838
1710
|
DISCOVERY_STATUS_TRANSITION_INVALID: "Der \xDCbergang '{from}' \u2192 '{to}' ist nicht erlaubt",
|
|
839
1711
|
DISCOVERY_NOT_INITIALIZED: "Die Freigabe braucht einen Discovery-Snapshot \u2014 Discovery ist nicht initialisiert (#25)",
|
|
1712
|
+
// ── catalogue import ──
|
|
1713
|
+
PLAN_CATALOG_UNREADABLE: "Diese Datei ist kein Plankatalog \u2014 sie lie\xDF sich nicht als einer lesen. Pr\xFCfe, ob es die YAML-Datei ist, in der deine App ihre Pl\xE4ne deklariert.",
|
|
1714
|
+
PLAN_CATALOG_INVALID: "Der Plankatalog wurde gelesen und dann abgelehnt: {message}",
|
|
840
1715
|
// ── billing ──
|
|
841
1716
|
BUNDLE_ALREADY_SUBSCRIBED: "Abonnement '{subscriptionId}' hat dieses Bundle bereits aktiv gebucht.",
|
|
842
1717
|
BUNDLE_INCOMPATIBLE_WITH_PLAN: "BundleVersion '{bundleVersionId}' passt nicht zum Plan '{planKey}'. Erlaubt: [{allowedPlanKeys}].",
|
|
843
1718
|
BUNDLE_NOT_SELF_SERVICE: "Bundle '{bundleKey}' wird nur \xFCber einen Sondervertrag freigeschaltet. Bitte wende dich an die Vertragsverwaltung.",
|
|
1719
|
+
BUNDLE_CYCLE_EXCEEDS_PLAN: "Ein j\xE4hrlich abgerechnetes Bundle passt nicht zu einem monatlich abgerechneten Plan: es w\xE4re an jedem Tag gebunden, an dem der Plan enden kann.",
|
|
1720
|
+
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "F\xFCr dieses Bundle ist im Plan {planKey} kein {billingCycle} Preis hinterlegt, es kann hier deshalb nicht gebucht werden.",
|
|
844
1721
|
SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' ist bereits gek\xFCndigt.",
|
|
845
1722
|
SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' ist nicht gek\xFCndigt.",
|
|
846
1723
|
SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "Die K\xFCndigung ist bereits wirksam \u2014 buche das Bundle einfach neu.",
|
|
@@ -849,9 +1726,25 @@ var ERROR_MESSAGES_DE = {
|
|
|
849
1726
|
SUBSCRIPTION_BUNDLE_NOT_FOUND: "SubscriptionBundle '{subscriptionBundleId}' nicht gefunden",
|
|
850
1727
|
TENANT_NOT_FOUND: "Mandant {slug} nicht gefunden",
|
|
851
1728
|
NO_ACTIVE_PLAN_VERSION: "Zum {asOf} ist keine Planversion f\xFCr {planId} aktiv \u2014 weder ist das validFrom-Fenster erf\xFCllt, noch steht eine j\xFCngste Live-Version bereit.",
|
|
852
|
-
PLAN_NOT_IN_CATALOG: 'Plan "{planKey}" steht nicht im Katalog
|
|
853
|
-
PLAN_NOT_SELF_SERVICE: "{
|
|
1729
|
+
PLAN_NOT_IN_CATALOG: 'Plan "{planKey}" steht nicht im Katalog',
|
|
1730
|
+
PLAN_NOT_SELF_SERVICE: "{planName} wird nur \xFCber einen Sondervertrag freigeschaltet. Bitte wenden Sie sich an die Vertragsverwaltung.",
|
|
1731
|
+
PLAN_NOT_SOLD_IN_CYCLE: "{planName} hat f\xFCr diesen Abrechnungsrhythmus keinen Preis und kann darin nicht gebucht werden.",
|
|
854
1732
|
PLAN_CHANGE_BLOCKED: "W\xE4hrend des Onboardings ist ein Planwechsel gesperrt.",
|
|
1733
|
+
SUBSCRIPTION_CHANGED: "Dieses Abonnement hat sich ge\xE4ndert, w\xE4hrend die Anfrage entschieden wurde. Bitte lade es neu.",
|
|
1734
|
+
NO_SUBSCRIPTION: "Dieser Mandant hat kein Abonnement, das gek\xFCndigt werden kann.",
|
|
1735
|
+
CANCELLATION_TERMS_CHANGED: "Das Wirksamkeitsdatum hat sich seit der Anzeige ge\xE4ndert. Bitte best\xE4tige das neue Datum.",
|
|
1736
|
+
SUBSCRIPTION_ENDED: "Dieses Abonnement ist beendet. Sein Paket kann nicht mehr gewechselt werden.",
|
|
1737
|
+
PLAN_LOCKED: "Aktiver Sondervertrag {planName} \u2014 f\xFCr einen Paketwechsel wenden Sie sich bitte an die Vertragsverwaltung.",
|
|
1738
|
+
QUOTA_OVER_TARGET: "Aktuelle Nutzung {used} \xFCberschreitet das Ziel-Limit {targetMax} ({quotaKey}) im Paket {planName}. Bitte zuerst die Nutzung reduzieren.",
|
|
1739
|
+
FEATURE_LOST: "Beim Wechsel verlieren Sie den Zugriff auf eine Funktion. Vorhandene Daten bleiben erhalten und werden nie gel\xF6scht \u2014 ein sp\xE4teres Upgrade schaltet sie wieder frei.",
|
|
1740
|
+
FEATURES_LOST: "Beim Wechsel verlieren Sie den Zugriff auf {count} Funktionen. Vorhandene Daten bleiben erhalten und werden nie gel\xF6scht \u2014 ein sp\xE4teres Upgrade schaltet sie wieder frei.",
|
|
1741
|
+
NO_CHANGE: "Zielpaket und Abrechnungsrhythmus entsprechen bereits dem aktuellen Stand.",
|
|
1742
|
+
CANCELLATION_LOCKS_THE_CYCLE: "Dieses Abonnement ist gek\xFCndigt, sein Abrechnungsrhythmus kann daher nicht wechseln. Das Paket schon \u2014 behalten Sie den aktuellen Rhythmus, um es heute zu wechseln.",
|
|
1743
|
+
CYCLE_SHORTENS_AT_TERM_END: "Ein monatlich abgerechnetes {planName} kann nicht innerhalb der laufenden Jahreslaufzeit beginnen. Das Upgrade greift zum Ende dieser Laufzeit; wer es sofort m\xF6chte, beh\xE4lt den j\xE4hrlichen Rhythmus.",
|
|
1744
|
+
BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "Ein j\xE4hrlich abgerechnetes Bundle ist bis {until} gebucht. Ein monatlich abgerechnetes Paket kann es nicht tragen \u2014 k\xFCndigen Sie das Bundle zuerst, oder behalten Sie den j\xE4hrlichen Rhythmus.",
|
|
1745
|
+
REDUNDANT_FEATURES: "Der Plan oder ein anderes gebuchtes Bundle enth\xE4lt bereits {count} der Funktionen aus diesem Bundle \u2014 mit der Buchung werden sie doppelt bezahlt.",
|
|
1746
|
+
MINIMUM_TERM_BINDS: "Die Mindestlaufzeit reicht \xFCber das Ende der Periode hinaus \u2014 die K\xFCndigung wird erst zum Ende der Mindestlaufzeit wirksam.",
|
|
1747
|
+
BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "Das Bundle setzt [{features}] voraus \u2014 weder im Plan noch in den aktiven Bundles vorhanden.",
|
|
855
1748
|
NO_PENDING_PLAN_VERSION: "Es wartet keine Planversion auf eine Best\xE4tigung.",
|
|
856
1749
|
ONBOARDING_CREATE_FAILED: "Das Konto konnte nicht angelegt werden. Bitte versuche es noch einmal.",
|
|
857
1750
|
BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "Genau eines von bundleVersionId (Vorschau f\xFCrs Buchen) oder subscriptionBundleId (Vorschau f\xFCrs K\xFCndigen) muss angegeben sein.",
|
|
@@ -861,7 +1754,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
861
1754
|
// ── promo ──
|
|
862
1755
|
PROMO_CODE_NOT_FOUND: "Code nicht gefunden",
|
|
863
1756
|
PROMO_CODE_ALREADY_EXISTS: "Diesen Code gibt es bereits.",
|
|
864
|
-
PROMO_CODE_HAS_REDEMPTIONS: "Der Code wurde bereits eingel\xF6st \u2014 er l\xE4sst sich nicht per Soft-Delete entfernen. Pausiere ihn stattdessen.",
|
|
1757
|
+
PROMO_CODE_HAS_REDEMPTIONS: "Der Code wurde bereits eingel\xF6st oder ist f\xFCr einen laufenden Checkout reserviert \u2014 er l\xE4sst sich nicht per Soft-Delete entfernen. Pausiere ihn stattdessen.",
|
|
865
1758
|
PROMO_CODE_NOT_REDEEMABLE: "Der Code l\xE4sst sich nicht einl\xF6sen: {reason}",
|
|
866
1759
|
PROMO_CODE_FORMAT_INVALID: 'Der Code darf nur Gro\xDFbuchstaben, Ziffern, "-" und "_" enthalten (4\u201332 Zeichen).',
|
|
867
1760
|
PROMO_PERCENT_OUT_OF_RANGE: "Der Prozentwert muss zwischen 0 und 100 liegen.",
|
|
@@ -879,18 +1772,39 @@ var ERROR_MESSAGES_DE = {
|
|
|
879
1772
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Jede gew\xE4hlte Bundle-Version braucht eine eingefrorene Bundle-Position.",
|
|
880
1773
|
CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE: "Mindestens eine Bundle-Version aus dem Checkout-Angebot ist nicht mehr buchbar.",
|
|
881
1774
|
CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED: "Der gew\xE4hlte Plan deckt nicht alle Feature-Abh\xE4ngigkeiten ab: [{missingRequires}] fehlen in Plan und gew\xE4hlten Bundles.",
|
|
1775
|
+
CHECKOUT_OFFER_PLAN_NOT_OFFERED: "Plan '{planKey}' wird derzeit nicht mit einem Preis f\xFCr den Rhythmus {billingCycle} angeboten.",
|
|
1776
|
+
CHECKOUT_OFFER_BUNDLE_NOT_OFFERED: "Bundle-Version '{bundleVersionId}' kann diesem Angebot nicht hinzugef\xFCgt werden ({reason}).",
|
|
1777
|
+
CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED: "Der Rabattcode kann auf dieses Angebot nicht angewendet werden ({reason}).",
|
|
1778
|
+
CHECKOUT_OFFER_PRICE_NOT_CURRENT: "Die Preise des Angebots '{offerId}' stimmen nicht mehr mit dem Katalog \xFCberein; bitte ein neues Angebot erstellen.",
|
|
882
1779
|
SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED: "Ein Abo-Vertrag braucht mindestens eine Position.",
|
|
883
1780
|
SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "Ein Abo-Vertrag braucht genau eine Plan-Grundposition.",
|
|
884
1781
|
SUBSCRIPTION_CONTRACT_INVALID_DATE: "{field} muss ein g\xFCltiges Datum sein.",
|
|
885
1782
|
SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "effectiveUntil muss nach effectiveFrom liegen.",
|
|
1783
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "taxAmount einer Position muss exakt priceGross minus priceNet sein.",
|
|
1784
|
+
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "Ein Abo-Vertrag nennt bei {field} einen Steuersatz von {taxRate}, der kein Prozentsatz ist: Ein Satz liegt zwischen 0 und 100, und ein Wert zwischen 0 und 1 wird als Bruch abgelehnt.",
|
|
1785
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "Eine Position muss in der W\xE4hrung gebucht sein, in der ihr Vertrag bepreist ist.",
|
|
1786
|
+
SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "Die Positionen eines Abo-Vertrags ergeben {lines} f\xFCr {field}, der Vertrag nennt aber {stated}.",
|
|
1787
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "Ein Abo-Vertrag nennt bei {field} einen Rabatt von {amount}; ein Rabatt zieht Geld ab und ist nie negativ.",
|
|
886
1788
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil muss nach dem effectiveFrom des Vertrags liegen.",
|
|
887
1789
|
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' nicht gefunden",
|
|
888
1790
|
CHECKOUT_OFFER_EXPIRED: "Checkout-Angebot '{offerId}' ist abgelaufen \u2014 '{action}' ist nicht mehr m\xF6glich",
|
|
889
1791
|
CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout-Angebot '{offerId}' wurde bereits eingel\xF6st \u2014 '{action}' ist nicht mehr m\xF6glich",
|
|
890
1792
|
CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' muss eingel\xF6st sein, bevor der Vertrag entsteht",
|
|
1793
|
+
CHECKOUT_OFFER_CHANGED: "Checkout-Angebot '{offerId}' hat sich w\xE4hrend des Abschlusses ge\xE4ndert. Bitte neu laden.",
|
|
891
1794
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' nicht gefunden",
|
|
892
1795
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "Kein aktiver Abo-Vertrag f\xFCr Mandant {tenantId}",
|
|
893
1796
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' ist bereits geschlossen",
|
|
1797
|
+
// ── subscriber ──
|
|
1798
|
+
SUBSCRIBER_REQUIRED: "Mandant '{tenantId}' hat keinen Vertragspartner. Ohne ihn wird nichts vereinbart oder berechnet: Zuerst muss der Vertragspartner des Mandanten angelegt werden.",
|
|
1799
|
+
SUBSCRIBER_ALREADY_EXISTS: "Mandant '{tenantId}' hat bereits einen Vertragspartner.",
|
|
1800
|
+
SUBSCRIBER_NOT_FOUND: "Vertragspartner '{subscriberId}' nicht gefunden",
|
|
1801
|
+
SUBSCRIBER_LEGAL_NAME_REQUIRED: "Ein Vertragspartner braucht seinen rechtlichen Namen.",
|
|
1802
|
+
SUBSCRIBER_DETAIL_INVALID: "Das Feld {field} des Vertragspartners ist ung\xFCltig.",
|
|
1803
|
+
SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} geh\xF6rt zur rechtlichen Identit\xE4t des Vertragspartners und \xE4ndert sich nur als Korrektur mit Begr\xFCndung.",
|
|
1804
|
+
SUBSCRIBER_CORRECTION_REASON_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t braucht eine Begr\xFCndung.",
|
|
1805
|
+
SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t muss nennen, wer sie vornimmt.",
|
|
1806
|
+
SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "Die Korrektur \xE4ndert nichts: Jeder genannte Wert ist bereits erfasst.",
|
|
1807
|
+
SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "\xDCbernimmt eine andere Rechtsperson, ist das eine \xDCbertragung und keine Korrektur; sie l\xE4sst sich nicht als \xC4nderung erfassen.",
|
|
894
1808
|
// ── registration ──
|
|
895
1809
|
PENDING_REGISTRATION_NOT_FOUND: "Diese Registrierung konnten wir nicht finden. Vielleicht ist sie bereits abgeschlossen oder verworfen.",
|
|
896
1810
|
PENDING_REGISTRATION_EXPIRED: "Diese Registrierung ist abgelaufen. Bitte fange noch einmal von vorn an.",
|
|
@@ -906,52 +1820,105 @@ var ERROR_MESSAGES_DE = {
|
|
|
906
1820
|
PLAN_NOT_AVAILABLE: "Dieser Plan steht nicht zur Verf\xFCgung.",
|
|
907
1821
|
PLAN_NOT_SELECTED: "Bitte w\xE4hle zuerst einen Plan aus.",
|
|
908
1822
|
MODEL_NOT_AVAILABLE: "Diese Option steht nicht zur Verf\xFCgung.",
|
|
1823
|
+
// ── payments ──
|
|
1824
|
+
PAYMENTS_NOT_CONFIGURED: "Zahlungsmethoden k\xF6nnen hier noch nicht hinterlegt werden.",
|
|
1825
|
+
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "Unter '{account}' ist kein Konto eines Zahlungsanbieters eingerichtet.",
|
|
1826
|
+
PAYMENT_CALLBACK_REJECTED: "Die R\xFCckmeldung des Zahlungsanbieters lie\xDF sich nicht pr\xFCfen.",
|
|
1827
|
+
PAYMENT_RETURN_URL_NOT_ALLOWED: "Die {field} f\xFChrt auf eine Seite, zu der diese Installation nicht zur\xFCckleitet.",
|
|
909
1828
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
910
|
-
[FEATURE_NOT_LICENSED]: "Das Feature {featureKeys} ist im aktuellen Plan nicht enthalten."
|
|
1829
|
+
[FEATURE_NOT_LICENSED]: "Das Feature {featureKeys} ist im aktuellen Plan nicht enthalten.",
|
|
1830
|
+
// ── settings ──
|
|
1831
|
+
SETTINGS_CHANGE_NOT_FOUND: "Keine aufgezeichnete Einstellungs\xE4nderung hat diese ID."
|
|
911
1832
|
};
|
|
912
1833
|
// Annotate the CommonJS export names for ESM import in node:
|
|
913
1834
|
0 && (module.exports = {
|
|
1835
|
+
ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES,
|
|
914
1836
|
AUTH_ERROR_CODES,
|
|
915
1837
|
BILLING_ERROR_CODES,
|
|
1838
|
+
BUNDLE_PRICE_LOOKUP_LIMIT,
|
|
1839
|
+
CATALOGUE_KEYS,
|
|
916
1840
|
CATALOG_ERROR_CODES,
|
|
917
1841
|
CONTRACT_ERROR_CODES,
|
|
918
1842
|
ERROR_MESSAGES_DE,
|
|
919
1843
|
ERROR_MESSAGES_EN,
|
|
920
1844
|
FEATURE_NOT_LICENSED,
|
|
1845
|
+
ForeignPaymentMethodReferenceError,
|
|
1846
|
+
LEGAL_IDENTITY_FIELDS,
|
|
1847
|
+
MARKETING_PRIORITY_MAX,
|
|
1848
|
+
MARKETING_PRIORITY_MIN,
|
|
921
1849
|
OTP_RATE_LIMIT_MAX_SENDS,
|
|
922
1850
|
OTP_RATE_LIMIT_WINDOW_MINUTES,
|
|
923
1851
|
OTP_TTL_MINUTES,
|
|
924
1852
|
OTP_VERIFY_MAX_ATTEMPTS,
|
|
925
1853
|
PASSWORD_RESET_TTL_MINUTES,
|
|
1854
|
+
PAYMENT_ERROR_CODES,
|
|
1855
|
+
PAYMENT_METHOD_TYPES,
|
|
926
1856
|
PENDING_CHECKOUT_TTL_DAYS,
|
|
927
1857
|
PENDING_EMAIL_TTL_HOURS,
|
|
928
1858
|
PENDING_ONBOARDING_TTL_DAYS,
|
|
929
1859
|
PLATFORM_ERROR_CODES,
|
|
930
1860
|
PROMO_ERROR_CODES,
|
|
1861
|
+
PaymentCallbackRejectedError,
|
|
931
1862
|
PersistenceCapabilityError,
|
|
932
1863
|
PlatformUserExistsError,
|
|
933
1864
|
REGISTRATION_ERROR_CODES,
|
|
934
1865
|
REGISTRATION_RESUME_TTL_MINUTES,
|
|
935
1866
|
REGISTRATION_STEP_BY_STATUS,
|
|
1867
|
+
SETTINGS_ERROR_CODES,
|
|
936
1868
|
SETUP_ERROR_CODES,
|
|
1869
|
+
SUBSCRIBER_ERROR_CODES,
|
|
937
1870
|
applyPromo,
|
|
938
1871
|
assertPersistenceCapabilities,
|
|
939
1872
|
buildActivePlanVersionWhere,
|
|
940
1873
|
buildActiveVersionWhere,
|
|
941
1874
|
buildFeatureRequiresIndex,
|
|
1875
|
+
bundleDraftDefaults,
|
|
1876
|
+
bundleStemDefaults,
|
|
1877
|
+
canonicalJson,
|
|
942
1878
|
classifyBundleVersionDiff,
|
|
1879
|
+
classifyIssuerChange,
|
|
943
1880
|
classifyPlanDiff,
|
|
944
1881
|
collectUnsatisfiedRequires,
|
|
1882
|
+
contractPartiesOf,
|
|
945
1883
|
coverageExcludingSelf,
|
|
1884
|
+
definedFields,
|
|
1885
|
+
diffSettings,
|
|
1886
|
+
formatCustomerNumber,
|
|
946
1887
|
formatErrorMessage,
|
|
1888
|
+
identityCorrectionDelta,
|
|
947
1889
|
isBundleRedundant,
|
|
1890
|
+
isForeignPaymentMethodReferenceError,
|
|
1891
|
+
isPaymentCallbackRejectedError,
|
|
948
1892
|
isPlatformUserExistsError,
|
|
949
1893
|
isVersionEditable,
|
|
1894
|
+
issuerIdentityOf,
|
|
1895
|
+
keepOneRecommended,
|
|
950
1896
|
missingRequiresFor,
|
|
1897
|
+
movedIdentityFields,
|
|
951
1898
|
pickActivePromo,
|
|
1899
|
+
planCatalogSettingsOf,
|
|
1900
|
+
previousUtcDay,
|
|
952
1901
|
promoStatus,
|
|
1902
|
+
promotionOnPrice,
|
|
1903
|
+
readQuotaRecord,
|
|
1904
|
+
readQuotaValue,
|
|
1905
|
+
recordedIssuerIdentity,
|
|
1906
|
+
refuseForeignPaymentMethodReference,
|
|
953
1907
|
resolveBundleAvailability,
|
|
954
1908
|
resolveErrorMessage,
|
|
1909
|
+
sameLegalIdentity,
|
|
955
1910
|
selectChargeableBundles,
|
|
956
|
-
|
|
1911
|
+
settingsSubtreeOf,
|
|
1912
|
+
startOfUtcDay,
|
|
1913
|
+
subscriberFromRegistration,
|
|
1914
|
+
subscriberPaymentMethodColumns,
|
|
1915
|
+
toBundleStemRow,
|
|
1916
|
+
toContractLineItemRecord,
|
|
1917
|
+
toPlanRow,
|
|
1918
|
+
toPlanVersionRow,
|
|
1919
|
+
toRunningContractIssuer,
|
|
1920
|
+
toSubscriberCorrectionRecord,
|
|
1921
|
+
toSubscriberPaymentMethodRecord,
|
|
1922
|
+
toSubscriberRecord,
|
|
1923
|
+
toSubscriptionContractRecord
|
|
957
1924
|
});
|