@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/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 - promo.value)) / 100,
99
+ discounted: Math.round(basePrice * (100 - pct)) / 100,
95
100
  original: basePrice,
96
- pct: promo.value
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.max(0, basePrice - promo.value),
108
+ discounted: Math.round((basePrice - saved) * 100) / 100,
103
109
  original: basePrice,
104
- saved: promo.value
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
- function oneOf(allowed, value, column, id) {
1109
- const match = allowed.find((entry) => entry === value);
1110
- if (match === void 0) {
1111
- throw new Error(
1112
- `subscriber_payment_methods row '${id}' holds ${column} '${value}', which is none of ${allowed.join(", ")}.`
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
- return match;
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.20",
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",