@saasicat/core 1.0.0-rc.20 → 1.0.0-rc.21
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 +73 -8
- package/dist/index.d.cts +330 -13
- package/dist/index.d.ts +330 -13
- package/dist/index.js +69 -8
- package/package.json +1 -1
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
369e8bc186ed4a646394a1f9b7c7dcc8605204b4bae0e8c40edc4f3b5dd5e378
|
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,
|
|
@@ -74,6 +75,7 @@ __export(index_exports, {
|
|
|
74
75
|
formatErrorMessage: () => formatErrorMessage,
|
|
75
76
|
identityCorrectionDelta: () => identityCorrectionDelta,
|
|
76
77
|
isBundleRedundant: () => isBundleRedundant,
|
|
78
|
+
isForeignPaymentMethodReferenceError: () => isForeignPaymentMethodReferenceError,
|
|
77
79
|
isPaymentCallbackRejectedError: () => isPaymentCallbackRejectedError,
|
|
78
80
|
isPlatformUserExistsError: () => isPlatformUserExistsError,
|
|
79
81
|
isVersionEditable: () => isVersionEditable,
|
|
@@ -85,9 +87,11 @@ __export(index_exports, {
|
|
|
85
87
|
planCatalogSettingsOf: () => planCatalogSettingsOf,
|
|
86
88
|
previousUtcDay: () => previousUtcDay,
|
|
87
89
|
promoStatus: () => promoStatus,
|
|
90
|
+
promotionOnPrice: () => promotionOnPrice,
|
|
88
91
|
readQuotaRecord: () => readQuotaRecord,
|
|
89
92
|
readQuotaValue: () => readQuotaValue,
|
|
90
93
|
recordedIssuerIdentity: () => recordedIssuerIdentity,
|
|
94
|
+
refuseForeignPaymentMethodReference: () => refuseForeignPaymentMethodReference,
|
|
91
95
|
resolveBundleAvailability: () => resolveBundleAvailability,
|
|
92
96
|
resolveErrorMessage: () => resolveErrorMessage,
|
|
93
97
|
sameLegalIdentity: () => sameLegalIdentity,
|
|
@@ -197,27 +201,33 @@ function pickActivePromo(promotions, targetKey, locale, cycle, today = /* @__PUR
|
|
|
197
201
|
return matches[0] ?? null;
|
|
198
202
|
}
|
|
199
203
|
function applyPromo(promo, basePrice) {
|
|
204
|
+
const result = boundedPromo(promo, basePrice);
|
|
205
|
+
return result && result.discounted < result.original ? result : null;
|
|
206
|
+
}
|
|
207
|
+
function boundedPromo(promo, basePrice) {
|
|
200
208
|
if (!promo || basePrice === null || basePrice === void 0) return null;
|
|
201
209
|
if (promo.type === "percent" && typeof promo.value === "number") {
|
|
210
|
+
const pct = withinBounds(promo.value, 100);
|
|
202
211
|
return {
|
|
203
212
|
kind: "percent",
|
|
204
|
-
discounted: Math.round(basePrice * (100 -
|
|
213
|
+
discounted: Math.round(basePrice * (100 - pct)) / 100,
|
|
205
214
|
original: basePrice,
|
|
206
|
-
pct
|
|
215
|
+
pct
|
|
207
216
|
};
|
|
208
217
|
}
|
|
209
218
|
if (promo.type === "amount" && typeof promo.value === "number") {
|
|
219
|
+
const saved = withinBounds(promo.value, basePrice);
|
|
210
220
|
return {
|
|
211
221
|
kind: "amount",
|
|
212
|
-
discounted: Math.
|
|
222
|
+
discounted: Math.round((basePrice - saved) * 100) / 100,
|
|
213
223
|
original: basePrice,
|
|
214
|
-
saved
|
|
224
|
+
saved
|
|
215
225
|
};
|
|
216
226
|
}
|
|
217
|
-
if (promo.type === "intro" && typeof promo.value === "object") {
|
|
227
|
+
if (promo.type === "intro" && typeof promo.value === "object" && promo.value !== null) {
|
|
218
228
|
return {
|
|
219
229
|
kind: "intro",
|
|
220
|
-
discounted: promo.value.price,
|
|
230
|
+
discounted: withinBounds(promo.value.price, basePrice),
|
|
221
231
|
original: basePrice,
|
|
222
232
|
months: promo.value.months
|
|
223
233
|
};
|
|
@@ -227,6 +237,14 @@ function applyPromo(promo, basePrice) {
|
|
|
227
237
|
}
|
|
228
238
|
return null;
|
|
229
239
|
}
|
|
240
|
+
function withinBounds(value, upper) {
|
|
241
|
+
return Math.min(Math.max(value, 0), Math.max(upper, 0));
|
|
242
|
+
}
|
|
243
|
+
function promotionOnPrice(promotions, targetKey, locale, cycle, basePrice, today = /* @__PURE__ */ new Date(), targetType = "PLAN") {
|
|
244
|
+
const promotion = pickActivePromo(promotions, targetKey, locale, cycle, today, targetType);
|
|
245
|
+
const result = applyPromo(promotion, basePrice);
|
|
246
|
+
return promotion && result ? { promotion, result } : null;
|
|
247
|
+
}
|
|
230
248
|
|
|
231
249
|
// src/quota-value.ts
|
|
232
250
|
function readQuotaValue(value) {
|
|
@@ -523,6 +541,12 @@ var CATALOG_ERROR_CODES = {
|
|
|
523
541
|
FEATURE_NOT_FOUND: "FEATURE_NOT_FOUND",
|
|
524
542
|
QUOTA_NOT_FOUND: "QUOTA_NOT_FOUND",
|
|
525
543
|
PROMOTION_NOT_FOUND: "PROMOTION_NOT_FOUND",
|
|
544
|
+
/**
|
|
545
|
+
* A promotion's value is not one its type takes: a percentage above 0 and
|
|
546
|
+
* at most 100, an amount above 0, an intro price of at least 0 for a whole
|
|
547
|
+
* number of months, or a whole number of free months.
|
|
548
|
+
*/
|
|
549
|
+
PROMOTION_VALUE_INVALID: "PROMOTION_VALUE_INVALID",
|
|
526
550
|
MARKETING_PROJECTION_NOT_FOUND: "MARKETING_PROJECTION_NOT_FOUND",
|
|
527
551
|
// ── already exists ──
|
|
528
552
|
PLAN_ALREADY_EXISTS: "PLAN_ALREADY_EXISTS",
|
|
@@ -682,6 +706,13 @@ var CONTRACT_ERROR_CODES = {
|
|
|
682
706
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH",
|
|
683
707
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT",
|
|
684
708
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH",
|
|
709
|
+
/**
|
|
710
|
+
* The lines do not add up to a total the contract states, counted as often
|
|
711
|
+
* as each falls due in one period. `field` names the total.
|
|
712
|
+
*/
|
|
713
|
+
SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP",
|
|
714
|
+
/** Something that takes money off states a negative amount. */
|
|
715
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE",
|
|
685
716
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START",
|
|
686
717
|
// ── stage 3 ──
|
|
687
718
|
CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND",
|
|
@@ -1224,6 +1255,30 @@ function oneOf(allowed, value, column, id) {
|
|
|
1224
1255
|
}
|
|
1225
1256
|
return match;
|
|
1226
1257
|
}
|
|
1258
|
+
var FOREIGN_PAYMENT_METHOD_REFERENCE = "FOREIGN_PAYMENT_METHOD_REFERENCE";
|
|
1259
|
+
var ForeignPaymentMethodReferenceError = class extends Error {
|
|
1260
|
+
constructor(gatewayAccount, paymentMethodRef) {
|
|
1261
|
+
super(
|
|
1262
|
+
`Payment method '${paymentMethodRef}' of account '${gatewayAccount}' belongs to another subscriber. A reference belongs to exactly one, so it is not handed out.`
|
|
1263
|
+
);
|
|
1264
|
+
this.gatewayAccount = gatewayAccount;
|
|
1265
|
+
this.paymentMethodRef = paymentMethodRef;
|
|
1266
|
+
this.name = "ForeignPaymentMethodReferenceError";
|
|
1267
|
+
}
|
|
1268
|
+
gatewayAccount;
|
|
1269
|
+
paymentMethodRef;
|
|
1270
|
+
code = FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
1271
|
+
};
|
|
1272
|
+
function isForeignPaymentMethodReferenceError(error) {
|
|
1273
|
+
return error instanceof Error && error.code === FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
1274
|
+
}
|
|
1275
|
+
function refuseForeignPaymentMethodReference(recorded, subscriberId) {
|
|
1276
|
+
if (recorded.subscriberId === subscriberId) return;
|
|
1277
|
+
throw new ForeignPaymentMethodReferenceError(
|
|
1278
|
+
recorded.gatewayAccount,
|
|
1279
|
+
recorded.paymentMethodRef
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1227
1282
|
|
|
1228
1283
|
// src/subscription-contract-mapping.ts
|
|
1229
1284
|
function toSubscriptionContractRecord(row, lineItems) {
|
|
@@ -1434,6 +1489,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
1434
1489
|
FEATURE_NOT_FOUND: "Feature '{featureKey}' not found",
|
|
1435
1490
|
QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found",
|
|
1436
1491
|
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' not found",
|
|
1492
|
+
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
1493
|
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' not found",
|
|
1438
1494
|
PLAN_ALREADY_EXISTS: "Plan '{planKey}' already exists",
|
|
1439
1495
|
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' already exists",
|
|
@@ -1488,7 +1544,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
1488
1544
|
// ── promo ──
|
|
1489
1545
|
PROMO_CODE_NOT_FOUND: "Code not found",
|
|
1490
1546
|
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.",
|
|
1547
|
+
PROMO_CODE_HAS_REDEMPTIONS: "The code already has redemptions, or checkouts holding one \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
1492
1548
|
PROMO_CODE_NOT_REDEEMABLE: "Code cannot be redeemed: {reason}",
|
|
1493
1549
|
PROMO_CODE_FORMAT_INVALID: 'The code may only contain upper-case letters, digits, "-" and "_" (4\u201332 characters).',
|
|
1494
1550
|
PROMO_PERCENT_OUT_OF_RANGE: "The percentage must be between 0 and 100.",
|
|
@@ -1518,6 +1574,8 @@ var ERROR_MESSAGES_EN = {
|
|
|
1518
1574
|
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
1575
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil must be after the effectiveFrom of the contract.",
|
|
1520
1576
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "A line item must be booked in the currency its contract was priced in.",
|
|
1577
|
+
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}.",
|
|
1578
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "A subscription contract states a discount of {amount} at {field}; a discount takes money off and is never negative.",
|
|
1521
1579
|
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' not found",
|
|
1522
1580
|
CHECKOUT_OFFER_EXPIRED: "Checkout offer '{offerId}' has expired and cannot be {action}",
|
|
1523
1581
|
CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
|
|
@@ -1641,6 +1699,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1641
1699
|
FEATURE_NOT_FOUND: "Feature '{featureKey}' nicht gefunden",
|
|
1642
1700
|
QUOTA_NOT_FOUND: "Quota '{quotaKey}' nicht gefunden",
|
|
1643
1701
|
PROMOTION_NOT_FOUND: "Promotion '{promotionId}' nicht gefunden",
|
|
1702
|
+
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
1703
|
MARKETING_PROJECTION_NOT_FOUND: "MarketingProjection '{projectionId}' nicht gefunden",
|
|
1645
1704
|
PLAN_ALREADY_EXISTS: "Plan '{planKey}' gibt es bereits",
|
|
1646
1705
|
BUNDLE_ALREADY_EXISTS: "Bundle '{bundleKey}' gibt es bereits",
|
|
@@ -1695,7 +1754,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1695
1754
|
// ── promo ──
|
|
1696
1755
|
PROMO_CODE_NOT_FOUND: "Code nicht gefunden",
|
|
1697
1756
|
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.",
|
|
1757
|
+
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
1758
|
PROMO_CODE_NOT_REDEEMABLE: "Der Code l\xE4sst sich nicht einl\xF6sen: {reason}",
|
|
1700
1759
|
PROMO_CODE_FORMAT_INVALID: 'Der Code darf nur Gro\xDFbuchstaben, Ziffern, "-" und "_" enthalten (4\u201332 Zeichen).',
|
|
1701
1760
|
PROMO_PERCENT_OUT_OF_RANGE: "Der Prozentwert muss zwischen 0 und 100 liegen.",
|
|
@@ -1724,6 +1783,8 @@ var ERROR_MESSAGES_DE = {
|
|
|
1724
1783
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "taxAmount einer Position muss exakt priceGross minus priceNet sein.",
|
|
1725
1784
|
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
1785
|
SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "Eine Position muss in der W\xE4hrung gebucht sein, in der ihr Vertrag bepreist ist.",
|
|
1786
|
+
SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP: "Die Positionen eines Abo-Vertrags ergeben {lines} f\xFCr {field}, der Vertrag nennt aber {stated}.",
|
|
1787
|
+
SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE: "Ein Abo-Vertrag nennt bei {field} einen Rabatt von {amount}; ein Rabatt zieht Geld ab und ist nie negativ.",
|
|
1727
1788
|
SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "effectiveUntil muss nach dem effectiveFrom des Vertrags liegen.",
|
|
1728
1789
|
CHECKOUT_OFFER_NOT_FOUND: "CheckoutOffer '{offerId}' nicht gefunden",
|
|
1729
1790
|
CHECKOUT_OFFER_EXPIRED: "Checkout-Angebot '{offerId}' ist abgelaufen \u2014 '{action}' ist nicht mehr m\xF6glich",
|
|
@@ -1781,6 +1842,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1781
1842
|
ERROR_MESSAGES_DE,
|
|
1782
1843
|
ERROR_MESSAGES_EN,
|
|
1783
1844
|
FEATURE_NOT_LICENSED,
|
|
1845
|
+
ForeignPaymentMethodReferenceError,
|
|
1784
1846
|
LEGAL_IDENTITY_FIELDS,
|
|
1785
1847
|
MARKETING_PRIORITY_MAX,
|
|
1786
1848
|
MARKETING_PRIORITY_MIN,
|
|
@@ -1825,6 +1887,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1825
1887
|
formatErrorMessage,
|
|
1826
1888
|
identityCorrectionDelta,
|
|
1827
1889
|
isBundleRedundant,
|
|
1890
|
+
isForeignPaymentMethodReferenceError,
|
|
1828
1891
|
isPaymentCallbackRejectedError,
|
|
1829
1892
|
isPlatformUserExistsError,
|
|
1830
1893
|
isVersionEditable,
|
|
@@ -1836,9 +1899,11 @@ var ERROR_MESSAGES_DE = {
|
|
|
1836
1899
|
planCatalogSettingsOf,
|
|
1837
1900
|
previousUtcDay,
|
|
1838
1901
|
promoStatus,
|
|
1902
|
+
promotionOnPrice,
|
|
1839
1903
|
readQuotaRecord,
|
|
1840
1904
|
readQuotaValue,
|
|
1841
1905
|
recordedIssuerIdentity,
|
|
1906
|
+
refuseForeignPaymentMethodReference,
|
|
1842
1907
|
resolveBundleAvailability,
|
|
1843
1908
|
resolveErrorMessage,
|
|
1844
1909
|
sameLegalIdentity,
|