@saasicat/nest 1.0.0-rc.2 → 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/README.md +44 -8
- package/dist/.build-stamp +1 -1
- package/dist/_entries.cjs +10214 -4796
- package/dist/admin/index.d.cts +5 -4
- package/dist/admin/index.d.ts +5 -4
- package/dist/admin/index.js +13 -9
- package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
- package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
- package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
- package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
- package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
- package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
- package/dist/billing/index.d.cts +448 -108
- package/dist/billing/index.d.ts +448 -108
- package/dist/billing/index.js +108 -41
- package/dist/catalog/index.d.cts +74 -45
- package/dist/catalog/index.d.ts +74 -45
- package/dist/catalog/index.js +21 -13
- package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
- package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
- package/dist/checkout-offer/index.d.cts +17 -22
- package/dist/checkout-offer/index.d.ts +17 -22
- package/dist/checkout-offer/index.js +21 -7
- package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
- package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
- package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
- package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
- package/dist/chunk-34XVPR6P.js +222 -0
- package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
- package/dist/chunk-3RLWML2S.js +896 -0
- package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
- package/dist/chunk-55QEDAMX.js +946 -0
- package/dist/chunk-5CKC7O52.js +140 -0
- package/dist/chunk-6PJE7EWO.js +185 -0
- package/dist/chunk-7NJKVC5X.js +353 -0
- package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
- package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
- package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
- package/dist/chunk-FEQZCWUL.js +174 -0
- package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
- package/dist/chunk-G7RQO2XO.js +0 -0
- package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
- package/dist/chunk-GZTL64QK.js +7 -0
- package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
- package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
- package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
- package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
- package/dist/chunk-KPEMTBOP.js +26 -0
- package/dist/chunk-LF6J4YYN.js +301 -0
- package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
- package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
- package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
- package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
- package/dist/chunk-QPVDCKYS.js +702 -0
- package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
- package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
- package/dist/chunk-SZ7RFPXA.js +10 -0
- package/dist/chunk-TBBZWZQT.js +98 -0
- package/dist/chunk-TOEFN7DN.js +295 -0
- package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
- package/dist/chunk-WOEJ6K7M.js +55 -0
- package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
- package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
- package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
- package/dist/discovery/index.d.cts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/discovery/index.js +6 -6
- package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
- package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
- package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
- package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
- package/dist/entitlement/index.d.cts +27 -4
- package/dist/entitlement/index.d.ts +27 -4
- package/dist/entitlement/index.js +13 -8
- package/dist/index.d.cts +209 -28
- package/dist/index.d.ts +209 -28
- package/dist/index.js +285 -163
- package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
- package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
- package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
- package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
- package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
- package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
- package/dist/payments/index.cjs +4 -0
- package/dist/payments/index.d.cts +187 -0
- package/dist/payments/index.d.ts +187 -0
- package/dist/payments/index.js +141 -0
- package/dist/payments.module-BdUq9ot_.d.cts +57 -0
- package/dist/payments.module-CQGv_VG7.d.ts +57 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
- package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
- package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
- package/dist/platform/index.d.cts +52 -23
- package/dist/platform/index.d.ts +52 -23
- package/dist/platform/index.js +57 -36
- package/dist/promo/index.d.cts +23 -8
- package/dist/promo/index.d.ts +23 -8
- package/dist/promo/index.js +15 -7
- package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
- package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
- package/dist/promo.service-BYBu6dy3.d.cts +215 -0
- package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
- package/dist/registration/index.d.cts +104 -47
- package/dist/registration/index.d.ts +104 -47
- package/dist/registration/index.js +15 -9
- package/dist/subscriber/index.cjs +4 -0
- package/dist/subscriber/index.d.cts +28 -0
- package/dist/subscriber/index.d.ts +28 -0
- package/dist/subscriber/index.js +16 -0
- package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
- package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
- package/dist/subscription-contract/index.d.cts +4 -2
- package/dist/subscription-contract/index.d.ts +4 -2
- package/dist/subscription-contract/index.js +13 -5
- package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
- package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
- package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
- package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
- package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
- package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
- package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
- package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
- package/dist/testing/index.d.cts +44 -17
- package/dist/testing/index.d.ts +44 -17
- package/dist/testing/index.js +189 -40
- package/package.json +24 -4
- package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
- package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
- package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
- package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
- package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
- package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
- package/dist/chunk-57V6ZTI6.js +0 -22
- package/dist/chunk-BSK6YBLI.js +0 -87
- package/dist/chunk-DQKCK7DX.js +0 -217
- package/dist/chunk-VXEYLNIB.js +0 -632
- package/dist/chunk-WUDYIPYH.js +0 -715
- package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
- package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
- package/dist/promo.service-BJbKAmw3.d.cts +0 -112
- package/dist/promo.service-BJbKAmw3.d.ts +0 -112
- package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
- package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
- package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
- package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
- /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
- /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
- /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
- /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
package/dist/chunk-VXEYLNIB.js
DELETED
|
@@ -1,632 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PLAN_CATALOG_TOKEN,
|
|
3
|
-
computeIncludedVat,
|
|
4
|
-
getPlanPriceGross,
|
|
5
|
-
round2
|
|
6
|
-
} from "./chunk-DQKCK7DX.js";
|
|
7
|
-
import {
|
|
8
|
-
__name
|
|
9
|
-
} from "./chunk-SHUYVCID.js";
|
|
10
|
-
|
|
11
|
-
// src/promo/calculator.ts
|
|
12
|
-
function computeDiscountGross(plan, code) {
|
|
13
|
-
const v = Number(code.value);
|
|
14
|
-
if (code.valueType === "PERCENT") {
|
|
15
|
-
return round2(plan.gross * v / 100);
|
|
16
|
-
}
|
|
17
|
-
return round2(v);
|
|
18
|
-
}
|
|
19
|
-
__name(computeDiscountGross, "computeDiscountGross");
|
|
20
|
-
function computeDiscountedGross(planGross, discountGross) {
|
|
21
|
-
return round2(planGross - discountGross);
|
|
22
|
-
}
|
|
23
|
-
__name(computeDiscountedGross, "computeDiscountedGross");
|
|
24
|
-
function computeRegularStartsAt(startsAt, cycle, durationType, durationValue) {
|
|
25
|
-
const result = new Date(startsAt);
|
|
26
|
-
if (durationType === "ONCE") {
|
|
27
|
-
return addCycles(result, cycle, 1);
|
|
28
|
-
}
|
|
29
|
-
if (durationType === "MONTHS") {
|
|
30
|
-
result.setMonth(result.getMonth() + (durationValue ?? 0));
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
return addCycles(result, cycle, durationValue ?? 0);
|
|
34
|
-
}
|
|
35
|
-
__name(computeRegularStartsAt, "computeRegularStartsAt");
|
|
36
|
-
function addCycles(date, cycle, n) {
|
|
37
|
-
const r = new Date(date);
|
|
38
|
-
if (cycle === "YEARLY") {
|
|
39
|
-
r.setFullYear(r.getFullYear() + n);
|
|
40
|
-
} else {
|
|
41
|
-
r.setMonth(r.getMonth() + n);
|
|
42
|
-
}
|
|
43
|
-
return r;
|
|
44
|
-
}
|
|
45
|
-
__name(addCycles, "addCycles");
|
|
46
|
-
var LEGACY_LABEL_LOCALE = "de-DE";
|
|
47
|
-
var LEGACY_LABEL_CURRENCY = "EUR";
|
|
48
|
-
var ICU_NON_BREAKING_SPACES = /[\u00A0\u202F]/g;
|
|
49
|
-
function requireSupportedLocale(locale) {
|
|
50
|
-
if (Intl.NumberFormat.supportedLocalesOf([
|
|
51
|
-
locale
|
|
52
|
-
]).length === 0) {
|
|
53
|
-
throw new RangeError(`buildLabel: locale "${locale}" names a language this runtime cannot serve. It would be formatted as "${new Intl.NumberFormat(locale).resolvedOptions().locale}" instead \u2014 a language nobody chose. Pass a tag the runtime serves.`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
__name(requireSupportedLocale, "requireSupportedLocale");
|
|
57
|
-
function formatPromoValue(value, valueType, { locale = LEGACY_LABEL_LOCALE, currency = LEGACY_LABEL_CURRENCY }) {
|
|
58
|
-
requireSupportedLocale(locale);
|
|
59
|
-
const numberFormat = valueType === "PERCENT" ? {
|
|
60
|
-
style: "unit",
|
|
61
|
-
unit: "percent"
|
|
62
|
-
} : {
|
|
63
|
-
style: "currency",
|
|
64
|
-
currency
|
|
65
|
-
};
|
|
66
|
-
return new Intl.NumberFormat(locale, numberFormat).format(value).replace(ICU_NON_BREAKING_SPACES, " ");
|
|
67
|
-
}
|
|
68
|
-
__name(formatPromoValue, "formatPromoValue");
|
|
69
|
-
function buildLabel(code, cycle, options = {}) {
|
|
70
|
-
const value = Number(code.value);
|
|
71
|
-
const valueStr = formatPromoValue(value, code.valueType, options);
|
|
72
|
-
if (code.durationType === "ONCE") {
|
|
73
|
-
return `${valueStr} once`;
|
|
74
|
-
}
|
|
75
|
-
if (code.durationType === "MONTHS") {
|
|
76
|
-
const m = code.durationValue ?? 0;
|
|
77
|
-
return m === 1 ? `${valueStr} for the first month` : `${valueStr} for ${m} months`;
|
|
78
|
-
}
|
|
79
|
-
const n = code.durationValue ?? 0;
|
|
80
|
-
if (n === 1) return cycle === "YEARLY" ? `${valueStr} for the first year` : `${valueStr} for the first month`;
|
|
81
|
-
return cycle === "YEARLY" ? `${valueStr} for the first ${n} years` : `${valueStr} for the first ${n} months`;
|
|
82
|
-
}
|
|
83
|
-
__name(buildLabel, "buildLabel");
|
|
84
|
-
|
|
85
|
-
// src/promo/promo.tokens.ts
|
|
86
|
-
var PROMO_CODE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoCodeRepository");
|
|
87
|
-
var PROMO_CODE_REDEMPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoCodeRedemptionRepository");
|
|
88
|
-
var PROMO_CODE_VALIDATION_LOG_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoCodeValidationLogRepository");
|
|
89
|
-
var PROMO_FIRST_TIME_CUSTOMER_CHECK_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoFirstTimeCustomerCheck");
|
|
90
|
-
var PROMO_SUBSCRIPTION_LOOKUP_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoSubscriptionLookup");
|
|
91
|
-
var PROMO_REVENUE_DEDUCTION_AGGREGATOR_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoRevenueDeductionAggregator");
|
|
92
|
-
var PROMO_TRANSACTION_RUNNER_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoTransactionRunner");
|
|
93
|
-
var PROMO_SERVICE_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PromoServiceConfig");
|
|
94
|
-
|
|
95
|
-
// src/promo/promo.service.ts
|
|
96
|
-
import { BadRequestException, Inject, Injectable, NotFoundException } from "@nestjs/common";
|
|
97
|
-
import { BILLING_ERROR_CODES, PROMO_ERROR_CODES } from "@saasicat/core";
|
|
98
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
99
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
100
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
101
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
102
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
103
|
-
}
|
|
104
|
-
__name(_ts_decorate, "_ts_decorate");
|
|
105
|
-
function _ts_metadata(k, v) {
|
|
106
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
107
|
-
}
|
|
108
|
-
__name(_ts_metadata, "_ts_metadata");
|
|
109
|
-
function _ts_param(paramIndex, decorator) {
|
|
110
|
-
return function(target, key) {
|
|
111
|
-
decorator(target, key, paramIndex);
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
__name(_ts_param, "_ts_param");
|
|
115
|
-
var CODE_MIN_LENGTH = 4;
|
|
116
|
-
var CODE_MAX_LENGTH = 32;
|
|
117
|
-
var CODE_PATTERN = /^[A-Z0-9_-]{4,32}$/;
|
|
118
|
-
var PERCENT_MIN_EXCLUSIVE = 0;
|
|
119
|
-
var PERCENT_MAX = 100;
|
|
120
|
-
var DURATION_MIN = 1;
|
|
121
|
-
var DURATION_MAX = 24;
|
|
122
|
-
function notRedeemable(reason) {
|
|
123
|
-
return new BadRequestException({
|
|
124
|
-
code: PROMO_ERROR_CODES.PROMO_CODE_NOT_REDEEMABLE,
|
|
125
|
-
message: `Code cannot be redeemed: ${reason}`,
|
|
126
|
-
params: {
|
|
127
|
-
reason
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
__name(notRedeemable, "notRedeemable");
|
|
132
|
-
var PromoCodesService = class {
|
|
133
|
-
static {
|
|
134
|
-
__name(this, "PromoCodesService");
|
|
135
|
-
}
|
|
136
|
-
promoRepo;
|
|
137
|
-
redemptionRepo;
|
|
138
|
-
validationLogRepo;
|
|
139
|
-
firstTimeCheck;
|
|
140
|
-
subscriptionLookup;
|
|
141
|
-
revenueAggregator;
|
|
142
|
-
transactionRunner;
|
|
143
|
-
planCatalog;
|
|
144
|
-
config;
|
|
145
|
-
constructor(promoRepo, redemptionRepo, validationLogRepo, firstTimeCheck, subscriptionLookup, revenueAggregator, transactionRunner, planCatalog, config) {
|
|
146
|
-
this.promoRepo = promoRepo;
|
|
147
|
-
this.redemptionRepo = redemptionRepo;
|
|
148
|
-
this.validationLogRepo = validationLogRepo;
|
|
149
|
-
this.firstTimeCheck = firstTimeCheck;
|
|
150
|
-
this.subscriptionLookup = subscriptionLookup;
|
|
151
|
-
this.revenueAggregator = revenueAggregator;
|
|
152
|
-
this.transactionRunner = transactionRunner;
|
|
153
|
-
this.planCatalog = planCatalog;
|
|
154
|
-
this.config = config;
|
|
155
|
-
}
|
|
156
|
-
// ─── ADMIN: Creation / Editing ─────────────────────────────────────────
|
|
157
|
-
async create(input) {
|
|
158
|
-
const code = input.code.trim().toUpperCase();
|
|
159
|
-
if (!CODE_PATTERN.test(code)) {
|
|
160
|
-
throw new BadRequestException({
|
|
161
|
-
code: PROMO_ERROR_CODES.PROMO_CODE_FORMAT_INVALID,
|
|
162
|
-
message: 'The code may only contain upper-case letters, digits, "-" and "_" (4\u201332 characters).',
|
|
163
|
-
params: {
|
|
164
|
-
promoCode: code,
|
|
165
|
-
minLength: CODE_MIN_LENGTH,
|
|
166
|
-
maxLength: CODE_MAX_LENGTH
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
if (input.valueType === "PERCENT") {
|
|
171
|
-
if (!(input.value > PERCENT_MIN_EXCLUSIVE && input.value <= PERCENT_MAX)) {
|
|
172
|
-
throw new BadRequestException({
|
|
173
|
-
code: PROMO_ERROR_CODES.PROMO_PERCENT_OUT_OF_RANGE,
|
|
174
|
-
message: "The percentage must be between 0 and 100.",
|
|
175
|
-
params: {
|
|
176
|
-
value: input.value,
|
|
177
|
-
min: PERCENT_MIN_EXCLUSIVE,
|
|
178
|
-
max: PERCENT_MAX
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
} else if (!(input.value > 0)) {
|
|
183
|
-
throw new BadRequestException({
|
|
184
|
-
code: PROMO_ERROR_CODES.PROMO_AMOUNT_NOT_POSITIVE,
|
|
185
|
-
message: "The amount must be positive.",
|
|
186
|
-
params: {
|
|
187
|
-
value: input.value,
|
|
188
|
-
min: 0
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
if (input.durationType === "ONCE") {
|
|
193
|
-
if (input.durationValue != null) {
|
|
194
|
-
throw new BadRequestException({
|
|
195
|
-
code: PROMO_ERROR_CODES.PROMO_ONE_OFF_WITH_DURATION,
|
|
196
|
-
message: "A one-off discount must not set a duration.",
|
|
197
|
-
params: {
|
|
198
|
-
durationValue: input.durationValue
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
const v = input.durationValue;
|
|
204
|
-
if (v == null || v < DURATION_MIN || v > DURATION_MAX) {
|
|
205
|
-
throw new BadRequestException({
|
|
206
|
-
code: PROMO_ERROR_CODES.PROMO_DURATION_INVALID,
|
|
207
|
-
message: "Invalid duration (at most 24 months or billing periods).",
|
|
208
|
-
params: {
|
|
209
|
-
durationValue: v ?? null,
|
|
210
|
-
min: DURATION_MIN,
|
|
211
|
-
max: DURATION_MAX
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (input.validFrom && input.validUntil && input.validFrom >= input.validUntil) {
|
|
217
|
-
throw new BadRequestException({
|
|
218
|
-
code: PROMO_ERROR_CODES.PROMO_VALIDITY_WINDOW_INVALID,
|
|
219
|
-
message: "Invalid validity window.",
|
|
220
|
-
params: {
|
|
221
|
-
validFrom: input.validFrom.toISOString(),
|
|
222
|
-
validUntil: input.validUntil.toISOString()
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
const plans = input.appliesToPlans ?? [];
|
|
227
|
-
const blocked = this.config.nonRedeemablePlans ?? [];
|
|
228
|
-
for (const p of plans) {
|
|
229
|
-
if (blocked.includes(p)) {
|
|
230
|
-
throw new BadRequestException({
|
|
231
|
-
code: PROMO_ERROR_CODES.PROMO_PLAN_NOT_DISCOUNTABLE,
|
|
232
|
-
message: `${p}-Plan cannot be discounted.`,
|
|
233
|
-
params: {
|
|
234
|
-
plan: p
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (input.minimumPlanAmountGross != null && input.minimumPlanAmountGross <= 0) {
|
|
240
|
-
throw new BadRequestException({
|
|
241
|
-
code: PROMO_ERROR_CODES.PROMO_MIN_AMOUNT_NOT_POSITIVE,
|
|
242
|
-
message: "The minimum plan gross amount must be positive.",
|
|
243
|
-
params: {
|
|
244
|
-
value: input.minimumPlanAmountGross,
|
|
245
|
-
min: 0
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
if (input.valueType === "ABSOLUTE" && !input.allowZeroInvoice) {
|
|
250
|
-
const lowestApplicableGross = this.lowestApplicablePlanGross(plans);
|
|
251
|
-
if (lowestApplicableGross != null && input.value >= lowestApplicableGross) {
|
|
252
|
-
throw new BadRequestException({
|
|
253
|
-
code: PROMO_ERROR_CODES.PROMO_WOULD_PRODUCE_ZERO_INVOICE,
|
|
254
|
-
message: "For absolute amounts the discount must stay below the lowest applicable plan price, or allowZeroInvoice must be enabled.",
|
|
255
|
-
params: {
|
|
256
|
-
value: input.value,
|
|
257
|
-
lowestApplicablePlanGross: lowestApplicableGross
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const exists = await this.promoRepo.findByCode(code);
|
|
263
|
-
if (exists) {
|
|
264
|
-
throw new BadRequestException({
|
|
265
|
-
code: PROMO_ERROR_CODES.PROMO_CODE_ALREADY_EXISTS,
|
|
266
|
-
message: "The code already exists.",
|
|
267
|
-
params: {
|
|
268
|
-
promoCode: code
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
return this.promoRepo.create({
|
|
273
|
-
...input,
|
|
274
|
-
code
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
async update(id, input) {
|
|
278
|
-
const existing = await this.promoRepo.findById(id);
|
|
279
|
-
if (!existing) {
|
|
280
|
-
throw new NotFoundException({
|
|
281
|
-
code: PROMO_ERROR_CODES.PROMO_CODE_NOT_FOUND,
|
|
282
|
-
message: "Code not found",
|
|
283
|
-
params: {
|
|
284
|
-
promoCodeId: id,
|
|
285
|
-
promoCode: null
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
if (input.maxRedemptions != null && existing.maxRedemptions != null) {
|
|
290
|
-
if (input.maxRedemptions < existing.maxRedemptions) {
|
|
291
|
-
throw new BadRequestException({
|
|
292
|
-
code: PROMO_ERROR_CODES.PROMO_MAX_REDEMPTIONS_LOWERED,
|
|
293
|
-
message: "maxRedemptions cannot be lowered.",
|
|
294
|
-
params: {
|
|
295
|
-
current: existing.maxRedemptions,
|
|
296
|
-
requested: input.maxRedemptions
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return this.promoRepo.update(id, input);
|
|
302
|
-
}
|
|
303
|
-
async softDelete(id) {
|
|
304
|
-
const redemptions = await this.redemptionRepo.countByPromoCode(id);
|
|
305
|
-
if (redemptions > 0) {
|
|
306
|
-
throw new BadRequestException({
|
|
307
|
-
code: PROMO_ERROR_CODES.PROMO_CODE_HAS_REDEMPTIONS,
|
|
308
|
-
message: "The code already has redemptions \u2014 it cannot be soft-deleted. Pause it instead.",
|
|
309
|
-
params: {
|
|
310
|
-
promoCodeId: id,
|
|
311
|
-
redemptions
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
await this.promoRepo.softDelete(id);
|
|
316
|
-
}
|
|
317
|
-
async findAll(filter = {}) {
|
|
318
|
-
await this.lazyExpire();
|
|
319
|
-
return this.promoRepo.findMany(filter);
|
|
320
|
-
}
|
|
321
|
-
async findOne(id) {
|
|
322
|
-
await this.lazyExpire();
|
|
323
|
-
const code = await this.promoRepo.findById(id);
|
|
324
|
-
if (!code || code.deletedAt) {
|
|
325
|
-
throw new NotFoundException({
|
|
326
|
-
code: PROMO_ERROR_CODES.PROMO_CODE_NOT_FOUND,
|
|
327
|
-
message: "Code not found",
|
|
328
|
-
params: {
|
|
329
|
-
promoCodeId: id,
|
|
330
|
-
promoCode: null
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
return code;
|
|
335
|
-
}
|
|
336
|
-
async stats(id) {
|
|
337
|
-
const code = await this.findOne(id);
|
|
338
|
-
const [active, total, reversed, expired, validations, revenueDeductionGross] = await Promise.all([
|
|
339
|
-
this.redemptionRepo.countByPromoCode(id, "ACTIVE"),
|
|
340
|
-
this.redemptionRepo.countByPromoCode(id),
|
|
341
|
-
this.redemptionRepo.countByPromoCode(id, "REVERSED"),
|
|
342
|
-
this.redemptionRepo.countByPromoCode(id, "EXPIRED"),
|
|
343
|
-
this.validationLogRepo.countValid(id),
|
|
344
|
-
this.revenueAggregator.sumGrossForPromoCode(id)
|
|
345
|
-
]);
|
|
346
|
-
return {
|
|
347
|
-
code,
|
|
348
|
-
validations,
|
|
349
|
-
redemptions: {
|
|
350
|
-
active,
|
|
351
|
-
total,
|
|
352
|
-
reversed,
|
|
353
|
-
expired
|
|
354
|
-
},
|
|
355
|
-
revenueDeductionGross
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
async listRedemptions(promoCodeId) {
|
|
359
|
-
return this.redemptionRepo.listByPromoCode(promoCodeId);
|
|
360
|
-
}
|
|
361
|
-
// ─── PUBLIC: Preview / Validation ──────────────────────────────────────
|
|
362
|
-
async preview(input) {
|
|
363
|
-
const code = input.code.trim().toUpperCase();
|
|
364
|
-
if (!CODE_PATTERN.test(code)) {
|
|
365
|
-
await this.validationLogRepo.log({
|
|
366
|
-
promoCodeId: null,
|
|
367
|
-
codeAttempt: code,
|
|
368
|
-
result: "NOT_FOUND",
|
|
369
|
-
ipHash: input.ipHash,
|
|
370
|
-
sessionId: input.sessionId
|
|
371
|
-
});
|
|
372
|
-
return {
|
|
373
|
-
valid: false,
|
|
374
|
-
reason: "NOT_FOUND"
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
await this.lazyExpire();
|
|
378
|
-
const promo = await this.promoRepo.findByCode(code);
|
|
379
|
-
const reason = this.checkEligibility(promo, input) ?? await this.checkFirstTimeCustomer(promo, input.email);
|
|
380
|
-
if (reason) {
|
|
381
|
-
await this.validationLogRepo.log({
|
|
382
|
-
promoCodeId: promo?.id ?? null,
|
|
383
|
-
codeAttempt: code,
|
|
384
|
-
result: reason,
|
|
385
|
-
ipHash: input.ipHash,
|
|
386
|
-
sessionId: input.sessionId
|
|
387
|
-
});
|
|
388
|
-
return {
|
|
389
|
-
valid: false,
|
|
390
|
-
reason
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
const planGross = getPlanPriceGross(this.planCatalog, input.planId, input.billingCycle);
|
|
394
|
-
const discountGross = Math.min(computeDiscountGross({
|
|
395
|
-
gross: planGross
|
|
396
|
-
}, promo), planGross);
|
|
397
|
-
const discountedGross = computeDiscountedGross(planGross, discountGross);
|
|
398
|
-
const includedVat = computeIncludedVat(discountedGross, this.planCatalog.vatRate);
|
|
399
|
-
if (!promo.allowZeroInvoice && discountedGross <= 0) {
|
|
400
|
-
await this.validationLogRepo.log({
|
|
401
|
-
promoCodeId: promo.id,
|
|
402
|
-
codeAttempt: code,
|
|
403
|
-
result: "WOULD_PRODUCE_ZERO_INVOICE",
|
|
404
|
-
ipHash: input.ipHash,
|
|
405
|
-
sessionId: input.sessionId
|
|
406
|
-
});
|
|
407
|
-
return {
|
|
408
|
-
valid: false,
|
|
409
|
-
reason: "WOULD_PRODUCE_ZERO_INVOICE"
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
const startsAt = /* @__PURE__ */ new Date();
|
|
413
|
-
const regularStartsAt = computeRegularStartsAt(startsAt, input.billingCycle, promo.durationType, promo.durationValue);
|
|
414
|
-
await this.validationLogRepo.log({
|
|
415
|
-
promoCodeId: promo.id,
|
|
416
|
-
codeAttempt: code,
|
|
417
|
-
result: "VALID",
|
|
418
|
-
ipHash: input.ipHash,
|
|
419
|
-
sessionId: input.sessionId
|
|
420
|
-
});
|
|
421
|
-
return {
|
|
422
|
-
valid: true,
|
|
423
|
-
code: promo.code,
|
|
424
|
-
label: buildLabel(promo, input.billingCycle),
|
|
425
|
-
discount: {
|
|
426
|
-
valueType: promo.valueType,
|
|
427
|
-
value: Number(promo.value).toFixed(2),
|
|
428
|
-
durationType: promo.durationType,
|
|
429
|
-
durationValue: promo.durationValue
|
|
430
|
-
},
|
|
431
|
-
price: {
|
|
432
|
-
originalGross: planGross.toFixed(2),
|
|
433
|
-
discountGross: discountGross.toFixed(2),
|
|
434
|
-
discountedGross: discountedGross.toFixed(2),
|
|
435
|
-
includedVat: includedVat.toFixed(2),
|
|
436
|
-
nextRegularAmountGross: planGross.toFixed(2),
|
|
437
|
-
regularStartsAt: regularStartsAt ? regularStartsAt.toISOString() : null
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
// ─── Onboarding path: atomic redemption ────────────────────────────────
|
|
442
|
-
/**
|
|
443
|
-
* Wraps `redeemInTransaction` in its own transaction-runner call —
|
|
444
|
-
* the default path when the caller has no external tx context
|
|
445
|
-
* (e.g. POST /billing/promo/redeem as a stand-alone endpoint).
|
|
446
|
-
*/
|
|
447
|
-
async redeem(input) {
|
|
448
|
-
return this.transactionRunner.run((tx) => this.redeemInTransaction(input, tx));
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Redeems a promo code within an EXTERNAL transaction — the caller
|
|
452
|
-
* (typically `TenantSubscriptionWritePort.applyOnboardingSelection`)
|
|
453
|
-
* already has `prisma.$transaction(...)` open and passes the `tx`
|
|
454
|
-
* context through. This lands plan change, add-on insert, and redemption
|
|
455
|
-
* insert in a single DB transaction (P10.1.1).
|
|
456
|
-
*/
|
|
457
|
-
async redeemInTransaction(input, tx) {
|
|
458
|
-
const code = input.code.trim().toUpperCase();
|
|
459
|
-
const promo = await this.promoRepo.findByCode(code, tx);
|
|
460
|
-
if (!promo || promo.deletedAt) {
|
|
461
|
-
throw new BadRequestException({
|
|
462
|
-
code: PROMO_ERROR_CODES.PROMO_CODE_NOT_FOUND,
|
|
463
|
-
// The lookup runs on the code string, so there is no id to report.
|
|
464
|
-
message: "Code not found",
|
|
465
|
-
params: {
|
|
466
|
-
promoCodeId: null,
|
|
467
|
-
promoCode: code
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
const sub = await this.subscriptionLookup.findById(input.subscriptionId, tx);
|
|
472
|
-
if (!sub) {
|
|
473
|
-
throw new NotFoundException({
|
|
474
|
-
code: BILLING_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
|
475
|
-
message: "Subscription not found",
|
|
476
|
-
params: {
|
|
477
|
-
tenantId: input.tenantId
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
if (sub.tenantId !== input.tenantId) {
|
|
482
|
-
throw new BadRequestException({
|
|
483
|
-
code: BILLING_ERROR_CODES.SUBSCRIPTION_TENANT_MISMATCH,
|
|
484
|
-
message: "Subscription does not belong to the tenant",
|
|
485
|
-
params: {
|
|
486
|
-
subscriptionId: sub.id,
|
|
487
|
-
tenantId: input.tenantId
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
const reason = this.checkEligibility(promo, {
|
|
492
|
-
code,
|
|
493
|
-
planId: sub.plan,
|
|
494
|
-
billingCycle: sub.billingCycle
|
|
495
|
-
});
|
|
496
|
-
if (reason) throw notRedeemable(reason);
|
|
497
|
-
const firstTimeReason = await this.checkFirstTimeCustomer(promo, input.email, {
|
|
498
|
-
requireEmail: true
|
|
499
|
-
});
|
|
500
|
-
if (firstTimeReason) {
|
|
501
|
-
throw notRedeemable(firstTimeReason);
|
|
502
|
-
}
|
|
503
|
-
const claimed = await this.promoRepo.claimSlot(promo.id, tx);
|
|
504
|
-
if (!claimed) {
|
|
505
|
-
throw notRedeemable("EXHAUSTED");
|
|
506
|
-
}
|
|
507
|
-
await this.promoRepo.markExhaustedIfFull(promo.id, tx);
|
|
508
|
-
const startsAt = sub.startedAt ?? /* @__PURE__ */ new Date();
|
|
509
|
-
const endsAt = promo.durationType === "ONCE" ? null : computeRegularStartsAt(startsAt, sub.billingCycle, promo.durationType, promo.durationValue);
|
|
510
|
-
return this.redemptionRepo.create({
|
|
511
|
-
promoCodeId: promo.id,
|
|
512
|
-
subscriptionId: sub.id,
|
|
513
|
-
tenantId: sub.tenantId,
|
|
514
|
-
appliedValueType: promo.valueType,
|
|
515
|
-
appliedValue: promo.value,
|
|
516
|
-
appliedDurationType: promo.durationType,
|
|
517
|
-
appliedDurationValue: promo.durationValue,
|
|
518
|
-
startsAt,
|
|
519
|
-
endsAt
|
|
520
|
-
}, tx);
|
|
521
|
-
}
|
|
522
|
-
async reverse(subscriptionId) {
|
|
523
|
-
return this.transactionRunner.run(async (tx) => {
|
|
524
|
-
const redemption = await this.redemptionRepo.findBySubscription(subscriptionId, tx);
|
|
525
|
-
if (!redemption || redemption.status !== "ACTIVE") return redemption;
|
|
526
|
-
const updated = await this.redemptionRepo.setReversed(redemption.id, tx);
|
|
527
|
-
await this.promoRepo.releaseSlot(redemption.promoCodeId, tx);
|
|
528
|
-
return updated;
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
// ─── Helpers ───────────────────────────────────────────────────────────
|
|
532
|
-
checkEligibility(promo, input) {
|
|
533
|
-
if (!promo || promo.deletedAt) return "NOT_FOUND";
|
|
534
|
-
if (promo.status === "EXPIRED") return "EXPIRED";
|
|
535
|
-
if (promo.status === "EXHAUSTED") return "EXHAUSTED";
|
|
536
|
-
if (promo.status === "PAUSED") return "PAUSED";
|
|
537
|
-
const now = /* @__PURE__ */ new Date();
|
|
538
|
-
if (promo.validFrom && promo.validFrom > now) return "EXPIRED";
|
|
539
|
-
if (promo.validUntil && promo.validUntil < now) return "EXPIRED";
|
|
540
|
-
const blocked = this.config.nonRedeemablePlans ?? [];
|
|
541
|
-
if (blocked.includes(input.planId)) {
|
|
542
|
-
return "PLAN_MISMATCH";
|
|
543
|
-
}
|
|
544
|
-
if (promo.appliesToPlans.length > 0 && !promo.appliesToPlans.includes(input.planId)) {
|
|
545
|
-
return "PLAN_MISMATCH";
|
|
546
|
-
}
|
|
547
|
-
if (promo.appliesToBilling && promo.appliesToBilling !== input.billingCycle) {
|
|
548
|
-
return "BILLING_MISMATCH";
|
|
549
|
-
}
|
|
550
|
-
const planGross = getPlanPriceGross(this.planCatalog, input.planId, input.billingCycle);
|
|
551
|
-
if (planGross == null) return "PLAN_MISMATCH";
|
|
552
|
-
if (promo.minimumPlanAmountGross && planGross < Number(promo.minimumPlanAmountGross)) {
|
|
553
|
-
return "BELOW_MINIMUM_AMOUNT";
|
|
554
|
-
}
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
async checkFirstTimeCustomer(promo, email, options = {}) {
|
|
558
|
-
if (!promo || !promo.firstTimeCustomersOnly) return null;
|
|
559
|
-
if (!email) return options.requireEmail ? "NOT_FIRST_TIME_CUSTOMER" : null;
|
|
560
|
-
const existing = await this.firstTimeCheck.hasExistingCustomerForEmail(email);
|
|
561
|
-
return existing ? "NOT_FIRST_TIME_CUSTOMER" : null;
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Lazy expiry: sets codes whose validUntil has passed to EXPIRED.
|
|
565
|
-
* Called before every find/preview — defense-in-depth alongside the cron.
|
|
566
|
-
*/
|
|
567
|
-
async lazyExpire() {
|
|
568
|
-
const now = /* @__PURE__ */ new Date();
|
|
569
|
-
await this.promoRepo.expireDueCodes(now);
|
|
570
|
-
await this.redemptionRepo.expireDueRedemptions(now);
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Lowest applicable plan price. With a whitelist it takes the minimum
|
|
574
|
-
* from the whitelist, otherwise across all marketed plans of the catalog
|
|
575
|
-
* (except non-redeemable).
|
|
576
|
-
*/
|
|
577
|
-
lowestApplicablePlanGross(plans) {
|
|
578
|
-
const blocked = new Set(this.config.nonRedeemablePlans ?? []);
|
|
579
|
-
const candidates = plans.length > 0 ? plans : (this.planCatalog.plans ?? []).filter((p) => p.marketed !== false && !blocked.has(p.id)).map((p) => p.id);
|
|
580
|
-
let min = null;
|
|
581
|
-
for (const p of candidates) {
|
|
582
|
-
const g = getPlanPriceGross(this.planCatalog, p, "MONTHLY");
|
|
583
|
-
if (g == null) continue;
|
|
584
|
-
if (min == null || g < min) min = g;
|
|
585
|
-
}
|
|
586
|
-
return min;
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
PromoCodesService = _ts_decorate([
|
|
590
|
-
Injectable(),
|
|
591
|
-
_ts_param(0, Inject(PROMO_CODE_REPOSITORY_TOKEN)),
|
|
592
|
-
_ts_param(1, Inject(PROMO_CODE_REDEMPTION_REPOSITORY_TOKEN)),
|
|
593
|
-
_ts_param(2, Inject(PROMO_CODE_VALIDATION_LOG_REPOSITORY_TOKEN)),
|
|
594
|
-
_ts_param(3, Inject(PROMO_FIRST_TIME_CUSTOMER_CHECK_TOKEN)),
|
|
595
|
-
_ts_param(4, Inject(PROMO_SUBSCRIPTION_LOOKUP_TOKEN)),
|
|
596
|
-
_ts_param(5, Inject(PROMO_REVENUE_DEDUCTION_AGGREGATOR_TOKEN)),
|
|
597
|
-
_ts_param(6, Inject(PROMO_TRANSACTION_RUNNER_TOKEN)),
|
|
598
|
-
_ts_param(7, Inject(PLAN_CATALOG_TOKEN)),
|
|
599
|
-
_ts_param(8, Inject(PROMO_SERVICE_CONFIG_TOKEN)),
|
|
600
|
-
_ts_metadata("design:type", Function),
|
|
601
|
-
_ts_metadata("design:paramtypes", [
|
|
602
|
-
typeof PromoCodeRepository === "undefined" ? Object : PromoCodeRepository,
|
|
603
|
-
typeof PromoCodeRedemptionRepository === "undefined" ? Object : PromoCodeRedemptionRepository,
|
|
604
|
-
typeof PromoCodeValidationLogRepository === "undefined" ? Object : PromoCodeValidationLogRepository,
|
|
605
|
-
typeof FirstTimeCustomerCheck === "undefined" ? Object : FirstTimeCustomerCheck,
|
|
606
|
-
typeof PromoSubscriptionLookup === "undefined" ? Object : PromoSubscriptionLookup,
|
|
607
|
-
typeof PromoRevenueDeductionAggregator === "undefined" ? Object : PromoRevenueDeductionAggregator,
|
|
608
|
-
typeof TransactionRunner === "undefined" ? Object : TransactionRunner,
|
|
609
|
-
typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
|
|
610
|
-
typeof PromoServiceConfig === "undefined" ? Object : PromoServiceConfig
|
|
611
|
-
])
|
|
612
|
-
], PromoCodesService);
|
|
613
|
-
|
|
614
|
-
export {
|
|
615
|
-
computeDiscountGross,
|
|
616
|
-
computeDiscountedGross,
|
|
617
|
-
computeRegularStartsAt,
|
|
618
|
-
addCycles,
|
|
619
|
-
buildLabel,
|
|
620
|
-
PROMO_CODE_REPOSITORY_TOKEN,
|
|
621
|
-
PROMO_CODE_REDEMPTION_REPOSITORY_TOKEN,
|
|
622
|
-
PROMO_CODE_VALIDATION_LOG_REPOSITORY_TOKEN,
|
|
623
|
-
PROMO_FIRST_TIME_CUSTOMER_CHECK_TOKEN,
|
|
624
|
-
PROMO_SUBSCRIPTION_LOOKUP_TOKEN,
|
|
625
|
-
PROMO_REVENUE_DEDUCTION_AGGREGATOR_TOKEN,
|
|
626
|
-
PROMO_TRANSACTION_RUNNER_TOKEN,
|
|
627
|
-
PROMO_SERVICE_CONFIG_TOKEN,
|
|
628
|
-
CODE_MIN_LENGTH,
|
|
629
|
-
CODE_MAX_LENGTH,
|
|
630
|
-
CODE_PATTERN,
|
|
631
|
-
PromoCodesService
|
|
632
|
-
};
|