@saasicat/nest 1.0.0-rc.21 → 1.0.0-rc.23
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/_entries.cjs +15561 -12478
- package/dist/admin/index.d.cts +4 -4
- package/dist/admin/index.d.ts +4 -4
- package/dist/admin/index.js +8 -5
- package/dist/{admin-resources.module-CWqdI9BV.d.ts → admin-resources.module-DtWAFI-G.d.ts} +1 -1
- package/dist/{admin-resources.module-B4XNvdH-.d.cts → admin-resources.module-pS7LbdbI.d.cts} +1 -1
- package/dist/{admin-stats.service-CsLNt19Z.d.ts → admin-stats.service--1sRNpUx.d.ts} +2 -2
- package/dist/{admin-stats.service-DrZrU8IJ.d.cts → admin-stats.service-ChATI9YK.d.cts} +2 -2
- package/dist/{aggregation-ChpZDh0g.d.ts → aggregation-B8iZBesJ.d.ts} +63 -4
- package/dist/{aggregation-OpnhwzF5.d.cts → aggregation-BRzgRXDb.d.cts} +63 -4
- package/dist/billing/index.d.cts +280 -13
- package/dist/billing/index.d.ts +280 -13
- package/dist/billing/index.js +32 -8
- package/dist/catalog/index.d.cts +2 -2
- package/dist/catalog/index.d.ts +2 -2
- package/dist/catalog/index.js +4 -2
- package/dist/{catalog.module-T-I5Ricz.d.cts → catalog.module-CeKslGHW.d.cts} +1 -1
- package/dist/{catalog.module-C9O7BxRq.d.ts → catalog.module-CiurcIjI.d.ts} +1 -1
- package/dist/checkout-offer/index.d.cts +4 -4
- package/dist/checkout-offer/index.d.ts +4 -4
- package/dist/checkout-offer/index.js +5 -5
- package/dist/{checkout-offer.module-x7SP_dX4.d.ts → checkout-offer.module-C761mcEv.d.ts} +1 -1
- package/dist/{checkout-offer.module-DFhGy6JO.d.cts → checkout-offer.module-CSHB4bB8.d.cts} +1 -1
- package/dist/{checkout-offer.service-BeHgYikI.d.ts → checkout-offer.service-Dux8EgEM.d.ts} +3 -3
- package/dist/{checkout-offer.service-CCVxG9b2.d.cts → checkout-offer.service-HKoTZ3JH.d.cts} +3 -3
- package/dist/{chunk-F5X66HF5.js → chunk-2ECX7BST.js} +1 -1
- package/dist/{chunk-G6EZWECL.js → chunk-2XBWYMOL.js} +141 -26
- package/dist/{chunk-R5YCJBHY.js → chunk-3HBP2HK3.js} +6 -2
- package/dist/{chunk-S33SO5XX.js → chunk-4OFJLVVH.js} +4 -0
- package/dist/{chunk-AOS2JPDD.js → chunk-5LL2554P.js} +757 -163
- package/dist/chunk-5MZH3VVU.js +292 -0
- package/dist/{chunk-WHJSYKNC.js → chunk-7LHUENAT.js} +2 -2
- package/dist/{chunk-M47BNEY2.js → chunk-7MLB6T5B.js} +2072 -407
- package/dist/{chunk-NDLC5GYK.js → chunk-CVCKXKGT.js} +138 -21
- package/dist/{chunk-VBRYPU7V.js → chunk-E224LKO2.js} +3 -1
- package/dist/{chunk-QPVDCKYS.js → chunk-ESEWSAOV.js} +4 -3
- package/dist/{chunk-CQ2ZZMTD.js → chunk-MU4O4EKX.js} +711 -828
- package/dist/{chunk-JBCC6C3M.js → chunk-NQIPQI2Q.js} +26 -7
- package/dist/{chunk-4SDSYV4A.js → chunk-PBL7N5J3.js} +18 -2
- package/dist/{chunk-LLYVYRGJ.js → chunk-QTJEZDKC.js} +15 -275
- package/dist/{chunk-3RLWML2S.js → chunk-QWKBOLBH.js} +35 -0
- package/dist/{chunk-XSSWYPP5.js → chunk-TR3L6FRF.js} +6 -2
- package/dist/chunk-TSLGMPGZ.js +21 -0
- package/dist/chunk-UJZCESIC.js +466 -0
- package/dist/{chunk-TOEFN7DN.js → chunk-W6VNCMOQ.js} +3 -3
- package/dist/{chunk-7NJKVC5X.js → chunk-WQNOCOI2.js} +53 -6
- package/dist/chunk-YZSILFW4.js +142 -0
- package/dist/{chunk-55QEDAMX.js → chunk-ZCXW24NH.js} +4 -9
- package/dist/discovery/index.js +2 -1
- package/dist/{enforce-quota.interceptor-BFyVdWe8.d.cts → enforce-quota.interceptor-Dc-1_zQf.d.cts} +2 -2
- package/dist/{enforce-quota.interceptor-GaBXboGx.d.ts → enforce-quota.interceptor-MXpwsOJ2.d.ts} +2 -2
- package/dist/entitlement/index.d.cts +1 -1
- package/dist/entitlement/index.d.ts +1 -1
- package/dist/entitlement/index.js +5 -3
- package/dist/index.d.cts +62 -25
- package/dist/index.d.ts +62 -25
- package/dist/index.js +81 -26
- package/dist/{issuer-identity.check-CBO0vcsq.d.cts → issuer-identity.check-BrZXesOF.d.cts} +3 -3
- package/dist/{issuer-identity.check-Cz9CEMkZ.d.ts → issuer-identity.check-BzlFSmSZ.d.ts} +3 -3
- package/dist/maintenance.service-CNMI3tSm.d.cts +133 -0
- package/dist/maintenance.service-yx6fxjUj.d.ts +133 -0
- package/dist/{module-options-DmQ3G4sZ.d.ts → module-options-BSYxlA38.d.ts} +50 -9
- package/dist/{module-options-qAOwinHR.d.cts → module-options-BkufuqlM.d.cts} +50 -9
- package/dist/payments/index.d.cts +2 -2
- package/dist/payments/index.d.ts +2 -2
- package/dist/payments/index.js +2 -2
- package/dist/{payments.module-BdUq9ot_.d.cts → payments.module-CwLeaflk.d.cts} +1 -1
- package/dist/{payments.module-CQGv_VG7.d.ts → payments.module-DmIN-HWx.d.ts} +1 -1
- package/dist/{plan-versions.service-DARLh-qI.d.cts → plan-versions.service-2mD0iEvY.d.cts} +1 -1
- package/dist/{plan-versions.service-C0N-Em2W.d.ts → plan-versions.service-C4a9aCEU.d.ts} +1 -1
- package/dist/platform/index.d.cts +34 -25
- package/dist/platform/index.d.ts +34 -25
- package/dist/platform/index.js +22 -17
- package/dist/promo/index.d.cts +15 -5
- package/dist/promo/index.d.ts +15 -5
- package/dist/promo/index.js +9 -3
- package/dist/{promo.module-CKkECmU8.d.cts → promo.module-Ddx-swUV.d.cts} +1 -1
- package/dist/{promo.module-DuIJdK3U.d.ts → promo.module-PsIvDcUp.d.ts} +1 -1
- package/dist/{promo.service-BYBu6dy3.d.cts → promo.service-BextPvx5.d.cts} +23 -1
- package/dist/{promo.service-CFjfF0Xm.d.ts → promo.service-Dqqh0JXn.d.ts} +23 -1
- package/dist/registration/index.d.cts +7 -4
- package/dist/registration/index.d.ts +7 -4
- package/dist/registration/index.js +11 -6
- package/dist/subscription-contract/index.d.cts +13 -3
- package/dist/subscription-contract/index.d.ts +13 -3
- package/dist/subscription-contract/index.js +5 -3
- package/dist/{subscription-contract.module-BfIRpc1o.d.ts → subscription-contract.module-B5wrmzkz.d.ts} +8 -1
- package/dist/{subscription-contract.module-DTMuUJYd.d.cts → subscription-contract.module-qaswPXwT.d.cts} +8 -1
- package/dist/{subscription-contract.service-CfNApLuP.d.cts → subscription-contract.service-CH2TKw-j.d.cts} +45 -3
- package/dist/{subscription-contract.service-CPZrTM9C.d.ts → subscription-contract.service-Dm2xEFZi.d.ts} +45 -3
- package/dist/{tenant-billing.controller-BLqbsONe.d.ts → tenant-billing.controller-BMH9YODT.d.ts} +92 -11
- package/dist/{tenant-billing.controller-DKbAqTbM.d.cts → tenant-billing.controller-BX3VExL3.d.cts} +92 -11
- package/dist/{tenant-billing.module-ClbjzocC.d.cts → tenant-billing.module-Do3QyKbr.d.cts} +33 -3
- package/dist/{tenant-billing.module-DohgJJ2m.d.ts → tenant-billing.module-W5_Y4ogN.d.ts} +33 -3
- package/dist/{tenant-billing.tokens-G-1xOlMr.d.cts → tenant-billing.tokens-D0vr1lfj.d.cts} +1 -1
- package/dist/{tenant-billing.tokens-G-1xOlMr.d.ts → tenant-billing.tokens-D0vr1lfj.d.ts} +1 -1
- package/dist/testing/index.d.cts +14 -13
- package/dist/testing/index.d.ts +14 -13
- package/dist/testing/index.js +54 -35
- package/package.json +5 -3
|
@@ -1,22 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AdminResourcesService
|
|
3
|
+
} from "./chunk-5MZH3VVU.js";
|
|
1
4
|
import {
|
|
2
5
|
AdminAuditService
|
|
3
6
|
} from "./chunk-OOTEXP47.js";
|
|
4
7
|
import {
|
|
5
|
-
PromoCodesService
|
|
6
|
-
|
|
8
|
+
PromoCodesService,
|
|
9
|
+
buildLabel,
|
|
10
|
+
promoCodeDiscountNet
|
|
11
|
+
} from "./chunk-QWKBOLBH.js";
|
|
7
12
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
SUCCESSOR_ATTEMPTS,
|
|
14
|
+
SubscriptionContractService,
|
|
15
|
+
contractChanged,
|
|
16
|
+
generatedDiscountLine
|
|
17
|
+
} from "./chunk-CVCKXKGT.js";
|
|
10
18
|
import {
|
|
11
19
|
resolveBundlePriceNet
|
|
12
20
|
} from "./chunk-KPEMTBOP.js";
|
|
21
|
+
import {
|
|
22
|
+
AllowDuringMaintenance
|
|
23
|
+
} from "./chunk-TSLGMPGZ.js";
|
|
13
24
|
import {
|
|
14
25
|
EntitlementService,
|
|
15
26
|
SUBSCRIPTION_BUNDLE_CONFIG_TOKEN,
|
|
16
27
|
SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN,
|
|
17
28
|
cancellationHasLanded,
|
|
29
|
+
contractBundleVersionIds,
|
|
30
|
+
contractLimits,
|
|
18
31
|
toEffectiveLimitsSnapshot
|
|
19
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-2XBWYMOL.js";
|
|
20
33
|
import {
|
|
21
34
|
ENTITLEMENT_SERVICE_TOKEN
|
|
22
35
|
} from "./chunk-OUDSQ3PU.js";
|
|
@@ -29,8 +42,9 @@ import {
|
|
|
29
42
|
planNotSoldInCycle
|
|
30
43
|
} from "./chunk-TBBZWZQT.js";
|
|
31
44
|
import {
|
|
45
|
+
CONTRACT_TRANSACTION_RUNNER_TOKEN,
|
|
32
46
|
SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
|
|
33
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-E224LKO2.js";
|
|
34
48
|
import {
|
|
35
49
|
assertContractWindow,
|
|
36
50
|
assertNoNegativeDiscount,
|
|
@@ -44,6 +58,9 @@ import {
|
|
|
44
58
|
CATALOG_ENTRY_REPOSITORY_TOKEN,
|
|
45
59
|
PLAN_REPOSITORY_TOKEN
|
|
46
60
|
} from "./chunk-3QVGA6JX.js";
|
|
61
|
+
import {
|
|
62
|
+
DISCOVERY_SNAPSHOT_TOKEN
|
|
63
|
+
} from "./chunk-QUZU2M33.js";
|
|
47
64
|
import {
|
|
48
65
|
ComposedTenantAuthGuard,
|
|
49
66
|
holdsTenantAdminRole
|
|
@@ -61,9 +78,15 @@ import {
|
|
|
61
78
|
USER_EMAIL_RESOLVER_TOKEN,
|
|
62
79
|
USER_ID_RESOLVER_TOKEN
|
|
63
80
|
} from "./chunk-GPQWGA6B.js";
|
|
81
|
+
import {
|
|
82
|
+
RLS_BYPASS_PORT_TOKEN
|
|
83
|
+
} from "./chunk-SZ7RFPXA.js";
|
|
64
84
|
import {
|
|
65
85
|
subscriberProviders
|
|
66
86
|
} from "./chunk-WOEJ6K7M.js";
|
|
87
|
+
import {
|
|
88
|
+
SUBSCRIBER_REPOSITORY_TOKEN
|
|
89
|
+
} from "./chunk-LF6J4YYN.js";
|
|
67
90
|
import {
|
|
68
91
|
SaaSiCatPublicRoute
|
|
69
92
|
} from "./chunk-YSC46BGM.js";
|
|
@@ -1028,23 +1051,52 @@ var SELF_SERVICE_BLOCKED_BUNDLES_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/ne
|
|
|
1028
1051
|
var DAY_MS2 = 864e5;
|
|
1029
1052
|
function computeProration(input) {
|
|
1030
1053
|
const { periodStart, periodEnd, now, currentPriceNet, targetPriceNet } = input;
|
|
1031
|
-
const daysInPeriod =
|
|
1032
|
-
const daysRemaining = Math.max(0, Math.min(daysInPeriod, Math.round((periodEnd.getTime() - now.getTime()) / DAY_MS2)));
|
|
1054
|
+
const { daysInPeriod, daysRemaining } = daysOf(periodStart, periodEnd, now);
|
|
1033
1055
|
const rawDeltaNet = round2((targetPriceNet - currentPriceNet) * daysRemaining / daysInPeriod);
|
|
1034
|
-
const prorataDeltaNet = Math.max(0, rawDeltaNet);
|
|
1035
1056
|
return {
|
|
1057
|
+
basis: "difference",
|
|
1036
1058
|
daysRemainingInPeriod: daysRemaining,
|
|
1037
1059
|
daysInPeriod,
|
|
1038
1060
|
periodStart,
|
|
1039
1061
|
periodEnd,
|
|
1040
1062
|
currentPriceNet,
|
|
1041
1063
|
targetPriceNet,
|
|
1042
|
-
prorataDeltaNet,
|
|
1064
|
+
prorataDeltaNet: Math.max(0, rawDeltaNet),
|
|
1043
1065
|
rawDeltaNet,
|
|
1044
|
-
isFree: rawDeltaNet < 0
|
|
1066
|
+
isFree: rawDeltaNet < 0,
|
|
1067
|
+
remainderNet: 0
|
|
1045
1068
|
};
|
|
1046
1069
|
}
|
|
1047
1070
|
__name(computeProration, "computeProration");
|
|
1071
|
+
function computeNewPeriodCharge(input) {
|
|
1072
|
+
const { periodStart, periodEnd, now, currentPriceNet, targetPriceNet } = input;
|
|
1073
|
+
const { daysInPeriod, daysRemaining } = daysOf(periodStart, periodEnd, now);
|
|
1074
|
+
const remainderNet = round2(currentPriceNet * daysRemaining / daysInPeriod);
|
|
1075
|
+
const rawDeltaNet = round2(targetPriceNet - remainderNet);
|
|
1076
|
+
return {
|
|
1077
|
+
basis: "newPeriod",
|
|
1078
|
+
daysRemainingInPeriod: daysRemaining,
|
|
1079
|
+
daysInPeriod,
|
|
1080
|
+
periodStart,
|
|
1081
|
+
periodEnd,
|
|
1082
|
+
currentPriceNet,
|
|
1083
|
+
targetPriceNet,
|
|
1084
|
+
prorataDeltaNet: Math.max(0, rawDeltaNet),
|
|
1085
|
+
rawDeltaNet,
|
|
1086
|
+
isFree: rawDeltaNet < 0,
|
|
1087
|
+
remainderNet
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
__name(computeNewPeriodCharge, "computeNewPeriodCharge");
|
|
1091
|
+
function daysOf(periodStart, periodEnd, now) {
|
|
1092
|
+
const daysInPeriod = Math.max(1, Math.round((periodEnd.getTime() - periodStart.getTime()) / DAY_MS2));
|
|
1093
|
+
const daysRemaining = Math.max(0, Math.min(daysInPeriod, Math.round((periodEnd.getTime() - now.getTime()) / DAY_MS2)));
|
|
1094
|
+
return {
|
|
1095
|
+
daysInPeriod,
|
|
1096
|
+
daysRemaining
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
__name(daysOf, "daysOf");
|
|
1048
1100
|
function round2(n) {
|
|
1049
1101
|
return Math.round(n * 100) / 100;
|
|
1050
1102
|
}
|
|
@@ -1081,7 +1133,8 @@ var PlanChangePreviewService = class {
|
|
|
1081
1133
|
blockedPlans;
|
|
1082
1134
|
trialProjection;
|
|
1083
1135
|
subscriptionBundles;
|
|
1084
|
-
|
|
1136
|
+
contracts;
|
|
1137
|
+
constructor(catalogs, entitlements, subscriptions, usageSnapshot, blockedPlans = null, trialProjection = null, subscriptionBundles = null, contracts = null) {
|
|
1085
1138
|
this.catalogs = catalogs;
|
|
1086
1139
|
this.entitlements = entitlements;
|
|
1087
1140
|
this.subscriptions = subscriptions;
|
|
@@ -1089,6 +1142,7 @@ var PlanChangePreviewService = class {
|
|
|
1089
1142
|
this.blockedPlans = blockedPlans;
|
|
1090
1143
|
this.trialProjection = trialProjection;
|
|
1091
1144
|
this.subscriptionBundles = subscriptionBundles;
|
|
1145
|
+
this.contracts = contracts;
|
|
1092
1146
|
}
|
|
1093
1147
|
async preview(tenantId, targetPlan, targetCycle, now = /* @__PURE__ */ new Date()) {
|
|
1094
1148
|
const sub = await this.subscriptions.findForTenant(tenantId);
|
|
@@ -1173,7 +1227,7 @@ var PlanChangePreviewService = class {
|
|
|
1173
1227
|
const commits = ctx.status !== "TRIAL";
|
|
1174
1228
|
const isImmediate = planDirection === "UP" && (!commits || cycleDirection !== "SHORTER");
|
|
1175
1229
|
const effectiveAt = isImmediate ? null : this.resolveEffectiveAt(ctx, now);
|
|
1176
|
-
const proration = isImmediate && ctx.status !== "TRIAL" ? this.computeProration(ctx, now,
|
|
1230
|
+
const proration = isImmediate && ctx.status !== "TRIAL" ? this.computeProration(ctx, now, await this.planPricePaid(tenantId, sub.plan, sub.billingCycle, now) ?? priceForCycle(currentSnap, sub.billingCycle) ?? 0, priceForCycle(targetSnap, targetCycle) ?? 0, cycleDirection) : null;
|
|
1177
1231
|
const blockers = [];
|
|
1178
1232
|
const warnings = [];
|
|
1179
1233
|
const landsAt = ctx.canceledEffectiveAt ?? ctx.canceledAt;
|
|
@@ -1360,17 +1414,41 @@ var PlanChangePreviewService = class {
|
|
|
1360
1414
|
if (ctx.minimumTermUntil && ctx.minimumTermUntil > periodEnd) return ctx.minimumTermUntil;
|
|
1361
1415
|
return periodEnd;
|
|
1362
1416
|
}
|
|
1363
|
-
computeProration(ctx, now,
|
|
1417
|
+
computeProration(ctx, now, currentPriceNet, targetPriceNet, cycleDirection) {
|
|
1418
|
+
if (ctx.currentPeriodEnd === null) {
|
|
1419
|
+
return computeNewPeriodCharge({
|
|
1420
|
+
periodStart: now,
|
|
1421
|
+
periodEnd: now,
|
|
1422
|
+
now,
|
|
1423
|
+
currentPriceNet,
|
|
1424
|
+
targetPriceNet
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1364
1427
|
const periodStart = ctx.currentPeriodStart ?? ctx.startedAt ?? now;
|
|
1365
|
-
const
|
|
1366
|
-
return
|
|
1428
|
+
const charge = cycleDirection === "SAME" ? computeProration : computeNewPeriodCharge;
|
|
1429
|
+
return charge({
|
|
1367
1430
|
periodStart,
|
|
1368
|
-
periodEnd,
|
|
1431
|
+
periodEnd: ctx.currentPeriodEnd,
|
|
1369
1432
|
now,
|
|
1370
|
-
currentPriceNet
|
|
1371
|
-
targetPriceNet
|
|
1433
|
+
currentPriceNet,
|
|
1434
|
+
targetPriceNet
|
|
1372
1435
|
});
|
|
1373
1436
|
}
|
|
1437
|
+
/**
|
|
1438
|
+
* The net price of the plan line in the contract in force, when it is for
|
|
1439
|
+
* `plan` billed in `cycle` — what the customer pays for the period they are
|
|
1440
|
+
* in. Null without contracts, or where the line is for another plan or
|
|
1441
|
+
* rhythm: a freeze is optional and does not stop the change it follows when
|
|
1442
|
+
* it fails, so the contract in force can still describe the plan before.
|
|
1443
|
+
* The caller then prices from the catalogue.
|
|
1444
|
+
*/
|
|
1445
|
+
async planPricePaid(tenantId, plan, cycle, now) {
|
|
1446
|
+
const contract = await this.contracts?.findActiveByTenantId(tenantId, now);
|
|
1447
|
+
const planLine = contract?.lineItems.find((line) => line.kind === "plan");
|
|
1448
|
+
if (!planLine || planLine.sourceKey !== plan) return null;
|
|
1449
|
+
if (planLine.billingCycle !== cycle.toLowerCase()) return null;
|
|
1450
|
+
return planLine.priceNet;
|
|
1451
|
+
}
|
|
1374
1452
|
};
|
|
1375
1453
|
PlanChangePreviewService = _ts_decorate4([
|
|
1376
1454
|
Injectable2(),
|
|
@@ -1384,6 +1462,8 @@ PlanChangePreviewService = _ts_decorate4([
|
|
|
1384
1462
|
_ts_param2(5, Inject2(TRIAL_PROJECTION_PORT_TOKEN)),
|
|
1385
1463
|
_ts_param2(6, Optional2()),
|
|
1386
1464
|
_ts_param2(6, Inject2(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN)),
|
|
1465
|
+
_ts_param2(7, Optional2()),
|
|
1466
|
+
_ts_param2(7, Inject2(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN)),
|
|
1387
1467
|
_ts_metadata2("design:type", Function),
|
|
1388
1468
|
_ts_metadata2("design:paramtypes", [
|
|
1389
1469
|
typeof PlanCatalogSource === "undefined" ? Object : PlanCatalogSource,
|
|
@@ -1392,6 +1472,7 @@ PlanChangePreviewService = _ts_decorate4([
|
|
|
1392
1472
|
typeof UsageSnapshotPort === "undefined" ? Object : UsageSnapshotPort,
|
|
1393
1473
|
Object,
|
|
1394
1474
|
Object,
|
|
1475
|
+
Object,
|
|
1395
1476
|
Object
|
|
1396
1477
|
])
|
|
1397
1478
|
], PlanChangePreviewService);
|
|
@@ -1509,183 +1590,1710 @@ PendingPlanMaterializationService = _ts_decorate5([
|
|
|
1509
1590
|
])
|
|
1510
1591
|
], PendingPlanMaterializationService);
|
|
1511
1592
|
|
|
1512
|
-
// src/billing/subscription-contract-freeze.service.ts
|
|
1513
|
-
import { Inject as Inject4, Injectable as Injectable4, Optional as Optional4, UnprocessableEntityException } from "@nestjs/common";
|
|
1514
|
-
function _ts_decorate6(decorators, target, key, desc) {
|
|
1593
|
+
// src/billing/subscription-contract-freeze.service.ts
|
|
1594
|
+
import { Inject as Inject4, Injectable as Injectable4, Optional as Optional4, UnprocessableEntityException } from "@nestjs/common";
|
|
1595
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
1596
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1597
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1598
|
+
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;
|
|
1599
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1600
|
+
}
|
|
1601
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
1602
|
+
function _ts_metadata4(k, v) {
|
|
1603
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1604
|
+
}
|
|
1605
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
1606
|
+
function _ts_param4(paramIndex, decorator) {
|
|
1607
|
+
return function(target, key) {
|
|
1608
|
+
decorator(target, key, paramIndex);
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
__name(_ts_param4, "_ts_param");
|
|
1612
|
+
var SubscriptionContractFreezeService = class {
|
|
1613
|
+
static {
|
|
1614
|
+
__name(this, "SubscriptionContractFreezeService");
|
|
1615
|
+
}
|
|
1616
|
+
catalogs;
|
|
1617
|
+
entitlements;
|
|
1618
|
+
contracts;
|
|
1619
|
+
source;
|
|
1620
|
+
subscriptions;
|
|
1621
|
+
promoCodes;
|
|
1622
|
+
constructor(catalogs, entitlements, contracts, source, writes = null, subscriptions = null, promoCodes = null) {
|
|
1623
|
+
this.catalogs = catalogs;
|
|
1624
|
+
this.entitlements = entitlements;
|
|
1625
|
+
this.contracts = contracts;
|
|
1626
|
+
this.source = source;
|
|
1627
|
+
this.subscriptions = subscriptions;
|
|
1628
|
+
this.promoCodes = promoCodes;
|
|
1629
|
+
if (writes?.bindsPlanVersion === false) {
|
|
1630
|
+
throw new Error("The contract freeze records the plan version a subscription is bound to, and this installation's subscription write does not bind it on a plan change. With @saasicat/adapter-prisma, leave `tenantSubscription.synchronizePlanVersion` at its default of true \u2014 or do not configure `contractFreeze`.");
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
assertPartyFor(tenantId) {
|
|
1634
|
+
return this.contracts.assertPartyFor(tenantId);
|
|
1635
|
+
}
|
|
1636
|
+
async endOnCancellation(tenantId, effectiveAt) {
|
|
1637
|
+
const active = await this.contracts.findActiveByTenantId(tenantId, effectiveAt);
|
|
1638
|
+
if (!active) return;
|
|
1639
|
+
await this.contracts.terminate(active.id, {
|
|
1640
|
+
effectiveUntil: effectiveAt,
|
|
1641
|
+
// Only when it is already over. An ordinary cancellation lands at
|
|
1642
|
+
// the term end, months out, and the customer is under this contract
|
|
1643
|
+
// until then — the invoice side has to keep finding it, and the
|
|
1644
|
+
// window in `findActiveByTenantId` is what stops it afterwards.
|
|
1645
|
+
status: effectiveAt <= /* @__PURE__ */ new Date() ? "terminated" : null
|
|
1646
|
+
});
|
|
1647
|
+
this.entitlements.invalidateTenant(tenantId);
|
|
1648
|
+
}
|
|
1649
|
+
async freezeOnPlanChange(tenantId, newPlan, billingCycle, effectiveFrom, endsAt = null) {
|
|
1650
|
+
const data = await this.composeOnPlanChange(tenantId, newPlan, billingCycle, effectiveFrom, endsAt);
|
|
1651
|
+
for (let attempt = 0; attempt < SUCCESSOR_ATTEMPTS; attempt++) {
|
|
1652
|
+
const previous = await this.contracts.findActiveByTenantId(tenantId, effectiveFrom);
|
|
1653
|
+
const written = await this.contracts.writeSuccessor(previous, data, effectiveFrom);
|
|
1654
|
+
if (written) {
|
|
1655
|
+
this.entitlements.invalidateTenant(tenantId);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
throw contractChanged(tenantId);
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* The contract `freezeOnPlanChange` would write, and nothing written: the
|
|
1663
|
+
* plan version the subscription is bound to, the add-ons booked, the
|
|
1664
|
+
* catalogue's rate and currency, and a promotional code not yet recorded —
|
|
1665
|
+
* checked the way a contract is checked before it is written.
|
|
1666
|
+
*
|
|
1667
|
+
* Public so that an operator's refresh can show a full re-freeze before it
|
|
1668
|
+
* makes one, measured by the same rules a plan change is.
|
|
1669
|
+
*/
|
|
1670
|
+
async composeOnPlanChange(tenantId, newPlan, billingCycle, effectiveFrom, endsAt = null) {
|
|
1671
|
+
const cycle = billingCycle === "YEARLY" ? "yearly" : "monthly";
|
|
1672
|
+
const catalog = await this.catalogs.current();
|
|
1673
|
+
const vatRate = catalog.vatRate;
|
|
1674
|
+
assertTaxRatePercent("catalog.vatRate", vatRate);
|
|
1675
|
+
assertContractWindow(effectiveFrom, endsAt);
|
|
1676
|
+
const bound = await this.source.findBoundPlanVersion(tenantId);
|
|
1677
|
+
if (!bound || bound.planId !== newPlan) {
|
|
1678
|
+
throw new Error(`The contract for tenant ${tenantId} was asked to record plan '${newPlan}', but the subscription is bound to ${bound ? `'${bound.planId}' (version ${bound.id})` : "no plan version"}. Bind the plan version on the subscription before the contract is frozen.`);
|
|
1679
|
+
}
|
|
1680
|
+
const stem = findPlan(catalog, newPlan);
|
|
1681
|
+
const planDef = planDefFromVersion({
|
|
1682
|
+
id: newPlan,
|
|
1683
|
+
name: stem?.name ?? newPlan,
|
|
1684
|
+
tagline: stem?.tagline
|
|
1685
|
+
}, bound);
|
|
1686
|
+
if (isPlanNotSoldInCycle(planDef, billingCycle)) {
|
|
1687
|
+
throw new UnprocessableEntityException(planNotSoldInCycle(planDef, billingCycle));
|
|
1688
|
+
}
|
|
1689
|
+
await this.contracts.assertPartyFor(tenantId);
|
|
1690
|
+
const bundles = await this.source.loadBookedBundles(tenantId, cycle);
|
|
1691
|
+
const planPriceNet = listPriceNet(planDef, billingCycle) ?? 0;
|
|
1692
|
+
const planLineItem = {
|
|
1693
|
+
kind: "plan",
|
|
1694
|
+
sourceKey: newPlan,
|
|
1695
|
+
sourceVersionId: bound.id,
|
|
1696
|
+
titleSnapshot: planDef.name ?? newPlan,
|
|
1697
|
+
descriptionSnapshot: planDef.tagline ?? null,
|
|
1698
|
+
quantity: 1,
|
|
1699
|
+
unit: null,
|
|
1700
|
+
priceNet: planPriceNet,
|
|
1701
|
+
billingCycle: cycle,
|
|
1702
|
+
minimumTermUntil: null,
|
|
1703
|
+
featuresSnapshot: planDef.features,
|
|
1704
|
+
quotaEffectsSnapshot: planDef.quotas,
|
|
1705
|
+
metadata: null
|
|
1706
|
+
};
|
|
1707
|
+
const redeemed = await this.redeemedCodeNotYetRecorded(tenantId, planPriceNet, billingCycle, vatRate);
|
|
1708
|
+
const lineItems = recordContractLinesMoney([
|
|
1709
|
+
planLineItem,
|
|
1710
|
+
...bundles.lineItems,
|
|
1711
|
+
...redeemed ? [
|
|
1712
|
+
redeemed.line
|
|
1713
|
+
] : []
|
|
1714
|
+
], {
|
|
1715
|
+
currency: catalog.currency,
|
|
1716
|
+
taxRate: vatRate
|
|
1717
|
+
});
|
|
1718
|
+
const totals = contractTotalsOf(lineItems, cycle);
|
|
1719
|
+
const priceSnapshot = {
|
|
1720
|
+
currency: catalog.currency,
|
|
1721
|
+
billingCycle: cycle,
|
|
1722
|
+
subtotalNet: totals.subtotalNet,
|
|
1723
|
+
discountNet: totals.discountNet,
|
|
1724
|
+
totalNet: totals.totalNet,
|
|
1725
|
+
vatRate,
|
|
1726
|
+
totalGross: totals.totalGross
|
|
1727
|
+
};
|
|
1728
|
+
assertOnePlanLine(lineItems);
|
|
1729
|
+
assertNoNegativeDiscount({
|
|
1730
|
+
priceSnapshot
|
|
1731
|
+
});
|
|
1732
|
+
const { limits, leftOutBundleVersionIds } = await this.entitlements.computeContractLimits(tenantId, effectiveFrom, catalog);
|
|
1733
|
+
const data = {
|
|
1734
|
+
tenantId,
|
|
1735
|
+
status: "active",
|
|
1736
|
+
effectiveFrom,
|
|
1737
|
+
// The successor inherits the ending. A cancellation capped the
|
|
1738
|
+
// contract that was running when it was declared; every contract
|
|
1739
|
+
// after it ends on the same date, or the repair lasts exactly until
|
|
1740
|
+
// the next plan change.
|
|
1741
|
+
effectiveUntil: endsAt,
|
|
1742
|
+
originalPlanVersionId: bound.id,
|
|
1743
|
+
originalBundleVersionIds: bundles.bundleVersionIds,
|
|
1744
|
+
entitlementSnapshot: {
|
|
1745
|
+
plan: limits.plan,
|
|
1746
|
+
quotas: {
|
|
1747
|
+
...limits.quotas
|
|
1748
|
+
},
|
|
1749
|
+
features: [
|
|
1750
|
+
...limits.features
|
|
1751
|
+
],
|
|
1752
|
+
...leftOutBundleVersionIds.length > 0 ? {
|
|
1753
|
+
leftOutBundleVersionIds
|
|
1754
|
+
} : {}
|
|
1755
|
+
},
|
|
1756
|
+
priceSnapshot,
|
|
1757
|
+
promoCodeSnapshots: redeemed ? [
|
|
1758
|
+
redeemed.snapshot
|
|
1759
|
+
] : [],
|
|
1760
|
+
lineItems
|
|
1761
|
+
};
|
|
1762
|
+
return data;
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* The code redeemed for the tenant's subscription, as a discount line, where
|
|
1766
|
+
* no contract of the tenant records it yet.
|
|
1767
|
+
*
|
|
1768
|
+
* The first contract written after a redemption records it — at onboarding,
|
|
1769
|
+
* or at the activation after a trial, where onboarding wrote none — and the
|
|
1770
|
+
* ones after it do not repeat it: the discount runs from where it was first
|
|
1771
|
+
* agreed, for the duration on its snapshot, counted from the first period
|
|
1772
|
+
* that is paid. A contract concluded from an offer that carried the code
|
|
1773
|
+
* already records it. Nothing is recorded without the promo module, for a
|
|
1774
|
+
* reversed redemption, or where a contract written since the redemption
|
|
1775
|
+
* does not record it: that was the first one, written before contracts
|
|
1776
|
+
* recorded codes or by the application itself, and a later contract does
|
|
1777
|
+
* not start the discount again.
|
|
1778
|
+
*/
|
|
1779
|
+
async redeemedCodeNotYetRecorded(tenantId, planPriceNet, billingCycle, vatRate) {
|
|
1780
|
+
if (!this.promoCodes || !this.subscriptions) return null;
|
|
1781
|
+
const subscription = await this.subscriptions.findForTenant(tenantId);
|
|
1782
|
+
if (!subscription?.id) return null;
|
|
1783
|
+
const redeemed = await this.promoCodes.redeemedCodeFor(subscription.id);
|
|
1784
|
+
if (!redeemed) return null;
|
|
1785
|
+
const recorded = await this.contracts.list({
|
|
1786
|
+
tenantId
|
|
1787
|
+
});
|
|
1788
|
+
if (recorded.some((contract) => recordsPromoCode(contract, redeemed.code) || contract.createdAt > redeemed.redeemedAt)) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
const cycle = billingCycle === "YEARLY" ? "yearly" : "monthly";
|
|
1792
|
+
const snapshot = {
|
|
1793
|
+
code: redeemed.code,
|
|
1794
|
+
label: buildLabel(redeemed, billingCycle),
|
|
1795
|
+
valueType: redeemed.valueType,
|
|
1796
|
+
value: Number(redeemed.value),
|
|
1797
|
+
resolvedAmountNet: promoCodeDiscountNet(planPriceNet, vatRate, redeemed),
|
|
1798
|
+
durationType: redeemed.durationType,
|
|
1799
|
+
durationValue: redeemed.durationValue
|
|
1800
|
+
};
|
|
1801
|
+
const line = generatedDiscountLine({
|
|
1802
|
+
billingCycle: cycle,
|
|
1803
|
+
promoCodeSnapshot: snapshot,
|
|
1804
|
+
promotionSnapshots: []
|
|
1805
|
+
}, snapshot.resolvedAmountNet);
|
|
1806
|
+
return {
|
|
1807
|
+
snapshot,
|
|
1808
|
+
line: {
|
|
1809
|
+
kind: line.kind,
|
|
1810
|
+
sourceKey: line.sourceKey,
|
|
1811
|
+
sourceVersionId: line.sourceVersionId ?? null,
|
|
1812
|
+
titleSnapshot: line.titleSnapshot,
|
|
1813
|
+
descriptionSnapshot: line.descriptionSnapshot ?? null,
|
|
1814
|
+
quantity: line.quantity,
|
|
1815
|
+
unit: line.unit ?? null,
|
|
1816
|
+
priceNet: line.priceNet,
|
|
1817
|
+
billingCycle: cycle,
|
|
1818
|
+
minimumTermUntil: null,
|
|
1819
|
+
featuresSnapshot: [],
|
|
1820
|
+
quotaEffectsSnapshot: {},
|
|
1821
|
+
metadata: line.metadata ?? null
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
SubscriptionContractFreezeService = _ts_decorate6([
|
|
1827
|
+
Injectable4(),
|
|
1828
|
+
_ts_param4(0, Inject4(PLAN_CATALOG_SOURCE_TOKEN)),
|
|
1829
|
+
_ts_param4(1, Inject4(ENTITLEMENT_SERVICE_TOKEN)),
|
|
1830
|
+
_ts_param4(2, Inject4(SubscriptionContractService)),
|
|
1831
|
+
_ts_param4(3, Inject4(CONTRACT_FREEZE_SOURCE_PORT_TOKEN)),
|
|
1832
|
+
_ts_param4(4, Optional4()),
|
|
1833
|
+
_ts_param4(4, Inject4(SUBSCRIPTION_WRITE_PORT_TOKEN)),
|
|
1834
|
+
_ts_param4(5, Optional4()),
|
|
1835
|
+
_ts_param4(5, Inject4(SUBSCRIPTION_USAGE_PORT_TOKEN)),
|
|
1836
|
+
_ts_param4(6, Optional4()),
|
|
1837
|
+
_ts_param4(6, Inject4(PromoCodesService)),
|
|
1838
|
+
_ts_metadata4("design:type", Function),
|
|
1839
|
+
_ts_metadata4("design:paramtypes", [
|
|
1840
|
+
typeof PlanCatalogSource === "undefined" ? Object : PlanCatalogSource,
|
|
1841
|
+
typeof EntitlementService === "undefined" ? Object : EntitlementService,
|
|
1842
|
+
typeof SubscriptionContractService === "undefined" ? Object : SubscriptionContractService,
|
|
1843
|
+
typeof ContractFreezeSourcePort === "undefined" ? Object : ContractFreezeSourcePort,
|
|
1844
|
+
Object,
|
|
1845
|
+
Object,
|
|
1846
|
+
Object
|
|
1847
|
+
])
|
|
1848
|
+
], SubscriptionContractFreezeService);
|
|
1849
|
+
function recordsPromoCode(contract, code) {
|
|
1850
|
+
return contract.promoCodeSnapshots.some((snapshot) => snapshot !== null && typeof snapshot === "object" && snapshot.code === code);
|
|
1851
|
+
}
|
|
1852
|
+
__name(recordsPromoCode, "recordsPromoCode");
|
|
1853
|
+
|
|
1854
|
+
// src/billing/contract-refresh.ts
|
|
1855
|
+
var PRICE_FIELDS = [
|
|
1856
|
+
"currency",
|
|
1857
|
+
"billingCycle",
|
|
1858
|
+
"vatRate",
|
|
1859
|
+
"subtotalNet",
|
|
1860
|
+
"discountNet",
|
|
1861
|
+
"totalNet",
|
|
1862
|
+
"totalGross"
|
|
1863
|
+
];
|
|
1864
|
+
var LINE_MONEY_FIELDS = [
|
|
1865
|
+
"quantity",
|
|
1866
|
+
"billingCycle",
|
|
1867
|
+
"currency",
|
|
1868
|
+
"taxRate",
|
|
1869
|
+
"priceNet",
|
|
1870
|
+
"priceGross",
|
|
1871
|
+
"taxAmount"
|
|
1872
|
+
];
|
|
1873
|
+
function featureChangeOf(before, after) {
|
|
1874
|
+
const was = new Set(before);
|
|
1875
|
+
const is = new Set(after);
|
|
1876
|
+
return {
|
|
1877
|
+
added: [
|
|
1878
|
+
...is
|
|
1879
|
+
].filter((key) => !was.has(key)).sort(),
|
|
1880
|
+
removed: [
|
|
1881
|
+
...was
|
|
1882
|
+
].filter((key) => !is.has(key)).sort()
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
__name(featureChangeOf, "featureChangeOf");
|
|
1886
|
+
function quotaChangesOf(before, after) {
|
|
1887
|
+
const keys = [
|
|
1888
|
+
.../* @__PURE__ */ new Set([
|
|
1889
|
+
...Object.keys(before),
|
|
1890
|
+
...Object.keys(after)
|
|
1891
|
+
])
|
|
1892
|
+
].sort();
|
|
1893
|
+
return keys.map((key) => ({
|
|
1894
|
+
key,
|
|
1895
|
+
before: before[key] ?? null,
|
|
1896
|
+
after: after[key] ?? null
|
|
1897
|
+
})).filter((change) => change.before !== change.after);
|
|
1898
|
+
}
|
|
1899
|
+
__name(quotaChangesOf, "quotaChangesOf");
|
|
1900
|
+
function moneyChangesOf(contract, successor) {
|
|
1901
|
+
const changes = [];
|
|
1902
|
+
for (const field of PRICE_FIELDS) {
|
|
1903
|
+
const before = contract.priceSnapshot[field];
|
|
1904
|
+
const after = successor.priceSnapshot[field];
|
|
1905
|
+
if (before !== after) changes.push({
|
|
1906
|
+
field: `priceSnapshot.${field}`,
|
|
1907
|
+
before,
|
|
1908
|
+
after
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
const unmatched = [
|
|
1912
|
+
...successor.lineItems
|
|
1913
|
+
];
|
|
1914
|
+
for (const line of contract.lineItems) {
|
|
1915
|
+
const index = unmatched.findIndex((other2) => lineKeyOf(other2) === lineKeyOf(line));
|
|
1916
|
+
if (index === -1) {
|
|
1917
|
+
changes.push({
|
|
1918
|
+
field: `line ${lineKeyOf(line)}`,
|
|
1919
|
+
before: line.priceNet,
|
|
1920
|
+
after: null
|
|
1921
|
+
});
|
|
1922
|
+
continue;
|
|
1923
|
+
}
|
|
1924
|
+
const [other] = unmatched.splice(index, 1);
|
|
1925
|
+
changes.push(...lineMoneyChangesOf(line, other));
|
|
1926
|
+
}
|
|
1927
|
+
for (const other of unmatched) {
|
|
1928
|
+
changes.push({
|
|
1929
|
+
field: `line ${lineKeyOf(other)}`,
|
|
1930
|
+
before: null,
|
|
1931
|
+
after: other.priceNet
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
return changes;
|
|
1935
|
+
}
|
|
1936
|
+
__name(moneyChangesOf, "moneyChangesOf");
|
|
1937
|
+
function lineMoneyChangesOf(before, after) {
|
|
1938
|
+
return LINE_MONEY_FIELDS.filter((field) => before[field] !== after[field]).map((field) => ({
|
|
1939
|
+
field: `line ${lineKeyOf(before)} ${field}`,
|
|
1940
|
+
before: before[field],
|
|
1941
|
+
after: after[field]
|
|
1942
|
+
}));
|
|
1943
|
+
}
|
|
1944
|
+
__name(lineMoneyChangesOf, "lineMoneyChangesOf");
|
|
1945
|
+
function lineKeyOf(line) {
|
|
1946
|
+
return `${line.kind}:${line.sourceKey}`;
|
|
1947
|
+
}
|
|
1948
|
+
__name(lineKeyOf, "lineKeyOf");
|
|
1949
|
+
function withAgreedTerms(contract, successor) {
|
|
1950
|
+
const unmatched = [
|
|
1951
|
+
...contract.lineItems
|
|
1952
|
+
];
|
|
1953
|
+
return {
|
|
1954
|
+
...successor,
|
|
1955
|
+
originalOfferId: contract.originalOfferId,
|
|
1956
|
+
termsSnapshot: contract.termsSnapshot ? {
|
|
1957
|
+
...contract.termsSnapshot
|
|
1958
|
+
} : null,
|
|
1959
|
+
promotionSnapshots: [
|
|
1960
|
+
...contract.promotionSnapshots
|
|
1961
|
+
],
|
|
1962
|
+
promoCodeSnapshots: [
|
|
1963
|
+
...contract.promoCodeSnapshots
|
|
1964
|
+
],
|
|
1965
|
+
lineItems: successor.lineItems.map((line) => {
|
|
1966
|
+
const index = unmatched.findIndex((agreed2) => lineKeyOf(agreed2) === lineKeyOf(line));
|
|
1967
|
+
if (index === -1) return line;
|
|
1968
|
+
const [agreed] = unmatched.splice(index, 1);
|
|
1969
|
+
return {
|
|
1970
|
+
...line,
|
|
1971
|
+
minimumTermUntil: agreed.minimumTermUntil
|
|
1972
|
+
};
|
|
1973
|
+
})
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
__name(withAgreedTerms, "withAgreedTerms");
|
|
1977
|
+
function vocabularyOf(input) {
|
|
1978
|
+
return {
|
|
1979
|
+
unknown: [
|
|
1980
|
+
...input.frozen
|
|
1981
|
+
].filter((key) => !input.known.has(key)).sort(),
|
|
1982
|
+
missing: [
|
|
1983
|
+
...input.grantedToday
|
|
1984
|
+
].filter((key) => !input.frozenWithReplacements.has(key)).sort()
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
__name(vocabularyOf, "vocabularyOf");
|
|
1988
|
+
|
|
1989
|
+
// src/billing/contract-refresh.service.ts
|
|
1990
|
+
import { Inject as Inject5, Injectable as Injectable5, Logger as Logger2, Optional as Optional5 } from "@nestjs/common";
|
|
1991
|
+
import { ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES } from "@saasicat/core";
|
|
1992
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
1993
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1994
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1995
|
+
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;
|
|
1996
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1997
|
+
}
|
|
1998
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
1999
|
+
function _ts_metadata5(k, v) {
|
|
2000
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2001
|
+
}
|
|
2002
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
2003
|
+
function _ts_param5(paramIndex, decorator) {
|
|
2004
|
+
return function(target, key) {
|
|
2005
|
+
decorator(target, key, paramIndex);
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
__name(_ts_param5, "_ts_param");
|
|
2009
|
+
var UNREADABLE = [
|
|
2010
|
+
"NO_SUBSCRIPTION",
|
|
2011
|
+
"REFUSED"
|
|
2012
|
+
];
|
|
2013
|
+
var NO_VOCABULARY = {
|
|
2014
|
+
unknown: [],
|
|
2015
|
+
missing: []
|
|
2016
|
+
};
|
|
2017
|
+
var ContractRefreshService = class {
|
|
2018
|
+
static {
|
|
2019
|
+
__name(this, "ContractRefreshService");
|
|
2020
|
+
}
|
|
2021
|
+
contracts;
|
|
2022
|
+
entitlements;
|
|
2023
|
+
usage;
|
|
2024
|
+
catalogs;
|
|
2025
|
+
freeze;
|
|
2026
|
+
discovery;
|
|
2027
|
+
rlsBypass;
|
|
2028
|
+
audit;
|
|
2029
|
+
logger = new Logger2("SaaSiCat.ContractRefresh");
|
|
2030
|
+
constructor(contracts, entitlements, usage, catalogs, freeze, discovery = null, rlsBypass = null, audit = null) {
|
|
2031
|
+
this.contracts = contracts;
|
|
2032
|
+
this.entitlements = entitlements;
|
|
2033
|
+
this.usage = usage;
|
|
2034
|
+
this.catalogs = catalogs;
|
|
2035
|
+
this.freeze = freeze;
|
|
2036
|
+
this.discovery = discovery;
|
|
2037
|
+
this.rlsBypass = rlsBypass;
|
|
2038
|
+
this.audit = audit;
|
|
2039
|
+
}
|
|
2040
|
+
/** The contracts in force whose frozen features hold unknown keys or lack granted ones. */
|
|
2041
|
+
inspect(asOf = /* @__PURE__ */ new Date()) {
|
|
2042
|
+
return this.withBypass(async () => {
|
|
2043
|
+
const previews = await this.previewAll({}, "features", asOf);
|
|
2044
|
+
return {
|
|
2045
|
+
inForce: previews.length,
|
|
2046
|
+
stale: previews.filter(({ vocabulary }) => vocabulary.unknown.length > 0 || vocabulary.missing.length > 0),
|
|
2047
|
+
unreadable: previews.filter(({ refusal }) => refusal !== null && UNREADABLE.includes(refusal.code))
|
|
2048
|
+
};
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
/** What a refresh would change on each contract selected. Writes nothing. */
|
|
2052
|
+
preview(selection, mode, asOf = /* @__PURE__ */ new Date()) {
|
|
2053
|
+
return this.withBypass(() => this.previewAll(selection, mode, asOf));
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Writes a successor for each contract selected that would change and is
|
|
2057
|
+
* not refused, and keeps the contract it replaces as superseded
|
|
2058
|
+
* (`SC-AUD-007`). What it writes is what `preview` shows at this moment.
|
|
2059
|
+
*/
|
|
2060
|
+
apply(selection, mode, actor, asOf = /* @__PURE__ */ new Date()) {
|
|
2061
|
+
return this.withBypass(async () => {
|
|
2062
|
+
const catalog = await this.catalogs.current();
|
|
2063
|
+
const outcomes = [];
|
|
2064
|
+
for (const target of await this.targets(selection, asOf)) {
|
|
2065
|
+
const planned = await this.plan(target, mode, catalog, asOf);
|
|
2066
|
+
outcomes.push(await this.write(target, planned, mode, actor, asOf));
|
|
2067
|
+
}
|
|
2068
|
+
return outcomes;
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
// -----------------------------------------------------------------
|
|
2072
|
+
async previewAll(selection, mode, asOf) {
|
|
2073
|
+
const catalog = await this.catalogs.current();
|
|
2074
|
+
const previews = [];
|
|
2075
|
+
for (const target of await this.targets(selection, asOf)) {
|
|
2076
|
+
previews.push((await this.plan(target, mode, catalog, asOf)).preview);
|
|
2077
|
+
}
|
|
2078
|
+
return previews;
|
|
2079
|
+
}
|
|
2080
|
+
async write(target, planned, mode, actor, asOf) {
|
|
2081
|
+
const { preview, successor } = planned;
|
|
2082
|
+
const unwritten = {
|
|
2083
|
+
...preview,
|
|
2084
|
+
successorId: null,
|
|
2085
|
+
moved: false
|
|
2086
|
+
};
|
|
2087
|
+
if (!target.contract || !successor || preview.refusal || !preview.changes) {
|
|
2088
|
+
return unwritten;
|
|
2089
|
+
}
|
|
2090
|
+
let written;
|
|
2091
|
+
try {
|
|
2092
|
+
written = await this.contracts.writeSuccessor(target.contract, successor, asOf, {
|
|
2093
|
+
keepParties: mode === "features"
|
|
2094
|
+
});
|
|
2095
|
+
} catch (error) {
|
|
2096
|
+
return {
|
|
2097
|
+
...unwritten,
|
|
2098
|
+
refusal: {
|
|
2099
|
+
code: "REFUSED",
|
|
2100
|
+
reason: messageOf(error)
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
if (!written) return {
|
|
2105
|
+
...unwritten,
|
|
2106
|
+
moved: true
|
|
2107
|
+
};
|
|
2108
|
+
this.entitlements.invalidateTenant(written.tenantId);
|
|
2109
|
+
await this.record(actor, target.contract, written, mode, preview);
|
|
2110
|
+
return {
|
|
2111
|
+
...preview,
|
|
2112
|
+
successorId: written.id,
|
|
2113
|
+
moved: false
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
async plan(target, mode, catalog, asOf) {
|
|
2117
|
+
const contract = target.contract;
|
|
2118
|
+
if (!contract) {
|
|
2119
|
+
return refused(target.contractId, target.found?.tenantId ?? null, NO_VOCABULARY, {
|
|
2120
|
+
code: "NOT_IN_FORCE"
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
const frozen = contractLimits(contract);
|
|
2124
|
+
const known = this.knownFeatures(catalog);
|
|
2125
|
+
const unknownOnly = vocabularyOf({
|
|
2126
|
+
frozen: frozen.features,
|
|
2127
|
+
frozenWithReplacements: frozen.features,
|
|
2128
|
+
grantedToday: /* @__PURE__ */ new Set(),
|
|
2129
|
+
known
|
|
2130
|
+
});
|
|
2131
|
+
let today;
|
|
2132
|
+
try {
|
|
2133
|
+
today = await this.entitlements.contractFeaturesFor(contract.tenantId, contractBundleVersionIds(contract), catalog);
|
|
2134
|
+
} catch (error) {
|
|
2135
|
+
return refused(contract.id, contract.tenantId, unknownOnly, {
|
|
2136
|
+
code: "REFUSED",
|
|
2137
|
+
reason: messageOf(error)
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
if (!today) {
|
|
2141
|
+
return refused(contract.id, contract.tenantId, unknownOnly, {
|
|
2142
|
+
code: "NO_SUBSCRIPTION"
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
const grantedToday = today.features;
|
|
2146
|
+
const vocabulary = vocabularyOf({
|
|
2147
|
+
frozen: frozen.features,
|
|
2148
|
+
frozenWithReplacements: this.entitlements.withReplacements(frozen.features),
|
|
2149
|
+
grantedToday,
|
|
2150
|
+
known
|
|
2151
|
+
});
|
|
2152
|
+
if (mode === "features") {
|
|
2153
|
+
const recorded = recordedPlanVersionOf(contract);
|
|
2154
|
+
if (!recorded) {
|
|
2155
|
+
return refused(contract.id, contract.tenantId, vocabulary, {
|
|
2156
|
+
code: "PLAN_VERSION_NOT_RECORDED"
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
if (recorded !== today.planVersionId) {
|
|
2160
|
+
return refused(contract.id, contract.tenantId, vocabulary, {
|
|
2161
|
+
code: "PLAN_VERSION_MOVED",
|
|
2162
|
+
recorded,
|
|
2163
|
+
bound: today.planVersionId
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
return this.compared(contract, frozen, vocabulary, {
|
|
2167
|
+
...this.contracts.dataOf(contract),
|
|
2168
|
+
status: "active",
|
|
2169
|
+
effectiveFrom: asOf,
|
|
2170
|
+
entitlementSnapshot: {
|
|
2171
|
+
plan: frozen.plan,
|
|
2172
|
+
quotas: {
|
|
2173
|
+
...frozen.quotas
|
|
2174
|
+
},
|
|
2175
|
+
features: [
|
|
2176
|
+
...grantedToday
|
|
2177
|
+
].sort(),
|
|
2178
|
+
...leftOutOf(contract)
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
let successor;
|
|
2183
|
+
try {
|
|
2184
|
+
successor = await this.refrozen(contract, asOf);
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
return refused(contract.id, contract.tenantId, vocabulary, {
|
|
2187
|
+
code: "REFUSED",
|
|
2188
|
+
reason: messageOf(error)
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
const planned = this.compared(contract, frozen, vocabulary, successor);
|
|
2192
|
+
if (planned.preview.money.length > 0) {
|
|
2193
|
+
planned.preview.refusal = {
|
|
2194
|
+
code: "MONEY_WOULD_CHANGE"
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
return planned;
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
2200
|
+
* The contract `freezeOnPlanChange` would write for the tenant now, with
|
|
2201
|
+
* the terms the contract it replaces agreed.
|
|
2202
|
+
*/
|
|
2203
|
+
async refrozen(contract, asOf) {
|
|
2204
|
+
const subscription = await this.usage.findForTenant(contract.tenantId);
|
|
2205
|
+
if (!subscription) throw new Error(`Tenant '${contract.tenantId}' has no subscription.`);
|
|
2206
|
+
const composed = await this.freeze.composeOnPlanChange(
|
|
2207
|
+
contract.tenantId,
|
|
2208
|
+
subscription.plan,
|
|
2209
|
+
// The column behind it is the `BillingCycle` enum in the canonical
|
|
2210
|
+
// schema; the usage port types it as a string.
|
|
2211
|
+
subscription.billingCycle,
|
|
2212
|
+
asOf,
|
|
2213
|
+
contract.effectiveUntil
|
|
2214
|
+
);
|
|
2215
|
+
return withAgreedTerms(contract, composed);
|
|
2216
|
+
}
|
|
2217
|
+
compared(contract, frozen, vocabulary, successor) {
|
|
2218
|
+
const snapshot = successor.entitlementSnapshot;
|
|
2219
|
+
const features = featureChangeOf(frozen.features, snapshot?.features ?? []);
|
|
2220
|
+
const quotas = quotaChangesOf(frozen.quotas, snapshot?.quotas ?? {});
|
|
2221
|
+
const money = moneyChangesOf(contract, successor);
|
|
2222
|
+
const leftOutMoved = !sameMembers(contract.entitlementSnapshot?.leftOutBundleVersionIds ?? [], snapshot?.leftOutBundleVersionIds ?? []);
|
|
2223
|
+
return {
|
|
2224
|
+
preview: {
|
|
2225
|
+
contractId: contract.id,
|
|
2226
|
+
tenantId: contract.tenantId,
|
|
2227
|
+
vocabulary,
|
|
2228
|
+
features,
|
|
2229
|
+
quotas,
|
|
2230
|
+
money,
|
|
2231
|
+
changes: features.added.length > 0 || features.removed.length > 0 || quotas.length > 0 || money.length > 0 || frozen.plan !== snapshot?.plan || leftOutMoved,
|
|
2232
|
+
refusal: null
|
|
2233
|
+
},
|
|
2234
|
+
successor
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Every feature key the application knows: the ones its code declares, the
|
|
2239
|
+
* old keys those declarations say they replace, and the catalogue's.
|
|
2240
|
+
*/
|
|
2241
|
+
knownFeatures(catalog) {
|
|
2242
|
+
const known = /* @__PURE__ */ new Set();
|
|
2243
|
+
for (const feature of this.discovery?.features ?? []) {
|
|
2244
|
+
known.add(feature.featureKey);
|
|
2245
|
+
for (const old of feature.replaces ?? []) known.add(old);
|
|
2246
|
+
}
|
|
2247
|
+
for (const feature of catalog.features ?? []) known.add(feature.key);
|
|
2248
|
+
return known;
|
|
2249
|
+
}
|
|
2250
|
+
/** The contracts selected, each read as it stands, or `null` where it is not in force. */
|
|
2251
|
+
async targets(selection, asOf) {
|
|
2252
|
+
if (selection.contractIds === void 0) {
|
|
2253
|
+
const inForce = await this.contracts.list({
|
|
2254
|
+
asOf
|
|
2255
|
+
});
|
|
2256
|
+
return inForce.filter((contract) => isInForce(contract, asOf)).map((contract) => ({
|
|
2257
|
+
contractId: contract.id,
|
|
2258
|
+
found: contract,
|
|
2259
|
+
contract
|
|
2260
|
+
}));
|
|
2261
|
+
}
|
|
2262
|
+
const targets = [];
|
|
2263
|
+
for (const contractId of selection.contractIds) {
|
|
2264
|
+
const found = await this.contracts.findById(contractId);
|
|
2265
|
+
targets.push({
|
|
2266
|
+
contractId,
|
|
2267
|
+
found,
|
|
2268
|
+
contract: found && isInForce(found, asOf) ? found : null
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
return targets;
|
|
2272
|
+
}
|
|
2273
|
+
/** Best effort: the successor stands whether or not its record could be written (`SC-AUD-004`). */
|
|
2274
|
+
async record(actor, previous, successor, mode, preview) {
|
|
2275
|
+
if (!this.audit) return;
|
|
2276
|
+
try {
|
|
2277
|
+
await this.audit.log({
|
|
2278
|
+
actor,
|
|
2279
|
+
entity: "SubscriptionContract",
|
|
2280
|
+
entityId: successor.id,
|
|
2281
|
+
action: "SUBSCRIPTION_CONTRACT_REFRESH",
|
|
2282
|
+
changes: {
|
|
2283
|
+
mode,
|
|
2284
|
+
supersedes: previous.id,
|
|
2285
|
+
tenantId: successor.tenantId,
|
|
2286
|
+
features: preview.features,
|
|
2287
|
+
quotas: preview.quotas
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
this.logger.error(`The audit record of the refresh that wrote contract ${successor.id} could not be written; the successor itself was.`, error instanceof Error ? error.stack : String(error));
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
withBypass(read) {
|
|
2295
|
+
return this.rlsBypass ? this.rlsBypass.runWithBypass(read) : read();
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
ContractRefreshService = _ts_decorate7([
|
|
2299
|
+
Injectable5(),
|
|
2300
|
+
_ts_param5(0, Inject5(SubscriptionContractService)),
|
|
2301
|
+
_ts_param5(1, Inject5(ENTITLEMENT_SERVICE_TOKEN)),
|
|
2302
|
+
_ts_param5(2, Inject5(SUBSCRIPTION_USAGE_PORT_TOKEN)),
|
|
2303
|
+
_ts_param5(3, Inject5(PLAN_CATALOG_SOURCE_TOKEN)),
|
|
2304
|
+
_ts_param5(4, Inject5(SubscriptionContractFreezeService)),
|
|
2305
|
+
_ts_param5(5, Optional5()),
|
|
2306
|
+
_ts_param5(5, Inject5(DISCOVERY_SNAPSHOT_TOKEN)),
|
|
2307
|
+
_ts_param5(6, Optional5()),
|
|
2308
|
+
_ts_param5(6, Inject5(RLS_BYPASS_PORT_TOKEN)),
|
|
2309
|
+
_ts_param5(7, Optional5()),
|
|
2310
|
+
_ts_param5(7, Inject5(AdminAuditService)),
|
|
2311
|
+
_ts_metadata5("design:type", Function),
|
|
2312
|
+
_ts_metadata5("design:paramtypes", [
|
|
2313
|
+
typeof SubscriptionContractService === "undefined" ? Object : SubscriptionContractService,
|
|
2314
|
+
typeof EntitlementService === "undefined" ? Object : EntitlementService,
|
|
2315
|
+
typeof SubscriptionUsagePort === "undefined" ? Object : SubscriptionUsagePort,
|
|
2316
|
+
typeof PlanCatalogSource === "undefined" ? Object : PlanCatalogSource,
|
|
2317
|
+
typeof SubscriptionContractFreezeService === "undefined" ? Object : SubscriptionContractFreezeService,
|
|
2318
|
+
Object,
|
|
2319
|
+
Object,
|
|
2320
|
+
Object
|
|
2321
|
+
])
|
|
2322
|
+
], ContractRefreshService);
|
|
2323
|
+
function refused(contractId, tenantId, vocabulary, refusal) {
|
|
2324
|
+
return {
|
|
2325
|
+
preview: {
|
|
2326
|
+
contractId,
|
|
2327
|
+
tenantId,
|
|
2328
|
+
vocabulary,
|
|
2329
|
+
features: {
|
|
2330
|
+
added: [],
|
|
2331
|
+
removed: []
|
|
2332
|
+
},
|
|
2333
|
+
quotas: [],
|
|
2334
|
+
money: [],
|
|
2335
|
+
changes: false,
|
|
2336
|
+
refusal
|
|
2337
|
+
},
|
|
2338
|
+
successor: null
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
__name(refused, "refused");
|
|
2342
|
+
function leftOutOf(contract) {
|
|
2343
|
+
const leftOut = contract.entitlementSnapshot?.leftOutBundleVersionIds;
|
|
2344
|
+
return leftOut && leftOut.length > 0 ? {
|
|
2345
|
+
leftOutBundleVersionIds: [
|
|
2346
|
+
...leftOut
|
|
2347
|
+
]
|
|
2348
|
+
} : {};
|
|
2349
|
+
}
|
|
2350
|
+
__name(leftOutOf, "leftOutOf");
|
|
2351
|
+
function recordedPlanVersionOf(contract) {
|
|
2352
|
+
return contract.originalPlanVersionId ?? contract.lineItems.find((line) => line.kind === "plan")?.sourceVersionId ?? null;
|
|
2353
|
+
}
|
|
2354
|
+
__name(recordedPlanVersionOf, "recordedPlanVersionOf");
|
|
2355
|
+
function isInForce(contract, asOf) {
|
|
2356
|
+
return ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES.includes(contract.status) && contract.effectiveFrom.getTime() <= asOf.getTime() && (contract.effectiveUntil === null || contract.effectiveUntil.getTime() > asOf.getTime());
|
|
2357
|
+
}
|
|
2358
|
+
__name(isInForce, "isInForce");
|
|
2359
|
+
function sameMembers(a, b) {
|
|
2360
|
+
const left = new Set(a);
|
|
2361
|
+
const right = new Set(b);
|
|
2362
|
+
return left.size === right.size && [
|
|
2363
|
+
...left
|
|
2364
|
+
].every((id) => right.has(id));
|
|
2365
|
+
}
|
|
2366
|
+
__name(sameMembers, "sameMembers");
|
|
2367
|
+
function messageOf(error) {
|
|
2368
|
+
if (error && typeof error === "object" && "getResponse" in error) {
|
|
2369
|
+
const response = error.getResponse();
|
|
2370
|
+
if (response && typeof response === "object" && "message" in response) {
|
|
2371
|
+
return String(response.message);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
return error instanceof Error ? error.message : String(error);
|
|
2375
|
+
}
|
|
2376
|
+
__name(messageOf, "messageOf");
|
|
2377
|
+
|
|
2378
|
+
// src/billing/charges/subscriber-charge.tokens.ts
|
|
2379
|
+
var SUBSCRIBER_LEDGER_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SubscriberLedgerRepository");
|
|
2380
|
+
|
|
2381
|
+
// src/billing/charges/subscriber-account.service.ts
|
|
2382
|
+
import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
|
|
2383
|
+
|
|
2384
|
+
// src/billing/charges/subscriber-account.ts
|
|
2385
|
+
var SOURCE_ORDER = {
|
|
2386
|
+
plan: 0,
|
|
2387
|
+
bundle: 1,
|
|
2388
|
+
discount: 2
|
|
2389
|
+
};
|
|
2390
|
+
function toWire(charge) {
|
|
2391
|
+
return {
|
|
2392
|
+
...charge,
|
|
2393
|
+
periodStart: charge.periodStart.toISOString(),
|
|
2394
|
+
periodEnd: charge.periodEnd.toISOString(),
|
|
2395
|
+
bookedAt: charge.bookedAt.toISOString(),
|
|
2396
|
+
createdAt: charge.createdAt.toISOString()
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
__name(toWire, "toWire");
|
|
2400
|
+
function newestFirst(a, b) {
|
|
2401
|
+
return b.bookedAt.getTime() - a.bookedAt.getTime() || b.periodStart.getTime() - a.periodStart.getTime() || SOURCE_ORDER[a.source] - SOURCE_ORDER[b.source] || a.id.localeCompare(b.id);
|
|
2402
|
+
}
|
|
2403
|
+
__name(newestFirst, "newestFirst");
|
|
2404
|
+
function accountViewOf(input) {
|
|
2405
|
+
const titles = /* @__PURE__ */ new Map();
|
|
2406
|
+
for (const contract of input.contracts) {
|
|
2407
|
+
for (const line of contract.lineItems) titles.set(line.id, line.titleSnapshot);
|
|
2408
|
+
}
|
|
2409
|
+
const entries = [
|
|
2410
|
+
...input.charges
|
|
2411
|
+
].sort(newestFirst).map((charge) => ({
|
|
2412
|
+
charge: toWire(charge),
|
|
2413
|
+
title: titles.get(charge.contractLineItemId) ?? null
|
|
2414
|
+
}));
|
|
2415
|
+
const subscriber = input.subscriber;
|
|
2416
|
+
return {
|
|
2417
|
+
holder: subscriber ? {
|
|
2418
|
+
id: subscriber.id,
|
|
2419
|
+
customerNumber: subscriber.customerNumber,
|
|
2420
|
+
legalName: subscriber.legalName
|
|
2421
|
+
} : null,
|
|
2422
|
+
entries
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
__name(accountViewOf, "accountViewOf");
|
|
2426
|
+
|
|
2427
|
+
// src/billing/charges/subscriber-account.service.ts
|
|
2428
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
2429
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2430
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2431
|
+
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;
|
|
2432
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2433
|
+
}
|
|
2434
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
2435
|
+
function _ts_metadata6(k, v) {
|
|
2436
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2437
|
+
}
|
|
2438
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
2439
|
+
function _ts_param6(paramIndex, decorator) {
|
|
2440
|
+
return function(target, key) {
|
|
2441
|
+
decorator(target, key, paramIndex);
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
__name(_ts_param6, "_ts_param");
|
|
2445
|
+
var SubscriberAccountService = class {
|
|
2446
|
+
static {
|
|
2447
|
+
__name(this, "SubscriberAccountService");
|
|
2448
|
+
}
|
|
2449
|
+
ledger;
|
|
2450
|
+
contracts;
|
|
2451
|
+
subscribers;
|
|
2452
|
+
subscriptions;
|
|
2453
|
+
constructor(ledger, contracts, subscribers, subscriptions) {
|
|
2454
|
+
this.ledger = ledger;
|
|
2455
|
+
this.contracts = contracts;
|
|
2456
|
+
this.subscribers = subscribers;
|
|
2457
|
+
this.subscriptions = subscriptions;
|
|
2458
|
+
}
|
|
2459
|
+
/** The account of the tenant's subscriber; empty while it has no subscription. */
|
|
2460
|
+
async accountOf(tenantId) {
|
|
2461
|
+
const [subscription, subscriber, contracts] = await Promise.all([
|
|
2462
|
+
this.subscriptions.findForTenant(tenantId),
|
|
2463
|
+
this.subscribers.findByTenantId(tenantId),
|
|
2464
|
+
this.contracts.list({
|
|
2465
|
+
tenantId
|
|
2466
|
+
})
|
|
2467
|
+
]);
|
|
2468
|
+
const charges = subscription?.id ? await this.ledger.listBySubscription(subscription.id) : [];
|
|
2469
|
+
return accountViewOf({
|
|
2470
|
+
subscriber,
|
|
2471
|
+
charges,
|
|
2472
|
+
contracts
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
SubscriberAccountService = _ts_decorate8([
|
|
2477
|
+
Injectable6(),
|
|
2478
|
+
_ts_param6(0, Inject6(SUBSCRIBER_LEDGER_REPOSITORY_TOKEN)),
|
|
2479
|
+
_ts_param6(1, Inject6(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN)),
|
|
2480
|
+
_ts_param6(2, Inject6(SUBSCRIBER_REPOSITORY_TOKEN)),
|
|
2481
|
+
_ts_param6(3, Inject6(SUBSCRIPTION_USAGE_PORT_TOKEN)),
|
|
2482
|
+
_ts_metadata6("design:type", Function),
|
|
2483
|
+
_ts_metadata6("design:paramtypes", [
|
|
2484
|
+
typeof SubscriberLedgerRepository === "undefined" ? Object : SubscriberLedgerRepository,
|
|
2485
|
+
typeof SubscriptionContractRepository === "undefined" ? Object : SubscriptionContractRepository,
|
|
2486
|
+
typeof SubscriberRepository === "undefined" ? Object : SubscriberRepository,
|
|
2487
|
+
typeof SubscriptionUsagePort === "undefined" ? Object : SubscriptionUsagePort
|
|
2488
|
+
])
|
|
2489
|
+
], SubscriberAccountService);
|
|
2490
|
+
|
|
2491
|
+
// src/billing/charges/subscriber-account.module.ts
|
|
2492
|
+
import { Controller as Controller2, Get as Get2, Inject as Inject7, Module as Module2, Param, UseGuards } from "@nestjs/common";
|
|
2493
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
2494
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2495
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2496
|
+
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;
|
|
2497
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2498
|
+
}
|
|
2499
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
2500
|
+
function _ts_metadata7(k, v) {
|
|
2501
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2502
|
+
}
|
|
2503
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
2504
|
+
function _ts_param7(paramIndex, decorator) {
|
|
2505
|
+
return function(target, key) {
|
|
2506
|
+
decorator(target, key, paramIndex);
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
__name(_ts_param7, "_ts_param");
|
|
2510
|
+
function buildSubscriberAccountController(guards) {
|
|
2511
|
+
let GeneratedSubscriberAccountController = class GeneratedSubscriberAccountController {
|
|
2512
|
+
static {
|
|
2513
|
+
__name(this, "GeneratedSubscriberAccountController");
|
|
2514
|
+
}
|
|
2515
|
+
tenants;
|
|
2516
|
+
accounts;
|
|
2517
|
+
rlsBypass;
|
|
2518
|
+
constructor(tenants, accounts, rlsBypass) {
|
|
2519
|
+
this.tenants = tenants;
|
|
2520
|
+
this.accounts = accounts;
|
|
2521
|
+
this.rlsBypass = rlsBypass;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* The charges of the tenant's subscriber. An operator's request is
|
|
2525
|
+
* scoped to no tenant, so finding the tenant and reading its account
|
|
2526
|
+
* both run outside any row-level policy the application keeps its
|
|
2527
|
+
* tenants apart with.
|
|
2528
|
+
*/
|
|
2529
|
+
chargesOf(slug) {
|
|
2530
|
+
const read = /* @__PURE__ */ __name(async () => {
|
|
2531
|
+
const tenant = await this.tenants.getTenantDetail(slug);
|
|
2532
|
+
return this.accounts.accountOf(tenant.id);
|
|
2533
|
+
}, "read");
|
|
2534
|
+
return this.rlsBypass.runWithBypass(read);
|
|
2535
|
+
}
|
|
2536
|
+
};
|
|
2537
|
+
_ts_decorate9([
|
|
2538
|
+
Get2("tenants/:slug/charges"),
|
|
2539
|
+
_ts_param7(0, Param("slug")),
|
|
2540
|
+
_ts_metadata7("design:type", Function),
|
|
2541
|
+
_ts_metadata7("design:paramtypes", [
|
|
2542
|
+
String
|
|
2543
|
+
]),
|
|
2544
|
+
_ts_metadata7("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
2545
|
+
], GeneratedSubscriberAccountController.prototype, "chargesOf", null);
|
|
2546
|
+
GeneratedSubscriberAccountController = _ts_decorate9([
|
|
2547
|
+
Controller2("admin"),
|
|
2548
|
+
AllowDuringMaintenance(),
|
|
2549
|
+
UseGuards(...guards),
|
|
2550
|
+
_ts_param7(0, Inject7(AdminResourcesService)),
|
|
2551
|
+
_ts_param7(1, Inject7(SubscriberAccountService)),
|
|
2552
|
+
_ts_param7(2, Inject7(RLS_BYPASS_PORT_TOKEN)),
|
|
2553
|
+
_ts_metadata7("design:type", Function),
|
|
2554
|
+
_ts_metadata7("design:paramtypes", [
|
|
2555
|
+
typeof AdminResourcesService === "undefined" ? Object : AdminResourcesService,
|
|
2556
|
+
typeof SubscriberAccountService === "undefined" ? Object : SubscriberAccountService,
|
|
2557
|
+
typeof RlsBypassPort === "undefined" ? Object : RlsBypassPort
|
|
2558
|
+
])
|
|
2559
|
+
], GeneratedSubscriberAccountController);
|
|
2560
|
+
return GeneratedSubscriberAccountController;
|
|
2561
|
+
}
|
|
2562
|
+
__name(buildSubscriberAccountController, "buildSubscriberAccountController");
|
|
2563
|
+
var SubscriberAccountModule = class _SubscriberAccountModule {
|
|
2564
|
+
static {
|
|
2565
|
+
__name(this, "SubscriberAccountModule");
|
|
2566
|
+
}
|
|
2567
|
+
static forRoot(options) {
|
|
2568
|
+
return {
|
|
2569
|
+
module: _SubscriberAccountModule,
|
|
2570
|
+
imports: options.imports,
|
|
2571
|
+
controllers: [
|
|
2572
|
+
buildSubscriberAccountController(options.guards)
|
|
2573
|
+
]
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
};
|
|
2577
|
+
SubscriberAccountModule = _ts_decorate9([
|
|
2578
|
+
Module2({})
|
|
2579
|
+
], SubscriberAccountModule);
|
|
2580
|
+
|
|
2581
|
+
// src/billing/charges/subscriber-charge.service.ts
|
|
2582
|
+
import { Inject as Inject8, Injectable as Injectable7, Logger as Logger3, Optional as Optional6 } from "@nestjs/common";
|
|
2583
|
+
|
|
2584
|
+
// src/billing/charges/charge-derivation.ts
|
|
2585
|
+
var PERIOD_ORIGINS = [
|
|
2586
|
+
"activation",
|
|
2587
|
+
"renewal",
|
|
2588
|
+
"bundleBooking",
|
|
2589
|
+
"planChange"
|
|
2590
|
+
];
|
|
2591
|
+
var MAX_CYCLES = 600;
|
|
2592
|
+
var CENTS = 100;
|
|
2593
|
+
function deriveDueCharges(input) {
|
|
2594
|
+
const planCharges = derivePlanCharges(input);
|
|
2595
|
+
const periods = [
|
|
2596
|
+
...writtenPlanPeriods(input),
|
|
2597
|
+
...planCharges.map(({ period }) => period)
|
|
2598
|
+
];
|
|
2599
|
+
return [
|
|
2600
|
+
...planCharges.map(({ charge }) => charge),
|
|
2601
|
+
...deriveUpgradeDifferences(input, periods),
|
|
2602
|
+
...deriveDiscountCharges(input, planCharges, periods),
|
|
2603
|
+
...deriveBundleCharges(input, accountStart(input))
|
|
2604
|
+
];
|
|
2605
|
+
}
|
|
2606
|
+
__name(deriveDueCharges, "deriveDueCharges");
|
|
2607
|
+
function bookingsTheContractMisses(contracts, bookings, now) {
|
|
2608
|
+
const inForce = contractInForce(contracts, now);
|
|
2609
|
+
if (!inForce) return [];
|
|
2610
|
+
return bookings.filter((booking) => inForce.effectiveFrom < booking.startedAt && booking.currentPeriodEnd !== null && (booking.canceledEffectiveAt === null || booking.canceledEffectiveAt > now) && !inForce.lineItems.some((item) => item.kind === "bundle" && item.sourceVersionId === booking.bundleVersionId));
|
|
2611
|
+
}
|
|
2612
|
+
__name(bookingsTheContractMisses, "bookingsTheContractMisses");
|
|
2613
|
+
function derivePlanCharges(input) {
|
|
2614
|
+
const { subscription } = input;
|
|
2615
|
+
if (subscription.status === "TRIAL") return [];
|
|
2616
|
+
const window = windowOf(subscription.currentPeriodStart, subscription.currentPeriodEnd);
|
|
2617
|
+
if (!window) return [];
|
|
2618
|
+
const rhythm = rhythmOf(subscription.billingCycle);
|
|
2619
|
+
const isPlanLine = /* @__PURE__ */ __name((item) => item.kind === "plan" && item.billingCycle === rhythm, "isPlanLine");
|
|
2620
|
+
const replaced = periodReplacedBy(window, input);
|
|
2621
|
+
if (replaced) return newPeriodAfterChange(input, window, replaced, isPlanLine);
|
|
2622
|
+
const priced = periodsToCharge({
|
|
2623
|
+
window,
|
|
2624
|
+
from: lastPeriodEnd(input.written, "plan", subscription.id) ?? window.start,
|
|
2625
|
+
cycle: subscription.billingCycle,
|
|
2626
|
+
anchorDay: subscription.anchorDay,
|
|
2627
|
+
input,
|
|
2628
|
+
endsAt: subscription.endsAt
|
|
2629
|
+
}).flatMap((period) => {
|
|
2630
|
+
const found = lineFor(input.contracts, period, isPlanLine);
|
|
2631
|
+
return found ? [
|
|
2632
|
+
{
|
|
2633
|
+
period,
|
|
2634
|
+
...found
|
|
2635
|
+
}
|
|
2636
|
+
] : [];
|
|
2637
|
+
});
|
|
2638
|
+
const firstContract = earliest2(input.contracts);
|
|
2639
|
+
const opensSubscription = /* @__PURE__ */ __name((period) => holds(period, subscription.startedAt) || holds(period, firstContract?.effectiveFrom ?? null), "opensSubscription");
|
|
2640
|
+
return priced.map(({ period, contract, line }) => {
|
|
2641
|
+
const charge = chargeOf(input, contract, line, {
|
|
2642
|
+
origin: opensSubscription(period) ? "activation" : "renewal",
|
|
2643
|
+
source: "plan",
|
|
2644
|
+
sourceRef: subscription.id,
|
|
2645
|
+
period,
|
|
2646
|
+
amountNet: line.priceNet,
|
|
2647
|
+
bookedAt: period.start
|
|
2648
|
+
});
|
|
2649
|
+
return {
|
|
2650
|
+
charge,
|
|
2651
|
+
period: {
|
|
2652
|
+
charge,
|
|
2653
|
+
rhythm
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
__name(derivePlanCharges, "derivePlanCharges");
|
|
2659
|
+
function periodReplacedBy(window, input) {
|
|
2660
|
+
const whole = input.written.filter((charge) => isWholePlanPeriod(charge, input.written, input.subscription.id));
|
|
2661
|
+
return whole.filter((charge) => charge.periodStart < window.start && window.start < charge.periodEnd).sort((a, b) => a.periodStart.getTime() - b.periodStart.getTime())[0] ?? null;
|
|
2662
|
+
}
|
|
2663
|
+
__name(periodReplacedBy, "periodReplacedBy");
|
|
2664
|
+
function newPeriodAfterChange(input, window, replaced, isPlanLine) {
|
|
2665
|
+
const { subscription } = input;
|
|
2666
|
+
if (!isChargeable(window, input, subscription.endsAt)) return [];
|
|
2667
|
+
const found = lineFor(input.contracts, window, isPlanLine);
|
|
2668
|
+
if (!found) return [];
|
|
2669
|
+
const charge = chargeOf(input, found.contract, found.line, {
|
|
2670
|
+
origin: "planChange",
|
|
2671
|
+
source: "plan",
|
|
2672
|
+
sourceRef: subscription.id,
|
|
2673
|
+
period: window,
|
|
2674
|
+
amountNet: computeNewPeriodCharge({
|
|
2675
|
+
periodStart: replaced.periodStart,
|
|
2676
|
+
periodEnd: replaced.periodEnd,
|
|
2677
|
+
now: window.start,
|
|
2678
|
+
currentPriceNet: pricePaidBefore(window.start, replaced, input.contracts),
|
|
2679
|
+
targetPriceNet: found.line.priceNet
|
|
2680
|
+
}).prorataDeltaNet,
|
|
2681
|
+
bookedAt: window.start
|
|
2682
|
+
});
|
|
2683
|
+
return [
|
|
2684
|
+
{
|
|
2685
|
+
charge,
|
|
2686
|
+
period: {
|
|
2687
|
+
charge,
|
|
2688
|
+
rhythm: rhythmOf(subscription.billingCycle)
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
];
|
|
2692
|
+
}
|
|
2693
|
+
__name(newPeriodAfterChange, "newPeriodAfterChange");
|
|
2694
|
+
function pricePaidBefore(at, replaced, contracts) {
|
|
2695
|
+
const chargedUnder = lineById(contracts, replaced.contractLineItemId);
|
|
2696
|
+
const rhythm = chargedUnder?.billingCycle;
|
|
2697
|
+
const before = contractInForce(contracts, new Date(at.getTime() - 1));
|
|
2698
|
+
const line = before?.lineItems.find((item) => item.kind === "plan" && item.billingCycle === rhythm);
|
|
2699
|
+
return line?.priceNet ?? chargedUnder?.priceNet ?? replaced.amountNet;
|
|
2700
|
+
}
|
|
2701
|
+
__name(pricePaidBefore, "pricePaidBefore");
|
|
2702
|
+
function deriveUpgradeDifferences(input, periods) {
|
|
2703
|
+
const { subscription } = input;
|
|
2704
|
+
const charges = [];
|
|
2705
|
+
for (const { charge: period, rhythm } of periods) {
|
|
2706
|
+
const isPlanLine = /* @__PURE__ */ __name((item) => item.kind === "plan" && item.billingCycle === rhythm, "isPlanLine");
|
|
2707
|
+
for (const contract of input.contracts) {
|
|
2708
|
+
const at = contract.effectiveFrom;
|
|
2709
|
+
if (contract.status === "scheduled") continue;
|
|
2710
|
+
if (!(period.periodStart < at && at < period.periodEnd)) continue;
|
|
2711
|
+
if (!isChargeable({
|
|
2712
|
+
start: at,
|
|
2713
|
+
end: period.periodEnd
|
|
2714
|
+
}, input, subscription.endsAt)) {
|
|
2715
|
+
continue;
|
|
2716
|
+
}
|
|
2717
|
+
const line = contract.lineItems.find(isPlanLine);
|
|
2718
|
+
const before = contractInForce(input.contracts, new Date(at.getTime() - 1));
|
|
2719
|
+
const lineBefore = before?.lineItems.find(isPlanLine);
|
|
2720
|
+
if (!line || !lineBefore) continue;
|
|
2721
|
+
const difference = computeProration({
|
|
2722
|
+
periodStart: period.periodStart,
|
|
2723
|
+
periodEnd: period.periodEnd,
|
|
2724
|
+
now: at,
|
|
2725
|
+
currentPriceNet: lineBefore.priceNet,
|
|
2726
|
+
targetPriceNet: line.priceNet
|
|
2727
|
+
}).prorataDeltaNet;
|
|
2728
|
+
if (difference <= 0) continue;
|
|
2729
|
+
charges.push(chargeOf(input, contract, line, {
|
|
2730
|
+
origin: "planChange",
|
|
2731
|
+
source: "plan",
|
|
2732
|
+
sourceRef: subscription.id,
|
|
2733
|
+
period: {
|
|
2734
|
+
start: at,
|
|
2735
|
+
end: period.periodEnd
|
|
2736
|
+
},
|
|
2737
|
+
amountNet: difference,
|
|
2738
|
+
bookedAt: at
|
|
2739
|
+
}));
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
return charges;
|
|
2743
|
+
}
|
|
2744
|
+
__name(deriveUpgradeDifferences, "deriveUpgradeDifferences");
|
|
2745
|
+
function writtenPlanPeriods(input) {
|
|
2746
|
+
return input.written.flatMap((charge) => {
|
|
2747
|
+
if (!isWholePlanPeriod(charge, input.written, input.subscription.id)) return [];
|
|
2748
|
+
const line = lineById(input.contracts, charge.contractLineItemId);
|
|
2749
|
+
return line ? [
|
|
2750
|
+
{
|
|
2751
|
+
charge,
|
|
2752
|
+
rhythm: line.billingCycle
|
|
2753
|
+
}
|
|
2754
|
+
] : [];
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
__name(writtenPlanPeriods, "writtenPlanPeriods");
|
|
2758
|
+
function isWholePlanPeriod(charge, written, subscriptionId) {
|
|
2759
|
+
if (charge.source !== "plan" || charge.sourceRef !== subscriptionId) return false;
|
|
2760
|
+
if (!PERIOD_ORIGINS.includes(charge.origin)) return false;
|
|
2761
|
+
return charge.origin !== "planChange" || !isDifference(charge, written);
|
|
2762
|
+
}
|
|
2763
|
+
__name(isWholePlanPeriod, "isWholePlanPeriod");
|
|
2764
|
+
function isDifference(charge, written) {
|
|
2765
|
+
return written.some((other) => other.source === "plan" && other.sourceRef === charge.sourceRef && other.periodStart < charge.periodStart && sameInstant(other.periodEnd, charge.periodEnd));
|
|
2766
|
+
}
|
|
2767
|
+
__name(isDifference, "isDifference");
|
|
2768
|
+
function accountStart(input) {
|
|
2769
|
+
const { subscription } = input;
|
|
2770
|
+
const firstCharged = firstPlanPeriodStart(input.written, subscription.id);
|
|
2771
|
+
if (firstCharged) return firstCharged;
|
|
2772
|
+
if (subscription.status === "TRIAL") return null;
|
|
2773
|
+
return windowOf(subscription.currentPeriodStart, subscription.currentPeriodEnd)?.start ?? null;
|
|
2774
|
+
}
|
|
2775
|
+
__name(accountStart, "accountStart");
|
|
2776
|
+
function deriveBundleCharges(input, accountBegins) {
|
|
2777
|
+
if (!accountBegins) return [];
|
|
2778
|
+
const { subscription } = input;
|
|
2779
|
+
const anchorDay = subscription.anchorDay;
|
|
2780
|
+
const charges = [];
|
|
2781
|
+
for (const booking of input.bookings) {
|
|
2782
|
+
const window = windowOf(booking.currentPeriodStart, booking.currentPeriodEnd);
|
|
2783
|
+
if (!window) continue;
|
|
2784
|
+
const cycle = booking.billingCycle ?? subscription.billingCycle;
|
|
2785
|
+
const endsAt = earlierOf(booking.canceledEffectiveAt, subscription.endsAt);
|
|
2786
|
+
const lastEnd = lastPeriodEnd(input.written, "bundle", booking.id);
|
|
2787
|
+
const chainStart = laterOf(booking.startedAt, accountBegins);
|
|
2788
|
+
const periods = lastEnd ? periodsToCharge({
|
|
2789
|
+
window,
|
|
2790
|
+
from: lastEnd,
|
|
2791
|
+
cycle,
|
|
2792
|
+
anchorDay,
|
|
2793
|
+
input,
|
|
2794
|
+
endsAt
|
|
2795
|
+
}) : [
|
|
2796
|
+
...firstBundlePeriod(window, chainStart, cycle, anchorDay),
|
|
2797
|
+
...periodsToCharge({
|
|
2798
|
+
window,
|
|
2799
|
+
from: firstBoundaryAfter(window, chainStart, cycle, anchorDay),
|
|
2800
|
+
cycle,
|
|
2801
|
+
anchorDay,
|
|
2802
|
+
input,
|
|
2803
|
+
endsAt
|
|
2804
|
+
})
|
|
2805
|
+
].filter((period) => isChargeable(period, input, endsAt));
|
|
2806
|
+
for (const period of periods) {
|
|
2807
|
+
const priced = lineFor(input.contracts, period, (item) => item.kind === "bundle" && item.sourceVersionId === booking.bundleVersionId);
|
|
2808
|
+
if (!priced) continue;
|
|
2809
|
+
const { contract, line } = priced;
|
|
2810
|
+
charges.push(chargeOf(input, contract, line, {
|
|
2811
|
+
origin: !lastEnd && sameInstant(period.start, chainStart) ? "bundleBooking" : "renewal",
|
|
2812
|
+
source: "bundle",
|
|
2813
|
+
sourceRef: booking.id,
|
|
2814
|
+
period,
|
|
2815
|
+
// Every period is charged for its share of the whole cycle
|
|
2816
|
+
// it ends, which is all of it except for the short first
|
|
2817
|
+
// one — the same arithmetic the preview quoted
|
|
2818
|
+
// (`SC-BUN-003`, `SC-PRIC-002`).
|
|
2819
|
+
amountNet: computeProration({
|
|
2820
|
+
periodStart: bundleFirstPeriodStart(period.end, cycle, anchorDay),
|
|
2821
|
+
periodEnd: period.end,
|
|
2822
|
+
now: period.start,
|
|
2823
|
+
currentPriceNet: 0,
|
|
2824
|
+
targetPriceNet: line.priceNet
|
|
2825
|
+
}).prorataDeltaNet,
|
|
2826
|
+
bookedAt: period.start
|
|
2827
|
+
}));
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
return charges;
|
|
2831
|
+
}
|
|
2832
|
+
__name(deriveBundleCharges, "deriveBundleCharges");
|
|
2833
|
+
function deriveDiscountCharges(input, due, periods) {
|
|
2834
|
+
const { subscription } = input;
|
|
2835
|
+
const charges = [];
|
|
2836
|
+
for (const { contract, line } of discountsWhereAgreed(input.contracts)) {
|
|
2837
|
+
const rhythm = line.billingCycle;
|
|
2838
|
+
const cycle = rhythm === "yearly" ? "YEARLY" : "MONTHLY";
|
|
2839
|
+
const first = firstPeriodConcludedFor(contract, periods);
|
|
2840
|
+
if (!first) continue;
|
|
2841
|
+
const anchorDay = rhythm === rhythmOf(subscription.billingCycle) ? subscription.anchorDay : first.getUTCDate();
|
|
2842
|
+
const beganInItsRhythm = periods.some((period) => period.rhythm === rhythm && sameInstant(period.charge.periodStart, first));
|
|
2843
|
+
const endedAt = beganInItsRhythm ? firstPeriodStart(periods.filter((period) => period.rhythm !== rhythm && period.charge.periodStart > first)) : first;
|
|
2844
|
+
for (const { charge: planCharge, period } of due) {
|
|
2845
|
+
const amount = endedAt && planCharge.periodStart > endedAt ? 0 : period.rhythm === rhythm ? discountInPeriod(line, first, planCharge.periodStart, cycle, anchorDay) : sameInstant(planCharge.periodStart, endedAt) ? (
|
|
2846
|
+
// period, and no more than it costs.
|
|
2847
|
+
Math.min(discountLeftAt(line, first, planCharge.periodStart, cycle, anchorDay), planCharge.amountNet)
|
|
2848
|
+
) : 0;
|
|
2849
|
+
if (amount <= 0) continue;
|
|
2850
|
+
charges.push(chargeOf(input, contract, line, {
|
|
2851
|
+
origin: planCharge.origin,
|
|
2852
|
+
source: "discount",
|
|
2853
|
+
sourceRef: line.sourceKey,
|
|
2854
|
+
period: {
|
|
2855
|
+
start: planCharge.periodStart,
|
|
2856
|
+
end: planCharge.periodEnd
|
|
2857
|
+
},
|
|
2858
|
+
amountNet: -amount,
|
|
2859
|
+
bookedAt: planCharge.bookedAt
|
|
2860
|
+
}));
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
return charges;
|
|
2864
|
+
}
|
|
2865
|
+
__name(deriveDiscountCharges, "deriveDiscountCharges");
|
|
2866
|
+
function discountInPeriod(line, first, periodStart, cycle, anchorDay) {
|
|
2867
|
+
const index = cyclesBetween(first, periodStart, cycle, anchorDay);
|
|
2868
|
+
return index === null ? 0 : discountFor(line, index, first, periodStart, anchorDay);
|
|
2869
|
+
}
|
|
2870
|
+
__name(discountInPeriod, "discountInPeriod");
|
|
2871
|
+
function discountLeftAt(line, first, at, cycle, anchorDay) {
|
|
2872
|
+
let cents = 0;
|
|
2873
|
+
let periodStart = first;
|
|
2874
|
+
for (let index = 0; index < MAX_CYCLES; index++) {
|
|
2875
|
+
const amount = discountFor(line, index, first, periodStart, anchorDay);
|
|
2876
|
+
if (amount === 0) break;
|
|
2877
|
+
if (periodStart >= at) cents += toCents(amount);
|
|
2878
|
+
periodStart = advanceOneCycle(periodStart, cycle, anchorDay ?? void 0);
|
|
2879
|
+
}
|
|
2880
|
+
return cents / CENTS;
|
|
2881
|
+
}
|
|
2882
|
+
__name(discountLeftAt, "discountLeftAt");
|
|
2883
|
+
function discountsWhereAgreed(contracts) {
|
|
2884
|
+
const agreed = /* @__PURE__ */ new Map();
|
|
2885
|
+
for (const contract of [
|
|
2886
|
+
...contracts
|
|
2887
|
+
].sort(byEffectiveFrom)) {
|
|
2888
|
+
for (const line of contract.lineItems) {
|
|
2889
|
+
if (line.kind !== "discount" || agreed.has(line.sourceKey)) continue;
|
|
2890
|
+
agreed.set(line.sourceKey, {
|
|
2891
|
+
contract,
|
|
2892
|
+
line
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
return [
|
|
2897
|
+
...agreed.values()
|
|
2898
|
+
];
|
|
2899
|
+
}
|
|
2900
|
+
__name(discountsWhereAgreed, "discountsWhereAgreed");
|
|
2901
|
+
function discountFor(line, index, first, periodStart, anchorDay) {
|
|
2902
|
+
const snapshots = discountSnapshotsOf(line);
|
|
2903
|
+
if (!snapshots) {
|
|
2904
|
+
return index === 0 ? -line.priceNet : 0;
|
|
2905
|
+
}
|
|
2906
|
+
const withinMonths = /* @__PURE__ */ __name((months) => periodStart < monthsAfter(first, months, anchorDay), "withinMonths");
|
|
2907
|
+
let cents = 0;
|
|
2908
|
+
for (const promotion of snapshots.promotions) {
|
|
2909
|
+
const months = promotionMonths(promotion.value);
|
|
2910
|
+
const applies = promotion.type === "intro" || promotion.type === "freeMonths" ? months !== null && withinMonths(months) : index === 0;
|
|
2911
|
+
if (applies) cents += toCents(promotion.resolvedAmountNet);
|
|
2912
|
+
}
|
|
2913
|
+
const code = snapshots.promoCode;
|
|
2914
|
+
if (code) {
|
|
2915
|
+
const applies = code.durationType === "MONTHS" ? withinMonths(code.durationValue ?? 0) : code.durationType === "BILLING_CYCLES" ? index < (code.durationValue ?? 0) : index === 0;
|
|
2916
|
+
if (applies) cents += toCents(code.resolvedAmountNet);
|
|
2917
|
+
}
|
|
2918
|
+
return cents / CENTS;
|
|
2919
|
+
}
|
|
2920
|
+
__name(discountFor, "discountFor");
|
|
2921
|
+
function discountSnapshotsOf(line) {
|
|
2922
|
+
const metadata = line.metadata;
|
|
2923
|
+
if (!isRecord(metadata) || metadata.generated !== true) return null;
|
|
2924
|
+
const promotions = Array.isArray(metadata.promotionSnapshots) ? metadata.promotionSnapshots.filter(isRecord).map((snapshot) => ({
|
|
2925
|
+
type: typeof snapshot.type === "string" ? snapshot.type : "",
|
|
2926
|
+
value: snapshot.value,
|
|
2927
|
+
resolvedAmountNet: numberOr0(snapshot.resolvedAmountNet)
|
|
2928
|
+
})) : [];
|
|
2929
|
+
const code = isRecord(metadata.promoCodeSnapshot) ? metadata.promoCodeSnapshot : null;
|
|
2930
|
+
return {
|
|
2931
|
+
promotions,
|
|
2932
|
+
promoCode: code ? {
|
|
2933
|
+
durationType: typeof code.durationType === "string" ? code.durationType : null,
|
|
2934
|
+
durationValue: typeof code.durationValue === "number" ? code.durationValue : null,
|
|
2935
|
+
resolvedAmountNet: numberOr0(code.resolvedAmountNet)
|
|
2936
|
+
} : null
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
__name(discountSnapshotsOf, "discountSnapshotsOf");
|
|
2940
|
+
function promotionMonths(value) {
|
|
2941
|
+
if (typeof value === "number") return value;
|
|
2942
|
+
if (isRecord(value) && typeof value.months === "number") return value.months;
|
|
2943
|
+
return null;
|
|
2944
|
+
}
|
|
2945
|
+
__name(promotionMonths, "promotionMonths");
|
|
2946
|
+
function periodsToCharge(args) {
|
|
2947
|
+
const { window, from, cycle, anchorDay, input, endsAt } = args;
|
|
2948
|
+
const periods = [];
|
|
2949
|
+
let start = from;
|
|
2950
|
+
for (let step = 0; step < MAX_CYCLES && start < window.start; step++) {
|
|
2951
|
+
const end = advanceOneCycle(start, cycle, anchorDay ?? void 0);
|
|
2952
|
+
if (end > window.start) break;
|
|
2953
|
+
periods.push({
|
|
2954
|
+
start,
|
|
2955
|
+
end
|
|
2956
|
+
});
|
|
2957
|
+
start = end;
|
|
2958
|
+
}
|
|
2959
|
+
if (from <= window.start) periods.push(window);
|
|
2960
|
+
return periods.filter((period) => isChargeable(period, input, endsAt));
|
|
2961
|
+
}
|
|
2962
|
+
__name(periodsToCharge, "periodsToCharge");
|
|
2963
|
+
function isChargeable(period, input, endsAt) {
|
|
2964
|
+
return period.start < period.end && period.start <= input.now && (!endsAt || period.start < endsAt);
|
|
2965
|
+
}
|
|
2966
|
+
__name(isChargeable, "isChargeable");
|
|
2967
|
+
function firstBoundaryAfter(window, start, cycle, anchorDay) {
|
|
2968
|
+
if (window.start <= start) return window.end;
|
|
2969
|
+
let boundary = window.start;
|
|
2970
|
+
for (let step = 0; step < MAX_CYCLES; step++) {
|
|
2971
|
+
const before = retreatOneCycle(boundary, cycle, anchorDay ?? void 0);
|
|
2972
|
+
if (before <= start) break;
|
|
2973
|
+
boundary = before;
|
|
2974
|
+
}
|
|
2975
|
+
return boundary;
|
|
2976
|
+
}
|
|
2977
|
+
__name(firstBoundaryAfter, "firstBoundaryAfter");
|
|
2978
|
+
function firstBundlePeriod(window, start, cycle, anchorDay) {
|
|
2979
|
+
return [
|
|
2980
|
+
{
|
|
2981
|
+
start,
|
|
2982
|
+
end: firstBoundaryAfter(window, start, cycle, anchorDay)
|
|
2983
|
+
}
|
|
2984
|
+
];
|
|
2985
|
+
}
|
|
2986
|
+
__name(firstBundlePeriod, "firstBundlePeriod");
|
|
2987
|
+
function lastPeriodEnd(written, source, sourceRef) {
|
|
2988
|
+
let last = null;
|
|
2989
|
+
for (const charge of written) {
|
|
2990
|
+
if (charge.source !== source || charge.sourceRef !== sourceRef) continue;
|
|
2991
|
+
if (!PERIOD_ORIGINS.includes(charge.origin)) continue;
|
|
2992
|
+
if (!last || charge.periodEnd > last) last = charge.periodEnd;
|
|
2993
|
+
}
|
|
2994
|
+
return last;
|
|
2995
|
+
}
|
|
2996
|
+
__name(lastPeriodEnd, "lastPeriodEnd");
|
|
2997
|
+
function firstPlanPeriodStart(written, subscriptionId) {
|
|
2998
|
+
let first = null;
|
|
2999
|
+
for (const charge of written) {
|
|
3000
|
+
if (charge.source !== "plan" || charge.sourceRef !== subscriptionId) continue;
|
|
3001
|
+
if (!PERIOD_ORIGINS.includes(charge.origin)) continue;
|
|
3002
|
+
if (!first || charge.periodStart < first) first = charge.periodStart;
|
|
3003
|
+
}
|
|
3004
|
+
return first;
|
|
3005
|
+
}
|
|
3006
|
+
__name(firstPlanPeriodStart, "firstPlanPeriodStart");
|
|
3007
|
+
function firstPeriodConcludedFor(concluded, periods) {
|
|
3008
|
+
const at = concluded.effectiveFrom;
|
|
3009
|
+
let first = null;
|
|
3010
|
+
for (const { charge } of periods) {
|
|
3011
|
+
if (charge.periodEnd <= at) continue;
|
|
3012
|
+
if (charge.periodStart < at && charge.contractId !== concluded.id) continue;
|
|
3013
|
+
if (!first || charge.periodStart < first) first = charge.periodStart;
|
|
3014
|
+
}
|
|
3015
|
+
return first;
|
|
3016
|
+
}
|
|
3017
|
+
__name(firstPeriodConcludedFor, "firstPeriodConcludedFor");
|
|
3018
|
+
function cyclesBetween(from, to, cycle, anchorDay) {
|
|
3019
|
+
let at = from;
|
|
3020
|
+
for (let step = 0; step < MAX_CYCLES; step++) {
|
|
3021
|
+
if (at.getTime() === to.getTime()) return step;
|
|
3022
|
+
if (at > to) return null;
|
|
3023
|
+
at = advanceOneCycle(at, cycle, anchorDay ?? void 0);
|
|
3024
|
+
}
|
|
3025
|
+
return null;
|
|
3026
|
+
}
|
|
3027
|
+
__name(cyclesBetween, "cyclesBetween");
|
|
3028
|
+
function monthsAfter(from, months, anchorDay) {
|
|
3029
|
+
let at = from;
|
|
3030
|
+
for (let step = 0; step < months; step++) {
|
|
3031
|
+
at = advanceOneCycle(at, "MONTHLY", anchorDay ?? from.getUTCDate());
|
|
3032
|
+
}
|
|
3033
|
+
return at;
|
|
3034
|
+
}
|
|
3035
|
+
__name(monthsAfter, "monthsAfter");
|
|
3036
|
+
function lineFor(contracts, period, matches) {
|
|
3037
|
+
const inForce = contractInForce(contracts, period.start);
|
|
3038
|
+
const inForceLine = inForce?.lineItems.find(matches);
|
|
3039
|
+
if (inForce && inForceLine) return {
|
|
3040
|
+
contract: inForce,
|
|
3041
|
+
line: inForceLine
|
|
3042
|
+
};
|
|
3043
|
+
for (const contract of [
|
|
3044
|
+
...contracts
|
|
3045
|
+
].sort(byEffectiveFrom)) {
|
|
3046
|
+
if (contract.status === "scheduled") continue;
|
|
3047
|
+
if (contract.effectiveFrom < period.start || contract.effectiveFrom >= period.end) continue;
|
|
3048
|
+
const line = contract.lineItems.find(matches);
|
|
3049
|
+
if (line) return {
|
|
3050
|
+
contract,
|
|
3051
|
+
line
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
__name(lineFor, "lineFor");
|
|
3057
|
+
function contractInForce(contracts, at) {
|
|
3058
|
+
return [
|
|
3059
|
+
...contracts
|
|
3060
|
+
].filter((contract) => contract.status !== "scheduled" && contract.effectiveFrom <= at && (contract.effectiveUntil === null || at < contract.effectiveUntil)).sort(byEffectiveFrom).at(-1) ?? null;
|
|
3061
|
+
}
|
|
3062
|
+
__name(contractInForce, "contractInForce");
|
|
3063
|
+
function earliest2(contracts) {
|
|
3064
|
+
return [
|
|
3065
|
+
...contracts
|
|
3066
|
+
].sort(byEffectiveFrom)[0] ?? null;
|
|
3067
|
+
}
|
|
3068
|
+
__name(earliest2, "earliest");
|
|
3069
|
+
function byEffectiveFrom(a, b) {
|
|
3070
|
+
return a.effectiveFrom.getTime() - b.effectiveFrom.getTime();
|
|
3071
|
+
}
|
|
3072
|
+
__name(byEffectiveFrom, "byEffectiveFrom");
|
|
3073
|
+
function chargeOf(input, contract, line, charge) {
|
|
3074
|
+
return {
|
|
3075
|
+
subscriberId: input.subscriberId,
|
|
3076
|
+
tenantId: input.subscription.tenantId,
|
|
3077
|
+
subscriptionId: input.subscription.id,
|
|
3078
|
+
contractId: contract.id,
|
|
3079
|
+
contractLineItemId: line.id,
|
|
3080
|
+
origin: charge.origin,
|
|
3081
|
+
source: charge.source,
|
|
3082
|
+
sourceRef: charge.sourceRef,
|
|
3083
|
+
periodStart: charge.period.start,
|
|
3084
|
+
periodEnd: charge.period.end,
|
|
3085
|
+
currency: line.currency,
|
|
3086
|
+
// Rounded once, here, where it is derived (`SC-PRIC-018`).
|
|
3087
|
+
amountNet: toCents(charge.amountNet) / CENTS,
|
|
3088
|
+
bookedAt: charge.bookedAt
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
__name(chargeOf, "chargeOf");
|
|
3092
|
+
function windowOf(start, end) {
|
|
3093
|
+
return start && end && start < end ? {
|
|
3094
|
+
start,
|
|
3095
|
+
end
|
|
3096
|
+
} : null;
|
|
3097
|
+
}
|
|
3098
|
+
__name(windowOf, "windowOf");
|
|
3099
|
+
function laterOf(a, b) {
|
|
3100
|
+
return b > a ? b : a;
|
|
3101
|
+
}
|
|
3102
|
+
__name(laterOf, "laterOf");
|
|
3103
|
+
function sameInstant(a, b) {
|
|
3104
|
+
return b !== null && a.getTime() === b.getTime();
|
|
3105
|
+
}
|
|
3106
|
+
__name(sameInstant, "sameInstant");
|
|
3107
|
+
function firstPeriodStart(periods) {
|
|
3108
|
+
return periods.reduce((first, { charge }) => !first || charge.periodStart < first ? charge.periodStart : first, null);
|
|
3109
|
+
}
|
|
3110
|
+
__name(firstPeriodStart, "firstPeriodStart");
|
|
3111
|
+
function holds(period, at) {
|
|
3112
|
+
return at !== null && period.start <= at && at < period.end;
|
|
3113
|
+
}
|
|
3114
|
+
__name(holds, "holds");
|
|
3115
|
+
function earlierOf(a, b) {
|
|
3116
|
+
if (!a) return b;
|
|
3117
|
+
if (!b) return a;
|
|
3118
|
+
return a < b ? a : b;
|
|
3119
|
+
}
|
|
3120
|
+
__name(earlierOf, "earlierOf");
|
|
3121
|
+
function rhythmOf(cycle) {
|
|
3122
|
+
return cycle === "YEARLY" ? "yearly" : "monthly";
|
|
3123
|
+
}
|
|
3124
|
+
__name(rhythmOf, "rhythmOf");
|
|
3125
|
+
function lineById(contracts, id) {
|
|
3126
|
+
for (const contract of contracts) {
|
|
3127
|
+
const line = contract.lineItems.find((item) => item.id === id);
|
|
3128
|
+
if (line) return line;
|
|
3129
|
+
}
|
|
3130
|
+
return null;
|
|
3131
|
+
}
|
|
3132
|
+
__name(lineById, "lineById");
|
|
3133
|
+
function toCents(amount) {
|
|
3134
|
+
return Math.round(amount * CENTS);
|
|
3135
|
+
}
|
|
3136
|
+
__name(toCents, "toCents");
|
|
3137
|
+
function numberOr0(value) {
|
|
3138
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3139
|
+
}
|
|
3140
|
+
__name(numberOr0, "numberOr0");
|
|
3141
|
+
function isRecord(value) {
|
|
3142
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3143
|
+
}
|
|
3144
|
+
__name(isRecord, "isRecord");
|
|
3145
|
+
|
|
3146
|
+
// src/billing/charges/subscriber-charge.service.ts
|
|
3147
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
1515
3148
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1516
3149
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1517
3150
|
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;
|
|
1518
3151
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1519
3152
|
}
|
|
1520
|
-
__name(
|
|
1521
|
-
function
|
|
3153
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
3154
|
+
function _ts_metadata8(k, v) {
|
|
1522
3155
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1523
3156
|
}
|
|
1524
|
-
__name(
|
|
1525
|
-
function
|
|
3157
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
3158
|
+
function _ts_param8(paramIndex, decorator) {
|
|
1526
3159
|
return function(target, key) {
|
|
1527
3160
|
decorator(target, key, paramIndex);
|
|
1528
3161
|
};
|
|
1529
3162
|
}
|
|
1530
|
-
__name(
|
|
1531
|
-
|
|
3163
|
+
__name(_ts_param8, "_ts_param");
|
|
3164
|
+
function planCycleOf(subscription) {
|
|
3165
|
+
return subscription.billingCycle;
|
|
3166
|
+
}
|
|
3167
|
+
__name(planCycleOf, "planCycleOf");
|
|
3168
|
+
function endOf(subscription) {
|
|
3169
|
+
return subscription.canceledEffectiveAt ?? subscription.canceledAt ?? null;
|
|
3170
|
+
}
|
|
3171
|
+
__name(endOf, "endOf");
|
|
3172
|
+
var SubscriberChargeService = class _SubscriberChargeService {
|
|
1532
3173
|
static {
|
|
1533
|
-
__name(this, "
|
|
3174
|
+
__name(this, "SubscriberChargeService");
|
|
1534
3175
|
}
|
|
1535
|
-
|
|
1536
|
-
entitlements;
|
|
3176
|
+
ledger;
|
|
1537
3177
|
contracts;
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
3178
|
+
subscribers;
|
|
3179
|
+
subscriptions;
|
|
3180
|
+
bookings;
|
|
3181
|
+
freeze;
|
|
3182
|
+
logger = new Logger3(_SubscriberChargeService.name);
|
|
3183
|
+
constructor(ledger, contracts, subscribers, subscriptions, bookings = null, freeze = null) {
|
|
3184
|
+
this.ledger = ledger;
|
|
1542
3185
|
this.contracts = contracts;
|
|
1543
|
-
this.
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
}
|
|
1548
|
-
assertPartyFor(tenantId) {
|
|
1549
|
-
return this.contracts.assertPartyFor(tenantId);
|
|
3186
|
+
this.subscribers = subscribers;
|
|
3187
|
+
this.subscriptions = subscriptions;
|
|
3188
|
+
this.bookings = bookings;
|
|
3189
|
+
this.freeze = freeze;
|
|
1550
3190
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
3191
|
+
/**
|
|
3192
|
+
* Writes every charge the tenant's records give rise to by `now` that is
|
|
3193
|
+
* not written yet, and returns those.
|
|
3194
|
+
*
|
|
3195
|
+
* Nothing is charged without a contract (a charge points at a contract
|
|
3196
|
+
* line), in a trial, before the plan's current window when the account is
|
|
3197
|
+
* still empty, for a period that starts after `now`, or for one that
|
|
3198
|
+
* starts on or after the date a cancellation takes effect.
|
|
3199
|
+
*
|
|
3200
|
+
* A window that moved on before anything charged it is not charged
|
|
3201
|
+
* afterwards, so a job that renews periods calls this before it moves a
|
|
3202
|
+
* window, and moves it only when the call succeeded.
|
|
3203
|
+
*/
|
|
3204
|
+
async recordDueCharges(tenantId, now = /* @__PURE__ */ new Date()) {
|
|
3205
|
+
const subscription = await this.subscriptions.findForTenant(tenantId);
|
|
3206
|
+
if (!subscription?.id) return [];
|
|
3207
|
+
const subscriber = await this.subscribers.findByTenantId(tenantId);
|
|
3208
|
+
if (!subscriber) return [];
|
|
3209
|
+
const [bookings, written] = await Promise.all([
|
|
3210
|
+
this.bookings?.listBySubscription(subscription.id) ?? [],
|
|
3211
|
+
this.ledger.listBySubscription(subscription.id)
|
|
3212
|
+
]);
|
|
3213
|
+
const contracts = await this.contractsNamingEveryBooking(tenantId, subscription, bookings, now);
|
|
3214
|
+
const due = deriveDueCharges({
|
|
3215
|
+
now,
|
|
3216
|
+
subscriberId: subscriber.id,
|
|
3217
|
+
subscription: {
|
|
3218
|
+
id: subscription.id,
|
|
3219
|
+
tenantId,
|
|
3220
|
+
status: subscription.status,
|
|
3221
|
+
billingCycle: planCycleOf(subscription),
|
|
3222
|
+
currentPeriodStart: subscription.currentPeriodStart,
|
|
3223
|
+
currentPeriodEnd: subscription.currentPeriodEnd,
|
|
3224
|
+
anchorDay: resolvePlanAnchorDay(subscription),
|
|
3225
|
+
startedAt: subscription.startedAt,
|
|
3226
|
+
endsAt: endOf(subscription)
|
|
3227
|
+
},
|
|
3228
|
+
contracts,
|
|
3229
|
+
bookings,
|
|
3230
|
+
written
|
|
1561
3231
|
});
|
|
1562
|
-
this.
|
|
3232
|
+
return this.ledger.recordCharges(due);
|
|
1563
3233
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
3234
|
+
/**
|
|
3235
|
+
* The tenant's contracts, written once more where the one in force misses
|
|
3236
|
+
* a running booking.
|
|
3237
|
+
*
|
|
3238
|
+
* A booking writes its contract after it is saved, and a failure there does
|
|
3239
|
+
* not undo the booking. The booking would then stay uncharged for good,
|
|
3240
|
+
* since a charge points at a contract line — so the journal writes the
|
|
3241
|
+
* contract the way the booking would have. A failure here is logged and the
|
|
3242
|
+
* rest is charged: the next call tries again.
|
|
3243
|
+
*/
|
|
3244
|
+
async contractsNamingEveryBooking(tenantId, subscription, bookings, now) {
|
|
3245
|
+
const contracts = await this.contracts.list({
|
|
3246
|
+
tenantId
|
|
3247
|
+
});
|
|
3248
|
+
const missed = bookingsTheContractMisses(contracts, bookings, now);
|
|
3249
|
+
if (missed.length === 0 || !this.freeze || subscription.status === "TRIAL" || cancellationHasLanded(subscription, now)) {
|
|
3250
|
+
return contracts;
|
|
1573
3251
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}, bound);
|
|
1580
|
-
if (isPlanNotSoldInCycle(planDef, billingCycle)) {
|
|
1581
|
-
throw new UnprocessableEntityException(planNotSoldInCycle(planDef, billingCycle));
|
|
3252
|
+
try {
|
|
3253
|
+
await this.freeze.freezeOnPlanChange(tenantId, subscription.planVersion.planId, planCycleOf(subscription), now, endOf(subscription));
|
|
3254
|
+
} catch (err) {
|
|
3255
|
+
this.logger.error(`Writing the contract for ${missed.length} booking(s) it misses failed (tenant ${tenantId}): ${String(err)}`);
|
|
3256
|
+
return contracts;
|
|
1582
3257
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const planPriceNet = listPriceNet(planDef, billingCycle) ?? 0;
|
|
1586
|
-
const planLineItem = {
|
|
1587
|
-
kind: "plan",
|
|
1588
|
-
sourceKey: newPlan,
|
|
1589
|
-
sourceVersionId: bound.id,
|
|
1590
|
-
titleSnapshot: planDef.name ?? newPlan,
|
|
1591
|
-
descriptionSnapshot: planDef.tagline ?? null,
|
|
1592
|
-
quantity: 1,
|
|
1593
|
-
unit: null,
|
|
1594
|
-
priceNet: planPriceNet,
|
|
1595
|
-
billingCycle: cycle,
|
|
1596
|
-
minimumTermUntil: null,
|
|
1597
|
-
featuresSnapshot: planDef.features,
|
|
1598
|
-
quotaEffectsSnapshot: planDef.quotas,
|
|
1599
|
-
metadata: null
|
|
1600
|
-
};
|
|
1601
|
-
const lineItems = recordContractLinesMoney([
|
|
1602
|
-
planLineItem,
|
|
1603
|
-
...bundles.lineItems
|
|
1604
|
-
], {
|
|
1605
|
-
currency: catalog.currency,
|
|
1606
|
-
taxRate: vatRate
|
|
1607
|
-
});
|
|
1608
|
-
const totals = contractTotalsOf(lineItems, cycle);
|
|
1609
|
-
const priceSnapshot = {
|
|
1610
|
-
currency: catalog.currency,
|
|
1611
|
-
billingCycle: cycle,
|
|
1612
|
-
subtotalNet: totals.subtotalNet,
|
|
1613
|
-
discountNet: totals.discountNet,
|
|
1614
|
-
totalNet: totals.totalNet,
|
|
1615
|
-
vatRate,
|
|
1616
|
-
totalGross: totals.totalGross
|
|
1617
|
-
};
|
|
1618
|
-
assertOnePlanLine(lineItems);
|
|
1619
|
-
assertNoNegativeDiscount({
|
|
1620
|
-
priceSnapshot
|
|
3258
|
+
return this.contracts.list({
|
|
3259
|
+
tenantId
|
|
1621
3260
|
});
|
|
1622
|
-
const previous = await this.contracts.findActiveByTenantId(tenantId, effectiveFrom);
|
|
1623
|
-
if (previous) {
|
|
1624
|
-
await this.contracts.terminate(previous.id, {
|
|
1625
|
-
effectiveUntil: effectiveFrom,
|
|
1626
|
-
status: "superseded"
|
|
1627
|
-
});
|
|
1628
|
-
}
|
|
1629
|
-
this.entitlements.invalidateTenant(tenantId);
|
|
1630
|
-
const limits = await this.entitlements.computeLimits(tenantId, effectiveFrom, catalog);
|
|
1631
|
-
const data = {
|
|
1632
|
-
tenantId,
|
|
1633
|
-
status: "active",
|
|
1634
|
-
effectiveFrom,
|
|
1635
|
-
// The successor inherits the ending. A cancellation capped the
|
|
1636
|
-
// contract that was running when it was declared; every contract
|
|
1637
|
-
// after it ends on the same date, or the repair lasts exactly until
|
|
1638
|
-
// the next plan change.
|
|
1639
|
-
effectiveUntil: endsAt,
|
|
1640
|
-
originalPlanVersionId: bound.id,
|
|
1641
|
-
originalBundleVersionIds: bundles.bundleVersionIds,
|
|
1642
|
-
entitlementSnapshot: {
|
|
1643
|
-
plan: limits.plan,
|
|
1644
|
-
quotas: {
|
|
1645
|
-
...limits.quotas
|
|
1646
|
-
},
|
|
1647
|
-
features: [
|
|
1648
|
-
...limits.features
|
|
1649
|
-
]
|
|
1650
|
-
},
|
|
1651
|
-
priceSnapshot,
|
|
1652
|
-
lineItems
|
|
1653
|
-
};
|
|
1654
|
-
await this.contracts.create(data);
|
|
1655
|
-
this.entitlements.invalidateTenant(tenantId);
|
|
1656
3261
|
}
|
|
1657
3262
|
};
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
typeof
|
|
1671
|
-
typeof
|
|
3263
|
+
SubscriberChargeService = _ts_decorate10([
|
|
3264
|
+
Injectable7(),
|
|
3265
|
+
_ts_param8(0, Inject8(SUBSCRIBER_LEDGER_REPOSITORY_TOKEN)),
|
|
3266
|
+
_ts_param8(1, Inject8(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN)),
|
|
3267
|
+
_ts_param8(2, Inject8(SUBSCRIBER_REPOSITORY_TOKEN)),
|
|
3268
|
+
_ts_param8(3, Inject8(SUBSCRIPTION_USAGE_PORT_TOKEN)),
|
|
3269
|
+
_ts_param8(4, Optional6()),
|
|
3270
|
+
_ts_param8(4, Inject8(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN)),
|
|
3271
|
+
_ts_param8(5, Optional6()),
|
|
3272
|
+
_ts_param8(5, Inject8(CONTRACT_FREEZE_PORT_TOKEN)),
|
|
3273
|
+
_ts_metadata8("design:type", Function),
|
|
3274
|
+
_ts_metadata8("design:paramtypes", [
|
|
3275
|
+
typeof SubscriberLedgerRepository === "undefined" ? Object : SubscriberLedgerRepository,
|
|
3276
|
+
typeof SubscriptionContractRepository === "undefined" ? Object : SubscriptionContractRepository,
|
|
3277
|
+
typeof SubscriberRepository === "undefined" ? Object : SubscriberRepository,
|
|
3278
|
+
typeof SubscriptionUsagePort === "undefined" ? Object : SubscriptionUsagePort,
|
|
3279
|
+
Object,
|
|
1672
3280
|
Object
|
|
1673
3281
|
])
|
|
1674
|
-
],
|
|
3282
|
+
], SubscriberChargeService);
|
|
1675
3283
|
|
|
1676
3284
|
// src/billing/dto/tenant-billing.dto.ts
|
|
1677
3285
|
import { IsISO8601, IsOptional, IsString, Matches } from "class-validator";
|
|
1678
|
-
function
|
|
3286
|
+
function _ts_decorate11(decorators, target, key, desc) {
|
|
1679
3287
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1680
3288
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1681
3289
|
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;
|
|
1682
3290
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1683
3291
|
}
|
|
1684
|
-
__name(
|
|
1685
|
-
function
|
|
3292
|
+
__name(_ts_decorate11, "_ts_decorate");
|
|
3293
|
+
function _ts_metadata9(k, v) {
|
|
1686
3294
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1687
3295
|
}
|
|
1688
|
-
__name(
|
|
3296
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
1689
3297
|
var PLAN_OR_CYCLE_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
1690
3298
|
var PreviewPlanChangeDto = class {
|
|
1691
3299
|
static {
|
|
@@ -1694,19 +3302,19 @@ var PreviewPlanChangeDto = class {
|
|
|
1694
3302
|
plan;
|
|
1695
3303
|
billingCycle;
|
|
1696
3304
|
};
|
|
1697
|
-
|
|
3305
|
+
_ts_decorate11([
|
|
1698
3306
|
IsString(),
|
|
1699
3307
|
Matches(PLAN_OR_CYCLE_PATTERN, {
|
|
1700
3308
|
message: "plan must be SCREAMING_SNAKE_CASE"
|
|
1701
3309
|
}),
|
|
1702
|
-
|
|
3310
|
+
_ts_metadata9("design:type", String)
|
|
1703
3311
|
], PreviewPlanChangeDto.prototype, "plan", void 0);
|
|
1704
|
-
|
|
3312
|
+
_ts_decorate11([
|
|
1705
3313
|
IsString(),
|
|
1706
3314
|
Matches(PLAN_OR_CYCLE_PATTERN, {
|
|
1707
3315
|
message: "billingCycle must be SCREAMING_SNAKE_CASE (e.g. MONTHLY, YEARLY)"
|
|
1708
3316
|
}),
|
|
1709
|
-
|
|
3317
|
+
_ts_metadata9("design:type", String)
|
|
1710
3318
|
], PreviewPlanChangeDto.prototype, "billingCycle", void 0);
|
|
1711
3319
|
var ChangePlanDto = class extends PreviewPlanChangeDto {
|
|
1712
3320
|
static {
|
|
@@ -1731,25 +3339,25 @@ var CancelSubscriptionDto = class {
|
|
|
1731
3339
|
*/
|
|
1732
3340
|
expectedEffectiveAt;
|
|
1733
3341
|
};
|
|
1734
|
-
|
|
3342
|
+
_ts_decorate11([
|
|
1735
3343
|
IsOptional(),
|
|
1736
3344
|
IsISO8601(),
|
|
1737
|
-
|
|
3345
|
+
_ts_metadata9("design:type", String)
|
|
1738
3346
|
], CancelSubscriptionDto.prototype, "expectedEffectiveAt", void 0);
|
|
1739
3347
|
|
|
1740
3348
|
// src/billing/dto/onboarding-subscription.dto.ts
|
|
1741
3349
|
import { ArrayMaxSize, IsArray, IsOptional as IsOptional2, IsString as IsString2, IsUUID, Matches as Matches2 } from "class-validator";
|
|
1742
|
-
function
|
|
3350
|
+
function _ts_decorate12(decorators, target, key, desc) {
|
|
1743
3351
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1744
3352
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1745
3353
|
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;
|
|
1746
3354
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1747
3355
|
}
|
|
1748
|
-
__name(
|
|
1749
|
-
function
|
|
3356
|
+
__name(_ts_decorate12, "_ts_decorate");
|
|
3357
|
+
function _ts_metadata10(k, v) {
|
|
1750
3358
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1751
3359
|
}
|
|
1752
|
-
__name(
|
|
3360
|
+
__name(_ts_metadata10, "_ts_metadata");
|
|
1753
3361
|
var PLAN_OR_CYCLE_PATTERN2 = /^[A-Z][A-Z0-9_]*$/;
|
|
1754
3362
|
var PROMO_CODE_PATTERN = /^[A-Z0-9_-]{4,32}$/i;
|
|
1755
3363
|
var CompleteOnboardingSubscriptionDto = class {
|
|
@@ -1769,52 +3377,52 @@ var CompleteOnboardingSubscriptionDto = class {
|
|
|
1769
3377
|
*/
|
|
1770
3378
|
bundleVersionIds;
|
|
1771
3379
|
};
|
|
1772
|
-
|
|
3380
|
+
_ts_decorate12([
|
|
1773
3381
|
IsString2(),
|
|
1774
3382
|
Matches2(PLAN_OR_CYCLE_PATTERN2, {
|
|
1775
3383
|
message: "plan must be SCREAMING_SNAKE_CASE"
|
|
1776
3384
|
}),
|
|
1777
|
-
|
|
3385
|
+
_ts_metadata10("design:type", String)
|
|
1778
3386
|
], CompleteOnboardingSubscriptionDto.prototype, "plan", void 0);
|
|
1779
|
-
|
|
3387
|
+
_ts_decorate12([
|
|
1780
3388
|
IsString2(),
|
|
1781
3389
|
Matches2(PLAN_OR_CYCLE_PATTERN2, {
|
|
1782
3390
|
message: "billingCycle must be SCREAMING_SNAKE_CASE (e.g. MONTHLY, YEARLY)"
|
|
1783
3391
|
}),
|
|
1784
|
-
|
|
3392
|
+
_ts_metadata10("design:type", String)
|
|
1785
3393
|
], CompleteOnboardingSubscriptionDto.prototype, "billingCycle", void 0);
|
|
1786
|
-
|
|
3394
|
+
_ts_decorate12([
|
|
1787
3395
|
IsOptional2(),
|
|
1788
3396
|
IsString2(),
|
|
1789
3397
|
Matches2(PROMO_CODE_PATTERN, {
|
|
1790
3398
|
message: 'promoCode must consist of A\u2013Z, 0\u20139, "-", "_" (4\u201332 characters)'
|
|
1791
3399
|
}),
|
|
1792
|
-
|
|
3400
|
+
_ts_metadata10("design:type", String)
|
|
1793
3401
|
], CompleteOnboardingSubscriptionDto.prototype, "promoCode", void 0);
|
|
1794
|
-
|
|
3402
|
+
_ts_decorate12([
|
|
1795
3403
|
IsOptional2(),
|
|
1796
3404
|
IsArray(),
|
|
1797
3405
|
ArrayMaxSize(10),
|
|
1798
3406
|
IsUUID("all", {
|
|
1799
3407
|
each: true
|
|
1800
3408
|
}),
|
|
1801
|
-
|
|
3409
|
+
_ts_metadata10("design:type", Array)
|
|
1802
3410
|
], CompleteOnboardingSubscriptionDto.prototype, "bundleVersionIds", void 0);
|
|
1803
3411
|
|
|
1804
3412
|
// src/billing/dto/subscription-bundles.dto.ts
|
|
1805
3413
|
import { ArrayMaxSize as ArrayMaxSize2, IsArray as IsArray2, IsIn, IsInt, IsOptional as IsOptional3, IsUUID as IsUUID2, Matches as Matches3, Max, Min } from "class-validator";
|
|
1806
3414
|
import { BUNDLE_PRICE_LOOKUP_LIMIT } from "@saasicat/core";
|
|
1807
|
-
function
|
|
3415
|
+
function _ts_decorate13(decorators, target, key, desc) {
|
|
1808
3416
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1809
3417
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1810
3418
|
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;
|
|
1811
3419
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1812
3420
|
}
|
|
1813
|
-
__name(
|
|
1814
|
-
function
|
|
3421
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
3422
|
+
function _ts_metadata11(k, v) {
|
|
1815
3423
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1816
3424
|
}
|
|
1817
|
-
__name(
|
|
3425
|
+
__name(_ts_metadata11, "_ts_metadata");
|
|
1818
3426
|
var ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}(T.*)?$/;
|
|
1819
3427
|
var AddSubscriptionBundleDto = class {
|
|
1820
3428
|
static {
|
|
@@ -1839,24 +3447,24 @@ var AddSubscriptionBundleDto = class {
|
|
|
1839
3447
|
*/
|
|
1840
3448
|
billingCycle;
|
|
1841
3449
|
};
|
|
1842
|
-
|
|
3450
|
+
_ts_decorate13([
|
|
1843
3451
|
IsUUID2(),
|
|
1844
|
-
|
|
3452
|
+
_ts_metadata11("design:type", String)
|
|
1845
3453
|
], AddSubscriptionBundleDto.prototype, "bundleVersionId", void 0);
|
|
1846
|
-
|
|
3454
|
+
_ts_decorate13([
|
|
1847
3455
|
IsOptional3(),
|
|
1848
3456
|
IsInt(),
|
|
1849
3457
|
Min(0),
|
|
1850
3458
|
Max(120),
|
|
1851
|
-
|
|
3459
|
+
_ts_metadata11("design:type", Number)
|
|
1852
3460
|
], AddSubscriptionBundleDto.prototype, "minimumTermMonths", void 0);
|
|
1853
|
-
|
|
3461
|
+
_ts_decorate13([
|
|
1854
3462
|
IsOptional3(),
|
|
1855
3463
|
IsIn([
|
|
1856
3464
|
"MONTHLY",
|
|
1857
3465
|
"YEARLY"
|
|
1858
3466
|
]),
|
|
1859
|
-
|
|
3467
|
+
_ts_metadata11("design:type", String)
|
|
1860
3468
|
], AddSubscriptionBundleDto.prototype, "billingCycle", void 0);
|
|
1861
3469
|
var PreviewSubscriptionBundleDto = class {
|
|
1862
3470
|
static {
|
|
@@ -1880,30 +3488,30 @@ var PreviewSubscriptionBundleDto = class {
|
|
|
1880
3488
|
*/
|
|
1881
3489
|
billingCycle;
|
|
1882
3490
|
};
|
|
1883
|
-
|
|
3491
|
+
_ts_decorate13([
|
|
1884
3492
|
IsOptional3(),
|
|
1885
3493
|
IsUUID2(),
|
|
1886
|
-
|
|
3494
|
+
_ts_metadata11("design:type", String)
|
|
1887
3495
|
], PreviewSubscriptionBundleDto.prototype, "bundleVersionId", void 0);
|
|
1888
|
-
|
|
3496
|
+
_ts_decorate13([
|
|
1889
3497
|
IsOptional3(),
|
|
1890
3498
|
IsUUID2(),
|
|
1891
|
-
|
|
3499
|
+
_ts_metadata11("design:type", String)
|
|
1892
3500
|
], PreviewSubscriptionBundleDto.prototype, "subscriptionBundleId", void 0);
|
|
1893
|
-
|
|
3501
|
+
_ts_decorate13([
|
|
1894
3502
|
IsOptional3(),
|
|
1895
3503
|
IsInt(),
|
|
1896
3504
|
Min(0),
|
|
1897
3505
|
Max(120),
|
|
1898
|
-
|
|
3506
|
+
_ts_metadata11("design:type", Number)
|
|
1899
3507
|
], PreviewSubscriptionBundleDto.prototype, "minimumTermMonths", void 0);
|
|
1900
|
-
|
|
3508
|
+
_ts_decorate13([
|
|
1901
3509
|
IsOptional3(),
|
|
1902
3510
|
IsIn([
|
|
1903
3511
|
"MONTHLY",
|
|
1904
3512
|
"YEARLY"
|
|
1905
3513
|
]),
|
|
1906
|
-
|
|
3514
|
+
_ts_metadata11("design:type", String)
|
|
1907
3515
|
], PreviewSubscriptionBundleDto.prototype, "billingCycle", void 0);
|
|
1908
3516
|
var CancelSubscriptionBundleDto = class {
|
|
1909
3517
|
static {
|
|
@@ -1916,12 +3524,12 @@ var CancelSubscriptionBundleDto = class {
|
|
|
1916
3524
|
*/
|
|
1917
3525
|
canceledAt;
|
|
1918
3526
|
};
|
|
1919
|
-
|
|
3527
|
+
_ts_decorate13([
|
|
1920
3528
|
IsOptional3(),
|
|
1921
3529
|
Matches3(ISO_DATE_PATTERN, {
|
|
1922
3530
|
message: "canceledAt must be an ISO date (YYYY-MM-DD)"
|
|
1923
3531
|
}),
|
|
1924
|
-
|
|
3532
|
+
_ts_metadata11("design:type", String)
|
|
1925
3533
|
], CancelSubscriptionBundleDto.prototype, "canceledAt", void 0);
|
|
1926
3534
|
var BundlePriceLookupDto = class {
|
|
1927
3535
|
static {
|
|
@@ -1933,35 +3541,35 @@ var BundlePriceLookupDto = class {
|
|
|
1933
3541
|
*/
|
|
1934
3542
|
bundleVersionIds;
|
|
1935
3543
|
};
|
|
1936
|
-
|
|
3544
|
+
_ts_decorate13([
|
|
1937
3545
|
IsArray2(),
|
|
1938
3546
|
ArrayMaxSize2(BUNDLE_PRICE_LOOKUP_LIMIT),
|
|
1939
3547
|
IsUUID2("4", {
|
|
1940
3548
|
each: true
|
|
1941
3549
|
}),
|
|
1942
|
-
|
|
3550
|
+
_ts_metadata11("design:type", Array)
|
|
1943
3551
|
], BundlePriceLookupDto.prototype, "bundleVersionIds", void 0);
|
|
1944
3552
|
|
|
1945
3553
|
// src/billing/subscription-bundles.service.ts
|
|
1946
|
-
import { Inject as
|
|
3554
|
+
import { Inject as Inject9, Injectable as Injectable8, NotFoundException as NotFoundException2, Optional as Optional7, UnprocessableEntityException as UnprocessableEntityException2 } from "@nestjs/common";
|
|
1947
3555
|
import { BILLING_ERROR_CODES as BILLING_ERROR_CODES2, CATALOG_ERROR_CODES } from "@saasicat/core";
|
|
1948
|
-
function
|
|
3556
|
+
function _ts_decorate14(decorators, target, key, desc) {
|
|
1949
3557
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1950
3558
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1951
3559
|
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;
|
|
1952
3560
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1953
3561
|
}
|
|
1954
|
-
__name(
|
|
1955
|
-
function
|
|
3562
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
3563
|
+
function _ts_metadata12(k, v) {
|
|
1956
3564
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1957
3565
|
}
|
|
1958
|
-
__name(
|
|
1959
|
-
function
|
|
3566
|
+
__name(_ts_metadata12, "_ts_metadata");
|
|
3567
|
+
function _ts_param9(paramIndex, decorator) {
|
|
1960
3568
|
return function(target, key) {
|
|
1961
3569
|
decorator(target, key, paramIndex);
|
|
1962
3570
|
};
|
|
1963
3571
|
}
|
|
1964
|
-
__name(
|
|
3572
|
+
__name(_ts_param9, "_ts_param");
|
|
1965
3573
|
var SubscriptionBundlesService = class {
|
|
1966
3574
|
static {
|
|
1967
3575
|
__name(this, "SubscriptionBundlesService");
|
|
@@ -2202,16 +3810,16 @@ var SubscriptionBundlesService = class {
|
|
|
2202
3810
|
return this.repo.reactivate(subscriptionBundleId);
|
|
2203
3811
|
}
|
|
2204
3812
|
};
|
|
2205
|
-
SubscriptionBundlesService =
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
3813
|
+
SubscriptionBundlesService = _ts_decorate14([
|
|
3814
|
+
Injectable8(),
|
|
3815
|
+
_ts_param9(0, Inject9(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN)),
|
|
3816
|
+
_ts_param9(1, Inject9(BUNDLE_REPOSITORY_TOKEN)),
|
|
3817
|
+
_ts_param9(2, Optional7()),
|
|
3818
|
+
_ts_param9(2, Inject9(SUBSCRIPTION_BUNDLE_CONFIG_TOKEN)),
|
|
3819
|
+
_ts_param9(3, Optional7()),
|
|
3820
|
+
_ts_param9(3, Inject9(SELF_SERVICE_BLOCKED_BUNDLES_TOKEN)),
|
|
3821
|
+
_ts_metadata12("design:type", Function),
|
|
3822
|
+
_ts_metadata12("design:paramtypes", [
|
|
2215
3823
|
typeof SubscriptionBundleRepository === "undefined" ? Object : SubscriptionBundleRepository,
|
|
2216
3824
|
typeof BundleRepository === "undefined" ? Object : BundleRepository,
|
|
2217
3825
|
typeof SubscriptionBundleConfig === "undefined" ? Object : SubscriptionBundleConfig,
|
|
@@ -2243,25 +3851,38 @@ function clampToParent(ownTermEndsAt, parentEndsAt) {
|
|
|
2243
3851
|
__name(clampToParent, "clampToParent");
|
|
2244
3852
|
|
|
2245
3853
|
// src/billing/tenant-billing.controller.ts
|
|
2246
|
-
import { BadRequestException, Body, Controller as
|
|
3854
|
+
import { BadRequestException, Body, Controller as Controller3, ForbiddenException as ForbiddenException2, Get as Get3, HttpCode, HttpStatus, Inject as Inject10, Logger as Logger4, ConflictException, NotFoundException as NotFoundException3, Optional as Optional8, Post, Req, UseGuards as UseGuards2 } from "@nestjs/common";
|
|
2247
3855
|
import { AUTH_ERROR_CODES as AUTH_ERROR_CODES2, BILLING_ERROR_CODES as BILLING_ERROR_CODES3 } from "@saasicat/core";
|
|
2248
|
-
|
|
3856
|
+
|
|
3857
|
+
// src/billing/charges/record-charges-after.ts
|
|
3858
|
+
async function recordChargesAfter(charges, tenantId, change, logger) {
|
|
3859
|
+
if (!charges) return;
|
|
3860
|
+
try {
|
|
3861
|
+
await charges.recordDueCharges(tenantId);
|
|
3862
|
+
} catch (err) {
|
|
3863
|
+
logger.error(`Recording charges after ${change} failed (tenant ${tenantId}): ${String(err)}`);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
__name(recordChargesAfter, "recordChargesAfter");
|
|
3867
|
+
|
|
3868
|
+
// src/billing/tenant-billing.controller.ts
|
|
3869
|
+
function _ts_decorate15(decorators, target, key, desc) {
|
|
2249
3870
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2250
3871
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2251
3872
|
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;
|
|
2252
3873
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2253
3874
|
}
|
|
2254
|
-
__name(
|
|
2255
|
-
function
|
|
3875
|
+
__name(_ts_decorate15, "_ts_decorate");
|
|
3876
|
+
function _ts_metadata13(k, v) {
|
|
2256
3877
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2257
3878
|
}
|
|
2258
|
-
__name(
|
|
2259
|
-
function
|
|
3879
|
+
__name(_ts_metadata13, "_ts_metadata");
|
|
3880
|
+
function _ts_param10(paramIndex, decorator) {
|
|
2260
3881
|
return function(target, key) {
|
|
2261
3882
|
decorator(target, key, paramIndex);
|
|
2262
3883
|
};
|
|
2263
3884
|
}
|
|
2264
|
-
__name(
|
|
3885
|
+
__name(_ts_param10, "_ts_param");
|
|
2265
3886
|
var TenantBillingController = class _TenantBillingController {
|
|
2266
3887
|
static {
|
|
2267
3888
|
__name(this, "TenantBillingController");
|
|
@@ -2282,7 +3903,8 @@ var TenantBillingController = class _TenantBillingController {
|
|
|
2282
3903
|
contractFreeze;
|
|
2283
3904
|
trialProjection;
|
|
2284
3905
|
cancellationNoticeDays;
|
|
2285
|
-
|
|
3906
|
+
charges;
|
|
3907
|
+
constructor(entitlements, planPreview, subscriptionUsage, usageSnapshot, subscriptionWrite, tenantIdResolver = null, userIdResolver = null, blockedPlans = null, promoCodes = null, auditService = null, userEmailResolver = null, auditContextResolver = null, subscriptionBundles = null, contractFreeze = null, trialProjection = null, cancellationNoticeDays = NO_NOTICE_PERIOD, charges = null) {
|
|
2286
3908
|
this.entitlements = entitlements;
|
|
2287
3909
|
this.planPreview = planPreview;
|
|
2288
3910
|
this.subscriptionUsage = subscriptionUsage;
|
|
@@ -2299,8 +3921,9 @@ var TenantBillingController = class _TenantBillingController {
|
|
|
2299
3921
|
this.contractFreeze = contractFreeze;
|
|
2300
3922
|
this.trialProjection = trialProjection;
|
|
2301
3923
|
this.cancellationNoticeDays = cancellationNoticeDays;
|
|
3924
|
+
this.charges = charges;
|
|
2302
3925
|
}
|
|
2303
|
-
logger = new
|
|
3926
|
+
logger = new Logger4(_TenantBillingController.name);
|
|
2304
3927
|
// ---------------------------------------------------------------------
|
|
2305
3928
|
// Reads (Phase B) — all authenticated tenant users
|
|
2306
3929
|
// ---------------------------------------------------------------------
|
|
@@ -2456,7 +4079,8 @@ var TenantBillingController = class _TenantBillingController {
|
|
|
2456
4079
|
};
|
|
2457
4080
|
if (decision.isImmediate) {
|
|
2458
4081
|
const wasTrial = sub.status === "TRIAL";
|
|
2459
|
-
const
|
|
4082
|
+
const opensPeriod = decision.proration?.basis === "newPeriod";
|
|
4083
|
+
const period = wasTrial || cancellationOutstanding || !opensPeriod ? null : initialPeriodWindow(/* @__PURE__ */ new Date(), dto.billingCycle);
|
|
2460
4084
|
const trialEndsAt = wasTrial && this.trialProjection ? await this.trialProjection.projectTrialEndsAt({
|
|
2461
4085
|
currentPlan: sub.plan,
|
|
2462
4086
|
targetPlan: dto.plan,
|
|
@@ -2486,6 +4110,7 @@ var TenantBillingController = class _TenantBillingController {
|
|
|
2486
4110
|
// contract it freezes still ends when the subscription does.
|
|
2487
4111
|
sub.canceledEffectiveAt ?? sub.canceledAt ?? null
|
|
2488
4112
|
);
|
|
4113
|
+
await recordChargesAfter(this.charges, tenantId, "a plan change", this.logger);
|
|
2489
4114
|
await this.auditLog(req, userId, "Subscription", tenantId, "CHANGE_PLAN", {
|
|
2490
4115
|
fromPlan: sub.plan,
|
|
2491
4116
|
fromCycle: sub.billingCycle,
|
|
@@ -2658,7 +4283,6 @@ var TenantBillingController = class _TenantBillingController {
|
|
|
2658
4283
|
}
|
|
2659
4284
|
}
|
|
2660
4285
|
}
|
|
2661
|
-
await this.tryFreezeOnPlanChange(tenantId, dto.plan, dto.billingCycle, wasTrial);
|
|
2662
4286
|
this.entitlements.invalidateTenant(tenantId);
|
|
2663
4287
|
await this.auditLog(req, userId, "Subscription", tenantId, "COMPLETE_ONBOARDING_SUBSCRIPTION", {
|
|
2664
4288
|
plan: planResult.plan,
|
|
@@ -2698,6 +4322,8 @@ var TenantBillingController = class _TenantBillingController {
|
|
|
2698
4322
|
} else if (bundleVersionIds.length > 0 && !this.subscriptionBundles) {
|
|
2699
4323
|
warnings.push("Bundle bookings were requested during onboarding, but SubscriptionBundleModule is not registered in the consumer. No bundles were created.");
|
|
2700
4324
|
}
|
|
4325
|
+
await this.tryFreezeOnPlanChange(tenantId, dto.plan, dto.billingCycle, wasTrial);
|
|
4326
|
+
await recordChargesAfter(this.charges, tenantId, "onboarding", this.logger);
|
|
2701
4327
|
return {
|
|
2702
4328
|
plan: planResult.plan,
|
|
2703
4329
|
// The adapter returns `string`; the UI expects `'MONTHLY' | 'YEARLY'`.
|
|
@@ -2976,115 +4602,117 @@ var TenantBillingController = class _TenantBillingController {
|
|
|
2976
4602
|
}
|
|
2977
4603
|
}
|
|
2978
4604
|
};
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
4605
|
+
_ts_decorate15([
|
|
4606
|
+
Get3("entitlement"),
|
|
4607
|
+
_ts_param10(0, Req()),
|
|
4608
|
+
_ts_metadata13("design:type", Function),
|
|
4609
|
+
_ts_metadata13("design:paramtypes", [
|
|
2984
4610
|
typeof RequestLike === "undefined" ? Object : RequestLike
|
|
2985
4611
|
]),
|
|
2986
|
-
|
|
4612
|
+
_ts_metadata13("design:returntype", Promise)
|
|
2987
4613
|
], TenantBillingController.prototype, "getEntitlement", null);
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
4614
|
+
_ts_decorate15([
|
|
4615
|
+
Get3("usage"),
|
|
4616
|
+
_ts_param10(0, Req()),
|
|
4617
|
+
_ts_metadata13("design:type", Function),
|
|
4618
|
+
_ts_metadata13("design:paramtypes", [
|
|
2993
4619
|
typeof RequestLike === "undefined" ? Object : RequestLike
|
|
2994
4620
|
]),
|
|
2995
|
-
|
|
4621
|
+
_ts_metadata13("design:returntype", Promise)
|
|
2996
4622
|
], TenantBillingController.prototype, "getUsage", null);
|
|
2997
|
-
|
|
4623
|
+
_ts_decorate15([
|
|
2998
4624
|
Post("plan/preview"),
|
|
2999
4625
|
HttpCode(HttpStatus.OK),
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
4626
|
+
UseGuards2(TenantAdminGuard),
|
|
4627
|
+
_ts_param10(0, Req()),
|
|
4628
|
+
_ts_param10(1, Body()),
|
|
4629
|
+
_ts_metadata13("design:type", Function),
|
|
4630
|
+
_ts_metadata13("design:paramtypes", [
|
|
3005
4631
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3006
4632
|
typeof PreviewPlanChangeDto === "undefined" ? Object : PreviewPlanChangeDto
|
|
3007
4633
|
]),
|
|
3008
|
-
|
|
4634
|
+
_ts_metadata13("design:returntype", Promise)
|
|
3009
4635
|
], TenantBillingController.prototype, "previewPlanChange", null);
|
|
3010
|
-
|
|
4636
|
+
_ts_decorate15([
|
|
3011
4637
|
Post("plan"),
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
4638
|
+
UseGuards2(TenantAdminGuard),
|
|
4639
|
+
_ts_param10(0, Req()),
|
|
4640
|
+
_ts_param10(1, Body()),
|
|
4641
|
+
_ts_metadata13("design:type", Function),
|
|
4642
|
+
_ts_metadata13("design:paramtypes", [
|
|
3017
4643
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3018
4644
|
typeof ChangePlanDto === "undefined" ? Object : ChangePlanDto
|
|
3019
4645
|
]),
|
|
3020
|
-
|
|
4646
|
+
_ts_metadata13("design:returntype", Promise)
|
|
3021
4647
|
], TenantBillingController.prototype, "changePlan", null);
|
|
3022
|
-
|
|
4648
|
+
_ts_decorate15([
|
|
3023
4649
|
Post("onboarding/initial-subscription"),
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
4650
|
+
UseGuards2(TenantAdminGuard),
|
|
4651
|
+
_ts_param10(0, Req()),
|
|
4652
|
+
_ts_param10(1, Body()),
|
|
4653
|
+
_ts_metadata13("design:type", Function),
|
|
4654
|
+
_ts_metadata13("design:paramtypes", [
|
|
3029
4655
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3030
4656
|
typeof CompleteOnboardingSubscriptionDto === "undefined" ? Object : CompleteOnboardingSubscriptionDto
|
|
3031
4657
|
]),
|
|
3032
|
-
|
|
4658
|
+
_ts_metadata13("design:returntype", Promise)
|
|
3033
4659
|
], TenantBillingController.prototype, "completeOnboardingSubscription", null);
|
|
3034
|
-
|
|
4660
|
+
_ts_decorate15([
|
|
3035
4661
|
Post("subscription/accept-pending-version"),
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
4662
|
+
UseGuards2(TenantAdminGuard),
|
|
4663
|
+
_ts_param10(0, Req()),
|
|
4664
|
+
_ts_metadata13("design:type", Function),
|
|
4665
|
+
_ts_metadata13("design:paramtypes", [
|
|
3040
4666
|
typeof RequestLike === "undefined" ? Object : RequestLike
|
|
3041
4667
|
]),
|
|
3042
|
-
|
|
4668
|
+
_ts_metadata13("design:returntype", Promise)
|
|
3043
4669
|
], TenantBillingController.prototype, "acceptPendingPlanVersion", null);
|
|
3044
|
-
|
|
4670
|
+
_ts_decorate15([
|
|
3045
4671
|
Post("cancel"),
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
4672
|
+
UseGuards2(TenantAdminGuard),
|
|
4673
|
+
_ts_param10(0, Req()),
|
|
4674
|
+
_ts_param10(1, Body()),
|
|
4675
|
+
_ts_metadata13("design:type", Function),
|
|
4676
|
+
_ts_metadata13("design:paramtypes", [
|
|
3051
4677
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3052
4678
|
typeof CancelSubscriptionDto === "undefined" ? Object : CancelSubscriptionDto
|
|
3053
4679
|
]),
|
|
3054
|
-
|
|
4680
|
+
_ts_metadata13("design:returntype", Promise)
|
|
3055
4681
|
], TenantBillingController.prototype, "cancelSubscription", null);
|
|
3056
|
-
TenantBillingController =
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
4682
|
+
TenantBillingController = _ts_decorate15([
|
|
4683
|
+
Controller3("billing"),
|
|
4684
|
+
UseGuards2(ComposedTenantAuthGuard),
|
|
4685
|
+
_ts_param10(0, Inject10(ENTITLEMENT_SERVICE_TOKEN)),
|
|
4686
|
+
_ts_param10(1, Inject10(PlanChangePreviewService)),
|
|
4687
|
+
_ts_param10(2, Inject10(SUBSCRIPTION_USAGE_PORT_TOKEN)),
|
|
4688
|
+
_ts_param10(3, Inject10(USAGE_SNAPSHOT_PORT_TOKEN)),
|
|
4689
|
+
_ts_param10(4, Inject10(SUBSCRIPTION_WRITE_PORT_TOKEN)),
|
|
4690
|
+
_ts_param10(5, Optional8()),
|
|
4691
|
+
_ts_param10(5, Inject10(TENANT_ID_RESOLVER_TOKEN)),
|
|
4692
|
+
_ts_param10(6, Optional8()),
|
|
4693
|
+
_ts_param10(6, Inject10(USER_ID_RESOLVER_TOKEN)),
|
|
4694
|
+
_ts_param10(7, Optional8()),
|
|
4695
|
+
_ts_param10(7, Inject10(SELF_SERVICE_BLOCKED_PLANS_TOKEN)),
|
|
4696
|
+
_ts_param10(8, Optional8()),
|
|
4697
|
+
_ts_param10(8, Inject10(PromoCodesService)),
|
|
4698
|
+
_ts_param10(9, Optional8()),
|
|
4699
|
+
_ts_param10(9, Inject10(AdminAuditService)),
|
|
4700
|
+
_ts_param10(10, Optional8()),
|
|
4701
|
+
_ts_param10(10, Inject10(USER_EMAIL_RESOLVER_TOKEN)),
|
|
4702
|
+
_ts_param10(11, Optional8()),
|
|
4703
|
+
_ts_param10(11, Inject10(AUDIT_CONTEXT_RESOLVER_TOKEN)),
|
|
4704
|
+
_ts_param10(12, Optional8()),
|
|
4705
|
+
_ts_param10(12, Inject10(SubscriptionBundlesService)),
|
|
4706
|
+
_ts_param10(13, Optional8()),
|
|
4707
|
+
_ts_param10(13, Inject10(CONTRACT_FREEZE_PORT_TOKEN)),
|
|
4708
|
+
_ts_param10(14, Optional8()),
|
|
4709
|
+
_ts_param10(14, Inject10(TRIAL_PROJECTION_PORT_TOKEN)),
|
|
4710
|
+
_ts_param10(15, Optional8()),
|
|
4711
|
+
_ts_param10(15, Inject10(CANCELLATION_NOTICE_DAYS_TOKEN)),
|
|
4712
|
+
_ts_param10(16, Optional8()),
|
|
4713
|
+
_ts_param10(16, Inject10(SubscriberChargeService)),
|
|
4714
|
+
_ts_metadata13("design:type", Function),
|
|
4715
|
+
_ts_metadata13("design:paramtypes", [
|
|
3088
4716
|
typeof EntitlementService === "undefined" ? Object : EntitlementService,
|
|
3089
4717
|
typeof PlanChangePreviewService === "undefined" ? Object : PlanChangePreviewService,
|
|
3090
4718
|
typeof SubscriptionUsagePort === "undefined" ? Object : SubscriptionUsagePort,
|
|
@@ -3100,20 +4728,21 @@ TenantBillingController = _ts_decorate11([
|
|
|
3100
4728
|
Object,
|
|
3101
4729
|
Object,
|
|
3102
4730
|
Object,
|
|
3103
|
-
typeof CancellationNoticePeriods === "undefined" ? Object : CancellationNoticePeriods
|
|
4731
|
+
typeof CancellationNoticePeriods === "undefined" ? Object : CancellationNoticePeriods,
|
|
4732
|
+
Object
|
|
3104
4733
|
])
|
|
3105
4734
|
], TenantBillingController);
|
|
3106
4735
|
|
|
3107
4736
|
// src/billing/tenant-billing.module.ts
|
|
3108
|
-
import { Module as
|
|
4737
|
+
import { Module as Module3 } from "@nestjs/common";
|
|
3109
4738
|
import { planCatalogSchema as planCatalogSchema2 } from "@saasicat/spec";
|
|
3110
|
-
function
|
|
4739
|
+
function _ts_decorate16(decorators, target, key, desc) {
|
|
3111
4740
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3112
4741
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3113
4742
|
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;
|
|
3114
4743
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3115
4744
|
}
|
|
3116
|
-
__name(
|
|
4745
|
+
__name(_ts_decorate16, "_ts_decorate");
|
|
3117
4746
|
function settingFromCatalog(catalog, key) {
|
|
3118
4747
|
const settings = catalog.tenantBilling;
|
|
3119
4748
|
const value = settings?.[key];
|
|
@@ -3178,10 +4807,20 @@ var TenantBillingModule = class _TenantBillingModule {
|
|
|
3178
4807
|
if (!options.contractFreeze.subscriberRepository) {
|
|
3179
4808
|
throw new Error("TenantBillingModule: `contractFreeze` needs `subscriberRepository` \u2014 a frozen contract names the subscriber it is concluded with, and a plan change for a tenant without one is refused before anything changes.");
|
|
3180
4809
|
}
|
|
3181
|
-
providers.push(asProvider(CONTRACT_FREEZE_SOURCE_PORT_TOKEN, options.contractFreeze.sourcePort), asProvider(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN, options.contractFreeze.subscriptionContractRepository), ...subscriberProviders(options.contractFreeze.subscriberRepository), SubscriptionContractService, {
|
|
4810
|
+
providers.push(asProvider(CONTRACT_FREEZE_SOURCE_PORT_TOKEN, options.contractFreeze.sourcePort), asProvider(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN, options.contractFreeze.subscriptionContractRepository), ...subscriberProviders(options.contractFreeze.subscriberRepository), SubscriptionContractService, SubscriptionContractFreezeService, {
|
|
3182
4811
|
provide: CONTRACT_FREEZE_PORT_TOKEN,
|
|
3183
|
-
|
|
3184
|
-
});
|
|
4812
|
+
useExisting: SubscriptionContractFreezeService
|
|
4813
|
+
}, ContractRefreshService);
|
|
4814
|
+
if (options.contractFreeze.transactionRunner) {
|
|
4815
|
+
providers.push(asProvider(CONTRACT_TRANSACTION_RUNNER_TOKEN, options.contractFreeze.transactionRunner));
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
const hasChargeJournal = Boolean(options.chargeJournal);
|
|
4819
|
+
if (options.chargeJournal) {
|
|
4820
|
+
if (!options.contractFreeze) {
|
|
4821
|
+
throw new Error("TenantBillingModule: `chargeJournal` needs `contractFreeze` \u2014 a charge points at a line of a frozen contract, and the contracts and the subscribers they are concluded with come from there.");
|
|
4822
|
+
}
|
|
4823
|
+
providers.push(asProvider(SUBSCRIBER_LEDGER_REPOSITORY_TOKEN, options.chargeJournal.ledgerRepository), SubscriberChargeService, SubscriberAccountService);
|
|
3185
4824
|
}
|
|
3186
4825
|
if (options.tenantIdResolver) {
|
|
3187
4826
|
providers.push({
|
|
@@ -3227,37 +4866,42 @@ var TenantBillingModule = class _TenantBillingModule {
|
|
|
3227
4866
|
PendingPlanMaterializationService
|
|
3228
4867
|
] : [],
|
|
3229
4868
|
...hasContractFreeze ? [
|
|
3230
|
-
CONTRACT_FREEZE_PORT_TOKEN
|
|
4869
|
+
CONTRACT_FREEZE_PORT_TOKEN,
|
|
4870
|
+
ContractRefreshService
|
|
4871
|
+
] : [],
|
|
4872
|
+
...hasChargeJournal ? [
|
|
4873
|
+
SubscriberChargeService,
|
|
4874
|
+
SubscriberAccountService
|
|
3231
4875
|
] : [],
|
|
3232
4876
|
...options.extraExports ?? []
|
|
3233
4877
|
]
|
|
3234
4878
|
};
|
|
3235
4879
|
}
|
|
3236
4880
|
};
|
|
3237
|
-
TenantBillingModule =
|
|
3238
|
-
|
|
4881
|
+
TenantBillingModule = _ts_decorate16([
|
|
4882
|
+
Module3({})
|
|
3239
4883
|
], TenantBillingModule);
|
|
3240
4884
|
|
|
3241
4885
|
// src/billing/subscription-bundle-preview.service.ts
|
|
3242
|
-
import { Inject as
|
|
4886
|
+
import { Inject as Inject11, Injectable as Injectable9, NotFoundException as NotFoundException4, Optional as Optional9 } from "@nestjs/common";
|
|
3243
4887
|
import { BILLING_ERROR_CODES as BILLING_ERROR_CODES4, CATALOG_ERROR_CODES as CATALOG_ERROR_CODES2, buildFeatureRequiresIndex as buildFeatureRequiresIndex2, collectUnsatisfiedRequires as collectUnsatisfiedRequires2 } from "@saasicat/core";
|
|
3244
|
-
function
|
|
4888
|
+
function _ts_decorate17(decorators, target, key, desc) {
|
|
3245
4889
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3246
4890
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3247
4891
|
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;
|
|
3248
4892
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3249
4893
|
}
|
|
3250
|
-
__name(
|
|
3251
|
-
function
|
|
4894
|
+
__name(_ts_decorate17, "_ts_decorate");
|
|
4895
|
+
function _ts_metadata14(k, v) {
|
|
3252
4896
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
3253
4897
|
}
|
|
3254
|
-
__name(
|
|
3255
|
-
function
|
|
4898
|
+
__name(_ts_metadata14, "_ts_metadata");
|
|
4899
|
+
function _ts_param11(paramIndex, decorator) {
|
|
3256
4900
|
return function(target, key) {
|
|
3257
4901
|
decorator(target, key, paramIndex);
|
|
3258
4902
|
};
|
|
3259
4903
|
}
|
|
3260
|
-
__name(
|
|
4904
|
+
__name(_ts_param11, "_ts_param");
|
|
3261
4905
|
var SubscriptionBundlePreviewService = class {
|
|
3262
4906
|
static {
|
|
3263
4907
|
__name(this, "SubscriptionBundlePreviewService");
|
|
@@ -3527,20 +5171,20 @@ var SubscriptionBundlePreviewService = class {
|
|
|
3527
5171
|
return collectUnsatisfiedRequires2(bundleVersion.features ?? [], requiresIndex).filter((key) => !covered.has(key));
|
|
3528
5172
|
}
|
|
3529
5173
|
};
|
|
3530
|
-
SubscriptionBundlePreviewService =
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
5174
|
+
SubscriptionBundlePreviewService = _ts_decorate17([
|
|
5175
|
+
Injectable9(),
|
|
5176
|
+
_ts_param11(0, Inject11(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN)),
|
|
5177
|
+
_ts_param11(1, Inject11(BUNDLE_REPOSITORY_TOKEN)),
|
|
5178
|
+
_ts_param11(2, Optional9()),
|
|
5179
|
+
_ts_param11(2, Inject11(PLAN_REPOSITORY_TOKEN)),
|
|
5180
|
+
_ts_param11(3, Optional9()),
|
|
5181
|
+
_ts_param11(3, Inject11(CATALOG_ENTRY_REPOSITORY_TOKEN)),
|
|
5182
|
+
_ts_param11(4, Optional9()),
|
|
5183
|
+
_ts_param11(4, Inject11(SELF_SERVICE_BLOCKED_BUNDLES_TOKEN)),
|
|
5184
|
+
_ts_param11(5, Optional9()),
|
|
5185
|
+
_ts_param11(5, Inject11(SUBSCRIPTION_BUNDLE_CONFIG_TOKEN)),
|
|
5186
|
+
_ts_metadata14("design:type", Function),
|
|
5187
|
+
_ts_metadata14("design:paramtypes", [
|
|
3544
5188
|
typeof SubscriptionBundleRepository === "undefined" ? Object : SubscriptionBundleRepository,
|
|
3545
5189
|
typeof BundleRepository === "undefined" ? Object : BundleRepository,
|
|
3546
5190
|
Object,
|
|
@@ -3565,25 +5209,29 @@ function toSnapshot(bundleVersion) {
|
|
|
3565
5209
|
__name(toSnapshot, "toSnapshot");
|
|
3566
5210
|
|
|
3567
5211
|
// src/billing/tenant-subscription-bundles.controller.ts
|
|
3568
|
-
import { BadRequestException as BadRequestException2, Body as Body2, Controller as
|
|
5212
|
+
import { BadRequestException as BadRequestException2, Body as Body2, Controller as Controller4, Delete, Get as Get4, HttpCode as HttpCode2, HttpStatus as HttpStatus2, Inject as Inject12, Logger as Logger5, ConflictException as ConflictException2, NotFoundException as NotFoundException5, Optional as Optional10, Param as Param2, ParseUUIDPipe, Post as Post2, Req as Req2, UseGuards as UseGuards3 } from "@nestjs/common";
|
|
3569
5213
|
import { AUTH_ERROR_CODES as AUTH_ERROR_CODES3, BILLING_ERROR_CODES as BILLING_ERROR_CODES5 } from "@saasicat/core";
|
|
3570
|
-
function
|
|
5214
|
+
function _ts_decorate18(decorators, target, key, desc) {
|
|
3571
5215
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3572
5216
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3573
5217
|
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;
|
|
3574
5218
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3575
5219
|
}
|
|
3576
|
-
__name(
|
|
3577
|
-
function
|
|
5220
|
+
__name(_ts_decorate18, "_ts_decorate");
|
|
5221
|
+
function _ts_metadata15(k, v) {
|
|
3578
5222
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
3579
5223
|
}
|
|
3580
|
-
__name(
|
|
3581
|
-
function
|
|
5224
|
+
__name(_ts_metadata15, "_ts_metadata");
|
|
5225
|
+
function _ts_param12(paramIndex, decorator) {
|
|
3582
5226
|
return function(target, key) {
|
|
3583
5227
|
decorator(target, key, paramIndex);
|
|
3584
5228
|
};
|
|
3585
5229
|
}
|
|
3586
|
-
__name(
|
|
5230
|
+
__name(_ts_param12, "_ts_param");
|
|
5231
|
+
function planCycleOf2(sub) {
|
|
5232
|
+
return sub.billingCycle;
|
|
5233
|
+
}
|
|
5234
|
+
__name(planCycleOf2, "planCycleOf");
|
|
3587
5235
|
function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
3588
5236
|
let GeneratedTenantSubscriptionBundlesController = class GeneratedTenantSubscriptionBundlesController2 {
|
|
3589
5237
|
static {
|
|
@@ -3594,19 +5242,21 @@ function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
|
3594
5242
|
subscriptionUsage;
|
|
3595
5243
|
tenantIdResolver;
|
|
3596
5244
|
contractFreeze;
|
|
3597
|
-
|
|
5245
|
+
charges;
|
|
5246
|
+
logger = new Logger5("TenantSubscriptionBundlesController");
|
|
3598
5247
|
/** Keeps the "no contractFreeze hook" warning to one line per process. */
|
|
3599
5248
|
static warnedAboutMissingFreeze = false;
|
|
3600
|
-
constructor(service, previewService, subscriptionUsage, tenantIdResolver, contractFreeze = null) {
|
|
5249
|
+
constructor(service, previewService, subscriptionUsage, tenantIdResolver, contractFreeze = null, charges = null) {
|
|
3601
5250
|
this.service = service;
|
|
3602
5251
|
this.previewService = previewService;
|
|
3603
5252
|
this.subscriptionUsage = subscriptionUsage;
|
|
3604
5253
|
this.tenantIdResolver = tenantIdResolver;
|
|
3605
5254
|
this.contractFreeze = contractFreeze;
|
|
5255
|
+
this.charges = charges;
|
|
3606
5256
|
}
|
|
3607
5257
|
async list(req) {
|
|
3608
5258
|
const sub = await this.requireSubscription(this.requireTenantId(req));
|
|
3609
|
-
return this.service.listForSubscription(this.requireSubscriptionPk(sub), sub.plan, sub
|
|
5259
|
+
return this.service.listForSubscription(this.requireSubscriptionPk(sub), sub.plan, planCycleOf2(sub));
|
|
3610
5260
|
}
|
|
3611
5261
|
/**
|
|
3612
5262
|
* List prices for the bundles the store is showing, resolved for this
|
|
@@ -3636,7 +5286,7 @@ function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
|
3636
5286
|
// A bundle cannot commit past the subscription paying for it.
|
|
3637
5287
|
parentEndsAt: sub.canceledEffectiveAt ?? sub.canceledAt ?? null,
|
|
3638
5288
|
// And it runs in the plan's rhythm, on the plan's day.
|
|
3639
|
-
planCycle: sub
|
|
5289
|
+
planCycle: planCycleOf2(sub),
|
|
3640
5290
|
planPeriodEnd: sub.currentPeriodEnd ?? null,
|
|
3641
5291
|
// Resolved rather than passed through: a null here would let
|
|
3642
5292
|
// the arithmetic read the period END, which a short month has
|
|
@@ -3646,6 +5296,7 @@ function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
|
3646
5296
|
billingCycle: dto.billingCycle
|
|
3647
5297
|
});
|
|
3648
5298
|
await this.refreezeContract(tenantId, sub);
|
|
5299
|
+
await recordChargesAfter(this.charges, tenantId, "an add-on booking", this.logger);
|
|
3649
5300
|
return result;
|
|
3650
5301
|
}
|
|
3651
5302
|
async preview(req, dto) {
|
|
@@ -3662,7 +5313,7 @@ function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
|
3662
5313
|
subscriptionId: this.requireSubscriptionPk(sub),
|
|
3663
5314
|
// Plan KEY (compatibility.planIds is key-based) — not the planVersion UUID.
|
|
3664
5315
|
currentPlanKey: sub.plan,
|
|
3665
|
-
billingCycle: sub
|
|
5316
|
+
billingCycle: planCycleOf2(sub),
|
|
3666
5317
|
status: sub.status,
|
|
3667
5318
|
startedAt: sub.startedAt,
|
|
3668
5319
|
currentPeriodStart: sub.currentPeriodStart,
|
|
@@ -3772,7 +5423,7 @@ function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
|
3772
5423
|
await this.contractFreeze.freezeOnPlanChange(
|
|
3773
5424
|
tenantId,
|
|
3774
5425
|
sub.planVersion.planId,
|
|
3775
|
-
sub
|
|
5426
|
+
planCycleOf2(sub),
|
|
3776
5427
|
/* @__PURE__ */ new Date(),
|
|
3777
5428
|
// Cancelling a bundle stays open on a cancelled
|
|
3778
5429
|
// subscription, and it re-freezes: the replacement contract
|
|
@@ -3784,93 +5435,96 @@ function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
|
3784
5435
|
}
|
|
3785
5436
|
}
|
|
3786
5437
|
};
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
5438
|
+
_ts_decorate18([
|
|
5439
|
+
Get4(),
|
|
5440
|
+
_ts_param12(0, Req2()),
|
|
5441
|
+
_ts_metadata15("design:type", Function),
|
|
5442
|
+
_ts_metadata15("design:paramtypes", [
|
|
3792
5443
|
typeof RequestLike === "undefined" ? Object : RequestLike
|
|
3793
5444
|
]),
|
|
3794
|
-
|
|
5445
|
+
_ts_metadata15("design:returntype", Promise)
|
|
3795
5446
|
], GeneratedTenantSubscriptionBundlesController.prototype, "list", null);
|
|
3796
|
-
|
|
5447
|
+
_ts_decorate18([
|
|
3797
5448
|
Post2("prices"),
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
5449
|
+
_ts_param12(0, Req2()),
|
|
5450
|
+
_ts_param12(1, Body2()),
|
|
5451
|
+
_ts_metadata15("design:type", Function),
|
|
5452
|
+
_ts_metadata15("design:paramtypes", [
|
|
3802
5453
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3803
5454
|
typeof BundlePriceLookupDto === "undefined" ? Object : BundlePriceLookupDto
|
|
3804
5455
|
]),
|
|
3805
|
-
|
|
5456
|
+
_ts_metadata15("design:returntype", Promise)
|
|
3806
5457
|
], GeneratedTenantSubscriptionBundlesController.prototype, "prices", null);
|
|
3807
|
-
|
|
5458
|
+
_ts_decorate18([
|
|
3808
5459
|
Post2(),
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
5460
|
+
UseGuards3(TenantAdminGuard),
|
|
5461
|
+
_ts_param12(0, Req2()),
|
|
5462
|
+
_ts_param12(1, Body2()),
|
|
5463
|
+
_ts_metadata15("design:type", Function),
|
|
5464
|
+
_ts_metadata15("design:paramtypes", [
|
|
3814
5465
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3815
5466
|
typeof AddSubscriptionBundleDto === "undefined" ? Object : AddSubscriptionBundleDto
|
|
3816
5467
|
]),
|
|
3817
|
-
|
|
5468
|
+
_ts_metadata15("design:returntype", Promise)
|
|
3818
5469
|
], GeneratedTenantSubscriptionBundlesController.prototype, "add", null);
|
|
3819
|
-
|
|
5470
|
+
_ts_decorate18([
|
|
3820
5471
|
Post2("preview"),
|
|
3821
5472
|
HttpCode2(HttpStatus2.OK),
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
5473
|
+
_ts_param12(0, Req2()),
|
|
5474
|
+
_ts_param12(1, Body2()),
|
|
5475
|
+
_ts_metadata15("design:type", Function),
|
|
5476
|
+
_ts_metadata15("design:paramtypes", [
|
|
3826
5477
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3827
5478
|
typeof PreviewSubscriptionBundleDto === "undefined" ? Object : PreviewSubscriptionBundleDto
|
|
3828
5479
|
]),
|
|
3829
|
-
|
|
5480
|
+
_ts_metadata15("design:returntype", Promise)
|
|
3830
5481
|
], GeneratedTenantSubscriptionBundlesController.prototype, "preview", null);
|
|
3831
|
-
|
|
5482
|
+
_ts_decorate18([
|
|
3832
5483
|
Delete(":id"),
|
|
3833
|
-
|
|
5484
|
+
UseGuards3(TenantAdminGuard),
|
|
3834
5485
|
HttpCode2(HttpStatus2.OK),
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
5486
|
+
_ts_param12(0, Req2()),
|
|
5487
|
+
_ts_param12(1, Param2("id", new ParseUUIDPipe())),
|
|
5488
|
+
_ts_param12(2, Body2()),
|
|
5489
|
+
_ts_metadata15("design:type", Function),
|
|
5490
|
+
_ts_metadata15("design:paramtypes", [
|
|
3840
5491
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3841
5492
|
String,
|
|
3842
5493
|
typeof CancelSubscriptionBundleDto === "undefined" ? Object : CancelSubscriptionBundleDto
|
|
3843
5494
|
]),
|
|
3844
|
-
|
|
5495
|
+
_ts_metadata15("design:returntype", Promise)
|
|
3845
5496
|
], GeneratedTenantSubscriptionBundlesController.prototype, "cancel", null);
|
|
3846
|
-
|
|
5497
|
+
_ts_decorate18([
|
|
3847
5498
|
Post2(":id/reactivate"),
|
|
3848
|
-
|
|
5499
|
+
UseGuards3(TenantAdminGuard),
|
|
3849
5500
|
HttpCode2(HttpStatus2.OK),
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
5501
|
+
_ts_param12(0, Req2()),
|
|
5502
|
+
_ts_param12(1, Param2("id", new ParseUUIDPipe())),
|
|
5503
|
+
_ts_metadata15("design:type", Function),
|
|
5504
|
+
_ts_metadata15("design:paramtypes", [
|
|
3854
5505
|
typeof RequestLike === "undefined" ? Object : RequestLike,
|
|
3855
5506
|
String
|
|
3856
5507
|
]),
|
|
3857
|
-
|
|
5508
|
+
_ts_metadata15("design:returntype", Promise)
|
|
3858
5509
|
], GeneratedTenantSubscriptionBundlesController.prototype, "reactivate", null);
|
|
3859
|
-
GeneratedTenantSubscriptionBundlesController =
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
5510
|
+
GeneratedTenantSubscriptionBundlesController = _ts_decorate18([
|
|
5511
|
+
Controller4("billing/subscription-bundles"),
|
|
5512
|
+
UseGuards3(ComposedTenantAuthGuard, ...extraGuards),
|
|
5513
|
+
_ts_param12(0, Inject12(SubscriptionBundlesService)),
|
|
5514
|
+
_ts_param12(1, Inject12(SubscriptionBundlePreviewService)),
|
|
5515
|
+
_ts_param12(2, Inject12(SUBSCRIPTION_USAGE_PORT_TOKEN)),
|
|
5516
|
+
_ts_param12(3, Inject12(TENANT_ID_RESOLVER_TOKEN)),
|
|
5517
|
+
_ts_param12(4, Optional10()),
|
|
5518
|
+
_ts_param12(4, Inject12(CONTRACT_FREEZE_PORT_TOKEN)),
|
|
5519
|
+
_ts_param12(5, Optional10()),
|
|
5520
|
+
_ts_param12(5, Inject12(SubscriberChargeService)),
|
|
5521
|
+
_ts_metadata15("design:type", Function),
|
|
5522
|
+
_ts_metadata15("design:paramtypes", [
|
|
3870
5523
|
typeof SubscriptionBundlesService === "undefined" ? Object : SubscriptionBundlesService,
|
|
3871
5524
|
typeof SubscriptionBundlePreviewService === "undefined" ? Object : SubscriptionBundlePreviewService,
|
|
3872
5525
|
typeof SubscriptionUsagePort === "undefined" ? Object : SubscriptionUsagePort,
|
|
3873
5526
|
typeof TenantIdResolver === "undefined" ? Object : TenantIdResolver,
|
|
5527
|
+
Object,
|
|
3874
5528
|
Object
|
|
3875
5529
|
])
|
|
3876
5530
|
], GeneratedTenantSubscriptionBundlesController);
|
|
@@ -3879,14 +5533,14 @@ function buildTenantSubscriptionBundlesController(extraGuards = []) {
|
|
|
3879
5533
|
__name(buildTenantSubscriptionBundlesController, "buildTenantSubscriptionBundlesController");
|
|
3880
5534
|
|
|
3881
5535
|
// src/billing/subscription-bundles.module.ts
|
|
3882
|
-
import { Module as
|
|
3883
|
-
function
|
|
5536
|
+
import { Module as Module4 } from "@nestjs/common";
|
|
5537
|
+
function _ts_decorate19(decorators, target, key, desc) {
|
|
3884
5538
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3885
5539
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3886
5540
|
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;
|
|
3887
5541
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3888
5542
|
}
|
|
3889
|
-
__name(
|
|
5543
|
+
__name(_ts_decorate19, "_ts_decorate");
|
|
3890
5544
|
var SubscriptionBundleModule = class _SubscriptionBundleModule {
|
|
3891
5545
|
static {
|
|
3892
5546
|
__name(this, "SubscriptionBundleModule");
|
|
@@ -3948,8 +5602,8 @@ var SubscriptionBundleModule = class _SubscriptionBundleModule {
|
|
|
3948
5602
|
};
|
|
3949
5603
|
}
|
|
3950
5604
|
};
|
|
3951
|
-
SubscriptionBundleModule =
|
|
3952
|
-
|
|
5605
|
+
SubscriptionBundleModule = _ts_decorate19([
|
|
5606
|
+
Module4({})
|
|
3953
5607
|
], SubscriptionBundleModule);
|
|
3954
5608
|
|
|
3955
5609
|
export {
|
|
@@ -3989,11 +5643,22 @@ export {
|
|
|
3989
5643
|
SELF_SERVICE_BLOCKED_PLANS_TOKEN,
|
|
3990
5644
|
SELF_SERVICE_BLOCKED_BUNDLES_TOKEN,
|
|
3991
5645
|
computeProration,
|
|
5646
|
+
computeNewPeriodCharge,
|
|
3992
5647
|
PlanChangePreviewService,
|
|
3993
5648
|
CONTRACT_FREEZE_PORT_TOKEN,
|
|
3994
5649
|
CONTRACT_FREEZE_SOURCE_PORT_TOKEN,
|
|
3995
5650
|
PendingPlanMaterializationService,
|
|
3996
5651
|
SubscriptionContractFreezeService,
|
|
5652
|
+
featureChangeOf,
|
|
5653
|
+
quotaChangesOf,
|
|
5654
|
+
moneyChangesOf,
|
|
5655
|
+
withAgreedTerms,
|
|
5656
|
+
vocabularyOf,
|
|
5657
|
+
ContractRefreshService,
|
|
5658
|
+
SUBSCRIBER_LEDGER_REPOSITORY_TOKEN,
|
|
5659
|
+
SubscriberAccountService,
|
|
5660
|
+
SubscriberAccountModule,
|
|
5661
|
+
SubscriberChargeService,
|
|
3997
5662
|
PreviewPlanChangeDto,
|
|
3998
5663
|
ChangePlanDto,
|
|
3999
5664
|
CancelSubscriptionDto,
|