@saasicat/core 1.0.0-rc.23 → 1.0.0-rc.25
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 +1 -1
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +1589 -367
- package/dist/index.d.cts +2866 -254
- package/dist/index.d.ts +2866 -254
- package/dist/index.js +1522 -366
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -24,9 +24,11 @@ __export(index_exports, {
|
|
|
24
24
|
AUTH_ERROR_CODES: () => AUTH_ERROR_CODES,
|
|
25
25
|
BILLING_ERROR_CODES: () => BILLING_ERROR_CODES,
|
|
26
26
|
BUNDLE_PRICE_LOOKUP_LIMIT: () => BUNDLE_PRICE_LOOKUP_LIMIT,
|
|
27
|
+
BUNDLE_VERSION_RETIREMENT_CAPABILITY: () => BUNDLE_VERSION_RETIREMENT_CAPABILITY,
|
|
27
28
|
CATALOGUE_KEYS: () => CATALOGUE_KEYS,
|
|
28
29
|
CATALOG_ERROR_CODES: () => CATALOG_ERROR_CODES,
|
|
29
30
|
CONTRACT_ERROR_CODES: () => CONTRACT_ERROR_CODES,
|
|
31
|
+
CUSTOM_LIMITS_KEYS: () => CUSTOM_LIMITS_KEYS,
|
|
30
32
|
ERROR_MESSAGES_DE: () => ERROR_MESSAGES_DE,
|
|
31
33
|
ERROR_MESSAGES_EN: () => ERROR_MESSAGES_EN,
|
|
32
34
|
FEATURE_NOT_LICENSED: () => FEATURE_NOT_LICENSED,
|
|
@@ -38,6 +40,8 @@ __export(index_exports, {
|
|
|
38
40
|
MAINTENANCE_STATE_MAX_AGE_SECONDS: () => MAINTENANCE_STATE_MAX_AGE_SECONDS,
|
|
39
41
|
MARKETING_PRIORITY_MAX: () => MARKETING_PRIORITY_MAX,
|
|
40
42
|
MARKETING_PRIORITY_MIN: () => MARKETING_PRIORITY_MIN,
|
|
43
|
+
NO_SCHEDULED_CHANGE: () => NO_SCHEDULED_CHANGE,
|
|
44
|
+
OPTIONAL_CANONICAL_MODELS: () => OPTIONAL_CANONICAL_MODELS,
|
|
41
45
|
OTP_RATE_LIMIT_MAX_SENDS: () => OTP_RATE_LIMIT_MAX_SENDS,
|
|
42
46
|
OTP_RATE_LIMIT_WINDOW_MINUTES: () => OTP_RATE_LIMIT_WINDOW_MINUTES,
|
|
43
47
|
OTP_TTL_MINUTES: () => OTP_TTL_MINUTES,
|
|
@@ -52,69 +56,121 @@ __export(index_exports, {
|
|
|
52
56
|
PROMO_ERROR_CODES: () => PROMO_ERROR_CODES,
|
|
53
57
|
PaymentCallbackRejectedError: () => PaymentCallbackRejectedError,
|
|
54
58
|
PersistenceCapabilityError: () => PersistenceCapabilityError,
|
|
59
|
+
PersistenceRefusal: () => PersistenceRefusal,
|
|
55
60
|
PlatformUserExistsError: () => PlatformUserExistsError,
|
|
56
61
|
REGISTRATION_ERROR_CODES: () => REGISTRATION_ERROR_CODES,
|
|
57
62
|
REGISTRATION_RESUME_TTL_MINUTES: () => REGISTRATION_RESUME_TTL_MINUTES,
|
|
58
63
|
REGISTRATION_STEP_BY_STATUS: () => REGISTRATION_STEP_BY_STATUS,
|
|
64
|
+
RETIREMENT_REMINDER_LEAD_DAYS: () => RETIREMENT_REMINDER_LEAD_DAYS,
|
|
59
65
|
SETTINGS_ERROR_CODES: () => SETTINGS_ERROR_CODES,
|
|
60
66
|
SETUP_ERROR_CODES: () => SETUP_ERROR_CODES,
|
|
61
67
|
SUBSCRIBER_ACCOUNT_CAPABILITY: () => SUBSCRIBER_ACCOUNT_CAPABILITY,
|
|
68
|
+
SUBSCRIBER_ATTENTION_CAPABILITY: () => SUBSCRIBER_ATTENTION_CAPABILITY,
|
|
69
|
+
SUBSCRIBER_ATTENTION_PAGE_SIZE: () => SUBSCRIBER_ATTENTION_PAGE_SIZE,
|
|
70
|
+
SUBSCRIBER_CORRECTION_CAPABILITY: () => SUBSCRIBER_CORRECTION_CAPABILITY,
|
|
62
71
|
SUBSCRIBER_ERROR_CODES: () => SUBSCRIBER_ERROR_CODES,
|
|
72
|
+
SUBSCRIBER_INVOICE_ADDRESS_FIELDS: () => SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
|
|
73
|
+
SUBSCRIBER_STANDING_CAPABILITY: () => SUBSCRIBER_STANDING_CAPABILITY,
|
|
74
|
+
SUBSCRIBER_TAX_ORIGIN_FIELDS: () => SUBSCRIBER_TAX_ORIGIN_FIELDS,
|
|
75
|
+
TAX_ERROR_CODES: () => TAX_ERROR_CODES,
|
|
76
|
+
TAX_TREATMENT_KINDS: () => TAX_TREATMENT_KINDS,
|
|
77
|
+
VERSION_RETIREMENT_CAPABILITY: () => VERSION_RETIREMENT_CAPABILITY,
|
|
63
78
|
applyPromo: () => applyPromo,
|
|
64
79
|
assertPersistenceCapabilities: () => assertPersistenceCapabilities,
|
|
65
80
|
buildActivePlanVersionWhere: () => buildActivePlanVersionWhere,
|
|
66
81
|
buildActiveVersionWhere: () => buildActiveVersionWhere,
|
|
67
82
|
buildFeatureRequiresIndex: () => buildFeatureRequiresIndex,
|
|
68
83
|
bundleDraftDefaults: () => bundleDraftDefaults,
|
|
84
|
+
bundleKeyTaken: () => bundleKeyTaken,
|
|
69
85
|
bundleStemDefaults: () => bundleStemDefaults,
|
|
70
86
|
canonicalJson: () => canonicalJson,
|
|
87
|
+
canonicalVatId: () => canonicalVatId,
|
|
88
|
+
catalogDraftExists: () => catalogDraftExists,
|
|
89
|
+
catalogVersionAlreadyPublished: () => catalogVersionAlreadyPublished,
|
|
90
|
+
catalogVersionGone: () => catalogVersionGone,
|
|
71
91
|
classifyBundleVersionDiff: () => classifyBundleVersionDiff,
|
|
72
92
|
classifyIssuerChange: () => classifyIssuerChange,
|
|
73
93
|
classifyPlanDiff: () => classifyPlanDiff,
|
|
94
|
+
classifyVersionOffer: () => classifyVersionOffer,
|
|
74
95
|
collectUnsatisfiedRequires: () => collectUnsatisfiedRequires,
|
|
96
|
+
computeIncludedVat: () => computeIncludedVat,
|
|
97
|
+
concurrencyGate: () => concurrencyGate,
|
|
98
|
+
concurrencyGateOf: () => concurrencyGateOf,
|
|
75
99
|
contractPartiesOf: () => contractPartiesOf,
|
|
76
100
|
coverageExcludingSelf: () => coverageExcludingSelf,
|
|
101
|
+
customLimitsReader: () => customLimitsReader,
|
|
77
102
|
definedFields: () => definedFields,
|
|
78
103
|
diffSettings: () => diffSettings,
|
|
79
104
|
formatCustomerNumber: () => formatCustomerNumber,
|
|
80
105
|
formatErrorMessage: () => formatErrorMessage,
|
|
106
|
+
grossFromNet: () => grossFromNet,
|
|
81
107
|
identityCorrectionDelta: () => identityCorrectionDelta,
|
|
82
108
|
isBundleRedundant: () => isBundleRedundant,
|
|
83
109
|
isForeignPaymentMethodReferenceError: () => isForeignPaymentMethodReferenceError,
|
|
84
110
|
isMaintenanceAnnouncementLapsed: () => isMaintenanceAnnouncementLapsed,
|
|
85
111
|
isMaintenanceOverrun: () => isMaintenanceOverrun,
|
|
86
112
|
isPaymentCallbackRejectedError: () => isPaymentCallbackRejectedError,
|
|
113
|
+
isPersistenceRefusal: () => isPersistenceRefusal,
|
|
87
114
|
isPlatformUserExistsError: () => isPlatformUserExistsError,
|
|
115
|
+
isVersionActiveAt: () => isVersionActiveAt,
|
|
88
116
|
isVersionEditable: () => isVersionEditable,
|
|
89
117
|
issuerIdentityOf: () => issuerIdentityOf,
|
|
90
118
|
keepOneRecommended: () => keepOneRecommended,
|
|
119
|
+
keepsVatIdCheck: () => keepsVatIdCheck,
|
|
91
120
|
maintenanceRetryAfterSeconds: () => maintenanceRetryAfterSeconds,
|
|
92
121
|
maintenanceStatusOf: () => maintenanceStatusOf,
|
|
93
122
|
maintenanceWindowStatusOf: () => maintenanceWindowStatusOf,
|
|
94
123
|
maintenanceWindowViewOf: () => maintenanceWindowViewOf,
|
|
124
|
+
marketingProjectionTaken: () => marketingProjectionTaken,
|
|
125
|
+
membersLeftOut: () => membersLeftOut,
|
|
95
126
|
missingRequiresFor: () => missingRequiresFor,
|
|
96
127
|
movedIdentityFields: () => movedIdentityFields,
|
|
128
|
+
netFromGross: () => netFromGross,
|
|
129
|
+
noActivePlanVersion: () => noActivePlanVersion,
|
|
97
130
|
parseZonedInstant: () => parseZonedInstant,
|
|
131
|
+
percentOf: () => percentOf,
|
|
98
132
|
pickActivePromo: () => pickActivePromo,
|
|
99
133
|
planCatalogSettingsOf: () => planCatalogSettingsOf,
|
|
134
|
+
planKeyTaken: () => planKeyTaken,
|
|
135
|
+
planNotInCatalog: () => planNotInCatalog,
|
|
100
136
|
previousUtcDay: () => previousUtcDay,
|
|
137
|
+
promoCodeTaken: () => promoCodeTaken,
|
|
101
138
|
promoStatus: () => promoStatus,
|
|
102
139
|
promotionOnPrice: () => promotionOnPrice,
|
|
140
|
+
prorate: () => prorate,
|
|
141
|
+
readCustomLimits: () => readCustomLimits,
|
|
103
142
|
readQuotaRecord: () => readQuotaRecord,
|
|
104
143
|
readQuotaValue: () => readQuotaValue,
|
|
105
144
|
recordedIssuerIdentity: () => recordedIssuerIdentity,
|
|
106
145
|
refuseForeignPaymentMethodReference: () => refuseForeignPaymentMethodReference,
|
|
107
146
|
resolveBundleAvailability: () => resolveBundleAvailability,
|
|
108
147
|
resolveErrorMessage: () => resolveErrorMessage,
|
|
148
|
+
retirementCostsTheSubscription: () => retirementCostsTheSubscription,
|
|
149
|
+
retirementReminderDueAt: () => retirementReminderDueAt,
|
|
150
|
+
retirementReminderIsDue: () => retirementReminderIsDue,
|
|
151
|
+
retirementSwitchTerms: () => retirementSwitchTerms,
|
|
152
|
+
roundToCents: () => roundToCents,
|
|
109
153
|
sameLegalIdentity: () => sameLegalIdentity,
|
|
154
|
+
scheduledChangeAfterWrite: () => scheduledChangeAfterWrite,
|
|
110
155
|
selectChargeableBundles: () => selectChargeableBundles,
|
|
111
156
|
settingsSubtreeOf: () => settingsSubtreeOf,
|
|
112
157
|
startOfUtcDay: () => startOfUtcDay,
|
|
113
158
|
subscriberChargeColumns: () => subscriberChargeColumns,
|
|
114
159
|
subscriberFromRegistration: () => subscriberFromRegistration,
|
|
115
160
|
subscriberPaymentMethodColumns: () => subscriberPaymentMethodColumns,
|
|
161
|
+
subscriptionBundleAlreadyCancelled: () => subscriptionBundleAlreadyCancelled,
|
|
162
|
+
subscriptionBundleGone: () => subscriptionBundleGone,
|
|
163
|
+
subscriptionContractGone: () => subscriptionContractGone,
|
|
164
|
+
subscriptionGone: () => subscriptionGone,
|
|
165
|
+
sumToCents: () => sumToCents,
|
|
166
|
+
taxOriginOf: () => taxOriginOf,
|
|
167
|
+
taxOriginWrite: () => taxOriginWrite,
|
|
168
|
+
taxTreatmentToJson: () => taxTreatmentToJson,
|
|
116
169
|
toBundleStemRow: () => toBundleStemRow,
|
|
170
|
+
toBundleVersionRetirementRecord: () => toBundleVersionRetirementRecord,
|
|
171
|
+
toCents: () => toCents,
|
|
117
172
|
toContractLineItemRecord: () => toContractLineItemRecord,
|
|
173
|
+
toDecimalString: () => toDecimalString,
|
|
118
174
|
toMaintenanceWindowRecord: () => toMaintenanceWindowRecord,
|
|
119
175
|
toPlanRow: () => toPlanRow,
|
|
120
176
|
toPlanVersionRow: () => toPlanVersionRow,
|
|
@@ -123,8 +179,18 @@ __export(index_exports, {
|
|
|
123
179
|
toSubscriberCorrectionRecord: () => toSubscriberCorrectionRecord,
|
|
124
180
|
toSubscriberPaymentMethodRecord: () => toSubscriberPaymentMethodRecord,
|
|
125
181
|
toSubscriberRecord: () => toSubscriberRecord,
|
|
182
|
+
toSubscriberTaxOriginChangeRecord: () => toSubscriberTaxOriginChangeRecord,
|
|
183
|
+
toSubscriberVatIdCheckRecord: () => toSubscriberVatIdCheckRecord,
|
|
126
184
|
toSubscriptionBundleRecord: () => toSubscriptionBundleRecord,
|
|
127
|
-
toSubscriptionContractRecord: () => toSubscriptionContractRecord
|
|
185
|
+
toSubscriptionContractRecord: () => toSubscriptionContractRecord,
|
|
186
|
+
toSubscriptionNoticeRecord: () => toSubscriptionNoticeRecord,
|
|
187
|
+
toTaxOriginValues: () => toTaxOriginValues,
|
|
188
|
+
toTaxTreatment: () => toTaxTreatment,
|
|
189
|
+
toVersionRetirementRecord: () => toVersionRetirementRecord,
|
|
190
|
+
vatIdCheckFromStore: () => vatIdCheckFromStore,
|
|
191
|
+
versionOnSale: () => versionOnSale,
|
|
192
|
+
versionOnSaleOrNext: () => versionOnSaleOrNext,
|
|
193
|
+
versionSale: () => versionSale
|
|
128
194
|
});
|
|
129
195
|
module.exports = __toCommonJS(index_exports);
|
|
130
196
|
|
|
@@ -141,7 +207,8 @@ function buildActivePlanVersionWhere(asOf, options = {}) {
|
|
|
141
207
|
const asOfDayStart = startOfUtcDay(asOf);
|
|
142
208
|
const validityWindow = [
|
|
143
209
|
{ OR: [{ validFrom: null }, { validFrom: { lte: asOf } }] },
|
|
144
|
-
{ OR: [{ validUntil: null }, { validUntil: { gte: asOfDayStart } }] }
|
|
210
|
+
{ OR: [{ validUntil: null }, { validUntil: { gte: asOfDayStart } }] },
|
|
211
|
+
{ OR: [{ supersededAt: null }, { validUntil: { not: null } }] }
|
|
145
212
|
];
|
|
146
213
|
if (!options.withEndsAt) {
|
|
147
214
|
return { publishedAt: { not: null }, AND: validityWindow };
|
|
@@ -151,6 +218,16 @@ function buildActivePlanVersionWhere(asOf, options = {}) {
|
|
|
151
218
|
AND: [...validityWindow, { OR: [{ endsAt: null }, { endsAt: { gt: asOf } }] }]
|
|
152
219
|
};
|
|
153
220
|
}
|
|
221
|
+
function isVersionActiveAt(version, asOf) {
|
|
222
|
+
const at = (value) => value === null || value === void 0 ? null : new Date(value);
|
|
223
|
+
const validFrom = at(version.validFrom);
|
|
224
|
+
const validUntil = at(version.validUntil);
|
|
225
|
+
const endsAt = at(version.endsAt);
|
|
226
|
+
if (validFrom && validFrom > asOf) return false;
|
|
227
|
+
if (validUntil && validUntil < startOfUtcDay(asOf)) return false;
|
|
228
|
+
if (version.supersededAt && !validUntil) return false;
|
|
229
|
+
return !(endsAt && endsAt <= asOf);
|
|
230
|
+
}
|
|
154
231
|
var buildActiveVersionWhere = buildActivePlanVersionWhere;
|
|
155
232
|
|
|
156
233
|
// src/bundle-draft-defaults.ts
|
|
@@ -204,6 +281,79 @@ function definedFields(data, keys) {
|
|
|
204
281
|
var MARKETING_PRIORITY_MIN = 0;
|
|
205
282
|
var MARKETING_PRIORITY_MAX = 1e4;
|
|
206
283
|
|
|
284
|
+
// src/money.ts
|
|
285
|
+
var HUNDRED = { units: 100n, scale: 0 };
|
|
286
|
+
function exactDecimal(value) {
|
|
287
|
+
if (!Number.isFinite(value)) {
|
|
288
|
+
throw new RangeError(`${String(value)} is not an amount money can be computed from.`);
|
|
289
|
+
}
|
|
290
|
+
const text = String(value);
|
|
291
|
+
const exponentAt = text.indexOf("e");
|
|
292
|
+
const mantissa = exponentAt === -1 ? text : text.slice(0, exponentAt);
|
|
293
|
+
const exponent = exponentAt === -1 ? 0 : Number(text.slice(exponentAt + 1));
|
|
294
|
+
const pointAt = mantissa.indexOf(".");
|
|
295
|
+
const whole = pointAt === -1 ? mantissa : mantissa.slice(0, pointAt);
|
|
296
|
+
const fraction = pointAt === -1 ? "" : mantissa.slice(pointAt + 1);
|
|
297
|
+
let units = BigInt(whole + fraction);
|
|
298
|
+
let scale = fraction.length - exponent;
|
|
299
|
+
if (scale < 0) {
|
|
300
|
+
units *= 10n ** BigInt(-scale);
|
|
301
|
+
scale = 0;
|
|
302
|
+
}
|
|
303
|
+
return { units, scale };
|
|
304
|
+
}
|
|
305
|
+
function sum(a, b) {
|
|
306
|
+
const scale = Math.max(a.scale, b.scale);
|
|
307
|
+
return {
|
|
308
|
+
units: a.units * 10n ** BigInt(scale - a.scale) + b.units * 10n ** BigInt(scale - b.scale),
|
|
309
|
+
scale
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function roundHalfAwayFromZero(numerator, denominator) {
|
|
313
|
+
if (denominator === 0n) throw new RangeError("An amount cannot be divided by zero.");
|
|
314
|
+
const negative = numerator < 0n !== denominator < 0n;
|
|
315
|
+
const n = numerator < 0n ? -numerator : numerator;
|
|
316
|
+
const d = denominator < 0n ? -denominator : denominator;
|
|
317
|
+
const quotient = n / d;
|
|
318
|
+
const rounded = n % d * 2n >= d ? quotient + 1n : quotient;
|
|
319
|
+
return negative ? -rounded : rounded;
|
|
320
|
+
}
|
|
321
|
+
function cents(amount, factor, divisor) {
|
|
322
|
+
const numerator = amount.units * factor.units * 10n ** BigInt(divisor.scale) * 100n;
|
|
323
|
+
const denominator = divisor.units * 10n ** BigInt(amount.scale + factor.scale);
|
|
324
|
+
return roundHalfAwayFromZero(numerator, denominator);
|
|
325
|
+
}
|
|
326
|
+
function amountOf(inCents) {
|
|
327
|
+
return Number(inCents) / 100;
|
|
328
|
+
}
|
|
329
|
+
var ONE = { units: 1n, scale: 0 };
|
|
330
|
+
function roundToCents(amount) {
|
|
331
|
+
return amountOf(cents(exactDecimal(amount), ONE, ONE));
|
|
332
|
+
}
|
|
333
|
+
function sumToCents(...terms) {
|
|
334
|
+
const total = terms.map(exactDecimal).reduce(sum, { units: 0n, scale: 0 });
|
|
335
|
+
return amountOf(cents(total, ONE, ONE));
|
|
336
|
+
}
|
|
337
|
+
function toCents(amount) {
|
|
338
|
+
return Number(cents(exactDecimal(amount), ONE, ONE));
|
|
339
|
+
}
|
|
340
|
+
function percentOf(amount, percent) {
|
|
341
|
+
return amountOf(cents(exactDecimal(amount), exactDecimal(percent), HUNDRED));
|
|
342
|
+
}
|
|
343
|
+
function prorate(amount, part, whole) {
|
|
344
|
+
return amountOf(cents(exactDecimal(amount), exactDecimal(part), exactDecimal(whole)));
|
|
345
|
+
}
|
|
346
|
+
function grossFromNet(net, vatRate) {
|
|
347
|
+
return amountOf(cents(exactDecimal(net), sum(HUNDRED, exactDecimal(vatRate)), HUNDRED));
|
|
348
|
+
}
|
|
349
|
+
function netFromGross(gross, vatRate) {
|
|
350
|
+
return amountOf(cents(exactDecimal(gross), HUNDRED, sum(HUNDRED, exactDecimal(vatRate))));
|
|
351
|
+
}
|
|
352
|
+
function computeIncludedVat(gross, vatRate) {
|
|
353
|
+
const rate = exactDecimal(vatRate);
|
|
354
|
+
return amountOf(cents(exactDecimal(gross), rate, sum(HUNDRED, rate)));
|
|
355
|
+
}
|
|
356
|
+
|
|
207
357
|
// src/promotion.types.ts
|
|
208
358
|
function promoStatus(promo, today = /* @__PURE__ */ new Date()) {
|
|
209
359
|
const from = new Date(promo.validFrom);
|
|
@@ -226,7 +376,9 @@ function boundedPromo(promo, basePrice) {
|
|
|
226
376
|
const pct = withinBounds(promo.value, 100);
|
|
227
377
|
return {
|
|
228
378
|
kind: "percent",
|
|
229
|
-
|
|
379
|
+
// The discount is rounded and taken off, as a promo code's is: two
|
|
380
|
+
// rules for one percentage would state two prices for it.
|
|
381
|
+
discounted: sumToCents(basePrice, -percentOf(basePrice, pct)),
|
|
230
382
|
original: basePrice,
|
|
231
383
|
pct
|
|
232
384
|
};
|
|
@@ -235,7 +387,7 @@ function boundedPromo(promo, basePrice) {
|
|
|
235
387
|
const saved = withinBounds(promo.value, basePrice);
|
|
236
388
|
return {
|
|
237
389
|
kind: "amount",
|
|
238
|
-
discounted:
|
|
390
|
+
discounted: sumToCents(basePrice, -saved),
|
|
239
391
|
original: basePrice,
|
|
240
392
|
saved
|
|
241
393
|
};
|
|
@@ -413,11 +565,222 @@ function appendNullableDecimalChange(out, field, oldValue, newValue, polarity) {
|
|
|
413
565
|
appendDecimalChange(out, field, oldValue, newValue, polarity);
|
|
414
566
|
}
|
|
415
567
|
|
|
568
|
+
// src/version-offer.ts
|
|
569
|
+
function takesSomethingAway(change) {
|
|
570
|
+
if (change.direction !== "REGRESSION") return false;
|
|
571
|
+
return change.field === "features.removed" || change.field.startsWith("quotas.");
|
|
572
|
+
}
|
|
573
|
+
function classifyVersionOffer(bound, candidate) {
|
|
574
|
+
const { changes } = classifyBundleVersionDiff(bound, candidate);
|
|
575
|
+
if (changes.some(takesSomethingAway)) return { class: "takes-something-away", changes };
|
|
576
|
+
if (changes.some((change) => change.direction === "REGRESSION")) {
|
|
577
|
+
return { class: "more-for-more", changes };
|
|
578
|
+
}
|
|
579
|
+
return { class: changes.length > 0 ? "improvement" : "same", changes };
|
|
580
|
+
}
|
|
581
|
+
|
|
416
582
|
// src/maintenance-window.types.ts
|
|
417
583
|
var MAINTENANCE_MESSAGE_MAX_LENGTH = 500;
|
|
418
584
|
var MAINTENANCE_STATE_MAX_AGE_SECONDS = 5;
|
|
419
585
|
var MAINTENANCE_CAPABILITY = "maintenance.manage";
|
|
420
586
|
|
|
587
|
+
// src/closed-value.ts
|
|
588
|
+
function oneOf(allowed, value, source) {
|
|
589
|
+
const match = allowed.find((entry) => entry === value);
|
|
590
|
+
if (match === void 0) {
|
|
591
|
+
throw new Error(
|
|
592
|
+
`${source.table} row '${source.id}' holds ${source.column} '${value}', which is none of ${allowed.join(", ")}.`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
return match;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/subscription-notice-mapping.ts
|
|
599
|
+
var TABLE = "subscription_notices";
|
|
600
|
+
var KINDS = {
|
|
601
|
+
"version-offered": true,
|
|
602
|
+
"version-retired": true,
|
|
603
|
+
"version-retirement-reminder": true,
|
|
604
|
+
"bundle-version-retired": true,
|
|
605
|
+
"bundle-version-retirement-reminder": true,
|
|
606
|
+
"bundle-version-offered": true
|
|
607
|
+
};
|
|
608
|
+
function toSubscriptionNoticeRecord(row) {
|
|
609
|
+
return {
|
|
610
|
+
id: row.id,
|
|
611
|
+
tenantId: row.tenantId,
|
|
612
|
+
subscriptionId: row.subscriptionId,
|
|
613
|
+
kind: oneOf(Object.keys(KINDS), row.kind, {
|
|
614
|
+
table: TABLE,
|
|
615
|
+
column: "kind",
|
|
616
|
+
id: row.id
|
|
617
|
+
}),
|
|
618
|
+
subject: row.subject,
|
|
619
|
+
content: row.content,
|
|
620
|
+
createdAt: row.createdAt,
|
|
621
|
+
claimedAt: row.claimedAt,
|
|
622
|
+
deliveredAt: row.deliveredAt,
|
|
623
|
+
delivery: row.deliveredAt === null ? null : deliveryOf(row)
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function deliveryOf(row) {
|
|
627
|
+
const { recipients, channel } = row;
|
|
628
|
+
if (!Array.isArray(recipients) || !recipients.every((recipient) => typeof recipient === "string") || channel === null) {
|
|
629
|
+
throw new Error(
|
|
630
|
+
`${TABLE} row '${row.id}' is delivered, but its recipients are not a list of strings or its channel is missing.`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
return { recipients: [...recipients], channel };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/version-sale.ts
|
|
637
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
638
|
+
function versionSale(version, now) {
|
|
639
|
+
if (!version.publishedAt) return { kind: "draft" };
|
|
640
|
+
if (isVersionActiveAt(version, now)) {
|
|
641
|
+
return { kind: "on-sale", until: lastDayOnSale(version) };
|
|
642
|
+
}
|
|
643
|
+
const start = dateOf(version.validFrom);
|
|
644
|
+
if (start && start > now && isVersionActiveAt(version, start)) {
|
|
645
|
+
return { kind: "scheduled", from: dayOf(start) };
|
|
646
|
+
}
|
|
647
|
+
return { kind: "off-sale", since: firstDayOffSale(version, now) };
|
|
648
|
+
}
|
|
649
|
+
function versionOnSale(versions, now) {
|
|
650
|
+
const onSale = versions.filter((v) => versionSale(v, now).kind === "on-sale");
|
|
651
|
+
const startOf = (v) => dateOf(v.validFrom)?.getTime() ?? -Infinity;
|
|
652
|
+
onSale.sort((a, b) => startOf(b) - startOf(a) || (b.version ?? 0) - (a.version ?? 0));
|
|
653
|
+
return onSale[0] ?? null;
|
|
654
|
+
}
|
|
655
|
+
function versionOnSaleOrNext(versions, now) {
|
|
656
|
+
const onSale = versionOnSale(versions, now);
|
|
657
|
+
if (onSale) return onSale;
|
|
658
|
+
const scheduled = versions.filter((v) => versionSale(v, now).kind === "scheduled");
|
|
659
|
+
const startOf = (v) => dateOf(v.validFrom)?.getTime() ?? Infinity;
|
|
660
|
+
scheduled.sort((a, b) => startOf(a) - startOf(b));
|
|
661
|
+
return scheduled[0] ?? null;
|
|
662
|
+
}
|
|
663
|
+
function lastDayOnSale(version) {
|
|
664
|
+
const days = [];
|
|
665
|
+
const validUntil = dateOf(version.validUntil);
|
|
666
|
+
if (validUntil) days.push(dayOf(validUntil));
|
|
667
|
+
const endsAt = dateOf(version.endsAt);
|
|
668
|
+
if (endsAt) days.push(dayOf(new Date(endsAt.getTime() - 1)));
|
|
669
|
+
return earliest(days);
|
|
670
|
+
}
|
|
671
|
+
function firstDayOffSale(version, now) {
|
|
672
|
+
const days = [];
|
|
673
|
+
const validUntil = dateOf(version.validUntil);
|
|
674
|
+
if (validUntil && dayOf(validUntil) < dayOf(now)) {
|
|
675
|
+
days.push(dayOf(new Date(validUntil.getTime() + DAY_MS)));
|
|
676
|
+
}
|
|
677
|
+
const endsAt = dateOf(version.endsAt);
|
|
678
|
+
if (endsAt && endsAt <= now) days.push(dayOf(new Date(endsAt.getTime() - 1 + DAY_MS)));
|
|
679
|
+
return earliest(days);
|
|
680
|
+
}
|
|
681
|
+
function earliest(days) {
|
|
682
|
+
return days.length === 0 ? null : days.reduce((a, b) => a < b ? a : b);
|
|
683
|
+
}
|
|
684
|
+
function dateOf(value) {
|
|
685
|
+
if (value === null || value === void 0) return null;
|
|
686
|
+
const date = new Date(value);
|
|
687
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
688
|
+
}
|
|
689
|
+
function dayOf(date) {
|
|
690
|
+
return date.toISOString().slice(0, 10);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/version-retirement.types.ts
|
|
694
|
+
var VERSION_RETIREMENT_CAPABILITY = "planVersions.retire";
|
|
695
|
+
|
|
696
|
+
// src/version-retirement-mapping.ts
|
|
697
|
+
function toVersionRetirementRecord(row) {
|
|
698
|
+
return {
|
|
699
|
+
id: row.id,
|
|
700
|
+
retired: {
|
|
701
|
+
planVersionId: row.retiredPlanVersionId,
|
|
702
|
+
planKey: row.retiredPlanKey,
|
|
703
|
+
version: row.retiredVersion
|
|
704
|
+
},
|
|
705
|
+
replacement: {
|
|
706
|
+
planVersionId: row.replacementPlanVersionId,
|
|
707
|
+
planKey: row.replacementPlanKey,
|
|
708
|
+
version: row.replacementVersion
|
|
709
|
+
},
|
|
710
|
+
announcedAt: row.announcedAt,
|
|
711
|
+
announcedBy: row.announcedBy
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/bundle-version-retirement.types.ts
|
|
716
|
+
var BUNDLE_VERSION_RETIREMENT_CAPABILITY = "bundleVersions.retire";
|
|
717
|
+
|
|
718
|
+
// src/bundle-version-retirement-mapping.ts
|
|
719
|
+
function toBundleVersionRetirementRecord(row) {
|
|
720
|
+
return {
|
|
721
|
+
id: row.id,
|
|
722
|
+
retired: {
|
|
723
|
+
bundleVersionId: row.retiredBundleVersionId,
|
|
724
|
+
bundleKey: row.retiredBundleKey,
|
|
725
|
+
version: row.retiredVersion
|
|
726
|
+
},
|
|
727
|
+
replacement: {
|
|
728
|
+
bundleVersionId: row.replacementBundleVersionId,
|
|
729
|
+
bundleKey: row.replacementBundleKey,
|
|
730
|
+
version: row.replacementVersion
|
|
731
|
+
},
|
|
732
|
+
announcedAt: row.announcedAt,
|
|
733
|
+
announcedBy: row.announcedBy
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/scheduled-change-after-write.ts
|
|
738
|
+
var NO_SCHEDULED_CHANGE = {
|
|
739
|
+
pendingPlan: null,
|
|
740
|
+
pendingBillingCycle: null,
|
|
741
|
+
pendingEffectiveAt: null,
|
|
742
|
+
pendingChangeVersionId: null
|
|
743
|
+
};
|
|
744
|
+
function scheduledChangeAfterWrite(current, input) {
|
|
745
|
+
if (!input.keepsPendingChange) return NO_SCHEDULED_CHANGE;
|
|
746
|
+
const movesOnlyTheRhythm = current.pendingPlan === current.plan && current.pendingChangeVersionId === null;
|
|
747
|
+
return movesOnlyTheRhythm && current.plan !== input.planId ? { pendingPlan: input.planId } : {};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/retirement-switch.ts
|
|
751
|
+
function retirementSwitchTerms(notice, billingCycle) {
|
|
752
|
+
const priceIn = (side) => billingCycle === "YEARLY" ? side.yearlyNet : side.monthlyNet;
|
|
753
|
+
const replacement = priceIn(notice.replacement);
|
|
754
|
+
if (replacement === null) return null;
|
|
755
|
+
const retired = priceIn(notice.retired);
|
|
756
|
+
if (retired === null || replacement <= retired) return { priceNet: replacement, held: null };
|
|
757
|
+
return {
|
|
758
|
+
priceNet: replacement,
|
|
759
|
+
held: {
|
|
760
|
+
priceNet: retired,
|
|
761
|
+
amountNet: sumToCents(replacement, -retired),
|
|
762
|
+
// The last day to cancel without notice is the same day: the last
|
|
763
|
+
// whole one before the date the replacement takes over.
|
|
764
|
+
lastDay: notice.lastDayToCancel
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/retirement-reminder.ts
|
|
770
|
+
var RETIREMENT_REMINDER_LEAD_DAYS = 14;
|
|
771
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
772
|
+
function retirementReminderDueAt(notice) {
|
|
773
|
+
return new Date(Date.parse(notice.effectiveAt) - RETIREMENT_REMINDER_LEAD_DAYS * DAY_MS2);
|
|
774
|
+
}
|
|
775
|
+
function retirementReminderIsDue(notice, now) {
|
|
776
|
+
return retirementReminderDueAt(notice) <= now && now.getTime() < Date.parse(notice.effectiveAt);
|
|
777
|
+
}
|
|
778
|
+
function retirementCostsTheSubscription(notice, billingCycle) {
|
|
779
|
+
if (notice.changes.some(takesSomethingAway)) return true;
|
|
780
|
+
const terms = retirementSwitchTerms(notice, billingCycle);
|
|
781
|
+
return terms === null || terms.held !== null;
|
|
782
|
+
}
|
|
783
|
+
|
|
421
784
|
// src/maintenance-window-mapping.ts
|
|
422
785
|
function toMaintenanceWindowRecord(row) {
|
|
423
786
|
return {
|
|
@@ -624,11 +987,42 @@ var CATALOG_ERROR_CODES = {
|
|
|
624
987
|
PLAN_TERMINATE_INVALID_DATE: "PLAN_TERMINATE_INVALID_DATE",
|
|
625
988
|
PLAN_TERMINATE_DATE_NOT_FUTURE: "PLAN_TERMINATE_DATE_NOT_FUTURE",
|
|
626
989
|
PLAN_TERMINATE_NOT_IMPLEMENTED: "PLAN_TERMINATE_NOT_IMPLEMENTED",
|
|
990
|
+
/**
|
|
991
|
+
* Subscriptions told of a retirement still move onto this version, and
|
|
992
|
+
* nothing moves onto a version that has ended. Carries the version, its
|
|
993
|
+
* plan key and the first day it may end: the day after the last move.
|
|
994
|
+
*/
|
|
995
|
+
PLAN_TERMINATE_BEFORE_RETIREMENT_MOVES: "PLAN_TERMINATE_BEFORE_RETIREMENT_MOVES",
|
|
996
|
+
/**
|
|
997
|
+
* Subscriptions told of a retirement are past their date and still to be
|
|
998
|
+
* moved onto this version — a move the platform could not make yet — so it
|
|
999
|
+
* cannot end until they have moved. Carries how many, the version and its
|
|
1000
|
+
* plan key.
|
|
1001
|
+
*/
|
|
1002
|
+
PLAN_TERMINATE_WHILE_MOVES_OVERDUE: "PLAN_TERMINATE_WHILE_MOVES_OVERDUE",
|
|
1003
|
+
/**
|
|
1004
|
+
* Subscriptions on a retired version have not yet been told of their move
|
|
1005
|
+
* onto this version — their notice has reached nobody, so their date is not
|
|
1006
|
+
* set — and it cannot end until they have been. Carries how many, the
|
|
1007
|
+
* version and its plan key.
|
|
1008
|
+
*/
|
|
1009
|
+
PLAN_TERMINATE_WHILE_NOTICES_UNDELIVERED: "PLAN_TERMINATE_WHILE_NOTICES_UNDELIVERED",
|
|
627
1010
|
// ── bundle versions ──
|
|
628
1011
|
BUNDLE_VERSION_ALREADY_PUBLISHED: "BUNDLE_VERSION_ALREADY_PUBLISHED",
|
|
629
1012
|
BUNDLE_VERSION_NOT_EDITABLE: "BUNDLE_VERSION_NOT_EDITABLE",
|
|
630
1013
|
BUNDLE_VERSION_NOT_PUBLISHED: "BUNDLE_VERSION_NOT_PUBLISHED",
|
|
631
1014
|
BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED",
|
|
1015
|
+
/**
|
|
1016
|
+
* The bundle version is published, and its window has not opened: it goes
|
|
1017
|
+
* on sale on `validFrom`, and until then its predecessor is the one sold.
|
|
1018
|
+
*/
|
|
1019
|
+
BUNDLE_VERSION_NOT_YET_ON_SALE: "BUNDLE_VERSION_NOT_YET_ON_SALE",
|
|
1020
|
+
/**
|
|
1021
|
+
* The add-on itself has been deleted from the catalogue. Deleting it leaves
|
|
1022
|
+
* its versions' dates as they were, so the version asked for can still be
|
|
1023
|
+
* inside its window; it is not on offer all the same.
|
|
1024
|
+
*/
|
|
1025
|
+
BUNDLE_DELETED: "BUNDLE_DELETED",
|
|
632
1026
|
BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION",
|
|
633
1027
|
BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE",
|
|
634
1028
|
BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE",
|
|
@@ -647,6 +1041,13 @@ var CATALOG_ERROR_CODES = {
|
|
|
647
1041
|
PLAN_NOT_FOUND: "PLAN_NOT_FOUND",
|
|
648
1042
|
PLAN_VERSION_NOT_FOUND: "PLAN_VERSION_NOT_FOUND",
|
|
649
1043
|
BUNDLE_NOT_FOUND: "BUNDLE_NOT_FOUND",
|
|
1044
|
+
/**
|
|
1045
|
+
* Bookings told that their add-on version is being retired have still to
|
|
1046
|
+
* move onto a version of this add-on — waiting for their notice, for their
|
|
1047
|
+
* date, or past it — and a deleted add-on is booked by nothing, the move
|
|
1048
|
+
* included. Carries how many, and the add-on's key.
|
|
1049
|
+
*/
|
|
1050
|
+
BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING: "BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING",
|
|
650
1051
|
BUNDLE_VERSION_NOT_FOUND: "BUNDLE_VERSION_NOT_FOUND",
|
|
651
1052
|
FEATURE_NOT_FOUND: "FEATURE_NOT_FOUND",
|
|
652
1053
|
QUOTA_NOT_FOUND: "QUOTA_NOT_FOUND",
|
|
@@ -687,6 +1088,22 @@ var BILLING_ERROR_CODES = {
|
|
|
687
1088
|
BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE",
|
|
688
1089
|
BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN",
|
|
689
1090
|
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN",
|
|
1091
|
+
/**
|
|
1092
|
+
* The bundle cannot run on a plan the subscription is already set to move
|
|
1093
|
+
* to — by a scheduled change, or by a retirement it has been told of — and
|
|
1094
|
+
* the booking would still be running then: that plan does not book it, or
|
|
1095
|
+
* has no price for it. Carries that plan's key, the rhythm it is billed in
|
|
1096
|
+
* there, and the day the move takes effect from, which can lie in the past
|
|
1097
|
+
* while the move has not run yet.
|
|
1098
|
+
*/
|
|
1099
|
+
BUNDLE_CANNOT_RUN_ON_UPCOMING_PLAN: "BUNDLE_CANNOT_RUN_ON_UPCOMING_PLAN",
|
|
1100
|
+
/**
|
|
1101
|
+
* As `BUNDLE_CANNOT_RUN_ON_UPCOMING_PLAN`, where only the rhythm stands in
|
|
1102
|
+
* the way: a yearly bundle, and the subscription billed monthly from the
|
|
1103
|
+
* day of the move — a switch of rhythm on the plan it keeps, for one, where
|
|
1104
|
+
* naming the plan would point at the one it is already on. Same values.
|
|
1105
|
+
*/
|
|
1106
|
+
BUNDLE_CANNOT_RUN_ON_UPCOMING_CYCLE: "BUNDLE_CANNOT_RUN_ON_UPCOMING_CYCLE",
|
|
690
1107
|
SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED",
|
|
691
1108
|
SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED",
|
|
692
1109
|
SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE",
|
|
@@ -706,6 +1123,12 @@ var BILLING_ERROR_CODES = {
|
|
|
706
1123
|
* in it: a plan without a yearly price is a monthly plan.
|
|
707
1124
|
*/
|
|
708
1125
|
PLAN_NOT_SOLD_IN_CYCLE: "PLAN_NOT_SOLD_IN_CYCLE",
|
|
1126
|
+
/**
|
|
1127
|
+
* The subscription is bound to a plan version the plan repository does not
|
|
1128
|
+
* find, or finds as a version of another plan. What the subscription pays
|
|
1129
|
+
* cannot be read, so no change is quoted until the binding is repaired.
|
|
1130
|
+
*/
|
|
1131
|
+
BOUND_PLAN_VERSION_UNREADABLE: "BOUND_PLAN_VERSION_UNREADABLE",
|
|
709
1132
|
/** Plan change refused. Carries `blockers[]` with their own codes. */
|
|
710
1133
|
PLAN_CHANGE_BLOCKED: "PLAN_CHANGE_BLOCKED",
|
|
711
1134
|
/**
|
|
@@ -728,6 +1151,182 @@ var BILLING_ERROR_CODES = {
|
|
|
728
1151
|
* Carries the recomputed dates, so the page can re-ask instead of guessing.
|
|
729
1152
|
*/
|
|
730
1153
|
CANCELLATION_TERMS_CHANGED: "CANCELLATION_TERMS_CHANGED",
|
|
1154
|
+
/**
|
|
1155
|
+
* The version the caller asked to switch to is no longer the one offered —
|
|
1156
|
+
* another is on sale, the subscription moved, or there is no offer. Nothing
|
|
1157
|
+
* was switched. Carries the current `offer`, or `null`, so the page can show
|
|
1158
|
+
* it instead of guessing.
|
|
1159
|
+
*/
|
|
1160
|
+
VERSION_OFFER_CHANGED: "VERSION_OFFER_CHANGED",
|
|
1161
|
+
/**
|
|
1162
|
+
* The version a change to another plan named is not the one on sale when
|
|
1163
|
+
* the change was submitted: its preview was shown at another moment.
|
|
1164
|
+
* Nothing was changed. Carries the current `preview`, so the page shows
|
|
1165
|
+
* what the change binds now instead of what it would have.
|
|
1166
|
+
*/
|
|
1167
|
+
PLAN_CHANGE_QUOTE_CHANGED: "PLAN_CHANGE_QUOTE_CHANGED",
|
|
1168
|
+
/**
|
|
1169
|
+
* A change to another plan named no version, where its preview names one:
|
|
1170
|
+
* nothing says which price and terms the customer was shown, so nothing
|
|
1171
|
+
* is changed.
|
|
1172
|
+
*/
|
|
1173
|
+
PLAN_CHANGE_VERSION_NOT_NAMED: "PLAN_CHANGE_VERSION_NOT_NAMED",
|
|
1174
|
+
/**
|
|
1175
|
+
* A version that takes something away takes effect when the term ends, and
|
|
1176
|
+
* the subscription's cancellation lands no later: the switch would never
|
|
1177
|
+
* happen, so it is refused rather than recorded. Carries both dates.
|
|
1178
|
+
*/
|
|
1179
|
+
VERSION_SWITCH_AFTER_CANCELLATION: "VERSION_SWITCH_AFTER_CANCELLATION",
|
|
1180
|
+
/**
|
|
1181
|
+
* A version that takes something away takes effect when the term ends, and
|
|
1182
|
+
* it stops being sold before then — by its window or because an operator
|
|
1183
|
+
* ended it — so it could not be bound when the change comes due. Refused
|
|
1184
|
+
* rather than recorded; carries the dates.
|
|
1185
|
+
*/
|
|
1186
|
+
VERSION_ENDS_BEFORE_SWITCH: "VERSION_ENDS_BEFORE_SWITCH",
|
|
1187
|
+
// ── retiring a version for running subscriptions ──
|
|
1188
|
+
/**
|
|
1189
|
+
* The operator's terms are not confirmed to carry the clause a retirement
|
|
1190
|
+
* rests on (`tenantBilling.orderlyRetirement.termsConfirmed` in
|
|
1191
|
+
* `config/saas.yaml`), so none is accepted.
|
|
1192
|
+
*/
|
|
1193
|
+
RETIREMENT_TERMS_NOT_CONFIRMED: "RETIREMENT_TERMS_NOT_CONFIRMED",
|
|
1194
|
+
/**
|
|
1195
|
+
* The version is still on sale: a subscription booked after the
|
|
1196
|
+
* announcement would be on it without having been told. Carries the plan
|
|
1197
|
+
* key and the version.
|
|
1198
|
+
*/
|
|
1199
|
+
RETIREMENT_VERSION_ON_SALE: "RETIREMENT_VERSION_ON_SALE",
|
|
1200
|
+
/** The replacement is not on sale, so nobody can continue on it. Carries plan key and version. */
|
|
1201
|
+
RETIREMENT_REPLACEMENT_NOT_ON_SALE: "RETIREMENT_REPLACEMENT_NOT_ON_SALE",
|
|
1202
|
+
/**
|
|
1203
|
+
* The replacement has no price in the rhythm some of the subscriptions it
|
|
1204
|
+
* reaches are billed in, so they cannot continue on it. Carries how many,
|
|
1205
|
+
* and the replacement's plan key and version.
|
|
1206
|
+
*/
|
|
1207
|
+
RETIREMENT_REPLACEMENT_NOT_SOLD_IN_RHYTHM: "RETIREMENT_REPLACEMENT_NOT_SOLD_IN_RHYTHM",
|
|
1208
|
+
/**
|
|
1209
|
+
* Some of the subscriptions the retirement reaches hold an add-on that
|
|
1210
|
+
* cannot run on the replacement's plan, so they cannot continue on it.
|
|
1211
|
+
* Carries how many, and the replacement's plan key and version.
|
|
1212
|
+
*/
|
|
1213
|
+
RETIREMENT_REPLACEMENT_CANNOT_CARRY_BUNDLES: "RETIREMENT_REPLACEMENT_CANNOT_CARRY_BUNDLES",
|
|
1214
|
+
/** The replacement named is the version being retired. */
|
|
1215
|
+
RETIREMENT_REPLACEMENT_IS_RETIRED: "RETIREMENT_REPLACEMENT_IS_RETIRED",
|
|
1216
|
+
/** No running subscription is on the version: there is nobody to tell. Carries plan key and version. */
|
|
1217
|
+
RETIREMENT_NOTHING_AFFECTED: "RETIREMENT_NOTHING_AFFECTED",
|
|
1218
|
+
/**
|
|
1219
|
+
* Some of the subscriptions it reaches were reached by a retirement within
|
|
1220
|
+
* the last twelve months, and a subscription is reached at most once in that
|
|
1221
|
+
* time. Carries how many and which.
|
|
1222
|
+
*/
|
|
1223
|
+
RETIREMENT_WITHIN_TWELVE_MONTHS: "RETIREMENT_WITHIN_TWELVE_MONTHS",
|
|
1224
|
+
/**
|
|
1225
|
+
* The subscriptions the retirement reaches, or their dates, changed since
|
|
1226
|
+
* the preview the operator confirmed. Carries the preview as it stands.
|
|
1227
|
+
*/
|
|
1228
|
+
RETIREMENT_PREVIEW_CHANGED: "RETIREMENT_PREVIEW_CHANGED",
|
|
1229
|
+
/** No retirement of the subscription's version waits for its date: there is nothing to switch to. */
|
|
1230
|
+
RETIREMENT_SWITCH_NOT_PENDING: "RETIREMENT_SWITCH_NOT_PENDING",
|
|
1231
|
+
/** The switch opens once the trial has converted: a trial has no contract to hold the price on. */
|
|
1232
|
+
RETIREMENT_SWITCH_IN_TRIAL: "RETIREMENT_SWITCH_IN_TRIAL",
|
|
1233
|
+
/**
|
|
1234
|
+
* An add-on running today cannot run beside the replacement's plan, which
|
|
1235
|
+
* the switch would move the subscription onto today. Carries the add-on,
|
|
1236
|
+
* the plan and the earliest day the booking can end. Its own code rather
|
|
1237
|
+
* than a plan change's: the switch has no other plan to choose and no day
|
|
1238
|
+
* to pick, so the way past it is the add-on ending.
|
|
1239
|
+
*/
|
|
1240
|
+
RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW",
|
|
1241
|
+
/**
|
|
1242
|
+
* As `RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW`, where only the version the
|
|
1243
|
+
* add-on continues on from a retirement's date cannot run beside the
|
|
1244
|
+
* replacement's plan. The version it is on can, so cancelling it — it then
|
|
1245
|
+
* ends before that date — lets the switch through. Given while that date
|
|
1246
|
+
* is ahead and the add-on is not cancelled yet; otherwise the earlier code
|
|
1247
|
+
* names the day it can end. Carries the add-on, that version, its date and
|
|
1248
|
+
* the plan.
|
|
1249
|
+
*/
|
|
1250
|
+
RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW: "RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW",
|
|
1251
|
+
/**
|
|
1252
|
+
* The subscription takes no switch now: a change is scheduled, it has
|
|
1253
|
+
* ended, or its plan is held for a special contract.
|
|
1254
|
+
*/
|
|
1255
|
+
RETIREMENT_SWITCH_NOT_OPEN: "RETIREMENT_SWITCH_NOT_OPEN",
|
|
1256
|
+
/**
|
|
1257
|
+
* The version named is not the replacement the retirement names. Carries
|
|
1258
|
+
* the retirement as it now stands.
|
|
1259
|
+
*/
|
|
1260
|
+
RETIREMENT_SWITCH_CHANGED: "RETIREMENT_SWITCH_CHANGED",
|
|
1261
|
+
// ── retiring an add-on version for running bookings ──
|
|
1262
|
+
//
|
|
1263
|
+
// An add-on retirement answers with the plan retirement's codes where the
|
|
1264
|
+
// sentence is the same — terms not confirmed, a version named as its own
|
|
1265
|
+
// replacement, twelve months, a preview that changed — and with these
|
|
1266
|
+
// where it names an add-on rather than a plan.
|
|
1267
|
+
/**
|
|
1268
|
+
* The add-on version is still on sale: a booking made after the
|
|
1269
|
+
* announcement would be on it without having been told. Carries the
|
|
1270
|
+
* add-on key and the version.
|
|
1271
|
+
*/
|
|
1272
|
+
BUNDLE_RETIREMENT_VERSION_ON_SALE: "BUNDLE_RETIREMENT_VERSION_ON_SALE",
|
|
1273
|
+
/** The replacement is not on sale, so no booking can continue on it. Carries add-on key and version. */
|
|
1274
|
+
BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE: "BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE",
|
|
1275
|
+
/**
|
|
1276
|
+
* The replacement is a version of another add-on. A booking continues on
|
|
1277
|
+
* a version of its own add-on, so it stays the same booking with its term.
|
|
1278
|
+
* Carries both add-on keys.
|
|
1279
|
+
*/
|
|
1280
|
+
BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE: "BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE",
|
|
1281
|
+
/**
|
|
1282
|
+
* Some of the bookings it reaches run, at their date, beside a plan the
|
|
1283
|
+
* replacement cannot run beside: not allowed there, without a price there
|
|
1284
|
+
* in the booking's rhythm, or in a longer rhythm than the plan's. Carries
|
|
1285
|
+
* how many, and the replacement's add-on key and version.
|
|
1286
|
+
*/
|
|
1287
|
+
BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN: "BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN",
|
|
1288
|
+
/** No running booking is on the version: there is nobody to tell. Carries add-on key and version. */
|
|
1289
|
+
BUNDLE_RETIREMENT_NOTHING_AFFECTED: "BUNDLE_RETIREMENT_NOTHING_AFFECTED",
|
|
1290
|
+
/**
|
|
1291
|
+
* The booking is on an add-on version being retired, and was cancelled to
|
|
1292
|
+
* end before it would move, so it was never told and nothing moves it.
|
|
1293
|
+
* Reinstated, it would run on past the date on a version nobody sells;
|
|
1294
|
+
* booking the replacement once it has ended is the way. Carries the add-on
|
|
1295
|
+
* key, the version, the replacement's version and the day it can be booked
|
|
1296
|
+
* from.
|
|
1297
|
+
*/
|
|
1298
|
+
BUNDLE_RETIREMENT_REINSTATE_REFUSED: "BUNDLE_RETIREMENT_REINSTATE_REFUSED",
|
|
1299
|
+
/**
|
|
1300
|
+
* As `BUNDLE_RETIREMENT_REINSTATE_REFUSED`, where the replacement cannot
|
|
1301
|
+
* run beside the plan the subscription is on when the booking ends, or
|
|
1302
|
+
* beside one it is already set to move to after that, in any rhythm it
|
|
1303
|
+
* could be booked in, so there is no version of the add-on to book.
|
|
1304
|
+
* Carries the add-on key, the version and the replacement's version.
|
|
1305
|
+
*/
|
|
1306
|
+
BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN: "BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN",
|
|
1307
|
+
/**
|
|
1308
|
+
* As `BUNDLE_RETIREMENT_REINSTATE_REFUSED`, where the subscription ends by
|
|
1309
|
+
* the day the replacement could be booked from, so there is no day to
|
|
1310
|
+
* name. Carries the add-on key, the version and the replacement's version.
|
|
1311
|
+
*/
|
|
1312
|
+
BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS",
|
|
1313
|
+
/**
|
|
1314
|
+
* The plan the add-on runs beside, or the rhythm it is billed in, changes
|
|
1315
|
+
* before the date the booking was told — by a change scheduled, or by a
|
|
1316
|
+
* retirement told that moves the subscription to another plan — so the
|
|
1317
|
+
* price a switch now would hold is not the one the booking pays until
|
|
1318
|
+
* then. Switching once the plan has changed, or waiting for the
|
|
1319
|
+
* date, is the way. Carries the add-on and the date.
|
|
1320
|
+
*/
|
|
1321
|
+
BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES",
|
|
1322
|
+
/**
|
|
1323
|
+
* The add-on version the caller asked to switch a booking to is no longer
|
|
1324
|
+
* the one offered beside it — another is on sale, the booking moved, or
|
|
1325
|
+
* there is no offer. Nothing was switched. Carries the version named
|
|
1326
|
+
* (`bundleVersionId`) and the current `offer`, or `null`, so the page can
|
|
1327
|
+
* show it instead of guessing.
|
|
1328
|
+
*/
|
|
1329
|
+
BUNDLE_VERSION_OFFER_CHANGED: "BUNDLE_VERSION_OFFER_CHANGED",
|
|
731
1330
|
// ── the preview routes' own blockers and warnings ──
|
|
732
1331
|
//
|
|
733
1332
|
// These travel inside a 200 response, in `blockers[]` and `warnings[]`, not
|
|
@@ -757,14 +1356,33 @@ var BILLING_ERROR_CODES = {
|
|
|
757
1356
|
CANCELLATION_LOCKS_THE_CYCLE: "CANCELLATION_LOCKS_THE_CYCLE",
|
|
758
1357
|
/**
|
|
759
1358
|
* A bundle the tenant already holds runs past the cycle they are moving to.
|
|
1359
|
+
* Carries the bundle, the earliest day the booking can end, and the two
|
|
1360
|
+
* rhythms.
|
|
760
1361
|
*
|
|
761
1362
|
* Its own code rather than `BUNDLE_CYCLE_EXCEEDS_PLAN`, which states the
|
|
762
1363
|
* same rule about a booking that has not been made yet. The two need
|
|
763
1364
|
* different sentences: this one can name the day the obstacle lifts and
|
|
764
|
-
* tell the reader to
|
|
765
|
-
*
|
|
1365
|
+
* tell the reader how to get past it, and that advice is wrong for someone
|
|
1366
|
+
* who is only about to book. One template cannot serve both.
|
|
766
1367
|
*/
|
|
767
1368
|
BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE",
|
|
1369
|
+
/**
|
|
1370
|
+
* A bundle the tenant already holds cannot run on the plan they are moving
|
|
1371
|
+
* to: that plan may not book it, or it has no price there in the rhythm
|
|
1372
|
+
* the booking is billed in. Carries the bundle, the plan and the earliest
|
|
1373
|
+
* day the booking can end.
|
|
1374
|
+
*/
|
|
1375
|
+
BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN: "BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN",
|
|
1376
|
+
/**
|
|
1377
|
+
* A bundle the tenant already holds can run on the plan they are moving
|
|
1378
|
+
* to, but the version it continues on from a retirement's date cannot.
|
|
1379
|
+
* Cancelling it lets the change through at once: the booking then ends
|
|
1380
|
+
* before that date and never reaches the version. Given while that date is
|
|
1381
|
+
* ahead and the booking is not cancelled yet; otherwise
|
|
1382
|
+
* `BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN` names the day it can end.
|
|
1383
|
+
* Carries the bundle, that version, its date and the plan.
|
|
1384
|
+
*/
|
|
1385
|
+
BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN: "BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN",
|
|
768
1386
|
/**
|
|
769
1387
|
* Features of the previewed bundle are already covered by the plan or by
|
|
770
1388
|
* another booked bundle. A warning rather than a blocker: paying twice is
|
|
@@ -786,7 +1404,6 @@ var BILLING_ERROR_CODES = {
|
|
|
786
1404
|
* tenant reads and therefore needs a shipped text for.
|
|
787
1405
|
*/
|
|
788
1406
|
BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED",
|
|
789
|
-
NO_PENDING_PLAN_VERSION: "NO_PENDING_PLAN_VERSION",
|
|
790
1407
|
ONBOARDING_CREATE_FAILED: "ONBOARDING_CREATE_FAILED",
|
|
791
1408
|
BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS",
|
|
792
1409
|
/**
|
|
@@ -835,6 +1452,21 @@ var CONTRACT_ERROR_CODES = {
|
|
|
835
1452
|
* describes. Nothing was written; load the offer and conclude it again.
|
|
836
1453
|
*/
|
|
837
1454
|
CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED",
|
|
1455
|
+
/**
|
|
1456
|
+
* An offer concludes a tenant's first contract, and this tenant has a
|
|
1457
|
+
* contract in force at the moment the offer's would take effect, or one
|
|
1458
|
+
* beginning after it. Concluded, the two would run side by side. Nothing
|
|
1459
|
+
* was written; a running subscription changes through its plan and its
|
|
1460
|
+
* add-ons. Carries the offer and the contract that runs.
|
|
1461
|
+
*/
|
|
1462
|
+
CHECKOUT_OFFER_CONTRACT_IN_FORCE: "CHECKOUT_OFFER_CONTRACT_IN_FORCE",
|
|
1463
|
+
/**
|
|
1464
|
+
* The offer names a version of an add-on the tenant has booked in another
|
|
1465
|
+
* version. Concluded, the contract would name a version the booking is not
|
|
1466
|
+
* on. Nothing was written; a booking changes its version through the offer
|
|
1467
|
+
* beside it. Carries the offer, the add-on, the booking and both versions.
|
|
1468
|
+
*/
|
|
1469
|
+
CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION: "CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION",
|
|
838
1470
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND",
|
|
839
1471
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT",
|
|
840
1472
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED",
|
|
@@ -845,7 +1477,12 @@ var CONTRACT_ERROR_CODES = {
|
|
|
845
1477
|
* second attempt, or a contract begins after the moment the successor
|
|
846
1478
|
* would take effect. Nothing was written.
|
|
847
1479
|
*/
|
|
848
|
-
SUBSCRIPTION_CONTRACT_CHANGED: "SUBSCRIPTION_CONTRACT_CHANGED"
|
|
1480
|
+
SUBSCRIPTION_CONTRACT_CHANGED: "SUBSCRIPTION_CONTRACT_CHANGED",
|
|
1481
|
+
/**
|
|
1482
|
+
* A contract states a tax rate — `field` names where — other than the one
|
|
1483
|
+
* the installation's tax adapter decides for its subscriber.
|
|
1484
|
+
*/
|
|
1485
|
+
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED"
|
|
849
1486
|
};
|
|
850
1487
|
var SUBSCRIBER_ERROR_CODES = {
|
|
851
1488
|
/**
|
|
@@ -869,7 +1506,30 @@ var SUBSCRIBER_ERROR_CODES = {
|
|
|
869
1506
|
SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "SUBSCRIBER_CORRECTION_ACTOR_REQUIRED",
|
|
870
1507
|
SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "SUBSCRIBER_CORRECTION_CHANGES_NOTHING",
|
|
871
1508
|
/** The operator declared another legal entity: that is a transfer, not an edit. */
|
|
872
|
-
SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER"
|
|
1509
|
+
SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER",
|
|
1510
|
+
/**
|
|
1511
|
+
* A contact change named whether the subscriber is a business. That is part
|
|
1512
|
+
* of its tax origin and changes as a recorded change naming who made it.
|
|
1513
|
+
*/
|
|
1514
|
+
SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT: "SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT",
|
|
1515
|
+
/** A change of the subscriber's contact details or business status did not say who makes it. */
|
|
1516
|
+
SUBSCRIBER_CHANGE_ACTOR_REQUIRED: "SUBSCRIBER_CHANGE_ACTOR_REQUIRED",
|
|
1517
|
+
/**
|
|
1518
|
+
* Where a tax adapter decides, a contract names its subscriber only once
|
|
1519
|
+
* the address an invoice names is complete (`SC-PRIC-032`): a party copied
|
|
1520
|
+
* onto a contract incomplete cannot be completed afterwards. Carries
|
|
1521
|
+
* `missing`, the empty fields, for a screen to name by their labels; the
|
|
1522
|
+
* shipped sentence names all four rather than the keys.
|
|
1523
|
+
*/
|
|
1524
|
+
SUBSCRIBER_IDENTITY_INCOMPLETE: "SUBSCRIBER_IDENTITY_INCOMPLETE",
|
|
1525
|
+
/**
|
|
1526
|
+
* A change of whether the subscriber is a business states no reason. It
|
|
1527
|
+
* decides the tax treatment of the next invoice, so the change log keeps
|
|
1528
|
+
* why it moved (`SC-PRIC-043`).
|
|
1529
|
+
*/
|
|
1530
|
+
SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED: "SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED",
|
|
1531
|
+
/** A check of the subscriber's VAT identification number was asked for, and it holds none. */
|
|
1532
|
+
SUBSCRIBER_VAT_ID_MISSING: "SUBSCRIBER_VAT_ID_MISSING"
|
|
873
1533
|
};
|
|
874
1534
|
var REGISTRATION_ERROR_CODES = {
|
|
875
1535
|
PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND",
|
|
@@ -974,6 +1634,29 @@ var MAINTENANCE_ERROR_CODES = {
|
|
|
974
1634
|
/** The message is longer than `MAINTENANCE_MESSAGE_MAX_LENGTH`. */
|
|
975
1635
|
MAINTENANCE_MESSAGE_TOO_LONG: "MAINTENANCE_MESSAGE_TOO_LONG"
|
|
976
1636
|
};
|
|
1637
|
+
var TAX_ERROR_CODES = {
|
|
1638
|
+
/**
|
|
1639
|
+
* The tax adapter answers that it does not support the subscriber's case;
|
|
1640
|
+
* `adapter` names it and `reason` is its sentence. Nothing is concluded with
|
|
1641
|
+
* a guessed tax (`SC-PRIC-039`).
|
|
1642
|
+
*/
|
|
1643
|
+
TAX_TREATMENT_NOT_SUPPORTED: "TAX_TREATMENT_NOT_SUPPORTED",
|
|
1644
|
+
/**
|
|
1645
|
+
* The tax adapter needs the VAT identification number checked to treat the
|
|
1646
|
+
* case, and the check did not complete — the service is unavailable, timed
|
|
1647
|
+
* out, or answered in a way the adapter cannot read (`503`). Nothing is
|
|
1648
|
+
* decided in the subscriber's favour on a number not checked
|
|
1649
|
+
* (`SC-PRIC-040`): try again later. `adapter` names the adapter and
|
|
1650
|
+
* `reason` says why.
|
|
1651
|
+
*/
|
|
1652
|
+
TAX_VAT_ID_CHECK_NOT_COMPLETED: "TAX_VAT_ID_CHECK_NOT_COMPLETED",
|
|
1653
|
+
/**
|
|
1654
|
+
* A VAT identification number was to be checked, and `config/saas.yaml`
|
|
1655
|
+
* names no tax adapter, which is what names the service to check with
|
|
1656
|
+
* (`409`).
|
|
1657
|
+
*/
|
|
1658
|
+
TAX_VAT_ID_CHECK_NOT_AVAILABLE: "TAX_VAT_ID_CHECK_NOT_AVAILABLE"
|
|
1659
|
+
};
|
|
977
1660
|
var PLATFORM_ERROR_CODES = {
|
|
978
1661
|
...SETUP_ERROR_CODES,
|
|
979
1662
|
...AUTH_ERROR_CODES,
|
|
@@ -985,7 +1668,8 @@ var PLATFORM_ERROR_CODES = {
|
|
|
985
1668
|
...REGISTRATION_ERROR_CODES,
|
|
986
1669
|
...PAYMENT_ERROR_CODES,
|
|
987
1670
|
...SETTINGS_ERROR_CODES,
|
|
988
|
-
...MAINTENANCE_ERROR_CODES
|
|
1671
|
+
...MAINTENANCE_ERROR_CODES,
|
|
1672
|
+
...TAX_ERROR_CODES
|
|
989
1673
|
};
|
|
990
1674
|
|
|
991
1675
|
// src/errors.ts
|
|
@@ -1004,92 +1688,552 @@ var PlatformUserExistsError = class extends Error {
|
|
|
1004
1688
|
function isPlatformUserExistsError(err) {
|
|
1005
1689
|
return err instanceof Error && err.code === USER_ALREADY_EXISTS;
|
|
1006
1690
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
return index;
|
|
1016
|
-
}
|
|
1017
|
-
function collectUnsatisfiedRequires(selected, index) {
|
|
1018
|
-
const own = new Set(selected);
|
|
1019
|
-
const unsatisfied = /* @__PURE__ */ new Set();
|
|
1020
|
-
for (const featureKey of selected) {
|
|
1021
|
-
for (const required of index.get(featureKey) ?? []) {
|
|
1022
|
-
if (!own.has(required)) unsatisfied.add(required);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
return [...unsatisfied].sort((a, b) => a.localeCompare(b));
|
|
1026
|
-
}
|
|
1027
|
-
function missingRequiresFor(bundle, coveredFeatures) {
|
|
1028
|
-
const missing = (bundle.requiresFeatures ?? []).filter((key) => !coveredFeatures.has(key));
|
|
1029
|
-
return [...new Set(missing)].sort((a, b) => a.localeCompare(b));
|
|
1030
|
-
}
|
|
1031
|
-
function resolveBundleAvailability(bundle, coveredFeatures) {
|
|
1032
|
-
if (bundle.features.length > 0 && bundle.features.every((f) => coveredFeatures.has(f))) {
|
|
1033
|
-
return "covered";
|
|
1034
|
-
}
|
|
1035
|
-
if (missingRequiresFor(bundle, coveredFeatures).length > 0) return "missing-requires";
|
|
1036
|
-
return "bookable";
|
|
1037
|
-
}
|
|
1038
|
-
function coverageExcludingSelf(selfVersionId, planFeatures, selectedBundles) {
|
|
1039
|
-
const covered = new Set(planFeatures);
|
|
1040
|
-
for (const bundle of selectedBundles) {
|
|
1041
|
-
if (bundle.bundleVersionId === selfVersionId) continue;
|
|
1042
|
-
for (const feature of bundle.features) covered.add(feature);
|
|
1691
|
+
var PERSISTENCE_REFUSAL = "PersistenceRefusal";
|
|
1692
|
+
var PersistenceRefusal = class extends Error {
|
|
1693
|
+
constructor(code, reason, message, params = {}) {
|
|
1694
|
+
super(message);
|
|
1695
|
+
this.code = code;
|
|
1696
|
+
this.reason = reason;
|
|
1697
|
+
this.params = params;
|
|
1698
|
+
this.name = PERSISTENCE_REFUSAL;
|
|
1043
1699
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const orderB = b.sortOrder ?? Number.MAX_SAFE_INTEGER;
|
|
1053
|
-
if (orderA !== orderB) return orderA - orderB;
|
|
1054
|
-
return a.bundleVersionId.localeCompare(b.bundleVersionId);
|
|
1700
|
+
code;
|
|
1701
|
+
reason;
|
|
1702
|
+
params;
|
|
1703
|
+
};
|
|
1704
|
+
function isPersistenceRefusal(err) {
|
|
1705
|
+
if (!(err instanceof Error) || err.name !== PERSISTENCE_REFUSAL) return false;
|
|
1706
|
+
const { code, reason } = err;
|
|
1707
|
+
return typeof code === "string" && (reason === "gone" || reason === "moved");
|
|
1055
1708
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1709
|
+
|
|
1710
|
+
// src/optional-canonical-models.ts
|
|
1711
|
+
var OPTIONAL_CANONICAL_MODELS = {
|
|
1712
|
+
SuperAdminUser: ["core.superAdminProvisioning"],
|
|
1713
|
+
SuperAdminMfa: ["core.mfa"],
|
|
1714
|
+
AppliedSettings: ["core.appliedSettings"],
|
|
1715
|
+
SettingsChange: ["core.appliedSettings"],
|
|
1716
|
+
MaintenanceWindow: ["core.maintenanceWindows"],
|
|
1717
|
+
SubscriptionNotice: ["tenantBilling.subscriptionNotices"],
|
|
1718
|
+
VersionRetirement: ["tenantBilling.versionRetirements"],
|
|
1719
|
+
BundleVersionRetirement: ["tenantBilling.bundleVersionRetirements"],
|
|
1720
|
+
SubscriberLedgerEntry: ["entitlement.subscriberLedgerRepository"],
|
|
1721
|
+
PromoCodeHold: ["promo.holdRepository"]
|
|
1722
|
+
};
|
|
1723
|
+
function membersLeftOut(notAdopted = []) {
|
|
1724
|
+
const unknown = notAdopted.filter((model) => !Object.hasOwn(OPTIONAL_CANONICAL_MODELS, model));
|
|
1725
|
+
if (unknown.length > 0) {
|
|
1726
|
+
throw new Error(
|
|
1727
|
+
`notAdopted names ${unknown.join(", ")}, which the persistence bundle cannot leave out. It can leave out ${Object.keys(OPTIONAL_CANONICAL_MODELS).join(", ")}; for any other model it needs, pass your own adapter for the port that model serves.`
|
|
1061
1728
|
);
|
|
1062
|
-
if (redundantIndex === -1) return kept;
|
|
1063
|
-
kept.splice(redundantIndex, 1);
|
|
1064
1729
|
}
|
|
1730
|
+
return new Set(
|
|
1731
|
+
notAdopted.flatMap((model) => OPTIONAL_CANONICAL_MODELS[model])
|
|
1732
|
+
);
|
|
1065
1733
|
}
|
|
1066
1734
|
|
|
1067
|
-
// src/
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
constructor(reason) {
|
|
1072
|
-
super(`Payment callback rejected: ${reason}`);
|
|
1073
|
-
this.name = "PaymentCallbackRejectedError";
|
|
1735
|
+
// src/concurrency-gate.ts
|
|
1736
|
+
function concurrencyGate(limit) {
|
|
1737
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
1738
|
+
throw new RangeError(`A concurrency limit is a whole number of at least 1, not ${limit}.`);
|
|
1074
1739
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1740
|
+
let active = 0;
|
|
1741
|
+
const waiting = [];
|
|
1742
|
+
const release = () => {
|
|
1743
|
+
const next = waiting.shift();
|
|
1744
|
+
if (next) next();
|
|
1745
|
+
else active -= 1;
|
|
1746
|
+
};
|
|
1747
|
+
return async (work) => {
|
|
1748
|
+
if (active < limit) active += 1;
|
|
1749
|
+
else await new Promise((admit) => waiting.push(admit));
|
|
1750
|
+
try {
|
|
1751
|
+
return await work();
|
|
1752
|
+
} finally {
|
|
1753
|
+
release();
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
var GATES = /* @__PURE__ */ new WeakMap();
|
|
1758
|
+
function concurrencyGateOf(pool, limit) {
|
|
1759
|
+
const known = GATES.get(pool);
|
|
1760
|
+
if (known) {
|
|
1761
|
+
if (known.limit !== limit) {
|
|
1762
|
+
throw new RangeError(
|
|
1763
|
+
`This pool is bounded at ${known.limit} transactions already; a second bound of ${limit} would split it.`
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
return known.gate;
|
|
1767
|
+
}
|
|
1768
|
+
const gate = concurrencyGate(limit);
|
|
1769
|
+
GATES.set(pool, { limit, gate });
|
|
1770
|
+
return gate;
|
|
1078
1771
|
}
|
|
1079
1772
|
|
|
1080
|
-
// src/
|
|
1081
|
-
var
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1773
|
+
// src/error-messages.ts
|
|
1774
|
+
var ERROR_MESSAGES_EN = {
|
|
1775
|
+
// ── setup ──
|
|
1776
|
+
SETUP_DISABLED: "{envVar} is not set \u2014 setup is disabled.",
|
|
1777
|
+
INVALID_SETUP_TOKEN: "The setup token is not valid.",
|
|
1778
|
+
SETUP_ALREADY_DONE: "Setup has already been completed.",
|
|
1779
|
+
INVALID_EMAIL: "That is not a valid email address.",
|
|
1780
|
+
EMAIL_EXISTS: "An account with this email address already exists.",
|
|
1781
|
+
// ── auth ──
|
|
1782
|
+
NOT_AUTHENTICATED: "Not authenticated",
|
|
1783
|
+
NO_TENANT_ASSIGNED: "No tenant assigned",
|
|
1784
|
+
TENANT_CONTEXT_MISSING: "No tenant ID found on the request",
|
|
1785
|
+
TENANT_ADMIN_REQUIRED: "This action requires the TENANT_ADMIN role.",
|
|
1786
|
+
BILLING_PERMISSION_REQUIRED: "This part of the billing area requires the billing permission.",
|
|
1787
|
+
SUPER_ADMIN_REQUIRED: "Only the SUPER_ADMIN role is allowed",
|
|
1788
|
+
MFA_NOT_SET_UP: "Run the MFA setup via the CLI first.",
|
|
1789
|
+
MFA_REQUIRED: "TOTP code required in the X-Mfa-Code header.",
|
|
1790
|
+
MFA_FAILED: "Invalid TOTP code.",
|
|
1791
|
+
AUTH_GUARDS_NOT_CONFIGURED: "TenantBillingModule.forRoot.authGuards is not configured.",
|
|
1792
|
+
// ── catalog ──
|
|
1793
|
+
PLAN_HAS_DRAFTS: "Plan '{planKey}' still has {draftCount} open draft version(s). Discard them first (DELETE /admin/catalog/plan-versions/:id) or publish them.",
|
|
1794
|
+
PLAN_HAS_PUBLISHED_VERSIONS: "Plan '{planKey}' cannot be {operation} \u2014 it has {publishedCount} published version(s) ({liveCount} live, {supersededCount} superseded). Existing subscriptions reference those versions (contract protection P1), so the plan record must be preserved.",
|
|
1795
|
+
PLAN_HARD_DELETE_NOT_IMPLEMENTED: "Hard delete is not implemented in the current repository. Implement PlanRepository.hardDelete.",
|
|
1796
|
+
PLAN_VERSION_ALREADY_PUBLISHED: "PlanVersion '{versionId}' is already published; it is not published again or discarded.",
|
|
1797
|
+
PLAN_VERSION_NOT_EDITABLE: "PlanVersion '{versionId}' is not editable. Only drafts and published versions are editable that are latest-in-chain, bind no subscription yet, and whose validFrom lies in the future.",
|
|
1798
|
+
PLAN_VERSION_REGRESSION: "This plan version is regressive (feature removed / quota lowered / price raised). Publishing requires an explicit `forceRegressive: true` (UI confirmation dialog with MFA).",
|
|
1799
|
+
PLAN_VERSION_ZERO_PRICE: "A plan version cannot be published with a price of 0.00 (guard against seed placeholder). For deliberately free special contracts, set allowZeroPrice.",
|
|
1800
|
+
PLAN_VERSION_DISCARD_NOT_IMPLEMENTED: "Discard is not implemented in the current repository. Implement PlanRepository.deletePlanVersionDraft.",
|
|
1801
|
+
PLAN_VERSION_NOT_PUBLISHED: "PlanVersion '{versionId}' is not published and cannot be terminated.",
|
|
1802
|
+
PLAN_VERSION_SUPERSEDED: "PlanVersion '{versionId}' has already been superseded by a newer version and cannot be terminated.",
|
|
1803
|
+
PLAN_VERSION_VALID_FROM_REQUIRED: "validFrom must be set when publishing (on the draft or the publish call)..",
|
|
1804
|
+
PLAN_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' is not a day (YYYY-MM-DD)",
|
|
1805
|
+
PLAN_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS: "validFrom ({validFrom}) must be strictly after the validFrom of the previous version ({previousValidFrom}).",
|
|
1806
|
+
PLAN_VERSION_VALID_FROM_NOT_GAPLESS: "The predecessor has validUntil={previousValidUntil} \u2014 the successor must start seamlessly on the next day ({requiredValidFrom}). Received: {received}.",
|
|
1807
|
+
PLAN_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' is not a day (YYYY-MM-DD)",
|
|
1808
|
+
PLAN_VERSION_VALID_UNTIL_BEFORE_FROM: "validUntil ({validUntil}) must be strictly after validFrom ({validFrom}).",
|
|
1809
|
+
PLAN_TERMINATE_INVALID_DATE: "endsAt is not a valid date.",
|
|
1810
|
+
PLAN_TERMINATE_DATE_NOT_FUTURE: "endsAt ({endsAt}) must lie strictly in the future.",
|
|
1811
|
+
PLAN_TERMINATE_NOT_IMPLEMENTED: "Terminate is not implemented in the current repository. Implement PlanRepository.terminate.",
|
|
1812
|
+
PLAN_TERMINATE_BEFORE_RETIREMENT_MOVES: "Subscriptions still move to version {version} of {planKey}, so it can end on {date} at the earliest.",
|
|
1813
|
+
PLAN_TERMINATE_WHILE_MOVES_OVERDUE: "{count} subscriptions are past their date and still to be moved to version {version} of {planKey}, so it cannot end until they have moved.",
|
|
1814
|
+
PLAN_TERMINATE_WHILE_NOTICES_UNDELIVERED: "{count} subscriptions have not yet been told that they move to version {version} of {planKey}, so it cannot end until they have been.",
|
|
1815
|
+
BUNDLE_VERSION_ALREADY_PUBLISHED: "BundleVersion '{versionId}' is already published; it is not published again or discarded.",
|
|
1816
|
+
BUNDLE_VERSION_NOT_EDITABLE: "BundleVersion '{versionId}' is not editable. Only drafts and published versions are editable that are latest-in-chain, bind no subscription yet, and whose validFrom lies in the future.",
|
|
1817
|
+
BUNDLE_VERSION_NOT_PUBLISHED: "BundleVersion '{bundleVersionId}' is not published and cannot be booked.",
|
|
1818
|
+
BUNDLE_VERSION_SUPERSEDED: "BundleVersion '{bundleVersionId}' has been superseded by a newer version.",
|
|
1819
|
+
BUNDLE_VERSION_NOT_YET_ON_SALE: "BundleVersion '{bundleVersionId}' goes on sale on {validFrom} and cannot be booked before.",
|
|
1820
|
+
BUNDLE_DELETED: "Bundle '{bundleKey}' has been deleted from the catalogue and cannot be booked.",
|
|
1821
|
+
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).",
|
|
1822
|
+
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.",
|
|
1823
|
+
BUNDLE_VERSION_NO_PRICE: "This bundle version cannot be published without a price: neither a base price nor any plan override resolves one.",
|
|
1824
|
+
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.",
|
|
1825
|
+
BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "Discard is not implemented in the current repository. Implement BundleRepository.deleteDraft.",
|
|
1826
|
+
BUNDLE_VERSION_VALID_FROM_REQUIRED: "Published bundle versions must keep a validFrom. Set a new future date, or edit the draft before publishing.",
|
|
1827
|
+
BUNDLE_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' is not a day (YYYY-MM-DD)",
|
|
1828
|
+
BUNDLE_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS: "validFrom ({validFrom}) must be strictly after the validFrom of the previous version ({previousValidFrom}).",
|
|
1829
|
+
BUNDLE_VERSION_VALID_FROM_NOT_GAPLESS: "The predecessor has validUntil={previousValidUntil} \u2014 the successor must start seamlessly on the next day ({requiredValidFrom}). Received: {received}.",
|
|
1830
|
+
BUNDLE_VERSION_VALID_FROM_NOT_FUTURE: "validFrom ({validFrom}) must, for a published-but-future bundle version, still lie in the future.",
|
|
1831
|
+
BUNDLE_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' is not a day (YYYY-MM-DD)",
|
|
1832
|
+
BUNDLE_VERSION_VALID_UNTIL_BEFORE_FROM: "validUntil ({validUntil}) must be strictly after validFrom ({validFrom}).",
|
|
1833
|
+
STRICT_MODE_VIOLATIONS: "The strict-mode check found drift against the discovery snapshot.",
|
|
1834
|
+
PLAN_NOT_FOUND: "Plan '{planId}' not found",
|
|
1835
|
+
PLAN_VERSION_NOT_FOUND: "PlanVersion '{versionId}' not found",
|
|
1836
|
+
BUNDLE_NOT_FOUND: "Bundle '{bundleId}' not found",
|
|
1837
|
+
BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING: "{count} bookings still move onto a version of {bundleKey}, as a retirement told them. The add-on can be deleted once they have.",
|
|
1838
|
+
BUNDLE_VERSION_NOT_FOUND: "BundleVersion '{bundleVersionId}' not found",
|
|
1839
|
+
FEATURE_NOT_FOUND: "Feature '{featureKey}' not found",
|
|
1840
|
+
QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found",
|
|
1841
|
+
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' not found",
|
|
1842
|
+
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.",
|
|
1843
|
+
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' not found",
|
|
1844
|
+
PLAN_ALREADY_EXISTS: "Plan '{planKey}' already exists",
|
|
1845
|
+
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' already exists",
|
|
1846
|
+
MARKETING_PROJECTION_ALREADY_EXISTS: "Marketing projection for {targetType}/{targetVersionId}/{locale} already exists \u2014 use PATCH to edit it",
|
|
1847
|
+
PLAN_DRAFT_ALREADY_EXISTS: "Plan '{planKey}' already has a draft version v{draftVersion}; publish or discard it first",
|
|
1848
|
+
BUNDLE_DRAFT_ALREADY_EXISTS: "Bundle '{bundleKey}' already has a draft version v{draftVersion}; publish or discard it first",
|
|
1849
|
+
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",
|
|
1850
|
+
DISCOVERY_STATUS_TRANSITION_INVALID: "Transition '{from}' \u2192 '{to}' is not allowed",
|
|
1851
|
+
DISCOVERY_NOT_INITIALIZED: "Approval requires a discovery snapshot \u2014 discovery is not initialised (#25)",
|
|
1852
|
+
// ── catalogue import ──
|
|
1853
|
+
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.",
|
|
1854
|
+
PLAN_CATALOG_INVALID: "The plan catalog was read, and then rejected: {message}",
|
|
1855
|
+
// ── billing ──
|
|
1856
|
+
BUNDLE_ALREADY_SUBSCRIBED: "Subscription '{subscriptionId}' has already actively booked this bundle.",
|
|
1857
|
+
BUNDLE_INCOMPATIBLE_WITH_PLAN: "This bundle cannot be booked on the {planKey} plan. The plans that can book it: {allowedPlanKeys}.",
|
|
1858
|
+
BUNDLE_NOT_SELF_SERVICE: "Bundle '{bundleKey}' is only activated via a special contract. Please contact the contract manager.",
|
|
1859
|
+
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.",
|
|
1860
|
+
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "This bundle has no price for the {planKey} plan in this billing cycle, so it cannot be booked from here.",
|
|
1861
|
+
BUNDLE_CANNOT_RUN_ON_UPCOMING_PLAN: "This bundle cannot run on the {planKey} plan, which the subscription moves to with effect from {from}.",
|
|
1862
|
+
BUNDLE_CANNOT_RUN_ON_UPCOMING_CYCLE: "A yearly bundle cannot run beside the monthly billing the subscription moves to with effect from {from}.",
|
|
1863
|
+
SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' is already cancelled.",
|
|
1864
|
+
SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' is not cancelled.",
|
|
1865
|
+
SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "Cancellation already in effect \u2014 book the bundle again.",
|
|
1866
|
+
SUBSCRIPTION_NOT_FOUND: "No subscription for tenant {tenantId}",
|
|
1867
|
+
SUBSCRIPTION_TENANT_MISMATCH: "Subscription does not belong to the tenant",
|
|
1868
|
+
SUBSCRIPTION_BUNDLE_NOT_FOUND: "SubscriptionBundle '{subscriptionBundleId}' not found",
|
|
1869
|
+
TENANT_NOT_FOUND: "Tenant {slug} not found",
|
|
1870
|
+
NO_ACTIVE_PLAN_VERSION: "No version of plan {planId} is on sale as of {asOf}.",
|
|
1871
|
+
BOUND_PLAN_VERSION_UNREADABLE: 'The plan version this subscription is bound to ({planVersionId}) cannot be read for plan "{planKey}", so no change can be quoted.',
|
|
1872
|
+
PLAN_NOT_IN_CATALOG: 'Plan "{planKey}" is not in the catalog',
|
|
1873
|
+
PLAN_NOT_SELF_SERVICE: "{planName} is only activated via a special contract. Please contact the contract manager.",
|
|
1874
|
+
PLAN_NOT_SOLD_IN_CYCLE: "{planName} has no price for this billing rhythm and cannot be booked in it.",
|
|
1875
|
+
PLAN_CHANGE_BLOCKED: "Plan change during onboarding is blocked.",
|
|
1876
|
+
SUBSCRIPTION_CHANGED: "This subscription changed while the request was being decided. Reload it.",
|
|
1877
|
+
NO_SUBSCRIPTION: "This tenant has no subscription to cancel.",
|
|
1878
|
+
CANCELLATION_TERMS_CHANGED: "The effective date changed since it was shown. Confirm the new one.",
|
|
1879
|
+
VERSION_OFFER_CHANGED: "The offer changed since it was shown. Look at the current one before switching.",
|
|
1880
|
+
PLAN_CHANGE_QUOTE_CHANGED: "This plan changed since it was shown. Look at it again before changing to it.",
|
|
1881
|
+
PLAN_CHANGE_VERSION_NOT_NAMED: "A plan change has to name the version its preview showed.",
|
|
1882
|
+
VERSION_SWITCH_AFTER_CANCELLATION: "This subscription ends before this version would take effect, so it cannot be switched to.",
|
|
1883
|
+
VERSION_ENDS_BEFORE_SWITCH: "This version stops being sold before it would take effect, so it cannot be switched to.",
|
|
1884
|
+
RETIREMENT_TERMS_NOT_CONFIRMED: "Retiring a version for running subscriptions needs a clause in your terms. Set tenantBilling.orderlyRetirement.termsConfirmed in config/saas.yaml once they carry it.",
|
|
1885
|
+
RETIREMENT_VERSION_ON_SALE: "Version {version} of {planKey} is still on sale. End its sale first, so nobody books it after the announcement.",
|
|
1886
|
+
RETIREMENT_REPLACEMENT_NOT_ON_SALE: "Version {version} of {planKey} is not on sale, so subscriptions cannot continue on it.",
|
|
1887
|
+
RETIREMENT_REPLACEMENT_NOT_SOLD_IN_RHYTHM: "{count} of these subscriptions are billed in a rhythm version {version} of {planKey} has no price for, so they cannot continue on it.",
|
|
1888
|
+
RETIREMENT_REPLACEMENT_CANNOT_CARRY_BUNDLES: "{count} of these subscriptions hold a bundle that cannot run on version {version} of {planKey}, so they cannot continue on it.",
|
|
1889
|
+
RETIREMENT_REPLACEMENT_IS_RETIRED: "A version cannot be its own replacement.",
|
|
1890
|
+
RETIREMENT_NOTHING_AFFECTED: "No running subscription is on version {version} of {planKey}, so there is nobody to tell.",
|
|
1891
|
+
RETIREMENT_WITHIN_TWELVE_MONTHS: "{count} of these subscriptions were reached by a retirement within the last twelve months. A subscription is reached at most once a year.",
|
|
1892
|
+
RETIREMENT_PREVIEW_CHANGED: "The subscriptions this retirement reaches changed since they were shown. Look at them again before announcing it.",
|
|
1893
|
+
BUNDLE_RETIREMENT_VERSION_ON_SALE: "Version {version} of {bundleKey} is still on sale. Publish the version that replaces it, and retire this one once its sale has ended, so nobody books it after the announcement.",
|
|
1894
|
+
BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE: "Version {version} of {bundleKey} is not on sale, so bookings cannot continue on it.",
|
|
1895
|
+
BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE: "The replacement is a version of {replacementBundleKey}, not of {bundleKey}. A booking continues on a version of its own add-on.",
|
|
1896
|
+
BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN: "{count} of these bookings run beside a plan that version {version} of {bundleKey} cannot run beside, so they cannot continue on it.",
|
|
1897
|
+
BUNDLE_RETIREMENT_NOTHING_AFFECTED: "No running booking is on version {version} of {bundleKey}, so there is nobody to tell.",
|
|
1898
|
+
BUNDLE_RETIREMENT_REINSTATE_REFUSED: "Version {version} of {bundleKey} is being retired, and this booking ends before it would move. Book version {replacementVersion} from {bookableFrom}, when this booking has ended.",
|
|
1899
|
+
BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN: "Version {version} of {bundleKey} is being retired, and this booking ends before it would move. Version {replacementVersion}, which replaces it, cannot run beside your plan, or beside one your subscription is set to move to.",
|
|
1900
|
+
BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "Version {version} of {bundleKey} is being retired, and this booking ends before it would move. Your subscription ends by then as well.",
|
|
1901
|
+
BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "{bundleName} moves to its new version on {date}, and your plan changes before then, so the price a switch would keep is not known yet. Switch once your plan has changed, or let it move on that date.",
|
|
1902
|
+
BUNDLE_VERSION_OFFER_CHANGED: "The offer changed since it was shown. Look at the current one before switching.",
|
|
1903
|
+
RETIREMENT_SWITCH_NOT_PENDING: "No retirement of your version is waiting for its date, so there is nothing to switch to.",
|
|
1904
|
+
RETIREMENT_SWITCH_IN_TRIAL: "The switch opens when your trial ends.",
|
|
1905
|
+
RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "{bundleName} cannot run on {planName} and runs until {until} at the earliest. Once it is cancelled, the switch can be made from that day.",
|
|
1906
|
+
RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW: "{bundleName} continues on version {version} from {from}, which cannot run on {planName}. Once it is cancelled, the switch can be made.",
|
|
1907
|
+
RETIREMENT_SWITCH_NOT_OPEN: "This subscription cannot switch now: a change is scheduled, it has ended, or its plan is held for a special contract.",
|
|
1908
|
+
RETIREMENT_SWITCH_CHANGED: "The retirement changed since it was shown. Look at it again before switching.",
|
|
1909
|
+
SUBSCRIPTION_ENDED: "This subscription has ended. Its plan can no longer be changed.",
|
|
1910
|
+
PLAN_LOCKED: "Active {planName} special contract \u2014 please contact the contract manager to change plans.",
|
|
1911
|
+
QUOTA_OVER_TARGET: "Current usage {used} exceeds the target limit {targetMax} ({quotaKey}) in the {planName} plan. Please reduce usage first.",
|
|
1912
|
+
FEATURE_LOST: "Switching means losing access to one feature. Existing data is retained and never deleted \u2014 upgrading again unlocks it.",
|
|
1913
|
+
FEATURES_LOST: "Switching means losing access to {count} features. Existing data is retained and never deleted \u2014 upgrading again unlocks it.",
|
|
1914
|
+
NO_CHANGE: "Target plan and billing cycle already match the current state.",
|
|
1915
|
+
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.",
|
|
1916
|
+
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.",
|
|
1917
|
+
BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "{bundleName} is billed yearly and runs until {until} at the earliest, which a monthly plan cannot carry. Once it is cancelled, a change that takes effect on or after that day goes through \u2014 or keep the yearly cycle.",
|
|
1918
|
+
BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} cannot run on {planName} and runs until {until} at the earliest. Once it is cancelled, a change that takes effect on or after that day goes through \u2014 or choose another plan.",
|
|
1919
|
+
BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} continues on version {version} from {from}, which cannot run on {planName}. Cancel it, and the change goes through \u2014 or choose another plan.",
|
|
1920
|
+
REDUNDANT_FEATURES: "The plan or another booked bundle already includes {count} of the features in this bundle \u2014 booking it pays for them twice.",
|
|
1921
|
+
MINIMUM_TERM_BINDS: "The minimum term extends beyond the end of the period \u2014 the cancellation only takes effect when the minimum term ends.",
|
|
1922
|
+
BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "The bundle requires [{features}] \u2014 present neither in the plan nor in the active bundles.",
|
|
1923
|
+
ONBOARDING_CREATE_FAILED: "The account could not be created. Please try again.",
|
|
1924
|
+
BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "Exactly one of bundleVersionId (add preview) or subscriptionBundleId (cancel preview) must be given.",
|
|
1925
|
+
SUBSCRIPTION_PK_MISSING: "The adapter returned a subscription usage record without an id. Pass the subscription primary key through (see SubscriptionUsageRecord.id).",
|
|
1926
|
+
LIMIT_EXCEEDED: "The limit for {dimension} has been reached: {used} of {max}.",
|
|
1927
|
+
QUOTA_DIMENSION_UNKNOWN: 'Unknown quota dimension "{dimension}".',
|
|
1928
|
+
// ── promo ──
|
|
1929
|
+
PROMO_CODE_NOT_FOUND: "Code not found",
|
|
1930
|
+
PROMO_CODE_ALREADY_EXISTS: "The code exists, or a deleted code carries it; a deleted code keeps its name.",
|
|
1931
|
+
PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions, or checkouts holding one \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
1932
|
+
PROMO_CODE_NOT_REDEEMABLE: "Code cannot be redeemed: {reason}",
|
|
1933
|
+
PROMO_CODE_FORMAT_INVALID: 'The code may only contain upper-case letters, digits, "-" and "_" (4\u201332 characters).',
|
|
1934
|
+
PROMO_PERCENT_OUT_OF_RANGE: "The percentage must be between 0 and 100.",
|
|
1935
|
+
PROMO_AMOUNT_NOT_POSITIVE: "The amount must be positive.",
|
|
1936
|
+
PROMO_ONE_OFF_WITH_DURATION: "A one-off discount must not set a duration.",
|
|
1937
|
+
PROMO_DURATION_INVALID: "Invalid duration (at most 24 months or billing periods).",
|
|
1938
|
+
PROMO_VALIDITY_WINDOW_INVALID: "Invalid validity window.",
|
|
1939
|
+
PROMO_PLAN_NOT_DISCOUNTABLE: "{plan}-Plan cannot be discounted.",
|
|
1940
|
+
PROMO_MIN_AMOUNT_NOT_POSITIVE: "The minimum plan gross amount must be positive.",
|
|
1941
|
+
PROMO_WOULD_PRODUCE_ZERO_INVOICE: "For absolute amounts the discount must stay below the lowest applicable plan price, or allowZeroInvoice must be enabled.",
|
|
1942
|
+
PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions cannot be lowered.",
|
|
1943
|
+
// ── contract ──
|
|
1944
|
+
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "The contract states {stated} % in {field}, and the tax adapter decides {decided} %.",
|
|
1945
|
+
CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "A checkout offer can yield only one contract, and only once its line items are frozen.",
|
|
1946
|
+
CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "A checkout offer requires a frozen plan line item.",
|
|
1947
|
+
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Every selected bundle version requires a frozen bundle line item.",
|
|
1948
|
+
CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE: "At least one bundle version from the checkout offer is no longer bookable.",
|
|
1949
|
+
CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED: "The selected plan does not cover all feature dependencies: [{missingRequires}] are missing from the plan + selected bundles.",
|
|
1950
|
+
CHECKOUT_OFFER_PLAN_NOT_OFFERED: "Plan '{planKey}' is not offered with a {billingCycle} price at the moment.",
|
|
1951
|
+
CHECKOUT_OFFER_BUNDLE_NOT_OFFERED: "Bundle version '{bundleVersionId}' cannot be added to this offer ({reason}).",
|
|
1952
|
+
CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED: "The promo code cannot be applied to this offer ({reason}).",
|
|
1953
|
+
CHECKOUT_OFFER_PRICE_NOT_CURRENT: "The prices of checkout offer '{offerId}' no longer match the catalogue; create a new offer.",
|
|
1954
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED: "A subscription contract requires at least one line item.",
|
|
1955
|
+
SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "A subscription contract requires exactly one plan base item.",
|
|
1956
|
+
SUBSCRIPTION_CONTRACT_INVALID_DATE: "{field} must be a valid date.",
|
|
1957
|
+
SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "effectiveUntil must be after effectiveFrom.",
|
|
1958
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "A line item's taxAmount must be exactly priceGross minus priceNet.",
|
|
1959
|
+
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.",
|
|
1960
|
+
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil must be after the effectiveFrom of the contract.",
|
|
1961
|
+
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "A line item must be booked in the currency its contract was priced in.",
|
|
1962
|
+
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}.",
|
|
1963
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "A subscription contract states a discount of {amount} at {field}; a discount takes money off and is never negative.",
|
|
1964
|
+
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' not found",
|
|
1965
|
+
CHECKOUT_OFFER_EXPIRED: "Checkout offer '{offerId}' has expired and cannot be {action}",
|
|
1966
|
+
CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
|
|
1967
|
+
CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' must be consumed before the contract is created",
|
|
1968
|
+
CHECKOUT_OFFER_CHANGED: "Checkout offer '{offerId}' changed while it was being concluded. Load it again.",
|
|
1969
|
+
CHECKOUT_OFFER_CONTRACT_IN_FORCE: "Checkout offer '{offerId}' concludes a first contract, and this tenant already has one. A running subscription changes through its plan and its add-ons.",
|
|
1970
|
+
CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION: "Checkout offer '{offerId}' names version {offeredVersion} of add-on '{bundleKey}', which this tenant has booked in version {bookedVersion} (booking {subscriptionBundleId}). Its version changes through the offer beside the add-on.",
|
|
1971
|
+
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
|
|
1972
|
+
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
|
|
1973
|
+
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' is already closed",
|
|
1974
|
+
SUBSCRIPTION_CONTRACT_CHANGED: "The contracts of tenant '{tenantId}' changed while a successor was being written, or one begins after the moment it would take effect, so it would run beside another. Nothing was written.",
|
|
1975
|
+
// ── subscriber ──
|
|
1976
|
+
SUBSCRIBER_REQUIRED: "Tenant '{tenantId}' has no subscriber. Nothing is agreed or charged without the party to it: create the tenant's subscriber first.",
|
|
1977
|
+
SUBSCRIBER_ALREADY_EXISTS: "Tenant '{tenantId}' already has a subscriber.",
|
|
1978
|
+
SUBSCRIBER_NOT_FOUND: "Subscriber '{subscriberId}' not found",
|
|
1979
|
+
SUBSCRIBER_LEGAL_NAME_REQUIRED: "A subscriber needs its legal name.",
|
|
1980
|
+
SUBSCRIBER_DETAIL_INVALID: "The subscriber's {field} is not valid.",
|
|
1981
|
+
SUBSCRIBER_IDENTITY_INCOMPLETE: "The subscriber's billing address is not complete yet: a contract names it only with street and number, postal code, city and country.",
|
|
1982
|
+
SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} is part of the subscriber's legal identity and changes only as a correction, with a reason.",
|
|
1983
|
+
SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED: "A change of whether the subscriber is a business needs a reason.",
|
|
1984
|
+
SUBSCRIBER_VAT_ID_MISSING: "The subscriber has no VAT identification number to check.",
|
|
1985
|
+
SUBSCRIBER_CORRECTION_REASON_REQUIRED: "A correction of the legal identity needs a reason.",
|
|
1986
|
+
SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "A correction of the legal identity has to say who makes it.",
|
|
1987
|
+
SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "The correction changes nothing: every value it names is already recorded.",
|
|
1988
|
+
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.",
|
|
1989
|
+
SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT: "Whether the subscriber is a business is part of its tax origin and changes only as a recorded change that names who made it.",
|
|
1990
|
+
SUBSCRIBER_CHANGE_ACTOR_REQUIRED: "A change of the subscriber's details has to say who makes it.",
|
|
1991
|
+
// ── registration ──
|
|
1992
|
+
PENDING_REGISTRATION_NOT_FOUND: "This registration could not be found. It may have already been completed or discarded.",
|
|
1993
|
+
PENDING_REGISTRATION_EXPIRED: "This registration has expired. Please start again.",
|
|
1994
|
+
INVALID_REGISTRATION_STATE: "This step is not available at the current stage of the registration.",
|
|
1995
|
+
OTP_INVALID: "That code is not correct. Please check it and try again.",
|
|
1996
|
+
OTP_EXPIRED: "That code has expired. Please request a new one.",
|
|
1997
|
+
OTP_LOCKED: "Too many incorrect attempts. Please request a new code.",
|
|
1998
|
+
RATE_LIMITED: "Too many attempts. Please try again in {retryAfterSeconds} seconds.",
|
|
1999
|
+
RESUME_TOKEN_INVALID: "This resume link is no longer valid. Please request a new one.",
|
|
2000
|
+
RESUME_NOT_CONFIGURED: "Resuming a registration is not available.",
|
|
2001
|
+
CONFIGURATOR_NOT_CONFIGURED: "The configurator is not available.",
|
|
2002
|
+
CONFIG_NOT_SAVED: "The configuration could not be saved.",
|
|
2003
|
+
PLAN_NOT_AVAILABLE: "This plan is not available.",
|
|
2004
|
+
PLAN_NOT_SELECTED: "Please select a plan first.",
|
|
2005
|
+
MODEL_NOT_AVAILABLE: "This option is not available.",
|
|
2006
|
+
// ── payments ──
|
|
2007
|
+
PAYMENTS_NOT_CONFIGURED: "Payment methods cannot be set up here yet.",
|
|
2008
|
+
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "No payment gateway account is configured under '{account}'.",
|
|
2009
|
+
PAYMENT_CALLBACK_REJECTED: "The payment callback could not be verified.",
|
|
2010
|
+
PAYMENT_RETURN_URL_NOT_ALLOWED: "The {field} leads to a site this installation does not return to.",
|
|
2011
|
+
PAYMENT_GATEWAY_FAILED: "The payment provider did not answer as expected. Please try again in a few minutes.",
|
|
2012
|
+
// ── entitlement (code lives in upsell.types.ts) ──
|
|
2013
|
+
[FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan.",
|
|
2014
|
+
// ── settings ──
|
|
2015
|
+
SETTINGS_CHANGE_NOT_FOUND: "No recorded settings change has this id.",
|
|
2016
|
+
// ── maintenance ──
|
|
2017
|
+
MAINTENANCE: "The application is under maintenance.",
|
|
2018
|
+
MAINTENANCE_WINDOW_ALREADY_OPEN: "A maintenance window is already open. Move or cancel it before announcing another.",
|
|
2019
|
+
MAINTENANCE_WINDOW_NOT_OPEN: "No open maintenance window has this id.",
|
|
2020
|
+
MAINTENANCE_WINDOW_LOCKED: "This maintenance window is locked. Its start cannot move, and unlocking is what ends it.",
|
|
2021
|
+
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "The end of a maintenance window has to be after its start.",
|
|
2022
|
+
MAINTENANCE_WINDOW_END_IN_PAST: "The end of a maintenance window cannot be in the past.",
|
|
2023
|
+
MAINTENANCE_TIME_INVALID: "The {field} has to be a date and time with its zone, such as 2026-10-02T22:00+02:00.",
|
|
2024
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "The message may be at most {max} characters long.",
|
|
2025
|
+
// ── tax ──
|
|
2026
|
+
TAX_TREATMENT_NOT_SUPPORTED: "The tax adapter {adapter} does not support this case: {reason}",
|
|
2027
|
+
TAX_VAT_ID_CHECK_NOT_COMPLETED: "The VAT identification number could not be checked just now ({reason}). Please try again later.",
|
|
2028
|
+
TAX_VAT_ID_CHECK_NOT_AVAILABLE: "This installation names no tax adapter, so no VAT identification number is checked."
|
|
2029
|
+
};
|
|
2030
|
+
function formatErrorMessage(template, params = {}) {
|
|
2031
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
2032
|
+
const value = params[name];
|
|
2033
|
+
return value === void 0 || value === null ? match : String(value);
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
function resolveErrorMessage(body, overrides = {}, defaults = ERROR_MESSAGES_EN) {
|
|
2037
|
+
const code = typeof body.code === "string" ? body.code : void 0;
|
|
2038
|
+
const template = (code && (overrides[code] ?? defaults[code])) ?? void 0;
|
|
2039
|
+
if (!template) {
|
|
2040
|
+
return typeof body.message === "string" ? body.message : code ?? "";
|
|
2041
|
+
}
|
|
2042
|
+
const { params, ...rest } = body;
|
|
2043
|
+
return formatErrorMessage(template, { ...rest, ...params });
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// src/refusal.ts
|
|
2047
|
+
function refusal(code, reason, params) {
|
|
2048
|
+
return new PersistenceRefusal(
|
|
2049
|
+
code,
|
|
2050
|
+
reason,
|
|
2051
|
+
formatErrorMessage(ERROR_MESSAGES_EN[code], params),
|
|
2052
|
+
params
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// src/catalog-version-refusals.ts
|
|
2057
|
+
var CASES = {
|
|
2058
|
+
PlanVersion: {
|
|
2059
|
+
gone: { code: CATALOG_ERROR_CODES.PLAN_VERSION_NOT_FOUND, param: "versionId" },
|
|
2060
|
+
published: { code: CATALOG_ERROR_CODES.PLAN_VERSION_ALREADY_PUBLISHED, param: "versionId" },
|
|
2061
|
+
draft: { code: CATALOG_ERROR_CODES.PLAN_DRAFT_ALREADY_EXISTS, param: "planKey" }
|
|
2062
|
+
},
|
|
2063
|
+
BundleVersion: {
|
|
2064
|
+
gone: { code: CATALOG_ERROR_CODES.BUNDLE_VERSION_NOT_FOUND, param: "bundleVersionId" },
|
|
2065
|
+
published: {
|
|
2066
|
+
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_ALREADY_PUBLISHED,
|
|
2067
|
+
param: "versionId"
|
|
2068
|
+
},
|
|
2069
|
+
draft: { code: CATALOG_ERROR_CODES.BUNDLE_DRAFT_ALREADY_EXISTS, param: "bundleKey" }
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
function catalogVersionGone(kind, versionId) {
|
|
2073
|
+
const { code, param } = CASES[kind].gone;
|
|
2074
|
+
return new PersistenceRefusal(code, "gone", `${kind} '${versionId}' not found.`, {
|
|
2075
|
+
[param]: versionId
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
function catalogVersionAlreadyPublished(kind, versionId) {
|
|
2079
|
+
const { code, param } = CASES[kind].published;
|
|
2080
|
+
return new PersistenceRefusal(
|
|
2081
|
+
code,
|
|
2082
|
+
"moved",
|
|
2083
|
+
`${kind} '${versionId}' is already published; it is not published again or discarded.`,
|
|
2084
|
+
{ [param]: versionId }
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
function catalogDraftExists(kind, key, draftVersion) {
|
|
2088
|
+
const { code, param } = CASES[kind].draft;
|
|
2089
|
+
return refusal(code, "moved", { [param]: key, draftVersion });
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// src/catalog-key-refusals.ts
|
|
2093
|
+
function planKeyTaken(planKey) {
|
|
2094
|
+
return refusal(CATALOG_ERROR_CODES.PLAN_ALREADY_EXISTS, "moved", { planKey });
|
|
2095
|
+
}
|
|
2096
|
+
function bundleKeyTaken(bundleKey) {
|
|
2097
|
+
return refusal(CATALOG_ERROR_CODES.BUNDLE_ALREADY_EXISTS, "moved", { bundleKey });
|
|
2098
|
+
}
|
|
2099
|
+
function marketingProjectionTaken(target) {
|
|
2100
|
+
return refusal(CATALOG_ERROR_CODES.MARKETING_PROJECTION_ALREADY_EXISTS, "moved", {
|
|
2101
|
+
targetType: target.targetType,
|
|
2102
|
+
targetVersionId: target.targetVersionId,
|
|
2103
|
+
locale: target.locale
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
// src/decimal-string.ts
|
|
2108
|
+
function toDecimalString(value) {
|
|
2109
|
+
if (!Number.isFinite(value)) {
|
|
2110
|
+
throw new RangeError(`${String(value)} is not an amount a numeric column can hold.`);
|
|
2111
|
+
}
|
|
2112
|
+
return String(value);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// src/promo-code-refusals.ts
|
|
2116
|
+
function promoCodeTaken(code) {
|
|
2117
|
+
return new PersistenceRefusal(
|
|
2118
|
+
PROMO_ERROR_CODES.PROMO_CODE_ALREADY_EXISTS,
|
|
2119
|
+
"moved",
|
|
2120
|
+
`Promo code '${code}' is taken.`,
|
|
2121
|
+
{ promoCode: code }
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// src/subscription-refusals.ts
|
|
2126
|
+
function subscriptionGone(tenantId) {
|
|
2127
|
+
return refusal(BILLING_ERROR_CODES.SUBSCRIPTION_NOT_FOUND, "gone", { tenantId });
|
|
2128
|
+
}
|
|
2129
|
+
function noActivePlanVersion(planId, asOf) {
|
|
2130
|
+
return refusal(BILLING_ERROR_CODES.NO_ACTIVE_PLAN_VERSION, "gone", {
|
|
2131
|
+
planId,
|
|
2132
|
+
asOf: asOf.toISOString().slice(0, 10)
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
function planNotInCatalog(planKey) {
|
|
2136
|
+
return refusal(BILLING_ERROR_CODES.PLAN_NOT_IN_CATALOG, "gone", { planKey });
|
|
2137
|
+
}
|
|
2138
|
+
function subscriptionBundleGone(subscriptionBundleId) {
|
|
2139
|
+
return refusal(BILLING_ERROR_CODES.SUBSCRIPTION_BUNDLE_NOT_FOUND, "gone", {
|
|
2140
|
+
subscriptionBundleId
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
function subscriptionBundleAlreadyCancelled(subscriptionBundleId) {
|
|
2144
|
+
return refusal(BILLING_ERROR_CODES.SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED, "moved", {
|
|
2145
|
+
subscriptionBundleId
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
function subscriptionContractGone(contractId) {
|
|
2149
|
+
return refusal(CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_NOT_FOUND, "gone", { contractId });
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// src/feature-requires.ts
|
|
2153
|
+
function buildFeatureRequiresIndex(features) {
|
|
2154
|
+
const index = /* @__PURE__ */ new Map();
|
|
2155
|
+
for (const feature of features) {
|
|
2156
|
+
const requires = (feature.requires ?? []).filter((key) => key !== feature.featureKey);
|
|
2157
|
+
if (requires.length > 0) index.set(feature.featureKey, requires);
|
|
2158
|
+
}
|
|
2159
|
+
return index;
|
|
2160
|
+
}
|
|
2161
|
+
function collectUnsatisfiedRequires(selected, index) {
|
|
2162
|
+
const own = new Set(selected);
|
|
2163
|
+
const unsatisfied = /* @__PURE__ */ new Set();
|
|
2164
|
+
for (const featureKey of selected) {
|
|
2165
|
+
for (const required of index.get(featureKey) ?? []) {
|
|
2166
|
+
if (!own.has(required)) unsatisfied.add(required);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return [...unsatisfied].sort((a, b) => a.localeCompare(b));
|
|
2170
|
+
}
|
|
2171
|
+
function missingRequiresFor(bundle, coveredFeatures) {
|
|
2172
|
+
const missing = (bundle.requiresFeatures ?? []).filter((key) => !coveredFeatures.has(key));
|
|
2173
|
+
return [...new Set(missing)].sort((a, b) => a.localeCompare(b));
|
|
2174
|
+
}
|
|
2175
|
+
function resolveBundleAvailability(bundle, coveredFeatures) {
|
|
2176
|
+
if (bundle.features.length > 0 && bundle.features.every((f) => coveredFeatures.has(f))) {
|
|
2177
|
+
return "covered";
|
|
2178
|
+
}
|
|
2179
|
+
if (missingRequiresFor(bundle, coveredFeatures).length > 0) return "missing-requires";
|
|
2180
|
+
return "bookable";
|
|
2181
|
+
}
|
|
2182
|
+
function coverageExcludingSelf(selfVersionId, planFeatures, selectedBundles) {
|
|
2183
|
+
const covered = new Set(planFeatures);
|
|
2184
|
+
for (const bundle of selectedBundles) {
|
|
2185
|
+
if (bundle.bundleVersionId === selfVersionId) continue;
|
|
2186
|
+
for (const feature of bundle.features) covered.add(feature);
|
|
2187
|
+
}
|
|
2188
|
+
return covered;
|
|
2189
|
+
}
|
|
2190
|
+
function isBundleRedundant(bundle, planFeatures, selectedBundles) {
|
|
2191
|
+
const covered = coverageExcludingSelf(bundle.bundleVersionId, planFeatures, selectedBundles);
|
|
2192
|
+
return resolveBundleAvailability(bundle, covered) === "covered";
|
|
2193
|
+
}
|
|
2194
|
+
function compareSelectable(a, b) {
|
|
2195
|
+
const orderA = a.sortOrder ?? Number.MAX_SAFE_INTEGER;
|
|
2196
|
+
const orderB = b.sortOrder ?? Number.MAX_SAFE_INTEGER;
|
|
2197
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
2198
|
+
return a.bundleVersionId.localeCompare(b.bundleVersionId);
|
|
2199
|
+
}
|
|
2200
|
+
function selectChargeableBundles(planFeatures, selectedBundles) {
|
|
2201
|
+
const kept = [...selectedBundles].sort(compareSelectable);
|
|
2202
|
+
for (; ; ) {
|
|
2203
|
+
const redundantIndex = kept.findIndex(
|
|
2204
|
+
(bundle) => isBundleRedundant(bundle, planFeatures, kept)
|
|
2205
|
+
);
|
|
2206
|
+
if (redundantIndex === -1) return kept;
|
|
2207
|
+
kept.splice(redundantIndex, 1);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
// src/payment-gateway.types.ts
|
|
2212
|
+
var PAYMENT_CALLBACK_REJECTED = "PAYMENT_CALLBACK_REJECTED";
|
|
2213
|
+
var PaymentCallbackRejectedError = class extends Error {
|
|
2214
|
+
code = PAYMENT_CALLBACK_REJECTED;
|
|
2215
|
+
constructor(reason) {
|
|
2216
|
+
super(`Payment callback rejected: ${reason}`);
|
|
2217
|
+
this.name = "PaymentCallbackRejectedError";
|
|
2218
|
+
}
|
|
2219
|
+
};
|
|
2220
|
+
function isPaymentCallbackRejectedError(err) {
|
|
2221
|
+
return err instanceof Error && err.code === PAYMENT_CALLBACK_REJECTED;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
// src/registration.types.ts
|
|
2225
|
+
var PENDING_EMAIL_TTL_HOURS = 72;
|
|
2226
|
+
var PENDING_ONBOARDING_TTL_DAYS = 14;
|
|
2227
|
+
var PENDING_CHECKOUT_TTL_DAYS = 30;
|
|
2228
|
+
var OTP_TTL_MINUTES = 10;
|
|
2229
|
+
var PASSWORD_RESET_TTL_MINUTES = 30;
|
|
2230
|
+
var OTP_RATE_LIMIT_MAX_SENDS = 3;
|
|
2231
|
+
var OTP_RATE_LIMIT_WINDOW_MINUTES = 15;
|
|
2232
|
+
var OTP_VERIFY_MAX_ATTEMPTS = 5;
|
|
2233
|
+
var REGISTRATION_STEP_BY_STATUS = {
|
|
2234
|
+
PENDING_EMAIL_VERIFICATION: 2,
|
|
2235
|
+
EMAIL_VERIFIED: 3,
|
|
2236
|
+
PLAN_SELECTED: 4,
|
|
1093
2237
|
CHECKOUT_STARTED: 4,
|
|
1094
2238
|
EXPIRED: 1,
|
|
1095
2239
|
DELETED: 1
|
|
@@ -1110,8 +2254,8 @@ function toPlanRow(row) {
|
|
|
1110
2254
|
deletedAt: toIsoOrNull(row.deletedAt)
|
|
1111
2255
|
};
|
|
1112
2256
|
}
|
|
1113
|
-
function toPlanVersionRow(row, planKey
|
|
1114
|
-
|
|
2257
|
+
function toPlanVersionRow(row, planKey) {
|
|
2258
|
+
return {
|
|
1115
2259
|
id: row.id,
|
|
1116
2260
|
version: row.version,
|
|
1117
2261
|
baseVersionId: row.baseVersionId,
|
|
@@ -1126,17 +2270,14 @@ function toPlanVersionRow(row, planKey, fields) {
|
|
|
1126
2270
|
publishedChanges: Array.isArray(row.publishedChanges) ? row.publishedChanges : null,
|
|
1127
2271
|
changeNote: row.changeNote,
|
|
1128
2272
|
nonRegressive: row.nonRegressive,
|
|
1129
|
-
validFrom:
|
|
1130
|
-
validUntil:
|
|
2273
|
+
validFrom: toIsoOrNull(row.validFrom),
|
|
2274
|
+
validUntil: toIsoOrNull(row.validUntil),
|
|
2275
|
+
endsAt: toIsoOrNull(row.endsAt),
|
|
1131
2276
|
createdByUserId: row.createdByUserId,
|
|
1132
2277
|
publishedByUserId: row.publishedByUserId,
|
|
1133
2278
|
createdAt: row.createdAt.toISOString(),
|
|
1134
2279
|
updatedAt: row.updatedAt.toISOString()
|
|
1135
2280
|
};
|
|
1136
|
-
if (fields.endsAt) {
|
|
1137
|
-
mapped.endsAt = toIsoOrNull(row.endsAt);
|
|
1138
|
-
}
|
|
1139
|
-
return mapped;
|
|
1140
2281
|
}
|
|
1141
2282
|
function toIsoOrNull(value) {
|
|
1142
2283
|
return value ? value.toISOString() : null;
|
|
@@ -1235,23 +2376,13 @@ function textOrNull(value) {
|
|
|
1235
2376
|
return trimmed === "" ? null : trimmed;
|
|
1236
2377
|
}
|
|
1237
2378
|
|
|
1238
|
-
// src/closed-value.ts
|
|
1239
|
-
function oneOf(allowed, value, source) {
|
|
1240
|
-
const match = allowed.find((entry) => entry === value);
|
|
1241
|
-
if (match === void 0) {
|
|
1242
|
-
throw new Error(
|
|
1243
|
-
`${source.table} row '${source.id}' holds ${source.column} '${value}', which is none of ${allowed.join(", ")}.`
|
|
1244
|
-
);
|
|
1245
|
-
}
|
|
1246
|
-
return match;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
2379
|
// src/subscriber-ledger-mapping.ts
|
|
1250
2380
|
var ORIGINS = {
|
|
1251
2381
|
activation: true,
|
|
1252
2382
|
renewal: true,
|
|
1253
2383
|
planChange: true,
|
|
1254
2384
|
bundleBooking: true,
|
|
2385
|
+
bundleChange: true,
|
|
1255
2386
|
credit: true,
|
|
1256
2387
|
correction: true
|
|
1257
2388
|
};
|
|
@@ -1260,10 +2391,10 @@ var SOURCES = {
|
|
|
1260
2391
|
bundle: true,
|
|
1261
2392
|
discount: true
|
|
1262
2393
|
};
|
|
1263
|
-
var
|
|
2394
|
+
var TABLE2 = "subscriber_ledger_entries";
|
|
1264
2395
|
var CENT_TOLERANCE = 1e-6;
|
|
1265
2396
|
function toSubscriberChargeRecord(row) {
|
|
1266
|
-
const where = (column) => ({ table:
|
|
2397
|
+
const where = (column) => ({ table: TABLE2, column, id: row.id });
|
|
1267
2398
|
return {
|
|
1268
2399
|
id: row.id,
|
|
1269
2400
|
subscriberId: row.subscriberId,
|
|
@@ -1283,34 +2414,165 @@ function toSubscriberChargeRecord(row) {
|
|
|
1283
2414
|
};
|
|
1284
2415
|
}
|
|
1285
2416
|
function subscriberChargeColumns(charge) {
|
|
1286
|
-
const
|
|
1287
|
-
if (!Number.isFinite(
|
|
2417
|
+
const cents2 = charge.amountNet * 100;
|
|
2418
|
+
if (!Number.isFinite(cents2) || Math.abs(cents2 - Math.round(cents2)) > CENT_TOLERANCE) {
|
|
1288
2419
|
throw new Error(
|
|
1289
2420
|
`A charge of ${charge.amountNet} is not a whole number of cents; round it once where it is derived, before it is written.`
|
|
1290
2421
|
);
|
|
1291
2422
|
}
|
|
1292
2423
|
return {
|
|
1293
|
-
subscriberId: charge.subscriberId,
|
|
1294
|
-
tenantId: charge.tenantId,
|
|
1295
|
-
subscriptionId: charge.subscriptionId,
|
|
1296
|
-
contractId: charge.contractId,
|
|
1297
|
-
contractLineItemId: charge.contractLineItemId,
|
|
1298
|
-
origin: charge.origin,
|
|
1299
|
-
source: charge.source,
|
|
1300
|
-
sourceRef: charge.sourceRef,
|
|
1301
|
-
periodStart: charge.periodStart,
|
|
1302
|
-
periodEnd: charge.periodEnd,
|
|
1303
|
-
currency: charge.currency,
|
|
1304
|
-
amountNet: charge.amountNet.toFixed(2),
|
|
1305
|
-
bookedAt: charge.bookedAt
|
|
2424
|
+
subscriberId: charge.subscriberId,
|
|
2425
|
+
tenantId: charge.tenantId,
|
|
2426
|
+
subscriptionId: charge.subscriptionId,
|
|
2427
|
+
contractId: charge.contractId,
|
|
2428
|
+
contractLineItemId: charge.contractLineItemId,
|
|
2429
|
+
origin: charge.origin,
|
|
2430
|
+
source: charge.source,
|
|
2431
|
+
sourceRef: charge.sourceRef,
|
|
2432
|
+
periodStart: charge.periodStart,
|
|
2433
|
+
periodEnd: charge.periodEnd,
|
|
2434
|
+
currency: charge.currency,
|
|
2435
|
+
amountNet: charge.amountNet.toFixed(2),
|
|
2436
|
+
bookedAt: charge.bookedAt
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
function keysOf(record) {
|
|
2440
|
+
return Object.keys(record);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// src/subscriber-ledger.types.ts
|
|
2444
|
+
var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
|
|
2445
|
+
|
|
2446
|
+
// src/subscriber.types.ts
|
|
2447
|
+
var SUBSCRIBER_INVOICE_ADDRESS_FIELDS = [
|
|
2448
|
+
"addressLine1",
|
|
2449
|
+
"postalCode",
|
|
2450
|
+
"city",
|
|
2451
|
+
"country"
|
|
2452
|
+
];
|
|
2453
|
+
var SUBSCRIBER_STANDING_CAPABILITY = "subscribers.read";
|
|
2454
|
+
var SUBSCRIBER_ATTENTION_CAPABILITY = "subscribers.attention";
|
|
2455
|
+
var SUBSCRIBER_ATTENTION_PAGE_SIZE = 200;
|
|
2456
|
+
var SUBSCRIBER_CORRECTION_CAPABILITY = "subscribers.correct";
|
|
2457
|
+
var SUBSCRIBER_TAX_ORIGIN_FIELDS = ["country", "business", "vatId"];
|
|
2458
|
+
|
|
2459
|
+
// src/tax.types.ts
|
|
2460
|
+
var TAX_TREATMENT_KINDS = [
|
|
2461
|
+
"standard",
|
|
2462
|
+
"reduced",
|
|
2463
|
+
"exempt",
|
|
2464
|
+
"reverse-charge",
|
|
2465
|
+
"not-taxable",
|
|
2466
|
+
"small-business"
|
|
2467
|
+
];
|
|
2468
|
+
|
|
2469
|
+
// src/tax-origin.ts
|
|
2470
|
+
var VAT_ID_SEPARATORS = /[\s.-]/g;
|
|
2471
|
+
function canonicalVatId(written) {
|
|
2472
|
+
const vatId = written.replace(VAT_ID_SEPARATORS, "").toUpperCase();
|
|
2473
|
+
return vatId === "" ? null : vatId;
|
|
2474
|
+
}
|
|
2475
|
+
function taxOriginOf(subscriber, currentCheck) {
|
|
2476
|
+
const validated = subscriber.vatId !== null && currentCheck?.valid === true && currentCheck.vatId === subscriber.vatId;
|
|
2477
|
+
return {
|
|
2478
|
+
country: subscriber.country,
|
|
2479
|
+
business: subscriber.business,
|
|
2480
|
+
vatId: subscriber.vatId,
|
|
2481
|
+
validatedVatId: validated ? subscriber.vatId : null
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
function keepsVatIdCheck(held, counting, check) {
|
|
2485
|
+
if (held.vatId === null || check.vatId !== held.vatId) return false;
|
|
2486
|
+
if (held.vatIdSince !== null && check.checkedAt.getTime() < held.vatIdSince.getTime()) {
|
|
2487
|
+
return false;
|
|
2488
|
+
}
|
|
2489
|
+
if (counting === null || counting.vatId !== held.vatId) return true;
|
|
2490
|
+
return counting.checkedAt.getTime() <= check.checkedAt.getTime();
|
|
2491
|
+
}
|
|
2492
|
+
function toSubscriberVatIdCheckRecord(row) {
|
|
2493
|
+
return {
|
|
2494
|
+
id: row.id,
|
|
2495
|
+
subscriberId: row.subscriberId,
|
|
2496
|
+
vatId: row.vatId,
|
|
2497
|
+
checkedAt: row.checkedAt,
|
|
2498
|
+
valid: row.valid,
|
|
2499
|
+
service: row.service,
|
|
2500
|
+
confirmation: textEntries(row.confirmation),
|
|
2501
|
+
recordedAt: row.recordedAt
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
function taxTreatmentToJson(treatment) {
|
|
2505
|
+
return {
|
|
2506
|
+
kind: treatment.kind,
|
|
2507
|
+
rate: treatment.rate,
|
|
2508
|
+
note: treatment.note,
|
|
2509
|
+
adapter: { name: treatment.adapter.name, version: treatment.adapter.version }
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
function toTaxTreatment(value) {
|
|
2513
|
+
if (!isObject(value)) return null;
|
|
2514
|
+
const { kind, rate, note, adapter } = value;
|
|
2515
|
+
if (!isTreatmentKind(kind)) return null;
|
|
2516
|
+
if (typeof rate !== "number" || !Number.isFinite(rate)) return null;
|
|
2517
|
+
if (note !== null && typeof note !== "string") return null;
|
|
2518
|
+
if (!isObject(adapter)) return null;
|
|
2519
|
+
if (typeof adapter.name !== "string" || typeof adapter.version !== "string") return null;
|
|
2520
|
+
return { kind, rate, note, adapter: { name: adapter.name, version: adapter.version } };
|
|
2521
|
+
}
|
|
2522
|
+
function taxOriginWrite(current, next) {
|
|
2523
|
+
const previous = {};
|
|
2524
|
+
const changed = {};
|
|
2525
|
+
for (const field of SUBSCRIBER_TAX_ORIGIN_FIELDS) {
|
|
2526
|
+
const value = next[field];
|
|
2527
|
+
if (value === void 0 || value === current[field]) continue;
|
|
2528
|
+
previous[field] = current[field];
|
|
2529
|
+
changed[field] = value;
|
|
2530
|
+
}
|
|
2531
|
+
return {
|
|
2532
|
+
previous,
|
|
2533
|
+
changed,
|
|
2534
|
+
moved: Object.keys(changed).length > 0,
|
|
2535
|
+
endsCountingVatIdCheck: Object.prototype.hasOwnProperty.call(changed, "vatId")
|
|
1306
2536
|
};
|
|
1307
2537
|
}
|
|
1308
|
-
function
|
|
1309
|
-
|
|
2538
|
+
function toTaxOriginValues(value) {
|
|
2539
|
+
if (!isObject(value)) return {};
|
|
2540
|
+
const values = {};
|
|
2541
|
+
for (const field of SUBSCRIBER_TAX_ORIGIN_FIELDS) {
|
|
2542
|
+
if (!Object.prototype.hasOwnProperty.call(value, field)) continue;
|
|
2543
|
+
const entry = value[field];
|
|
2544
|
+
if (field === "business") {
|
|
2545
|
+
values[field] = typeof entry === "boolean" ? entry : null;
|
|
2546
|
+
} else {
|
|
2547
|
+
values[field] = typeof entry === "string" ? entry : null;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return values;
|
|
2551
|
+
}
|
|
2552
|
+
function isTreatmentKind(value) {
|
|
2553
|
+
return TAX_TREATMENT_KINDS.includes(value);
|
|
2554
|
+
}
|
|
2555
|
+
function isObject(value) {
|
|
2556
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2557
|
+
}
|
|
2558
|
+
function vatIdCheckFromStore(value) {
|
|
2559
|
+
if (!isObject(value)) return null;
|
|
2560
|
+
const { vatId, checkedAt, valid, service, confirmation } = value;
|
|
2561
|
+
const at = checkedAt instanceof Date ? checkedAt : new Date(String(checkedAt));
|
|
2562
|
+
if (typeof vatId !== "string" || typeof valid !== "boolean" || typeof service !== "string") {
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
if (checkedAt === void 0 || checkedAt === null || Number.isNaN(at.getTime())) return null;
|
|
2566
|
+
return { vatId, checkedAt: at, valid, service, confirmation: textEntries(confirmation) };
|
|
2567
|
+
}
|
|
2568
|
+
function textEntries(value) {
|
|
2569
|
+
if (!isObject(value)) return {};
|
|
2570
|
+
const entries = {};
|
|
2571
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2572
|
+
if (typeof entry === "string") entries[key] = entry;
|
|
2573
|
+
}
|
|
2574
|
+
return entries;
|
|
1310
2575
|
}
|
|
1311
|
-
|
|
1312
|
-
// src/subscriber-ledger.types.ts
|
|
1313
|
-
var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
|
|
1314
2576
|
|
|
1315
2577
|
// src/subscriber-mapping.ts
|
|
1316
2578
|
function formatCustomerNumber(prefix, sequence) {
|
|
@@ -1329,6 +2591,7 @@ function toSubscriberRecord(row, tenantId) {
|
|
|
1329
2591
|
vatId: row.vatId,
|
|
1330
2592
|
taxNumber: row.taxNumber,
|
|
1331
2593
|
invoiceEmail: row.invoiceEmail,
|
|
2594
|
+
business: row.business,
|
|
1332
2595
|
tenantId,
|
|
1333
2596
|
migrated: row.migrated,
|
|
1334
2597
|
createdAt: row.createdAt,
|
|
@@ -1346,6 +2609,17 @@ function toSubscriberCorrectionRecord(row) {
|
|
|
1346
2609
|
correctedAt: row.correctedAt
|
|
1347
2610
|
};
|
|
1348
2611
|
}
|
|
2612
|
+
function toSubscriberTaxOriginChangeRecord(row) {
|
|
2613
|
+
return {
|
|
2614
|
+
id: row.id,
|
|
2615
|
+
subscriberId: row.subscriberId,
|
|
2616
|
+
previous: toTaxOriginValues(row.previous),
|
|
2617
|
+
changed: toTaxOriginValues(row.changed),
|
|
2618
|
+
changedBy: row.changedBy,
|
|
2619
|
+
changedAt: row.changedAt,
|
|
2620
|
+
reason: row.reason
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
1349
2623
|
function identityCorrectionDelta(current, corrected) {
|
|
1350
2624
|
const previous = {};
|
|
1351
2625
|
const next = {};
|
|
@@ -1409,7 +2683,9 @@ function subscriberFromRegistration(pending) {
|
|
|
1409
2683
|
city: pending.city,
|
|
1410
2684
|
country: pending.country,
|
|
1411
2685
|
vatId: pending.vatId,
|
|
1412
|
-
taxNumber: pending.taxNumber
|
|
2686
|
+
taxNumber: pending.taxNumber,
|
|
2687
|
+
business: pending.business,
|
|
2688
|
+
vatIdCheck: vatIdCheckFromStore(pending.vatIdCheck)
|
|
1413
2689
|
};
|
|
1414
2690
|
}
|
|
1415
2691
|
function toIdentityValues(value) {
|
|
@@ -1427,7 +2703,7 @@ function toIdentityValues(value) {
|
|
|
1427
2703
|
// src/subscriber-payment-method-mapping.ts
|
|
1428
2704
|
var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
|
|
1429
2705
|
var STATUSES = ["ACTIVE", "REPLACED"];
|
|
1430
|
-
var
|
|
2706
|
+
var TABLE3 = "subscriber_payment_methods";
|
|
1431
2707
|
function toSubscriberPaymentMethodRecord(row) {
|
|
1432
2708
|
return {
|
|
1433
2709
|
id: row.id,
|
|
@@ -1436,7 +2712,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
1436
2712
|
provider: row.provider,
|
|
1437
2713
|
customerRef: row.customerRef,
|
|
1438
2714
|
paymentMethodRef: row.paymentMethodRef,
|
|
1439
|
-
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table:
|
|
2715
|
+
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE3, column: "type", id: row.id }),
|
|
1440
2716
|
brand: row.brand,
|
|
1441
2717
|
last4: row.last4,
|
|
1442
2718
|
expiryMonth: row.expiryMonth,
|
|
@@ -1444,7 +2720,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
1444
2720
|
country: row.country,
|
|
1445
2721
|
bankCode: row.bankCode,
|
|
1446
2722
|
mandateReference: row.mandateReference,
|
|
1447
|
-
status: oneOf(STATUSES, row.status, { table:
|
|
2723
|
+
status: oneOf(STATUSES, row.status, { table: TABLE3, column: "status", id: row.id }),
|
|
1448
2724
|
confirmedAt: row.confirmedAt,
|
|
1449
2725
|
replacedAt: row.replacedAt,
|
|
1450
2726
|
createdAt: row.createdAt
|
|
@@ -1510,6 +2786,8 @@ function toSubscriptionBundleRecord(row) {
|
|
|
1510
2786
|
}),
|
|
1511
2787
|
currentPeriodStart: row.currentPeriodStart ?? null,
|
|
1512
2788
|
currentPeriodEnd: row.currentPeriodEnd ?? null,
|
|
2789
|
+
pendingBundleVersionId: row.pendingBundleVersionId ?? null,
|
|
2790
|
+
pendingVersionEffectiveAt: row.pendingVersionEffectiveAt ?? null,
|
|
1513
2791
|
canceledAt: row.canceledAt,
|
|
1514
2792
|
canceledEffectiveAt: row.canceledEffectiveAt,
|
|
1515
2793
|
createdAt: row.createdAt,
|
|
@@ -1526,6 +2804,7 @@ function toSubscriptionContractRecord(row, lineItems) {
|
|
|
1526
2804
|
subscriber: toSubscriberParty(row.subscriberSnapshot),
|
|
1527
2805
|
issuer: isPlainObject2(row.issuerSnapshot) ? toIssuerParty(row.issuerSnapshot) : null,
|
|
1528
2806
|
partiesMigrated: row.partiesMigrated,
|
|
2807
|
+
taxTreatment: toTaxTreatment(row.taxTreatment),
|
|
1529
2808
|
status: row.status,
|
|
1530
2809
|
effectiveFrom: row.effectiveFrom,
|
|
1531
2810
|
effectiveUntil: row.effectiveUntil,
|
|
@@ -1625,6 +2904,52 @@ var ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES = [
|
|
|
1625
2904
|
"scheduled"
|
|
1626
2905
|
];
|
|
1627
2906
|
|
|
2907
|
+
// src/custom-limits.ts
|
|
2908
|
+
var CUSTOM_LIMITS_KEYS = ["quotas", "features"];
|
|
2909
|
+
var NOT_AN_OBJECT = "(the value is not an object)";
|
|
2910
|
+
function readCustomLimits(value) {
|
|
2911
|
+
if (value === null || value === void 0) return { limits: null, unread: [] };
|
|
2912
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
2913
|
+
return { limits: null, unread: [NOT_AN_OBJECT] };
|
|
2914
|
+
}
|
|
2915
|
+
const limits = {};
|
|
2916
|
+
const unread = [];
|
|
2917
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2918
|
+
if (key === "quotas" && isRecord(entry)) {
|
|
2919
|
+
limits.quotas = {};
|
|
2920
|
+
for (const [quota, amount] of Object.entries(entry)) {
|
|
2921
|
+
const value2 = readQuotaValue(amount);
|
|
2922
|
+
if (value2 === null) unread.push(`quotas.${quota}`);
|
|
2923
|
+
else limits.quotas[quota] = value2;
|
|
2924
|
+
}
|
|
2925
|
+
} else if (key === "features" && isFeatureList(entry)) {
|
|
2926
|
+
limits.features = [...entry];
|
|
2927
|
+
} else {
|
|
2928
|
+
unread.push(key);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
return { limits, unread };
|
|
2932
|
+
}
|
|
2933
|
+
function isRecord(value) {
|
|
2934
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2935
|
+
}
|
|
2936
|
+
function isFeatureList(value) {
|
|
2937
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
2938
|
+
}
|
|
2939
|
+
function customLimitsReader(report) {
|
|
2940
|
+
const reported = /* @__PURE__ */ new Set();
|
|
2941
|
+
return (subscriptionId, stored) => {
|
|
2942
|
+
const { limits, unread } = readCustomLimits(stored);
|
|
2943
|
+
if (unread.length > 0 && !reported.has(subscriptionId)) {
|
|
2944
|
+
reported.add(subscriptionId);
|
|
2945
|
+
report(
|
|
2946
|
+
`Subscription ${subscriptionId}: customLimits carries ${unread.join(", ")}, which the platform does not read, so these limits are not applied. It reads { quotas: { <quotaKey>: number }, features: string[] } (CustomLimits in @saasicat/core).`
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
return limits;
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
|
|
1628
2953
|
// src/version-editability.ts
|
|
1629
2954
|
function isVersionEditable(v, now = /* @__PURE__ */ new Date()) {
|
|
1630
2955
|
if (v.publishedAt === null) {
|
|
@@ -1663,227 +2988,6 @@ function keepOneRecommended(plans, inRequestedLocale) {
|
|
|
1663
2988
|
return winner;
|
|
1664
2989
|
}
|
|
1665
2990
|
|
|
1666
|
-
// src/error-messages.ts
|
|
1667
|
-
var ERROR_MESSAGES_EN = {
|
|
1668
|
-
// ── setup ──
|
|
1669
|
-
SETUP_DISABLED: "{envVar} is not set \u2014 setup is disabled.",
|
|
1670
|
-
INVALID_SETUP_TOKEN: "The setup token is not valid.",
|
|
1671
|
-
SETUP_ALREADY_DONE: "Setup has already been completed.",
|
|
1672
|
-
INVALID_EMAIL: "That is not a valid email address.",
|
|
1673
|
-
EMAIL_EXISTS: "An account with this email address already exists.",
|
|
1674
|
-
// ── auth ──
|
|
1675
|
-
NOT_AUTHENTICATED: "Not authenticated",
|
|
1676
|
-
NO_TENANT_ASSIGNED: "No tenant assigned",
|
|
1677
|
-
TENANT_CONTEXT_MISSING: "No tenant ID found on the request",
|
|
1678
|
-
TENANT_ADMIN_REQUIRED: "This action requires the TENANT_ADMIN role.",
|
|
1679
|
-
BILLING_PERMISSION_REQUIRED: "This part of the billing area requires the billing permission.",
|
|
1680
|
-
SUPER_ADMIN_REQUIRED: "Only the SUPER_ADMIN role is allowed",
|
|
1681
|
-
MFA_NOT_SET_UP: "Run the MFA setup via the CLI first.",
|
|
1682
|
-
MFA_REQUIRED: "TOTP code required in the X-Mfa-Code header.",
|
|
1683
|
-
MFA_FAILED: "Invalid TOTP code.",
|
|
1684
|
-
AUTH_GUARDS_NOT_CONFIGURED: "TenantBillingModule.forRoot.authGuards is not configured.",
|
|
1685
|
-
// ── catalog ──
|
|
1686
|
-
PLAN_HAS_DRAFTS: "Plan '{planKey}' still has {draftCount} open draft version(s). Discard them first (DELETE /admin/catalog/plan-versions/:id) or publish them.",
|
|
1687
|
-
PLAN_HAS_PUBLISHED_VERSIONS: "Plan '{planKey}' cannot be {operation} \u2014 it has {publishedCount} published version(s) ({liveCount} live, {supersededCount} superseded). Existing subscriptions reference those versions (contract protection P1), so the plan record must be preserved.",
|
|
1688
|
-
PLAN_HARD_DELETE_NOT_IMPLEMENTED: "Hard delete is not implemented in the current repository. Implement PlanRepository.hardDelete.",
|
|
1689
|
-
PLAN_VERSION_ALREADY_PUBLISHED: "PlanVersion '{versionId}' is already published and cannot be discarded.",
|
|
1690
|
-
PLAN_VERSION_NOT_EDITABLE: "PlanVersion '{versionId}' is not editable. Only drafts and published versions are editable that are latest-in-chain, bind no subscription yet, and whose validFrom lies in the future.",
|
|
1691
|
-
PLAN_VERSION_REGRESSION: "This plan version is regressive (feature removed / quota lowered / price raised). Publishing requires an explicit `forceRegressive: true` (UI confirmation dialog with MFA).",
|
|
1692
|
-
PLAN_VERSION_ZERO_PRICE: "A plan version cannot be published with a price of 0.00 (guard against seed placeholder). For deliberately free special contracts, set allowZeroPrice.",
|
|
1693
|
-
PLAN_VERSION_DISCARD_NOT_IMPLEMENTED: "Discard is not implemented in the current repository. Implement PlanRepository.deletePlanVersionDraft.",
|
|
1694
|
-
PLAN_VERSION_NOT_PUBLISHED: "PlanVersion '{versionId}' is not published and cannot be terminated.",
|
|
1695
|
-
PLAN_VERSION_SUPERSEDED: "PlanVersion '{versionId}' has already been superseded by a newer version and cannot be terminated.",
|
|
1696
|
-
PLAN_VERSION_VALID_FROM_REQUIRED: "validFrom must be set when publishing (on the draft or the publish call)..",
|
|
1697
|
-
PLAN_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' is not a valid date",
|
|
1698
|
-
PLAN_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS: "validFrom ({validFrom}) must be strictly after the validFrom of the previous version ({previousValidFrom}).",
|
|
1699
|
-
PLAN_VERSION_VALID_FROM_NOT_GAPLESS: "The predecessor has validUntil={previousValidUntil} \u2014 the successor must start seamlessly on the next day ({requiredValidFrom}). Received: {received}.",
|
|
1700
|
-
PLAN_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' is not a valid date",
|
|
1701
|
-
PLAN_VERSION_VALID_UNTIL_BEFORE_FROM: "validUntil ({validUntil}) must be strictly after validFrom ({validFrom}).",
|
|
1702
|
-
PLAN_TERMINATE_INVALID_DATE: "endsAt is not a valid date.",
|
|
1703
|
-
PLAN_TERMINATE_DATE_NOT_FUTURE: "endsAt ({endsAt}) must lie strictly in the future.",
|
|
1704
|
-
PLAN_TERMINATE_NOT_IMPLEMENTED: "Terminate is not implemented in the current repository. Implement PlanRepository.terminate.",
|
|
1705
|
-
BUNDLE_VERSION_ALREADY_PUBLISHED: "BundleVersion '{versionId}' is already published and cannot be discarded.",
|
|
1706
|
-
BUNDLE_VERSION_NOT_EDITABLE: "BundleVersion '{versionId}' is not editable. Only drafts and published versions are editable that are latest-in-chain, bind no subscription yet, and whose validFrom lies in the future.",
|
|
1707
|
-
BUNDLE_VERSION_NOT_PUBLISHED: "BundleVersion '{bundleVersionId}' is not published and cannot be booked.",
|
|
1708
|
-
BUNDLE_VERSION_SUPERSEDED: "BundleVersion '{bundleVersionId}' has been superseded by a newer version.",
|
|
1709
|
-
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).",
|
|
1710
|
-
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.",
|
|
1711
|
-
BUNDLE_VERSION_NO_PRICE: "This bundle version cannot be published without a price: neither a base price nor any plan override resolves one.",
|
|
1712
|
-
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.",
|
|
1713
|
-
BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "Discard is not implemented in the current repository. Implement BundleRepository.deleteDraft.",
|
|
1714
|
-
BUNDLE_VERSION_VALID_FROM_REQUIRED: "Published bundle versions must keep a validFrom. Set a new future date, or edit the draft before publishing.",
|
|
1715
|
-
BUNDLE_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' is not a valid date",
|
|
1716
|
-
BUNDLE_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS: "validFrom ({validFrom}) must be strictly after the validFrom of the previous version ({previousValidFrom}).",
|
|
1717
|
-
BUNDLE_VERSION_VALID_FROM_NOT_GAPLESS: "The predecessor has validUntil={previousValidUntil} \u2014 the successor must start seamlessly on the next day ({requiredValidFrom}). Received: {received}.",
|
|
1718
|
-
BUNDLE_VERSION_VALID_FROM_NOT_FUTURE: "validFrom ({validFrom}) must, for a published-but-future bundle version, still lie in the future.",
|
|
1719
|
-
BUNDLE_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' is not a valid date",
|
|
1720
|
-
BUNDLE_VERSION_VALID_UNTIL_BEFORE_FROM: "validUntil ({validUntil}) must be strictly after validFrom ({validFrom}).",
|
|
1721
|
-
STRICT_MODE_VIOLATIONS: "The strict-mode check found drift against the discovery snapshot.",
|
|
1722
|
-
PLAN_NOT_FOUND: "Plan '{planId}' not found",
|
|
1723
|
-
PLAN_VERSION_NOT_FOUND: "PlanVersion '{versionId}' not found",
|
|
1724
|
-
BUNDLE_NOT_FOUND: "Bundle '{bundleId}' not found",
|
|
1725
|
-
BUNDLE_VERSION_NOT_FOUND: "BundleVersion '{bundleVersionId}' not found",
|
|
1726
|
-
FEATURE_NOT_FOUND: "Feature '{featureKey}' not found",
|
|
1727
|
-
QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found",
|
|
1728
|
-
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' not found",
|
|
1729
|
-
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.",
|
|
1730
|
-
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' not found",
|
|
1731
|
-
PLAN_ALREADY_EXISTS: "Plan '{planKey}' already exists",
|
|
1732
|
-
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' already exists",
|
|
1733
|
-
MARKETING_PROJECTION_ALREADY_EXISTS: "Marketing projection for {targetType}/{targetVersionId}/{locale} already exists \u2014 use PATCH to edit it",
|
|
1734
|
-
PLAN_DRAFT_ALREADY_EXISTS: "Plan '{planKey}' already has a draft version v{draftVersion}; publish or discard it first",
|
|
1735
|
-
BUNDLE_DRAFT_ALREADY_EXISTS: "Bundle '{bundleKey}' already has a draft version v{draftVersion}; publish or discard it first",
|
|
1736
|
-
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",
|
|
1737
|
-
DISCOVERY_STATUS_TRANSITION_INVALID: "Transition '{from}' \u2192 '{to}' is not allowed",
|
|
1738
|
-
DISCOVERY_NOT_INITIALIZED: "Approval requires a discovery snapshot \u2014 discovery is not initialised (#25)",
|
|
1739
|
-
// ── catalogue import ──
|
|
1740
|
-
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.",
|
|
1741
|
-
PLAN_CATALOG_INVALID: "The plan catalog was read, and then rejected: {message}",
|
|
1742
|
-
// ── billing ──
|
|
1743
|
-
BUNDLE_ALREADY_SUBSCRIBED: "Subscription '{subscriptionId}' has already actively booked this bundle.",
|
|
1744
|
-
BUNDLE_INCOMPATIBLE_WITH_PLAN: "BundleVersion '{bundleVersionId}' is not compatible with plan '{planKey}'. Allowed: [{allowedPlanKeys}].",
|
|
1745
|
-
BUNDLE_NOT_SELF_SERVICE: "Bundle '{bundleKey}' is only activated via a special contract. Please contact the contract manager.",
|
|
1746
|
-
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.",
|
|
1747
|
-
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "This bundle has no {billingCycle} price for the {planKey} plan, so it cannot be booked from here.",
|
|
1748
|
-
SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' is already cancelled.",
|
|
1749
|
-
SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' is not cancelled.",
|
|
1750
|
-
SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "Cancellation already in effect \u2014 book the bundle again.",
|
|
1751
|
-
SUBSCRIPTION_NOT_FOUND: "No subscription for tenant {tenantId}",
|
|
1752
|
-
SUBSCRIPTION_TENANT_MISMATCH: "Subscription does not belong to the tenant",
|
|
1753
|
-
SUBSCRIPTION_BUNDLE_NOT_FOUND: "SubscriptionBundle '{subscriptionBundleId}' not found",
|
|
1754
|
-
TENANT_NOT_FOUND: "Tenant {slug} not found",
|
|
1755
|
-
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.",
|
|
1756
|
-
PLAN_NOT_IN_CATALOG: 'Plan "{planKey}" is not in the catalog',
|
|
1757
|
-
PLAN_NOT_SELF_SERVICE: "{planName} is only activated via a special contract. Please contact the contract manager.",
|
|
1758
|
-
PLAN_NOT_SOLD_IN_CYCLE: "{planName} has no price for this billing rhythm and cannot be booked in it.",
|
|
1759
|
-
PLAN_CHANGE_BLOCKED: "Plan change during onboarding is blocked.",
|
|
1760
|
-
SUBSCRIPTION_CHANGED: "This subscription changed while the request was being decided. Reload it.",
|
|
1761
|
-
NO_SUBSCRIPTION: "This tenant has no subscription to cancel.",
|
|
1762
|
-
CANCELLATION_TERMS_CHANGED: "The effective date changed since it was shown. Confirm the new one.",
|
|
1763
|
-
SUBSCRIPTION_ENDED: "This subscription has ended. Its plan can no longer be changed.",
|
|
1764
|
-
PLAN_LOCKED: "Active {planName} special contract \u2014 please contact the contract manager to change plans.",
|
|
1765
|
-
QUOTA_OVER_TARGET: "Current usage {used} exceeds the target limit {targetMax} ({quotaKey}) in the {planName} plan. Please reduce usage first.",
|
|
1766
|
-
FEATURE_LOST: "Switching means losing access to one feature. Existing data is retained and never deleted \u2014 upgrading again unlocks it.",
|
|
1767
|
-
FEATURES_LOST: "Switching means losing access to {count} features. Existing data is retained and never deleted \u2014 upgrading again unlocks it.",
|
|
1768
|
-
NO_CHANGE: "Target plan and billing cycle already match the current state.",
|
|
1769
|
-
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.",
|
|
1770
|
-
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.",
|
|
1771
|
-
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.",
|
|
1772
|
-
REDUNDANT_FEATURES: "The plan or another booked bundle already includes {count} of the features in this bundle \u2014 booking it pays for them twice.",
|
|
1773
|
-
MINIMUM_TERM_BINDS: "The minimum term extends beyond the end of the period \u2014 the cancellation only takes effect when the minimum term ends.",
|
|
1774
|
-
BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "The bundle requires [{features}] \u2014 present neither in the plan nor in the active bundles.",
|
|
1775
|
-
NO_PENDING_PLAN_VERSION: "There is no pending plan version awaiting confirmation.",
|
|
1776
|
-
ONBOARDING_CREATE_FAILED: "The account could not be created. Please try again.",
|
|
1777
|
-
BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "Exactly one of bundleVersionId (add preview) or subscriptionBundleId (cancel preview) must be given.",
|
|
1778
|
-
SUBSCRIPTION_PK_MISSING: "The adapter returned a subscription usage record without an id. Pass the subscription primary key through (see SubscriptionUsageRecord.id).",
|
|
1779
|
-
LIMIT_EXCEEDED: "The limit for {dimension} has been reached: {used} of {max}.",
|
|
1780
|
-
QUOTA_DIMENSION_UNKNOWN: 'Unknown quota dimension "{dimension}".',
|
|
1781
|
-
// ── promo ──
|
|
1782
|
-
PROMO_CODE_NOT_FOUND: "Code not found",
|
|
1783
|
-
PROMO_CODE_ALREADY_EXISTS: "The code already exists.",
|
|
1784
|
-
PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions, or checkouts holding one \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
1785
|
-
PROMO_CODE_NOT_REDEEMABLE: "Code cannot be redeemed: {reason}",
|
|
1786
|
-
PROMO_CODE_FORMAT_INVALID: 'The code may only contain upper-case letters, digits, "-" and "_" (4\u201332 characters).',
|
|
1787
|
-
PROMO_PERCENT_OUT_OF_RANGE: "The percentage must be between 0 and 100.",
|
|
1788
|
-
PROMO_AMOUNT_NOT_POSITIVE: "The amount must be positive.",
|
|
1789
|
-
PROMO_ONE_OFF_WITH_DURATION: "A one-off discount must not set a duration.",
|
|
1790
|
-
PROMO_DURATION_INVALID: "Invalid duration (at most 24 months or billing periods).",
|
|
1791
|
-
PROMO_VALIDITY_WINDOW_INVALID: "Invalid validity window.",
|
|
1792
|
-
PROMO_PLAN_NOT_DISCOUNTABLE: "{plan}-Plan cannot be discounted.",
|
|
1793
|
-
PROMO_MIN_AMOUNT_NOT_POSITIVE: "The minimum plan gross amount must be positive.",
|
|
1794
|
-
PROMO_WOULD_PRODUCE_ZERO_INVOICE: "For absolute amounts the discount must stay below the lowest applicable plan price, or allowZeroInvoice must be enabled.",
|
|
1795
|
-
PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions cannot be lowered.",
|
|
1796
|
-
// ── contract ──
|
|
1797
|
-
CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "A checkout offer can yield only one contract, and only once its line items are frozen.",
|
|
1798
|
-
CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "A checkout offer requires a frozen plan line item.",
|
|
1799
|
-
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Every selected bundle version requires a frozen bundle line item.",
|
|
1800
|
-
CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE: "At least one bundle version from the checkout offer is no longer bookable.",
|
|
1801
|
-
CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED: "The selected plan does not cover all feature dependencies: [{missingRequires}] are missing from the plan + selected bundles.",
|
|
1802
|
-
CHECKOUT_OFFER_PLAN_NOT_OFFERED: "Plan '{planKey}' is not offered with a {billingCycle} price at the moment.",
|
|
1803
|
-
CHECKOUT_OFFER_BUNDLE_NOT_OFFERED: "Bundle version '{bundleVersionId}' cannot be added to this offer ({reason}).",
|
|
1804
|
-
CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED: "The promo code cannot be applied to this offer ({reason}).",
|
|
1805
|
-
CHECKOUT_OFFER_PRICE_NOT_CURRENT: "The prices of checkout offer '{offerId}' no longer match the catalogue; create a new offer.",
|
|
1806
|
-
SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED: "A subscription contract requires at least one line item.",
|
|
1807
|
-
SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "A subscription contract requires exactly one plan base item.",
|
|
1808
|
-
SUBSCRIPTION_CONTRACT_INVALID_DATE: "{field} must be a valid date.",
|
|
1809
|
-
SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "effectiveUntil must be after effectiveFrom.",
|
|
1810
|
-
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "A line item's taxAmount must be exactly priceGross minus priceNet.",
|
|
1811
|
-
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.",
|
|
1812
|
-
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil must be after the effectiveFrom of the contract.",
|
|
1813
|
-
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "A line item must be booked in the currency its contract was priced in.",
|
|
1814
|
-
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}.",
|
|
1815
|
-
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "A subscription contract states a discount of {amount} at {field}; a discount takes money off and is never negative.",
|
|
1816
|
-
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' not found",
|
|
1817
|
-
CHECKOUT_OFFER_EXPIRED: "Checkout offer '{offerId}' has expired and cannot be {action}",
|
|
1818
|
-
CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
|
|
1819
|
-
CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' must be consumed before the contract is created",
|
|
1820
|
-
CHECKOUT_OFFER_CHANGED: "Checkout offer '{offerId}' changed while it was being concluded. Load it again.",
|
|
1821
|
-
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
|
|
1822
|
-
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
|
|
1823
|
-
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' is already closed",
|
|
1824
|
-
SUBSCRIPTION_CONTRACT_CHANGED: "The contracts of tenant '{tenantId}' changed while a successor was being written, or one begins after the moment it would take effect, so it would run beside another. Nothing was written.",
|
|
1825
|
-
// ── subscriber ──
|
|
1826
|
-
SUBSCRIBER_REQUIRED: "Tenant '{tenantId}' has no subscriber. Nothing is agreed or charged without the party to it: create the tenant's subscriber first.",
|
|
1827
|
-
SUBSCRIBER_ALREADY_EXISTS: "Tenant '{tenantId}' already has a subscriber.",
|
|
1828
|
-
SUBSCRIBER_NOT_FOUND: "Subscriber '{subscriberId}' not found",
|
|
1829
|
-
SUBSCRIBER_LEGAL_NAME_REQUIRED: "A subscriber needs its legal name.",
|
|
1830
|
-
SUBSCRIBER_DETAIL_INVALID: "The subscriber's {field} is not valid.",
|
|
1831
|
-
SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} is part of the subscriber's legal identity and changes only as a correction, with a reason.",
|
|
1832
|
-
SUBSCRIBER_CORRECTION_REASON_REQUIRED: "A correction of the legal identity needs a reason.",
|
|
1833
|
-
SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "A correction of the legal identity has to say who makes it.",
|
|
1834
|
-
SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "The correction changes nothing: every value it names is already recorded.",
|
|
1835
|
-
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.",
|
|
1836
|
-
// ── registration ──
|
|
1837
|
-
PENDING_REGISTRATION_NOT_FOUND: "This registration could not be found. It may have already been completed or discarded.",
|
|
1838
|
-
PENDING_REGISTRATION_EXPIRED: "This registration has expired. Please start again.",
|
|
1839
|
-
INVALID_REGISTRATION_STATE: "This step is not available at the current stage of the registration.",
|
|
1840
|
-
OTP_INVALID: "That code is not correct. Please check it and try again.",
|
|
1841
|
-
OTP_EXPIRED: "That code has expired. Please request a new one.",
|
|
1842
|
-
OTP_LOCKED: "Too many incorrect attempts. Please request a new code.",
|
|
1843
|
-
RATE_LIMITED: "Too many attempts. Please try again in {retryAfterSeconds} seconds.",
|
|
1844
|
-
RESUME_TOKEN_INVALID: "This resume link is no longer valid. Please request a new one.",
|
|
1845
|
-
RESUME_NOT_CONFIGURED: "Resuming a registration is not available.",
|
|
1846
|
-
CONFIGURATOR_NOT_CONFIGURED: "The configurator is not available.",
|
|
1847
|
-
CONFIG_NOT_SAVED: "The configuration could not be saved.",
|
|
1848
|
-
PLAN_NOT_AVAILABLE: "This plan is not available.",
|
|
1849
|
-
PLAN_NOT_SELECTED: "Please select a plan first.",
|
|
1850
|
-
MODEL_NOT_AVAILABLE: "This option is not available.",
|
|
1851
|
-
// ── payments ──
|
|
1852
|
-
PAYMENTS_NOT_CONFIGURED: "Payment methods cannot be set up here yet.",
|
|
1853
|
-
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "No payment gateway account is configured under '{account}'.",
|
|
1854
|
-
PAYMENT_CALLBACK_REJECTED: "The payment callback could not be verified.",
|
|
1855
|
-
PAYMENT_RETURN_URL_NOT_ALLOWED: "The {field} leads to a site this installation does not return to.",
|
|
1856
|
-
PAYMENT_GATEWAY_FAILED: "The payment provider did not answer as expected. Please try again in a few minutes.",
|
|
1857
|
-
// ── entitlement (code lives in upsell.types.ts) ──
|
|
1858
|
-
[FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan.",
|
|
1859
|
-
// ── settings ──
|
|
1860
|
-
SETTINGS_CHANGE_NOT_FOUND: "No recorded settings change has this id.",
|
|
1861
|
-
// ── maintenance ──
|
|
1862
|
-
MAINTENANCE: "The application is under maintenance.",
|
|
1863
|
-
MAINTENANCE_WINDOW_ALREADY_OPEN: "A maintenance window is already open. Move or cancel it before announcing another.",
|
|
1864
|
-
MAINTENANCE_WINDOW_NOT_OPEN: "No open maintenance window has this id.",
|
|
1865
|
-
MAINTENANCE_WINDOW_LOCKED: "This maintenance window is locked. Its start cannot move, and unlocking is what ends it.",
|
|
1866
|
-
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "The end of a maintenance window has to be after its start.",
|
|
1867
|
-
MAINTENANCE_WINDOW_END_IN_PAST: "The end of a maintenance window cannot be in the past.",
|
|
1868
|
-
MAINTENANCE_TIME_INVALID: "The {field} has to be a date and time with its zone, such as 2026-10-02T22:00+02:00.",
|
|
1869
|
-
MAINTENANCE_MESSAGE_TOO_LONG: "The message may be at most {max} characters long."
|
|
1870
|
-
};
|
|
1871
|
-
function formatErrorMessage(template, params = {}) {
|
|
1872
|
-
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
1873
|
-
const value = params[name];
|
|
1874
|
-
return value === void 0 || value === null ? match : String(value);
|
|
1875
|
-
});
|
|
1876
|
-
}
|
|
1877
|
-
function resolveErrorMessage(body, overrides = {}, defaults = ERROR_MESSAGES_EN) {
|
|
1878
|
-
const code = typeof body.code === "string" ? body.code : void 0;
|
|
1879
|
-
const template = (code && (overrides[code] ?? defaults[code])) ?? void 0;
|
|
1880
|
-
if (!template) {
|
|
1881
|
-
return typeof body.message === "string" ? body.message : code ?? "";
|
|
1882
|
-
}
|
|
1883
|
-
const { params, ...rest } = body;
|
|
1884
|
-
return formatErrorMessage(template, { ...rest, ...params });
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
2991
|
// src/error-messages-de.ts
|
|
1888
2992
|
var ERROR_MESSAGES_DE = {
|
|
1889
2993
|
// ── setup ──
|
|
@@ -1907,7 +3011,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1907
3011
|
PLAN_HAS_DRAFTS: "Plan '{planKey}' hat noch {draftCount} offene Entwurfsversion(en). Verwirf sie zuerst (DELETE /admin/catalog/plan-versions/:id) oder ver\xF6ffentliche sie.",
|
|
1908
3012
|
PLAN_HAS_PUBLISHED_VERSIONS: "Plan '{planKey}' kann nicht {operation} werden \u2014 er hat {publishedCount} ver\xF6ffentlichte Version(en) ({liveCount} live, {supersededCount} abgel\xF6st). Bestehende Abonnements verweisen auf diese Versionen (Vertragsschutz P1), deshalb muss der Plan-Datensatz erhalten bleiben.",
|
|
1909
3013
|
PLAN_HARD_DELETE_NOT_IMPLEMENTED: "Hard Delete ist im aktuellen Repository nicht implementiert. Implementiere PlanRepository.hardDelete.",
|
|
1910
|
-
PLAN_VERSION_ALREADY_PUBLISHED: "PlanVersion '{versionId}' ist bereits ver\xF6ffentlicht und
|
|
3014
|
+
PLAN_VERSION_ALREADY_PUBLISHED: "PlanVersion '{versionId}' ist bereits ver\xF6ffentlicht und wird weder erneut ver\xF6ffentlicht noch verworfen.",
|
|
1911
3015
|
PLAN_VERSION_NOT_EDITABLE: "PlanVersion '{versionId}' ist nicht bearbeitbar. Bearbeitbar sind nur Entw\xFCrfe sowie ver\xF6ffentlichte Versionen, die letzte in der Kette sind, noch kein Abonnement binden und deren validFrom in der Zukunft liegt.",
|
|
1912
3016
|
PLAN_VERSION_REGRESSION: "Diese Planversion 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).",
|
|
1913
3017
|
PLAN_VERSION_ZERO_PRICE: "Eine Planversion l\xE4sst sich nicht mit einem Preis von 0,00 ver\xF6ffentlichen (Schutz vor Seed-Platzhaltern). F\xFCr bewusst kostenlose Sondervertr\xE4ge setze allowZeroPrice.",
|
|
@@ -1915,34 +3019,40 @@ var ERROR_MESSAGES_DE = {
|
|
|
1915
3019
|
PLAN_VERSION_NOT_PUBLISHED: "Die PlanVersion '{versionId}' ist nicht ver\xF6ffentlicht und kann nicht beendet werden.",
|
|
1916
3020
|
PLAN_VERSION_SUPERSEDED: "Die PlanVersion '{versionId}' wurde bereits von einer Nachfolgeversion abgel\xF6st und kann nicht beendet werden.",
|
|
1917
3021
|
PLAN_VERSION_VALID_FROM_REQUIRED: "validFrom muss beim Ver\xF6ffentlichen gesetzt sein (am Entwurf oder im Publish-Aufruf)..",
|
|
1918
|
-
PLAN_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' ist kein
|
|
3022
|
+
PLAN_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' ist kein Tag (JJJJ-MM-TT)",
|
|
1919
3023
|
PLAN_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS: "validFrom ({validFrom}) muss strikt nach dem validFrom der Vorg\xE4ngerversion ({previousValidFrom}) liegen.",
|
|
1920
3024
|
PLAN_VERSION_VALID_FROM_NOT_GAPLESS: "Der Vorg\xE4nger hat validUntil={previousValidUntil} \u2014 der Nachfolger muss l\xFCckenlos am Folgetag ({requiredValidFrom}) beginnen. Erhalten: {received}.",
|
|
1921
|
-
PLAN_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' ist kein
|
|
3025
|
+
PLAN_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' ist kein Tag (JJJJ-MM-TT)",
|
|
1922
3026
|
PLAN_VERSION_VALID_UNTIL_BEFORE_FROM: "validUntil ({validUntil}) muss strikt nach validFrom ({validFrom}) liegen.",
|
|
1923
3027
|
PLAN_TERMINATE_INVALID_DATE: "endsAt ist kein g\xFCltiges Datum.",
|
|
1924
3028
|
PLAN_TERMINATE_DATE_NOT_FUTURE: "endsAt ({endsAt}) muss strikt in der Zukunft liegen.",
|
|
1925
3029
|
PLAN_TERMINATE_NOT_IMPLEMENTED: "Das Beenden ist im aktuellen Repository nicht implementiert. Implementiere PlanRepository.terminate.",
|
|
1926
|
-
|
|
3030
|
+
PLAN_TERMINATE_BEFORE_RETIREMENT_MOVES: "Abonnements ziehen noch auf Version {version} von {planKey} um. Sie kann fr\xFChestens am {date} enden.",
|
|
3031
|
+
PLAN_TERMINATE_WHILE_MOVES_OVERDUE: "{count} Abonnements sind \xFCber ihr Datum und ziehen noch auf Version {version} von {planKey} um. Sie kann erst enden, wenn sie umgezogen sind.",
|
|
3032
|
+
PLAN_TERMINATE_WHILE_NOTICES_UNDELIVERED: "{count} Abonnements wissen noch nicht, dass sie auf Version {version} von {planKey} umziehen. Sie kann erst enden, wenn sie es erfahren haben.",
|
|
3033
|
+
BUNDLE_VERSION_ALREADY_PUBLISHED: "BundleVersion '{versionId}' ist bereits ver\xF6ffentlicht und wird weder erneut ver\xF6ffentlicht noch verworfen.",
|
|
1927
3034
|
BUNDLE_VERSION_NOT_EDITABLE: "BundleVersion '{versionId}' ist nicht bearbeitbar. Bearbeitbar sind nur Entw\xFCrfe sowie ver\xF6ffentlichte Versionen, die letzte in der Kette sind, noch kein Abonnement binden und deren validFrom in der Zukunft liegt.",
|
|
1928
3035
|
BUNDLE_VERSION_NOT_PUBLISHED: "BundleVersion '{bundleVersionId}' ist nicht ver\xF6ffentlicht und l\xE4sst sich nicht buchen.",
|
|
1929
3036
|
BUNDLE_VERSION_SUPERSEDED: "BundleVersion '{bundleVersionId}' wurde durch eine neuere Version abgel\xF6st.",
|
|
3037
|
+
BUNDLE_VERSION_NOT_YET_ON_SALE: "BundleVersion '{bundleVersionId}' ist ab {validFrom} im Verkauf und kann vorher nicht gebucht werden.",
|
|
3038
|
+
BUNDLE_DELETED: "Bundle '{bundleKey}' wurde aus dem Katalog gel\xF6scht und l\xE4sst sich nicht buchen.",
|
|
1930
3039
|
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).",
|
|
1931
3040
|
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.",
|
|
1932
3041
|
BUNDLE_VERSION_NO_PRICE: "Diese Bundle-Version kann ohne Preis nicht ver\xF6ffentlicht werden: weder ein Grundpreis noch eine Plan-\xDCberschreibung ergibt einen.",
|
|
1933
3042
|
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.",
|
|
1934
3043
|
BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "Das Verwerfen ist im aktuellen Repository nicht implementiert. Implementiere BundleRepository.deleteDraft.",
|
|
1935
3044
|
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.",
|
|
1936
|
-
BUNDLE_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' ist kein
|
|
3045
|
+
BUNDLE_VERSION_VALID_FROM_INVALID: "validFrom '{validFrom}' ist kein Tag (JJJJ-MM-TT)",
|
|
1937
3046
|
BUNDLE_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS: "validFrom ({validFrom}) muss strikt nach dem validFrom der Vorg\xE4ngerversion ({previousValidFrom}) liegen.",
|
|
1938
3047
|
BUNDLE_VERSION_VALID_FROM_NOT_GAPLESS: "Der Vorg\xE4nger hat validUntil={previousValidUntil} \u2014 der Nachfolger muss l\xFCckenlos am Folgetag ({requiredValidFrom}) beginnen. Erhalten: {received}.",
|
|
1939
3048
|
BUNDLE_VERSION_VALID_FROM_NOT_FUTURE: "validFrom ({validFrom}) muss bei einer ver\xF6ffentlichten, aber zuk\xFCnftigen Bundle-Version weiterhin in der Zukunft liegen.",
|
|
1940
|
-
BUNDLE_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' ist kein
|
|
3049
|
+
BUNDLE_VERSION_VALID_UNTIL_INVALID: "validUntil '{validUntil}' ist kein Tag (JJJJ-MM-TT)",
|
|
1941
3050
|
BUNDLE_VERSION_VALID_UNTIL_BEFORE_FROM: "validUntil ({validUntil}) muss strikt nach validFrom ({validFrom}) liegen.",
|
|
1942
3051
|
STRICT_MODE_VIOLATIONS: "Die Strict-Mode-Pr\xFCfung hat eine Abweichung gegen\xFCber dem Discovery-Snapshot gefunden.",
|
|
1943
3052
|
PLAN_NOT_FOUND: "Plan '{planId}' nicht gefunden",
|
|
1944
3053
|
PLAN_VERSION_NOT_FOUND: "PlanVersion '{versionId}' nicht gefunden",
|
|
1945
3054
|
BUNDLE_NOT_FOUND: "Bundle '{bundleId}' nicht gefunden",
|
|
3055
|
+
BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING: "{count} Buchungen ziehen noch auf eine Version von {bundleKey} um, wie eine Stilllegung es ihnen mitgeteilt hat. Das Add-on l\xE4sst sich l\xF6schen, sobald sie umgezogen sind.",
|
|
1946
3056
|
BUNDLE_VERSION_NOT_FOUND: "BundleVersion '{bundleVersionId}' nicht gefunden",
|
|
1947
3057
|
FEATURE_NOT_FOUND: "Feature '{featureKey}' nicht gefunden",
|
|
1948
3058
|
QUOTA_NOT_FOUND: "Quota '{quotaKey}' nicht gefunden",
|
|
@@ -1962,10 +3072,12 @@ var ERROR_MESSAGES_DE = {
|
|
|
1962
3072
|
PLAN_CATALOG_INVALID: "Der Plankatalog wurde gelesen und dann abgelehnt: {message}",
|
|
1963
3073
|
// ── billing ──
|
|
1964
3074
|
BUNDLE_ALREADY_SUBSCRIBED: "Abonnement '{subscriptionId}' hat dieses Bundle bereits aktiv gebucht.",
|
|
1965
|
-
BUNDLE_INCOMPATIBLE_WITH_PLAN: "
|
|
3075
|
+
BUNDLE_INCOMPATIBLE_WITH_PLAN: "Dieses Bundle kann im Plan {planKey} nicht gebucht werden. Buchbar ist es in: {allowedPlanKeys}.",
|
|
1966
3076
|
BUNDLE_NOT_SELF_SERVICE: "Bundle '{bundleKey}' wird nur \xFCber einen Sondervertrag freigeschaltet. Bitte wende dich an die Vertragsverwaltung.",
|
|
1967
3077
|
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.",
|
|
1968
|
-
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "F\xFCr dieses Bundle ist im Plan {planKey} kein
|
|
3078
|
+
BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "F\xFCr dieses Bundle ist im Plan {planKey} in diesem Abrechnungsrhythmus kein Preis hinterlegt, es kann hier deshalb nicht gebucht werden.",
|
|
3079
|
+
BUNDLE_CANNOT_RUN_ON_UPCOMING_PLAN: "Dieses Bundle kann nicht im Plan {planKey} laufen, in den das Abonnement mit Wirkung ab {from} wechselt.",
|
|
3080
|
+
BUNDLE_CANNOT_RUN_ON_UPCOMING_CYCLE: "Ein j\xE4hrlich abgerechnetes Bundle kann nicht neben der monatlichen Abrechnung laufen, auf die das Abonnement mit Wirkung ab {from} wechselt.",
|
|
1969
3081
|
SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' ist bereits gek\xFCndigt.",
|
|
1970
3082
|
SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SubscriptionBundle '{subscriptionBundleId}' ist nicht gek\xFCndigt.",
|
|
1971
3083
|
SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "Die K\xFCndigung ist bereits wirksam \u2014 buche das Bundle einfach neu.",
|
|
@@ -1973,7 +3085,8 @@ var ERROR_MESSAGES_DE = {
|
|
|
1973
3085
|
SUBSCRIPTION_TENANT_MISMATCH: "Das Abonnement geh\xF6rt nicht zu diesem Mandanten",
|
|
1974
3086
|
SUBSCRIPTION_BUNDLE_NOT_FOUND: "SubscriptionBundle '{subscriptionBundleId}' nicht gefunden",
|
|
1975
3087
|
TENANT_NOT_FOUND: "Mandant {slug} nicht gefunden",
|
|
1976
|
-
NO_ACTIVE_PLAN_VERSION: "Zum {asOf} ist keine
|
|
3088
|
+
NO_ACTIVE_PLAN_VERSION: "Zum {asOf} ist keine Version des Plans {planId} im Verkauf.",
|
|
3089
|
+
BOUND_PLAN_VERSION_UNREADABLE: 'Die Planversion, an die dieses Abonnement gebunden ist ({planVersionId}), ist f\xFCr den Plan "{planKey}" nicht lesbar; deshalb kann kein Wechsel berechnet werden.',
|
|
1977
3090
|
PLAN_NOT_IN_CATALOG: 'Plan "{planKey}" steht nicht im Katalog',
|
|
1978
3091
|
PLAN_NOT_SELF_SERVICE: "{planName} wird nur \xFCber einen Sondervertrag freigeschaltet. Bitte wenden Sie sich an die Vertragsverwaltung.",
|
|
1979
3092
|
PLAN_NOT_SOLD_IN_CYCLE: "{planName} hat f\xFCr diesen Abrechnungsrhythmus keinen Preis und kann darin nicht gebucht werden.",
|
|
@@ -1981,6 +3094,36 @@ var ERROR_MESSAGES_DE = {
|
|
|
1981
3094
|
SUBSCRIPTION_CHANGED: "Dieses Abonnement hat sich ge\xE4ndert, w\xE4hrend die Anfrage entschieden wurde. Bitte lade es neu.",
|
|
1982
3095
|
NO_SUBSCRIPTION: "Dieser Mandant hat kein Abonnement, das gek\xFCndigt werden kann.",
|
|
1983
3096
|
CANCELLATION_TERMS_CHANGED: "Das Wirksamkeitsdatum hat sich seit der Anzeige ge\xE4ndert. Bitte best\xE4tige das neue Datum.",
|
|
3097
|
+
VERSION_OFFER_CHANGED: "Das Angebot hat sich seit der Anzeige ge\xE4ndert. Bitte sieh dir das aktuelle an, bevor du wechselst.",
|
|
3098
|
+
PLAN_CHANGE_QUOTE_CHANGED: "Dieser Plan hat sich seit der Anzeige ge\xE4ndert. Bitte sieh ihn dir noch einmal an, bevor du wechselst.",
|
|
3099
|
+
PLAN_CHANGE_VERSION_NOT_NAMED: "Ein Planwechsel muss die Version nennen, die seine Vorschau gezeigt hat.",
|
|
3100
|
+
VERSION_SWITCH_AFTER_CANCELLATION: "Dieses Abonnement endet, bevor diese Version wirksam w\xFCrde. Ein Wechsel dorthin ist nicht m\xF6glich.",
|
|
3101
|
+
VERSION_ENDS_BEFORE_SWITCH: "Diese Version wird nicht mehr verkauft, bevor sie wirksam w\xFCrde. Ein Wechsel dorthin ist nicht m\xF6glich.",
|
|
3102
|
+
RETIREMENT_TERMS_NOT_CONFIRMED: "Eine Version f\xFCr laufende Abonnements stillzulegen, braucht eine Klausel in deinen AGB. Setze tenantBilling.orderlyRetirement.termsConfirmed in config/saas.yaml, sobald sie sie enthalten.",
|
|
3103
|
+
RETIREMENT_VERSION_ON_SALE: "Version {version} von {planKey} wird noch verkauft. Beende zuerst ihren Verkauf, damit sie nach der Ank\xFCndigung niemand mehr bucht.",
|
|
3104
|
+
RETIREMENT_REPLACEMENT_NOT_ON_SALE: "Version {version} von {planKey} wird nicht verkauft. Abonnements k\xF6nnen nicht auf ihr weiterlaufen.",
|
|
3105
|
+
RETIREMENT_REPLACEMENT_NOT_SOLD_IN_RHYTHM: "{count} dieser Abonnements werden in einem Rhythmus abgerechnet, f\xFCr den Version {version} von {planKey} keinen Preis hat. Sie k\xF6nnen nicht auf ihr weiterlaufen.",
|
|
3106
|
+
RETIREMENT_REPLACEMENT_CANNOT_CARRY_BUNDLES: "{count} dieser Abonnements halten ein Bundle, das in Version {version} von {planKey} nicht laufen kann. Sie k\xF6nnen nicht auf ihr weiterlaufen.",
|
|
3107
|
+
RETIREMENT_REPLACEMENT_IS_RETIRED: "Eine Version kann nicht ihr eigener Ersatz sein.",
|
|
3108
|
+
RETIREMENT_NOTHING_AFFECTED: "Kein laufendes Abonnement nutzt Version {version} von {planKey}. Es gibt niemanden zu benachrichtigen.",
|
|
3109
|
+
RETIREMENT_WITHIN_TWELVE_MONTHS: "{count} dieser Abonnements waren in den letzten zw\xF6lf Monaten schon von einer Stilllegung betroffen. Ein Abonnement ist h\xF6chstens einmal im Jahr betroffen.",
|
|
3110
|
+
RETIREMENT_PREVIEW_CHANGED: "Die Abonnements, die diese Stilllegung betrifft, haben sich seit der Anzeige ge\xE4ndert. Sieh sie dir noch einmal an, bevor du sie ank\xFCndigst.",
|
|
3111
|
+
BUNDLE_RETIREMENT_VERSION_ON_SALE: "Version {version} von {bundleKey} wird noch verkauft. Ver\xF6ffentliche die Version, die sie ersetzt, und lege diese still, sobald ihr Verkauf geendet hat, damit sie nach der Ank\xFCndigung niemand mehr bucht.",
|
|
3112
|
+
BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE: "Version {version} von {bundleKey} wird nicht verkauft. Buchungen k\xF6nnen nicht auf ihr weiterlaufen.",
|
|
3113
|
+
BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE: "Der Ersatz ist eine Version von {replacementBundleKey}, nicht von {bundleKey}. Eine Buchung l\xE4uft auf einer Version ihres eigenen Add-ons weiter.",
|
|
3114
|
+
BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN: "{count} dieser Buchungen laufen neben einem Plan, neben dem Version {version} von {bundleKey} nicht laufen kann. Sie k\xF6nnen nicht auf ihr weiterlaufen.",
|
|
3115
|
+
BUNDLE_RETIREMENT_NOTHING_AFFECTED: "Keine laufende Buchung nutzt Version {version} von {bundleKey}. Es gibt niemanden zu benachrichtigen.",
|
|
3116
|
+
BUNDLE_RETIREMENT_REINSTATE_REFUSED: "Version {version} von {bundleKey} wird stillgelegt, und diese Buchung endet, bevor sie umziehen w\xFCrde. Buchen Sie Version {replacementVersion} ab dem {bookableFrom}, wenn diese Buchung beendet ist.",
|
|
3117
|
+
BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN: "Version {version} von {bundleKey} wird stillgelegt, und diese Buchung endet, bevor sie umziehen w\xFCrde. Version {replacementVersion}, die sie ersetzt, kann neben Ihrem Paket oder einem Paket, zu dem Ihr Abonnement wechselt, nicht laufen.",
|
|
3118
|
+
BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "Version {version} von {bundleKey} wird stillgelegt, und diese Buchung endet, bevor sie umziehen w\xFCrde. Ihr Abonnement endet bis dahin ebenfalls.",
|
|
3119
|
+
BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "{bundleName} zieht am {date} auf seine neue Version um, und Ihr Paket \xE4ndert sich vorher. Der Preis, den ein Wechsel halten w\xFCrde, steht deshalb noch nicht fest. Wechseln Sie, sobald Ihr Paket gewechselt hat, oder lassen Sie es an diesem Tag umziehen.",
|
|
3120
|
+
BUNDLE_VERSION_OFFER_CHANGED: "Das Angebot hat sich seit der Anzeige ge\xE4ndert. Bitte sieh dir das aktuelle an, bevor du wechselst.",
|
|
3121
|
+
RETIREMENT_SWITCH_NOT_PENDING: "F\xFCr Ihre Version steht keine Einstellung mehr bevor. Es gibt nichts, wohin Sie wechseln k\xF6nnten.",
|
|
3122
|
+
RETIREMENT_SWITCH_IN_TRIAL: "Der Wechsel ist m\xF6glich, sobald Ihre Testphase endet.",
|
|
3123
|
+
RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "{bundleName} kann im Paket {planName} nicht laufen und l\xE4uft fr\xFChestens bis {until}. Ist es gek\xFCndigt, ist der Wechsel ab diesem Tag m\xF6glich.",
|
|
3124
|
+
RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW: "{bundleName} l\xE4uft ab dem {from} auf Version {version} weiter, die im Paket {planName} nicht laufen kann. Ist es gek\xFCndigt, ist der Wechsel m\xF6glich.",
|
|
3125
|
+
RETIREMENT_SWITCH_NOT_OPEN: "Dieses Abonnement kann gerade nicht wechseln: Eine \xC4nderung ist geplant, es ist beendet, oder sein Paket gilt f\xFCr einen Sondervertrag.",
|
|
3126
|
+
RETIREMENT_SWITCH_CHANGED: "Die Einstellung hat sich seit der Anzeige ge\xE4ndert. Sehen Sie sie sich noch einmal an, bevor Sie wechseln.",
|
|
1984
3127
|
SUBSCRIPTION_ENDED: "Dieses Abonnement ist beendet. Sein Paket kann nicht mehr gewechselt werden.",
|
|
1985
3128
|
PLAN_LOCKED: "Aktiver Sondervertrag {planName} \u2014 f\xFCr einen Paketwechsel wenden Sie sich bitte an die Vertragsverwaltung.",
|
|
1986
3129
|
QUOTA_OVER_TARGET: "Aktuelle Nutzung {used} \xFCberschreitet das Ziel-Limit {targetMax} ({quotaKey}) im Paket {planName}. Bitte zuerst die Nutzung reduzieren.",
|
|
@@ -1989,11 +3132,12 @@ var ERROR_MESSAGES_DE = {
|
|
|
1989
3132
|
NO_CHANGE: "Zielpaket und Abrechnungsrhythmus entsprechen bereits dem aktuellen Stand.",
|
|
1990
3133
|
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.",
|
|
1991
3134
|
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.",
|
|
1992
|
-
BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "
|
|
3135
|
+
BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "{bundleName} wird j\xE4hrlich abgerechnet und l\xE4uft fr\xFChestens bis {until}; ein monatlich abgerechnetes Paket kann es nicht tragen. Ist es gek\xFCndigt, geht ein Wechsel durch, der an diesem Tag oder sp\xE4ter wirksam wird \u2014 oder behalten Sie den j\xE4hrlichen Rhythmus.",
|
|
3136
|
+
BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} kann im Paket {planName} nicht laufen und l\xE4uft fr\xFChestens bis {until}. Ist es gek\xFCndigt, geht ein Wechsel durch, der an diesem Tag oder sp\xE4ter wirksam wird \u2014 oder w\xE4hlen Sie ein anderes Paket.",
|
|
3137
|
+
BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} l\xE4uft ab dem {from} auf Version {version} weiter, die im Paket {planName} nicht laufen kann. K\xFCndigen Sie es, dann geht der Wechsel durch \u2014 oder w\xE4hlen Sie ein anderes Paket.",
|
|
1993
3138
|
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.",
|
|
1994
3139
|
MINIMUM_TERM_BINDS: "Die Mindestlaufzeit reicht \xFCber das Ende der Periode hinaus \u2014 die K\xFCndigung wird erst zum Ende der Mindestlaufzeit wirksam.",
|
|
1995
3140
|
BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "Das Bundle setzt [{features}] voraus \u2014 weder im Plan noch in den aktiven Bundles vorhanden.",
|
|
1996
|
-
NO_PENDING_PLAN_VERSION: "Es wartet keine Planversion auf eine Best\xE4tigung.",
|
|
1997
3141
|
ONBOARDING_CREATE_FAILED: "Das Konto konnte nicht angelegt werden. Bitte versuche es noch einmal.",
|
|
1998
3142
|
BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "Genau eines von bundleVersionId (Vorschau f\xFCrs Buchen) oder subscriptionBundleId (Vorschau f\xFCrs K\xFCndigen) muss angegeben sein.",
|
|
1999
3143
|
SUBSCRIPTION_PK_MISSING: "Der Adapter hat einen SubscriptionUsageRecord ohne id geliefert. Bitte den Prim\xE4rschl\xFCssel der Subscription durchreichen (siehe SubscriptionUsageRecord.id).",
|
|
@@ -2001,7 +3145,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
2001
3145
|
QUOTA_DIMENSION_UNKNOWN: 'Unbekannte Quota-Dimension "{dimension}".',
|
|
2002
3146
|
// ── promo ──
|
|
2003
3147
|
PROMO_CODE_NOT_FOUND: "Code nicht gefunden",
|
|
2004
|
-
PROMO_CODE_ALREADY_EXISTS: "Diesen Code gibt es bereits.",
|
|
3148
|
+
PROMO_CODE_ALREADY_EXISTS: "Diesen Code gibt es bereits, oder ein gel\xF6schter Code tr\xE4gt ihn; ein gel\xF6schter Code beh\xE4lt seinen Namen.",
|
|
2005
3149
|
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.",
|
|
2006
3150
|
PROMO_CODE_NOT_REDEEMABLE: "Der Code l\xE4sst sich nicht einl\xF6sen: {reason}",
|
|
2007
3151
|
PROMO_CODE_FORMAT_INVALID: 'Der Code darf nur Gro\xDFbuchstaben, Ziffern, "-" und "_" enthalten (4\u201332 Zeichen).',
|
|
@@ -2015,6 +3159,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
2015
3159
|
PROMO_WOULD_PRODUCE_ZERO_INVOICE: "Bei absoluten Betr\xE4gen muss der Rabatt unter dem niedrigsten anwendbaren Planpreis bleiben, oder allowZeroInvoice muss aktiviert sein.",
|
|
2016
3160
|
PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions l\xE4sst sich nicht senken.",
|
|
2017
3161
|
// ── contract ──
|
|
3162
|
+
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "Der Vertrag nennt in {field} {stated} %, der Steueradapter entscheidet {decided} %.",
|
|
2018
3163
|
CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "Aus einem Checkout-Angebot entsteht nur ein einziger Vertrag, und das erst, wenn seine Positionen eingefroren sind.",
|
|
2019
3164
|
CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "Ein Checkout-Angebot braucht eine eingefrorene Plan-Position.",
|
|
2020
3165
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Jede gew\xE4hlte Bundle-Version braucht eine eingefrorene Bundle-Position.",
|
|
@@ -2039,6 +3184,8 @@ var ERROR_MESSAGES_DE = {
|
|
|
2039
3184
|
CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout-Angebot '{offerId}' wurde bereits eingel\xF6st \u2014 '{action}' ist nicht mehr m\xF6glich",
|
|
2040
3185
|
CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' muss eingel\xF6st sein, bevor der Vertrag entsteht",
|
|
2041
3186
|
CHECKOUT_OFFER_CHANGED: "Checkout-Angebot '{offerId}' hat sich w\xE4hrend des Abschlusses ge\xE4ndert. Bitte neu laden.",
|
|
3187
|
+
CHECKOUT_OFFER_CONTRACT_IN_FORCE: "Checkout-Angebot '{offerId}' schlie\xDFt einen ersten Vertrag ab, und dieser Mandant hat bereits einen. Ein laufendes Abo \xE4ndert sich \xFCber seinen Plan und seine Add-ons.",
|
|
3188
|
+
CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION: "Checkout-Angebot '{offerId}' nennt Version {offeredVersion} des Add-ons '{bundleKey}', das dieser Mandant in Version {bookedVersion} gebucht hat (Buchung {subscriptionBundleId}). Die Version wechselt \xFCber das Angebot neben dem Add-on.",
|
|
2042
3189
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' nicht gefunden",
|
|
2043
3190
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "Kein aktiver Abo-Vertrag f\xFCr Mandant {tenantId}",
|
|
2044
3191
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' ist bereits geschlossen",
|
|
@@ -2049,11 +3196,16 @@ var ERROR_MESSAGES_DE = {
|
|
|
2049
3196
|
SUBSCRIBER_NOT_FOUND: "Vertragspartner '{subscriberId}' nicht gefunden",
|
|
2050
3197
|
SUBSCRIBER_LEGAL_NAME_REQUIRED: "Ein Vertragspartner braucht seinen rechtlichen Namen.",
|
|
2051
3198
|
SUBSCRIBER_DETAIL_INVALID: "Das Feld {field} des Vertragspartners ist ung\xFCltig.",
|
|
3199
|
+
SUBSCRIBER_IDENTITY_INCOMPLETE: "Die Rechnungsadresse des Vertragspartners ist noch nicht vollst\xE4ndig: Ein Vertrag nennt ihn nur mit Stra\xDFe und Hausnummer, PLZ, Ort und Land.",
|
|
3200
|
+
SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED: "Eine \xC4nderung, ob der Vertragspartner ein Unternehmen ist, braucht eine Begr\xFCndung.",
|
|
3201
|
+
SUBSCRIBER_VAT_ID_MISSING: "Der Vertragspartner hat keine Umsatzsteuer-Identifikationsnummer, die sich pr\xFCfen lie\xDFe.",
|
|
2052
3202
|
SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} geh\xF6rt zur rechtlichen Identit\xE4t des Vertragspartners und \xE4ndert sich nur als Korrektur mit Begr\xFCndung.",
|
|
2053
3203
|
SUBSCRIBER_CORRECTION_REASON_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t braucht eine Begr\xFCndung.",
|
|
2054
3204
|
SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t muss nennen, wer sie vornimmt.",
|
|
2055
3205
|
SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "Die Korrektur \xE4ndert nichts: Jeder genannte Wert ist bereits erfasst.",
|
|
2056
3206
|
SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "\xDCbernimmt eine andere Rechtsperson, ist das eine \xDCbertragung und keine Korrektur; sie l\xE4sst sich nicht als \xC4nderung erfassen.",
|
|
3207
|
+
SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT: "Ob der Vertragspartner ein Unternehmen ist, geh\xF6rt zu seiner steuerlichen Herkunft und \xE4ndert sich nur als erfasste \xC4nderung, die nennt, wer sie vornimmt.",
|
|
3208
|
+
SUBSCRIBER_CHANGE_ACTOR_REQUIRED: "Eine \xC4nderung der Angaben des Vertragspartners muss nennen, wer sie vornimmt.",
|
|
2057
3209
|
// ── registration ──
|
|
2058
3210
|
PENDING_REGISTRATION_NOT_FOUND: "Diese Registrierung konnten wir nicht finden. Vielleicht ist sie bereits abgeschlossen oder verworfen.",
|
|
2059
3211
|
PENDING_REGISTRATION_EXPIRED: "Diese Registrierung ist abgelaufen. Bitte fange noch einmal von vorn an.",
|
|
@@ -2087,7 +3239,11 @@ var ERROR_MESSAGES_DE = {
|
|
|
2087
3239
|
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "Das Ende eines Wartungsfensters muss nach seinem Beginn liegen.",
|
|
2088
3240
|
MAINTENANCE_WINDOW_END_IN_PAST: "Das Ende eines Wartungsfensters darf nicht in der Vergangenheit liegen.",
|
|
2089
3241
|
MAINTENANCE_TIME_INVALID: "{field} muss Datum und Uhrzeit mit Zeitzone sein, etwa 2026-10-02T22:00+02:00.",
|
|
2090
|
-
MAINTENANCE_MESSAGE_TOO_LONG: "Die Nachricht darf h\xF6chstens {max} Zeichen lang sein."
|
|
3242
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "Die Nachricht darf h\xF6chstens {max} Zeichen lang sein.",
|
|
3243
|
+
// ── tax ──
|
|
3244
|
+
TAX_TREATMENT_NOT_SUPPORTED: "Der Steueradapter {adapter} unterst\xFCtzt diesen Fall nicht: {reason}",
|
|
3245
|
+
TAX_VAT_ID_CHECK_NOT_COMPLETED: "Die Umsatzsteuer-Identifikationsnummer lie\xDF sich gerade nicht pr\xFCfen ({reason}). Bitte sp\xE4ter erneut versuchen.",
|
|
3246
|
+
TAX_VAT_ID_CHECK_NOT_AVAILABLE: "Diese Installation nennt keinen Steueradapter, darum wird keine Umsatzsteuer-Identifikationsnummer gepr\xFCft."
|
|
2091
3247
|
};
|
|
2092
3248
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2093
3249
|
0 && (module.exports = {
|
|
@@ -2095,9 +3251,11 @@ var ERROR_MESSAGES_DE = {
|
|
|
2095
3251
|
AUTH_ERROR_CODES,
|
|
2096
3252
|
BILLING_ERROR_CODES,
|
|
2097
3253
|
BUNDLE_PRICE_LOOKUP_LIMIT,
|
|
3254
|
+
BUNDLE_VERSION_RETIREMENT_CAPABILITY,
|
|
2098
3255
|
CATALOGUE_KEYS,
|
|
2099
3256
|
CATALOG_ERROR_CODES,
|
|
2100
3257
|
CONTRACT_ERROR_CODES,
|
|
3258
|
+
CUSTOM_LIMITS_KEYS,
|
|
2101
3259
|
ERROR_MESSAGES_DE,
|
|
2102
3260
|
ERROR_MESSAGES_EN,
|
|
2103
3261
|
FEATURE_NOT_LICENSED,
|
|
@@ -2109,6 +3267,8 @@ var ERROR_MESSAGES_DE = {
|
|
|
2109
3267
|
MAINTENANCE_STATE_MAX_AGE_SECONDS,
|
|
2110
3268
|
MARKETING_PRIORITY_MAX,
|
|
2111
3269
|
MARKETING_PRIORITY_MIN,
|
|
3270
|
+
NO_SCHEDULED_CHANGE,
|
|
3271
|
+
OPTIONAL_CANONICAL_MODELS,
|
|
2112
3272
|
OTP_RATE_LIMIT_MAX_SENDS,
|
|
2113
3273
|
OTP_RATE_LIMIT_WINDOW_MINUTES,
|
|
2114
3274
|
OTP_TTL_MINUTES,
|
|
@@ -2123,69 +3283,121 @@ var ERROR_MESSAGES_DE = {
|
|
|
2123
3283
|
PROMO_ERROR_CODES,
|
|
2124
3284
|
PaymentCallbackRejectedError,
|
|
2125
3285
|
PersistenceCapabilityError,
|
|
3286
|
+
PersistenceRefusal,
|
|
2126
3287
|
PlatformUserExistsError,
|
|
2127
3288
|
REGISTRATION_ERROR_CODES,
|
|
2128
3289
|
REGISTRATION_RESUME_TTL_MINUTES,
|
|
2129
3290
|
REGISTRATION_STEP_BY_STATUS,
|
|
3291
|
+
RETIREMENT_REMINDER_LEAD_DAYS,
|
|
2130
3292
|
SETTINGS_ERROR_CODES,
|
|
2131
3293
|
SETUP_ERROR_CODES,
|
|
2132
3294
|
SUBSCRIBER_ACCOUNT_CAPABILITY,
|
|
3295
|
+
SUBSCRIBER_ATTENTION_CAPABILITY,
|
|
3296
|
+
SUBSCRIBER_ATTENTION_PAGE_SIZE,
|
|
3297
|
+
SUBSCRIBER_CORRECTION_CAPABILITY,
|
|
2133
3298
|
SUBSCRIBER_ERROR_CODES,
|
|
3299
|
+
SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
|
|
3300
|
+
SUBSCRIBER_STANDING_CAPABILITY,
|
|
3301
|
+
SUBSCRIBER_TAX_ORIGIN_FIELDS,
|
|
3302
|
+
TAX_ERROR_CODES,
|
|
3303
|
+
TAX_TREATMENT_KINDS,
|
|
3304
|
+
VERSION_RETIREMENT_CAPABILITY,
|
|
2134
3305
|
applyPromo,
|
|
2135
3306
|
assertPersistenceCapabilities,
|
|
2136
3307
|
buildActivePlanVersionWhere,
|
|
2137
3308
|
buildActiveVersionWhere,
|
|
2138
3309
|
buildFeatureRequiresIndex,
|
|
2139
3310
|
bundleDraftDefaults,
|
|
3311
|
+
bundleKeyTaken,
|
|
2140
3312
|
bundleStemDefaults,
|
|
2141
3313
|
canonicalJson,
|
|
3314
|
+
canonicalVatId,
|
|
3315
|
+
catalogDraftExists,
|
|
3316
|
+
catalogVersionAlreadyPublished,
|
|
3317
|
+
catalogVersionGone,
|
|
2142
3318
|
classifyBundleVersionDiff,
|
|
2143
3319
|
classifyIssuerChange,
|
|
2144
3320
|
classifyPlanDiff,
|
|
3321
|
+
classifyVersionOffer,
|
|
2145
3322
|
collectUnsatisfiedRequires,
|
|
3323
|
+
computeIncludedVat,
|
|
3324
|
+
concurrencyGate,
|
|
3325
|
+
concurrencyGateOf,
|
|
2146
3326
|
contractPartiesOf,
|
|
2147
3327
|
coverageExcludingSelf,
|
|
3328
|
+
customLimitsReader,
|
|
2148
3329
|
definedFields,
|
|
2149
3330
|
diffSettings,
|
|
2150
3331
|
formatCustomerNumber,
|
|
2151
3332
|
formatErrorMessage,
|
|
3333
|
+
grossFromNet,
|
|
2152
3334
|
identityCorrectionDelta,
|
|
2153
3335
|
isBundleRedundant,
|
|
2154
3336
|
isForeignPaymentMethodReferenceError,
|
|
2155
3337
|
isMaintenanceAnnouncementLapsed,
|
|
2156
3338
|
isMaintenanceOverrun,
|
|
2157
3339
|
isPaymentCallbackRejectedError,
|
|
3340
|
+
isPersistenceRefusal,
|
|
2158
3341
|
isPlatformUserExistsError,
|
|
3342
|
+
isVersionActiveAt,
|
|
2159
3343
|
isVersionEditable,
|
|
2160
3344
|
issuerIdentityOf,
|
|
2161
3345
|
keepOneRecommended,
|
|
3346
|
+
keepsVatIdCheck,
|
|
2162
3347
|
maintenanceRetryAfterSeconds,
|
|
2163
3348
|
maintenanceStatusOf,
|
|
2164
3349
|
maintenanceWindowStatusOf,
|
|
2165
3350
|
maintenanceWindowViewOf,
|
|
3351
|
+
marketingProjectionTaken,
|
|
3352
|
+
membersLeftOut,
|
|
2166
3353
|
missingRequiresFor,
|
|
2167
3354
|
movedIdentityFields,
|
|
3355
|
+
netFromGross,
|
|
3356
|
+
noActivePlanVersion,
|
|
2168
3357
|
parseZonedInstant,
|
|
3358
|
+
percentOf,
|
|
2169
3359
|
pickActivePromo,
|
|
2170
3360
|
planCatalogSettingsOf,
|
|
3361
|
+
planKeyTaken,
|
|
3362
|
+
planNotInCatalog,
|
|
2171
3363
|
previousUtcDay,
|
|
3364
|
+
promoCodeTaken,
|
|
2172
3365
|
promoStatus,
|
|
2173
3366
|
promotionOnPrice,
|
|
3367
|
+
prorate,
|
|
3368
|
+
readCustomLimits,
|
|
2174
3369
|
readQuotaRecord,
|
|
2175
3370
|
readQuotaValue,
|
|
2176
3371
|
recordedIssuerIdentity,
|
|
2177
3372
|
refuseForeignPaymentMethodReference,
|
|
2178
3373
|
resolveBundleAvailability,
|
|
2179
3374
|
resolveErrorMessage,
|
|
3375
|
+
retirementCostsTheSubscription,
|
|
3376
|
+
retirementReminderDueAt,
|
|
3377
|
+
retirementReminderIsDue,
|
|
3378
|
+
retirementSwitchTerms,
|
|
3379
|
+
roundToCents,
|
|
2180
3380
|
sameLegalIdentity,
|
|
3381
|
+
scheduledChangeAfterWrite,
|
|
2181
3382
|
selectChargeableBundles,
|
|
2182
3383
|
settingsSubtreeOf,
|
|
2183
3384
|
startOfUtcDay,
|
|
2184
3385
|
subscriberChargeColumns,
|
|
2185
3386
|
subscriberFromRegistration,
|
|
2186
3387
|
subscriberPaymentMethodColumns,
|
|
3388
|
+
subscriptionBundleAlreadyCancelled,
|
|
3389
|
+
subscriptionBundleGone,
|
|
3390
|
+
subscriptionContractGone,
|
|
3391
|
+
subscriptionGone,
|
|
3392
|
+
sumToCents,
|
|
3393
|
+
taxOriginOf,
|
|
3394
|
+
taxOriginWrite,
|
|
3395
|
+
taxTreatmentToJson,
|
|
2187
3396
|
toBundleStemRow,
|
|
3397
|
+
toBundleVersionRetirementRecord,
|
|
3398
|
+
toCents,
|
|
2188
3399
|
toContractLineItemRecord,
|
|
3400
|
+
toDecimalString,
|
|
2189
3401
|
toMaintenanceWindowRecord,
|
|
2190
3402
|
toPlanRow,
|
|
2191
3403
|
toPlanVersionRow,
|
|
@@ -2194,6 +3406,16 @@ var ERROR_MESSAGES_DE = {
|
|
|
2194
3406
|
toSubscriberCorrectionRecord,
|
|
2195
3407
|
toSubscriberPaymentMethodRecord,
|
|
2196
3408
|
toSubscriberRecord,
|
|
3409
|
+
toSubscriberTaxOriginChangeRecord,
|
|
3410
|
+
toSubscriberVatIdCheckRecord,
|
|
2197
3411
|
toSubscriptionBundleRecord,
|
|
2198
|
-
toSubscriptionContractRecord
|
|
3412
|
+
toSubscriptionContractRecord,
|
|
3413
|
+
toSubscriptionNoticeRecord,
|
|
3414
|
+
toTaxOriginValues,
|
|
3415
|
+
toTaxTreatment,
|
|
3416
|
+
toVersionRetirementRecord,
|
|
3417
|
+
vatIdCheckFromStore,
|
|
3418
|
+
versionOnSale,
|
|
3419
|
+
versionOnSaleOrNext,
|
|
3420
|
+
versionSale
|
|
2199
3421
|
});
|