@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 CHANGED
@@ -1 +1 @@
1
- 52f37c89214b0d0e2647b9735ce3fb3773f0f8e9e8d1f6d301e4df83160dbd85
1
+ 91754758b2193699e34b267e3d43034f1ffa6d651baee667fbdf3456d23db8b2
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ __export(index_exports, {
30
30
  ERROR_MESSAGES_DE: () => ERROR_MESSAGES_DE,
31
31
  ERROR_MESSAGES_EN: () => ERROR_MESSAGES_EN,
32
32
  FEATURE_NOT_LICENSED: () => FEATURE_NOT_LICENSED,
33
+ ForeignPaymentMethodReferenceError: () => ForeignPaymentMethodReferenceError,
33
34
  LEGAL_IDENTITY_FIELDS: () => LEGAL_IDENTITY_FIELDS,
34
35
  MARKETING_PRIORITY_MAX: () => MARKETING_PRIORITY_MAX,
35
36
  MARKETING_PRIORITY_MIN: () => MARKETING_PRIORITY_MIN,
@@ -53,6 +54,7 @@ __export(index_exports, {
53
54
  REGISTRATION_STEP_BY_STATUS: () => REGISTRATION_STEP_BY_STATUS,
54
55
  SETTINGS_ERROR_CODES: () => SETTINGS_ERROR_CODES,
55
56
  SETUP_ERROR_CODES: () => SETUP_ERROR_CODES,
57
+ SUBSCRIBER_ACCOUNT_CAPABILITY: () => SUBSCRIBER_ACCOUNT_CAPABILITY,
56
58
  SUBSCRIBER_ERROR_CODES: () => SUBSCRIBER_ERROR_CODES,
57
59
  applyPromo: () => applyPromo,
58
60
  assertPersistenceCapabilities: () => assertPersistenceCapabilities,
@@ -74,6 +76,7 @@ __export(index_exports, {
74
76
  formatErrorMessage: () => formatErrorMessage,
75
77
  identityCorrectionDelta: () => identityCorrectionDelta,
76
78
  isBundleRedundant: () => isBundleRedundant,
79
+ isForeignPaymentMethodReferenceError: () => isForeignPaymentMethodReferenceError,
77
80
  isPaymentCallbackRejectedError: () => isPaymentCallbackRejectedError,
78
81
  isPlatformUserExistsError: () => isPlatformUserExistsError,
79
82
  isVersionEditable: () => isVersionEditable,
@@ -85,15 +88,18 @@ __export(index_exports, {
85
88
  planCatalogSettingsOf: () => planCatalogSettingsOf,
86
89
  previousUtcDay: () => previousUtcDay,
87
90
  promoStatus: () => promoStatus,
91
+ promotionOnPrice: () => promotionOnPrice,
88
92
  readQuotaRecord: () => readQuotaRecord,
89
93
  readQuotaValue: () => readQuotaValue,
90
94
  recordedIssuerIdentity: () => recordedIssuerIdentity,
95
+ refuseForeignPaymentMethodReference: () => refuseForeignPaymentMethodReference,
91
96
  resolveBundleAvailability: () => resolveBundleAvailability,
92
97
  resolveErrorMessage: () => resolveErrorMessage,
93
98
  sameLegalIdentity: () => sameLegalIdentity,
94
99
  selectChargeableBundles: () => selectChargeableBundles,
95
100
  settingsSubtreeOf: () => settingsSubtreeOf,
96
101
  startOfUtcDay: () => startOfUtcDay,
102
+ subscriberChargeColumns: () => subscriberChargeColumns,
97
103
  subscriberFromRegistration: () => subscriberFromRegistration,
98
104
  subscriberPaymentMethodColumns: () => subscriberPaymentMethodColumns,
99
105
  toBundleStemRow: () => toBundleStemRow,
@@ -101,9 +107,11 @@ __export(index_exports, {
101
107
  toPlanRow: () => toPlanRow,
102
108
  toPlanVersionRow: () => toPlanVersionRow,
103
109
  toRunningContractIssuer: () => toRunningContractIssuer,
110
+ toSubscriberChargeRecord: () => toSubscriberChargeRecord,
104
111
  toSubscriberCorrectionRecord: () => toSubscriberCorrectionRecord,
105
112
  toSubscriberPaymentMethodRecord: () => toSubscriberPaymentMethodRecord,
106
113
  toSubscriberRecord: () => toSubscriberRecord,
114
+ toSubscriptionBundleRecord: () => toSubscriptionBundleRecord,
107
115
  toSubscriptionContractRecord: () => toSubscriptionContractRecord
108
116
  });
109
117
  module.exports = __toCommonJS(index_exports);
@@ -197,27 +205,33 @@ function pickActivePromo(promotions, targetKey, locale, cycle, today = /* @__PUR
197
205
  return matches[0] ?? null;
198
206
  }
199
207
  function applyPromo(promo, basePrice) {
208
+ const result = boundedPromo(promo, basePrice);
209
+ return result && result.discounted < result.original ? result : null;
210
+ }
211
+ function boundedPromo(promo, basePrice) {
200
212
  if (!promo || basePrice === null || basePrice === void 0) return null;
201
213
  if (promo.type === "percent" && typeof promo.value === "number") {
214
+ const pct = withinBounds(promo.value, 100);
202
215
  return {
203
216
  kind: "percent",
204
- discounted: Math.round(basePrice * (100 - promo.value)) / 100,
217
+ discounted: Math.round(basePrice * (100 - pct)) / 100,
205
218
  original: basePrice,
206
- pct: promo.value
219
+ pct
207
220
  };
208
221
  }
209
222
  if (promo.type === "amount" && typeof promo.value === "number") {
223
+ const saved = withinBounds(promo.value, basePrice);
210
224
  return {
211
225
  kind: "amount",
212
- discounted: Math.max(0, basePrice - promo.value),
226
+ discounted: Math.round((basePrice - saved) * 100) / 100,
213
227
  original: basePrice,
214
- saved: promo.value
228
+ saved
215
229
  };
216
230
  }
217
- if (promo.type === "intro" && typeof promo.value === "object") {
231
+ if (promo.type === "intro" && typeof promo.value === "object" && promo.value !== null) {
218
232
  return {
219
233
  kind: "intro",
220
- discounted: promo.value.price,
234
+ discounted: withinBounds(promo.value.price, basePrice),
221
235
  original: basePrice,
222
236
  months: promo.value.months
223
237
  };
@@ -227,6 +241,14 @@ function applyPromo(promo, basePrice) {
227
241
  }
228
242
  return null;
229
243
  }
244
+ function withinBounds(value, upper) {
245
+ return Math.min(Math.max(value, 0), Math.max(upper, 0));
246
+ }
247
+ function promotionOnPrice(promotions, targetKey, locale, cycle, basePrice, today = /* @__PURE__ */ new Date(), targetType = "PLAN") {
248
+ const promotion = pickActivePromo(promotions, targetKey, locale, cycle, today, targetType);
249
+ const result = applyPromo(promotion, basePrice);
250
+ return promotion && result ? { promotion, result } : null;
251
+ }
230
252
 
231
253
  // src/quota-value.ts
232
254
  function readQuotaValue(value) {
@@ -523,6 +545,12 @@ var CATALOG_ERROR_CODES = {
523
545
  FEATURE_NOT_FOUND: "FEATURE_NOT_FOUND",
524
546
  QUOTA_NOT_FOUND: "QUOTA_NOT_FOUND",
525
547
  PROMOTION_NOT_FOUND: "PROMOTION_NOT_FOUND",
548
+ /**
549
+ * A promotion's value is not one its type takes: a percentage above 0 and
550
+ * at most 100, an amount above 0, an intro price of at least 0 for a whole
551
+ * number of months, or a whole number of free months.
552
+ */
553
+ PROMOTION_VALUE_INVALID: "PROMOTION_VALUE_INVALID",
526
554
  MARKETING_PROJECTION_NOT_FOUND: "MARKETING_PROJECTION_NOT_FOUND",
527
555
  // ── already exists ──
528
556
  PLAN_ALREADY_EXISTS: "PLAN_ALREADY_EXISTS",
@@ -682,6 +710,13 @@ var CONTRACT_ERROR_CODES = {
682
710
  SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH",
683
711
  SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT",
684
712
  SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH",
713
+ /**
714
+ * The lines do not add up to a total the contract states, counted as often
715
+ * as each falls due in one period. `field` names the total.
716
+ */
717
+ SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP",
718
+ /** Something that takes money off states a negative amount. */
719
+ SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE",
685
720
  SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START",
686
721
  // ── stage 3 ──
687
722
  CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND",
@@ -794,7 +829,14 @@ var PAYMENT_ERROR_CODES = {
794
829
  * A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
795
830
  * does not name. Carries `field`.
796
831
  */
797
- PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED"
832
+ PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED",
833
+ /**
834
+ * The gateway failed — unreachable, refusing the account's keys, or
835
+ * answering with an error — while opening its form or reading a callback
836
+ * back. Answered with 502; what the gateway answered stays in the server
837
+ * log.
838
+ */
839
+ PAYMENT_GATEWAY_FAILED: "PAYMENT_GATEWAY_FAILED"
798
840
  };
799
841
  var SETTINGS_ERROR_CODES = {
800
842
  /** No recorded change has this id, or the installation keeps no record at all. */
@@ -1060,6 +1102,83 @@ function textOrNull(value) {
1060
1102
  return trimmed === "" ? null : trimmed;
1061
1103
  }
1062
1104
 
1105
+ // src/closed-value.ts
1106
+ function oneOf(allowed, value, source) {
1107
+ const match = allowed.find((entry) => entry === value);
1108
+ if (match === void 0) {
1109
+ throw new Error(
1110
+ `${source.table} row '${source.id}' holds ${source.column} '${value}', which is none of ${allowed.join(", ")}.`
1111
+ );
1112
+ }
1113
+ return match;
1114
+ }
1115
+
1116
+ // src/subscriber-ledger-mapping.ts
1117
+ var ORIGINS = {
1118
+ activation: true,
1119
+ renewal: true,
1120
+ planChange: true,
1121
+ bundleBooking: true,
1122
+ credit: true,
1123
+ correction: true
1124
+ };
1125
+ var SOURCES = {
1126
+ plan: true,
1127
+ bundle: true,
1128
+ discount: true
1129
+ };
1130
+ var TABLE = "subscriber_ledger_entries";
1131
+ var CENT_TOLERANCE = 1e-6;
1132
+ function toSubscriberChargeRecord(row) {
1133
+ const where = (column) => ({ table: TABLE, column, id: row.id });
1134
+ return {
1135
+ id: row.id,
1136
+ subscriberId: row.subscriberId,
1137
+ tenantId: row.tenantId,
1138
+ subscriptionId: row.subscriptionId,
1139
+ contractId: row.contractId,
1140
+ contractLineItemId: row.contractLineItemId,
1141
+ origin: oneOf(keysOf(ORIGINS), row.origin, where("origin")),
1142
+ source: oneOf(keysOf(SOURCES), row.source, where("source")),
1143
+ sourceRef: row.sourceRef,
1144
+ periodStart: row.periodStart,
1145
+ periodEnd: row.periodEnd,
1146
+ currency: row.currency,
1147
+ amountNet: Number(row.amountNet),
1148
+ bookedAt: row.bookedAt,
1149
+ createdAt: row.createdAt
1150
+ };
1151
+ }
1152
+ function subscriberChargeColumns(charge) {
1153
+ const cents = charge.amountNet * 100;
1154
+ if (!Number.isFinite(cents) || Math.abs(cents - Math.round(cents)) > CENT_TOLERANCE) {
1155
+ throw new Error(
1156
+ `A charge of ${charge.amountNet} is not a whole number of cents; round it once where it is derived, before it is written.`
1157
+ );
1158
+ }
1159
+ return {
1160
+ subscriberId: charge.subscriberId,
1161
+ tenantId: charge.tenantId,
1162
+ subscriptionId: charge.subscriptionId,
1163
+ contractId: charge.contractId,
1164
+ contractLineItemId: charge.contractLineItemId,
1165
+ origin: charge.origin,
1166
+ source: charge.source,
1167
+ sourceRef: charge.sourceRef,
1168
+ periodStart: charge.periodStart,
1169
+ periodEnd: charge.periodEnd,
1170
+ currency: charge.currency,
1171
+ amountNet: charge.amountNet.toFixed(2),
1172
+ bookedAt: charge.bookedAt
1173
+ };
1174
+ }
1175
+ function keysOf(record) {
1176
+ return Object.keys(record);
1177
+ }
1178
+
1179
+ // src/subscriber-ledger.types.ts
1180
+ var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
1181
+
1063
1182
  // src/subscriber-mapping.ts
1064
1183
  function formatCustomerNumber(prefix, sequence) {
1065
1184
  return `${prefix}${sequence}`;
@@ -1175,6 +1294,7 @@ function toIdentityValues(value) {
1175
1294
  // src/subscriber-payment-method-mapping.ts
1176
1295
  var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
1177
1296
  var STATUSES = ["ACTIVE", "REPLACED"];
1297
+ var TABLE2 = "subscriber_payment_methods";
1178
1298
  function toSubscriberPaymentMethodRecord(row) {
1179
1299
  return {
1180
1300
  id: row.id,
@@ -1183,7 +1303,7 @@ function toSubscriberPaymentMethodRecord(row) {
1183
1303
  provider: row.provider,
1184
1304
  customerRef: row.customerRef,
1185
1305
  paymentMethodRef: row.paymentMethodRef,
1186
- type: oneOf(PAYMENT_METHOD_TYPES, row.type, "type", row.id),
1306
+ type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE2, column: "type", id: row.id }),
1187
1307
  brand: row.brand,
1188
1308
  last4: row.last4,
1189
1309
  expiryMonth: row.expiryMonth,
@@ -1191,7 +1311,7 @@ function toSubscriberPaymentMethodRecord(row) {
1191
1311
  country: row.country,
1192
1312
  bankCode: row.bankCode,
1193
1313
  mandateReference: row.mandateReference,
1194
- status: oneOf(STATUSES, row.status, "status", row.id),
1314
+ status: oneOf(STATUSES, row.status, { table: TABLE2, column: "status", id: row.id }),
1195
1315
  confirmedAt: row.confirmedAt,
1196
1316
  replacedAt: row.replacedAt,
1197
1317
  createdAt: row.createdAt
@@ -1215,14 +1335,53 @@ function subscriberPaymentMethodColumns(data) {
1215
1335
  confirmedAt: data.confirmedAt
1216
1336
  };
1217
1337
  }
1218
- function oneOf(allowed, value, column, id) {
1219
- const match = allowed.find((entry) => entry === value);
1220
- if (match === void 0) {
1221
- throw new Error(
1222
- `subscriber_payment_methods row '${id}' holds ${column} '${value}', which is none of ${allowed.join(", ")}.`
1338
+ var FOREIGN_PAYMENT_METHOD_REFERENCE = "FOREIGN_PAYMENT_METHOD_REFERENCE";
1339
+ var ForeignPaymentMethodReferenceError = class extends Error {
1340
+ constructor(gatewayAccount, paymentMethodRef) {
1341
+ super(
1342
+ `Payment method '${paymentMethodRef}' of account '${gatewayAccount}' belongs to another subscriber. A reference belongs to exactly one, so it is not handed out.`
1223
1343
  );
1344
+ this.gatewayAccount = gatewayAccount;
1345
+ this.paymentMethodRef = paymentMethodRef;
1346
+ this.name = "ForeignPaymentMethodReferenceError";
1224
1347
  }
1225
- return match;
1348
+ gatewayAccount;
1349
+ paymentMethodRef;
1350
+ code = FOREIGN_PAYMENT_METHOD_REFERENCE;
1351
+ };
1352
+ function isForeignPaymentMethodReferenceError(error) {
1353
+ return error instanceof Error && error.code === FOREIGN_PAYMENT_METHOD_REFERENCE;
1354
+ }
1355
+ function refuseForeignPaymentMethodReference(recorded, subscriberId) {
1356
+ if (recorded.subscriberId === subscriberId) return;
1357
+ throw new ForeignPaymentMethodReferenceError(
1358
+ recorded.gatewayAccount,
1359
+ recorded.paymentMethodRef
1360
+ );
1361
+ }
1362
+
1363
+ // src/subscription-bundle-mapping.ts
1364
+ var BILLING_CYCLES = ["MONTHLY", "YEARLY"];
1365
+ function toSubscriptionBundleRecord(row) {
1366
+ const billingCycle = row.billingCycle ?? null;
1367
+ return {
1368
+ id: row.id,
1369
+ subscriptionId: row.subscriptionId,
1370
+ bundleVersionId: row.bundleVersionId,
1371
+ startedAt: row.startedAt,
1372
+ minimumTermEndsAt: row.minimumTermEndsAt,
1373
+ billingCycle: billingCycle === null ? null : oneOf(BILLING_CYCLES, billingCycle, {
1374
+ table: "subscription_bundles",
1375
+ column: "billingCycle",
1376
+ id: row.id
1377
+ }),
1378
+ currentPeriodStart: row.currentPeriodStart ?? null,
1379
+ currentPeriodEnd: row.currentPeriodEnd ?? null,
1380
+ canceledAt: row.canceledAt,
1381
+ canceledEffectiveAt: row.canceledEffectiveAt,
1382
+ createdAt: row.createdAt,
1383
+ updatedAt: row.updatedAt
1384
+ };
1226
1385
  }
1227
1386
 
1228
1387
  // src/subscription-contract-mapping.ts
@@ -1434,6 +1593,7 @@ var ERROR_MESSAGES_EN = {
1434
1593
  FEATURE_NOT_FOUND: "Feature '{featureKey}' not found",
1435
1594
  QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found",
1436
1595
  PROMOTION_NOT_FOUND: "Promotion '{promotionId}' not found",
1596
+ 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.",
1437
1597
  MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' not found",
1438
1598
  PLAN_ALREADY_EXISTS: "Plan '{planKey}' already exists",
1439
1599
  BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' already exists",
@@ -1488,7 +1648,7 @@ var ERROR_MESSAGES_EN = {
1488
1648
  // ── promo ──
1489
1649
  PROMO_CODE_NOT_FOUND: "Code not found",
1490
1650
  PROMO_CODE_ALREADY_EXISTS: "The code already exists.",
1491
- PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions \u2014 it cannot be soft-deleted. Pause it instead.",
1651
+ PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions, or checkouts holding one \u2014 it cannot be soft-deleted. Pause it instead.",
1492
1652
  PROMO_CODE_NOT_REDEEMABLE: "Code cannot be redeemed: {reason}",
1493
1653
  PROMO_CODE_FORMAT_INVALID: 'The code may only contain upper-case letters, digits, "-" and "_" (4\u201332 characters).',
1494
1654
  PROMO_PERCENT_OUT_OF_RANGE: "The percentage must be between 0 and 100.",
@@ -1518,6 +1678,8 @@ var ERROR_MESSAGES_EN = {
1518
1678
  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.",
1519
1679
  SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil must be after the effectiveFrom of the contract.",
1520
1680
  SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "A line item must be booked in the currency its contract was priced in.",
1681
+ 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}.",
1682
+ SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "A subscription contract states a discount of {amount} at {field}; a discount takes money off and is never negative.",
1521
1683
  CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' not found",
1522
1684
  CHECKOUT_OFFER_EXPIRED: "Checkout offer '{offerId}' has expired and cannot be {action}",
1523
1685
  CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
@@ -1557,6 +1719,7 @@ var ERROR_MESSAGES_EN = {
1557
1719
  PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "No payment gateway account is configured under '{account}'.",
1558
1720
  PAYMENT_CALLBACK_REJECTED: "The payment callback could not be verified.",
1559
1721
  PAYMENT_RETURN_URL_NOT_ALLOWED: "The {field} leads to a site this installation does not return to.",
1722
+ PAYMENT_GATEWAY_FAILED: "The payment provider did not answer as expected. Please try again in a few minutes.",
1560
1723
  // ── entitlement (code lives in upsell.types.ts) ──
1561
1724
  [FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan.",
1562
1725
  // ── settings ──
@@ -1641,6 +1804,7 @@ var ERROR_MESSAGES_DE = {
1641
1804
  FEATURE_NOT_FOUND: "Feature '{featureKey}' nicht gefunden",
1642
1805
  QUOTA_NOT_FOUND: "Quota '{quotaKey}' nicht gefunden",
1643
1806
  PROMOTION_NOT_FOUND: "Promotion '{promotionId}' nicht gefunden",
1807
+ 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.",
1644
1808
  MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' nicht gefunden",
1645
1809
  PLAN_ALREADY_EXISTS: "Plan '{planKey}' gibt es bereits",
1646
1810
  BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' gibt es bereits",
@@ -1695,7 +1859,7 @@ var ERROR_MESSAGES_DE = {
1695
1859
  // ── promo ──
1696
1860
  PROMO_CODE_NOT_FOUND: "Code nicht gefunden",
1697
1861
  PROMO_CODE_ALREADY_EXISTS: "Diesen Code gibt es bereits.",
1698
- PROMO_CODE_HAS_REDEMPTIONS: "Der Code wurde bereits eingel\xF6st \u2014 er l\xE4sst sich nicht per Soft-Delete entfernen. Pausiere ihn stattdessen.",
1862
+ 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.",
1699
1863
  PROMO_CODE_NOT_REDEEMABLE: "Der Code l\xE4sst sich nicht einl\xF6sen: {reason}",
1700
1864
  PROMO_CODE_FORMAT_INVALID: 'Der Code darf nur Gro\xDFbuchstaben, Ziffern, "-" und "_" enthalten (4\u201332 Zeichen).',
1701
1865
  PROMO_PERCENT_OUT_OF_RANGE: "Der Prozentwert muss zwischen 0 und 100 liegen.",
@@ -1724,6 +1888,8 @@ var ERROR_MESSAGES_DE = {
1724
1888
  SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "taxAmount einer Position muss exakt priceGross minus priceNet sein.",
1725
1889
  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.",
1726
1890
  SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "Eine Position muss in der W\xE4hrung gebucht sein, in der ihr Vertrag bepreist ist.",
1891
+ SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "Die Positionen eines Abo-Vertrags ergeben {lines} f\xFCr {field}, der Vertrag nennt aber {stated}.",
1892
+ SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "Ein Abo-Vertrag nennt bei {field} einen Rabatt von {amount}; ein Rabatt zieht Geld ab und ist nie negativ.",
1727
1893
  SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil muss nach dem effectiveFrom des Vertrags liegen.",
1728
1894
  CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' nicht gefunden",
1729
1895
  CHECKOUT_OFFER_EXPIRED: "Checkout-Angebot '{offerId}' ist abgelaufen \u2014 '{action}' ist nicht mehr m\xF6glich",
@@ -1764,6 +1930,7 @@ var ERROR_MESSAGES_DE = {
1764
1930
  PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "Unter '{account}' ist kein Konto eines Zahlungsanbieters eingerichtet.",
1765
1931
  PAYMENT_CALLBACK_REJECTED: "Die R\xFCckmeldung des Zahlungsanbieters lie\xDF sich nicht pr\xFCfen.",
1766
1932
  PAYMENT_RETURN_URL_NOT_ALLOWED: "Die {field} f\xFChrt auf eine Seite, zu der diese Installation nicht zur\xFCckleitet.",
1933
+ PAYMENT_GATEWAY_FAILED: "Der Zahlungsanbieter hat nicht wie erwartet geantwortet. Bitte versuche es in ein paar Minuten noch einmal.",
1767
1934
  // ── entitlement (code lives in upsell.types.ts) ──
1768
1935
  [FEATURE_NOT_LICENSED]: "Das Feature {featureKeys} ist im aktuellen Plan nicht enthalten.",
1769
1936
  // ── settings ──
@@ -1781,6 +1948,7 @@ var ERROR_MESSAGES_DE = {
1781
1948
  ERROR_MESSAGES_DE,
1782
1949
  ERROR_MESSAGES_EN,
1783
1950
  FEATURE_NOT_LICENSED,
1951
+ ForeignPaymentMethodReferenceError,
1784
1952
  LEGAL_IDENTITY_FIELDS,
1785
1953
  MARKETING_PRIORITY_MAX,
1786
1954
  MARKETING_PRIORITY_MIN,
@@ -1804,6 +1972,7 @@ var ERROR_MESSAGES_DE = {
1804
1972
  REGISTRATION_STEP_BY_STATUS,
1805
1973
  SETTINGS_ERROR_CODES,
1806
1974
  SETUP_ERROR_CODES,
1975
+ SUBSCRIBER_ACCOUNT_CAPABILITY,
1807
1976
  SUBSCRIBER_ERROR_CODES,
1808
1977
  applyPromo,
1809
1978
  assertPersistenceCapabilities,
@@ -1825,6 +1994,7 @@ var ERROR_MESSAGES_DE = {
1825
1994
  formatErrorMessage,
1826
1995
  identityCorrectionDelta,
1827
1996
  isBundleRedundant,
1997
+ isForeignPaymentMethodReferenceError,
1828
1998
  isPaymentCallbackRejectedError,
1829
1999
  isPlatformUserExistsError,
1830
2000
  isVersionEditable,
@@ -1836,15 +2006,18 @@ var ERROR_MESSAGES_DE = {
1836
2006
  planCatalogSettingsOf,
1837
2007
  previousUtcDay,
1838
2008
  promoStatus,
2009
+ promotionOnPrice,
1839
2010
  readQuotaRecord,
1840
2011
  readQuotaValue,
1841
2012
  recordedIssuerIdentity,
2013
+ refuseForeignPaymentMethodReference,
1842
2014
  resolveBundleAvailability,
1843
2015
  resolveErrorMessage,
1844
2016
  sameLegalIdentity,
1845
2017
  selectChargeableBundles,
1846
2018
  settingsSubtreeOf,
1847
2019
  startOfUtcDay,
2020
+ subscriberChargeColumns,
1848
2021
  subscriberFromRegistration,
1849
2022
  subscriberPaymentMethodColumns,
1850
2023
  toBundleStemRow,
@@ -1852,8 +2025,10 @@ var ERROR_MESSAGES_DE = {
1852
2025
  toPlanRow,
1853
2026
  toPlanVersionRow,
1854
2027
  toRunningContractIssuer,
2028
+ toSubscriberChargeRecord,
1855
2029
  toSubscriberCorrectionRecord,
1856
2030
  toSubscriberPaymentMethodRecord,
1857
2031
  toSubscriberRecord,
2032
+ toSubscriptionBundleRecord,
1858
2033
  toSubscriptionContractRecord
1859
2034
  });