@saasicat/core 1.0.0-rc.21 → 1.0.0-rc.23
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/dist/.build-stamp +1 -1
- package/dist/index.cjs +291 -16
- package/dist/index.d.cts +563 -6
- package/dist/index.d.ts +563 -6
- package/dist/index.js +275 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -283,6 +283,100 @@ function appendNullableDecimalChange(out, field, oldValue, newValue, polarity) {
|
|
|
283
283
|
appendDecimalChange(out, field, oldValue, newValue, polarity);
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
// src/maintenance-window.types.ts
|
|
287
|
+
var MAINTENANCE_MESSAGE_MAX_LENGTH = 500;
|
|
288
|
+
var MAINTENANCE_STATE_MAX_AGE_SECONDS = 5;
|
|
289
|
+
var MAINTENANCE_CAPABILITY = "maintenance.manage";
|
|
290
|
+
|
|
291
|
+
// src/maintenance-window-mapping.ts
|
|
292
|
+
function toMaintenanceWindowRecord(row) {
|
|
293
|
+
return {
|
|
294
|
+
id: row.id,
|
|
295
|
+
startsAt: row.startsAt,
|
|
296
|
+
endsAt: row.endsAt,
|
|
297
|
+
message: row.message,
|
|
298
|
+
createdAt: row.createdAt,
|
|
299
|
+
createdBy: row.createdBy,
|
|
300
|
+
lockedAt: row.lockedAt,
|
|
301
|
+
lockedBy: row.lockedBy,
|
|
302
|
+
endedAt: row.endedAt,
|
|
303
|
+
endedBy: row.endedBy
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/maintenance-window-views.ts
|
|
308
|
+
var RETRY_AFTER_WITHOUT_END_SECONDS = 60;
|
|
309
|
+
var iso = (date) => date ? date.toISOString() : null;
|
|
310
|
+
function maintenanceWindowStatusOf(window) {
|
|
311
|
+
if (window.endedAt) return window.lockedAt ? "ended" : "cancelled";
|
|
312
|
+
return window.lockedAt ? "locked" : "announced";
|
|
313
|
+
}
|
|
314
|
+
function isMaintenanceOverrun(window, now) {
|
|
315
|
+
return maintenanceWindowStatusOf(window) === "locked" && window.endsAt !== null && window.endsAt.getTime() <= now.getTime();
|
|
316
|
+
}
|
|
317
|
+
function isMaintenanceAnnouncementLapsed(window, now) {
|
|
318
|
+
return maintenanceWindowStatusOf(window) === "announced" && window.endsAt !== null && window.endsAt.getTime() <= now.getTime();
|
|
319
|
+
}
|
|
320
|
+
function maintenanceStatusOf(open, now) {
|
|
321
|
+
if (!open) return { state: "none" };
|
|
322
|
+
const status = maintenanceWindowStatusOf(open);
|
|
323
|
+
if (status === "locked" && open.lockedAt) {
|
|
324
|
+
return {
|
|
325
|
+
state: "locked",
|
|
326
|
+
lockedAt: open.lockedAt.toISOString(),
|
|
327
|
+
endsAt: iso(open.endsAt),
|
|
328
|
+
message: open.message,
|
|
329
|
+
overrun: isMaintenanceOverrun(open, now)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (status === "announced" && open.startsAt && open.endsAt && !isMaintenanceAnnouncementLapsed(open, now)) {
|
|
333
|
+
return {
|
|
334
|
+
state: "announced",
|
|
335
|
+
startsAt: open.startsAt.toISOString(),
|
|
336
|
+
endsAt: open.endsAt.toISOString(),
|
|
337
|
+
message: open.message
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return { state: "none" };
|
|
341
|
+
}
|
|
342
|
+
function maintenanceWindowViewOf(window, now) {
|
|
343
|
+
return {
|
|
344
|
+
id: window.id,
|
|
345
|
+
status: maintenanceWindowStatusOf(window),
|
|
346
|
+
startsAt: iso(window.startsAt),
|
|
347
|
+
endsAt: iso(window.endsAt),
|
|
348
|
+
message: window.message,
|
|
349
|
+
createdAt: window.createdAt.toISOString(),
|
|
350
|
+
createdBy: window.createdBy,
|
|
351
|
+
lockedAt: iso(window.lockedAt),
|
|
352
|
+
lockedBy: window.lockedBy,
|
|
353
|
+
endedAt: iso(window.endedAt),
|
|
354
|
+
endedBy: window.endedBy,
|
|
355
|
+
overrun: isMaintenanceOverrun(window, now),
|
|
356
|
+
lapsed: isMaintenanceAnnouncementLapsed(window, now)
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function maintenanceRetryAfterSeconds(window, now) {
|
|
360
|
+
if (!window.endsAt || window.endsAt.getTime() <= now.getTime()) {
|
|
361
|
+
return RETRY_AFTER_WITHOUT_END_SECONDS;
|
|
362
|
+
}
|
|
363
|
+
return Math.max(1, Math.ceil((window.endsAt.getTime() - now.getTime()) / 1e3));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/zoned-instant.ts
|
|
367
|
+
function endsInOffset(text) {
|
|
368
|
+
const sign = text.charAt(text.length - 6);
|
|
369
|
+
return (sign === "+" || sign === "-") && text.charAt(text.length - 3) === ":";
|
|
370
|
+
}
|
|
371
|
+
function parseZonedInstant(text) {
|
|
372
|
+
const value = text.trim();
|
|
373
|
+
if (!value.includes("T")) return null;
|
|
374
|
+
const zoned = value.endsWith("Z") || value.endsWith("z") || endsInOffset(value);
|
|
375
|
+
if (!zoned) return null;
|
|
376
|
+
const moment = new Date(value);
|
|
377
|
+
return Number.isNaN(moment.getTime()) ? null : moment;
|
|
378
|
+
}
|
|
379
|
+
|
|
286
380
|
// src/settings-subtree.ts
|
|
287
381
|
var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
|
|
288
382
|
"schemaVersion",
|
|
@@ -613,7 +707,15 @@ var CONTRACT_ERROR_CODES = {
|
|
|
613
707
|
CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED",
|
|
614
708
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND",
|
|
615
709
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT",
|
|
616
|
-
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED"
|
|
710
|
+
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED",
|
|
711
|
+
/**
|
|
712
|
+
* A successor would run beside another contract of the tenant: the
|
|
713
|
+
* contract in force moved while it was being written — another successor
|
|
714
|
+
* took its place, or a cancellation capped it — and kept moving on a
|
|
715
|
+
* second attempt, or a contract begins after the moment the successor
|
|
716
|
+
* would take effect. Nothing was written.
|
|
717
|
+
*/
|
|
718
|
+
SUBSCRIPTION_CONTRACT_CHANGED: "SUBSCRIPTION_CONTRACT_CHANGED"
|
|
617
719
|
};
|
|
618
720
|
var SUBSCRIBER_ERROR_CODES = {
|
|
619
721
|
/**
|
|
@@ -711,12 +813,37 @@ var PAYMENT_ERROR_CODES = {
|
|
|
711
813
|
* A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
|
|
712
814
|
* does not name. Carries `field`.
|
|
713
815
|
*/
|
|
714
|
-
PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED"
|
|
816
|
+
PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED",
|
|
817
|
+
/**
|
|
818
|
+
* The gateway failed — unreachable, refusing the account's keys, or
|
|
819
|
+
* answering with an error — while opening its form or reading a callback
|
|
820
|
+
* back. Answered with 502; what the gateway answered stays in the server
|
|
821
|
+
* log.
|
|
822
|
+
*/
|
|
823
|
+
PAYMENT_GATEWAY_FAILED: "PAYMENT_GATEWAY_FAILED"
|
|
715
824
|
};
|
|
716
825
|
var SETTINGS_ERROR_CODES = {
|
|
717
826
|
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
718
827
|
SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND"
|
|
719
828
|
};
|
|
829
|
+
var MAINTENANCE_ERROR_CODES = {
|
|
830
|
+
/** The application is locked for maintenance; the body carries the window. */
|
|
831
|
+
MAINTENANCE: "MAINTENANCE",
|
|
832
|
+
/** A window is already open: move or cancel it before announcing another. */
|
|
833
|
+
MAINTENANCE_WINDOW_ALREADY_OPEN: "MAINTENANCE_WINDOW_ALREADY_OPEN",
|
|
834
|
+
/** No open window has this id — it ended, or it never existed. */
|
|
835
|
+
MAINTENANCE_WINDOW_NOT_OPEN: "MAINTENANCE_WINDOW_NOT_OPEN",
|
|
836
|
+
/** The window is locked: its start is history, and unlocking is what ends it. */
|
|
837
|
+
MAINTENANCE_WINDOW_LOCKED: "MAINTENANCE_WINDOW_LOCKED",
|
|
838
|
+
/** The announced end is not after the announced start. */
|
|
839
|
+
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "MAINTENANCE_WINDOW_END_NOT_AFTER_START",
|
|
840
|
+
/** The announced end has already passed. */
|
|
841
|
+
MAINTENANCE_WINDOW_END_IN_PAST: "MAINTENANCE_WINDOW_END_IN_PAST",
|
|
842
|
+
/** A time without its zone, or not a date and time at all; `field` names which. */
|
|
843
|
+
MAINTENANCE_TIME_INVALID: "MAINTENANCE_TIME_INVALID",
|
|
844
|
+
/** The message is longer than `MAINTENANCE_MESSAGE_MAX_LENGTH`. */
|
|
845
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "MAINTENANCE_MESSAGE_TOO_LONG"
|
|
846
|
+
};
|
|
720
847
|
var PLATFORM_ERROR_CODES = {
|
|
721
848
|
...SETUP_ERROR_CODES,
|
|
722
849
|
...AUTH_ERROR_CODES,
|
|
@@ -727,7 +854,8 @@ var PLATFORM_ERROR_CODES = {
|
|
|
727
854
|
...SUBSCRIBER_ERROR_CODES,
|
|
728
855
|
...REGISTRATION_ERROR_CODES,
|
|
729
856
|
...PAYMENT_ERROR_CODES,
|
|
730
|
-
...SETTINGS_ERROR_CODES
|
|
857
|
+
...SETTINGS_ERROR_CODES,
|
|
858
|
+
...MAINTENANCE_ERROR_CODES
|
|
731
859
|
};
|
|
732
860
|
|
|
733
861
|
// src/errors.ts
|
|
@@ -977,6 +1105,83 @@ function textOrNull(value) {
|
|
|
977
1105
|
return trimmed === "" ? null : trimmed;
|
|
978
1106
|
}
|
|
979
1107
|
|
|
1108
|
+
// src/closed-value.ts
|
|
1109
|
+
function oneOf(allowed, value, source) {
|
|
1110
|
+
const match = allowed.find((entry) => entry === value);
|
|
1111
|
+
if (match === void 0) {
|
|
1112
|
+
throw new Error(
|
|
1113
|
+
`${source.table} row '${source.id}' holds ${source.column} '${value}', which is none of ${allowed.join(", ")}.`
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
return match;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/subscriber-ledger-mapping.ts
|
|
1120
|
+
var ORIGINS = {
|
|
1121
|
+
activation: true,
|
|
1122
|
+
renewal: true,
|
|
1123
|
+
planChange: true,
|
|
1124
|
+
bundleBooking: true,
|
|
1125
|
+
credit: true,
|
|
1126
|
+
correction: true
|
|
1127
|
+
};
|
|
1128
|
+
var SOURCES = {
|
|
1129
|
+
plan: true,
|
|
1130
|
+
bundle: true,
|
|
1131
|
+
discount: true
|
|
1132
|
+
};
|
|
1133
|
+
var TABLE = "subscriber_ledger_entries";
|
|
1134
|
+
var CENT_TOLERANCE = 1e-6;
|
|
1135
|
+
function toSubscriberChargeRecord(row) {
|
|
1136
|
+
const where = (column) => ({ table: TABLE, column, id: row.id });
|
|
1137
|
+
return {
|
|
1138
|
+
id: row.id,
|
|
1139
|
+
subscriberId: row.subscriberId,
|
|
1140
|
+
tenantId: row.tenantId,
|
|
1141
|
+
subscriptionId: row.subscriptionId,
|
|
1142
|
+
contractId: row.contractId,
|
|
1143
|
+
contractLineItemId: row.contractLineItemId,
|
|
1144
|
+
origin: oneOf(keysOf(ORIGINS), row.origin, where("origin")),
|
|
1145
|
+
source: oneOf(keysOf(SOURCES), row.source, where("source")),
|
|
1146
|
+
sourceRef: row.sourceRef,
|
|
1147
|
+
periodStart: row.periodStart,
|
|
1148
|
+
periodEnd: row.periodEnd,
|
|
1149
|
+
currency: row.currency,
|
|
1150
|
+
amountNet: Number(row.amountNet),
|
|
1151
|
+
bookedAt: row.bookedAt,
|
|
1152
|
+
createdAt: row.createdAt
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
function subscriberChargeColumns(charge) {
|
|
1156
|
+
const cents = charge.amountNet * 100;
|
|
1157
|
+
if (!Number.isFinite(cents) || Math.abs(cents - Math.round(cents)) > CENT_TOLERANCE) {
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`A charge of ${charge.amountNet} is not a whole number of cents; round it once where it is derived, before it is written.`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
return {
|
|
1163
|
+
subscriberId: charge.subscriberId,
|
|
1164
|
+
tenantId: charge.tenantId,
|
|
1165
|
+
subscriptionId: charge.subscriptionId,
|
|
1166
|
+
contractId: charge.contractId,
|
|
1167
|
+
contractLineItemId: charge.contractLineItemId,
|
|
1168
|
+
origin: charge.origin,
|
|
1169
|
+
source: charge.source,
|
|
1170
|
+
sourceRef: charge.sourceRef,
|
|
1171
|
+
periodStart: charge.periodStart,
|
|
1172
|
+
periodEnd: charge.periodEnd,
|
|
1173
|
+
currency: charge.currency,
|
|
1174
|
+
amountNet: charge.amountNet.toFixed(2),
|
|
1175
|
+
bookedAt: charge.bookedAt
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function keysOf(record) {
|
|
1179
|
+
return Object.keys(record);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/subscriber-ledger.types.ts
|
|
1183
|
+
var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
|
|
1184
|
+
|
|
980
1185
|
// src/subscriber-mapping.ts
|
|
981
1186
|
function formatCustomerNumber(prefix, sequence) {
|
|
982
1187
|
return `${prefix}${sequence}`;
|
|
@@ -1092,6 +1297,7 @@ function toIdentityValues(value) {
|
|
|
1092
1297
|
// src/subscriber-payment-method-mapping.ts
|
|
1093
1298
|
var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
|
|
1094
1299
|
var STATUSES = ["ACTIVE", "REPLACED"];
|
|
1300
|
+
var TABLE2 = "subscriber_payment_methods";
|
|
1095
1301
|
function toSubscriberPaymentMethodRecord(row) {
|
|
1096
1302
|
return {
|
|
1097
1303
|
id: row.id,
|
|
@@ -1100,7 +1306,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
1100
1306
|
provider: row.provider,
|
|
1101
1307
|
customerRef: row.customerRef,
|
|
1102
1308
|
paymentMethodRef: row.paymentMethodRef,
|
|
1103
|
-
type: oneOf(PAYMENT_METHOD_TYPES, row.type, "type", row.id),
|
|
1309
|
+
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE2, column: "type", id: row.id }),
|
|
1104
1310
|
brand: row.brand,
|
|
1105
1311
|
last4: row.last4,
|
|
1106
1312
|
expiryMonth: row.expiryMonth,
|
|
@@ -1108,7 +1314,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
1108
1314
|
country: row.country,
|
|
1109
1315
|
bankCode: row.bankCode,
|
|
1110
1316
|
mandateReference: row.mandateReference,
|
|
1111
|
-
status: oneOf(STATUSES, row.status, "status", row.id),
|
|
1317
|
+
status: oneOf(STATUSES, row.status, { table: TABLE2, column: "status", id: row.id }),
|
|
1112
1318
|
confirmedAt: row.confirmedAt,
|
|
1113
1319
|
replacedAt: row.replacedAt,
|
|
1114
1320
|
createdAt: row.createdAt
|
|
@@ -1132,15 +1338,6 @@ function subscriberPaymentMethodColumns(data) {
|
|
|
1132
1338
|
confirmedAt: data.confirmedAt
|
|
1133
1339
|
};
|
|
1134
1340
|
}
|
|
1135
|
-
function oneOf(allowed, value, column, id) {
|
|
1136
|
-
const match = allowed.find((entry) => entry === value);
|
|
1137
|
-
if (match === void 0) {
|
|
1138
|
-
throw new Error(
|
|
1139
|
-
`subscriber_payment_methods row '${id}' holds ${column} '${value}', which is none of ${allowed.join(", ")}.`
|
|
1140
|
-
);
|
|
1141
|
-
}
|
|
1142
|
-
return match;
|
|
1143
|
-
}
|
|
1144
1341
|
var FOREIGN_PAYMENT_METHOD_REFERENCE = "FOREIGN_PAYMENT_METHOD_REFERENCE";
|
|
1145
1342
|
var ForeignPaymentMethodReferenceError = class extends Error {
|
|
1146
1343
|
constructor(gatewayAccount, paymentMethodRef) {
|
|
@@ -1166,6 +1363,30 @@ function refuseForeignPaymentMethodReference(recorded, subscriberId) {
|
|
|
1166
1363
|
);
|
|
1167
1364
|
}
|
|
1168
1365
|
|
|
1366
|
+
// src/subscription-bundle-mapping.ts
|
|
1367
|
+
var BILLING_CYCLES = ["MONTHLY", "YEARLY"];
|
|
1368
|
+
function toSubscriptionBundleRecord(row) {
|
|
1369
|
+
const billingCycle = row.billingCycle ?? null;
|
|
1370
|
+
return {
|
|
1371
|
+
id: row.id,
|
|
1372
|
+
subscriptionId: row.subscriptionId,
|
|
1373
|
+
bundleVersionId: row.bundleVersionId,
|
|
1374
|
+
startedAt: row.startedAt,
|
|
1375
|
+
minimumTermEndsAt: row.minimumTermEndsAt,
|
|
1376
|
+
billingCycle: billingCycle === null ? null : oneOf(BILLING_CYCLES, billingCycle, {
|
|
1377
|
+
table: "subscription_bundles",
|
|
1378
|
+
column: "billingCycle",
|
|
1379
|
+
id: row.id
|
|
1380
|
+
}),
|
|
1381
|
+
currentPeriodStart: row.currentPeriodStart ?? null,
|
|
1382
|
+
currentPeriodEnd: row.currentPeriodEnd ?? null,
|
|
1383
|
+
canceledAt: row.canceledAt,
|
|
1384
|
+
canceledEffectiveAt: row.canceledEffectiveAt,
|
|
1385
|
+
createdAt: row.createdAt,
|
|
1386
|
+
updatedAt: row.updatedAt
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1169
1390
|
// src/subscription-contract-mapping.ts
|
|
1170
1391
|
function toSubscriptionContractRecord(row, lineItems) {
|
|
1171
1392
|
return {
|
|
@@ -1470,6 +1691,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
1470
1691
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
|
|
1471
1692
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
|
|
1472
1693
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' is already closed",
|
|
1694
|
+
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.",
|
|
1473
1695
|
// ── subscriber ──
|
|
1474
1696
|
SUBSCRIBER_REQUIRED: "Tenant '{tenantId}' has no subscriber. Nothing is agreed or charged without the party to it: create the tenant's subscriber first.",
|
|
1475
1697
|
SUBSCRIBER_ALREADY_EXISTS: "Tenant '{tenantId}' already has a subscriber.",
|
|
@@ -1501,10 +1723,20 @@ var ERROR_MESSAGES_EN = {
|
|
|
1501
1723
|
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "No payment gateway account is configured under '{account}'.",
|
|
1502
1724
|
PAYMENT_CALLBACK_REJECTED: "The payment callback could not be verified.",
|
|
1503
1725
|
PAYMENT_RETURN_URL_NOT_ALLOWED: "The {field} leads to a site this installation does not return to.",
|
|
1726
|
+
PAYMENT_GATEWAY_FAILED: "The payment provider did not answer as expected. Please try again in a few minutes.",
|
|
1504
1727
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
1505
1728
|
[FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan.",
|
|
1506
1729
|
// ── settings ──
|
|
1507
|
-
SETTINGS_CHANGE_NOT_FOUND: "No recorded settings change has this id."
|
|
1730
|
+
SETTINGS_CHANGE_NOT_FOUND: "No recorded settings change has this id.",
|
|
1731
|
+
// ── maintenance ──
|
|
1732
|
+
MAINTENANCE: "The application is under maintenance.",
|
|
1733
|
+
MAINTENANCE_WINDOW_ALREADY_OPEN: "A maintenance window is already open. Move or cancel it before announcing another.",
|
|
1734
|
+
MAINTENANCE_WINDOW_NOT_OPEN: "No open maintenance window has this id.",
|
|
1735
|
+
MAINTENANCE_WINDOW_LOCKED: "This maintenance window is locked. Its start cannot move, and unlocking is what ends it.",
|
|
1736
|
+
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "The end of a maintenance window has to be after its start.",
|
|
1737
|
+
MAINTENANCE_WINDOW_END_IN_PAST: "The end of a maintenance window cannot be in the past.",
|
|
1738
|
+
MAINTENANCE_TIME_INVALID: "The {field} has to be a date and time with its zone, such as 2026-10-02T22:00+02:00.",
|
|
1739
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "The message may be at most {max} characters long."
|
|
1508
1740
|
};
|
|
1509
1741
|
function formatErrorMessage(template, params = {}) {
|
|
1510
1742
|
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
@@ -1680,6 +1912,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1680
1912
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' nicht gefunden",
|
|
1681
1913
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "Kein aktiver Abo-Vertrag f\xFCr Mandant {tenantId}",
|
|
1682
1914
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' ist bereits geschlossen",
|
|
1915
|
+
SUBSCRIPTION_CONTRACT_CHANGED: "Die Vertr\xE4ge von Mandant '{tenantId}' haben sich ge\xE4ndert, w\xE4hrend ein Nachfolger geschrieben wurde, oder einer beginnt nach dem Zeitpunkt, ab dem er g\xE4lte, sodass er neben einem anderen liefe. Es wurde nichts geschrieben.",
|
|
1683
1916
|
// ── subscriber ──
|
|
1684
1917
|
SUBSCRIBER_REQUIRED: "Mandant '{tenantId}' hat keinen Vertragspartner. Ohne ihn wird nichts vereinbart oder berechnet: Zuerst muss der Vertragspartner des Mandanten angelegt werden.",
|
|
1685
1918
|
SUBSCRIBER_ALREADY_EXISTS: "Mandant '{tenantId}' hat bereits einen Vertragspartner.",
|
|
@@ -1711,10 +1944,20 @@ var ERROR_MESSAGES_DE = {
|
|
|
1711
1944
|
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "Unter '{account}' ist kein Konto eines Zahlungsanbieters eingerichtet.",
|
|
1712
1945
|
PAYMENT_CALLBACK_REJECTED: "Die R\xFCckmeldung des Zahlungsanbieters lie\xDF sich nicht pr\xFCfen.",
|
|
1713
1946
|
PAYMENT_RETURN_URL_NOT_ALLOWED: "Die {field} f\xFChrt auf eine Seite, zu der diese Installation nicht zur\xFCckleitet.",
|
|
1947
|
+
PAYMENT_GATEWAY_FAILED: "Der Zahlungsanbieter hat nicht wie erwartet geantwortet. Bitte versuche es in ein paar Minuten noch einmal.",
|
|
1714
1948
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
1715
1949
|
[FEATURE_NOT_LICENSED]: "Das Feature {featureKeys} ist im aktuellen Plan nicht enthalten.",
|
|
1716
1950
|
// ── settings ──
|
|
1717
|
-
SETTINGS_CHANGE_NOT_FOUND: "Keine aufgezeichnete Einstellungs\xE4nderung hat diese ID."
|
|
1951
|
+
SETTINGS_CHANGE_NOT_FOUND: "Keine aufgezeichnete Einstellungs\xE4nderung hat diese ID.",
|
|
1952
|
+
// ── maintenance ──
|
|
1953
|
+
MAINTENANCE: "Die Anwendung wird gerade gewartet.",
|
|
1954
|
+
MAINTENANCE_WINDOW_ALREADY_OPEN: "Es ist bereits ein Wartungsfenster offen. Verschiebe oder streiche es, bevor du ein weiteres ank\xFCndigst.",
|
|
1955
|
+
MAINTENANCE_WINDOW_NOT_OPEN: "Kein offenes Wartungsfenster hat diese ID.",
|
|
1956
|
+
MAINTENANCE_WINDOW_LOCKED: "Dieses Wartungsfenster ist gesperrt. Sein Beginn l\xE4sst sich nicht mehr verschieben, und beendet wird es durch Entsperren.",
|
|
1957
|
+
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "Das Ende eines Wartungsfensters muss nach seinem Beginn liegen.",
|
|
1958
|
+
MAINTENANCE_WINDOW_END_IN_PAST: "Das Ende eines Wartungsfensters darf nicht in der Vergangenheit liegen.",
|
|
1959
|
+
MAINTENANCE_TIME_INVALID: "{field} muss Datum und Uhrzeit mit Zeitzone sein, etwa 2026-10-02T22:00+02:00.",
|
|
1960
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "Die Nachricht darf h\xF6chstens {max} Zeichen lang sein."
|
|
1718
1961
|
};
|
|
1719
1962
|
export {
|
|
1720
1963
|
ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES,
|
|
@@ -1729,6 +1972,10 @@ export {
|
|
|
1729
1972
|
FEATURE_NOT_LICENSED,
|
|
1730
1973
|
ForeignPaymentMethodReferenceError,
|
|
1731
1974
|
LEGAL_IDENTITY_FIELDS,
|
|
1975
|
+
MAINTENANCE_CAPABILITY,
|
|
1976
|
+
MAINTENANCE_ERROR_CODES,
|
|
1977
|
+
MAINTENANCE_MESSAGE_MAX_LENGTH,
|
|
1978
|
+
MAINTENANCE_STATE_MAX_AGE_SECONDS,
|
|
1732
1979
|
MARKETING_PRIORITY_MAX,
|
|
1733
1980
|
MARKETING_PRIORITY_MIN,
|
|
1734
1981
|
OTP_RATE_LIMIT_MAX_SENDS,
|
|
@@ -1751,6 +1998,7 @@ export {
|
|
|
1751
1998
|
REGISTRATION_STEP_BY_STATUS,
|
|
1752
1999
|
SETTINGS_ERROR_CODES,
|
|
1753
2000
|
SETUP_ERROR_CODES,
|
|
2001
|
+
SUBSCRIBER_ACCOUNT_CAPABILITY,
|
|
1754
2002
|
SUBSCRIBER_ERROR_CODES,
|
|
1755
2003
|
applyPromo,
|
|
1756
2004
|
assertPersistenceCapabilities,
|
|
@@ -1773,13 +2021,20 @@ export {
|
|
|
1773
2021
|
identityCorrectionDelta,
|
|
1774
2022
|
isBundleRedundant,
|
|
1775
2023
|
isForeignPaymentMethodReferenceError,
|
|
2024
|
+
isMaintenanceAnnouncementLapsed,
|
|
2025
|
+
isMaintenanceOverrun,
|
|
1776
2026
|
isPaymentCallbackRejectedError,
|
|
1777
2027
|
isPlatformUserExistsError,
|
|
1778
2028
|
isVersionEditable,
|
|
1779
2029
|
issuerIdentityOf,
|
|
1780
2030
|
keepOneRecommended,
|
|
2031
|
+
maintenanceRetryAfterSeconds,
|
|
2032
|
+
maintenanceStatusOf,
|
|
2033
|
+
maintenanceWindowStatusOf,
|
|
2034
|
+
maintenanceWindowViewOf,
|
|
1781
2035
|
missingRequiresFor,
|
|
1782
2036
|
movedIdentityFields,
|
|
2037
|
+
parseZonedInstant,
|
|
1783
2038
|
pickActivePromo,
|
|
1784
2039
|
planCatalogSettingsOf,
|
|
1785
2040
|
previousUtcDay,
|
|
@@ -1795,15 +2050,19 @@ export {
|
|
|
1795
2050
|
selectChargeableBundles,
|
|
1796
2051
|
settingsSubtreeOf,
|
|
1797
2052
|
startOfUtcDay,
|
|
2053
|
+
subscriberChargeColumns,
|
|
1798
2054
|
subscriberFromRegistration,
|
|
1799
2055
|
subscriberPaymentMethodColumns,
|
|
1800
2056
|
toBundleStemRow,
|
|
1801
2057
|
toContractLineItemRecord,
|
|
2058
|
+
toMaintenanceWindowRecord,
|
|
1802
2059
|
toPlanRow,
|
|
1803
2060
|
toPlanVersionRow,
|
|
1804
2061
|
toRunningContractIssuer,
|
|
2062
|
+
toSubscriberChargeRecord,
|
|
1805
2063
|
toSubscriberCorrectionRecord,
|
|
1806
2064
|
toSubscriberPaymentMethodRecord,
|
|
1807
2065
|
toSubscriberRecord,
|
|
2066
|
+
toSubscriptionBundleRecord,
|
|
1808
2067
|
toSubscriptionContractRecord
|
|
1809
2068
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/core",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.23",
|
|
4
4
|
"description": "TypeScript contracts for SaaSiCat — capabilities, catalogs, subscriptions, entitlements, admin manifests, persistence ports — and the pure domain logic both server and browser run: error catalogues, plan-diff classification, promo arithmetic, feature requirements.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|