@saasicat/nest 1.0.0-rc.2 → 1.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -8
- package/dist/.build-stamp +1 -1
- package/dist/_entries.cjs +10214 -4796
- package/dist/admin/index.d.cts +5 -4
- package/dist/admin/index.d.ts +5 -4
- package/dist/admin/index.js +13 -9
- package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
- package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
- package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
- package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
- package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
- package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
- package/dist/billing/index.d.cts +448 -108
- package/dist/billing/index.d.ts +448 -108
- package/dist/billing/index.js +108 -41
- package/dist/catalog/index.d.cts +74 -45
- package/dist/catalog/index.d.ts +74 -45
- package/dist/catalog/index.js +21 -13
- package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
- package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
- package/dist/checkout-offer/index.d.cts +17 -22
- package/dist/checkout-offer/index.d.ts +17 -22
- package/dist/checkout-offer/index.js +21 -7
- package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
- package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
- package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
- package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
- package/dist/chunk-34XVPR6P.js +222 -0
- package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
- package/dist/chunk-3RLWML2S.js +896 -0
- package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
- package/dist/chunk-55QEDAMX.js +946 -0
- package/dist/chunk-5CKC7O52.js +140 -0
- package/dist/chunk-6PJE7EWO.js +185 -0
- package/dist/chunk-7NJKVC5X.js +353 -0
- package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
- package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
- package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
- package/dist/chunk-FEQZCWUL.js +174 -0
- package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
- package/dist/chunk-G7RQO2XO.js +0 -0
- package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
- package/dist/chunk-GZTL64QK.js +7 -0
- package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
- package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
- package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
- package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
- package/dist/chunk-KPEMTBOP.js +26 -0
- package/dist/chunk-LF6J4YYN.js +301 -0
- package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
- package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
- package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
- package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
- package/dist/chunk-QPVDCKYS.js +702 -0
- package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
- package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
- package/dist/chunk-SZ7RFPXA.js +10 -0
- package/dist/chunk-TBBZWZQT.js +98 -0
- package/dist/chunk-TOEFN7DN.js +295 -0
- package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
- package/dist/chunk-WOEJ6K7M.js +55 -0
- package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
- package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
- package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
- package/dist/discovery/index.d.cts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/discovery/index.js +6 -6
- package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
- package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
- package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
- package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
- package/dist/entitlement/index.d.cts +27 -4
- package/dist/entitlement/index.d.ts +27 -4
- package/dist/entitlement/index.js +13 -8
- package/dist/index.d.cts +209 -28
- package/dist/index.d.ts +209 -28
- package/dist/index.js +285 -163
- package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
- package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
- package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
- package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
- package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
- package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
- package/dist/payments/index.cjs +4 -0
- package/dist/payments/index.d.cts +187 -0
- package/dist/payments/index.d.ts +187 -0
- package/dist/payments/index.js +141 -0
- package/dist/payments.module-BdUq9ot_.d.cts +57 -0
- package/dist/payments.module-CQGv_VG7.d.ts +57 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
- package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
- package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
- package/dist/platform/index.d.cts +52 -23
- package/dist/platform/index.d.ts +52 -23
- package/dist/platform/index.js +57 -36
- package/dist/promo/index.d.cts +23 -8
- package/dist/promo/index.d.ts +23 -8
- package/dist/promo/index.js +15 -7
- package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
- package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
- package/dist/promo.service-BYBu6dy3.d.cts +215 -0
- package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
- package/dist/registration/index.d.cts +104 -47
- package/dist/registration/index.d.ts +104 -47
- package/dist/registration/index.js +15 -9
- package/dist/subscriber/index.cjs +4 -0
- package/dist/subscriber/index.d.cts +28 -0
- package/dist/subscriber/index.d.ts +28 -0
- package/dist/subscriber/index.js +16 -0
- package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
- package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
- package/dist/subscription-contract/index.d.cts +4 -2
- package/dist/subscription-contract/index.d.ts +4 -2
- package/dist/subscription-contract/index.js +13 -5
- package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
- package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
- package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
- package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
- package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
- package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
- package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
- package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
- package/dist/testing/index.d.cts +44 -17
- package/dist/testing/index.d.ts +44 -17
- package/dist/testing/index.js +189 -40
- package/package.json +24 -4
- package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
- package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
- package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
- package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
- package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
- package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
- package/dist/chunk-57V6ZTI6.js +0 -22
- package/dist/chunk-BSK6YBLI.js +0 -87
- package/dist/chunk-DQKCK7DX.js +0 -217
- package/dist/chunk-VXEYLNIB.js +0 -632
- package/dist/chunk-WUDYIPYH.js +0 -715
- package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
- package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
- package/dist/promo.service-BJbKAmw3.d.cts +0 -112
- package/dist/promo.service-BJbKAmw3.d.ts +0 -112
- package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
- package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
- package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
- package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
- /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
- /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
- /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
- /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BILLING_PERMISSION_GUARDS_TOKEN,
|
|
3
|
+
TENANT_AUTH_GUARDS_TOKEN
|
|
4
|
+
} from "./chunk-GPQWGA6B.js";
|
|
5
|
+
import {
|
|
6
|
+
codedError
|
|
7
|
+
} from "./chunk-J7NJRK3K.js";
|
|
8
|
+
import {
|
|
9
|
+
__name
|
|
10
|
+
} from "./chunk-SHUYVCID.js";
|
|
11
|
+
|
|
12
|
+
// src/billing/composed-tenant-auth.guard.ts
|
|
13
|
+
import { ForbiddenException, Inject, Injectable, Optional } from "@nestjs/common";
|
|
14
|
+
import { AUTH_ERROR_CODES } from "@saasicat/core";
|
|
15
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
16
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
17
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
18
|
+
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;
|
|
19
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
20
|
+
}
|
|
21
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
22
|
+
function _ts_metadata(k, v) {
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
24
|
+
}
|
|
25
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
26
|
+
function _ts_param(paramIndex, decorator) {
|
|
27
|
+
return function(target, key) {
|
|
28
|
+
decorator(target, key, paramIndex);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
__name(_ts_param, "_ts_param");
|
|
32
|
+
var ComposedTenantAuthGuard = class {
|
|
33
|
+
static {
|
|
34
|
+
__name(this, "ComposedTenantAuthGuard");
|
|
35
|
+
}
|
|
36
|
+
guards;
|
|
37
|
+
constructor(guards = null) {
|
|
38
|
+
this.guards = guards;
|
|
39
|
+
}
|
|
40
|
+
async canActivate(context) {
|
|
41
|
+
if (!this.guards || this.guards.length === 0) {
|
|
42
|
+
throw new ForbiddenException({
|
|
43
|
+
code: AUTH_ERROR_CODES.AUTH_GUARDS_NOT_CONFIGURED,
|
|
44
|
+
message: "TenantBillingModule.forRoot.authGuards is not configured."
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
for (const guard of this.guards) {
|
|
48
|
+
const result = await Promise.resolve(guard.canActivate(context));
|
|
49
|
+
if (!result) return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
ComposedTenantAuthGuard = _ts_decorate([
|
|
55
|
+
Injectable(),
|
|
56
|
+
_ts_param(0, Optional()),
|
|
57
|
+
_ts_param(0, Inject(TENANT_AUTH_GUARDS_TOKEN)),
|
|
58
|
+
_ts_metadata("design:type", Function),
|
|
59
|
+
_ts_metadata("design:paramtypes", [
|
|
60
|
+
Object
|
|
61
|
+
])
|
|
62
|
+
], ComposedTenantAuthGuard);
|
|
63
|
+
|
|
64
|
+
// src/billing/billing-permission.guard.ts
|
|
65
|
+
import { ForbiddenException as ForbiddenException3, Inject as Inject2, Injectable as Injectable2, Optional as Optional2 } from "@nestjs/common";
|
|
66
|
+
import { AUTH_ERROR_CODES as AUTH_ERROR_CODES3 } from "@saasicat/core";
|
|
67
|
+
|
|
68
|
+
// src/billing/tenant-roles.ts
|
|
69
|
+
import { ForbiddenException as ForbiddenException2 } from "@nestjs/common";
|
|
70
|
+
import { AUTH_ERROR_CODES as AUTH_ERROR_CODES2 } from "@saasicat/core";
|
|
71
|
+
var ADMIN_ROLES = /* @__PURE__ */ new Set([
|
|
72
|
+
"TENANT_ADMIN",
|
|
73
|
+
"SUPER_ADMIN"
|
|
74
|
+
]);
|
|
75
|
+
function holdsTenantAdminRole(context) {
|
|
76
|
+
const request = context.switchToHttp().getRequest();
|
|
77
|
+
if (!request.user) {
|
|
78
|
+
throw new ForbiddenException2({
|
|
79
|
+
code: AUTH_ERROR_CODES2.NOT_AUTHENTICATED,
|
|
80
|
+
message: "Not authenticated"
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const role = request.user.platformRole ?? request.user.role;
|
|
84
|
+
return Boolean(role && ADMIN_ROLES.has(role));
|
|
85
|
+
}
|
|
86
|
+
__name(holdsTenantAdminRole, "holdsTenantAdminRole");
|
|
87
|
+
|
|
88
|
+
// src/billing/billing-permission.guard.ts
|
|
89
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
90
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
91
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
92
|
+
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;
|
|
93
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
94
|
+
}
|
|
95
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
96
|
+
function _ts_metadata2(k, v) {
|
|
97
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
98
|
+
}
|
|
99
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
100
|
+
function _ts_param2(paramIndex, decorator) {
|
|
101
|
+
return function(target, key) {
|
|
102
|
+
decorator(target, key, paramIndex);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
__name(_ts_param2, "_ts_param");
|
|
106
|
+
var BillingPermissionGuard = class {
|
|
107
|
+
static {
|
|
108
|
+
__name(this, "BillingPermissionGuard");
|
|
109
|
+
}
|
|
110
|
+
guards;
|
|
111
|
+
constructor(guards = null) {
|
|
112
|
+
this.guards = guards;
|
|
113
|
+
}
|
|
114
|
+
async canActivate(context) {
|
|
115
|
+
if (await this.holdsPermission(context)) return true;
|
|
116
|
+
throw new ForbiddenException3(codedError(AUTH_ERROR_CODES3.BILLING_PERMISSION_REQUIRED));
|
|
117
|
+
}
|
|
118
|
+
async holdsPermission(context) {
|
|
119
|
+
if (!this.guards || this.guards.length === 0) return holdsTenantAdminRole(context);
|
|
120
|
+
for (const guard of this.guards) {
|
|
121
|
+
if (!await Promise.resolve(guard.canActivate(context))) return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
BillingPermissionGuard = _ts_decorate2([
|
|
127
|
+
Injectable2(),
|
|
128
|
+
_ts_param2(0, Optional2()),
|
|
129
|
+
_ts_param2(0, Inject2(BILLING_PERMISSION_GUARDS_TOKEN)),
|
|
130
|
+
_ts_metadata2("design:type", Function),
|
|
131
|
+
_ts_metadata2("design:paramtypes", [
|
|
132
|
+
Object
|
|
133
|
+
])
|
|
134
|
+
], BillingPermissionGuard);
|
|
135
|
+
|
|
136
|
+
export {
|
|
137
|
+
ComposedTenantAuthGuard,
|
|
138
|
+
holdsTenantAdminRole,
|
|
139
|
+
BillingPermissionGuard
|
|
140
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-SHUYVCID.js";
|
|
4
|
+
|
|
5
|
+
// src/promo/math.ts
|
|
6
|
+
function round2(n) {
|
|
7
|
+
return Math.round(n * 100) / 100;
|
|
8
|
+
}
|
|
9
|
+
__name(round2, "round2");
|
|
10
|
+
function isTaxRatePercent(rate) {
|
|
11
|
+
return Number.isFinite(rate) && rate >= 0 && rate <= 100 && !(rate > 0 && rate < 1);
|
|
12
|
+
}
|
|
13
|
+
__name(isTaxRatePercent, "isTaxRatePercent");
|
|
14
|
+
function grossFromNet(net, vatRate) {
|
|
15
|
+
return round2(net * (1 + vatRate / 100));
|
|
16
|
+
}
|
|
17
|
+
__name(grossFromNet, "grossFromNet");
|
|
18
|
+
function netFromGross(gross, vatRate) {
|
|
19
|
+
return round2(gross * 100 / (100 + vatRate));
|
|
20
|
+
}
|
|
21
|
+
__name(netFromGross, "netFromGross");
|
|
22
|
+
function computeIncludedVat(grossAmount, vatRate) {
|
|
23
|
+
return round2(grossAmount * vatRate / (100 + vatRate));
|
|
24
|
+
}
|
|
25
|
+
__name(computeIncludedVat, "computeIncludedVat");
|
|
26
|
+
|
|
27
|
+
// src/subscription-contract/contract-line-item-money.ts
|
|
28
|
+
var MONTHS_PER_YEAR = 12;
|
|
29
|
+
function recordContractLinesMoney(lines, money) {
|
|
30
|
+
const shares = grossSharesOf(lines, money.taxRate);
|
|
31
|
+
return lines.map((line, index) => {
|
|
32
|
+
const priceGross = shares[index];
|
|
33
|
+
return {
|
|
34
|
+
...line,
|
|
35
|
+
priceGross,
|
|
36
|
+
currency: money.currency,
|
|
37
|
+
taxRate: money.taxRate,
|
|
38
|
+
taxAmount: fromCents(toCents(priceGross) - toCents(line.priceNet))
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
__name(recordContractLinesMoney, "recordContractLinesMoney");
|
|
43
|
+
function grossSharesOf(lines, taxRate) {
|
|
44
|
+
const netSoFar = /* @__PURE__ */ new Map();
|
|
45
|
+
return lines.map((line) => {
|
|
46
|
+
const before = netSoFar.get(line.billingCycle) ?? 0;
|
|
47
|
+
const after = before + toCents(line.priceNet);
|
|
48
|
+
netSoFar.set(line.billingCycle, after);
|
|
49
|
+
return fromCents(grossCents(after, taxRate) - grossCents(before, taxRate));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
__name(grossSharesOf, "grossSharesOf");
|
|
53
|
+
function contractTotalsOf(lines, contractCycle) {
|
|
54
|
+
let subtotalNet = 0;
|
|
55
|
+
let discountNet = 0;
|
|
56
|
+
let totalGross = 0;
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
const times = timesDueInOnePeriod(contractCycle, line.billingCycle);
|
|
59
|
+
const net = times * toCents(line.priceNet);
|
|
60
|
+
if (line.kind === "discount") discountNet -= net;
|
|
61
|
+
else subtotalNet += net;
|
|
62
|
+
totalGross += times * toCents(line.priceGross);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
subtotalNet: fromCents(subtotalNet),
|
|
66
|
+
discountNet: fromCents(discountNet),
|
|
67
|
+
totalNet: fromCents(subtotalNet - discountNet),
|
|
68
|
+
totalGross: fromCents(totalGross)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
__name(contractTotalsOf, "contractTotalsOf");
|
|
72
|
+
function timesDueInOnePeriod(contractCycle, lineCycle) {
|
|
73
|
+
return contractCycle === "yearly" && lineCycle === "monthly" ? MONTHS_PER_YEAR : 1;
|
|
74
|
+
}
|
|
75
|
+
__name(timesDueInOnePeriod, "timesDueInOnePeriod");
|
|
76
|
+
function grossCents(netCents, taxRate) {
|
|
77
|
+
return toCents(grossFromNet(fromCents(netCents), taxRate));
|
|
78
|
+
}
|
|
79
|
+
__name(grossCents, "grossCents");
|
|
80
|
+
function toCents(amount) {
|
|
81
|
+
return Math.round(amount * 100);
|
|
82
|
+
}
|
|
83
|
+
__name(toCents, "toCents");
|
|
84
|
+
function fromCents(cents) {
|
|
85
|
+
return cents === 0 ? 0 : cents / 100;
|
|
86
|
+
}
|
|
87
|
+
__name(fromCents, "fromCents");
|
|
88
|
+
|
|
89
|
+
// src/subscription-contract/contract-refusals.ts
|
|
90
|
+
import { UnprocessableEntityException } from "@nestjs/common";
|
|
91
|
+
import { CONTRACT_ERROR_CODES } from "@saasicat/core";
|
|
92
|
+
function assertTaxRatePercent(field, taxRate) {
|
|
93
|
+
if (isTaxRatePercent(taxRate)) return;
|
|
94
|
+
throw new UnprocessableEntityException({
|
|
95
|
+
code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT,
|
|
96
|
+
message: `A subscription contract states a tax rate of ${taxRate} at ${field}, which is not a percentage: a rate lies from 0 to 100, and a value between 0 and 1 is refused as a fraction.`,
|
|
97
|
+
params: {
|
|
98
|
+
field,
|
|
99
|
+
taxRate
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
__name(assertTaxRatePercent, "assertTaxRatePercent");
|
|
104
|
+
function assertContractWindow(effectiveFrom, effectiveUntil) {
|
|
105
|
+
if (!effectiveUntil || effectiveUntil > effectiveFrom) return;
|
|
106
|
+
throw new UnprocessableEntityException({
|
|
107
|
+
code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_INVALID_WINDOW,
|
|
108
|
+
message: "effectiveUntil must be after effectiveFrom."
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
__name(assertContractWindow, "assertContractWindow");
|
|
112
|
+
function assertOnePlanLine(lineItems) {
|
|
113
|
+
if (lineItems.filter((item) => item.kind === "plan").length === 1) return;
|
|
114
|
+
throw new UnprocessableEntityException({
|
|
115
|
+
code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED,
|
|
116
|
+
message: "A subscription contract requires exactly one plan base item."
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
__name(assertOnePlanLine, "assertOnePlanLine");
|
|
120
|
+
var ADDED_UP_TOTALS = [
|
|
121
|
+
"subtotalNet",
|
|
122
|
+
"discountNet",
|
|
123
|
+
"totalNet",
|
|
124
|
+
"totalGross"
|
|
125
|
+
];
|
|
126
|
+
function assertLinesAddUp(lineItems, priceSnapshot) {
|
|
127
|
+
const lines = contractTotalsOf(lineItems, priceSnapshot.billingCycle);
|
|
128
|
+
const field = ADDED_UP_TOTALS.find((total) => Math.round(lines[total] * 100) !== Math.round(priceSnapshot[total] * 100));
|
|
129
|
+
if (!field) return;
|
|
130
|
+
throw new UnprocessableEntityException({
|
|
131
|
+
code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP,
|
|
132
|
+
message: `The line items of a subscription contract add up to ${lines[field]} for priceSnapshot.${field}, but the contract states ${priceSnapshot[field]}.`,
|
|
133
|
+
params: {
|
|
134
|
+
field: `priceSnapshot.${field}`,
|
|
135
|
+
lines: lines[field],
|
|
136
|
+
stated: priceSnapshot[field]
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
__name(assertLinesAddUp, "assertLinesAddUp");
|
|
141
|
+
function assertNoNegativeDiscount(data) {
|
|
142
|
+
const negative = (data.priceSnapshot.discountNet < 0 ? {
|
|
143
|
+
field: "priceSnapshot.discountNet",
|
|
144
|
+
amount: data.priceSnapshot.discountNet
|
|
145
|
+
} : null) ?? negativeResolvedAmount("promotionSnapshots", data.promotionSnapshots) ?? negativeResolvedAmount("promoCodeSnapshots", data.promoCodeSnapshots);
|
|
146
|
+
if (!negative) return;
|
|
147
|
+
throw new UnprocessableEntityException({
|
|
148
|
+
code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE,
|
|
149
|
+
message: `A subscription contract states a discount of ${negative.amount} at ${negative.field}; a discount takes money off and is never negative.`,
|
|
150
|
+
params: {
|
|
151
|
+
field: negative.field,
|
|
152
|
+
amount: negative.amount
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
__name(assertNoNegativeDiscount, "assertNoNegativeDiscount");
|
|
157
|
+
function negativeResolvedAmount(list, snapshots) {
|
|
158
|
+
for (const [index, snapshot] of (snapshots ?? []).entries()) {
|
|
159
|
+
const amount = snapshot?.resolvedAmountNet;
|
|
160
|
+
if (typeof amount === "number" && amount < 0) {
|
|
161
|
+
return {
|
|
162
|
+
field: `${list}[${index}].resolvedAmountNet`,
|
|
163
|
+
amount
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
__name(negativeResolvedAmount, "negativeResolvedAmount");
|
|
170
|
+
|
|
171
|
+
export {
|
|
172
|
+
round2,
|
|
173
|
+
isTaxRatePercent,
|
|
174
|
+
grossFromNet,
|
|
175
|
+
netFromGross,
|
|
176
|
+
computeIncludedVat,
|
|
177
|
+
recordContractLinesMoney,
|
|
178
|
+
grossSharesOf,
|
|
179
|
+
contractTotalsOf,
|
|
180
|
+
assertTaxRatePercent,
|
|
181
|
+
assertContractWindow,
|
|
182
|
+
assertOnePlanLine,
|
|
183
|
+
assertLinesAddUp,
|
|
184
|
+
assertNoNegativeDiscount
|
|
185
|
+
};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PLAN_CATALOG_SETTINGS_TOKEN
|
|
3
|
+
} from "./chunk-FEQZCWUL.js";
|
|
4
|
+
import {
|
|
5
|
+
codedError
|
|
6
|
+
} from "./chunk-J7NJRK3K.js";
|
|
7
|
+
import {
|
|
8
|
+
__name
|
|
9
|
+
} from "./chunk-SHUYVCID.js";
|
|
10
|
+
|
|
11
|
+
// src/payments/payments.tokens.ts
|
|
12
|
+
var PAYMENT_GATEWAYS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PaymentGateways");
|
|
13
|
+
var PAYMENT_EVENT_LOG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PaymentEventLog");
|
|
14
|
+
var SUBSCRIBER_PAYMENT_METHOD_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SubscriberPaymentMethodRepository");
|
|
15
|
+
var PAYMENT_TRANSACTION_RUNNER_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PaymentTransactionRunner");
|
|
16
|
+
|
|
17
|
+
// src/payments/payment-gateway-registry.ts
|
|
18
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
19
|
+
|
|
20
|
+
// src/payments/payment-accounts.ts
|
|
21
|
+
function paymentAccountProblems(payments, gateways) {
|
|
22
|
+
const bound = Object.keys(gateways);
|
|
23
|
+
if (!payments) {
|
|
24
|
+
return [
|
|
25
|
+
`Payment gateways are bound for ${bound.join(", ") || "no account"}, but config/saas.yaml has no \`payments\` block naming the accounts.`
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
const named = Object.keys(payments.accounts);
|
|
29
|
+
const problems = [];
|
|
30
|
+
for (const account of named) {
|
|
31
|
+
const gateway = gateways[account];
|
|
32
|
+
if (!gateway) {
|
|
33
|
+
problems.push(`config/saas.yaml names the payment account '${account}', and no gateway is bound for it: bind its adapter, or remove the account once it holds nothing.`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const provider = payments.accounts[account].provider;
|
|
37
|
+
if (gateway.provider !== provider) {
|
|
38
|
+
problems.push(`The payment account '${account}' is at '${provider}' in config/saas.yaml, but the gateway bound for it is '${gateway.provider}'.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const account of bound) {
|
|
42
|
+
if (!named.includes(account)) {
|
|
43
|
+
problems.push(`A gateway is bound for the payment account '${account}', which config/saas.yaml#payments.accounts does not name.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const takesNew = payments.newPaymentMethods;
|
|
47
|
+
if (takesNew !== void 0) {
|
|
48
|
+
const account = payments.accounts[takesNew];
|
|
49
|
+
if (!account) {
|
|
50
|
+
problems.push(`config/saas.yaml#payments.newPaymentMethods names '${takesNew}', which is not one of its accounts.`);
|
|
51
|
+
} else if (!account.methods?.length) {
|
|
52
|
+
problems.push(`The payment account '${takesNew}' takes new payment methods, and lists no \`methods\` to offer.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return problems;
|
|
56
|
+
}
|
|
57
|
+
__name(paymentAccountProblems, "paymentAccountProblems");
|
|
58
|
+
|
|
59
|
+
// src/payments/payment-gateway-registry.ts
|
|
60
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
61
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
62
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
63
|
+
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;
|
|
64
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
65
|
+
}
|
|
66
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
67
|
+
function _ts_metadata(k, v) {
|
|
68
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
69
|
+
}
|
|
70
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
71
|
+
function _ts_param(paramIndex, decorator) {
|
|
72
|
+
return function(target, key) {
|
|
73
|
+
decorator(target, key, paramIndex);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
__name(_ts_param, "_ts_param");
|
|
77
|
+
var PaymentGatewayRegistry = class {
|
|
78
|
+
static {
|
|
79
|
+
__name(this, "PaymentGatewayRegistry");
|
|
80
|
+
}
|
|
81
|
+
settings;
|
|
82
|
+
accounts = /* @__PURE__ */ new Map();
|
|
83
|
+
takesNew;
|
|
84
|
+
constructor(settings, gateways) {
|
|
85
|
+
this.settings = settings;
|
|
86
|
+
const problems = paymentAccountProblems(settings.payments, gateways);
|
|
87
|
+
if (problems.length > 0) {
|
|
88
|
+
throw new Error(`Payments cannot start:
|
|
89
|
+
- ${problems.join("\n- ")}`);
|
|
90
|
+
}
|
|
91
|
+
for (const [name, account] of Object.entries(settings.payments.accounts)) {
|
|
92
|
+
this.accounts.set(name, {
|
|
93
|
+
name,
|
|
94
|
+
provider: account.provider,
|
|
95
|
+
gateway: gateways[name]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
this.takesNew = settings.payments.newPaymentMethods;
|
|
99
|
+
}
|
|
100
|
+
/** The account a callback names, or `null` when none is configured under it. */
|
|
101
|
+
account(name) {
|
|
102
|
+
return this.accounts.get(name) ?? null;
|
|
103
|
+
}
|
|
104
|
+
/** The accounts among `held` that are not configured, each once — references nothing can reach any more. */
|
|
105
|
+
unconfigured(held) {
|
|
106
|
+
return [
|
|
107
|
+
...new Set(held)
|
|
108
|
+
].filter((name) => !this.accounts.has(name));
|
|
109
|
+
}
|
|
110
|
+
/** The origins a gateway's form may send a person back to. */
|
|
111
|
+
returnUrlOrigins() {
|
|
112
|
+
return this.settings.payments.returnUrlOrigins;
|
|
113
|
+
}
|
|
114
|
+
/** The account a new payment method is taken at, or `null` when none takes new ones. */
|
|
115
|
+
forNewPaymentMethods() {
|
|
116
|
+
if (this.takesNew === void 0) return null;
|
|
117
|
+
const account = this.accounts.get(this.takesNew);
|
|
118
|
+
return {
|
|
119
|
+
...account,
|
|
120
|
+
methods: this.settings.payments.accounts[this.takesNew].methods
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
PaymentGatewayRegistry = _ts_decorate([
|
|
125
|
+
Injectable(),
|
|
126
|
+
_ts_param(0, Inject(PLAN_CATALOG_SETTINGS_TOKEN)),
|
|
127
|
+
_ts_param(1, Inject(PAYMENT_GATEWAYS_TOKEN)),
|
|
128
|
+
_ts_metadata("design:type", Function),
|
|
129
|
+
_ts_metadata("design:paramtypes", [
|
|
130
|
+
typeof PlanCatalogSettings === "undefined" ? Object : PlanCatalogSettings,
|
|
131
|
+
typeof Readonly === "undefined" ? Object : Readonly
|
|
132
|
+
])
|
|
133
|
+
], PaymentGatewayRegistry);
|
|
134
|
+
|
|
135
|
+
// src/payments/payment-callback.service.ts
|
|
136
|
+
import { BadRequestException, Inject as Inject2, Injectable as Injectable2, Logger, NotFoundException } from "@nestjs/common";
|
|
137
|
+
import { PAYMENT_ERROR_CODES, isForeignPaymentMethodReferenceError, isPaymentCallbackRejectedError } from "@saasicat/core";
|
|
138
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
139
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
140
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
141
|
+
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;
|
|
142
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
143
|
+
}
|
|
144
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
145
|
+
function _ts_metadata2(k, v) {
|
|
146
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
147
|
+
}
|
|
148
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
149
|
+
function _ts_param2(paramIndex, decorator) {
|
|
150
|
+
return function(target, key) {
|
|
151
|
+
decorator(target, key, paramIndex);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
__name(_ts_param2, "_ts_param");
|
|
155
|
+
var PaymentCallbackService = class _PaymentCallbackService {
|
|
156
|
+
static {
|
|
157
|
+
__name(this, "PaymentCallbackService");
|
|
158
|
+
}
|
|
159
|
+
registry;
|
|
160
|
+
log;
|
|
161
|
+
transactions;
|
|
162
|
+
logger = new Logger(_PaymentCallbackService.name);
|
|
163
|
+
handlers = /* @__PURE__ */ new Map();
|
|
164
|
+
constructor(registry, log, transactions) {
|
|
165
|
+
this.registry = registry;
|
|
166
|
+
this.log = log;
|
|
167
|
+
this.transactions = transactions;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Names who handles the setups of one kind of subject. Each kind has one
|
|
171
|
+
* handler; a second is a wiring error, not a replacement.
|
|
172
|
+
*/
|
|
173
|
+
handleSetupsOf(kind, handler) {
|
|
174
|
+
if (this.handlers.has(kind)) {
|
|
175
|
+
throw new Error(`A handler for payment method setups of '${kind}' is registered twice.`);
|
|
176
|
+
}
|
|
177
|
+
this.handlers.set(kind, handler);
|
|
178
|
+
}
|
|
179
|
+
async handle(account, callback) {
|
|
180
|
+
const entry = this.registry.account(account);
|
|
181
|
+
if (!entry) {
|
|
182
|
+
throw new NotFoundException(codedError(PAYMENT_ERROR_CODES.PAYMENT_GATEWAY_ACCOUNT_UNKNOWN, {
|
|
183
|
+
account
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
const event = await this.read(entry.gateway.readCallback.bind(entry.gateway), callback, account);
|
|
187
|
+
if (event.kind === "unhandled") {
|
|
188
|
+
this.logger.debug(`Payment event ${event.eventId} (${event.type}) at '${account}' needs no action.`);
|
|
189
|
+
return "unhandled";
|
|
190
|
+
}
|
|
191
|
+
const handler = this.handlerFor(event);
|
|
192
|
+
const afterCommit = [];
|
|
193
|
+
const claimed = await this.transactions.run(async (tx) => {
|
|
194
|
+
const fresh = await this.log.claim({
|
|
195
|
+
gatewayAccount: account,
|
|
196
|
+
eventId: event.eventId,
|
|
197
|
+
provider: entry.provider,
|
|
198
|
+
sessionId: event.sessionRef,
|
|
199
|
+
kind: event.kind,
|
|
200
|
+
summary: summaryOf(event)
|
|
201
|
+
}, tx);
|
|
202
|
+
if (!fresh) return false;
|
|
203
|
+
const context = {
|
|
204
|
+
tx,
|
|
205
|
+
gatewayAccount: account,
|
|
206
|
+
provider: entry.provider,
|
|
207
|
+
afterCommit: /* @__PURE__ */ __name((step) => afterCommit.push(step), "afterCommit")
|
|
208
|
+
};
|
|
209
|
+
if (event.kind === "payment-method-confirmed") {
|
|
210
|
+
const effect = await this.confirm(handler, event, context);
|
|
211
|
+
if (effect === "nothing-to-do") {
|
|
212
|
+
await this.log.releaseSession(account, event.eventId, tx);
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
await handler.failed(event, context);
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
});
|
|
219
|
+
if (!claimed) {
|
|
220
|
+
this.logger.log(`Payment event ${event.eventId} at '${account}' was handled before; duplicate ignored.`);
|
|
221
|
+
return "duplicate";
|
|
222
|
+
}
|
|
223
|
+
for (const step of afterCommit) {
|
|
224
|
+
try {
|
|
225
|
+
await step();
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
228
|
+
this.logger.error(`Payment event ${event.eventId} at '${account}' is handled, and a step after it failed: ${reason}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return "handled";
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Hands the confirmation to its handler, and names the one refusal that
|
|
235
|
+
* will never come out differently.
|
|
236
|
+
*
|
|
237
|
+
* A reference belongs to one subscriber for good (`SC-SEC-014`), so a
|
|
238
|
+
* confirmation refused for that reason is refused on every delivery. It
|
|
239
|
+
* still leaves by throwing, which rolls this transaction back: by the time
|
|
240
|
+
* it is raised a handler has written something that must not stand on its
|
|
241
|
+
* own — the setup marked complete on the tenant's path, the whole
|
|
242
|
+
* activation on the sign-up's. Answering the gateway anything else would
|
|
243
|
+
* commit one of those without the payment method that justifies it.
|
|
244
|
+
*
|
|
245
|
+
* The gateway therefore retries until it gives up, and the claim rolls back
|
|
246
|
+
* with everything else, so nothing durable is left to say why. This line is
|
|
247
|
+
* what says it.
|
|
248
|
+
*
|
|
249
|
+
* There is no terminal state for it, and that is worth knowing before an
|
|
250
|
+
* operator waits for one: the webhook route has no filter for this, so every
|
|
251
|
+
* redelivery is answered `500`, for as long as the provider keeps trying. A
|
|
252
|
+
* provider that disables an endpoint after sustained failures would take the
|
|
253
|
+
* account's other callbacks with it. Giving the event an end would mean
|
|
254
|
+
* keeping the claim while undoing what the handler wrote, which needs a
|
|
255
|
+
* savepoint the port does not have — so it is named here rather than
|
|
256
|
+
* implied. It names the account, the reference and the subject the
|
|
257
|
+
* event was about — never the subscriber the reference belongs to, which is
|
|
258
|
+
* on the other side of the boundary being refused.
|
|
259
|
+
*/
|
|
260
|
+
async confirm(handler, event, context) {
|
|
261
|
+
try {
|
|
262
|
+
return await handler.confirmed(event, context);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (!isForeignPaymentMethodReferenceError(error)) throw error;
|
|
265
|
+
this.logger.error(`Payment event ${event.eventId} at '${context.gatewayAccount}' confirms payment method '${error.paymentMethodRef}' for ${subjectOf(event)}, and that reference belongs to another subscriber; nothing was recorded, and every delivery of this event is refused the same way.`);
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async read(readCallback, callback, account) {
|
|
270
|
+
try {
|
|
271
|
+
return await readCallback(callback);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (!isPaymentCallbackRejectedError(error)) throw error;
|
|
274
|
+
this.logger.warn(`A payment callback for '${account}' was rejected: ${error.message}`);
|
|
275
|
+
throw new BadRequestException(codedError(PAYMENT_ERROR_CODES.PAYMENT_CALLBACK_REJECTED));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
handlerFor(event) {
|
|
279
|
+
const handler = this.handlers.get(event.subject.kind);
|
|
280
|
+
if (handler) return handler;
|
|
281
|
+
throw new Error(`A payment method setup for a ${event.subject.kind} was reported, and nothing handles ` + (event.subject.kind === "registration" ? "sign-ups: wire RegistrationModule." : "subscriber payment methods."));
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
PaymentCallbackService = _ts_decorate2([
|
|
285
|
+
Injectable2(),
|
|
286
|
+
_ts_param2(1, Inject2(PAYMENT_EVENT_LOG_TOKEN)),
|
|
287
|
+
_ts_param2(2, Inject2(PAYMENT_TRANSACTION_RUNNER_TOKEN)),
|
|
288
|
+
_ts_metadata2("design:type", Function),
|
|
289
|
+
_ts_metadata2("design:paramtypes", [
|
|
290
|
+
typeof PaymentGatewayRegistry === "undefined" ? Object : PaymentGatewayRegistry,
|
|
291
|
+
typeof PaymentEventLog === "undefined" ? Object : PaymentEventLog,
|
|
292
|
+
typeof TransactionRunner === "undefined" ? Object : TransactionRunner
|
|
293
|
+
])
|
|
294
|
+
], PaymentCallbackService);
|
|
295
|
+
function subjectOf(event) {
|
|
296
|
+
return event.subject.kind === "registration" ? `sign-up ${event.subject.pendingRegistrationId}` : `subscriber ${event.subject.subscriberId}`;
|
|
297
|
+
}
|
|
298
|
+
__name(subjectOf, "subjectOf");
|
|
299
|
+
function summaryOf(event) {
|
|
300
|
+
const subject = event.subject.kind === "registration" ? {
|
|
301
|
+
registration: event.subject.pendingRegistrationId
|
|
302
|
+
} : {
|
|
303
|
+
subscriber: event.subject.subscriberId
|
|
304
|
+
};
|
|
305
|
+
if (event.kind === "payment-method-setup-failed") return {
|
|
306
|
+
...subject
|
|
307
|
+
};
|
|
308
|
+
const { type, brand, last4 } = event.paymentMethod;
|
|
309
|
+
return {
|
|
310
|
+
...subject,
|
|
311
|
+
type,
|
|
312
|
+
brand,
|
|
313
|
+
last4
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
__name(summaryOf, "summaryOf");
|
|
317
|
+
|
|
318
|
+
// src/payments/return-urls.ts
|
|
319
|
+
import { BadRequestException as BadRequestException2 } from "@nestjs/common";
|
|
320
|
+
import { PAYMENT_ERROR_CODES as PAYMENT_ERROR_CODES2 } from "@saasicat/core";
|
|
321
|
+
function refuseForeignReturnUrls(urls, allowedOrigins) {
|
|
322
|
+
const allowed = new Set(allowedOrigins.map(originOf));
|
|
323
|
+
for (const field of [
|
|
324
|
+
"successUrl",
|
|
325
|
+
"cancelUrl"
|
|
326
|
+
]) {
|
|
327
|
+
const origin = originOf(urls[field]);
|
|
328
|
+
if (origin === null || !allowed.has(origin)) {
|
|
329
|
+
throw new BadRequestException2(codedError(PAYMENT_ERROR_CODES2.PAYMENT_RETURN_URL_NOT_ALLOWED, {
|
|
330
|
+
field
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
__name(refuseForeignReturnUrls, "refuseForeignReturnUrls");
|
|
336
|
+
function originOf(url) {
|
|
337
|
+
try {
|
|
338
|
+
return new URL(url).origin;
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
__name(originOf, "originOf");
|
|
344
|
+
|
|
345
|
+
export {
|
|
346
|
+
PAYMENT_GATEWAYS_TOKEN,
|
|
347
|
+
PAYMENT_EVENT_LOG_TOKEN,
|
|
348
|
+
SUBSCRIBER_PAYMENT_METHOD_REPOSITORY_TOKEN,
|
|
349
|
+
PAYMENT_TRANSACTION_RUNNER_TOKEN,
|
|
350
|
+
PaymentGatewayRegistry,
|
|
351
|
+
PaymentCallbackService,
|
|
352
|
+
refuseForeignReturnUrls
|
|
353
|
+
};
|