@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.
Files changed (154) hide show
  1. package/README.md +44 -8
  2. package/dist/.build-stamp +1 -1
  3. package/dist/_entries.cjs +10214 -4796
  4. package/dist/admin/index.d.cts +5 -4
  5. package/dist/admin/index.d.ts +5 -4
  6. package/dist/admin/index.js +13 -9
  7. package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
  8. package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
  9. package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
  10. package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
  11. package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
  12. package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
  13. package/dist/billing/index.d.cts +448 -108
  14. package/dist/billing/index.d.ts +448 -108
  15. package/dist/billing/index.js +108 -41
  16. package/dist/catalog/index.d.cts +74 -45
  17. package/dist/catalog/index.d.ts +74 -45
  18. package/dist/catalog/index.js +21 -13
  19. package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
  20. package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
  21. package/dist/checkout-offer/index.d.cts +17 -22
  22. package/dist/checkout-offer/index.d.ts +17 -22
  23. package/dist/checkout-offer/index.js +21 -7
  24. package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
  25. package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
  26. package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
  27. package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
  28. package/dist/chunk-34XVPR6P.js +222 -0
  29. package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
  30. package/dist/chunk-3RLWML2S.js +896 -0
  31. package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
  32. package/dist/chunk-55QEDAMX.js +946 -0
  33. package/dist/chunk-5CKC7O52.js +140 -0
  34. package/dist/chunk-6PJE7EWO.js +185 -0
  35. package/dist/chunk-7NJKVC5X.js +353 -0
  36. package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
  37. package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
  38. package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
  39. package/dist/chunk-FEQZCWUL.js +174 -0
  40. package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
  41. package/dist/chunk-G7RQO2XO.js +0 -0
  42. package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
  43. package/dist/chunk-GZTL64QK.js +7 -0
  44. package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
  45. package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
  46. package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
  47. package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
  48. package/dist/chunk-KPEMTBOP.js +26 -0
  49. package/dist/chunk-LF6J4YYN.js +301 -0
  50. package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
  51. package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
  52. package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
  53. package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
  54. package/dist/chunk-QPVDCKYS.js +702 -0
  55. package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
  56. package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
  57. package/dist/chunk-SZ7RFPXA.js +10 -0
  58. package/dist/chunk-TBBZWZQT.js +98 -0
  59. package/dist/chunk-TOEFN7DN.js +295 -0
  60. package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
  61. package/dist/chunk-WOEJ6K7M.js +55 -0
  62. package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
  63. package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
  64. package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
  65. package/dist/discovery/index.d.cts +2 -2
  66. package/dist/discovery/index.d.ts +2 -2
  67. package/dist/discovery/index.js +6 -6
  68. package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
  69. package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
  70. package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
  71. package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
  72. package/dist/entitlement/index.d.cts +27 -4
  73. package/dist/entitlement/index.d.ts +27 -4
  74. package/dist/entitlement/index.js +13 -8
  75. package/dist/index.d.cts +209 -28
  76. package/dist/index.d.ts +209 -28
  77. package/dist/index.js +285 -163
  78. package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
  79. package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
  80. package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
  81. package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
  82. package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
  83. package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
  84. package/dist/payments/index.cjs +4 -0
  85. package/dist/payments/index.d.cts +187 -0
  86. package/dist/payments/index.d.ts +187 -0
  87. package/dist/payments/index.js +141 -0
  88. package/dist/payments.module-BdUq9ot_.d.cts +57 -0
  89. package/dist/payments.module-CQGv_VG7.d.ts +57 -0
  90. package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
  91. package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
  92. package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
  93. package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
  94. package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
  95. package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
  96. package/dist/platform/index.d.cts +52 -23
  97. package/dist/platform/index.d.ts +52 -23
  98. package/dist/platform/index.js +57 -36
  99. package/dist/promo/index.d.cts +23 -8
  100. package/dist/promo/index.d.ts +23 -8
  101. package/dist/promo/index.js +15 -7
  102. package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
  103. package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
  104. package/dist/promo.service-BYBu6dy3.d.cts +215 -0
  105. package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
  106. package/dist/registration/index.d.cts +104 -47
  107. package/dist/registration/index.d.ts +104 -47
  108. package/dist/registration/index.js +15 -9
  109. package/dist/subscriber/index.cjs +4 -0
  110. package/dist/subscriber/index.d.cts +28 -0
  111. package/dist/subscriber/index.d.ts +28 -0
  112. package/dist/subscriber/index.js +16 -0
  113. package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
  114. package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
  115. package/dist/subscription-contract/index.d.cts +4 -2
  116. package/dist/subscription-contract/index.d.ts +4 -2
  117. package/dist/subscription-contract/index.js +13 -5
  118. package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
  119. package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
  120. package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
  121. package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
  122. package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
  123. package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
  124. package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
  125. package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
  126. package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
  127. package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
  128. package/dist/testing/index.d.cts +44 -17
  129. package/dist/testing/index.d.ts +44 -17
  130. package/dist/testing/index.js +189 -40
  131. package/package.json +24 -4
  132. package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
  133. package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
  134. package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
  135. package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
  136. package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
  137. package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
  138. package/dist/chunk-57V6ZTI6.js +0 -22
  139. package/dist/chunk-BSK6YBLI.js +0 -87
  140. package/dist/chunk-DQKCK7DX.js +0 -217
  141. package/dist/chunk-VXEYLNIB.js +0 -632
  142. package/dist/chunk-WUDYIPYH.js +0 -715
  143. package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
  144. package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
  145. package/dist/promo.service-BJbKAmw3.d.cts +0 -112
  146. package/dist/promo.service-BJbKAmw3.d.ts +0 -112
  147. package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
  148. package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
  149. package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
  150. package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
  151. /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
  152. /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
  153. /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
  154. /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
@@ -0,0 +1,141 @@
1
+ import {
2
+ ChangeBillingDetailsDto,
3
+ PaymentWebhookController,
4
+ PaymentsModule,
5
+ StartPaymentMethodSetupDto,
6
+ SubscriberPaymentMethodService,
7
+ TenantBillingDetailsController,
8
+ TenantPaymentMethodController
9
+ } from "../chunk-QPVDCKYS.js";
10
+ import {
11
+ PAYMENT_EVENT_LOG_TOKEN,
12
+ PAYMENT_GATEWAYS_TOKEN,
13
+ PAYMENT_TRANSACTION_RUNNER_TOKEN,
14
+ PaymentCallbackService,
15
+ PaymentGatewayRegistry,
16
+ SUBSCRIBER_PAYMENT_METHOD_REPOSITORY_TOKEN
17
+ } from "../chunk-7NJKVC5X.js";
18
+ import "../chunk-5CKC7O52.js";
19
+ import "../chunk-GPQWGA6B.js";
20
+ import "../chunk-SZ7RFPXA.js";
21
+ import "../chunk-WOEJ6K7M.js";
22
+ import "../chunk-LF6J4YYN.js";
23
+ import "../chunk-YSC46BGM.js";
24
+ import "../chunk-FEQZCWUL.js";
25
+ import "../chunk-J7NJRK3K.js";
26
+ import {
27
+ __name
28
+ } from "../chunk-SHUYVCID.js";
29
+
30
+ // src/payments/dev-payment-gateway.ts
31
+ import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "crypto";
32
+ import { PaymentCallbackRejectedError } from "@saasicat/core";
33
+ var SIGNATURE_HEADER = "x-saasicat-dev-signature";
34
+ var DEV_PAYMENT_PROVIDER = "dev";
35
+ var DevPaymentGateway = class {
36
+ static {
37
+ __name(this, "DevPaymentGateway");
38
+ }
39
+ provider = DEV_PAYMENT_PROVIDER;
40
+ key = randomBytes(32);
41
+ constructor() {
42
+ if (process.env.NODE_ENV === "production") {
43
+ throw new Error("DevPaymentGateway confirms every payment method without taking one, and refuses to run with NODE_ENV=production. Bind a gateway adapter such as @saasicat/payment-stripe.");
44
+ }
45
+ }
46
+ async startPaymentMethodSetup(input) {
47
+ const sessionRef = `dev_cs_${randomUUID()}`;
48
+ const customerRef = input.holder.customerRef ?? `dev_cus_${randomUUID()}`;
49
+ const confirmation = {
50
+ eventId: `dev_evt_${randomUUID()}`,
51
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
52
+ sessionRef,
53
+ subject: input.subject,
54
+ paymentMethod: {
55
+ ...maskedDetailsOf(input.methods[0] ?? "card"),
56
+ customerRef,
57
+ paymentMethodRef: `dev_pm_${randomUUID()}`
58
+ }
59
+ };
60
+ const body = JSON.stringify(confirmation);
61
+ return {
62
+ sessionRef,
63
+ redirectUrl: input.successUrl,
64
+ customerRef,
65
+ // Confirmed on the spot: there is no form left to complete later.
66
+ confirmableUntil: null,
67
+ immediateCallback: {
68
+ body,
69
+ headers: {
70
+ [SIGNATURE_HEADER]: this.sign(body)
71
+ }
72
+ }
73
+ };
74
+ }
75
+ async readCallback(callback) {
76
+ const body = typeof callback.body === "string" ? callback.body : Buffer.from(callback.body).toString("utf8");
77
+ const signature = callback.headers[SIGNATURE_HEADER];
78
+ if (typeof signature !== "string" || !this.verify(body, signature)) {
79
+ throw new PaymentCallbackRejectedError("the development signature does not verify");
80
+ }
81
+ const confirmation = JSON.parse(body);
82
+ return {
83
+ kind: "payment-method-confirmed",
84
+ eventId: confirmation.eventId,
85
+ occurredAt: new Date(confirmation.occurredAt),
86
+ sessionRef: confirmation.sessionRef,
87
+ subject: confirmation.subject,
88
+ paymentMethod: confirmation.paymentMethod
89
+ };
90
+ }
91
+ sign(body) {
92
+ return createHmac("sha256", this.key).update(body).digest("hex");
93
+ }
94
+ verify(body, signature) {
95
+ const expected = Buffer.from(this.sign(body), "hex");
96
+ const given = Buffer.from(signature, "hex");
97
+ return given.length === expected.length && timingSafeEqual(given, expected);
98
+ }
99
+ };
100
+ function maskedDetailsOf(type) {
101
+ if (type === "sepa_debit") {
102
+ return {
103
+ type,
104
+ brand: null,
105
+ last4: "3000",
106
+ expiryMonth: null,
107
+ expiryYear: null,
108
+ country: "DE",
109
+ bankCode: "37040044",
110
+ mandateReference: `DEV-${randomUUID().slice(0, 8).toUpperCase()}`
111
+ };
112
+ }
113
+ return {
114
+ type,
115
+ brand: "visa",
116
+ last4: "4242",
117
+ expiryMonth: 12,
118
+ expiryYear: (/* @__PURE__ */ new Date()).getUTCFullYear() + 3,
119
+ country: null,
120
+ bankCode: null,
121
+ mandateReference: null
122
+ };
123
+ }
124
+ __name(maskedDetailsOf, "maskedDetailsOf");
125
+ export {
126
+ ChangeBillingDetailsDto,
127
+ DEV_PAYMENT_PROVIDER,
128
+ DevPaymentGateway,
129
+ PAYMENT_EVENT_LOG_TOKEN,
130
+ PAYMENT_GATEWAYS_TOKEN,
131
+ PAYMENT_TRANSACTION_RUNNER_TOKEN,
132
+ PaymentCallbackService,
133
+ PaymentGatewayRegistry,
134
+ PaymentWebhookController,
135
+ PaymentsModule,
136
+ SUBSCRIBER_PAYMENT_METHOD_REPOSITORY_TOKEN,
137
+ StartPaymentMethodSetupDto,
138
+ SubscriberPaymentMethodService,
139
+ TenantBillingDetailsController,
140
+ TenantPaymentMethodController
141
+ };
@@ -0,0 +1,57 @@
1
+ import { CanActivate, Type, DynamicModule, ForwardReference, Provider } from '@nestjs/common';
2
+ import { PaymentGateway, PaymentEventLog, SubscriberPaymentMethodRepository, SubscriberRepository, TransactionRunner } from '@saasicat/core';
3
+ import { T as TenantIdResolver, U as UserEmailResolver } from './tenant-billing.tokens-G-1xOlMr.cjs';
4
+ import { P as ProviderSpec } from './di-CcNeq9v-.cjs';
5
+
6
+ /** The tenant's routes for its payment method. */
7
+ interface PaymentsTenantRoutesOptions {
8
+ /** The application's authentication guards, as `TenantBillingModule.forRoot.authGuards`. */
9
+ authGuards: ProviderSpec<ReadonlyArray<CanActivate>>;
10
+ /**
11
+ * Who holds the billing permission. Without guards, the tenant's
12
+ * administrator (`TENANT_ADMIN`, `SUPER_ADMIN`) does.
13
+ */
14
+ billingPermissionGuards?: ProviderSpec<ReadonlyArray<CanActivate>>;
15
+ /** Default: `req.user.tenantId`. */
16
+ tenantIdResolver?: TenantIdResolver;
17
+ /** Default: `req.user.email` — handed to the gateway when the subscriber has no invoice email. */
18
+ userEmailResolver?: UserEmailResolver;
19
+ }
20
+ interface PaymentsModuleOptions {
21
+ /**
22
+ * One adapter per account in `config/saas.yaml#payments.accounts`, by that
23
+ * account's name — each built with its keys from the environment.
24
+ */
25
+ gateways: ProviderSpec<Readonly<Record<string, PaymentGateway>>>;
26
+ paymentEventLog: ProviderSpec<PaymentEventLog>;
27
+ subscriberPaymentMethodRepository: ProviderSpec<SubscriberPaymentMethodRepository>;
28
+ subscriberRepository: ProviderSpec<SubscriberRepository>;
29
+ /** The runner of the same persistence adapter the two stores above come from. */
30
+ transactionRunner: ProviderSpec<TransactionRunner>;
31
+ /**
32
+ * Mounts the tenant's billing area: `GET /billing/payment-method`,
33
+ * `POST /billing/payment-method/setup`, and `GET` and `PATCH /billing/details`.
34
+ */
35
+ tenantRoutes?: PaymentsTenantRoutesOptions;
36
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
37
+ extraProviders?: Provider[];
38
+ /**
39
+ * Default `true`: self-registration, wired in a module of its own, starts
40
+ * its setups through the registry and hands its confirmations to the
41
+ * callback service.
42
+ */
43
+ global?: boolean;
44
+ }
45
+ /**
46
+ * Payment methods through a payment gateway: the webhook route every account's
47
+ * callbacks arrive at, the registry of configured accounts, and — with
48
+ * `tenantRoutes` — the tenant's view of its payment method.
49
+ *
50
+ * Reads `config/saas.yaml#payments` from `PLAN_CATALOG_SETTINGS_TOKEN`, which
51
+ * `SaaSiCatModule.forRoot` provides globally.
52
+ */
53
+ declare class PaymentsModule {
54
+ static forRoot(options: PaymentsModuleOptions): DynamicModule;
55
+ }
56
+
57
+ export { type PaymentsModuleOptions as P, type PaymentsTenantRoutesOptions as a, PaymentsModule as b };
@@ -0,0 +1,57 @@
1
+ import { CanActivate, Type, DynamicModule, ForwardReference, Provider } from '@nestjs/common';
2
+ import { PaymentGateway, PaymentEventLog, SubscriberPaymentMethodRepository, SubscriberRepository, TransactionRunner } from '@saasicat/core';
3
+ import { T as TenantIdResolver, U as UserEmailResolver } from './tenant-billing.tokens-G-1xOlMr.js';
4
+ import { P as ProviderSpec } from './di-CcNeq9v-.js';
5
+
6
+ /** The tenant's routes for its payment method. */
7
+ interface PaymentsTenantRoutesOptions {
8
+ /** The application's authentication guards, as `TenantBillingModule.forRoot.authGuards`. */
9
+ authGuards: ProviderSpec<ReadonlyArray<CanActivate>>;
10
+ /**
11
+ * Who holds the billing permission. Without guards, the tenant's
12
+ * administrator (`TENANT_ADMIN`, `SUPER_ADMIN`) does.
13
+ */
14
+ billingPermissionGuards?: ProviderSpec<ReadonlyArray<CanActivate>>;
15
+ /** Default: `req.user.tenantId`. */
16
+ tenantIdResolver?: TenantIdResolver;
17
+ /** Default: `req.user.email` — handed to the gateway when the subscriber has no invoice email. */
18
+ userEmailResolver?: UserEmailResolver;
19
+ }
20
+ interface PaymentsModuleOptions {
21
+ /**
22
+ * One adapter per account in `config/saas.yaml#payments.accounts`, by that
23
+ * account's name — each built with its keys from the environment.
24
+ */
25
+ gateways: ProviderSpec<Readonly<Record<string, PaymentGateway>>>;
26
+ paymentEventLog: ProviderSpec<PaymentEventLog>;
27
+ subscriberPaymentMethodRepository: ProviderSpec<SubscriberPaymentMethodRepository>;
28
+ subscriberRepository: ProviderSpec<SubscriberRepository>;
29
+ /** The runner of the same persistence adapter the two stores above come from. */
30
+ transactionRunner: ProviderSpec<TransactionRunner>;
31
+ /**
32
+ * Mounts the tenant's billing area: `GET /billing/payment-method`,
33
+ * `POST /billing/payment-method/setup`, and `GET` and `PATCH /billing/details`.
34
+ */
35
+ tenantRoutes?: PaymentsTenantRoutesOptions;
36
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
37
+ extraProviders?: Provider[];
38
+ /**
39
+ * Default `true`: self-registration, wired in a module of its own, starts
40
+ * its setups through the registry and hands its confirmations to the
41
+ * callback service.
42
+ */
43
+ global?: boolean;
44
+ }
45
+ /**
46
+ * Payment methods through a payment gateway: the webhook route every account's
47
+ * callbacks arrive at, the registry of configured accounts, and — with
48
+ * `tenantRoutes` — the tenant's view of its payment method.
49
+ *
50
+ * Reads `config/saas.yaml#payments` from `PLAN_CATALOG_SETTINGS_TOKEN`, which
51
+ * `SaaSiCatModule.forRoot` provides globally.
52
+ */
53
+ declare class PaymentsModule {
54
+ static forRoot(options: PaymentsModuleOptions): DynamicModule;
55
+ }
56
+
57
+ export { type PaymentsModuleOptions as P, type PaymentsTenantRoutesOptions as a, PaymentsModule as b };
@@ -0,0 +1,21 @@
1
+ import { PlanCatalog } from '@saasicat/core';
2
+
3
+ /** Where the plans and features of a `PlanCatalogSource` come from. */
4
+ type PlanCatalogOrigin = 'database' | 'given';
5
+ /**
6
+ * The plan catalogue as it stands now.
7
+ *
8
+ * Read it once per operation and hand that value on. Every check the operation
9
+ * makes then sees the same plans, so a version published halfway through
10
+ * cannot price one step at the old figure and the next at the new one.
11
+ */
12
+ interface PlanCatalogSource {
13
+ /** `database` for the plans an operator publishes, `given` for a fixed catalogue. */
14
+ readonly origin: PlanCatalogOrigin;
15
+ /** The settings of the file, with the plans and features as they stand at this moment. */
16
+ current(): Promise<PlanCatalog>;
17
+ }
18
+ /** A catalogue that does not move: the one given at start, for tests and in-memory setups. */
19
+ declare function givenPlanCatalogSource(catalog: PlanCatalog): PlanCatalogSource;
20
+
21
+ export { type PlanCatalogSource as P, type PlanCatalogOrigin as a, givenPlanCatalogSource as g };
@@ -0,0 +1,21 @@
1
+ import { PlanCatalog } from '@saasicat/core';
2
+
3
+ /** Where the plans and features of a `PlanCatalogSource` come from. */
4
+ type PlanCatalogOrigin = 'database' | 'given';
5
+ /**
6
+ * The plan catalogue as it stands now.
7
+ *
8
+ * Read it once per operation and hand that value on. Every check the operation
9
+ * makes then sees the same plans, so a version published halfway through
10
+ * cannot price one step at the old figure and the next at the new one.
11
+ */
12
+ interface PlanCatalogSource {
13
+ /** `database` for the plans an operator publishes, `given` for a fixed catalogue. */
14
+ readonly origin: PlanCatalogOrigin;
15
+ /** The settings of the file, with the plans and features as they stand at this moment. */
16
+ current(): Promise<PlanCatalog>;
17
+ }
18
+ /** A catalogue that does not move: the one given at start, for tests and in-memory setups. */
19
+ declare function givenPlanCatalogSource(catalog: PlanCatalog): PlanCatalogSource;
20
+
21
+ export { type PlanCatalogSource as P, type PlanCatalogOrigin as a, givenPlanCatalogSource as g };
@@ -23,6 +23,20 @@ interface EntitlementResolutionConfig {
23
23
  * If the subscription sets a `trialEntitlementPlan`, that one wins.
24
24
  */
25
25
  defaultTrialEntitlementPlan?: PlanId;
26
+ /**
27
+ * What a subscription keeps once its cancellation has taken effect.
28
+ *
29
+ * Undefined — the default — means nothing: no features, no quotas. That is
30
+ * what ending a contract means, and it is the answer an installation should
31
+ * leave in place unless it has decided otherwise.
32
+ *
33
+ * Set it to keep a floor after the end: a read-only tier that lets a former
34
+ * customer export their data, or a free plan they fall back to. The plan is
35
+ * resolved through the catalog like any other, so it needs an active
36
+ * version; bundle bookings and custom limits are NOT added to it, because
37
+ * those belonged to the subscription that ended.
38
+ */
39
+ canceledEntitlementPlan?: PlanId;
26
40
  }
27
41
  /**
28
42
  * Resolves the effective plan of a subscription for limit aggregation.
@@ -23,6 +23,20 @@ interface EntitlementResolutionConfig {
23
23
  * If the subscription sets a `trialEntitlementPlan`, that one wins.
24
24
  */
25
25
  defaultTrialEntitlementPlan?: PlanId;
26
+ /**
27
+ * What a subscription keeps once its cancellation has taken effect.
28
+ *
29
+ * Undefined — the default — means nothing: no features, no quotas. That is
30
+ * what ending a contract means, and it is the answer an installation should
31
+ * leave in place unless it has decided otherwise.
32
+ *
33
+ * Set it to keep a floor after the end: a read-only tier that lets a former
34
+ * customer export their data, or a free plan they fall back to. The plan is
35
+ * resolved through the catalog like any other, so it needs an active
36
+ * version; bundle bookings and custom limits are NOT added to it, because
37
+ * those belonged to the subscription that ended.
38
+ */
39
+ canceledEntitlementPlan?: PlanId;
26
40
  }
27
41
  /**
28
42
  * Resolves the effective plan of a subscription for limit aggregation.
@@ -1,6 +1,6 @@
1
- import { BundleRepository, DiscoverySnapshot, SubscriptionRepository, PlanRepository, CatalogEntryRepository, BundleRow, BundleVersionRow, CreateBundleData, UpdateBundleData, CreateBundleVersionDraftData, BundleVersionMutationResult, UpdateBundleVersionDraftData, PublishBundleVersionData, MarketingProjectionRepository, MarketingProjectionFilter, MarketingProjectionRow, CreateMarketingProjectionData, UpdateMarketingProjectionData, FeatureUiRegistry, CapabilityCatalogEntryRow, FeatureCatalogEntryRow, QuotaCatalogEntryRow, ReviewCatalogEntryData, CatalogEntryI18n, UpdateCatalogEntryBaseData, SyncDiscoveryResult, PromotionRepository, PromotionRow, CreatePromotionData, UpdatePromotionData, MarketingSettingsRepository, MarketingSettingsRow, UpdateMarketingSettingsData, PublicMarketingCatalogResponse, PlanRow, CreatePlanData, UpdatePlanData, PlanVersionRow, CreatePlanVersionDraftData, PlanVersionMutationResult, UpdatePlanVersionDraftData, PublishPlanVersionData } from '@saasicat/core';
1
+ import { BundleRepository, DiscoverySnapshot, SubscriptionRepository, PlanRepository, CatalogEntryRepository, BundleRow, BundleVersionRow, CreateBundleData, UpdateBundleData, CreateBundleVersionDraftData, BundleVersionMutationResult, UpdateBundleVersionDraftData, PublishBundleVersionData, MarketingProjectionRepository, MarketingProjectionFilter, MarketingProjectionRow, CreateMarketingProjectionData, UpdateMarketingProjectionData, FeatureUiRegistry, CapabilityCatalogEntryRow, FeatureCatalogEntryRow, QuotaCatalogEntryRow, ReviewCatalogEntryData, CatalogEntryI18n, UpdateCatalogEntryBaseData, SyncDiscoveryResult, PromotionRepository, PromotionRow, CreatePromotionData, UpdatePromotionData, MarketingSettingsRepository, MarketingSettingsRow, UpdateMarketingSettingsData, PlanRow, CreatePlanData, UpdatePlanData, PlanVersionRow, CreatePlanVersionDraftData, PlanVersionMutationResult, UpdatePlanVersionDraftData, PublishPlanVersionData } from '@saasicat/core';
2
2
  import { OnApplicationBootstrap } from '@nestjs/common';
3
- import { c as DiscoveryScanner } from './discovery.scanner-9cMqF95o.js';
3
+ import { a as DiscoveryScanner } from './discovery.scanner-DXKc6JkV.js';
4
4
 
5
5
  /**
6
6
  * Configuration for the mutating operations.
@@ -36,7 +36,7 @@ declare class BundlesService {
36
36
  private mode;
37
37
  private readonly marketedOnly;
38
38
  constructor(repo: BundleRepository, snapshot?: DiscoverySnapshot | null, config?: CatalogServiceConfig, subscriptions?: SubscriptionRepository | null, planRepo?: PlanRepository | null, scanner?: DiscoveryScanner | null, catalogEntries?: CatalogEntryRepository | null);
39
- listBundles(projectKey: string): Promise<BundleRow[]>;
39
+ listBundles(): Promise<BundleRow[]>;
40
40
  getBundle(bundleId: string): Promise<{
41
41
  bundle: BundleRow;
42
42
  versions: BundleVersionRow[];
@@ -90,6 +90,26 @@ declare class BundlesService {
90
90
  */
91
91
  publishBundleVersion(versionId: string, publishMeta: PublishBundleVersionData): Promise<BundleVersionMutationResult>;
92
92
  private runStrictCheck;
93
+ /**
94
+ * Refuses a version that a plan it is offered to could not actually buy.
95
+ *
96
+ * "Some price resolves" is not the same as "this plan, in the rhythm it is
97
+ * sold in, resolves one". A bundle priced monthly only, offered to a plan
98
+ * that is sold yearly, passes the first test and fails the second — and the
99
+ * failure surfaces at a tenant's checkout rather than at the operator's
100
+ * desk, which is the wrong end of the mistake.
101
+ *
102
+ * The expectation comes from the catalog, not from a list: the plans are
103
+ * whichever the compatibility declares (empty = every plan), and the cycles
104
+ * are whichever each plan version carries a price for. Add a third cycle to
105
+ * the catalog one day and this asks about it without being told.
106
+ *
107
+ * Skipped where no plan repository is bound — a consumer without one has no
108
+ * catalog to derive from, and guessing would refuse valid bundles.
109
+ */
110
+ private refuseUnsellableToACompatiblePlan;
111
+ /** The live version of a plan, or null where the port cannot answer. */
112
+ private liveVersionOf;
93
113
  private gateOrPass;
94
114
  private assertBundleVersionWindowUpdate;
95
115
  private findPreviousBundleVersion;
@@ -123,25 +143,25 @@ declare class CatalogEntriesService implements OnApplicationBootstrap {
123
143
  * `POST /admin/catalog/discovery/sync` remains as a fallback.
124
144
  */
125
145
  onApplicationBootstrap(): Promise<void>;
126
- listCapabilities(projectKey: string, codeStatus?: string): Promise<CapabilityCatalogEntryRow[]>;
127
- listFeatures(projectKey: string, discoveryStatus?: string): Promise<FeatureCatalogEntryRow[]>;
128
- listQuotas(projectKey: string, discoveryStatus?: string): Promise<QuotaCatalogEntryRow[]>;
146
+ listCapabilities(codeStatus?: string): Promise<CapabilityCatalogEntryRow[]>;
147
+ listFeatures(discoveryStatus?: string): Promise<FeatureCatalogEntryRow[]>;
148
+ listQuotas(discoveryStatus?: string): Promise<QuotaCatalogEntryRow[]>;
129
149
  /**
130
150
  * Approval transition of a feature (#20). Validates the state machine;
131
151
  * `approved` persists the approval signature from the current
132
152
  * snapshot, `pending` (revoke/reactivate) deletes it, `outdated`/
133
153
  * `obsolete` keep the last approval as history.
134
154
  */
135
- reviewFeature(projectKey: string, featureKey: string, data: ReviewCatalogEntryData, reviewedBy: string | null): Promise<FeatureCatalogEntryRow>;
155
+ reviewFeature(featureKey: string, data: ReviewCatalogEntryData, reviewedBy: string | null): Promise<FeatureCatalogEntryRow>;
136
156
  /** Approval transition of a quota — same state machine as features (#20). */
137
- reviewQuota(projectKey: string, quotaKey: string, data: ReviewCatalogEntryData, reviewedBy: string | null): Promise<QuotaCatalogEntryRow>;
157
+ reviewQuota(quotaKey: string, data: ReviewCatalogEntryData, reviewedBy: string | null): Promise<QuotaCatalogEntryRow>;
138
158
  private resolveReviewUpdate;
139
- setFeatureI18n(projectKey: string, featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
140
- setQuotaI18n(projectKey: string, quotaKey: string, i18n: CatalogEntryI18n): Promise<QuotaCatalogEntryRow>;
159
+ setFeatureI18n(featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
160
+ setQuotaI18n(quotaKey: string, i18n: CatalogEntryI18n): Promise<QuotaCatalogEntryRow>;
141
161
  /** Sets the editable default-locale label/description of a feature. */
142
- setFeatureBase(projectKey: string, featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
162
+ setFeatureBase(featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
143
163
  /** Sets the editable default-locale label/description of a quota. */
144
- setQuotaBase(projectKey: string, quotaKey: string, data: UpdateCatalogEntryBaseData): Promise<QuotaCatalogEntryRow>;
164
+ setQuotaBase(quotaKey: string, data: UpdateCatalogEntryBaseData): Promise<QuotaCatalogEntryRow>;
145
165
  /**
146
166
  * Synchronizes the catalog entries from a discovery snapshot.
147
167
  * Idempotent — can run against the same snapshot any number of times.
@@ -161,7 +181,7 @@ declare class CatalogEntriesService implements OnApplicationBootstrap {
161
181
  declare class PromotionsService {
162
182
  private readonly repo;
163
183
  constructor(repo: PromotionRepository);
164
- list(projectKey: string): Promise<PromotionRow[]>;
184
+ list(): Promise<PromotionRow[]>;
165
185
  getById(id: string): Promise<PromotionRow>;
166
186
  create(data: CreatePromotionData): Promise<PromotionRow>;
167
187
  update(id: string, data: UpdatePromotionData): Promise<PromotionRow>;
@@ -172,54 +192,8 @@ declare class MarketingSettingsService {
172
192
  private readonly repo;
173
193
  constructor(repo: MarketingSettingsRepository);
174
194
  /** Returns the stored config or `null` (in which case the full pool applies). */
175
- get(projectKey: string): Promise<MarketingSettingsRow | null>;
176
- upsert(projectKey: string, data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
177
- }
178
-
179
- declare class PublicMarketingCatalogService {
180
- private readonly planRepo;
181
- private readonly marketingRepo;
182
- private readonly promotionRepo;
183
- private readonly catalogEntryRepo;
184
- private readonly bundleRepo;
185
- constructor(planRepo: PlanRepository, marketingRepo: MarketingProjectionRepository, promotionRepo: PromotionRepository, catalogEntryRepo?: CatalogEntryRepository | null, bundleRepo?: BundleRepository | null);
186
- getCatalog(projectKey: string, locale: string, currency: string, vatRate: number, asOf?: Date): Promise<PublicMarketingCatalogResponse>;
187
- /**
188
- * Loads all published-and-live bundle versions for a project and
189
- * filters on `marketed === true`. Without a registered
190
- * `BundleRepository` (apps without the SuperAdmin bundle editor) the
191
- * list is empty — the public catalog stays plan-only.
192
- */
193
- private loadMarketedBundles;
194
- /**
195
- * Maps BundleVersionRow → PublicMarketingBundle. Locale resolution
196
- * runs via `MarketingProjectionRepository.findByTarget('BUNDLE',
197
- * bundleVersionId, locale)` — on a miss it falls back to
198
- * `DEFAULT_LOCALE`. If the fallback is missing too, label/description
199
- * come directly from the bundle root (denormalized in `BundleVersionRow`).
200
- *
201
- * If the marketing projection is `visible === false`, the bundle is
202
- * hidden from the public response (preparation phase).
203
- */
204
- private toPublicBundle;
205
- /**
206
- * Labels for bundle features ∪ their requires (#48): `comparison.features`
207
- * only covers the plan-feature union — bundle-only features would
208
- * otherwise get no label in the public payload. Curated keys only;
209
- * without a CatalogEntryRepository the map stays empty (frontends fall
210
- * back to the key).
211
- */
212
- private resolveBundleFeatureLabels;
213
- /** Loads the translated feature/quota labels (+ sortOrder) for a locale. */
214
- private loadLabelMeta;
215
- /**
216
- * Resolves the displayed label of a top feature: the `label` override
217
- * wins; otherwise the translated label of the referenced `key`.
218
- */
219
- private resolveTopFeature;
220
- /** Row definitions of the comparison matrix (feature/quota union + labels). */
221
- private buildComparison;
222
- private resolvePromo;
195
+ get(): Promise<MarketingSettingsRow | null>;
196
+ upsert(data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
223
197
  }
224
198
 
225
199
  declare class PlansService {
@@ -233,8 +207,8 @@ declare class PlansService {
233
207
  * `SubscriptionRepository` is registered or it does not implement
234
208
  * `countActiveByPlanKey`.
235
209
  */
236
- getTenantCounts(projectKey: string): Promise<Record<string, number>>;
237
- listPlans(projectKey: string, opts?: {
210
+ getTenantCounts(): Promise<Record<string, number>>;
211
+ listPlans(opts?: {
238
212
  onlyPublished?: boolean;
239
213
  }): Promise<PlanRow[]>;
240
214
  getPlan(planId: string): Promise<PlanRow>;
@@ -326,4 +300,4 @@ declare class PlanVersionsService {
326
300
  private gateOrPass;
327
301
  }
328
302
 
329
- export { BundlesService as B, CatalogEntriesService as C, MarketingProjectionsService as M, PlanVersionsService as P, type CatalogServiceConfig as a, MarketingSettingsService as b, PlansService as c, PromotionsService as d, PublicMarketingCatalogService as e };
303
+ export { BundlesService as B, CatalogEntriesService as C, MarketingProjectionsService as M, PlanVersionsService as P, type CatalogServiceConfig as a, MarketingSettingsService as b, PlansService as c, PromotionsService as d };