@saasicat/nest 0.2.0

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 (96) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +53 -0
  3. package/dist/admin/index.cjs +1283 -0
  4. package/dist/admin/index.d.cts +225 -0
  5. package/dist/admin/index.d.ts +225 -0
  6. package/dist/admin/index.js +58 -0
  7. package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
  8. package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
  9. package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
  10. package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
  11. package/dist/aggregation-CJ3qQf92.d.cts +288 -0
  12. package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
  13. package/dist/billing/index.cjs +5205 -0
  14. package/dist/billing/index.d.cts +1326 -0
  15. package/dist/billing/index.d.ts +1326 -0
  16. package/dist/billing/index.js +197 -0
  17. package/dist/catalog/index.cjs +6449 -0
  18. package/dist/catalog/index.d.cts +1073 -0
  19. package/dist/catalog/index.d.ts +1073 -0
  20. package/dist/catalog/index.js +158 -0
  21. package/dist/checkout-offer/index.cjs +807 -0
  22. package/dist/checkout-offer/index.d.cts +120 -0
  23. package/dist/checkout-offer/index.d.ts +120 -0
  24. package/dist/checkout-offer/index.js +20 -0
  25. package/dist/chunk-2FR6ZL7R.js +0 -0
  26. package/dist/chunk-2PNX2QL2.js +347 -0
  27. package/dist/chunk-2VNZYRDV.js +326 -0
  28. package/dist/chunk-7MLGHWKO.js +12 -0
  29. package/dist/chunk-ABB2CRAS.js +31 -0
  30. package/dist/chunk-AJ3EPELS.js +157 -0
  31. package/dist/chunk-E56W4U2P.js +90 -0
  32. package/dist/chunk-HRU3CP57.js +71 -0
  33. package/dist/chunk-L4SUWH5B.js +14 -0
  34. package/dist/chunk-M6CZQYPY.js +314 -0
  35. package/dist/chunk-MDIZUVIK.js +87 -0
  36. package/dist/chunk-MV6LW5UB.js +217 -0
  37. package/dist/chunk-NYLON2VC.js +6 -0
  38. package/dist/chunk-P6MYZMXQ.js +951 -0
  39. package/dist/chunk-Q53N43LQ.js +13 -0
  40. package/dist/chunk-QJVPRD3R.js +573 -0
  41. package/dist/chunk-RIGLQ4BV.js +3367 -0
  42. package/dist/chunk-SABTXESR.js +24 -0
  43. package/dist/chunk-SHUYVCID.js +6 -0
  44. package/dist/chunk-SYEVGPFC.js +686 -0
  45. package/dist/chunk-V7HUZ6UQ.js +5923 -0
  46. package/dist/chunk-WPUVJRIN.js +29 -0
  47. package/dist/chunk-XCZ3TTFK.js +22 -0
  48. package/dist/chunk-XSGU2Y2O.js +1611 -0
  49. package/dist/chunk-YNEW3L63.js +190 -0
  50. package/dist/chunk-YXGHPILG.js +614 -0
  51. package/dist/chunk-ZL4LLE2Z.js +465 -0
  52. package/dist/di-CcNeq9v-.d.cts +16 -0
  53. package/dist/di-CcNeq9v-.d.ts +16 -0
  54. package/dist/discovery/index.cjs +588 -0
  55. package/dist/discovery/index.d.cts +299 -0
  56. package/dist/discovery/index.d.ts +299 -0
  57. package/dist/discovery/index.js +49 -0
  58. package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
  59. package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
  60. package/dist/entitlement/index.cjs +805 -0
  61. package/dist/entitlement/index.d.cts +60 -0
  62. package/dist/entitlement/index.d.ts +60 -0
  63. package/dist/entitlement/index.js +57 -0
  64. package/dist/index.cjs +15918 -0
  65. package/dist/index.d.cts +85 -0
  66. package/dist/index.d.ts +85 -0
  67. package/dist/index.js +956 -0
  68. package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
  69. package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
  70. package/dist/platform/index.cjs +2772 -0
  71. package/dist/platform/index.d.cts +118 -0
  72. package/dist/platform/index.d.ts +118 -0
  73. package/dist/platform/index.js +37 -0
  74. package/dist/promo/index.cjs +995 -0
  75. package/dist/promo/index.d.cts +121 -0
  76. package/dist/promo/index.d.ts +121 -0
  77. package/dist/promo/index.js +58 -0
  78. package/dist/registration/index.cjs +1687 -0
  79. package/dist/registration/index.d.cts +444 -0
  80. package/dist/registration/index.d.ts +444 -0
  81. package/dist/registration/index.js +82 -0
  82. package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
  83. package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
  84. package/dist/service-DX8KbGXl.d.cts +109 -0
  85. package/dist/service-DX8KbGXl.d.ts +109 -0
  86. package/dist/subscription-contract/index.cjs +471 -0
  87. package/dist/subscription-contract/index.d.cts +17 -0
  88. package/dist/subscription-contract/index.d.ts +17 -0
  89. package/dist/subscription-contract/index.js +21 -0
  90. package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
  91. package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
  92. package/dist/testing/index.cjs +3942 -0
  93. package/dist/testing/index.d.cts +279 -0
  94. package/dist/testing/index.d.ts +279 -0
  95. package/dist/testing/index.js +1195 -0
  96. package/package.json +185 -0
@@ -0,0 +1,180 @@
1
+ import { Type, CanActivate, DynamicModule, ForwardReference, FactoryProvider } from '@nestjs/common';
2
+ import { MfaPort, AuditPort, RlsBypassPort, PlanCatalogReadSink, SubscriptionRepository, PlanVersionRepository, TransactionRunner, PlanCatalog, QuotaProvider } from '@saasicat/types';
3
+ import { P as ProviderSpec } from './di-CcNeq9v-.cjs';
4
+ import { A as AdminManifestConfig } from './admin-manifest.config-DyrQNT7M.cjs';
5
+ import { D as DiscoveryAppInfo } from './discovery.scanner-CUYLKlYT.cjs';
6
+ import { E as EntitlementResolutionConfig } from './plan-resolution-CFCoUkrE.cjs';
7
+
8
+ declare const PLAN_RESOLVER_PORT_TOKEN: unique symbol;
9
+ interface PlanResolverPort {
10
+ /**
11
+ * Returns the tenant's active `planId`. `null` = no plan assigned →
12
+ * the guard / interceptor throws 403 or lets it through, depending on
13
+ * the configuration.
14
+ */
15
+ getPlanIdForTenant(tenantId: string): Promise<string | null>;
16
+ }
17
+ /**
18
+ * Static fallback: always returns the same plan for all tenants. Useful
19
+ * for local development and for apps that do not yet have a contract concept.
20
+ *
21
+ * The quickstart path uses this implicitly when `adapters.planResolver` is
22
+ * not set and `defaultPlanId` was provided in SaasPlatformModule.forRoot().
23
+ */
24
+ declare class StaticPlanResolver implements PlanResolverPort {
25
+ private readonly planId;
26
+ constructor(planId: string);
27
+ getPlanIdForTenant(): Promise<string | null>;
28
+ }
29
+
30
+ interface TenantManifestControllerOptions {
31
+ path?: string;
32
+ guards: Array<Type<CanActivate>>;
33
+ }
34
+ /**
35
+ * Creates a controller class that maps `GET <path>` to
36
+ * `TenantManifestService.getManifest(tenantId)`. `tenantId` comes from
37
+ * `request.user.tenantId` (default — matches the JwtAuthGuard pattern of
38
+ * most apps) or from `request.tenantId`.
39
+ *
40
+ * Route path: default `tenant/manifest`. Apps with a globalPrefix `/api/v1`
41
+ * thus get `/api/v1/tenant/manifest`.
42
+ */
43
+ declare function buildTenantManifestController(options: TenantManifestControllerOptions): Type<unknown>;
44
+
45
+ /**
46
+ * Adapter bindings for the platform ports. Accepted as class tokens, values
47
+ * or factory specs.
48
+ */
49
+ interface SaasPlatformAdapters {
50
+ mfa: ProviderSpec<MfaPort>;
51
+ audit: ProviderSpec<AuditPort>;
52
+ rlsBypass: ProviderSpec<RlsBypassPort>;
53
+ /**
54
+ * Optional. If provided, `PlanCatalogModule` is hydrated from this sink
55
+ * (DB read at boot). If omitted, `planCatalog` MUST be passed as a ready
56
+ * object (quickstart path — YAML-direct).
57
+ */
58
+ planCatalogReadSink?: ProviderSpec<PlanCatalogReadSink>;
59
+ /**
60
+ * Optional — resolver `tenantId → planId`. The quickstart path uses this
61
+ * together with the `StaticEntitlementService` to automatically check
62
+ * `@RequireFeature` and `@EnforceQuota` against the plan catalog limit.
63
+ * If not set, `defaultPlanId` **must** be provided — a `StaticPlanResolver`
64
+ * then returns the same plan for all tenants.
65
+ */
66
+ planResolver?: ProviderSpec<PlanResolverPort>;
67
+ /**
68
+ * Optional — required only when `entitlement: true`. Repositories for the
69
+ * V3 contract/entitlement loop.
70
+ */
71
+ subscriptionRepository?: ProviderSpec<SubscriptionRepository>;
72
+ planVersionRepository?: ProviderSpec<PlanVersionRepository>;
73
+ transactionRunner?: ProviderSpec<TransactionRunner>;
74
+ }
75
+ interface SaasPlatformModuleOptions {
76
+ /**
77
+ * Plan catalog. Either as an already-loaded object (quickstart, comes
78
+ * directly from `loadPlanCatalogFromFile('config/saas.yaml')`) or as a
79
+ * sink reference in `adapters.planCatalogReadSink` for DB hydration.
80
+ */
81
+ planCatalog?: PlanCatalog;
82
+ /**
83
+ * Adapter bindings.
84
+ */
85
+ adapters: SaasPlatformAdapters;
86
+ /**
87
+ * Class-level guards for the platform controllers (`GET /admin/discovery`
88
+ * and `GET /admin/manifest`). REQUIRED — otherwise the platform throws at
89
+ * boot, because a manifest controller must never be silently registered
90
+ * without auth (platform security).
91
+ *
92
+ * Pass `[]` explicitly if the endpoint is intentionally auth-free
93
+ * (CI/smoke test).
94
+ */
95
+ controller: {
96
+ guards: Array<Type<CanActivate>>;
97
+ };
98
+ /**
99
+ * Additional guards only for `POST /admin/manifest/reload` (typically:
100
+ * `MfaGuard`). Optional.
101
+ */
102
+ reloadGuards?: Array<Type<CanActivate>>;
103
+ /**
104
+ * Modules whose providers must be visible in the DI scope (typically:
105
+ * `AuthModule` with the `JwtAuthGuard`).
106
+ */
107
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
108
+ /**
109
+ * App identity for the DiscoveryScanner. If omitted, `planCatalog.app` is
110
+ * used (recommendation: simply declare it in the YAML).
111
+ */
112
+ app?: DiscoveryAppInfo;
113
+ /**
114
+ * Optional — snapshot path for the DiscoveryScanner. Default:
115
+ * `var/discovery-snapshot.json`. `null` to disable.
116
+ */
117
+ discoverySnapshotPath?: string | null;
118
+ /**
119
+ * `AdminManifestConfig`. If omitted, the module assembles a minimal variant
120
+ * from `planCatalog` — good for quickstart, but for full manifest features
121
+ * (build hash, locales, KPI cards) the consumer should provide its own
122
+ * factory.
123
+ */
124
+ adminManifestConfig?: AdminManifestConfig | Pick<FactoryProvider<AdminManifestConfig>, 'useFactory' | 'inject'>;
125
+ /**
126
+ * Default `false`. If `true`, `EntitlementModule.forRoot({...})` is called
127
+ * with the repositories from `adapters` — only meaningful if the app
128
+ * implements the V3 contract path (`subscriptionRepository` & co. must then
129
+ * be set).
130
+ */
131
+ entitlement?: false | {
132
+ resolutionConfig?: EntitlementResolutionConfig;
133
+ };
134
+ /**
135
+ * Fallback plan ID for the `StaticPlanResolver`. If neither
136
+ * `adapters.planResolver` nor `defaultPlanId` is set, the
137
+ * `StaticEntitlementService` is not activated — `@RequireFeature`/
138
+ * `@EnforceQuota` are then **ineffective** (discovery markup with no
139
+ * runtime effect). Platform warning at boot.
140
+ */
141
+ defaultPlanId?: string;
142
+ /**
143
+ * QuotaProvider classes declared with `@DefinesQuota({...})` that the
144
+ * `EnforceQuotaInterceptor` must use for count calculation. The platform
145
+ * registers them as app providers and collects them in
146
+ * `QUOTA_PROVIDERS_TOKEN`.
147
+ */
148
+ quotaProviders?: Array<Type<QuotaProvider>>;
149
+ /**
150
+ * Enable the tenant manifest — the app UI gets a filtered manifest per
151
+ * tenant with features, quotas and visible navigation. Requires that
152
+ * `defaultPlanId` or `adapters.planResolver` is set.
153
+ */
154
+ tenantManifest?: TenantManifestControllerOptions;
155
+ }
156
+ /**
157
+ * Bundles PlanCatalog + Discovery + Admin + AdminManifest (+ optionally
158
+ * Entitlement) into a single `forRoot({...})` call. Reduces AppModule
159
+ * boilerplate and eliminates the ordering trap.
160
+ *
161
+ * Quickstart path:
162
+ *
163
+ * ```ts
164
+ * SaasPlatformModule.forRoot({
165
+ * planCatalog: loadPlanCatalogFromFile({ path: 'config/saas.yaml' }),
166
+ * controller: { guards: [JwtAuthGuard] },
167
+ * imports: [AuthModule],
168
+ * adapters: {
169
+ * mfa: PrismaMfaAdapter, // from @saasicat/prisma
170
+ * audit: PrismaAuditAdapter,
171
+ * rlsBypass: AsyncLocalRlsBypassAdapter,
172
+ * },
173
+ * })
174
+ * ```
175
+ */
176
+ declare class SaasPlatformModule {
177
+ static forRoot(options: SaasPlatformModuleOptions): DynamicModule;
178
+ }
179
+
180
+ export { type PlanResolverPort as P, type SaasPlatformModuleOptions as S, type TenantManifestControllerOptions as T, PLAN_RESOLVER_PORT_TOKEN as a, type SaasPlatformAdapters as b, SaasPlatformModule as c, StaticPlanResolver as d, buildTenantManifestController as e };
@@ -0,0 +1,180 @@
1
+ import { Type, CanActivate, DynamicModule, ForwardReference, FactoryProvider } from '@nestjs/common';
2
+ import { MfaPort, AuditPort, RlsBypassPort, PlanCatalogReadSink, SubscriptionRepository, PlanVersionRepository, TransactionRunner, PlanCatalog, QuotaProvider } from '@saasicat/types';
3
+ import { P as ProviderSpec } from './di-CcNeq9v-.js';
4
+ import { A as AdminManifestConfig } from './admin-manifest.config-DyrQNT7M.js';
5
+ import { D as DiscoveryAppInfo } from './discovery.scanner-CUYLKlYT.js';
6
+ import { E as EntitlementResolutionConfig } from './plan-resolution-CFCoUkrE.js';
7
+
8
+ declare const PLAN_RESOLVER_PORT_TOKEN: unique symbol;
9
+ interface PlanResolverPort {
10
+ /**
11
+ * Returns the tenant's active `planId`. `null` = no plan assigned →
12
+ * the guard / interceptor throws 403 or lets it through, depending on
13
+ * the configuration.
14
+ */
15
+ getPlanIdForTenant(tenantId: string): Promise<string | null>;
16
+ }
17
+ /**
18
+ * Static fallback: always returns the same plan for all tenants. Useful
19
+ * for local development and for apps that do not yet have a contract concept.
20
+ *
21
+ * The quickstart path uses this implicitly when `adapters.planResolver` is
22
+ * not set and `defaultPlanId` was provided in SaasPlatformModule.forRoot().
23
+ */
24
+ declare class StaticPlanResolver implements PlanResolverPort {
25
+ private readonly planId;
26
+ constructor(planId: string);
27
+ getPlanIdForTenant(): Promise<string | null>;
28
+ }
29
+
30
+ interface TenantManifestControllerOptions {
31
+ path?: string;
32
+ guards: Array<Type<CanActivate>>;
33
+ }
34
+ /**
35
+ * Creates a controller class that maps `GET <path>` to
36
+ * `TenantManifestService.getManifest(tenantId)`. `tenantId` comes from
37
+ * `request.user.tenantId` (default — matches the JwtAuthGuard pattern of
38
+ * most apps) or from `request.tenantId`.
39
+ *
40
+ * Route path: default `tenant/manifest`. Apps with a globalPrefix `/api/v1`
41
+ * thus get `/api/v1/tenant/manifest`.
42
+ */
43
+ declare function buildTenantManifestController(options: TenantManifestControllerOptions): Type<unknown>;
44
+
45
+ /**
46
+ * Adapter bindings for the platform ports. Accepted as class tokens, values
47
+ * or factory specs.
48
+ */
49
+ interface SaasPlatformAdapters {
50
+ mfa: ProviderSpec<MfaPort>;
51
+ audit: ProviderSpec<AuditPort>;
52
+ rlsBypass: ProviderSpec<RlsBypassPort>;
53
+ /**
54
+ * Optional. If provided, `PlanCatalogModule` is hydrated from this sink
55
+ * (DB read at boot). If omitted, `planCatalog` MUST be passed as a ready
56
+ * object (quickstart path — YAML-direct).
57
+ */
58
+ planCatalogReadSink?: ProviderSpec<PlanCatalogReadSink>;
59
+ /**
60
+ * Optional — resolver `tenantId → planId`. The quickstart path uses this
61
+ * together with the `StaticEntitlementService` to automatically check
62
+ * `@RequireFeature` and `@EnforceQuota` against the plan catalog limit.
63
+ * If not set, `defaultPlanId` **must** be provided — a `StaticPlanResolver`
64
+ * then returns the same plan for all tenants.
65
+ */
66
+ planResolver?: ProviderSpec<PlanResolverPort>;
67
+ /**
68
+ * Optional — required only when `entitlement: true`. Repositories for the
69
+ * V3 contract/entitlement loop.
70
+ */
71
+ subscriptionRepository?: ProviderSpec<SubscriptionRepository>;
72
+ planVersionRepository?: ProviderSpec<PlanVersionRepository>;
73
+ transactionRunner?: ProviderSpec<TransactionRunner>;
74
+ }
75
+ interface SaasPlatformModuleOptions {
76
+ /**
77
+ * Plan catalog. Either as an already-loaded object (quickstart, comes
78
+ * directly from `loadPlanCatalogFromFile('config/saas.yaml')`) or as a
79
+ * sink reference in `adapters.planCatalogReadSink` for DB hydration.
80
+ */
81
+ planCatalog?: PlanCatalog;
82
+ /**
83
+ * Adapter bindings.
84
+ */
85
+ adapters: SaasPlatformAdapters;
86
+ /**
87
+ * Class-level guards for the platform controllers (`GET /admin/discovery`
88
+ * and `GET /admin/manifest`). REQUIRED — otherwise the platform throws at
89
+ * boot, because a manifest controller must never be silently registered
90
+ * without auth (platform security).
91
+ *
92
+ * Pass `[]` explicitly if the endpoint is intentionally auth-free
93
+ * (CI/smoke test).
94
+ */
95
+ controller: {
96
+ guards: Array<Type<CanActivate>>;
97
+ };
98
+ /**
99
+ * Additional guards only for `POST /admin/manifest/reload` (typically:
100
+ * `MfaGuard`). Optional.
101
+ */
102
+ reloadGuards?: Array<Type<CanActivate>>;
103
+ /**
104
+ * Modules whose providers must be visible in the DI scope (typically:
105
+ * `AuthModule` with the `JwtAuthGuard`).
106
+ */
107
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
108
+ /**
109
+ * App identity for the DiscoveryScanner. If omitted, `planCatalog.app` is
110
+ * used (recommendation: simply declare it in the YAML).
111
+ */
112
+ app?: DiscoveryAppInfo;
113
+ /**
114
+ * Optional — snapshot path for the DiscoveryScanner. Default:
115
+ * `var/discovery-snapshot.json`. `null` to disable.
116
+ */
117
+ discoverySnapshotPath?: string | null;
118
+ /**
119
+ * `AdminManifestConfig`. If omitted, the module assembles a minimal variant
120
+ * from `planCatalog` — good for quickstart, but for full manifest features
121
+ * (build hash, locales, KPI cards) the consumer should provide its own
122
+ * factory.
123
+ */
124
+ adminManifestConfig?: AdminManifestConfig | Pick<FactoryProvider<AdminManifestConfig>, 'useFactory' | 'inject'>;
125
+ /**
126
+ * Default `false`. If `true`, `EntitlementModule.forRoot({...})` is called
127
+ * with the repositories from `adapters` — only meaningful if the app
128
+ * implements the V3 contract path (`subscriptionRepository` & co. must then
129
+ * be set).
130
+ */
131
+ entitlement?: false | {
132
+ resolutionConfig?: EntitlementResolutionConfig;
133
+ };
134
+ /**
135
+ * Fallback plan ID for the `StaticPlanResolver`. If neither
136
+ * `adapters.planResolver` nor `defaultPlanId` is set, the
137
+ * `StaticEntitlementService` is not activated — `@RequireFeature`/
138
+ * `@EnforceQuota` are then **ineffective** (discovery markup with no
139
+ * runtime effect). Platform warning at boot.
140
+ */
141
+ defaultPlanId?: string;
142
+ /**
143
+ * QuotaProvider classes declared with `@DefinesQuota({...})` that the
144
+ * `EnforceQuotaInterceptor` must use for count calculation. The platform
145
+ * registers them as app providers and collects them in
146
+ * `QUOTA_PROVIDERS_TOKEN`.
147
+ */
148
+ quotaProviders?: Array<Type<QuotaProvider>>;
149
+ /**
150
+ * Enable the tenant manifest — the app UI gets a filtered manifest per
151
+ * tenant with features, quotas and visible navigation. Requires that
152
+ * `defaultPlanId` or `adapters.planResolver` is set.
153
+ */
154
+ tenantManifest?: TenantManifestControllerOptions;
155
+ }
156
+ /**
157
+ * Bundles PlanCatalog + Discovery + Admin + AdminManifest (+ optionally
158
+ * Entitlement) into a single `forRoot({...})` call. Reduces AppModule
159
+ * boilerplate and eliminates the ordering trap.
160
+ *
161
+ * Quickstart path:
162
+ *
163
+ * ```ts
164
+ * SaasPlatformModule.forRoot({
165
+ * planCatalog: loadPlanCatalogFromFile({ path: 'config/saas.yaml' }),
166
+ * controller: { guards: [JwtAuthGuard] },
167
+ * imports: [AuthModule],
168
+ * adapters: {
169
+ * mfa: PrismaMfaAdapter, // from @saasicat/prisma
170
+ * audit: PrismaAuditAdapter,
171
+ * rlsBypass: AsyncLocalRlsBypassAdapter,
172
+ * },
173
+ * })
174
+ * ```
175
+ */
176
+ declare class SaasPlatformModule {
177
+ static forRoot(options: SaasPlatformModuleOptions): DynamicModule;
178
+ }
179
+
180
+ export { type PlanResolverPort as P, type SaasPlatformModuleOptions as S, type TenantManifestControllerOptions as T, PLAN_RESOLVER_PORT_TOKEN as a, type SaasPlatformAdapters as b, SaasPlatformModule as c, StaticPlanResolver as d, buildTenantManifestController as e };
@@ -0,0 +1,109 @@
1
+ import { PromoCodeRepository, PromoCodeRedemptionRepository, PromoCodeValidationLogRepository, FirstTimeCustomerCheck, PromoSubscriptionLookup, PromoRevenueDeductionAggregator, TransactionRunner, PlanCatalog, CreatePromoCodeData, PromoCodeRecord, UpdatePromoCodeData, PromoCodeFilter, PromoCodeRedemptionListItem, BillingCycle, PromoCodeRedemptionRecord, TransactionContext } from '@saasicat/types';
2
+
3
+ type PreviewReason = 'NOT_FOUND' | 'EXPIRED' | 'EXHAUSTED' | 'PAUSED' | 'PLAN_MISMATCH' | 'BILLING_MISMATCH' | 'BELOW_MINIMUM_AMOUNT' | 'WOULD_PRODUCE_ZERO_INVOICE' | 'NOT_FIRST_TIME_CUSTOMER' | 'RATE_LIMITED';
4
+ type PreviewInvalid = {
5
+ valid: false;
6
+ reason: PreviewReason;
7
+ };
8
+ interface PreviewValid {
9
+ valid: true;
10
+ code: string;
11
+ label: string;
12
+ discount: {
13
+ valueType: PromoCodeRecord['valueType'];
14
+ value: string;
15
+ durationType: PromoCodeRecord['durationType'];
16
+ durationValue: number | null;
17
+ };
18
+ price: {
19
+ originalGross: string;
20
+ discountGross: string;
21
+ discountedGross: string;
22
+ includedVat: string;
23
+ nextRegularAmountGross: string;
24
+ regularStartsAt: string | null;
25
+ };
26
+ }
27
+ type PreviewResult = PreviewValid | PreviewInvalid;
28
+ interface PreviewInput {
29
+ code: string;
30
+ planId: string;
31
+ billingCycle: BillingCycle;
32
+ /** For the firstTimeCustomersOnly check. */
33
+ email?: string;
34
+ ipHash?: string;
35
+ sessionId?: string;
36
+ }
37
+ interface RedeemInput {
38
+ code: string;
39
+ subscriptionId: string;
40
+ tenantId: string;
41
+ /** For the firstTimeCustomersOnly check in the final redeem path. */
42
+ email?: string;
43
+ }
44
+ interface PromoServiceConfig {
45
+ /** Plans that are not discountable (e.g. 'ENTERPRISE'). */
46
+ nonRedeemablePlans?: readonly string[];
47
+ }
48
+ interface PromoCodeStats {
49
+ code: PromoCodeRecord;
50
+ validations: number;
51
+ redemptions: {
52
+ active: number;
53
+ total: number;
54
+ reversed: number;
55
+ expired: number;
56
+ };
57
+ /** Decimal-as-string ('0.00' if no aggregator is available). */
58
+ revenueDeductionGross: string;
59
+ }
60
+ declare class PromoCodesService {
61
+ private readonly promoRepo;
62
+ private readonly redemptionRepo;
63
+ private readonly validationLogRepo;
64
+ private readonly firstTimeCheck;
65
+ private readonly subscriptionLookup;
66
+ private readonly revenueAggregator;
67
+ private readonly transactionRunner;
68
+ private readonly planCatalog;
69
+ private readonly config;
70
+ constructor(promoRepo: PromoCodeRepository, redemptionRepo: PromoCodeRedemptionRepository, validationLogRepo: PromoCodeValidationLogRepository, firstTimeCheck: FirstTimeCustomerCheck, subscriptionLookup: PromoSubscriptionLookup, revenueAggregator: PromoRevenueDeductionAggregator, transactionRunner: TransactionRunner, planCatalog: PlanCatalog, config: PromoServiceConfig);
71
+ create(input: CreatePromoCodeData): Promise<PromoCodeRecord>;
72
+ update(id: string, input: UpdatePromoCodeData): Promise<PromoCodeRecord>;
73
+ softDelete(id: string): Promise<void>;
74
+ findAll(filter?: PromoCodeFilter): Promise<PromoCodeRecord[]>;
75
+ findOne(id: string): Promise<PromoCodeRecord>;
76
+ stats(id: string): Promise<PromoCodeStats>;
77
+ listRedemptions(promoCodeId: string): Promise<PromoCodeRedemptionListItem[]>;
78
+ preview(input: PreviewInput): Promise<PreviewResult>;
79
+ /**
80
+ * Wraps `redeemInTransaction` in its own transaction-runner call —
81
+ * the default path when the caller has no external tx context
82
+ * (e.g. POST /billing/promo/redeem as a stand-alone endpoint).
83
+ */
84
+ redeem(input: RedeemInput): Promise<PromoCodeRedemptionRecord>;
85
+ /**
86
+ * Redeems a promo code within an EXTERNAL transaction — the caller
87
+ * (typically `TenantSubscriptionWritePort.applyOnboardingSelection`)
88
+ * already has `prisma.$transaction(...)` open and passes the `tx`
89
+ * context through. This lands plan change, add-on insert, and redemption
90
+ * insert in a single DB transaction (P10.1.1).
91
+ */
92
+ redeemInTransaction(input: RedeemInput, tx: TransactionContext): Promise<PromoCodeRedemptionRecord>;
93
+ reverse(subscriptionId: string): Promise<PromoCodeRedemptionRecord | null>;
94
+ private checkEligibility;
95
+ private checkFirstTimeCustomer;
96
+ /**
97
+ * Lazy expiry: sets codes whose validUntil has passed to EXPIRED.
98
+ * Called before every find/preview — defense-in-depth alongside the cron.
99
+ */
100
+ private lazyExpire;
101
+ /**
102
+ * Lowest applicable plan price. With a whitelist it takes the minimum
103
+ * from the whitelist, otherwise across all marketed plans of the catalog
104
+ * (except non-redeemable).
105
+ */
106
+ private lowestApplicablePlanGross;
107
+ }
108
+
109
+ export { type PreviewInput as P, type RedeemInput as R, type PreviewInvalid as a, type PreviewReason as b, type PreviewResult as c, type PreviewValid as d, type PromoCodeStats as e, PromoCodesService as f, type PromoServiceConfig as g };
@@ -0,0 +1,109 @@
1
+ import { PromoCodeRepository, PromoCodeRedemptionRepository, PromoCodeValidationLogRepository, FirstTimeCustomerCheck, PromoSubscriptionLookup, PromoRevenueDeductionAggregator, TransactionRunner, PlanCatalog, CreatePromoCodeData, PromoCodeRecord, UpdatePromoCodeData, PromoCodeFilter, PromoCodeRedemptionListItem, BillingCycle, PromoCodeRedemptionRecord, TransactionContext } from '@saasicat/types';
2
+
3
+ type PreviewReason = 'NOT_FOUND' | 'EXPIRED' | 'EXHAUSTED' | 'PAUSED' | 'PLAN_MISMATCH' | 'BILLING_MISMATCH' | 'BELOW_MINIMUM_AMOUNT' | 'WOULD_PRODUCE_ZERO_INVOICE' | 'NOT_FIRST_TIME_CUSTOMER' | 'RATE_LIMITED';
4
+ type PreviewInvalid = {
5
+ valid: false;
6
+ reason: PreviewReason;
7
+ };
8
+ interface PreviewValid {
9
+ valid: true;
10
+ code: string;
11
+ label: string;
12
+ discount: {
13
+ valueType: PromoCodeRecord['valueType'];
14
+ value: string;
15
+ durationType: PromoCodeRecord['durationType'];
16
+ durationValue: number | null;
17
+ };
18
+ price: {
19
+ originalGross: string;
20
+ discountGross: string;
21
+ discountedGross: string;
22
+ includedVat: string;
23
+ nextRegularAmountGross: string;
24
+ regularStartsAt: string | null;
25
+ };
26
+ }
27
+ type PreviewResult = PreviewValid | PreviewInvalid;
28
+ interface PreviewInput {
29
+ code: string;
30
+ planId: string;
31
+ billingCycle: BillingCycle;
32
+ /** For the firstTimeCustomersOnly check. */
33
+ email?: string;
34
+ ipHash?: string;
35
+ sessionId?: string;
36
+ }
37
+ interface RedeemInput {
38
+ code: string;
39
+ subscriptionId: string;
40
+ tenantId: string;
41
+ /** For the firstTimeCustomersOnly check in the final redeem path. */
42
+ email?: string;
43
+ }
44
+ interface PromoServiceConfig {
45
+ /** Plans that are not discountable (e.g. 'ENTERPRISE'). */
46
+ nonRedeemablePlans?: readonly string[];
47
+ }
48
+ interface PromoCodeStats {
49
+ code: PromoCodeRecord;
50
+ validations: number;
51
+ redemptions: {
52
+ active: number;
53
+ total: number;
54
+ reversed: number;
55
+ expired: number;
56
+ };
57
+ /** Decimal-as-string ('0.00' if no aggregator is available). */
58
+ revenueDeductionGross: string;
59
+ }
60
+ declare class PromoCodesService {
61
+ private readonly promoRepo;
62
+ private readonly redemptionRepo;
63
+ private readonly validationLogRepo;
64
+ private readonly firstTimeCheck;
65
+ private readonly subscriptionLookup;
66
+ private readonly revenueAggregator;
67
+ private readonly transactionRunner;
68
+ private readonly planCatalog;
69
+ private readonly config;
70
+ constructor(promoRepo: PromoCodeRepository, redemptionRepo: PromoCodeRedemptionRepository, validationLogRepo: PromoCodeValidationLogRepository, firstTimeCheck: FirstTimeCustomerCheck, subscriptionLookup: PromoSubscriptionLookup, revenueAggregator: PromoRevenueDeductionAggregator, transactionRunner: TransactionRunner, planCatalog: PlanCatalog, config: PromoServiceConfig);
71
+ create(input: CreatePromoCodeData): Promise<PromoCodeRecord>;
72
+ update(id: string, input: UpdatePromoCodeData): Promise<PromoCodeRecord>;
73
+ softDelete(id: string): Promise<void>;
74
+ findAll(filter?: PromoCodeFilter): Promise<PromoCodeRecord[]>;
75
+ findOne(id: string): Promise<PromoCodeRecord>;
76
+ stats(id: string): Promise<PromoCodeStats>;
77
+ listRedemptions(promoCodeId: string): Promise<PromoCodeRedemptionListItem[]>;
78
+ preview(input: PreviewInput): Promise<PreviewResult>;
79
+ /**
80
+ * Wraps `redeemInTransaction` in its own transaction-runner call —
81
+ * the default path when the caller has no external tx context
82
+ * (e.g. POST /billing/promo/redeem as a stand-alone endpoint).
83
+ */
84
+ redeem(input: RedeemInput): Promise<PromoCodeRedemptionRecord>;
85
+ /**
86
+ * Redeems a promo code within an EXTERNAL transaction — the caller
87
+ * (typically `TenantSubscriptionWritePort.applyOnboardingSelection`)
88
+ * already has `prisma.$transaction(...)` open and passes the `tx`
89
+ * context through. This lands plan change, add-on insert, and redemption
90
+ * insert in a single DB transaction (P10.1.1).
91
+ */
92
+ redeemInTransaction(input: RedeemInput, tx: TransactionContext): Promise<PromoCodeRedemptionRecord>;
93
+ reverse(subscriptionId: string): Promise<PromoCodeRedemptionRecord | null>;
94
+ private checkEligibility;
95
+ private checkFirstTimeCustomer;
96
+ /**
97
+ * Lazy expiry: sets codes whose validUntil has passed to EXPIRED.
98
+ * Called before every find/preview — defense-in-depth alongside the cron.
99
+ */
100
+ private lazyExpire;
101
+ /**
102
+ * Lowest applicable plan price. With a whitelist it takes the minimum
103
+ * from the whitelist, otherwise across all marketed plans of the catalog
104
+ * (except non-redeemable).
105
+ */
106
+ private lowestApplicablePlanGross;
107
+ }
108
+
109
+ export { type PreviewInput as P, type RedeemInput as R, type PreviewInvalid as a, type PreviewReason as b, type PreviewResult as c, type PreviewValid as d, type PromoCodeStats as e, PromoCodesService as f, type PromoServiceConfig as g };