@saasicat/core 1.0.0-rc.20 → 1.0.0-rc.22
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 +192 -17
- package/dist/index.d.cts +581 -16
- package/dist/index.d.ts +581 -16
- package/dist/index.js +184 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -87,27 +87,33 @@ function pickActivePromo(promotions, targetKey, locale, cycle, today = /* @__PUR
|
|
|
87
87
|
return matches[0] ?? null;
|
|
88
88
|
}
|
|
89
89
|
function applyPromo(promo, basePrice) {
|
|
90
|
+
const result = boundedPromo(promo, basePrice);
|
|
91
|
+
return result && result.discounted < result.original ? result : null;
|
|
92
|
+
}
|
|
93
|
+
function boundedPromo(promo, basePrice) {
|
|
90
94
|
if (!promo || basePrice === null || basePrice === void 0) return null;
|
|
91
95
|
if (promo.type === "percent" && typeof promo.value === "number") {
|
|
96
|
+
const pct = withinBounds(promo.value, 100);
|
|
92
97
|
return {
|
|
93
98
|
kind: "percent",
|
|
94
|
-
discounted: Math.round(basePrice * (100 -
|
|
99
|
+
discounted: Math.round(basePrice * (100 - pct)) / 100,
|
|
95
100
|
original: basePrice,
|
|
96
|
-
pct
|
|
101
|
+
pct
|
|
97
102
|
};
|
|
98
103
|
}
|
|
99
104
|
if (promo.type === "amount" && typeof promo.value === "number") {
|
|
105
|
+
const saved = withinBounds(promo.value, basePrice);
|
|
100
106
|
return {
|
|
101
107
|
kind: "amount",
|
|
102
|
-
discounted: Math.
|
|
108
|
+
discounted: Math.round((basePrice - saved) * 100) / 100,
|
|
103
109
|
original: basePrice,
|
|
104
|
-
saved
|
|
110
|
+
saved
|
|
105
111
|
};
|
|
106
112
|
}
|
|
107
|
-
if (promo.type === "intro" && typeof promo.value === "object") {
|
|
113
|
+
if (promo.type === "intro" && typeof promo.value === "object" && promo.value !== null) {
|
|
108
114
|
return {
|
|
109
115
|
kind: "intro",
|
|
110
|
-
discounted: promo.value.price,
|
|
116
|
+
discounted: withinBounds(promo.value.price, basePrice),
|
|
111
117
|
original: basePrice,
|
|
112
118
|
months: promo.value.months
|
|
113
119
|
};
|
|
@@ -117,6 +123,14 @@ function applyPromo(promo, basePrice) {
|
|
|
117
123
|
}
|
|
118
124
|
return null;
|
|
119
125
|
}
|
|
126
|
+
function withinBounds(value, upper) {
|
|
127
|
+
return Math.min(Math.max(value, 0), Math.max(upper, 0));
|
|
128
|
+
}
|
|
129
|
+
function promotionOnPrice(promotions, targetKey, locale, cycle, basePrice, today = /* @__PURE__ */ new Date(), targetType = "PLAN") {
|
|
130
|
+
const promotion = pickActivePromo(promotions, targetKey, locale, cycle, today, targetType);
|
|
131
|
+
const result = applyPromo(promotion, basePrice);
|
|
132
|
+
return promotion && result ? { promotion, result } : null;
|
|
133
|
+
}
|
|
120
134
|
|
|
121
135
|
// src/quota-value.ts
|
|
122
136
|
function readQuotaValue(value) {
|
|
@@ -413,6 +427,12 @@ var CATALOG_ERROR_CODES = {
|
|
|
413
427
|
FEATURE_NOT_FOUND: "FEATURE_NOT_FOUND",
|
|
414
428
|
QUOTA_NOT_FOUND: "QUOTA_NOT_FOUND",
|
|
415
429
|
PROMOTION_NOT_FOUND: "PROMOTION_NOT_FOUND",
|
|
430
|
+
/**
|
|
431
|
+
* A promotion's value is not one its type takes: a percentage above 0 and
|
|
432
|
+
* at most 100, an amount above 0, an intro price of at least 0 for a whole
|
|
433
|
+
* number of months, or a whole number of free months.
|
|
434
|
+
*/
|
|
435
|
+
PROMOTION_VALUE_INVALID: "PROMOTION_VALUE_INVALID",
|
|
416
436
|
MARKETING_PROJECTION_NOT_FOUND: "MARKETING_PROJECTION_NOT_FOUND",
|
|
417
437
|
// ── already exists ──
|
|
418
438
|
PLAN_ALREADY_EXISTS: "PLAN_ALREADY_EXISTS",
|
|
@@ -572,6 +592,13 @@ var CONTRACT_ERROR_CODES = {
|
|
|
572
592
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH",
|
|
573
593
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT",
|
|
574
594
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH",
|
|
595
|
+
/**
|
|
596
|
+
* The lines do not add up to a total the contract states, counted as often
|
|
597
|
+
* as each falls due in one period. `field` names the total.
|
|
598
|
+
*/
|
|
599
|
+
SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP",
|
|
600
|
+
/** Something that takes money off states a negative amount. */
|
|
601
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE",
|
|
575
602
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START",
|
|
576
603
|
// ── stage 3 ──
|
|
577
604
|
CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND",
|
|
@@ -684,7 +711,14 @@ var PAYMENT_ERROR_CODES = {
|
|
|
684
711
|
* A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
|
|
685
712
|
* does not name. Carries `field`.
|
|
686
713
|
*/
|
|
687
|
-
PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED"
|
|
714
|
+
PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED",
|
|
715
|
+
/**
|
|
716
|
+
* The gateway failed — unreachable, refusing the account's keys, or
|
|
717
|
+
* answering with an error — while opening its form or reading a callback
|
|
718
|
+
* back. Answered with 502; what the gateway answered stays in the server
|
|
719
|
+
* log.
|
|
720
|
+
*/
|
|
721
|
+
PAYMENT_GATEWAY_FAILED: "PAYMENT_GATEWAY_FAILED"
|
|
688
722
|
};
|
|
689
723
|
var SETTINGS_ERROR_CODES = {
|
|
690
724
|
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
@@ -950,6 +984,83 @@ function textOrNull(value) {
|
|
|
950
984
|
return trimmed === "" ? null : trimmed;
|
|
951
985
|
}
|
|
952
986
|
|
|
987
|
+
// src/closed-value.ts
|
|
988
|
+
function oneOf(allowed, value, source) {
|
|
989
|
+
const match = allowed.find((entry) => entry === value);
|
|
990
|
+
if (match === void 0) {
|
|
991
|
+
throw new Error(
|
|
992
|
+
`${source.table} row '${source.id}' holds ${source.column} '${value}', which is none of ${allowed.join(", ")}.`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
return match;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/subscriber-ledger-mapping.ts
|
|
999
|
+
var ORIGINS = {
|
|
1000
|
+
activation: true,
|
|
1001
|
+
renewal: true,
|
|
1002
|
+
planChange: true,
|
|
1003
|
+
bundleBooking: true,
|
|
1004
|
+
credit: true,
|
|
1005
|
+
correction: true
|
|
1006
|
+
};
|
|
1007
|
+
var SOURCES = {
|
|
1008
|
+
plan: true,
|
|
1009
|
+
bundle: true,
|
|
1010
|
+
discount: true
|
|
1011
|
+
};
|
|
1012
|
+
var TABLE = "subscriber_ledger_entries";
|
|
1013
|
+
var CENT_TOLERANCE = 1e-6;
|
|
1014
|
+
function toSubscriberChargeRecord(row) {
|
|
1015
|
+
const where = (column) => ({ table: TABLE, column, id: row.id });
|
|
1016
|
+
return {
|
|
1017
|
+
id: row.id,
|
|
1018
|
+
subscriberId: row.subscriberId,
|
|
1019
|
+
tenantId: row.tenantId,
|
|
1020
|
+
subscriptionId: row.subscriptionId,
|
|
1021
|
+
contractId: row.contractId,
|
|
1022
|
+
contractLineItemId: row.contractLineItemId,
|
|
1023
|
+
origin: oneOf(keysOf(ORIGINS), row.origin, where("origin")),
|
|
1024
|
+
source: oneOf(keysOf(SOURCES), row.source, where("source")),
|
|
1025
|
+
sourceRef: row.sourceRef,
|
|
1026
|
+
periodStart: row.periodStart,
|
|
1027
|
+
periodEnd: row.periodEnd,
|
|
1028
|
+
currency: row.currency,
|
|
1029
|
+
amountNet: Number(row.amountNet),
|
|
1030
|
+
bookedAt: row.bookedAt,
|
|
1031
|
+
createdAt: row.createdAt
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function subscriberChargeColumns(charge) {
|
|
1035
|
+
const cents = charge.amountNet * 100;
|
|
1036
|
+
if (!Number.isFinite(cents) || Math.abs(cents - Math.round(cents)) > CENT_TOLERANCE) {
|
|
1037
|
+
throw new Error(
|
|
1038
|
+
`A charge of ${charge.amountNet} is not a whole number of cents; round it once where it is derived, before it is written.`
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
return {
|
|
1042
|
+
subscriberId: charge.subscriberId,
|
|
1043
|
+
tenantId: charge.tenantId,
|
|
1044
|
+
subscriptionId: charge.subscriptionId,
|
|
1045
|
+
contractId: charge.contractId,
|
|
1046
|
+
contractLineItemId: charge.contractLineItemId,
|
|
1047
|
+
origin: charge.origin,
|
|
1048
|
+
source: charge.source,
|
|
1049
|
+
sourceRef: charge.sourceRef,
|
|
1050
|
+
periodStart: charge.periodStart,
|
|
1051
|
+
periodEnd: charge.periodEnd,
|
|
1052
|
+
currency: charge.currency,
|
|
1053
|
+
amountNet: charge.amountNet.toFixed(2),
|
|
1054
|
+
bookedAt: charge.bookedAt
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function keysOf(record) {
|
|
1058
|
+
return Object.keys(record);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/subscriber-ledger.types.ts
|
|
1062
|
+
var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
|
|
1063
|
+
|
|
953
1064
|
// src/subscriber-mapping.ts
|
|
954
1065
|
function formatCustomerNumber(prefix, sequence) {
|
|
955
1066
|
return `${prefix}${sequence}`;
|
|
@@ -1065,6 +1176,7 @@ function toIdentityValues(value) {
|
|
|
1065
1176
|
// src/subscriber-payment-method-mapping.ts
|
|
1066
1177
|
var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
|
|
1067
1178
|
var STATUSES = ["ACTIVE", "REPLACED"];
|
|
1179
|
+
var TABLE2 = "subscriber_payment_methods";
|
|
1068
1180
|
function toSubscriberPaymentMethodRecord(row) {
|
|
1069
1181
|
return {
|
|
1070
1182
|
id: row.id,
|
|
@@ -1073,7 +1185,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
1073
1185
|
provider: row.provider,
|
|
1074
1186
|
customerRef: row.customerRef,
|
|
1075
1187
|
paymentMethodRef: row.paymentMethodRef,
|
|
1076
|
-
type: oneOf(PAYMENT_METHOD_TYPES, row.type, "type", row.id),
|
|
1188
|
+
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE2, column: "type", id: row.id }),
|
|
1077
1189
|
brand: row.brand,
|
|
1078
1190
|
last4: row.last4,
|
|
1079
1191
|
expiryMonth: row.expiryMonth,
|
|
@@ -1081,7 +1193,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
1081
1193
|
country: row.country,
|
|
1082
1194
|
bankCode: row.bankCode,
|
|
1083
1195
|
mandateReference: row.mandateReference,
|
|
1084
|
-
status: oneOf(STATUSES, row.status, "status", row.id),
|
|
1196
|
+
status: oneOf(STATUSES, row.status, { table: TABLE2, column: "status", id: row.id }),
|
|
1085
1197
|
confirmedAt: row.confirmedAt,
|
|
1086
1198
|
replacedAt: row.replacedAt,
|
|
1087
1199
|
createdAt: row.createdAt
|
|
@@ -1105,14 +1217,53 @@ function subscriberPaymentMethodColumns(data) {
|
|
|
1105
1217
|
confirmedAt: data.confirmedAt
|
|
1106
1218
|
};
|
|
1107
1219
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
`
|
|
1220
|
+
var FOREIGN_PAYMENT_METHOD_REFERENCE = "FOREIGN_PAYMENT_METHOD_REFERENCE";
|
|
1221
|
+
var ForeignPaymentMethodReferenceError = class extends Error {
|
|
1222
|
+
constructor(gatewayAccount, paymentMethodRef) {
|
|
1223
|
+
super(
|
|
1224
|
+
`Payment method '${paymentMethodRef}' of account '${gatewayAccount}' belongs to another subscriber. A reference belongs to exactly one, so it is not handed out.`
|
|
1113
1225
|
);
|
|
1226
|
+
this.gatewayAccount = gatewayAccount;
|
|
1227
|
+
this.paymentMethodRef = paymentMethodRef;
|
|
1228
|
+
this.name = "ForeignPaymentMethodReferenceError";
|
|
1114
1229
|
}
|
|
1115
|
-
|
|
1230
|
+
gatewayAccount;
|
|
1231
|
+
paymentMethodRef;
|
|
1232
|
+
code = FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
1233
|
+
};
|
|
1234
|
+
function isForeignPaymentMethodReferenceError(error) {
|
|
1235
|
+
return error instanceof Error && error.code === FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
1236
|
+
}
|
|
1237
|
+
function refuseForeignPaymentMethodReference(recorded, subscriberId) {
|
|
1238
|
+
if (recorded.subscriberId === subscriberId) return;
|
|
1239
|
+
throw new ForeignPaymentMethodReferenceError(
|
|
1240
|
+
recorded.gatewayAccount,
|
|
1241
|
+
recorded.paymentMethodRef
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/subscription-bundle-mapping.ts
|
|
1246
|
+
var BILLING_CYCLES = ["MONTHLY", "YEARLY"];
|
|
1247
|
+
function toSubscriptionBundleRecord(row) {
|
|
1248
|
+
const billingCycle = row.billingCycle ?? null;
|
|
1249
|
+
return {
|
|
1250
|
+
id: row.id,
|
|
1251
|
+
subscriptionId: row.subscriptionId,
|
|
1252
|
+
bundleVersionId: row.bundleVersionId,
|
|
1253
|
+
startedAt: row.startedAt,
|
|
1254
|
+
minimumTermEndsAt: row.minimumTermEndsAt,
|
|
1255
|
+
billingCycle: billingCycle === null ? null : oneOf(BILLING_CYCLES, billingCycle, {
|
|
1256
|
+
table: "subscription_bundles",
|
|
1257
|
+
column: "billingCycle",
|
|
1258
|
+
id: row.id
|
|
1259
|
+
}),
|
|
1260
|
+
currentPeriodStart: row.currentPeriodStart ?? null,
|
|
1261
|
+
currentPeriodEnd: row.currentPeriodEnd ?? null,
|
|
1262
|
+
canceledAt: row.canceledAt,
|
|
1263
|
+
canceledEffectiveAt: row.canceledEffectiveAt,
|
|
1264
|
+
createdAt: row.createdAt,
|
|
1265
|
+
updatedAt: row.updatedAt
|
|
1266
|
+
};
|
|
1116
1267
|
}
|
|
1117
1268
|
|
|
1118
1269
|
// src/subscription-contract-mapping.ts
|
|
@@ -1324,6 +1475,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
1324
1475
|
FEATURE_NOT_FOUND: "Feature '{featureKey}' not found",
|
|
1325
1476
|
QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found",
|
|
1326
1477
|
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' not found",
|
|
1478
|
+
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.",
|
|
1327
1479
|
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' not found",
|
|
1328
1480
|
PLAN_ALREADY_EXISTS: "Plan '{planKey}' already exists",
|
|
1329
1481
|
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' already exists",
|
|
@@ -1378,7 +1530,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
1378
1530
|
// ── promo ──
|
|
1379
1531
|
PROMO_CODE_NOT_FOUND: "Code not found",
|
|
1380
1532
|
PROMO_CODE_ALREADY_EXISTS: "The code already exists.",
|
|
1381
|
-
PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
1533
|
+
PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions, or checkouts holding one \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
1382
1534
|
PROMO_CODE_NOT_REDEEMABLE: "Code cannot be redeemed: {reason}",
|
|
1383
1535
|
PROMO_CODE_FORMAT_INVALID: 'The code may only contain upper-case letters, digits, "-" and "_" (4\u201332 characters).',
|
|
1384
1536
|
PROMO_PERCENT_OUT_OF_RANGE: "The percentage must be between 0 and 100.",
|
|
@@ -1408,6 +1560,8 @@ var ERROR_MESSAGES_EN = {
|
|
|
1408
1560
|
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.",
|
|
1409
1561
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil must be after the effectiveFrom of the contract.",
|
|
1410
1562
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "A line item must be booked in the currency its contract was priced in.",
|
|
1563
|
+
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}.",
|
|
1564
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "A subscription contract states a discount of {amount} at {field}; a discount takes money off and is never negative.",
|
|
1411
1565
|
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' not found",
|
|
1412
1566
|
CHECKOUT_OFFER_EXPIRED: "Checkout offer '{offerId}' has expired and cannot be {action}",
|
|
1413
1567
|
CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
|
|
@@ -1447,6 +1601,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
1447
1601
|
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "No payment gateway account is configured under '{account}'.",
|
|
1448
1602
|
PAYMENT_CALLBACK_REJECTED: "The payment callback could not be verified.",
|
|
1449
1603
|
PAYMENT_RETURN_URL_NOT_ALLOWED: "The {field} leads to a site this installation does not return to.",
|
|
1604
|
+
PAYMENT_GATEWAY_FAILED: "The payment provider did not answer as expected. Please try again in a few minutes.",
|
|
1450
1605
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
1451
1606
|
[FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan.",
|
|
1452
1607
|
// ── settings ──
|
|
@@ -1531,6 +1686,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1531
1686
|
FEATURE_NOT_FOUND: "Feature '{featureKey}' nicht gefunden",
|
|
1532
1687
|
QUOTA_NOT_FOUND: "Quota '{quotaKey}' nicht gefunden",
|
|
1533
1688
|
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' nicht gefunden",
|
|
1689
|
+
PROMOTION_VALUE_INVALID: "Der Wert einer Aktion vom Typ '{type}' passt nicht zu ihrem Typ: ein Prozentsatz \xFCber 0 und h\xF6chstens 100, ein Betrag \xFCber 0, ein Einf\xFChrungspreis ab 0 f\xFCr eine ganze Zahl von Monaten oder eine ganze Zahl von Gratismonaten.",
|
|
1534
1690
|
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' nicht gefunden",
|
|
1535
1691
|
PLAN_ALREADY_EXISTS: "Plan '{planKey}' gibt es bereits",
|
|
1536
1692
|
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' gibt es bereits",
|
|
@@ -1585,7 +1741,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1585
1741
|
// ── promo ──
|
|
1586
1742
|
PROMO_CODE_NOT_FOUND: "Code nicht gefunden",
|
|
1587
1743
|
PROMO_CODE_ALREADY_EXISTS: "Diesen Code gibt es bereits.",
|
|
1588
|
-
PROMO_CODE_HAS_REDEMPTIONS: "Der Code wurde bereits eingel\xF6st \u2014 er l\xE4sst sich nicht per Soft-Delete entfernen. Pausiere ihn stattdessen.",
|
|
1744
|
+
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.",
|
|
1589
1745
|
PROMO_CODE_NOT_REDEEMABLE: "Der Code l\xE4sst sich nicht einl\xF6sen: {reason}",
|
|
1590
1746
|
PROMO_CODE_FORMAT_INVALID: 'Der Code darf nur Gro\xDFbuchstaben, Ziffern, "-" und "_" enthalten (4\u201332 Zeichen).',
|
|
1591
1747
|
PROMO_PERCENT_OUT_OF_RANGE: "Der Prozentwert muss zwischen 0 und 100 liegen.",
|
|
@@ -1614,6 +1770,8 @@ var ERROR_MESSAGES_DE = {
|
|
|
1614
1770
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "taxAmount einer Position muss exakt priceGross minus priceNet sein.",
|
|
1615
1771
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "Ein Abo-Vertrag nennt bei {field} einen Steuersatz von {taxRate}, der kein Prozentsatz ist: Ein Satz liegt zwischen 0 und 100, und ein Wert zwischen 0 und 1 wird als Bruch abgelehnt.",
|
|
1616
1772
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "Eine Position muss in der W\xE4hrung gebucht sein, in der ihr Vertrag bepreist ist.",
|
|
1773
|
+
SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "Die Positionen eines Abo-Vertrags ergeben {lines} f\xFCr {field}, der Vertrag nennt aber {stated}.",
|
|
1774
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "Ein Abo-Vertrag nennt bei {field} einen Rabatt von {amount}; ein Rabatt zieht Geld ab und ist nie negativ.",
|
|
1617
1775
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil muss nach dem effectiveFrom des Vertrags liegen.",
|
|
1618
1776
|
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' nicht gefunden",
|
|
1619
1777
|
CHECKOUT_OFFER_EXPIRED: "Checkout-Angebot '{offerId}' ist abgelaufen \u2014 '{action}' ist nicht mehr m\xF6glich",
|
|
@@ -1654,6 +1812,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1654
1812
|
PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "Unter '{account}' ist kein Konto eines Zahlungsanbieters eingerichtet.",
|
|
1655
1813
|
PAYMENT_CALLBACK_REJECTED: "Die R\xFCckmeldung des Zahlungsanbieters lie\xDF sich nicht pr\xFCfen.",
|
|
1656
1814
|
PAYMENT_RETURN_URL_NOT_ALLOWED: "Die {field} f\xFChrt auf eine Seite, zu der diese Installation nicht zur\xFCckleitet.",
|
|
1815
|
+
PAYMENT_GATEWAY_FAILED: "Der Zahlungsanbieter hat nicht wie erwartet geantwortet. Bitte versuche es in ein paar Minuten noch einmal.",
|
|
1657
1816
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
1658
1817
|
[FEATURE_NOT_LICENSED]: "Das Feature {featureKeys} ist im aktuellen Plan nicht enthalten.",
|
|
1659
1818
|
// ── settings ──
|
|
@@ -1670,6 +1829,7 @@ export {
|
|
|
1670
1829
|
ERROR_MESSAGES_DE,
|
|
1671
1830
|
ERROR_MESSAGES_EN,
|
|
1672
1831
|
FEATURE_NOT_LICENSED,
|
|
1832
|
+
ForeignPaymentMethodReferenceError,
|
|
1673
1833
|
LEGAL_IDENTITY_FIELDS,
|
|
1674
1834
|
MARKETING_PRIORITY_MAX,
|
|
1675
1835
|
MARKETING_PRIORITY_MIN,
|
|
@@ -1693,6 +1853,7 @@ export {
|
|
|
1693
1853
|
REGISTRATION_STEP_BY_STATUS,
|
|
1694
1854
|
SETTINGS_ERROR_CODES,
|
|
1695
1855
|
SETUP_ERROR_CODES,
|
|
1856
|
+
SUBSCRIBER_ACCOUNT_CAPABILITY,
|
|
1696
1857
|
SUBSCRIBER_ERROR_CODES,
|
|
1697
1858
|
applyPromo,
|
|
1698
1859
|
assertPersistenceCapabilities,
|
|
@@ -1714,6 +1875,7 @@ export {
|
|
|
1714
1875
|
formatErrorMessage,
|
|
1715
1876
|
identityCorrectionDelta,
|
|
1716
1877
|
isBundleRedundant,
|
|
1878
|
+
isForeignPaymentMethodReferenceError,
|
|
1717
1879
|
isPaymentCallbackRejectedError,
|
|
1718
1880
|
isPlatformUserExistsError,
|
|
1719
1881
|
isVersionEditable,
|
|
@@ -1725,15 +1887,18 @@ export {
|
|
|
1725
1887
|
planCatalogSettingsOf,
|
|
1726
1888
|
previousUtcDay,
|
|
1727
1889
|
promoStatus,
|
|
1890
|
+
promotionOnPrice,
|
|
1728
1891
|
readQuotaRecord,
|
|
1729
1892
|
readQuotaValue,
|
|
1730
1893
|
recordedIssuerIdentity,
|
|
1894
|
+
refuseForeignPaymentMethodReference,
|
|
1731
1895
|
resolveBundleAvailability,
|
|
1732
1896
|
resolveErrorMessage,
|
|
1733
1897
|
sameLegalIdentity,
|
|
1734
1898
|
selectChargeableBundles,
|
|
1735
1899
|
settingsSubtreeOf,
|
|
1736
1900
|
startOfUtcDay,
|
|
1901
|
+
subscriberChargeColumns,
|
|
1737
1902
|
subscriberFromRegistration,
|
|
1738
1903
|
subscriberPaymentMethodColumns,
|
|
1739
1904
|
toBundleStemRow,
|
|
@@ -1741,8 +1906,10 @@ export {
|
|
|
1741
1906
|
toPlanRow,
|
|
1742
1907
|
toPlanVersionRow,
|
|
1743
1908
|
toRunningContractIssuer,
|
|
1909
|
+
toSubscriberChargeRecord,
|
|
1744
1910
|
toSubscriberCorrectionRecord,
|
|
1745
1911
|
toSubscriberPaymentMethodRecord,
|
|
1746
1912
|
toSubscriberRecord,
|
|
1913
|
+
toSubscriptionBundleRecord,
|
|
1747
1914
|
toSubscriptionContractRecord
|
|
1748
1915
|
};
|
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.22",
|
|
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",
|