@saasicat/core 1.0.0-rc.2 → 1.0.0-rc.20
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 +21 -0
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +944 -42
- package/dist/index.d.cts +2403 -476
- package/dist/index.d.ts +2403 -476
- package/dist/index.js +901 -41
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
/** Start of day (00:00 UTC) of the moment — for day-inclusive date comparisons. */
|
|
2
2
|
declare function startOfUtcDay(date: Date): Date;
|
|
3
|
+
/**
|
|
4
|
+
* The day before `value` — how a validity window closes when its successor
|
|
5
|
+
* opens (`validUntil = successor.validFrom − 1 day`, the rule this module's
|
|
6
|
+
* header states and `startOfUtcDay` above is the reading half of).
|
|
7
|
+
*
|
|
8
|
+
* It lived as three separate expressions before 2026-08-27: one in each
|
|
9
|
+
* adapter's publish path and one in the bundle repository, all agreeing by
|
|
10
|
+
* coincidence rather than by construction. Day arithmetic rather than
|
|
11
|
+
* `− 24 * 60 * 60 * 1000`: the subtraction is only equivalent while the value
|
|
12
|
+
* is a UTC midnight, and nothing in the type says it is.
|
|
13
|
+
*/
|
|
14
|
+
declare function previousUtcDay(value: Date): Date;
|
|
3
15
|
/** `<=` upper bound for a date (structurally Prisma-compatible). */
|
|
4
16
|
interface DateAtOrBefore {
|
|
5
17
|
lte: Date;
|
|
@@ -73,256 +85,6 @@ type ActiveVersionWhereWithEndsAt = ActivePlanVersionWhereWithEndsAt;
|
|
|
73
85
|
*/
|
|
74
86
|
declare const buildActiveVersionWhere: typeof buildActivePlanVersionWhere;
|
|
75
87
|
|
|
76
|
-
type FeatureKey = string;
|
|
77
|
-
type PlanId = string;
|
|
78
|
-
type QuotaKey = string;
|
|
79
|
-
interface FeatureDef {
|
|
80
|
-
key: FeatureKey;
|
|
81
|
-
label?: string;
|
|
82
|
-
icon?: string;
|
|
83
|
-
/** CORE / ADVANCED / PRO / BUSINESS / ENTERPRISE_ONLY — convention. */
|
|
84
|
-
tier?: string;
|
|
85
|
-
plannedOnly?: boolean;
|
|
86
|
-
}
|
|
87
|
-
interface PlanDef {
|
|
88
|
-
id: PlanId;
|
|
89
|
-
name?: string;
|
|
90
|
-
tagline?: string;
|
|
91
|
-
/** false = not selectable in self-service onboarding. Default: true. */
|
|
92
|
-
marketed?: boolean;
|
|
93
|
-
/** Highlighted card in onboarding (max. 1 per catalog). */
|
|
94
|
-
popular?: boolean;
|
|
95
|
-
/** Net monthly price. null = on request. */
|
|
96
|
-
monthlyNet?: number | null;
|
|
97
|
-
/** Net total amount per year. null = monthly only. */
|
|
98
|
-
yearlyNet?: number | null;
|
|
99
|
-
/** Map quotaKey → max value. -1 = unlimited. */
|
|
100
|
-
quotas: Record<QuotaKey, number>;
|
|
101
|
-
features: FeatureKey[];
|
|
102
|
-
}
|
|
103
|
-
/** App-wide marketing configuration. */
|
|
104
|
-
interface PlanCatalogMarketing {
|
|
105
|
-
/**
|
|
106
|
-
* Allowed language pool that the app may market. First = default
|
|
107
|
-
* locale. From it, the SuperAdmin activates a subset in the marketing
|
|
108
|
-
* catalog (LocaleManager).
|
|
109
|
-
*/
|
|
110
|
-
availableLocales: string[];
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* App identity block for branding + version. Consumed by the `AdminPublicBootController`
|
|
114
|
-
* and the `AdminManifestConfigFactory`; the SuperAdmin UI (platform
|
|
115
|
-
* LoginPage, AdminLayout brand block) reads the same fields via PublicBoot.
|
|
116
|
-
*
|
|
117
|
-
* `name` = brand display name (e.g. "DemoApp", "ClubApp").
|
|
118
|
-
* `label` = tag/subtitle in the brand block (e.g. "SuperAdmin").
|
|
119
|
-
* `version` = app version string (build info).
|
|
120
|
-
* `icon` = 2-character abbreviation for the logo badge (e.g. "ma", "da").
|
|
121
|
-
* `logoUrl` = optional URL to a PNG/SVG; if set, the UI renders an <img>
|
|
122
|
-
* instead of the initials badge.
|
|
123
|
-
*/
|
|
124
|
-
interface PlanCatalogApp {
|
|
125
|
-
name: string;
|
|
126
|
-
label?: string;
|
|
127
|
-
version?: string;
|
|
128
|
-
icon?: string;
|
|
129
|
-
logoUrl?: string;
|
|
130
|
-
}
|
|
131
|
-
interface PlanCatalog {
|
|
132
|
-
schemaVersion: 1;
|
|
133
|
-
projectKey: string;
|
|
134
|
-
/** App identity (branding + version), see PlanCatalogApp. Optional. */
|
|
135
|
-
app?: PlanCatalogApp;
|
|
136
|
-
/** ISO-4217 currency code. */
|
|
137
|
-
currency: string;
|
|
138
|
-
/** VAT rate in percent. */
|
|
139
|
-
vatRate: number;
|
|
140
|
-
/** App-wide marketing configuration. Optional. */
|
|
141
|
-
marketing?: PlanCatalogMarketing;
|
|
142
|
-
features?: FeatureDef[];
|
|
143
|
-
/**
|
|
144
|
-
* Optional. When omitted, plans come exclusively from the
|
|
145
|
-
* AdminUI / DB table (Plans/PlanVersions lifecycle).
|
|
146
|
-
*/
|
|
147
|
-
plans?: PlanDef[];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Backend capability key, convention: domain.action[.action]. */
|
|
151
|
-
type CapabilityKey = string;
|
|
152
|
-
/** Frontend action-registry key. Same convention as CapabilityKey. */
|
|
153
|
-
type ActionKey = CapabilityKey;
|
|
154
|
-
/** Lookup key in the static extensions: map of the UI build. */
|
|
155
|
-
type ComponentKey = string;
|
|
156
|
-
interface AdminManifest {
|
|
157
|
-
schemaVersion: 1;
|
|
158
|
-
project: {
|
|
159
|
-
key: string;
|
|
160
|
-
displayName: string;
|
|
161
|
-
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
162
|
-
label?: string;
|
|
163
|
-
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
164
|
-
icon?: string;
|
|
165
|
-
logoUrl?: string;
|
|
166
|
-
environment?: 'production' | 'staging' | 'development';
|
|
167
|
-
/**
|
|
168
|
-
* Allowed locale pool from the app config (`saas.yaml`
|
|
169
|
-
* `marketing.availableLocales`). First = default..
|
|
170
|
-
*/
|
|
171
|
-
availableLocales?: string[];
|
|
172
|
-
/** Default locale; equals `availableLocales[0]`. */
|
|
173
|
-
defaultLocale?: string;
|
|
174
|
-
};
|
|
175
|
-
build: {
|
|
176
|
-
platformPackageVersion: string;
|
|
177
|
-
appVersion: string;
|
|
178
|
-
manifestHash: string;
|
|
179
|
-
};
|
|
180
|
-
planCatalogSnapshot: {
|
|
181
|
-
source: string;
|
|
182
|
-
hash: string;
|
|
183
|
-
currency: string;
|
|
184
|
-
vatRate: number;
|
|
185
|
-
features?: FeatureDef[];
|
|
186
|
-
plans: PlanDef[];
|
|
187
|
-
};
|
|
188
|
-
/** Map CapabilityKey → boolean. Manifest is never a security source. */
|
|
189
|
-
capabilities: Record<CapabilityKey, boolean>;
|
|
190
|
-
navigation: {
|
|
191
|
-
standardPages: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
192
|
-
projectPages?: ProjectPageDef[];
|
|
193
|
-
};
|
|
194
|
-
dashboard?: {
|
|
195
|
-
kpiCards?: KpiCardDef[];
|
|
196
|
-
};
|
|
197
|
-
tenants?: {
|
|
198
|
-
columns?: TenantColumnDef[];
|
|
199
|
-
actions?: TenantActionDef[];
|
|
200
|
-
};
|
|
201
|
-
audit?: {
|
|
202
|
-
actions?: AuditActionDef[];
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
type StandardPageKey = 'dashboard' | 'tenants' | 'subscriptions' | 'promoCodes' | 'plans' | 'audit' | 'users' | 'pilots' | 'discovery' | 'bundles' | 'marketingCatalog' | 'platformEmail' | 'platformEmailHistory';
|
|
206
|
-
interface StandardPageDef {
|
|
207
|
-
enabled: boolean;
|
|
208
|
-
requiredCapability?: CapabilityKey;
|
|
209
|
-
}
|
|
210
|
-
interface ProjectPageDef {
|
|
211
|
-
/** `<projectKey>.<area>`, e.g. `demoapp.datev`. */
|
|
212
|
-
id: string;
|
|
213
|
-
label: string;
|
|
214
|
-
icon?: string;
|
|
215
|
-
/** Frontend route, e.g. `/admin/datev`. */
|
|
216
|
-
route: string;
|
|
217
|
-
navSection?: string;
|
|
218
|
-
/** Lookup in the static extensions: map of the shell build. */
|
|
219
|
-
componentKey: ComponentKey;
|
|
220
|
-
requiredCapability?: CapabilityKey;
|
|
221
|
-
prefetchOnIdle?: boolean;
|
|
222
|
-
}
|
|
223
|
-
interface KpiCardDef {
|
|
224
|
-
id: string;
|
|
225
|
-
label: string;
|
|
226
|
-
/** Required path: /api/v1/admin/(extras|dashboard)/... */
|
|
227
|
-
endpoint: string;
|
|
228
|
-
displayHint: KpiDisplayHint;
|
|
229
|
-
/** 0–100; UI sorts descending. */
|
|
230
|
-
slotPriority?: number;
|
|
231
|
-
requiredCapability?: CapabilityKey;
|
|
232
|
-
}
|
|
233
|
-
interface KpiDisplayHint {
|
|
234
|
-
type: 'value' | 'value+timestamp' | 'value+spark8w' | 'value+delta';
|
|
235
|
-
icon?: string;
|
|
236
|
-
}
|
|
237
|
-
interface TenantColumnDef {
|
|
238
|
-
key: string;
|
|
239
|
-
label: string;
|
|
240
|
-
/** Required path: /api/v1/admin/extras/...; MUST be batch-capable, no {slug}/{tenantId}. */
|
|
241
|
-
endpoint: string;
|
|
242
|
-
requiredCapability?: CapabilityKey;
|
|
243
|
-
}
|
|
244
|
-
interface TenantActionDef {
|
|
245
|
-
/** `<projectKey>.<area>.<verb>`, e.g. `demoapp.datev.runExport`. */
|
|
246
|
-
id: string;
|
|
247
|
-
label: string;
|
|
248
|
-
/** Lookup in the static actions: map of the shell build. */
|
|
249
|
-
actionKey: ActionKey;
|
|
250
|
-
requiredCapability?: CapabilityKey;
|
|
251
|
-
requiresMfa?: boolean;
|
|
252
|
-
confirmType?: 'none' | 'simple' | 'typed-slug' | 'typed-production' | 'date';
|
|
253
|
-
}
|
|
254
|
-
interface AuditActionDef {
|
|
255
|
-
/** SCREAMING_SNAKE_CASE; matched to the AuditLog.action column. */
|
|
256
|
-
key: string;
|
|
257
|
-
label: string;
|
|
258
|
-
severity?: 'info' | 'low' | 'medium' | 'high';
|
|
259
|
-
}
|
|
260
|
-
interface ManifestContribution {
|
|
261
|
-
capabilities?: Record<CapabilityKey, boolean>;
|
|
262
|
-
navigation?: {
|
|
263
|
-
standardPages?: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
264
|
-
projectPages?: ProjectPageDef[];
|
|
265
|
-
};
|
|
266
|
-
dashboard?: {
|
|
267
|
-
kpiCards?: KpiCardDef[];
|
|
268
|
-
};
|
|
269
|
-
tenants?: {
|
|
270
|
-
columns?: TenantColumnDef[];
|
|
271
|
-
actions?: TenantActionDef[];
|
|
272
|
-
};
|
|
273
|
-
audit?: {
|
|
274
|
-
actions?: AuditActionDef[];
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
interface PublicBootResponse {
|
|
278
|
-
project: {
|
|
279
|
-
key: string;
|
|
280
|
-
displayName: string;
|
|
281
|
-
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
282
|
-
label?: string;
|
|
283
|
-
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
284
|
-
icon?: string;
|
|
285
|
-
logoUrl?: string;
|
|
286
|
-
environment?: 'production' | 'staging' | 'development';
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** Format: 'web:<email>:<sessionId>' or 'cli:<email>:<host>'. */
|
|
291
|
-
type ActorTag = string;
|
|
292
|
-
interface AuditEntry {
|
|
293
|
-
id: string;
|
|
294
|
-
/** null = platform action without tenant context (SUPER_ADMIN). */
|
|
295
|
-
tenantId: string | null;
|
|
296
|
-
/** null = system / cron-triggered. */
|
|
297
|
-
userId: string | null;
|
|
298
|
-
/** Convenience field; backend resolves it from userId. */
|
|
299
|
-
userEmail: string | null;
|
|
300
|
-
/** e.g. 'Tenant', 'PromoCode', 'Subscription', 'PlanVersion', 'User'. */
|
|
301
|
-
entity: string;
|
|
302
|
-
entityId: string;
|
|
303
|
-
/** SCREAMING_SNAKE_CASE; past-tense oriented. */
|
|
304
|
-
action: string;
|
|
305
|
-
/** Freely structured. Convention: { field: { old, new } } or { reason, ... }. */
|
|
306
|
-
changes: Record<string, unknown> | null;
|
|
307
|
-
actorTag: ActorTag | null;
|
|
308
|
-
ipAddress: string | null;
|
|
309
|
-
userAgent: string | null;
|
|
310
|
-
createdAt: string;
|
|
311
|
-
}
|
|
312
|
-
interface AuditQuery {
|
|
313
|
-
tenantId?: string;
|
|
314
|
-
userId?: string;
|
|
315
|
-
entity?: string;
|
|
316
|
-
entityId?: string;
|
|
317
|
-
action?: string;
|
|
318
|
-
/** Wildcard-capable, e.g. 'cli:*'. */
|
|
319
|
-
actorTag?: string;
|
|
320
|
-
from?: string;
|
|
321
|
-
to?: string;
|
|
322
|
-
page?: number;
|
|
323
|
-
pageSize?: number;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
88
|
/**
|
|
327
89
|
* Approval lifecycle of a feature or a quota. Approval happens per
|
|
328
90
|
* FEATURE/QUOTA — not per capability (#20); only `approved` entries
|
|
@@ -385,7 +147,6 @@ type CatalogEntryI18n = Record<string, CatalogEntryI18nFields>;
|
|
|
385
147
|
*/
|
|
386
148
|
interface CapabilityCatalogEntryRow {
|
|
387
149
|
id: string;
|
|
388
|
-
projectKey: string;
|
|
389
150
|
capabilityKey: string;
|
|
390
151
|
label: string;
|
|
391
152
|
description: string | null;
|
|
@@ -422,7 +183,6 @@ type FeatureTier = 'CORE' | 'ADVANCED' | 'PRO' | 'ENTERPRISE' | string;
|
|
|
422
183
|
*/
|
|
423
184
|
interface FeatureCatalogEntryRow {
|
|
424
185
|
id: string;
|
|
425
|
-
projectKey: string;
|
|
426
186
|
featureKey: string;
|
|
427
187
|
label: string;
|
|
428
188
|
description: string | null;
|
|
@@ -477,7 +237,6 @@ interface FeatureCatalogEntryRow {
|
|
|
477
237
|
*/
|
|
478
238
|
interface QuotaCatalogEntryRow {
|
|
479
239
|
id: string;
|
|
480
|
-
projectKey: string;
|
|
481
240
|
quotaKey: string;
|
|
482
241
|
label: string;
|
|
483
242
|
description: string | null;
|
|
@@ -521,7 +280,6 @@ interface QuotaCatalogEntryRow {
|
|
|
521
280
|
* matching field is relevant.
|
|
522
281
|
*/
|
|
523
282
|
interface CatalogEntryFilter {
|
|
524
|
-
projectKey: string;
|
|
525
283
|
discoveryStatus?: DiscoveryStatus;
|
|
526
284
|
codeStatus?: CapabilityCodeStatus;
|
|
527
285
|
}
|
|
@@ -613,6 +371,18 @@ interface MarketingTopFeature {
|
|
|
613
371
|
label: string;
|
|
614
372
|
strong: string;
|
|
615
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* The range a marketing projection's `priority` may take.
|
|
376
|
+
*
|
|
377
|
+
* Declared here rather than in the DTO because two sides need the same answer:
|
|
378
|
+
* the request pipe rejects anything outside it, and the admin UI computes
|
|
379
|
+
* priorities when an operator drags a plan into a new position. A UI that
|
|
380
|
+
* picked its own bounds would produce a value the pipe refuses — and it did,
|
|
381
|
+
* at the top of the range, where a list of tied plans was lifted past the
|
|
382
|
+
* maximum to pull the ties apart.
|
|
383
|
+
*/
|
|
384
|
+
declare const MARKETING_PRIORITY_MIN = 0;
|
|
385
|
+
declare const MARKETING_PRIORITY_MAX = 10000;
|
|
616
386
|
/**
|
|
617
387
|
* Locale-specific marketing texts per plan/bundle version.
|
|
618
388
|
* Read and projected by the Public-Catalog-Controller
|
|
@@ -623,7 +393,6 @@ interface MarketingTopFeature {
|
|
|
623
393
|
*/
|
|
624
394
|
interface MarketingProjectionRow {
|
|
625
395
|
id: string;
|
|
626
|
-
projectKey: string;
|
|
627
396
|
targetType: MarketingTargetType;
|
|
628
397
|
targetVersionId: string;
|
|
629
398
|
/** ISO-639-1, optionally with region suffix (`de`, `en`, `de-AT`). */
|
|
@@ -668,42 +437,434 @@ interface MarketingProjectionRow {
|
|
|
668
437
|
createdAt: string;
|
|
669
438
|
updatedAt: string;
|
|
670
439
|
}
|
|
671
|
-
/** Filter for `MarketingProjectionRepository.list()`.
|
|
672
|
-
interface MarketingProjectionFilter {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
440
|
+
/** Filter for `MarketingProjectionRepository.list()`. */
|
|
441
|
+
interface MarketingProjectionFilter {
|
|
442
|
+
targetType?: MarketingTargetType;
|
|
443
|
+
targetVersionId?: string;
|
|
444
|
+
locale?: string;
|
|
445
|
+
}
|
|
446
|
+
interface CreateMarketingProjectionData {
|
|
447
|
+
targetType: MarketingTargetType;
|
|
448
|
+
targetVersionId: string;
|
|
449
|
+
locale?: string;
|
|
450
|
+
displayLabel: string;
|
|
451
|
+
description: string;
|
|
452
|
+
visible?: boolean;
|
|
453
|
+
badge?: string;
|
|
454
|
+
topFeatures?: MarketingTopFeature[];
|
|
455
|
+
trialEnabled?: boolean;
|
|
456
|
+
trialDays?: number;
|
|
457
|
+
priceTag?: string | null;
|
|
458
|
+
ctaLabel?: string | null;
|
|
459
|
+
priority?: number;
|
|
460
|
+
highlight?: boolean;
|
|
461
|
+
}
|
|
462
|
+
interface UpdateMarketingProjectionData {
|
|
463
|
+
displayLabel?: string;
|
|
464
|
+
description?: string;
|
|
465
|
+
visible?: boolean;
|
|
466
|
+
badge?: string;
|
|
467
|
+
topFeatures?: MarketingTopFeature[];
|
|
468
|
+
trialEnabled?: boolean;
|
|
469
|
+
trialDays?: number;
|
|
470
|
+
priceTag?: string | null;
|
|
471
|
+
ctaLabel?: string | null;
|
|
472
|
+
priority?: number;
|
|
473
|
+
highlight?: boolean;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** The address lines a party is named with. Every one of them may be unknown. */
|
|
477
|
+
interface PartyAddress {
|
|
478
|
+
/** Street and number. */
|
|
479
|
+
addressLine1: string | null;
|
|
480
|
+
/** A second line, such as a building or a c/o. */
|
|
481
|
+
addressLine2: string | null;
|
|
482
|
+
postalCode: string | null;
|
|
483
|
+
city: string | null;
|
|
484
|
+
/** ISO 3166-1 alpha-2, upper case. */
|
|
485
|
+
country: string | null;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* What names a legal entity: the name it is registered under and the
|
|
489
|
+
* identifiers its tax office gave it.
|
|
490
|
+
*
|
|
491
|
+
* These are the party a contract was concluded with. Under a running contract
|
|
492
|
+
* they change only as a correction of that same entity, recorded with the
|
|
493
|
+
* values they replaced and the reason; contact details change freely.
|
|
494
|
+
*/
|
|
495
|
+
interface LegalIdentity {
|
|
496
|
+
/** The registered name, legal form included. */
|
|
497
|
+
legalName: string;
|
|
498
|
+
/** VAT identification number. */
|
|
499
|
+
vatId: string | null;
|
|
500
|
+
/** The national tax number, where one is stated beside or instead of the VAT id. */
|
|
501
|
+
taxNumber: string | null;
|
|
502
|
+
}
|
|
503
|
+
/** The fields of the legal identity, the ones a correction may change. */
|
|
504
|
+
type LegalIdentityField = keyof LegalIdentity;
|
|
505
|
+
/** Those fields, in the order they are named and shown. */
|
|
506
|
+
declare const LEGAL_IDENTITY_FIELDS: readonly LegalIdentityField[];
|
|
507
|
+
/** Whether two identities name the same entity: every field, value for value. */
|
|
508
|
+
declare function sameLegalIdentity(before: LegalIdentity, after: LegalIdentity): boolean;
|
|
509
|
+
/** The fields whose value differs between two identities, in field order. */
|
|
510
|
+
declare function movedIdentityFields(before: LegalIdentity, after: LegalIdentity): LegalIdentityField[];
|
|
511
|
+
|
|
512
|
+
/** The payment methods SaaSiCat takes, by the names `config/saas.yaml` uses. */
|
|
513
|
+
type PaymentMethodType = 'card' | 'sepa_debit';
|
|
514
|
+
/**
|
|
515
|
+
* Whom a payment method is set up for. The gateway hands it back unchanged
|
|
516
|
+
* with its confirmation, which is how the confirmation finds its way to the
|
|
517
|
+
* sign-up or the subscriber that asked for it.
|
|
518
|
+
*/
|
|
519
|
+
type PaymentMethodSetupSubject = {
|
|
520
|
+
kind: 'registration';
|
|
521
|
+
pendingRegistrationId: string;
|
|
522
|
+
} | {
|
|
523
|
+
kind: 'subscriber';
|
|
524
|
+
subscriberId: string;
|
|
525
|
+
};
|
|
526
|
+
/** The party the gateway keeps the payment method for: its customer there. */
|
|
527
|
+
interface PaymentMethodHolder {
|
|
528
|
+
/** The legal name, which a direct debit mandate names. */
|
|
529
|
+
name: string;
|
|
530
|
+
/** Where the gateway sends what it sends; `null` lets its form ask. */
|
|
531
|
+
email: string | null;
|
|
532
|
+
address: PartyAddress;
|
|
533
|
+
/**
|
|
534
|
+
* The customer this party already has at the account, from an earlier
|
|
535
|
+
* payment method. `null` asks the gateway for a new one.
|
|
536
|
+
*/
|
|
537
|
+
customerRef: string | null;
|
|
538
|
+
}
|
|
539
|
+
interface StartPaymentMethodSetupInput {
|
|
540
|
+
subject: PaymentMethodSetupSubject;
|
|
541
|
+
holder: PaymentMethodHolder;
|
|
542
|
+
/** What the form offers, from the account's `methods`. */
|
|
543
|
+
methods: readonly PaymentMethodType[];
|
|
544
|
+
/** Where the gateway sends the person once the form is done. */
|
|
545
|
+
successUrl: string;
|
|
546
|
+
/** Where the gateway sends the person who leaves the form. */
|
|
547
|
+
cancelUrl: string;
|
|
548
|
+
}
|
|
549
|
+
/** A body and headers exactly as they arrived, before anything parsed them. */
|
|
550
|
+
interface PaymentGatewayCallback {
|
|
551
|
+
body: string | Uint8Array;
|
|
552
|
+
headers: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
553
|
+
}
|
|
554
|
+
interface PaymentMethodSetupSession {
|
|
555
|
+
/**
|
|
556
|
+
* The gateway's identifier of the session, unique within its account — and
|
|
557
|
+
* an adapter that has no session of its own makes one per start rather than
|
|
558
|
+
* a constant. A session is confirmed once, which the event log holds, so a
|
|
559
|
+
* value two starts share lets the second confirmation through as the
|
|
560
|
+
* duplicate it is not.
|
|
561
|
+
*/
|
|
562
|
+
sessionRef: string;
|
|
563
|
+
/** The gateway's form, where the person is sent next. */
|
|
564
|
+
redirectUrl: string;
|
|
565
|
+
/** The customer the payment method is set up for, created if none was given. */
|
|
566
|
+
customerRef: string;
|
|
567
|
+
/**
|
|
568
|
+
* A confirmation the gateway already holds when the session starts, to be
|
|
569
|
+
* handled like any callback. Only a gateway without a form of its own has
|
|
570
|
+
* one — the development gateway; a real gateway confirms through its
|
|
571
|
+
* webhook.
|
|
572
|
+
*/
|
|
573
|
+
immediateCallback?: PaymentGatewayCallback;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* What SaaSiCat keeps about a payment method: enough to show which one it is,
|
|
577
|
+
* never enough to pay with it (`SC-PRIV-005`).
|
|
578
|
+
*/
|
|
579
|
+
interface MaskedPaymentMethod {
|
|
580
|
+
type: PaymentMethodType;
|
|
581
|
+
/** The card network, such as `visa`; `null` for a direct debit. */
|
|
582
|
+
brand: string | null;
|
|
583
|
+
/** The last four digits of the card number or the IBAN. */
|
|
584
|
+
last4: string;
|
|
585
|
+
/** 1–12; `null` for a direct debit. */
|
|
586
|
+
expiryMonth: number | null;
|
|
587
|
+
/** Four digits; `null` for a direct debit. */
|
|
588
|
+
expiryYear: number | null;
|
|
589
|
+
/** ISO 3166-1 alpha-2 of the card's issuer or the bank account, where the gateway says. */
|
|
590
|
+
country: string | null;
|
|
591
|
+
/** The bank code of a direct debit account, where the gateway says. */
|
|
592
|
+
bankCode: string | null;
|
|
593
|
+
/** The reference of a direct debit mandate, which a debit announcement quotes. */
|
|
594
|
+
mandateReference: string | null;
|
|
595
|
+
}
|
|
596
|
+
/** A payment method the gateway confirmed, with the references that reach it there. */
|
|
597
|
+
interface ConfirmedPaymentMethod extends MaskedPaymentMethod {
|
|
598
|
+
customerRef: string;
|
|
599
|
+
paymentMethodRef: string;
|
|
600
|
+
}
|
|
601
|
+
/** A gateway callback, verified and translated. */
|
|
602
|
+
type PaymentGatewayEvent = {
|
|
603
|
+
kind: 'payment-method-confirmed';
|
|
604
|
+
/** The gateway's identifier of the event, unique within its account. */
|
|
605
|
+
eventId: string;
|
|
606
|
+
/** When the gateway says it happened. */
|
|
607
|
+
occurredAt: Date;
|
|
608
|
+
sessionRef: string;
|
|
609
|
+
subject: PaymentMethodSetupSubject;
|
|
610
|
+
paymentMethod: ConfirmedPaymentMethod;
|
|
611
|
+
} | {
|
|
612
|
+
kind: 'payment-method-setup-failed';
|
|
613
|
+
eventId: string;
|
|
614
|
+
occurredAt: Date;
|
|
615
|
+
sessionRef: string;
|
|
616
|
+
subject: PaymentMethodSetupSubject;
|
|
617
|
+
} | {
|
|
618
|
+
/** Genuine, and nothing SaaSiCat acts on. */
|
|
619
|
+
kind: 'unhandled';
|
|
620
|
+
eventId: string;
|
|
621
|
+
occurredAt: Date;
|
|
622
|
+
/** The gateway's own name for the event, for the log. */
|
|
623
|
+
type: string;
|
|
624
|
+
};
|
|
625
|
+
/**
|
|
626
|
+
* One account at a payment gateway: its keys, its form and its callbacks.
|
|
627
|
+
*
|
|
628
|
+
* An adapter is bound to one account, and `config/saas.yaml#payments.accounts`
|
|
629
|
+
* names it. Mollie or any other provider is another adapter behind this port.
|
|
630
|
+
*/
|
|
631
|
+
interface PaymentGateway {
|
|
632
|
+
/** The provider, as `config/saas.yaml` names it, e.g. `stripe`. */
|
|
633
|
+
readonly provider: string;
|
|
634
|
+
/**
|
|
635
|
+
* Opens the gateway's form for a payment method. Nothing is confirmed until
|
|
636
|
+
* the gateway says so through `readCallback`.
|
|
637
|
+
*/
|
|
638
|
+
startPaymentMethodSetup(input: StartPaymentMethodSetupInput): Promise<PaymentMethodSetupSession>;
|
|
639
|
+
/**
|
|
640
|
+
* Verifies a callback with the account's secret and translates it.
|
|
641
|
+
* Throws `PaymentCallbackRejectedError` for anything the gateway did not
|
|
642
|
+
* send, before a single field of it is trusted.
|
|
643
|
+
*/
|
|
644
|
+
readCallback(callback: PaymentGatewayCallback): Promise<PaymentGatewayEvent>;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* A callback that is not the gateway's: a missing or wrong signature, a stale
|
|
648
|
+
* timestamp, a body that was altered. Nothing is read from it.
|
|
649
|
+
*/
|
|
650
|
+
declare class PaymentCallbackRejectedError extends Error {
|
|
651
|
+
readonly code = "PAYMENT_CALLBACK_REJECTED";
|
|
652
|
+
constructor(reason: string);
|
|
653
|
+
}
|
|
654
|
+
/** Realm-safe type guard, like `isPlatformUserExistsError`. */
|
|
655
|
+
declare function isPaymentCallbackRejectedError(err: unknown): err is PaymentCallbackRejectedError;
|
|
656
|
+
|
|
657
|
+
type FeatureKey = string;
|
|
658
|
+
type PlanId = string;
|
|
659
|
+
type QuotaKey = string;
|
|
660
|
+
interface FeatureDef {
|
|
661
|
+
key: FeatureKey;
|
|
662
|
+
label?: string;
|
|
663
|
+
icon?: string;
|
|
664
|
+
/** CORE / ADVANCED / PRO / BUSINESS / ENTERPRISE_ONLY — convention. */
|
|
665
|
+
tier?: string;
|
|
666
|
+
plannedOnly?: boolean;
|
|
667
|
+
}
|
|
668
|
+
interface PlanDef {
|
|
669
|
+
id: PlanId;
|
|
670
|
+
name?: string;
|
|
671
|
+
tagline?: string;
|
|
672
|
+
/** false = not selectable in self-service onboarding. Default: true. */
|
|
673
|
+
marketed?: boolean;
|
|
674
|
+
/** Highlighted card in onboarding (max. 1 per catalog). */
|
|
675
|
+
popular?: boolean;
|
|
676
|
+
/** Net monthly price. null = on request. */
|
|
677
|
+
monthlyNet?: number | null;
|
|
678
|
+
/** Net total amount per year. null = monthly only. */
|
|
679
|
+
yearlyNet?: number | null;
|
|
680
|
+
/** Map quotaKey → max value. -1 = unlimited. */
|
|
681
|
+
quotas: Record<QuotaKey, number>;
|
|
682
|
+
features: FeatureKey[];
|
|
683
|
+
}
|
|
684
|
+
/** App-wide marketing configuration. */
|
|
685
|
+
interface PlanCatalogMarketing {
|
|
686
|
+
/**
|
|
687
|
+
* Allowed language pool that the app may market. First = default
|
|
688
|
+
* locale. From it, the SuperAdmin activates a subset in the marketing
|
|
689
|
+
* catalog (LocaleManager).
|
|
690
|
+
*/
|
|
691
|
+
availableLocales: string[];
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* App identity block for branding + version. Consumed by the `AdminPublicBootController`
|
|
695
|
+
* and the `AdminManifestConfigFactory`; the SuperAdmin UI (platform
|
|
696
|
+
* LoginPage, AdminLayout brand block) reads the same fields via PublicBoot.
|
|
697
|
+
*
|
|
698
|
+
* `name` = brand display name (e.g. "DemoApp", "ClubApp").
|
|
699
|
+
* `label` = tag/subtitle in the brand block (e.g. "SuperAdmin").
|
|
700
|
+
* `version` = app version string (build info).
|
|
701
|
+
* `icon` = 2-character abbreviation for the logo badge (e.g. "ma", "da").
|
|
702
|
+
* `logoUrl` = optional URL to a PNG/SVG; if set, the UI renders an <img>
|
|
703
|
+
* instead of the initials badge.
|
|
704
|
+
*/
|
|
705
|
+
interface PlanCatalogApp {
|
|
706
|
+
name: string;
|
|
707
|
+
label?: string;
|
|
708
|
+
version?: string;
|
|
709
|
+
icon?: string;
|
|
710
|
+
logoUrl?: string;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Notice periods, one per rhythm.
|
|
714
|
+
*
|
|
715
|
+
* One number for both was the shape until 2026-08-27, and it could not be right
|
|
716
|
+
* for both: a yearly contract with a fortnight of notice is unusual, and a
|
|
717
|
+
* monthly contract with three months of notice is void against a consumer. The
|
|
718
|
+
* two are configured apart because real contracts set them apart.
|
|
719
|
+
*
|
|
720
|
+
* Both members are required. A missing rhythm would read as zero, and a silent
|
|
721
|
+
* zero is a commercial decision nobody made — the same defect one level below
|
|
722
|
+
* the one that moved these settings into the file.
|
|
723
|
+
*
|
|
724
|
+
* **No ceiling is enforced.** §309 Nr. 9 BGB limits the notice period in German
|
|
725
|
+
* consumer contracts to one month, and an installation serving businesses is
|
|
726
|
+
* not bound by it. The platform cannot know which it is, so the number is the
|
|
727
|
+
* consumer app's to choose and this is the sentence that says what it costs.
|
|
728
|
+
*/
|
|
729
|
+
interface CancellationNoticePeriods {
|
|
730
|
+
/** Days of notice for a monthly subscription. */
|
|
731
|
+
monthly: number;
|
|
732
|
+
/** Days of notice for a yearly subscription. */
|
|
733
|
+
yearly: number;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Plans a tenant may not reach or leave without talking to sales.
|
|
737
|
+
*
|
|
738
|
+
* `asTarget`: may not be selected via self-service — typically ENTERPRISE,
|
|
739
|
+
* which only a special contract activates. `asSource`: may not be left via
|
|
740
|
+
* self-service — typically an active special contract.
|
|
741
|
+
*
|
|
742
|
+
* Both lists are required and may be empty. An empty list says out loud that
|
|
743
|
+
* self-service reaches every plan, which is a decision rather than an omission.
|
|
744
|
+
*/
|
|
745
|
+
interface SelfServiceBlockedPlans {
|
|
746
|
+
asTarget: string[];
|
|
747
|
+
asSource: string[];
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Commercial settings for the tenant-facing self-service routes.
|
|
751
|
+
*
|
|
752
|
+
* They live in `config/saas.yaml` and nowhere else: an operator reading the
|
|
753
|
+
* file has to be reading the values that are running, with no "unless somebody
|
|
754
|
+
* passed it in code" attached. The file is read at boot, so an edit lands on
|
|
755
|
+
* the next restart.
|
|
756
|
+
*/
|
|
757
|
+
interface PlanCatalogTenantBilling {
|
|
758
|
+
cancellationNoticeDays: CancellationNoticePeriods;
|
|
759
|
+
selfServiceBlockedPlans: SelfServiceBlockedPlans;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Who is told when the settings in the file change between two starts.
|
|
763
|
+
*
|
|
764
|
+
* The record inside the application is written whether or not anybody is
|
|
765
|
+
* named here; mail is the addition, never the substitute. Mailed only where an
|
|
766
|
+
* email port is bound — without one the boot log says so once, and the change
|
|
767
|
+
* is recorded in the application only.
|
|
768
|
+
*/
|
|
769
|
+
interface PlanCatalogNotifications {
|
|
770
|
+
/** Addresses mailed when a start finds the applied settings changed. */
|
|
771
|
+
settingsChanged?: string[];
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* The legal entity on the operator's side of every contract the installation
|
|
775
|
+
* concludes. A contract copies it on the day it is concluded; only the legal
|
|
776
|
+
* name is required while nothing is invoiced.
|
|
777
|
+
*/
|
|
778
|
+
interface PlanCatalogIssuer {
|
|
779
|
+
legalName: string;
|
|
780
|
+
addressLine1?: string;
|
|
781
|
+
addressLine2?: string;
|
|
782
|
+
postalCode?: string;
|
|
783
|
+
city?: string;
|
|
784
|
+
/** ISO 3166-1 alpha-2. */
|
|
785
|
+
country?: string;
|
|
786
|
+
vatId?: string;
|
|
787
|
+
taxNumber?: string;
|
|
788
|
+
/** Declares a changed identity as a correction of the same legal entity. */
|
|
789
|
+
correctionOf?: PlanCatalogIssuerCorrection;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* What a changed issuer identity replaces, and why.
|
|
793
|
+
*
|
|
794
|
+
* The identity is the counterparty a contract names, so it moves under a
|
|
795
|
+
* running contract only as a correction of that same entity. Each field names
|
|
796
|
+
* the value the installation recorded before the change, `null` where it
|
|
797
|
+
* recorded none; a field the change leaves alone need not be named.
|
|
798
|
+
*/
|
|
799
|
+
interface PlanCatalogIssuerCorrection {
|
|
800
|
+
legalName?: string | null;
|
|
801
|
+
vatId?: string | null;
|
|
802
|
+
taxNumber?: string | null;
|
|
803
|
+
/** Why the same entity now reads differently. Kept in the settings record. */
|
|
804
|
+
reason: string;
|
|
805
|
+
}
|
|
806
|
+
/** How the parties contracts are concluded with are numbered. */
|
|
807
|
+
interface PlanCatalogSubscribers {
|
|
808
|
+
/**
|
|
809
|
+
* Put in front of every customer number assigned from the next start on.
|
|
810
|
+
* A number keeps the prefix it was assigned with.
|
|
811
|
+
*/
|
|
812
|
+
customerNumberPrefix?: string;
|
|
813
|
+
}
|
|
814
|
+
/** One account at a payment gateway, by the name `PlanCatalogPayments.accounts` gives it. */
|
|
815
|
+
interface PlanCatalogPaymentAccount {
|
|
816
|
+
/** The provider its bound adapter names itself as, e.g. `stripe`. */
|
|
817
|
+
provider: string;
|
|
818
|
+
/** What a new payment method may be at this account. Read for `newPaymentMethods` only. */
|
|
819
|
+
methods?: PaymentMethodType[];
|
|
677
820
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
trialDays?: number;
|
|
690
|
-
priceTag?: string | null;
|
|
691
|
-
ctaLabel?: string | null;
|
|
692
|
-
priority?: number;
|
|
693
|
-
highlight?: boolean;
|
|
821
|
+
/**
|
|
822
|
+
* The gateway accounts payment methods are taken through. Their keys are bound
|
|
823
|
+
* in code from the environment, never written into the file.
|
|
824
|
+
*/
|
|
825
|
+
interface PlanCatalogPayments {
|
|
826
|
+
/** The account a new payment method is taken at. Omitted, none is taken. */
|
|
827
|
+
newPaymentMethods?: string;
|
|
828
|
+
/** The origins a gateway's form may send a person back to, such as `https://app.example.com`. */
|
|
829
|
+
returnUrlOrigins: string[];
|
|
830
|
+
/** Every account taking new payment methods or holding a reference in use. */
|
|
831
|
+
accounts: Record<string, PlanCatalogPaymentAccount>;
|
|
694
832
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
833
|
+
/**
|
|
834
|
+
* The part of `config/saas.yaml` that is configuration rather than catalogue.
|
|
835
|
+
*
|
|
836
|
+
* Declared apart so it can be handed on whole — a database catalogue takes its
|
|
837
|
+
* settings from the file and its plans from the database — without anybody
|
|
838
|
+
* listing the blocks again. `CATALOGUE_KEYS` names what is left over.
|
|
839
|
+
*/
|
|
840
|
+
interface PlanCatalogSettings {
|
|
841
|
+
/** App identity (branding + version), see PlanCatalogApp. */
|
|
842
|
+
app: PlanCatalogApp;
|
|
843
|
+
/** ISO-4217 currency code. */
|
|
844
|
+
currency: string;
|
|
845
|
+
/** VAT rate in percent. */
|
|
846
|
+
vatRate: number;
|
|
847
|
+
/** Commercial settings for the tenant self-service routes. */
|
|
848
|
+
tenantBilling: PlanCatalogTenantBilling;
|
|
849
|
+
/** App-wide marketing configuration. Optional. */
|
|
850
|
+
marketing?: PlanCatalogMarketing;
|
|
851
|
+
/** Who is told when the settings change between two starts. Optional. */
|
|
852
|
+
notifications?: PlanCatalogNotifications;
|
|
853
|
+
/** The operator's side of every contract. Optional until invoicing requires it. */
|
|
854
|
+
issuer?: PlanCatalogIssuer;
|
|
855
|
+
/** How subscribers are numbered. Optional. */
|
|
856
|
+
subscribers?: PlanCatalogSubscribers;
|
|
857
|
+
/** The payment gateway accounts. Optional until payment methods are taken. */
|
|
858
|
+
payments?: PlanCatalogPayments;
|
|
859
|
+
}
|
|
860
|
+
interface PlanCatalog extends PlanCatalogSettings {
|
|
861
|
+
schemaVersion: 1;
|
|
862
|
+
features?: FeatureDef[];
|
|
863
|
+
/**
|
|
864
|
+
* Optional. When omitted, plans come exclusively from the
|
|
865
|
+
* AdminUI / DB table (Plans/PlanVersions lifecycle).
|
|
866
|
+
*/
|
|
867
|
+
plans?: PlanDef[];
|
|
707
868
|
}
|
|
708
869
|
|
|
709
870
|
type PromoCodeValueType = 'PERCENT' | 'ABSOLUTE';
|
|
@@ -902,7 +1063,7 @@ interface VersionedEntityBase {
|
|
|
902
1063
|
* own migration).
|
|
903
1064
|
* - `startedAt` is the contract start of this booking.
|
|
904
1065
|
* - `minimumTermEndsAt` = end of the minimum term; `null` = no minimum term
|
|
905
|
-
* (platform default =
|
|
1066
|
+
* (platform default = no commitment, set service-side).
|
|
906
1067
|
* - `canceledAt` / `canceledEffectiveAt`: cancellation anchor vs. effective
|
|
907
1068
|
* date. Before the minimum term ends, `canceledEffectiveAt =
|
|
908
1069
|
* minimumTermEndsAt`, otherwise the subscription's period end.
|
|
@@ -919,6 +1080,18 @@ interface SubscriptionBundleRecord {
|
|
|
919
1080
|
minimumTermEndsAt: Date | null;
|
|
920
1081
|
canceledAt: Date | null;
|
|
921
1082
|
canceledEffectiveAt: Date | null;
|
|
1083
|
+
/**
|
|
1084
|
+
* The rhythm this booking is billed in, and the window it is billed for.
|
|
1085
|
+
*
|
|
1086
|
+
* A bundle's periods end on the day its plan's do — the first one short,
|
|
1087
|
+
* from the booking to the next occurrence of that day, and every one after
|
|
1088
|
+
* it anchor to anchor. Null on a booking made before these fields existed,
|
|
1089
|
+
* or on one whose plan has no period; readers fall back to the plan's
|
|
1090
|
+
* cycle, which is what every booking used before.
|
|
1091
|
+
*/
|
|
1092
|
+
billingCycle: string | null;
|
|
1093
|
+
currentPeriodStart: Date | null;
|
|
1094
|
+
currentPeriodEnd: Date | null;
|
|
922
1095
|
createdAt: Date;
|
|
923
1096
|
updatedAt: Date;
|
|
924
1097
|
}
|
|
@@ -932,14 +1105,27 @@ interface SubscriptionBundleRecord {
|
|
|
932
1105
|
interface SubscriptionBundleView extends SubscriptionBundleRecord {
|
|
933
1106
|
bundleKey: string | null;
|
|
934
1107
|
label: string | null;
|
|
935
|
-
|
|
1108
|
+
/**
|
|
1109
|
+
* What this booking is billed at, in the rhythm it was booked in and with
|
|
1110
|
+
* the plan's pricing override applied.
|
|
1111
|
+
*
|
|
1112
|
+
* It was `monthlyNet` until 2026-08-27 and carried the bundle's base
|
|
1113
|
+
* monthly price whatever the booking was — so a yearly booking of a bundle
|
|
1114
|
+
* priced 10 monthly and 100 yearly reported 10. The name was half the
|
|
1115
|
+
* defect: a field called `monthlyNet` on a yearly booking cannot be right.
|
|
1116
|
+
*/
|
|
1117
|
+
priceNet: number | null;
|
|
936
1118
|
}
|
|
937
1119
|
interface CreateSubscriptionBundleData {
|
|
938
1120
|
subscriptionId: string;
|
|
939
1121
|
bundleVersionId: string;
|
|
940
1122
|
startedAt: Date;
|
|
941
|
-
/**
|
|
1123
|
+
/** Null unless a commitment was configured or asked for. */
|
|
942
1124
|
minimumTermEndsAt?: Date | null;
|
|
1125
|
+
/** The rhythm and window worked out above this port. */
|
|
1126
|
+
billingCycle?: string | null;
|
|
1127
|
+
currentPeriodStart?: Date | null;
|
|
1128
|
+
currentPeriodEnd?: Date | null;
|
|
943
1129
|
}
|
|
944
1130
|
interface CancelSubscriptionBundleData {
|
|
945
1131
|
canceledAt: Date;
|
|
@@ -988,7 +1174,6 @@ interface BundlePricingOverride {
|
|
|
988
1174
|
*/
|
|
989
1175
|
interface BundleRow {
|
|
990
1176
|
id: string;
|
|
991
|
-
projectKey: string;
|
|
992
1177
|
bundleKey: string;
|
|
993
1178
|
label: string;
|
|
994
1179
|
description: string | null;
|
|
@@ -1027,7 +1212,6 @@ interface BundleVersionRow extends VersionedEntityBase {
|
|
|
1027
1212
|
* first BundleVersion via `CreateBundleVersionDraftData`.
|
|
1028
1213
|
*/
|
|
1029
1214
|
interface CreateBundleData {
|
|
1030
|
-
projectKey: string;
|
|
1031
1215
|
bundleKey: string;
|
|
1032
1216
|
label: string;
|
|
1033
1217
|
description?: string | null;
|
|
@@ -1036,9 +1220,9 @@ interface CreateBundleData {
|
|
|
1036
1220
|
i18n?: CatalogEntryI18n;
|
|
1037
1221
|
}
|
|
1038
1222
|
/**
|
|
1039
|
-
* Fields that may be changed on the bundle master. `bundleKey`
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1223
|
+
* Fields that may be changed on the bundle master. `bundleKey` is
|
|
1224
|
+
* intentionally not here — master identity is immutable; whoever wants to
|
|
1225
|
+
* change it creates a new bundle and retires the old one.
|
|
1042
1226
|
*/
|
|
1043
1227
|
interface UpdateBundleData {
|
|
1044
1228
|
label?: string;
|
|
@@ -1173,6 +1357,263 @@ interface BundleVersionMutationResult {
|
|
|
1173
1357
|
warnings: StrictModeWarning[];
|
|
1174
1358
|
}
|
|
1175
1359
|
|
|
1360
|
+
/**
|
|
1361
|
+
* The column values a new BundleVersion draft starts from.
|
|
1362
|
+
*
|
|
1363
|
+
* Every adapter has to apply the same defaults — an absent quota map is `{}`,
|
|
1364
|
+
* an absent price is null rather than zero, an unstated `marketed` is true —
|
|
1365
|
+
* and two adapters spelling that out separately is the same decision written
|
|
1366
|
+
* twice. It is also the variant jscpd does catch, which is how this came out:
|
|
1367
|
+
* `adapter-drizzle` learning about bundles put a second copy beside
|
|
1368
|
+
* `adapter-prisma`'s.
|
|
1369
|
+
*
|
|
1370
|
+
* Validity windows are deliberately absent. Whether a draft carries
|
|
1371
|
+
* `validFrom`/`validUntil` is an adapter capability rather than a default, and
|
|
1372
|
+
* an adapter that does not maintain those columns must not write them.
|
|
1373
|
+
*/
|
|
1374
|
+
declare function bundleDraftDefaults(data: CreateBundleVersionDraftData): {
|
|
1375
|
+
baseVersionId: string | null;
|
|
1376
|
+
features: string[];
|
|
1377
|
+
quotas: Record<string, number>;
|
|
1378
|
+
compatibility: Record<string, unknown>;
|
|
1379
|
+
pricingOverrides: unknown[];
|
|
1380
|
+
monthlyNet: string | null;
|
|
1381
|
+
yearlyNet: string | null;
|
|
1382
|
+
marketed: boolean;
|
|
1383
|
+
changeNote: string;
|
|
1384
|
+
createdByUserId: string | null;
|
|
1385
|
+
};
|
|
1386
|
+
/**
|
|
1387
|
+
* The column values a new Bundle stem starts from.
|
|
1388
|
+
*
|
|
1389
|
+
* The same defaulting rule as above, one level up: an absent description or
|
|
1390
|
+
* icon is null rather than an empty string, an unstated sort order is 0, an
|
|
1391
|
+
* absent translation map is `{}`. Written out in five places before this — two
|
|
1392
|
+
* adapters and two fakes — which is four opportunities for one of them to
|
|
1393
|
+
* decide differently.
|
|
1394
|
+
*/
|
|
1395
|
+
declare function bundleStemDefaults(data: CreateBundleData): {
|
|
1396
|
+
bundleKey: string;
|
|
1397
|
+
label: string;
|
|
1398
|
+
description: string | null;
|
|
1399
|
+
icon: string | null;
|
|
1400
|
+
sortOrder: number;
|
|
1401
|
+
i18n: CatalogEntryI18n;
|
|
1402
|
+
};
|
|
1403
|
+
/** The stored shape both adapters read a bundle stem back from. */
|
|
1404
|
+
interface StoredBundleStem {
|
|
1405
|
+
id: string;
|
|
1406
|
+
bundleKey: string;
|
|
1407
|
+
label: string;
|
|
1408
|
+
description: string | null;
|
|
1409
|
+
icon: string | null;
|
|
1410
|
+
sortOrder: number;
|
|
1411
|
+
i18n: unknown;
|
|
1412
|
+
createdAt: Date;
|
|
1413
|
+
updatedAt: Date;
|
|
1414
|
+
deletedAt: Date | null;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* A stored bundle stem as the port describes it.
|
|
1418
|
+
*
|
|
1419
|
+
* The two stores spell the columns identically, so the mapping was identical
|
|
1420
|
+
* too — and an identical mapping in two files is one place for a field to be
|
|
1421
|
+
* forgotten when the row grows. `i18n` arrives as JSON of unknown shape from
|
|
1422
|
+
* both, and a non-object becomes `{}` rather than reaching a caller that
|
|
1423
|
+
* expects a map.
|
|
1424
|
+
*/
|
|
1425
|
+
declare function toBundleStemRow(row: StoredBundleStem): BundleRow;
|
|
1426
|
+
/**
|
|
1427
|
+
* The fields a caller actually gave, as a patch.
|
|
1428
|
+
*
|
|
1429
|
+
* The update DTOs in this codebase mean three different things by three
|
|
1430
|
+
* different values: a value changes the column, an explicit `null` clears it,
|
|
1431
|
+
* and an **omitted** field leaves it alone. Only the last one needs care, and
|
|
1432
|
+
* it was written out as `...(data.x !== undefined ? { x: data.x } : {})` more
|
|
1433
|
+
* than fifty times across five repositories — one decision, fifty
|
|
1434
|
+
* opportunities to spell it differently, and the duplication ratchet is what
|
|
1435
|
+
* finally pointed at it.
|
|
1436
|
+
*
|
|
1437
|
+
* `null` is deliberately kept: it is a value a caller chose, not an absence.
|
|
1438
|
+
*/
|
|
1439
|
+
declare function definedFields<T extends object, K extends keyof T>(data: T, keys: readonly K[]): Partial<Pick<T, K>>;
|
|
1440
|
+
|
|
1441
|
+
/** Backend capability key, convention: domain.action[.action]. */
|
|
1442
|
+
type CapabilityKey = string;
|
|
1443
|
+
/** Frontend action-registry key. Same convention as CapabilityKey. */
|
|
1444
|
+
type ActionKey = CapabilityKey;
|
|
1445
|
+
/** Lookup key in the static extensions: map of the UI build. */
|
|
1446
|
+
type ComponentKey = string;
|
|
1447
|
+
interface AdminManifest {
|
|
1448
|
+
schemaVersion: 1;
|
|
1449
|
+
project: {
|
|
1450
|
+
key: string;
|
|
1451
|
+
displayName: string;
|
|
1452
|
+
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
1453
|
+
label?: string;
|
|
1454
|
+
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
1455
|
+
icon?: string;
|
|
1456
|
+
logoUrl?: string;
|
|
1457
|
+
environment?: 'production' | 'staging' | 'development';
|
|
1458
|
+
/**
|
|
1459
|
+
* Allowed locale pool from the app config (`saas.yaml`
|
|
1460
|
+
* `marketing.availableLocales`). First = default..
|
|
1461
|
+
*/
|
|
1462
|
+
availableLocales?: string[];
|
|
1463
|
+
/** Default locale; equals `availableLocales[0]`. */
|
|
1464
|
+
defaultLocale?: string;
|
|
1465
|
+
};
|
|
1466
|
+
build: {
|
|
1467
|
+
platformPackageVersion: string;
|
|
1468
|
+
appVersion: string;
|
|
1469
|
+
manifestHash: string;
|
|
1470
|
+
};
|
|
1471
|
+
planCatalogSnapshot: {
|
|
1472
|
+
source: string;
|
|
1473
|
+
hash: string;
|
|
1474
|
+
currency: string;
|
|
1475
|
+
vatRate: number;
|
|
1476
|
+
features?: FeatureDef[];
|
|
1477
|
+
plans: PlanDef[];
|
|
1478
|
+
};
|
|
1479
|
+
/** Map CapabilityKey → boolean. Manifest is never a security source. */
|
|
1480
|
+
capabilities: Record<CapabilityKey, boolean>;
|
|
1481
|
+
navigation: {
|
|
1482
|
+
standardPages: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
1483
|
+
projectPages?: ProjectPageDef[];
|
|
1484
|
+
};
|
|
1485
|
+
dashboard?: {
|
|
1486
|
+
kpiCards?: KpiCardDef[];
|
|
1487
|
+
};
|
|
1488
|
+
tenants?: {
|
|
1489
|
+
columns?: TenantColumnDef[];
|
|
1490
|
+
actions?: TenantActionDef[];
|
|
1491
|
+
};
|
|
1492
|
+
audit?: {
|
|
1493
|
+
actions?: AuditActionDef[];
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
type StandardPageKey = 'dashboard' | 'tenants' | 'subscriptions' | 'promoCodes' | 'plans' | 'audit' | 'users' | 'pilots' | 'discovery' | 'bundles' | 'marketingCatalog' | 'platformEmail' | 'platformEmailHistory' | 'settings';
|
|
1497
|
+
interface StandardPageDef {
|
|
1498
|
+
enabled: boolean;
|
|
1499
|
+
requiredCapability?: CapabilityKey;
|
|
1500
|
+
}
|
|
1501
|
+
interface ProjectPageDef {
|
|
1502
|
+
/** `<app>.<area>`, e.g. `demoapp.datev`. */
|
|
1503
|
+
id: string;
|
|
1504
|
+
label: string;
|
|
1505
|
+
icon?: string;
|
|
1506
|
+
/** Frontend route, e.g. `/admin/datev`. */
|
|
1507
|
+
route: string;
|
|
1508
|
+
navSection?: string;
|
|
1509
|
+
/** Lookup in the static extensions: map of the shell build. */
|
|
1510
|
+
componentKey: ComponentKey;
|
|
1511
|
+
requiredCapability?: CapabilityKey;
|
|
1512
|
+
prefetchOnIdle?: boolean;
|
|
1513
|
+
}
|
|
1514
|
+
interface KpiCardDef {
|
|
1515
|
+
id: string;
|
|
1516
|
+
label: string;
|
|
1517
|
+
/** Required path: /api/v1/admin/(extras|dashboard)/... */
|
|
1518
|
+
endpoint: string;
|
|
1519
|
+
displayHint: KpiDisplayHint;
|
|
1520
|
+
/** 0–100; UI sorts descending. */
|
|
1521
|
+
slotPriority?: number;
|
|
1522
|
+
requiredCapability?: CapabilityKey;
|
|
1523
|
+
}
|
|
1524
|
+
interface KpiDisplayHint {
|
|
1525
|
+
type: 'value' | 'value+timestamp' | 'value+spark8w' | 'value+delta';
|
|
1526
|
+
icon?: string;
|
|
1527
|
+
}
|
|
1528
|
+
interface TenantColumnDef {
|
|
1529
|
+
key: string;
|
|
1530
|
+
label: string;
|
|
1531
|
+
/** Required path: /api/v1/admin/extras/...; MUST be batch-capable, no {slug}/{tenantId}. */
|
|
1532
|
+
endpoint: string;
|
|
1533
|
+
requiredCapability?: CapabilityKey;
|
|
1534
|
+
}
|
|
1535
|
+
interface TenantActionDef {
|
|
1536
|
+
/** `<app>.<area>.<verb>`, e.g. `demoapp.datev.runExport`. */
|
|
1537
|
+
id: string;
|
|
1538
|
+
label: string;
|
|
1539
|
+
/** Lookup in the static actions: map of the shell build. */
|
|
1540
|
+
actionKey: ActionKey;
|
|
1541
|
+
requiredCapability?: CapabilityKey;
|
|
1542
|
+
requiresMfa?: boolean;
|
|
1543
|
+
confirmType?: 'none' | 'simple' | 'typed-slug' | 'typed-production' | 'date';
|
|
1544
|
+
}
|
|
1545
|
+
interface AuditActionDef {
|
|
1546
|
+
/** SCREAMING_SNAKE_CASE; matched to the AuditLog.action column. */
|
|
1547
|
+
key: string;
|
|
1548
|
+
label: string;
|
|
1549
|
+
severity?: 'info' | 'low' | 'medium' | 'high';
|
|
1550
|
+
}
|
|
1551
|
+
interface ManifestContribution {
|
|
1552
|
+
capabilities?: Record<CapabilityKey, boolean>;
|
|
1553
|
+
navigation?: {
|
|
1554
|
+
standardPages?: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
1555
|
+
projectPages?: ProjectPageDef[];
|
|
1556
|
+
};
|
|
1557
|
+
dashboard?: {
|
|
1558
|
+
kpiCards?: KpiCardDef[];
|
|
1559
|
+
};
|
|
1560
|
+
tenants?: {
|
|
1561
|
+
columns?: TenantColumnDef[];
|
|
1562
|
+
actions?: TenantActionDef[];
|
|
1563
|
+
};
|
|
1564
|
+
audit?: {
|
|
1565
|
+
actions?: AuditActionDef[];
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
interface PublicBootResponse {
|
|
1569
|
+
project: {
|
|
1570
|
+
key: string;
|
|
1571
|
+
displayName: string;
|
|
1572
|
+
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
1573
|
+
label?: string;
|
|
1574
|
+
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
1575
|
+
icon?: string;
|
|
1576
|
+
logoUrl?: string;
|
|
1577
|
+
environment?: 'production' | 'staging' | 'development';
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/** Format: 'web:<email>:<sessionId>' or 'cli:<email>:<host>'. */
|
|
1582
|
+
type ActorTag = string;
|
|
1583
|
+
interface AuditEntry {
|
|
1584
|
+
id: string;
|
|
1585
|
+
/** null = platform action without tenant context (SUPER_ADMIN). */
|
|
1586
|
+
tenantId: string | null;
|
|
1587
|
+
/** null = system / cron-triggered. */
|
|
1588
|
+
userId: string | null;
|
|
1589
|
+
/** Convenience field; backend resolves it from userId. */
|
|
1590
|
+
userEmail: string | null;
|
|
1591
|
+
/** e.g. 'Tenant', 'PromoCode', 'Subscription', 'PlanVersion', 'User'. */
|
|
1592
|
+
entity: string;
|
|
1593
|
+
entityId: string;
|
|
1594
|
+
/** SCREAMING_SNAKE_CASE; past-tense oriented. */
|
|
1595
|
+
action: string;
|
|
1596
|
+
/** Freely structured. Convention: { field: { old, new } } or { reason, ... }. */
|
|
1597
|
+
changes: Record<string, unknown> | null;
|
|
1598
|
+
actorTag: ActorTag | null;
|
|
1599
|
+
ipAddress: string | null;
|
|
1600
|
+
userAgent: string | null;
|
|
1601
|
+
createdAt: string;
|
|
1602
|
+
}
|
|
1603
|
+
interface AuditQuery {
|
|
1604
|
+
tenantId?: string;
|
|
1605
|
+
userId?: string;
|
|
1606
|
+
entity?: string;
|
|
1607
|
+
entityId?: string;
|
|
1608
|
+
action?: string;
|
|
1609
|
+
/** Wildcard-capable, e.g. 'cli:*'. */
|
|
1610
|
+
actorTag?: string;
|
|
1611
|
+
from?: string;
|
|
1612
|
+
to?: string;
|
|
1613
|
+
page?: number;
|
|
1614
|
+
pageSize?: number;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1176
1617
|
/** Promotion type. */
|
|
1177
1618
|
type PromotionType = 'percent' | 'amount' | 'intro' | 'freeMonths';
|
|
1178
1619
|
/** Billing cycle for which the promotion applies. */
|
|
@@ -1201,7 +1642,6 @@ type PromotionI18n = Record<string, PromotionI18nFields>;
|
|
|
1201
1642
|
/** Wire format of a `promotions` row. */
|
|
1202
1643
|
interface PromotionRow {
|
|
1203
1644
|
id: string;
|
|
1204
|
-
projectKey: string;
|
|
1205
1645
|
/** Internal label (not public). */
|
|
1206
1646
|
internalLabel: string;
|
|
1207
1647
|
type: PromotionType;
|
|
@@ -1227,11 +1667,7 @@ interface PromotionRow {
|
|
|
1227
1667
|
createdAt: string;
|
|
1228
1668
|
updatedAt: string;
|
|
1229
1669
|
}
|
|
1230
|
-
interface PromotionFilter {
|
|
1231
|
-
projectKey: string;
|
|
1232
|
-
}
|
|
1233
1670
|
interface CreatePromotionData {
|
|
1234
|
-
projectKey: string;
|
|
1235
1671
|
internalLabel: string;
|
|
1236
1672
|
type: PromotionType;
|
|
1237
1673
|
value: PromotionValue;
|
|
@@ -1346,6 +1782,7 @@ interface CheckoutOfferPriceBreakdown {
|
|
|
1346
1782
|
regularNet: number;
|
|
1347
1783
|
/** Net total after promo. */
|
|
1348
1784
|
effectiveNet: number;
|
|
1785
|
+
/** VAT rate in percent, as the plan catalogue names it (19 = 19 %). */
|
|
1349
1786
|
vatRate: number;
|
|
1350
1787
|
/** Gross total after promo. */
|
|
1351
1788
|
effectiveGross: number;
|
|
@@ -1354,7 +1791,6 @@ type CheckoutOfferStatus = 'open' | 'consumed' | 'expired';
|
|
|
1354
1791
|
/** Wire format of a `checkout_offers` row. */
|
|
1355
1792
|
interface CheckoutOfferRow {
|
|
1356
1793
|
id: string;
|
|
1357
|
-
projectKey: string;
|
|
1358
1794
|
/** Plan selected on the website. */
|
|
1359
1795
|
planKey: string;
|
|
1360
1796
|
/** Resolved plan version, if known. */
|
|
@@ -1362,7 +1798,7 @@ interface CheckoutOfferRow {
|
|
|
1362
1798
|
billingCycle: 'monthly' | 'yearly';
|
|
1363
1799
|
/** Applied promotion (active at offer time). */
|
|
1364
1800
|
promotionId: string | null;
|
|
1365
|
-
/**
|
|
1801
|
+
/** Promo code applied to the offer, as the promo module normalised it. */
|
|
1366
1802
|
promoCode: string | null;
|
|
1367
1803
|
/** Added bundle keys. Legacy display; V3 uses `bundleVersionIds` + `lineItems`. */
|
|
1368
1804
|
bundles: string[];
|
|
@@ -1385,12 +1821,44 @@ interface CheckoutOfferRow {
|
|
|
1385
1821
|
updatedAt: string;
|
|
1386
1822
|
}
|
|
1387
1823
|
interface CheckoutOfferFilter {
|
|
1388
|
-
projectKey: string;
|
|
1389
1824
|
status?: CheckoutOfferStatus;
|
|
1390
1825
|
}
|
|
1391
|
-
/**
|
|
1826
|
+
/**
|
|
1827
|
+
* What a caller chooses: the body of `POST /public/checkout-offer`, and the
|
|
1828
|
+
* input of `CheckoutOfferService.create`.
|
|
1829
|
+
*
|
|
1830
|
+
* No amount is part of it. The plan version, the bundle prices, the promotion
|
|
1831
|
+
* and the promo code discount are resolved on the server, so the offer costs
|
|
1832
|
+
* what the catalogue says rather than what a request says.
|
|
1833
|
+
*/
|
|
1834
|
+
interface CheckoutOfferSelection {
|
|
1835
|
+
planKey: string;
|
|
1836
|
+
billingCycle: 'monthly' | 'yearly';
|
|
1837
|
+
/** Concrete BundleVersion IDs to book with the plan. */
|
|
1838
|
+
bundleVersionIds?: string[];
|
|
1839
|
+
/** A promo code to apply; refused when the promo module cannot accept it. */
|
|
1840
|
+
promoCode?: string | null;
|
|
1841
|
+
locale?: string;
|
|
1842
|
+
validUntil?: string | null;
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* What a caller may change while an offer is open: the body of
|
|
1846
|
+
* `PATCH /public/checkout-offer/:id`. The plan is fixed; everything given here
|
|
1847
|
+
* is priced again.
|
|
1848
|
+
*/
|
|
1849
|
+
interface CheckoutOfferSelectionUpdate {
|
|
1850
|
+
billingCycle?: 'monthly' | 'yearly';
|
|
1851
|
+
bundleVersionIds?: string[];
|
|
1852
|
+
/** `null` removes a code applied before. */
|
|
1853
|
+
promoCode?: string | null;
|
|
1854
|
+
locale?: string;
|
|
1855
|
+
validUntil?: string | null;
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* A new offer as the repository stores it — the selection with the amounts
|
|
1859
|
+
* the server computed for it (`CheckoutOfferRepository.create`).
|
|
1860
|
+
*/
|
|
1392
1861
|
interface CreateCheckoutOfferData {
|
|
1393
|
-
projectKey: string;
|
|
1394
1862
|
planKey: string;
|
|
1395
1863
|
planVersionId?: string | null;
|
|
1396
1864
|
billingCycle: 'monthly' | 'yearly';
|
|
@@ -1406,11 +1874,13 @@ interface CreateCheckoutOfferData {
|
|
|
1406
1874
|
validUntil?: string | null;
|
|
1407
1875
|
}
|
|
1408
1876
|
/**
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1411
|
-
* sets them server-side.
|
|
1877
|
+
* A change to an open offer as the repository stores it, with the amounts
|
|
1878
|
+
* priced again (`CheckoutOfferRepository.update`). `status`/`consumedAt` are
|
|
1879
|
+
* not editable — `consume()` sets them server-side.
|
|
1412
1880
|
*/
|
|
1413
1881
|
interface UpdateCheckoutOfferData {
|
|
1882
|
+
/** The plan version active when the change was priced. */
|
|
1883
|
+
planVersionId?: string | null;
|
|
1414
1884
|
billingCycle?: 'monthly' | 'yearly';
|
|
1415
1885
|
promotionId?: string | null;
|
|
1416
1886
|
promoCode?: string | null;
|
|
@@ -1426,7 +1896,6 @@ interface UpdateCheckoutOfferData {
|
|
|
1426
1896
|
|
|
1427
1897
|
/** Wire format of the `marketing_settings` row. */
|
|
1428
1898
|
interface MarketingSettingsRow {
|
|
1429
|
-
projectKey: string;
|
|
1430
1899
|
/** Runtime-activated subset of the `availableLocales` pool. */
|
|
1431
1900
|
activeLocales: string[];
|
|
1432
1901
|
updatedAt: string;
|
|
@@ -1462,6 +1931,10 @@ interface PublicMarketingPlan {
|
|
|
1462
1931
|
badge: string;
|
|
1463
1932
|
/** Teaser / description text. */
|
|
1464
1933
|
description: string;
|
|
1934
|
+
/**
|
|
1935
|
+
* The recommended plan, and at most one card in a catalogue carries it —
|
|
1936
|
+
* see `keepOneRecommended`, which decides it per language served.
|
|
1937
|
+
*/
|
|
1465
1938
|
highlight: boolean;
|
|
1466
1939
|
/**
|
|
1467
1940
|
* Formatted pricing tag from the MarketingProjection (#47, e.g.
|
|
@@ -1544,7 +2017,6 @@ interface PublicComparisonRow {
|
|
|
1544
2017
|
}
|
|
1545
2018
|
/** Response of `GET /public/marketing-catalog`. */
|
|
1546
2019
|
interface PublicMarketingCatalogResponse {
|
|
1547
|
-
projectKey: string;
|
|
1548
2020
|
locale: string;
|
|
1549
2021
|
currency: string;
|
|
1550
2022
|
/** VAT rate in percent — for the CheckoutOffer price breakdown. */
|
|
@@ -1654,7 +2126,7 @@ interface DiscoverySnapshot {
|
|
|
1654
2126
|
/** ISO timestamp of the boot-time scan. */
|
|
1655
2127
|
scannedAt: string;
|
|
1656
2128
|
app: {
|
|
1657
|
-
/**
|
|
2129
|
+
/** The application's name, from `saas.yaml#app.name`. */
|
|
1658
2130
|
key: string;
|
|
1659
2131
|
/** Backend version, e.g. from package.json. */
|
|
1660
2132
|
version: string;
|
|
@@ -1690,6 +2162,18 @@ interface FeatureUiMeta {
|
|
|
1690
2162
|
/** Map FeatureKey → UI metadata. Consumer apps supply a complete table. */
|
|
1691
2163
|
type FeatureUiRegistry = Record<string, FeatureUiMeta>;
|
|
1692
2164
|
|
|
2165
|
+
/** A quota as a finite number, or `null` where the value cannot be read as one. */
|
|
2166
|
+
declare function readQuotaValue(value: unknown): number | null;
|
|
2167
|
+
/**
|
|
2168
|
+
* Every quota in a JSON column, for a caller that computes with them.
|
|
2169
|
+
*
|
|
2170
|
+
* A key that is there stays there. Dropping an unreadable one made it *absent*,
|
|
2171
|
+
* and absent means undeclared: `enforceLimit` answers an undeclared dimension
|
|
2172
|
+
* with a 500, so every operation on that quota was refused — a fail-closed
|
|
2173
|
+
* answer to somebody else's corrupt row.
|
|
2174
|
+
*/
|
|
2175
|
+
declare function readQuotaRecord(value: unknown): Record<string, number>;
|
|
2176
|
+
|
|
1693
2177
|
/** Alias for historical compatibility — equivalent to VersionChangeDirection. */
|
|
1694
2178
|
type ChangeDirection = VersionChangeDirection;
|
|
1695
2179
|
interface DiffResult {
|
|
@@ -1707,9 +2191,16 @@ type DecimalLike = number | string | {
|
|
|
1707
2191
|
};
|
|
1708
2192
|
interface PlanVersionFields {
|
|
1709
2193
|
features: FeatureKey[];
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2194
|
+
/**
|
|
2195
|
+
* Quotas of the version. -1 = unlimited; missing key = 0.
|
|
2196
|
+
*
|
|
2197
|
+
* Every key either side carries is compared. Which keys exist is the
|
|
2198
|
+
* installation's decision — they come from `@DefinesQuota` — so a fixed
|
|
2199
|
+
* set here would have compared the three the platform happened to know by
|
|
2200
|
+
* name and let every other one be lowered without the confirmation
|
|
2201
|
+
* publishing a regression asks for.
|
|
2202
|
+
*/
|
|
2203
|
+
quotas: Record<QuotaKey, number>;
|
|
1713
2204
|
monthlyNet: DecimalLike;
|
|
1714
2205
|
yearlyNet: DecimalLike;
|
|
1715
2206
|
}
|
|
@@ -1725,8 +2216,7 @@ declare function classifyPlanDiff(oldV: PlanVersionFields, newV: PlanVersionFiel
|
|
|
1725
2216
|
/**
|
|
1726
2217
|
* Classification of a BundleVersion diff for contract protection.
|
|
1727
2218
|
*
|
|
1728
|
-
*
|
|
1729
|
-
* number. Otherwise higher = better. Missing keys are treated as 0.
|
|
2219
|
+
* Quotas are compared exactly as they are for a plan.
|
|
1730
2220
|
*
|
|
1731
2221
|
* Pricing can be `null` (the bundle only has override pricing); a switch
|
|
1732
2222
|
* from value ↔ null is classified as REGRESSION (value dropped) or IMPROVEMENT
|
|
@@ -1769,6 +2259,11 @@ interface PromoPreviewValidResponse {
|
|
|
1769
2259
|
/** Decimal-as-string, e.g. "199.00". */
|
|
1770
2260
|
originalGross: string;
|
|
1771
2261
|
discountGross: string;
|
|
2262
|
+
/**
|
|
2263
|
+
* `discountGross` in net, converted at the installation's VAT rate —
|
|
2264
|
+
* the figure a page showing net prices takes off the plan price.
|
|
2265
|
+
*/
|
|
2266
|
+
discountNet: string;
|
|
1772
2267
|
discountedGross: string;
|
|
1773
2268
|
includedVat: string;
|
|
1774
2269
|
nextRegularAmountGross: string;
|
|
@@ -1824,9 +2319,94 @@ interface OnboardingPromoRedemption {
|
|
|
1824
2319
|
endsAt: string | null;
|
|
1825
2320
|
}
|
|
1826
2321
|
|
|
2322
|
+
/**
|
|
2323
|
+
* The settings subtree of a plan catalogue: every top-level block that is
|
|
2324
|
+
* configuration rather than the catalogue itself. JSON-shaped, because it is
|
|
2325
|
+
* stored as JSON and compared as JSON.
|
|
2326
|
+
*/
|
|
2327
|
+
type AppliedSettingsValues = Record<string, unknown>;
|
|
2328
|
+
/** The one row per installation: what is applied, since when, and from where. */
|
|
2329
|
+
interface AppliedSettingsRecord {
|
|
2330
|
+
/**
|
|
2331
|
+
* `sha256-<hex>` over the canonical JSON of `settings`. Two boots with the
|
|
2332
|
+
* same resolved values produce the same fingerprint however the file was
|
|
2333
|
+
* formatted, and a plan added to the catalogue does not move it.
|
|
2334
|
+
*/
|
|
2335
|
+
fingerprint: string;
|
|
2336
|
+
settings: AppliedSettingsValues;
|
|
2337
|
+
/**
|
|
2338
|
+
* Where the values came from: the absolute path of the file the platform
|
|
2339
|
+
* read, or a phrase saying they were handed to it in code.
|
|
2340
|
+
*/
|
|
2341
|
+
source: string;
|
|
2342
|
+
/** The moment these values became the running configuration. */
|
|
2343
|
+
appliedAt: Date;
|
|
2344
|
+
}
|
|
2345
|
+
/** What a boot noticed had changed since the previous record. */
|
|
2346
|
+
interface SettingsChangeRecord {
|
|
2347
|
+
id: string;
|
|
2348
|
+
/** The boot that noticed the difference and applied the new values. */
|
|
2349
|
+
noticedAt: Date;
|
|
2350
|
+
source: string;
|
|
2351
|
+
previous: AppliedSettingsValues;
|
|
2352
|
+
current: AppliedSettingsValues;
|
|
2353
|
+
/** Set once an operator has seen it; null while it is still owed a look. */
|
|
2354
|
+
acknowledgedAt: Date | null;
|
|
2355
|
+
/** Who acknowledged it — an actor tag, as the audit log writes it. */
|
|
2356
|
+
acknowledgedBy: string | null;
|
|
2357
|
+
}
|
|
2358
|
+
type NewSettingsChange = Pick<SettingsChangeRecord, 'noticedAt' | 'source' | 'previous' | 'current'>;
|
|
2359
|
+
/** One leaf that differs between two settings subtrees. */
|
|
2360
|
+
interface SettingsDifference {
|
|
2361
|
+
/** Dotted path, as the loader names a field: `tenantBilling.cancellationNoticeDays.monthly`. */
|
|
2362
|
+
path: string;
|
|
2363
|
+
/** `undefined` where the leaf did not exist on that side. */
|
|
2364
|
+
before: unknown;
|
|
2365
|
+
after: unknown;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
/**
|
|
2369
|
+
* The top-level blocks of `config/saas.yaml` that are the catalogue rather than
|
|
2370
|
+
* the configuration, and the format marker.
|
|
2371
|
+
*
|
|
2372
|
+
* An exclusion list rather than a list of settings, on purpose: a block the
|
|
2373
|
+
* schema gains tomorrow is a setting until somebody says otherwise, so it is
|
|
2374
|
+
* fingerprinted by default. The failure mode of the other list — a new setting
|
|
2375
|
+
* silently left out of the fingerprint, so a change to it is never noticed — is
|
|
2376
|
+
* the one this record exists to prevent. `schemaVersion` is excluded because a
|
|
2377
|
+
* format change is a migration of the file, not a decision an operator took.
|
|
2378
|
+
*
|
|
2379
|
+
* `tests/settings-subtree.test.js` holds this list to the schema in both
|
|
2380
|
+
* directions: every name here is a property the schema declares, and every
|
|
2381
|
+
* property the schema declares lands on one side.
|
|
2382
|
+
*/
|
|
2383
|
+
declare const CATALOGUE_KEYS: ReadonlySet<keyof PlanCatalog>;
|
|
2384
|
+
/**
|
|
2385
|
+
* The settings of a catalogue, typed, for handing them on as a whole.
|
|
2386
|
+
*
|
|
2387
|
+
* The same selection as `settingsSubtreeOf`, which is why it is that function:
|
|
2388
|
+
* a caller that listed the blocks it passes on would drop the next one the
|
|
2389
|
+
* schema gains, and nothing would say so.
|
|
2390
|
+
*/
|
|
2391
|
+
declare function planCatalogSettingsOf(catalog: PlanCatalog): PlanCatalogSettings;
|
|
2392
|
+
/** Everything in the catalogue that is configuration, as it was resolved. */
|
|
2393
|
+
declare function settingsSubtreeOf(catalog: PlanCatalog): AppliedSettingsValues;
|
|
2394
|
+
/**
|
|
2395
|
+
* `JSON.stringify` with object keys in sorted order at every depth, so that two
|
|
2396
|
+
* documents saying the same thing in a different order serialise identically.
|
|
2397
|
+
* Array order is kept: a list is what its author wrote, in the order they wrote
|
|
2398
|
+
* it.
|
|
2399
|
+
*/
|
|
2400
|
+
declare function canonicalJson(value: unknown): string;
|
|
2401
|
+
/**
|
|
2402
|
+
* Every leaf that differs between `before` and `after`, in the order the paths
|
|
2403
|
+
* sort. A list counts as one leaf: `asTarget: [] → [ENTERPRISE]` is one thing
|
|
2404
|
+
* that changed, not a change per element.
|
|
2405
|
+
*/
|
|
2406
|
+
declare function diffSettings(before: AppliedSettingsValues, after: AppliedSettingsValues): SettingsDifference[];
|
|
2407
|
+
|
|
1827
2408
|
interface PlanRow {
|
|
1828
2409
|
id: string;
|
|
1829
|
-
projectKey: string;
|
|
1830
2410
|
planKey: string;
|
|
1831
2411
|
label: string;
|
|
1832
2412
|
description: string | null;
|
|
@@ -1843,7 +2423,6 @@ interface PlanRow {
|
|
|
1843
2423
|
* creation (follows in M6 Pack 2).
|
|
1844
2424
|
*/
|
|
1845
2425
|
interface CreatePlanData {
|
|
1846
|
-
projectKey: string;
|
|
1847
2426
|
planKey: string;
|
|
1848
2427
|
label: string;
|
|
1849
2428
|
description?: string | null;
|
|
@@ -1851,9 +2430,9 @@ interface CreatePlanData {
|
|
|
1851
2430
|
sortOrder?: number;
|
|
1852
2431
|
}
|
|
1853
2432
|
/**
|
|
1854
|
-
* Fields that may be changed on the plan stem. `planKey`
|
|
1855
|
-
*
|
|
1856
|
-
*
|
|
2433
|
+
* Fields that may be changed on the plan stem. `planKey` is deliberately not
|
|
2434
|
+
* here — stem identity is immutable; whoever wants to change it creates a new
|
|
2435
|
+
* plan and retires the old one.
|
|
1857
2436
|
*/
|
|
1858
2437
|
interface UpdatePlanData {
|
|
1859
2438
|
label?: string;
|
|
@@ -1917,7 +2496,6 @@ interface UpsertResult {
|
|
|
1917
2496
|
skipReason?: string;
|
|
1918
2497
|
}
|
|
1919
2498
|
interface UpsertPlanInput {
|
|
1920
|
-
projectKey: string;
|
|
1921
2499
|
planKey: string;
|
|
1922
2500
|
label: string;
|
|
1923
2501
|
description?: string | null;
|
|
@@ -1937,7 +2515,6 @@ interface UpsertPlanVersionInput {
|
|
|
1937
2515
|
changeNote: string;
|
|
1938
2516
|
}
|
|
1939
2517
|
interface UpsertFeatureCatalogEntryInput {
|
|
1940
|
-
projectKey: string;
|
|
1941
2518
|
featureKey: FeatureKey;
|
|
1942
2519
|
label?: string;
|
|
1943
2520
|
icon?: string;
|
|
@@ -1983,7 +2560,7 @@ interface PlanCatalogReadSnapshot {
|
|
|
1983
2560
|
* implement it against their Prisma tables.
|
|
1984
2561
|
*/
|
|
1985
2562
|
interface PlanCatalogReadSink {
|
|
1986
|
-
loadSnapshot(
|
|
2563
|
+
loadSnapshot(): Promise<PlanCatalogReadSnapshot>;
|
|
1987
2564
|
}
|
|
1988
2565
|
|
|
1989
2566
|
/**
|
|
@@ -2220,6 +2797,26 @@ interface PasswordHasher {
|
|
|
2220
2797
|
hash(plain: string): Promise<string>;
|
|
2221
2798
|
verify(hash: string, plain: string): Promise<boolean>;
|
|
2222
2799
|
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Sends a plain-text mail to an operator.
|
|
2802
|
+
*
|
|
2803
|
+
* The platform composes the text; the adapter delivers it — over whatever the
|
|
2804
|
+
* installation already sends mail with. Deliberately narrow: no templates, no
|
|
2805
|
+
* locale, no HTML. The one thing the platform mails today is a diagnostic for
|
|
2806
|
+
* the operator who runs the installation, and a diagnostic is English and
|
|
2807
|
+
* plain, like the boot log it mirrors. Tenant-facing mail — a verification
|
|
2808
|
+
* code, a resume link — goes through the registration module's own delivery
|
|
2809
|
+
* ports, which carry the locale and the person's name because that mail is
|
|
2810
|
+
* for a customer.
|
|
2811
|
+
*/
|
|
2812
|
+
interface EmailPort {
|
|
2813
|
+
/** Delivers one plain-text mail to one address; rejects when it cannot. */
|
|
2814
|
+
send(message: {
|
|
2815
|
+
to: string;
|
|
2816
|
+
subject: string;
|
|
2817
|
+
text: string;
|
|
2818
|
+
}): Promise<void>;
|
|
2819
|
+
}
|
|
2223
2820
|
/** Adapter for MFA secret persistence. */
|
|
2224
2821
|
interface MfaPort {
|
|
2225
2822
|
/** Returns the stored TOTP secret or null. */
|
|
@@ -2427,6 +3024,26 @@ interface PromoRevenueDeductionAggregator {
|
|
|
2427
3024
|
|
|
2428
3025
|
type ContractLineItemKind = 'plan' | 'bundle' | 'discount';
|
|
2429
3026
|
type SubscriptionContractStatus = 'active' | 'scheduled' | 'terminated' | 'superseded';
|
|
3027
|
+
/**
|
|
3028
|
+
* The statuses a contract is looked up under when asking "what is this tenant
|
|
3029
|
+
* on right now" — `scheduled` included, because a contract that starts today
|
|
3030
|
+
* and has not been switched to `active` yet is still the one in force at its
|
|
3031
|
+
* own `effectiveFrom`.
|
|
3032
|
+
*
|
|
3033
|
+
* One list rather than one per adapter: the two adapters have to answer
|
|
3034
|
+
* `findActiveByTenantId` the same way, and a status added here must not reach
|
|
3035
|
+
* only whichever of them somebody remembered.
|
|
3036
|
+
*/
|
|
3037
|
+
/**
|
|
3038
|
+
* How many bundle versions one price lookup may name.
|
|
3039
|
+
*
|
|
3040
|
+
* One number rather than two: the server validates against it and the client
|
|
3041
|
+
* batches to stay inside it, and a client that learned the cap by receiving a
|
|
3042
|
+
* 400 would fail silently — the lookup answers with an empty map, and every
|
|
3043
|
+
* card falls back to a catalogue price the tenant may not be charged.
|
|
3044
|
+
*/
|
|
3045
|
+
declare const BUNDLE_PRICE_LOOKUP_LIMIT = 200;
|
|
3046
|
+
declare const ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES: readonly SubscriptionContractStatus[];
|
|
2430
3047
|
interface ContractLineItemRecord {
|
|
2431
3048
|
id: string;
|
|
2432
3049
|
contractId: string;
|
|
@@ -2440,6 +3057,26 @@ interface ContractLineItemRecord {
|
|
|
2440
3057
|
priceNet: number;
|
|
2441
3058
|
priceGross: number;
|
|
2442
3059
|
billingCycle: 'monthly' | 'yearly';
|
|
3060
|
+
/**
|
|
3061
|
+
* ISO 4217, as the line was booked in.
|
|
3062
|
+
*
|
|
3063
|
+
* An installation sells in one currency at a time, so this is never a
|
|
3064
|
+
* choice the line makes — it is what keeps the line meaning what it meant
|
|
3065
|
+
* after the configured currency is migrated to another one.
|
|
3066
|
+
*/
|
|
3067
|
+
currency: string;
|
|
3068
|
+
/**
|
|
3069
|
+
* The tax rate in percent that was applied, recorded rather than left in
|
|
3070
|
+
* the ratio between net and gross. That ratio is not the rate: it cannot be
|
|
3071
|
+
* reproduced for a rounded gross, cannot express an exempt or reverse-charge
|
|
3072
|
+
* line, and does not survive a rate change.
|
|
3073
|
+
*/
|
|
3074
|
+
taxRate: number;
|
|
3075
|
+
/**
|
|
3076
|
+
* The tax contained in the line — exactly `priceGross - priceNet`, so the
|
|
3077
|
+
* line cannot disagree with itself. Rounded once, when the line is written.
|
|
3078
|
+
*/
|
|
3079
|
+
taxAmount: number;
|
|
2443
3080
|
minimumTermUntil: Date | null;
|
|
2444
3081
|
featuresSnapshot: string[];
|
|
2445
3082
|
quotaEffectsSnapshot: Record<string, number>;
|
|
@@ -2452,13 +3089,47 @@ interface SubscriptionContractPriceSnapshot {
|
|
|
2452
3089
|
subtotalNet: number;
|
|
2453
3090
|
discountNet: number;
|
|
2454
3091
|
totalNet: number;
|
|
3092
|
+
/**
|
|
3093
|
+
* The tax rate this contract's total was computed at, as a percentage:
|
|
3094
|
+
* 19 means 19 %, as every tax rate in SaaSiCat is.
|
|
3095
|
+
*/
|
|
2455
3096
|
vatRate: number;
|
|
2456
3097
|
totalGross: number;
|
|
2457
3098
|
}
|
|
2458
|
-
|
|
3099
|
+
/**
|
|
3100
|
+
* The subscriber as a contract copied it on the day it was concluded.
|
|
3101
|
+
*
|
|
3102
|
+
* The invoice email is not part of it: it says how the party is reached, not
|
|
3103
|
+
* who the party is, and a contract is kept for years after an address like that
|
|
3104
|
+
* stopped mattering.
|
|
3105
|
+
*/
|
|
3106
|
+
interface ContractSubscriberParty extends LegalIdentity, PartyAddress {
|
|
3107
|
+
customerNumber: string;
|
|
3108
|
+
}
|
|
3109
|
+
/** The issuer as `config/saas.yaml` named it on the day a contract was concluded. */
|
|
3110
|
+
interface ContractIssuerParty extends LegalIdentity, PartyAddress {
|
|
3111
|
+
}
|
|
3112
|
+
/** Who a contract is between, copied when it is concluded. */
|
|
3113
|
+
interface SubscriptionContractParties {
|
|
3114
|
+
subscriberId: string;
|
|
3115
|
+
subscriber: ContractSubscriberParty;
|
|
3116
|
+
/** `null` where `config/saas.yaml` named no issuer that day. */
|
|
3117
|
+
issuer: ContractIssuerParty | null;
|
|
3118
|
+
}
|
|
3119
|
+
interface SubscriptionContractRecord extends SubscriptionContractParties {
|
|
2459
3120
|
id: string;
|
|
2460
|
-
|
|
3121
|
+
/**
|
|
3122
|
+
* The tenant the contract was concluded for, kept as a trace. The contract
|
|
3123
|
+
* belongs to its subscriber and outlives the tenant.
|
|
3124
|
+
*/
|
|
2461
3125
|
tenantId: string;
|
|
3126
|
+
/**
|
|
3127
|
+
* The parties were copied by the migration that attached contracts
|
|
3128
|
+
* concluded before subscribers existed, not on the day the contract was
|
|
3129
|
+
* concluded. Either party may have changed in between, so such a copy is
|
|
3130
|
+
* never presented as what was agreed.
|
|
3131
|
+
*/
|
|
3132
|
+
partiesMigrated: boolean;
|
|
2462
3133
|
status: SubscriptionContractStatus;
|
|
2463
3134
|
effectiveFrom: Date;
|
|
2464
3135
|
effectiveUntil: Date | null;
|
|
@@ -2475,8 +3146,12 @@ interface SubscriptionContractRecord {
|
|
|
2475
3146
|
updatedAt: Date;
|
|
2476
3147
|
}
|
|
2477
3148
|
type NewContractLineItemData = Omit<ContractLineItemRecord, 'id' | 'contractId' | 'createdAt'>;
|
|
3149
|
+
/**
|
|
3150
|
+
* A contract as a caller asks for it. The parties are not among it: the
|
|
3151
|
+
* platform copies them from the tenant's subscriber and the configuration when
|
|
3152
|
+
* the contract is written, so no caller can name a party of its own.
|
|
3153
|
+
*/
|
|
2478
3154
|
interface CreateSubscriptionContractData {
|
|
2479
|
-
projectKey: string;
|
|
2480
3155
|
tenantId: string;
|
|
2481
3156
|
status?: SubscriptionContractStatus;
|
|
2482
3157
|
effectiveFrom: Date;
|
|
@@ -2491,16 +3166,53 @@ interface CreateSubscriptionContractData {
|
|
|
2491
3166
|
termsSnapshot?: Record<string, unknown> | null;
|
|
2492
3167
|
lineItems: NewContractLineItemData[];
|
|
2493
3168
|
}
|
|
3169
|
+
/** What a repository writes: the contract as asked for, with the parties the platform copied. */
|
|
3170
|
+
interface NewSubscriptionContractData extends CreateSubscriptionContractData {
|
|
3171
|
+
parties: SubscriptionContractParties;
|
|
3172
|
+
}
|
|
2494
3173
|
interface TerminateSubscriptionContractData {
|
|
2495
3174
|
effectiveUntil: Date;
|
|
2496
|
-
|
|
3175
|
+
/**
|
|
3176
|
+
* The terminal status, or `null` to end the contract by date alone.
|
|
3177
|
+
*
|
|
3178
|
+
* `findActiveByTenantId` already asks its question as a window —
|
|
3179
|
+
* `effectiveFrom <= asOf` and `effectiveUntil` null or after it — so a
|
|
3180
|
+
* contract given an end in the FUTURE is found until that moment and not
|
|
3181
|
+
* afterwards, with no scheduled job to flip anything.
|
|
3182
|
+
*
|
|
3183
|
+
* Writing a terminal status instead makes the contract disappear from that
|
|
3184
|
+
* lookup at once, which for a cancellation declared months ahead removes an
|
|
3185
|
+
* agreement the customer is still under. Null is how a caller says "it ends
|
|
3186
|
+
* then", and a status is how it says "it is over now".
|
|
3187
|
+
*/
|
|
3188
|
+
status: Extract<SubscriptionContractStatus, 'terminated' | 'superseded'> | null;
|
|
2497
3189
|
}
|
|
2498
3190
|
interface SubscriptionContractFilter {
|
|
2499
|
-
projectKey?: string;
|
|
2500
3191
|
tenantId?: string;
|
|
2501
3192
|
status?: SubscriptionContractStatus;
|
|
2502
3193
|
asOf?: Date;
|
|
2503
3194
|
}
|
|
3195
|
+
/** One contract still running, and the issuer it was concluded under. */
|
|
3196
|
+
interface RunningContractIssuer {
|
|
3197
|
+
id: string;
|
|
3198
|
+
/** The tenant it was concluded for, kept as a trace. */
|
|
3199
|
+
tenantId: string;
|
|
3200
|
+
/**
|
|
3201
|
+
* The legal name on the issuer copy, or `null` where the contract names no
|
|
3202
|
+
* issuer — it was concluded while `config/saas.yaml` named none, or its
|
|
3203
|
+
* party copy was made by the migration that attached contracts concluded
|
|
3204
|
+
* before subscribers existed.
|
|
3205
|
+
*/
|
|
3206
|
+
issuerLegalName: string | null;
|
|
3207
|
+
effectiveFrom: Date;
|
|
3208
|
+
}
|
|
3209
|
+
/** How many contracts are concluded and not yet over, and the first few of them. */
|
|
3210
|
+
interface RunningContractIssuers {
|
|
3211
|
+
/** All of them, whether or not the list below holds them all. */
|
|
3212
|
+
total: number;
|
|
3213
|
+
/** At most the limit the caller asked for, oldest first. */
|
|
3214
|
+
contracts: RunningContractIssuer[];
|
|
3215
|
+
}
|
|
2504
3216
|
interface InvoiceLineItemSnapshot {
|
|
2505
3217
|
sourceContractLineItemId: string;
|
|
2506
3218
|
sourceKey: string;
|
|
@@ -2513,12 +3225,14 @@ interface InvoiceLineItemSnapshot {
|
|
|
2513
3225
|
priceNet: number;
|
|
2514
3226
|
priceGross: number;
|
|
2515
3227
|
billingCycle: 'monthly' | 'yearly';
|
|
3228
|
+
currency: string;
|
|
3229
|
+
taxRate: number;
|
|
3230
|
+
taxAmount: number;
|
|
2516
3231
|
minimumTermUntil: Date | null;
|
|
2517
3232
|
metadata: Record<string, unknown> | null;
|
|
2518
3233
|
}
|
|
2519
3234
|
interface SubscriptionContractInvoiceSnapshot {
|
|
2520
3235
|
contractId: string;
|
|
2521
|
-
projectKey: string;
|
|
2522
3236
|
tenantId: string;
|
|
2523
3237
|
originalOfferId: string | null;
|
|
2524
3238
|
currency: string;
|
|
@@ -2533,6 +3247,100 @@ interface SubscriptionContractInvoiceSnapshot {
|
|
|
2533
3247
|
lineItems: InvoiceLineItemSnapshot[];
|
|
2534
3248
|
}
|
|
2535
3249
|
|
|
3250
|
+
/** How a subscriber is reached, which may change at any time. */
|
|
3251
|
+
interface SubscriberContact extends PartyAddress {
|
|
3252
|
+
/** Where invoices will be sent. */
|
|
3253
|
+
invoiceEmail: string | null;
|
|
3254
|
+
}
|
|
3255
|
+
/** A subscriber's master data, as it stands. */
|
|
3256
|
+
type SubscriberDetails = LegalIdentity & SubscriberContact;
|
|
3257
|
+
/**
|
|
3258
|
+
* What a subscriber is created with.
|
|
3259
|
+
*
|
|
3260
|
+
* Only the legal name is required. The address, the tax identifiers and the
|
|
3261
|
+
* invoice email stay optional until sign-up asks for them and invoicing
|
|
3262
|
+
* requires them; an absent or blank value is recorded as unknown.
|
|
3263
|
+
*/
|
|
3264
|
+
type NewSubscriberDetails = Pick<SubscriberDetails, 'legalName'> & Partial<Omit<SubscriberDetails, 'legalName'>>;
|
|
3265
|
+
interface SubscriberRecord extends SubscriberDetails {
|
|
3266
|
+
id: string;
|
|
3267
|
+
/**
|
|
3268
|
+
* Assigned when the subscriber is created and never changed: the number,
|
|
3269
|
+
* counted per installation, behind the prefix configured at that moment.
|
|
3270
|
+
*/
|
|
3271
|
+
customerNumber: string;
|
|
3272
|
+
/** The tenant this subscriber is live for, or `null` once it has none. */
|
|
3273
|
+
tenantId: string | null;
|
|
3274
|
+
/**
|
|
3275
|
+
* Created by the migration that gave every existing tenant its subscriber,
|
|
3276
|
+
* from the application's own tenant record rather than from what a customer
|
|
3277
|
+
* entered.
|
|
3278
|
+
*/
|
|
3279
|
+
migrated: boolean;
|
|
3280
|
+
createdAt: Date;
|
|
3281
|
+
updatedAt: Date;
|
|
3282
|
+
}
|
|
3283
|
+
/** What a repository writes for a new subscriber, every detail already settled. */
|
|
3284
|
+
interface CreateSubscriberData extends SubscriberDetails {
|
|
3285
|
+
/** The tenant the subscriber is created for and live with. */
|
|
3286
|
+
tenantId: string;
|
|
3287
|
+
/** Put in front of the assigned number; empty for the number alone. */
|
|
3288
|
+
customerNumberPrefix: string;
|
|
3289
|
+
}
|
|
3290
|
+
/** Contact details to change; a member left out keeps its value. */
|
|
3291
|
+
type SubscriberContactChange = Partial<SubscriberContact>;
|
|
3292
|
+
/**
|
|
3293
|
+
* A change to a subscriber's legal identity, and what the operator declares it
|
|
3294
|
+
* to be.
|
|
3295
|
+
*
|
|
3296
|
+
* SaaSiCat cannot tell a misspelt name from another company taking over, so the
|
|
3297
|
+
* operator says which: `correction` for the same legal entity — a typo, a wrong
|
|
3298
|
+
* tax identifier, a change of name that entity went through — and `takeover`
|
|
3299
|
+
* for another one, which is a transfer rather than an edit and is refused.
|
|
3300
|
+
*/
|
|
3301
|
+
interface SubscriberIdentityCorrection {
|
|
3302
|
+
kind: 'correction' | 'takeover';
|
|
3303
|
+
legalName?: string;
|
|
3304
|
+
vatId?: string | null;
|
|
3305
|
+
taxNumber?: string | null;
|
|
3306
|
+
/** Why the identity is corrected. Part of the record. */
|
|
3307
|
+
reason: string;
|
|
3308
|
+
/** Who corrects it, as an actor tag the audit log would write. */
|
|
3309
|
+
correctedBy: string;
|
|
3310
|
+
}
|
|
3311
|
+
/** Identity values by field, holding only the fields a correction changed. */
|
|
3312
|
+
type SubscriberIdentityValues = Partial<LegalIdentity>;
|
|
3313
|
+
/** What a correction replaces and what it writes, holding only the fields that move. */
|
|
3314
|
+
interface SubscriberIdentityDelta {
|
|
3315
|
+
previous: SubscriberIdentityValues;
|
|
3316
|
+
corrected: SubscriberIdentityValues;
|
|
3317
|
+
}
|
|
3318
|
+
/** What a repository writes for a correction the service has accepted. */
|
|
3319
|
+
interface SubscriberCorrectionData {
|
|
3320
|
+
/** The new values; a field equal to what is stored is not recorded. */
|
|
3321
|
+
corrected: SubscriberIdentityValues;
|
|
3322
|
+
reason: string;
|
|
3323
|
+
correctedBy: string;
|
|
3324
|
+
correctedAt: Date;
|
|
3325
|
+
}
|
|
3326
|
+
/** One correction of a subscriber's legal identity, as it was recorded. */
|
|
3327
|
+
interface SubscriberCorrectionRecord {
|
|
3328
|
+
id: string;
|
|
3329
|
+
subscriberId: string;
|
|
3330
|
+
/** The values the correction replaced. */
|
|
3331
|
+
previous: SubscriberIdentityValues;
|
|
3332
|
+
/** The values it wrote. */
|
|
3333
|
+
corrected: SubscriberIdentityValues;
|
|
3334
|
+
reason: string;
|
|
3335
|
+
correctedBy: string;
|
|
3336
|
+
correctedAt: Date;
|
|
3337
|
+
}
|
|
3338
|
+
/** The outcome of writing a correction: nothing is recorded when no value moved. */
|
|
3339
|
+
interface SubscriberCorrectionResult {
|
|
3340
|
+
subscriber: SubscriberRecord;
|
|
3341
|
+
correction: SubscriberCorrectionRecord | null;
|
|
3342
|
+
}
|
|
3343
|
+
|
|
2536
3344
|
/**
|
|
2537
3345
|
* Snapshot form of a `Subscription` row for the EntitlementService
|
|
2538
3346
|
* computation. The consumer maps its Prisma structure onto this form.
|
|
@@ -2552,6 +3360,24 @@ interface SubscriptionRecord {
|
|
|
2552
3360
|
} | null;
|
|
2553
3361
|
planVersionId: string;
|
|
2554
3362
|
planVersion: PlanVersionRecord;
|
|
3363
|
+
/**
|
|
3364
|
+
* When a cancellation was declared, and when it takes effect.
|
|
3365
|
+
*
|
|
3366
|
+
* Required, and required together, because entitlement resolution ends a
|
|
3367
|
+
* subscription by reading them: without the second date it cannot tell a
|
|
3368
|
+
* subscription that ends next January from one that ended last January, and
|
|
3369
|
+
* it grants the latter everything. Nothing else in the platform would
|
|
3370
|
+
* notice — no repository filters a cancelled subscription out, and stopping
|
|
3371
|
+
* the billing period is a different decision from ending what a tenant may
|
|
3372
|
+
* do.
|
|
3373
|
+
*
|
|
3374
|
+
* `null` on both means no cancellation. On a row written before the two
|
|
3375
|
+
* fields separated, `canceledAt` carries the effective date and
|
|
3376
|
+
* `canceledEffectiveAt` is genuinely null; every reader in the platform
|
|
3377
|
+
* applies `canceledEffectiveAt ?? canceledAt` for that reason.
|
|
3378
|
+
*/
|
|
3379
|
+
canceledAt: Date | null;
|
|
3380
|
+
canceledEffectiveAt: Date | null;
|
|
2555
3381
|
}
|
|
2556
3382
|
/** Snapshot of a `PlanVersion` row. */
|
|
2557
3383
|
interface PlanVersionRecord {
|
|
@@ -2608,11 +3434,10 @@ interface SubscriptionRepository {
|
|
|
2608
3434
|
countByBundleVersionId?(bundleVersionId: string): Promise<number>;
|
|
2609
3435
|
/**
|
|
2610
3436
|
* Counts active subscriptions (status `ACTIVE` or `TRIAL`) per plan key,
|
|
2611
|
-
* platform-wide across
|
|
2612
|
-
*
|
|
3437
|
+
* platform-wide across every tenant — feeds the tenant column of the
|
|
3438
|
+
* SuperAdmin plan list (`GET /admin/catalog/plans/tenant-counts`).
|
|
2613
3439
|
* Cross-version: counts the plan, not a single PlanVersion
|
|
2614
|
-
* (subscriptions on superseded versions are included).
|
|
2615
|
-
* informational for single-project consumers.
|
|
3440
|
+
* (subscriptions on superseded versions are included).
|
|
2616
3441
|
*
|
|
2617
3442
|
* Returns a map `planKey → count`; plans without an active subscription
|
|
2618
3443
|
* are missing (UI defaults to 0). Platform-wide count across all tenants →
|
|
@@ -2620,7 +3445,7 @@ interface SubscriptionRepository {
|
|
|
2620
3445
|
*
|
|
2621
3446
|
* Optional — if not implemented, the tenant column stays 0.
|
|
2622
3447
|
*/
|
|
2623
|
-
countActiveByPlanKey?(
|
|
3448
|
+
countActiveByPlanKey?(): Promise<Record<string, number>>;
|
|
2624
3449
|
}
|
|
2625
3450
|
/**
|
|
2626
3451
|
* Adapter for the `subscription_bundles` junction.
|
|
@@ -2679,8 +3504,89 @@ interface SubscriptionContractRepository {
|
|
|
2679
3504
|
* instead of drawing an extra pool connection (starvation guard, #70).
|
|
2680
3505
|
*/
|
|
2681
3506
|
findActiveByTenantId(tenantId: string, asOf?: Date, tx?: TransactionContext): Promise<SubscriptionContractRecord | null>;
|
|
2682
|
-
|
|
3507
|
+
/**
|
|
3508
|
+
* Writes the contract with the parties it names. With `tx`, the contract is
|
|
3509
|
+
* written on that transaction and undone with it.
|
|
3510
|
+
*/
|
|
3511
|
+
create(data: NewSubscriptionContractData, tx?: TransactionContext): Promise<SubscriptionContractRecord>;
|
|
3512
|
+
/**
|
|
3513
|
+
* The contract concluded from a checkout offer (`originalOfferId`), or
|
|
3514
|
+
* `null` when none was. An offer is consumed once and so yields one
|
|
3515
|
+
* contract (`SC-MKT-017`); where an application's own path wrote two, the
|
|
3516
|
+
* earliest is returned, so the answer does not depend on read order.
|
|
3517
|
+
*/
|
|
3518
|
+
findByOriginalOfferId(offerId: string, tx?: TransactionContext): Promise<SubscriptionContractRecord | null>;
|
|
2683
3519
|
terminate(contractId: string, data: TerminateSubscriptionContractData): Promise<SubscriptionContractRecord>;
|
|
3520
|
+
/**
|
|
3521
|
+
* The contracts concluded and not yet over, with the issuer each was
|
|
3522
|
+
* concluded under: how many there are, and the first `limit` of them,
|
|
3523
|
+
* oldest first. `limit` caps the list and not the count, so a start refused
|
|
3524
|
+
* over a changed issuer identity says how many contracts it means before it
|
|
3525
|
+
* names any of them.
|
|
3526
|
+
*
|
|
3527
|
+
* Running means `active` or `scheduled` AND not ended at `asOf` — the same
|
|
3528
|
+
* window `findActiveByTenantId` uses on its upper end, and for the same
|
|
3529
|
+
* reason. Status alone is not enough: an ordinary cancellation lands at the
|
|
3530
|
+
* term end and writes only `effectiveUntil`, leaving the status where it
|
|
3531
|
+
* was, and nothing flips it when that day arrives. Counting by status would
|
|
3532
|
+
* therefore report every customer who ever left as still running — and
|
|
3533
|
+
* because the list is oldest first, the ones it names would be exactly the
|
|
3534
|
+
* expired ones.
|
|
3535
|
+
*
|
|
3536
|
+
* The window is open at the bottom on purpose: a contract that starts next
|
|
3537
|
+
* month is concluded, its party copy is fixed, and it will be invoiced under
|
|
3538
|
+
* the issuer it names.
|
|
3539
|
+
*
|
|
3540
|
+
* Platform-wide: unlike every other read here it is anchored by no tenant,
|
|
3541
|
+
* no contract and no offer, and a start makes it before anything is served.
|
|
3542
|
+
* An implementation on a tenant-scoped client must count RLS-exempt, as
|
|
3543
|
+
* `countActiveByPlanKey` must — the platform wraps the call in
|
|
3544
|
+
* `RlsBypassPort`, and one that answers with the caller's tenant scope
|
|
3545
|
+
* instead returns nothing at a boot, where there is no tenant. The
|
|
3546
|
+
* persistence contract runs with no policy forced, so it cannot catch that
|
|
3547
|
+
* for you.
|
|
3548
|
+
*
|
|
3549
|
+
* `limit` may be `0`, and a caller that wants only the count passes it:
|
|
3550
|
+
* `total` is exact whatever the limit, so nothing has to come back for it.
|
|
3551
|
+
* Zero means zero — an implementation that reads a falsy limit as "no limit"
|
|
3552
|
+
* returns every running contract to a caller asking for none, which is the
|
|
3553
|
+
* one shape of this method that gets slower the more an installation sells.
|
|
3554
|
+
* The executable contract asks for `0`.
|
|
3555
|
+
*/
|
|
3556
|
+
listRunningIssuers(limit: number, asOf?: Date): Promise<RunningContractIssuers>;
|
|
3557
|
+
}
|
|
3558
|
+
/**
|
|
3559
|
+
* The parties contracts are concluded with, their link to the tenant they are
|
|
3560
|
+
* live for, and the corrections of their legal identity.
|
|
3561
|
+
*
|
|
3562
|
+
* A subscriber has at most one live tenant and a tenant at most one live
|
|
3563
|
+
* subscriber; the database holds both, so two callers creating one for the
|
|
3564
|
+
* same tenant at once end with one.
|
|
3565
|
+
*/
|
|
3566
|
+
interface SubscriberRepository {
|
|
3567
|
+
/**
|
|
3568
|
+
* Creates a subscriber, assigns its customer number, and makes it the
|
|
3569
|
+
* tenant's live subscriber — all or nothing. `null` when the tenant already
|
|
3570
|
+
* has a live subscriber, in which case nothing is written and the caller's
|
|
3571
|
+
* transaction stays usable.
|
|
3572
|
+
*/
|
|
3573
|
+
createForTenant(data: CreateSubscriberData, tx?: TransactionContext): Promise<SubscriberRecord | null>;
|
|
3574
|
+
findById(subscriberId: string, tx?: TransactionContext): Promise<SubscriberRecord | null>;
|
|
3575
|
+
/** The subscriber live for this tenant, or `null` when it has none. */
|
|
3576
|
+
findByTenantId(tenantId: string, tx?: TransactionContext): Promise<SubscriberRecord | null>;
|
|
3577
|
+
/** Writes the members given and keeps the rest; `null` when no such subscriber exists. */
|
|
3578
|
+
updateContact(subscriberId: string, change: SubscriberContactChange, tx?: TransactionContext): Promise<SubscriberRecord | null>;
|
|
3579
|
+
/**
|
|
3580
|
+
* Writes a correction of the legal identity and records it, in one step:
|
|
3581
|
+
* the subscriber is read and changed under a lock, so the values recorded
|
|
3582
|
+
* as replaced are the ones this write replaced even when two corrections
|
|
3583
|
+
* arrive at once. A field whose stored value already equals the corrected
|
|
3584
|
+
* one is left out of the record, and when none differs nothing is written
|
|
3585
|
+
* and `correction` is `null`. `null` when no such subscriber exists.
|
|
3586
|
+
*/
|
|
3587
|
+
correctIdentity(subscriberId: string, data: SubscriberCorrectionData, tx?: TransactionContext): Promise<SubscriberCorrectionResult | null>;
|
|
3588
|
+
/** Every correction of this subscriber, the latest first. */
|
|
3589
|
+
listCorrections(subscriberId: string): Promise<SubscriberCorrectionRecord[]>;
|
|
2684
3590
|
}
|
|
2685
3591
|
/**
|
|
2686
3592
|
* Display form of a subscription for the tenant self-service UI.
|
|
@@ -2712,6 +3618,40 @@ interface SubscriptionUsageRecord {
|
|
|
2712
3618
|
/** Current period window — for proration and change-effective date. */
|
|
2713
3619
|
currentPeriodStart: Date | null;
|
|
2714
3620
|
currentPeriodEnd: Date | null;
|
|
3621
|
+
/**
|
|
3622
|
+
* End of what was committed to, which the period end need not equal.
|
|
3623
|
+
*
|
|
3624
|
+
* The cancellation rules measure against this: a subscription cancelled
|
|
3625
|
+
* inside its term keeps running until the term ends, not until the period
|
|
3626
|
+
* does. Null on a trial, and on any subscription written before the field
|
|
3627
|
+
* existed — readers treat that as "the period end is the answer".
|
|
3628
|
+
*/
|
|
3629
|
+
minimumTermUntil?: Date | null;
|
|
3630
|
+
/**
|
|
3631
|
+
* The day of the month the subscription is billed on, 1–31.
|
|
3632
|
+
*
|
|
3633
|
+
* Read by the cancellation rules: a declaration after the notice window
|
|
3634
|
+
* lands one period past the term end, and that step has to measure from the
|
|
3635
|
+
* billing day rather than from a term end that may already have been
|
|
3636
|
+
* clamped by a short month.
|
|
3637
|
+
*
|
|
3638
|
+
* Optional, because an adapter that does not store the column keeps today's
|
|
3639
|
+
* behaviour — the step then takes its day from the term end, which is
|
|
3640
|
+
* correct except in the month after a clamp.
|
|
3641
|
+
*/
|
|
3642
|
+
billingAnchorDay?: number | null;
|
|
3643
|
+
/**
|
|
3644
|
+
* When a cancellation was declared, and when it lands.
|
|
3645
|
+
*
|
|
3646
|
+
* Required for the reason the same pair is required on
|
|
3647
|
+
* `SubscriptionRecord`: the tenant billing route reads them to refuse a
|
|
3648
|
+
* plan change on a subscription that has ended, and a record that omits
|
|
3649
|
+
* them answers "not cancelled" — so the change is applied and prorated
|
|
3650
|
+
* while entitlement resolution, which reads a record that does carry them,
|
|
3651
|
+
* grants nothing.
|
|
3652
|
+
*/
|
|
3653
|
+
canceledAt: Date | null;
|
|
3654
|
+
canceledEffectiveAt: Date | null;
|
|
2715
3655
|
pendingPlan: string | null;
|
|
2716
3656
|
pendingBillingCycle: string | null;
|
|
2717
3657
|
pendingEffectiveAt: Date | null;
|
|
@@ -2787,12 +3727,29 @@ interface ImmediatePlanChangeInput {
|
|
|
2787
3727
|
* change, or target package without trial). A `Date` is persisted.
|
|
2788
3728
|
*/
|
|
2789
3729
|
trialEndsAt?: Date | null;
|
|
3730
|
+
/**
|
|
3731
|
+
* `canceledAt` as the caller read it, so the write can claim the row only
|
|
3732
|
+
* while that is still true.
|
|
3733
|
+
*
|
|
3734
|
+
* Three of the plan route's decisions depend on the cancellation — whether
|
|
3735
|
+
* the change is refused at all, whether the billing cycle may move, and
|
|
3736
|
+
* whether a fresh period is opened — and a read and a write are two
|
|
3737
|
+
* moments. A cancellation declared in between made every one of them answer
|
|
3738
|
+
* about a state that no longer existed, and the write went ahead anyway: a
|
|
3739
|
+
* plan term recorded past the date the subscription ends.
|
|
3740
|
+
*
|
|
3741
|
+
* `null` is a value here rather than an absence. It claims a row that has
|
|
3742
|
+
* no cancellation, and loses against one that has acquired one.
|
|
3743
|
+
*/
|
|
3744
|
+
expectedCanceledAt: Date | null;
|
|
2790
3745
|
}
|
|
2791
3746
|
/** Input for `schedulePlanChange` (change at period end). */
|
|
2792
3747
|
interface ScheduledPlanChangeInput {
|
|
2793
3748
|
pendingPlan: string;
|
|
2794
3749
|
pendingBillingCycle: string;
|
|
2795
3750
|
pendingEffectiveAt: Date;
|
|
3751
|
+
/** See `ImmediatePlanChangeInput.expectedCanceledAt`. */
|
|
3752
|
+
expectedCanceledAt: Date | null;
|
|
2796
3753
|
}
|
|
2797
3754
|
/**
|
|
2798
3755
|
* Input for `applyOnboardingSelection`. Plan-change fields that the
|
|
@@ -2801,6 +3758,12 @@ interface ScheduledPlanChangeInput {
|
|
|
2801
3758
|
interface ApplyOnboardingSelectionInput {
|
|
2802
3759
|
planId: string;
|
|
2803
3760
|
cycle: string;
|
|
3761
|
+
/**
|
|
3762
|
+
* See `ImmediatePlanChangeInput.expectedCanceledAt`. The atomic path needs
|
|
3763
|
+
* it for the same reason the sequential one does: without it, the preferred
|
|
3764
|
+
* implementation is the one where the race stays open.
|
|
3765
|
+
*/
|
|
3766
|
+
expectedCanceledAt: Date | null;
|
|
2804
3767
|
/** For TRIAL → null, otherwise period start from `initialPeriodWindow`. */
|
|
2805
3768
|
periodStart: Date | null;
|
|
2806
3769
|
periodEnd: Date | null;
|
|
@@ -2817,6 +3780,12 @@ interface ApplyOnboardingSelectionResult {
|
|
|
2817
3780
|
subscriptionId: string;
|
|
2818
3781
|
/** null if no redeemPromo callback was provided or the callback returned null. */
|
|
2819
3782
|
promoRedemption: PromoCodeRedemptionRecord | null;
|
|
3783
|
+
/**
|
|
3784
|
+
* False when the row's cancellation moved since the caller read it, in
|
|
3785
|
+
* which case nothing was written — including the promo redemption, which
|
|
3786
|
+
* shares the transaction.
|
|
3787
|
+
*/
|
|
3788
|
+
claimed: boolean;
|
|
2820
3789
|
}
|
|
2821
3790
|
/**
|
|
2822
3791
|
* Callback signature for promo-code redemption WITHIN the onboarding
|
|
@@ -2834,14 +3803,46 @@ type RedeemPromoInTransactionCallback = (tx: TransactionContext, subscriptionId:
|
|
|
2834
3803
|
* app-specific. The platform service calls `invalidateTenant` in the
|
|
2835
3804
|
* EntitlementService after a successful adapter call.
|
|
2836
3805
|
*/
|
|
3806
|
+
/** What `cancelSubscription` is told to write. Named so both adapters spell
|
|
3807
|
+
* the same shape once rather than each restating it. */
|
|
3808
|
+
interface CancelSubscriptionInput {
|
|
3809
|
+
canceledAt: Date;
|
|
3810
|
+
effectiveAt: Date;
|
|
3811
|
+
terminateNow: boolean;
|
|
3812
|
+
minimumTermUntil?: Date;
|
|
3813
|
+
}
|
|
3814
|
+
/** What `cancelSubscription` answers with. */
|
|
3815
|
+
interface CancelSubscriptionResult {
|
|
3816
|
+
canceledAt: Date | null;
|
|
3817
|
+
canceledEffectiveAt: Date | null;
|
|
3818
|
+
status: string;
|
|
3819
|
+
/**
|
|
3820
|
+
* True when a cancellation was already recorded and this call changed
|
|
3821
|
+
* nothing — the stored dates are returned instead.
|
|
3822
|
+
*
|
|
3823
|
+
* The caller checks first, but a check and a write are two moments, and two
|
|
3824
|
+
* requests can pass the check before either writes. Straddling a notice
|
|
3825
|
+
* deadline that costs a billing cycle: the first declaration lands on time,
|
|
3826
|
+
* the second recomputes against a later `now`, and an unconditional write
|
|
3827
|
+
* replaces the first answer with one a period further out. An
|
|
3828
|
+
* implementation therefore claims the row only while both cancellation
|
|
3829
|
+
* fields are still empty, and answers `true` here when the claim finds
|
|
3830
|
+
* nothing to claim.
|
|
3831
|
+
*/
|
|
3832
|
+
alreadyCanceled: boolean;
|
|
3833
|
+
}
|
|
2837
3834
|
interface TenantSubscriptionWritePort {
|
|
2838
3835
|
/** Immediate change: set plan + cycle, clear pending fields, optionally reset the period. */
|
|
2839
3836
|
changePlanImmediate(tenantId: string, input: ImmediatePlanChangeInput): Promise<{
|
|
2840
3837
|
plan: string;
|
|
2841
3838
|
billingCycle: string;
|
|
3839
|
+
/** False when the row's cancellation moved since the caller read it. */
|
|
3840
|
+
claimed: boolean;
|
|
2842
3841
|
}>;
|
|
2843
3842
|
/** Change at period end: set pending fields. */
|
|
2844
|
-
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<
|
|
3843
|
+
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<{
|
|
3844
|
+
claimed: boolean;
|
|
3845
|
+
}>;
|
|
2845
3846
|
/**
|
|
2846
3847
|
* Marks the pending PlanVersion as accepted. Idempotent — a duplicate
|
|
2847
3848
|
* accept is a no-op. Returns `alreadyAccepted: true` if the status was
|
|
@@ -2854,13 +3855,30 @@ interface TenantSubscriptionWritePort {
|
|
|
2854
3855
|
alreadyAccepted: boolean;
|
|
2855
3856
|
}>;
|
|
2856
3857
|
/**
|
|
2857
|
-
*
|
|
2858
|
-
*
|
|
3858
|
+
* Record a cancellation. The dates are decided above this port.
|
|
3859
|
+
*
|
|
3860
|
+
* `canceledAt` is when the customer said it; `effectiveAt` is when it
|
|
3861
|
+
* lands. They differ for every ordinary cancellation, because a
|
|
3862
|
+
* subscription cancelled inside its term keeps running, keeps being billed
|
|
3863
|
+
* and keeps its entitlements until the term ends. An adapter that computed
|
|
3864
|
+
* the second from the first — which this one did, as
|
|
3865
|
+
* `immediate ? now : currentPeriodEnd` — was deciding a commercial
|
|
3866
|
+
* question in a persistence layer, and could not see the minimum term or
|
|
3867
|
+
* the notice period at all.
|
|
3868
|
+
*
|
|
3869
|
+
* `terminateNow` flips the status immediately, and is set when the
|
|
3870
|
+
* cancellation is already effective: an operator ending a contract, or the
|
|
3871
|
+
* rules finding nothing left to run — no period, no term, as on a trial.
|
|
3872
|
+
* It is never a client's request. A tenant may always declare a
|
|
3873
|
+
* cancellation and may never shorten the term they are in; what decides
|
|
3874
|
+
* this flag is the date the rules returned, not the date they asked for.
|
|
3875
|
+
*
|
|
3876
|
+
* `minimumTermUntil` extends the stored commitment, and is set only when
|
|
3877
|
+
* the cancellation itself extends it: a declaration made after the notice
|
|
3878
|
+
* deadline buys the following period. Left unset the stored term end is
|
|
3879
|
+
* unchanged, which is the ordinary case.
|
|
2859
3880
|
*/
|
|
2860
|
-
cancelSubscription(tenantId: string,
|
|
2861
|
-
canceledAt: Date | null;
|
|
2862
|
-
status: string;
|
|
2863
|
-
}>;
|
|
3881
|
+
cancelSubscription(tenantId: string, input: CancelSubscriptionInput): Promise<CancelSubscriptionResult>;
|
|
2864
3882
|
/**
|
|
2865
3883
|
* Atomic onboarding creation: sets plan + cycle + period window
|
|
2866
3884
|
* AND optionally calls a promo-redeem callback — all in a
|
|
@@ -2883,6 +3901,8 @@ interface PlanVersionRepository {
|
|
|
2883
3901
|
*
|
|
2884
3902
|
* Note: ignores `validFrom`/`validUntil`. For time-aware
|
|
2885
3903
|
* resolution (onboarding, plan fallback for TRIAL) use `findActive`.
|
|
3904
|
+
*
|
|
3905
|
+
* A plan key no plan has finds `null`, not an error.
|
|
2886
3906
|
*/
|
|
2887
3907
|
findLatestLive(planId: string, tx?: TransactionContext): Promise<PlanVersionRecord | null>;
|
|
2888
3908
|
/**
|
|
@@ -2895,6 +3915,8 @@ interface PlanVersionRepository {
|
|
|
2895
3915
|
* return the highest `validFrom`, explicitly ordering null start dates
|
|
2896
3916
|
* last as a legacy fallback. Adapters without validity columns may omit
|
|
2897
3917
|
* the method (consumers fall back to `findLatestLive`).
|
|
3918
|
+
*
|
|
3919
|
+
* A plan key no plan has finds `null`, not an error.
|
|
2898
3920
|
*/
|
|
2899
3921
|
findActive?(planId: string, asOf?: Date, tx?: TransactionContext): Promise<PlanVersionRecord | null>;
|
|
2900
3922
|
}
|
|
@@ -3100,11 +4122,18 @@ interface ManifestAccessPort {
|
|
|
3100
4122
|
* Adapter for the RLS bypass context. Platform code calls `runWithBypass`,
|
|
3101
4123
|
* the consumer implementation triggers the Postgres session variable
|
|
3102
4124
|
* (`set_config('app.bypass_rls', 'true', true)`) or the equivalent in
|
|
3103
|
-
* Django/other stacks. The execution context lives for
|
|
3104
|
-
*
|
|
4125
|
+
* Django/other stacks. The execution context lives for the call it wraps
|
|
4126
|
+
* (AsyncLocalStorage / `contextvars` etc.).
|
|
3105
4127
|
*
|
|
3106
4128
|
* SuperAdmin operations are platform-wide without tenant scope — without
|
|
3107
4129
|
* bypass, all RLS-protected reads would come back empty.
|
|
4130
|
+
*
|
|
4131
|
+
* **Not only inside a request.** The platform's boot checks read platform-wide
|
|
4132
|
+
* before anything is served: an implementation that reaches for a
|
|
4133
|
+
* request-scoped connection, or that asserts a request context is open, fails
|
|
4134
|
+
* at start rather than where it is called. Wrap the call the way it is given —
|
|
4135
|
+
* `AsyncLocalStorage.run` is the shape both shipped adapters use, and it is as
|
|
4136
|
+
* good at boot as it is in a request.
|
|
3108
4137
|
*/
|
|
3109
4138
|
interface RlsBypassPort {
|
|
3110
4139
|
runWithBypass<T>(fn: () => Promise<T>): Promise<T>;
|
|
@@ -3112,7 +4141,6 @@ interface RlsBypassPort {
|
|
|
3112
4141
|
|
|
3113
4142
|
/** Filter for `PlanRepository.list()`. */
|
|
3114
4143
|
interface PlanListFilter {
|
|
3115
|
-
projectKey: string;
|
|
3116
4144
|
/** Exclude soft-deleted plans — default `true`. */
|
|
3117
4145
|
excludeDeleted?: boolean;
|
|
3118
4146
|
/**
|
|
@@ -3146,7 +4174,7 @@ interface PlanListFilter {
|
|
|
3146
4174
|
interface PlanRepository {
|
|
3147
4175
|
list(filter: PlanListFilter): Promise<PlanRow[]>;
|
|
3148
4176
|
findById(planId: string): Promise<PlanRow | null>;
|
|
3149
|
-
findByKey(
|
|
4177
|
+
findByKey(planKey: string): Promise<PlanRow | null>;
|
|
3150
4178
|
create(data: CreatePlanData): Promise<PlanRow>;
|
|
3151
4179
|
update(planId: string, data: UpdatePlanData): Promise<PlanRow>;
|
|
3152
4180
|
/** Sets `deletedAt` to NOW(); soft-deleted plans are filtered from `list` by default. */
|
|
@@ -3247,10 +4275,24 @@ interface PlanRepository {
|
|
|
3247
4275
|
}
|
|
3248
4276
|
/** Filter for `BundleRepository.list()`. */
|
|
3249
4277
|
interface BundleListFilter {
|
|
3250
|
-
projectKey: string;
|
|
3251
4278
|
/** Exclude soft-deleted bundles — default `true`. */
|
|
3252
4279
|
excludeDeleted?: boolean;
|
|
3253
4280
|
}
|
|
4281
|
+
/**
|
|
4282
|
+
* What publishing a bundle draft records.
|
|
4283
|
+
*
|
|
4284
|
+
* Named because it was written out three times — the port, and each adapter's
|
|
4285
|
+
* implementation of it — and a signature restated is a contract restated: the
|
|
4286
|
+
* copies can drift, and nothing but a reader would notice.
|
|
4287
|
+
*/
|
|
4288
|
+
interface PublishBundleVersionMeta {
|
|
4289
|
+
publishedByUserId: string | null;
|
|
4290
|
+
publishedChanges: VersionChange[];
|
|
4291
|
+
nonRegressive: boolean;
|
|
4292
|
+
/** Required — validated by the service before the repository call. */
|
|
4293
|
+
validFrom: Date;
|
|
4294
|
+
validUntil: Date | null;
|
|
4295
|
+
}
|
|
3254
4296
|
/**
|
|
3255
4297
|
* Adapter for `Bundle` + `BundleVersion` persistence. Consumers implement
|
|
3256
4298
|
* this against their Prisma tables (`bundles` + `bundle_versions`).
|
|
@@ -3269,7 +4311,7 @@ interface BundleListFilter {
|
|
|
3269
4311
|
interface BundleRepository {
|
|
3270
4312
|
list(filter: BundleListFilter): Promise<BundleRow[]>;
|
|
3271
4313
|
findById(bundleId: string): Promise<BundleRow | null>;
|
|
3272
|
-
findByKey(
|
|
4314
|
+
findByKey(bundleKey: string): Promise<BundleRow | null>;
|
|
3273
4315
|
create(data: CreateBundleData): Promise<BundleRow>;
|
|
3274
4316
|
update(bundleId: string, data: UpdateBundleData): Promise<BundleRow>;
|
|
3275
4317
|
/** Sets `deletedAt` to NOW(); soft-deleted bundles are filtered from `list` by default. */
|
|
@@ -3328,14 +4370,7 @@ interface BundleRepository {
|
|
|
3328
4370
|
* if the predecessor carries a `validUntil` — the adapter only
|
|
3329
4371
|
* persists, it does not validate again.
|
|
3330
4372
|
*/
|
|
3331
|
-
publishDraft(versionId: string, publishMeta:
|
|
3332
|
-
publishedByUserId: string | null;
|
|
3333
|
-
publishedChanges: VersionChange[];
|
|
3334
|
-
nonRegressive: boolean;
|
|
3335
|
-
/** Required — validated by the service before the repository call. */
|
|
3336
|
-
validFrom: Date;
|
|
3337
|
-
validUntil: Date | null;
|
|
3338
|
-
}, tx?: TransactionContext): Promise<BundleVersionRow>;
|
|
4373
|
+
publishDraft(versionId: string, publishMeta: PublishBundleVersionMeta, tx?: TransactionContext): Promise<BundleVersionRow>;
|
|
3339
4374
|
/**
|
|
3340
4375
|
* Hard-discards a draft version (`publishedAt === null`) from the DB.
|
|
3341
4376
|
* Throws if the version was already published — published versions
|
|
@@ -3373,7 +4408,6 @@ interface MarketingProjectionRepository {
|
|
|
3373
4408
|
}
|
|
3374
4409
|
/** Upsert input for a capability from the discovery sync. */
|
|
3375
4410
|
interface UpsertCapabilityEntryData {
|
|
3376
|
-
projectKey: string;
|
|
3377
4411
|
capabilityKey: string;
|
|
3378
4412
|
label: string;
|
|
3379
4413
|
description: string | null;
|
|
@@ -3390,7 +4424,6 @@ interface UpsertCapabilityEntryData {
|
|
|
3390
4424
|
}
|
|
3391
4425
|
/** Upsert input for a feature from the discovery sync. */
|
|
3392
4426
|
interface UpsertFeatureEntryData {
|
|
3393
|
-
projectKey: string;
|
|
3394
4427
|
featureKey: string;
|
|
3395
4428
|
label: string;
|
|
3396
4429
|
description: string | null;
|
|
@@ -3404,7 +4437,6 @@ interface UpsertFeatureEntryData {
|
|
|
3404
4437
|
}
|
|
3405
4438
|
/** Upsert input for a quota from the discovery sync. */
|
|
3406
4439
|
interface UpsertQuotaEntryData {
|
|
3407
|
-
projectKey: string;
|
|
3408
4440
|
quotaKey: string;
|
|
3409
4441
|
label: string;
|
|
3410
4442
|
description: string | null;
|
|
@@ -3434,7 +4466,7 @@ interface SetCatalogEntryReviewData {
|
|
|
3434
4466
|
* Prisma tables.
|
|
3435
4467
|
*
|
|
3436
4468
|
* Binding:
|
|
3437
|
-
* - `upsert*` matches on
|
|
4469
|
+
* - `upsert*` matches on `<key>` and leaves `i18n`,
|
|
3438
4470
|
* `sortOrder`, `createdAt` as well as the approval fields (`approvedAt`/
|
|
3439
4471
|
* `approvedBy`/`approvedSignature`) **untouched** on an update —
|
|
3440
4472
|
* only the code-derived fields + the status (resolved by the service)
|
|
@@ -3450,7 +4482,7 @@ interface CatalogEntryRepository {
|
|
|
3450
4482
|
upsertCapability(data: UpsertCapabilityEntryData): Promise<CapabilityCatalogEntryRow>;
|
|
3451
4483
|
upsertFeature(data: UpsertFeatureEntryData): Promise<FeatureCatalogEntryRow>;
|
|
3452
4484
|
upsertQuota(data: UpsertQuotaEntryData): Promise<QuotaCatalogEntryRow>;
|
|
3453
|
-
retireMissing(
|
|
4485
|
+
retireMissing(type: 'capability' | 'feature' | 'quota', presentKeys: string[]): Promise<number>;
|
|
3454
4486
|
/**
|
|
3455
4487
|
* Sets or clears the successor pointer of a feature/quota
|
|
3456
4488
|
* (#39). The sync calls this when a key disappears from the snapshot
|
|
@@ -3459,17 +4491,17 @@ interface CatalogEntryRepository {
|
|
|
3459
4491
|
* adapters without a `successor_key` column omit the methods, and the sync
|
|
3460
4492
|
* then skips the pointers with a warn log.
|
|
3461
4493
|
*/
|
|
3462
|
-
setFeatureSuccessor?(
|
|
3463
|
-
setQuotaSuccessor?(
|
|
3464
|
-
findFeature(
|
|
3465
|
-
findQuota(
|
|
3466
|
-
setFeatureReview(
|
|
3467
|
-
setQuotaReview(
|
|
3468
|
-
setFeatureI18n(
|
|
3469
|
-
setQuotaI18n(
|
|
4494
|
+
setFeatureSuccessor?(featureKey: string, successorKey: string | null): Promise<FeatureCatalogEntryRow>;
|
|
4495
|
+
setQuotaSuccessor?(quotaKey: string, successorKey: string | null): Promise<QuotaCatalogEntryRow>;
|
|
4496
|
+
findFeature(featureKey: string): Promise<FeatureCatalogEntryRow | null>;
|
|
4497
|
+
findQuota(quotaKey: string): Promise<QuotaCatalogEntryRow | null>;
|
|
4498
|
+
setFeatureReview(featureKey: string, data: SetCatalogEntryReviewData): Promise<FeatureCatalogEntryRow>;
|
|
4499
|
+
setQuotaReview(quotaKey: string, data: SetCatalogEntryReviewData): Promise<QuotaCatalogEntryRow>;
|
|
4500
|
+
setFeatureI18n(featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
|
|
4501
|
+
setQuotaI18n(quotaKey: string, i18n: CatalogEntryI18n): Promise<QuotaCatalogEntryRow>;
|
|
3470
4502
|
/** Sets the editable base fields (default locale `label`/`description`). */
|
|
3471
|
-
setFeatureBase(
|
|
3472
|
-
setQuotaBase(
|
|
4503
|
+
setFeatureBase(featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
|
|
4504
|
+
setQuotaBase(quotaKey: string, data: UpdateCatalogEntryBaseData): Promise<QuotaCatalogEntryRow>;
|
|
3473
4505
|
}
|
|
3474
4506
|
/**
|
|
3475
4507
|
* Adapter for `promotions`. **No versioning** — promotions are edited
|
|
@@ -3477,7 +4509,7 @@ interface CatalogEntryRepository {
|
|
|
3477
4509
|
* this against their `promotions` Prisma table.
|
|
3478
4510
|
*/
|
|
3479
4511
|
interface PromotionRepository {
|
|
3480
|
-
list(
|
|
4512
|
+
list(): Promise<PromotionRow[]>;
|
|
3481
4513
|
findById(id: string): Promise<PromotionRow | null>;
|
|
3482
4514
|
create(data: CreatePromotionData): Promise<PromotionRow>;
|
|
3483
4515
|
update(id: string, data: UpdatePromotionData): Promise<PromotionRow>;
|
|
@@ -3485,27 +4517,260 @@ interface PromotionRepository {
|
|
|
3485
4517
|
delete(id: string): Promise<void>;
|
|
3486
4518
|
}
|
|
3487
4519
|
/**
|
|
3488
|
-
* Adapter for `marketing_settings` — one row
|
|
3489
|
-
*
|
|
3490
|
-
*
|
|
4520
|
+
* Adapter for `marketing_settings` — at most one row, which a `CHECK` on the
|
|
4521
|
+
* canonical schema holds rather than convention. `get` returns `null` as long
|
|
4522
|
+
* as the SuperAdmin has saved nothing (then the full `availableLocales` pool
|
|
4523
|
+
* counts as active). `upsert` creates the row or replaces it.
|
|
4524
|
+
*/
|
|
4525
|
+
interface MarketingSettingsRepository {
|
|
4526
|
+
get(): Promise<MarketingSettingsRow | null>;
|
|
4527
|
+
upsert(data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
/**
|
|
4531
|
+
* Adapter for `checkout_offers`. The offer is an immutable bundle snapshot:
|
|
4532
|
+
* `create` creates it, `update` only allows customization while
|
|
4533
|
+
* `status = 'open'`, `consume` freezes it.
|
|
4534
|
+
*/
|
|
4535
|
+
interface CheckoutOfferRepository {
|
|
4536
|
+
list(filter: CheckoutOfferFilter): Promise<CheckoutOfferRow[]>;
|
|
4537
|
+
findById(id: string): Promise<CheckoutOfferRow | null>;
|
|
4538
|
+
create(data: CreateCheckoutOfferData): Promise<CheckoutOfferRow>;
|
|
4539
|
+
update(id: string, data: UpdateCheckoutOfferData): Promise<CheckoutOfferRow>;
|
|
4540
|
+
/**
|
|
4541
|
+
* Sets `status = 'consumed'` + `consumedAt = NOW()`, and only while the
|
|
4542
|
+
* offer is still `open`: the write carries that condition, so of two
|
|
4543
|
+
* callers consuming at once one succeeds and the other is refused with
|
|
4544
|
+
* `CHECKOUT_OFFER_ALREADY_CONSUMED` (or `CHECKOUT_OFFER_EXPIRED`). A check
|
|
4545
|
+
* before the write cannot decide it, because the status can change in
|
|
4546
|
+
* between.
|
|
4547
|
+
*
|
|
4548
|
+
* With `tx`, the write runs on that transaction, so it is undone with
|
|
4549
|
+
* everything else the transaction wrote. `CheckoutOfferService.conclude`
|
|
4550
|
+
* depends on that; the persistence contract holds both.
|
|
4551
|
+
*/
|
|
4552
|
+
consume(id: string, tx?: TransactionContext): Promise<CheckoutOfferRow>;
|
|
4553
|
+
}
|
|
4554
|
+
|
|
4555
|
+
/** `ACTIVE` is the one in use; `REPLACED` is one a later payment method took over from. */
|
|
4556
|
+
type SubscriberPaymentMethodStatus = 'ACTIVE' | 'REPLACED';
|
|
4557
|
+
interface SubscriberPaymentMethodRecord extends ConfirmedPaymentMethod {
|
|
4558
|
+
id: string;
|
|
4559
|
+
subscriberId: string;
|
|
4560
|
+
/** The account in `config/saas.yaml#payments.accounts` the references belong to. */
|
|
4561
|
+
gatewayAccount: string;
|
|
4562
|
+
/** The provider of that account when the payment method was confirmed. */
|
|
4563
|
+
provider: string;
|
|
4564
|
+
status: SubscriberPaymentMethodStatus;
|
|
4565
|
+
/** When the gateway confirmed the payment method. */
|
|
4566
|
+
confirmedAt: Date;
|
|
4567
|
+
/** When a later payment method took over; `null` while this one is in use. */
|
|
4568
|
+
replacedAt: Date | null;
|
|
4569
|
+
createdAt: Date;
|
|
4570
|
+
}
|
|
4571
|
+
/** What a repository records for a payment method the gateway confirmed. */
|
|
4572
|
+
interface RecordSubscriberPaymentMethodData extends ConfirmedPaymentMethod {
|
|
4573
|
+
subscriberId: string;
|
|
4574
|
+
gatewayAccount: string;
|
|
4575
|
+
provider: string;
|
|
4576
|
+
confirmedAt: Date;
|
|
4577
|
+
}
|
|
4578
|
+
/**
|
|
4579
|
+
* - `activated`: it is the subscriber's payment method now, and the one it
|
|
4580
|
+
* replaced, if any, is `REPLACED`.
|
|
4581
|
+
* - `already-recorded`: the account's reference was recorded before; nothing
|
|
4582
|
+
* was written, and `method` is the row that holds it.
|
|
4583
|
+
* - `superseded`: the subscriber's payment method in use was confirmed after
|
|
4584
|
+
* this one, so this one is recorded as already replaced — confirmations can
|
|
4585
|
+
* arrive in another order than the forms were filled in.
|
|
4586
|
+
*/
|
|
4587
|
+
type RecordSubscriberPaymentMethodOutcome = 'activated' | 'already-recorded' | 'superseded';
|
|
4588
|
+
/**
|
|
4589
|
+
* A change of payment method a tenant started: the gateway session it opened
|
|
4590
|
+
* for the subscriber. A confirmation is recorded only against the setup it
|
|
4591
|
+
* belongs to, so a callback that names another subscriber than the session was
|
|
4592
|
+
* opened for changes nobody's payment method.
|
|
4593
|
+
*/
|
|
4594
|
+
interface SubscriberPaymentMethodSetupData {
|
|
4595
|
+
subscriberId: string;
|
|
4596
|
+
gatewayAccount: string;
|
|
4597
|
+
/** The gateway's session, unique within the account. */
|
|
4598
|
+
sessionRef: string;
|
|
4599
|
+
/** The customer the gateway keeps the payment method under. */
|
|
4600
|
+
customerRef: string;
|
|
4601
|
+
startedAt: Date;
|
|
4602
|
+
}
|
|
4603
|
+
/** Which setup a confirmation claims to complete. */
|
|
4604
|
+
interface SubscriberPaymentMethodSetupMatch {
|
|
4605
|
+
gatewayAccount: string;
|
|
4606
|
+
sessionRef: string;
|
|
4607
|
+
subscriberId: string;
|
|
4608
|
+
}
|
|
4609
|
+
interface RecordSubscriberPaymentMethodResult {
|
|
4610
|
+
method: SubscriberPaymentMethodRecord;
|
|
4611
|
+
outcome: RecordSubscriberPaymentMethodOutcome;
|
|
4612
|
+
}
|
|
4613
|
+
|
|
4614
|
+
/** A gateway event, as the log records it. */
|
|
4615
|
+
interface PaymentEventClaim {
|
|
4616
|
+
/** The account the event came from; an event identifier is unique only within it. */
|
|
4617
|
+
gatewayAccount: string;
|
|
4618
|
+
eventId: string;
|
|
4619
|
+
provider: string;
|
|
4620
|
+
/** The gateway session the event is about, where it is about one. */
|
|
4621
|
+
sessionId: string | null;
|
|
4622
|
+
kind: PaymentGatewayEvent['kind'];
|
|
4623
|
+
/** What the event said, without anything that names or reaches a person. */
|
|
4624
|
+
summary: Record<string, unknown>;
|
|
4625
|
+
}
|
|
4626
|
+
/**
|
|
4627
|
+
* The events a gateway sent, each handled once.
|
|
4628
|
+
*
|
|
4629
|
+
* Gateways deliver at least once, so the same event arrives again after a
|
|
4630
|
+
* timeout or a retry. An event is claimed on the transaction that writes what
|
|
4631
|
+
* it changes: when that transaction rolls back, the claim goes with it and the
|
|
4632
|
+
* gateway's retry is handled rather than discarded as a duplicate.
|
|
4633
|
+
*/
|
|
4634
|
+
interface PaymentEventLog {
|
|
4635
|
+
/**
|
|
4636
|
+
* Records the event and returns `true`, or returns `false` when this account
|
|
4637
|
+
* already recorded it, writing nothing.
|
|
4638
|
+
*
|
|
4639
|
+
* The duplicate must not raise: on PostgreSQL an error aborts the
|
|
4640
|
+
* transaction it happened on, and the caller's transaction has to stay
|
|
4641
|
+
* usable. An `INSERT … ON CONFLICT DO NOTHING` does both — and when another
|
|
4642
|
+
* transaction holds the same claim uncommitted, it waits for that one and
|
|
4643
|
+
* answers by its outcome.
|
|
4644
|
+
*
|
|
4645
|
+
* A confirmation of a session this account already confirmed is a duplicate
|
|
4646
|
+
* as well, whatever its `eventId`: one session is set up once, and a gateway
|
|
4647
|
+
* may report it through more than one event. `sql/constraints.postgres.sql`
|
|
4648
|
+
* holds that as a second unique index, and the untargeted `DO NOTHING`
|
|
4649
|
+
* answers for it too.
|
|
4650
|
+
*/
|
|
4651
|
+
claim(claim: PaymentEventClaim, tx: TransactionContext): Promise<boolean>;
|
|
4652
|
+
/**
|
|
4653
|
+
* Takes the session off a claim whose event changed nothing, so the next
|
|
4654
|
+
* event about that session is handled instead of taken for the duplicate it
|
|
4655
|
+
* is not. The claim itself stays: that one event is never handled twice.
|
|
4656
|
+
*
|
|
4657
|
+
* The session is held from the claim onwards, which is what keeps two
|
|
4658
|
+
* confirmations delivered at once from both taking effect; an event that
|
|
4659
|
+
* turned out to have nothing to do gives it back on the same transaction.
|
|
4660
|
+
*/
|
|
4661
|
+
releaseSession(gatewayAccount: string, eventId: string, tx: TransactionContext): Promise<void>;
|
|
4662
|
+
}
|
|
4663
|
+
/**
|
|
4664
|
+
* The payment methods of subscribers, as references at the gateway accounts
|
|
4665
|
+
* that confirmed them.
|
|
4666
|
+
*
|
|
4667
|
+
* A subscriber has at most one `ACTIVE` payment method; the database holds
|
|
4668
|
+
* that, so two confirmations recorded at once for one subscriber end with one.
|
|
3491
4669
|
*/
|
|
3492
|
-
interface
|
|
3493
|
-
|
|
3494
|
-
|
|
4670
|
+
interface SubscriberPaymentMethodRepository {
|
|
4671
|
+
/**
|
|
4672
|
+
* Records a confirmed payment method for its subscriber, under a lock on the
|
|
4673
|
+
* subscriber so two confirmations take turns. See
|
|
4674
|
+
* `RecordSubscriberPaymentMethodOutcome` for the three outcomes.
|
|
4675
|
+
*/
|
|
4676
|
+
recordConfirmed(data: RecordSubscriberPaymentMethodData, tx?: TransactionContext): Promise<RecordSubscriberPaymentMethodResult>;
|
|
4677
|
+
/** The subscriber's payment method in use, or `null` when it has none. */
|
|
4678
|
+
findActive(subscriberId: string, tx?: TransactionContext): Promise<SubscriberPaymentMethodRecord | null>;
|
|
4679
|
+
/**
|
|
4680
|
+
* The payment method an account's reference names, whatever its status.
|
|
4681
|
+
*
|
|
4682
|
+
* The one read that reaches a payment method a newer one replaced, which is
|
|
4683
|
+
* how `@saasicat/persistence-testing` verifies that an implementation keeps
|
|
4684
|
+
* the history rather than overwriting the row (`SC-PRIC-030`).
|
|
4685
|
+
*/
|
|
4686
|
+
findByReference(gatewayAccount: string, paymentMethodRef: string, tx?: TransactionContext): Promise<SubscriberPaymentMethodRecord | null>;
|
|
4687
|
+
/**
|
|
4688
|
+
* Every account that holds a payment method in use, each once.
|
|
4689
|
+
*
|
|
4690
|
+
* Platform-wide: anchored by no subscriber and no tenant, and a start makes
|
|
4691
|
+
* it before anything is served, to refuse a configuration that no longer
|
|
4692
|
+
* names an account somebody's payment method is held at. An implementation
|
|
4693
|
+
* on a tenant-scoped client must read RLS-exempt — the platform wraps the
|
|
4694
|
+
* call in `RlsBypassPort`, and one that answers with the caller's tenant
|
|
4695
|
+
* scope instead returns an empty list at a boot, where there is no tenant,
|
|
4696
|
+
* so the check that exists to refuse passes. The persistence contract runs
|
|
4697
|
+
* with no policy forced, so it cannot catch that for you.
|
|
4698
|
+
*/
|
|
4699
|
+
accountsInUse(): Promise<string[]>;
|
|
4700
|
+
/**
|
|
4701
|
+
* Records a change of payment method a tenant started, open until its
|
|
4702
|
+
* confirmation completes it.
|
|
4703
|
+
*
|
|
4704
|
+
* One session is one setup: the account and the session are unique together,
|
|
4705
|
+
* and a second setup for a session already recorded raises. A gateway hands
|
|
4706
|
+
* out a session per start, so an adapter that returns one twice is the
|
|
4707
|
+
* defect this refuses to write over.
|
|
4708
|
+
*/
|
|
4709
|
+
recordSetup(data: SubscriberPaymentMethodSetupData, tx?: TransactionContext): Promise<void>;
|
|
4710
|
+
/**
|
|
4711
|
+
* Completes the open setup the account, the session and the subscriber all
|
|
4712
|
+
* name, and returns `true` — or returns `false`, writing nothing, when no
|
|
4713
|
+
* open setup matches all three: none was started, it was started for another
|
|
4714
|
+
* subscriber, or it is complete already. A single conditional write, so two
|
|
4715
|
+
* confirmations for one setup complete it once.
|
|
4716
|
+
*/
|
|
4717
|
+
completeSetup(match: SubscriberPaymentMethodSetupMatch, completedAt: Date, tx?: TransactionContext): Promise<boolean>;
|
|
3495
4718
|
}
|
|
3496
4719
|
|
|
4720
|
+
/** Which changes to list. */
|
|
4721
|
+
interface SettingsChangeFilter {
|
|
4722
|
+
/** Only changes an operator has, or has not, acknowledged. Omitted: both. */
|
|
4723
|
+
acknowledged?: boolean;
|
|
4724
|
+
/** The most recently recorded ones. Omitted: every matching change. */
|
|
4725
|
+
limit?: number;
|
|
4726
|
+
}
|
|
3497
4727
|
/**
|
|
3498
|
-
*
|
|
3499
|
-
*
|
|
3500
|
-
* `
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
4728
|
+
* Stores what the installation applied and what changed between two boots.
|
|
4729
|
+
*
|
|
4730
|
+
* `applied_settings` holds one row for the installation; `settings_changes`
|
|
4731
|
+
* holds one row per boot that found the fingerprint moved. An adapter
|
|
4732
|
+
* translates: it does not decide what a change is, and it does not read the
|
|
4733
|
+
* row back into anything that runs.
|
|
4734
|
+
*
|
|
4735
|
+
* Both writes are guarded on the fingerprint the caller read. Several replicas
|
|
4736
|
+
* of one installation start together after one edit of the file, each reads
|
|
4737
|
+
* the same record and each finds the same difference; the guard is what makes
|
|
4738
|
+
* one of them the boot that recorded it and the others boots that found it
|
|
4739
|
+
* recorded. Without it every replica would write the change and mail the
|
|
4740
|
+
* addresses, once per replica.
|
|
4741
|
+
*/
|
|
4742
|
+
interface AppliedSettingsPort {
|
|
4743
|
+
/** The record, or null before the first boot that could write one. */
|
|
4744
|
+
readApplied(): Promise<AppliedSettingsRecord | null>;
|
|
4745
|
+
/**
|
|
4746
|
+
* Replaces the installation's record — there is only ever the one row —
|
|
4747
|
+
* provided the stored record still carries `expectedFingerprint`: the
|
|
4748
|
+
* fingerprint the caller read, or `null` where it read no record. Returns
|
|
4749
|
+
* whether it did. `false` means the record moved between the caller's read
|
|
4750
|
+
* and this write, and nothing was written: another boot got there first.
|
|
4751
|
+
*/
|
|
4752
|
+
writeApplied(record: AppliedSettingsRecord, expectedFingerprint: string | null): Promise<boolean>;
|
|
4753
|
+
/**
|
|
4754
|
+
* Appends a change a boot noticed and replaces the record it supersedes, in
|
|
4755
|
+
* one step: both land, or neither does. Guarded like `writeApplied`, on the
|
|
4756
|
+
* fingerprint of the record the change was noticed against. Returns the
|
|
4757
|
+
* change as stored — the id is the adapter's to assign — or `null` where
|
|
4758
|
+
* the record had already moved on: another boot noticed first, and the
|
|
4759
|
+
* change is that boot's to report.
|
|
4760
|
+
*/
|
|
4761
|
+
recordChange(change: NewSettingsChange, record: AppliedSettingsRecord, expectedFingerprint: string): Promise<SettingsChangeRecord | null>;
|
|
4762
|
+
/**
|
|
4763
|
+
* Changes, the most recently recorded first: the order the record went
|
|
4764
|
+
* through them, which the database numbers at each write — not the order
|
|
4765
|
+
* of the moments they carry, which are the recording starts' clocks.
|
|
4766
|
+
*/
|
|
4767
|
+
listChanges(filter?: SettingsChangeFilter): Promise<SettingsChangeRecord[]>;
|
|
4768
|
+
/**
|
|
4769
|
+
* Marks a change as seen. Returns the updated record, or null where no
|
|
4770
|
+
* change has that id. A change already acknowledged keeps its first
|
|
4771
|
+
* acknowledgement — repeating the action changes nothing.
|
|
4772
|
+
*/
|
|
4773
|
+
acknowledgeChange(id: string, acknowledgedBy: string, acknowledgedAt: Date): Promise<SettingsChangeRecord | null>;
|
|
3509
4774
|
}
|
|
3510
4775
|
|
|
3511
4776
|
/** Class reference usable as a DI token (e.g. the consumer's `PrismaService`). */
|
|
@@ -3565,12 +4830,20 @@ interface SaaSiCatPersistenceCore {
|
|
|
3565
4830
|
auditQuery?: PersistenceProvider<AuditQueryPort>;
|
|
3566
4831
|
/** Aggregation for the admin stats dashboard. */
|
|
3567
4832
|
auditStats?: PersistenceProvider<AuditStatsPort>;
|
|
4833
|
+
/**
|
|
4834
|
+
* The record of the applied configuration (`SettingsModule`). Optional so
|
|
4835
|
+
* an adapter written before it existed keeps working; without it the
|
|
4836
|
+
* platform says once at boot that it is not recording.
|
|
4837
|
+
*/
|
|
4838
|
+
appliedSettings?: PersistenceProvider<AppliedSettingsPort>;
|
|
3568
4839
|
}
|
|
3569
4840
|
/** Repositories for the entitlement/contract loop (`EntitlementModule`). */
|
|
3570
4841
|
interface SaaSiCatPersistenceEntitlement {
|
|
3571
4842
|
subscriptionRepository: PersistenceProvider<SubscriptionRepository>;
|
|
3572
4843
|
planVersionRepository: PersistenceProvider<PlanVersionRepository>;
|
|
3573
4844
|
subscriptionContractRepository?: PersistenceProvider<SubscriptionContractRepository>;
|
|
4845
|
+
/** The parties contracts are concluded with; required wherever contracts are written. */
|
|
4846
|
+
subscriberRepository?: PersistenceProvider<SubscriberRepository>;
|
|
3574
4847
|
subscriptionBundleRepository?: PersistenceProvider<SubscriptionBundleRepository>;
|
|
3575
4848
|
bundleRepository?: PersistenceProvider<BundleRepository>;
|
|
3576
4849
|
}
|
|
@@ -3597,6 +4870,15 @@ interface SaaSiCatPersistenceTenantBilling {
|
|
|
3597
4870
|
subscriptionWritePort: PersistenceProvider<TenantSubscriptionWritePort>;
|
|
3598
4871
|
usageSnapshotPort?: PersistenceProvider<UsageSnapshotPort>;
|
|
3599
4872
|
}
|
|
4873
|
+
/**
|
|
4874
|
+
* The record of payment gateway callbacks and the payment methods they confirm
|
|
4875
|
+
* (`payments` in `SaaSiCatModule.forRoot`). Both are written on the transaction
|
|
4876
|
+
* a callback is handled on, so they come from the adapter that runs it.
|
|
4877
|
+
*/
|
|
4878
|
+
interface SaaSiCatPersistencePayments {
|
|
4879
|
+
paymentEventLog: PersistenceProvider<PaymentEventLog>;
|
|
4880
|
+
subscriberPaymentMethodRepository: PersistenceProvider<SubscriberPaymentMethodRepository>;
|
|
4881
|
+
}
|
|
3600
4882
|
/** Read/write backing for the standard SuperAdmin resource pages. */
|
|
3601
4883
|
interface SaaSiCatPersistenceAdminResources {
|
|
3602
4884
|
resources: PersistenceProvider<AdminResourcesPort>;
|
|
@@ -3631,6 +4913,8 @@ interface SaaSiCatPersistenceAdapter {
|
|
|
3631
4913
|
/** Tenant, user, audit and subscription resources for the SuperAdmin UI. */
|
|
3632
4914
|
adminResources?: SaaSiCatPersistenceAdminResources;
|
|
3633
4915
|
promo?: SaaSiCatPersistencePromo;
|
|
4916
|
+
/** Payment gateway callbacks and subscriber payment methods. */
|
|
4917
|
+
payments?: SaaSiCatPersistencePayments;
|
|
3634
4918
|
/** DB hydration of the plan catalog at boot (`PlanCatalogModule`). */
|
|
3635
4919
|
planCatalogReadSink?: PersistenceProvider<PlanCatalogReadSink>;
|
|
3636
4920
|
/** One-shot `saas.yaml → DB` import. */
|
|
@@ -3703,6 +4987,8 @@ declare const CATALOG_ERROR_CODES: {
|
|
|
3703
4987
|
readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
|
|
3704
4988
|
readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
|
|
3705
4989
|
readonly BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE";
|
|
4990
|
+
readonly BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE";
|
|
4991
|
+
readonly BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "BUNDLE_VERSION_NOT_PRICED_FOR_PLAN";
|
|
3706
4992
|
readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
|
|
3707
4993
|
readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
|
|
3708
4994
|
readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
|
|
@@ -3730,6 +5016,10 @@ declare const CATALOG_ERROR_CODES: {
|
|
|
3730
5016
|
readonly QUOTA_NOT_IN_DISCOVERY_SNAPSHOT: "QUOTA_NOT_IN_DISCOVERY_SNAPSHOT";
|
|
3731
5017
|
readonly DISCOVERY_STATUS_TRANSITION_INVALID: "DISCOVERY_STATUS_TRANSITION_INVALID";
|
|
3732
5018
|
readonly DISCOVERY_NOT_INITIALIZED: "DISCOVERY_NOT_INITIALIZED";
|
|
5019
|
+
/** The uploaded document is not a plan catalog — unparseable, or not an object. */
|
|
5020
|
+
readonly PLAN_CATALOG_UNREADABLE: "PLAN_CATALOG_UNREADABLE";
|
|
5021
|
+
/** It parsed, and then failed the schema or a cross-field rule. */
|
|
5022
|
+
readonly PLAN_CATALOG_INVALID: "PLAN_CATALOG_INVALID";
|
|
3733
5023
|
};
|
|
3734
5024
|
type CatalogErrorCode = (typeof CATALOG_ERROR_CODES)[keyof typeof CATALOG_ERROR_CODES];
|
|
3735
5025
|
/** Bundle bookings on a tenant subscription. */
|
|
@@ -3743,6 +5033,8 @@ declare const BILLING_ERROR_CODES: {
|
|
|
3743
5033
|
readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
|
|
3744
5034
|
readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
|
|
3745
5035
|
readonly BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE";
|
|
5036
|
+
readonly BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN";
|
|
5037
|
+
readonly BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN";
|
|
3746
5038
|
readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
|
|
3747
5039
|
readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
|
|
3748
5040
|
readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
|
|
@@ -3756,8 +5048,79 @@ declare const BILLING_ERROR_CODES: {
|
|
|
3756
5048
|
readonly PLAN_NOT_IN_CATALOG: "PLAN_NOT_IN_CATALOG";
|
|
3757
5049
|
/** Plan exists but cannot be booked via self-service. */
|
|
3758
5050
|
readonly PLAN_NOT_SELF_SERVICE: "PLAN_NOT_SELF_SERVICE";
|
|
5051
|
+
/**
|
|
5052
|
+
* Plan carries no price for the requested billing cycle, so it is not sold
|
|
5053
|
+
* in it: a plan without a yearly price is a monthly plan.
|
|
5054
|
+
*/
|
|
5055
|
+
readonly PLAN_NOT_SOLD_IN_CYCLE: "PLAN_NOT_SOLD_IN_CYCLE";
|
|
3759
5056
|
/** Plan change refused. Carries `blockers[]` with their own codes. */
|
|
3760
5057
|
readonly PLAN_CHANGE_BLOCKED: "PLAN_CHANGE_BLOCKED";
|
|
5058
|
+
/**
|
|
5059
|
+
* The subscription moved between the read a request was decided on and the
|
|
5060
|
+
* write it attempted, so nothing was written. The caller reloads and asks
|
|
5061
|
+
* again.
|
|
5062
|
+
*/
|
|
5063
|
+
readonly SUBSCRIPTION_CHANGED: "SUBSCRIPTION_CHANGED";
|
|
5064
|
+
/**
|
|
5065
|
+
* The tenant has no subscription to act on.
|
|
5066
|
+
*
|
|
5067
|
+
* `SUBSCRIPTION_NOT_FOUND` states the same fact on the read routes. Both
|
|
5068
|
+
* are already on the wire and a code is renamed only deliberately, so both
|
|
5069
|
+
* are named here rather than one being dropped behind a consumer's back.
|
|
5070
|
+
*/
|
|
5071
|
+
readonly NO_SUBSCRIPTION: "NO_SUBSCRIPTION";
|
|
5072
|
+
/**
|
|
5073
|
+
* The cancellation date the reader was shown is no longer the one the rules
|
|
5074
|
+
* return, so the confirmation is refused rather than silently applied.
|
|
5075
|
+
* Carries the recomputed dates, so the page can re-ask instead of guessing.
|
|
5076
|
+
*/
|
|
5077
|
+
readonly CANCELLATION_TERMS_CHANGED: "CANCELLATION_TERMS_CHANGED";
|
|
5078
|
+
/** The subscription has ended; its plan can no longer be changed. */
|
|
5079
|
+
readonly SUBSCRIPTION_ENDED: "SUBSCRIPTION_ENDED";
|
|
5080
|
+
/** An active special contract blocks self-service plan changes. */
|
|
5081
|
+
readonly PLAN_LOCKED: "PLAN_LOCKED";
|
|
5082
|
+
/** Current usage of one quota exceeds what the target plan allows. */
|
|
5083
|
+
readonly QUOTA_OVER_TARGET: "QUOTA_OVER_TARGET";
|
|
5084
|
+
/** The change drops features the tenant has today. */
|
|
5085
|
+
readonly FEATURE_LOST: "FEATURE_LOST";
|
|
5086
|
+
readonly FEATURES_LOST: "FEATURES_LOST";
|
|
5087
|
+
/** Target plan and cycle already match what is in place. */
|
|
5088
|
+
readonly NO_CHANGE: "NO_CHANGE";
|
|
5089
|
+
/** A shorter cycle cannot start inside the term already running. */
|
|
5090
|
+
readonly CYCLE_SHORTENS_AT_TERM_END: "CYCLE_SHORTENS_AT_TERM_END";
|
|
5091
|
+
/** A cancelled subscription cannot change its billing cycle. */
|
|
5092
|
+
readonly CANCELLATION_LOCKS_THE_CYCLE: "CANCELLATION_LOCKS_THE_CYCLE";
|
|
5093
|
+
/**
|
|
5094
|
+
* A bundle the tenant already holds runs past the cycle they are moving to.
|
|
5095
|
+
*
|
|
5096
|
+
* Its own code rather than `BUNDLE_CYCLE_EXCEEDS_PLAN`, which states the
|
|
5097
|
+
* same rule about a booking that has not been made yet. The two need
|
|
5098
|
+
* different sentences: this one can name the day the obstacle lifts and
|
|
5099
|
+
* tell the reader to cancel the booking, and that advice is wrong for
|
|
5100
|
+
* someone who is only about to book. One template cannot serve both.
|
|
5101
|
+
*/
|
|
5102
|
+
readonly BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE";
|
|
5103
|
+
/**
|
|
5104
|
+
* Features of the previewed bundle are already covered by the plan or by
|
|
5105
|
+
* another booked bundle. A warning rather than a blocker: paying twice is
|
|
5106
|
+
* the customer's decision, and the preview only has to say so first.
|
|
5107
|
+
*/
|
|
5108
|
+
readonly REDUNDANT_FEATURES: "REDUNDANT_FEATURES";
|
|
5109
|
+
/**
|
|
5110
|
+
* A booking's minimum term outlasts the period being cancelled, so the
|
|
5111
|
+
* cancellation takes effect at the end of the term, not of the period.
|
|
5112
|
+
*/
|
|
5113
|
+
readonly MINIMUM_TERM_BINDS: "MINIMUM_TERM_BINDS";
|
|
5114
|
+
/**
|
|
5115
|
+
* The previewed bundle requires features that neither the plan nor an
|
|
5116
|
+
* active booking provides.
|
|
5117
|
+
*
|
|
5118
|
+
* The same string is a `StrictModeWarningCode` in `bundle.types.ts`, where
|
|
5119
|
+
* it names the catalogue-authoring reading of the rule and travels with its
|
|
5120
|
+
* own message. This declaration is the booking preview's blocker, which a
|
|
5121
|
+
* tenant reads and therefore needs a shipped text for.
|
|
5122
|
+
*/
|
|
5123
|
+
readonly BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED";
|
|
3761
5124
|
readonly NO_PENDING_PLAN_VERSION: "NO_PENDING_PLAN_VERSION";
|
|
3762
5125
|
readonly ONBOARDING_CREATE_FAILED: "ONBOARDING_CREATE_FAILED";
|
|
3763
5126
|
readonly BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS";
|
|
@@ -3778,20 +5141,59 @@ declare const CONTRACT_ERROR_CODES: {
|
|
|
3778
5141
|
readonly CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED";
|
|
3779
5142
|
readonly CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE: "CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE";
|
|
3780
5143
|
readonly CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED: "CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED";
|
|
5144
|
+
readonly CHECKOUT_OFFER_PLAN_NOT_OFFERED: "CHECKOUT_OFFER_PLAN_NOT_OFFERED";
|
|
5145
|
+
readonly CHECKOUT_OFFER_BUNDLE_NOT_OFFERED: "CHECKOUT_OFFER_BUNDLE_NOT_OFFERED";
|
|
5146
|
+
readonly CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED: "CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED";
|
|
5147
|
+
readonly CHECKOUT_OFFER_PRICE_NOT_CURRENT: "CHECKOUT_OFFER_PRICE_NOT_CURRENT";
|
|
3781
5148
|
readonly SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED: "SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED";
|
|
3782
5149
|
readonly SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED";
|
|
3783
5150
|
readonly SUBSCRIPTION_CONTRACT_INVALID_DATE: "SUBSCRIPTION_CONTRACT_INVALID_DATE";
|
|
3784
5151
|
readonly SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "SUBSCRIPTION_CONTRACT_INVALID_WINDOW";
|
|
5152
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH";
|
|
5153
|
+
readonly SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT";
|
|
5154
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH";
|
|
3785
5155
|
readonly SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START";
|
|
3786
5156
|
readonly CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND";
|
|
3787
5157
|
readonly CHECKOUT_OFFER_EXPIRED: "CHECKOUT_OFFER_EXPIRED";
|
|
3788
5158
|
readonly CHECKOUT_OFFER_ALREADY_CONSUMED: "CHECKOUT_OFFER_ALREADY_CONSUMED";
|
|
3789
5159
|
readonly CHECKOUT_OFFER_NOT_CONSUMED: "CHECKOUT_OFFER_NOT_CONSUMED";
|
|
5160
|
+
/**
|
|
5161
|
+
* The offer changed between the checks of `conclude` and the transaction
|
|
5162
|
+
* that consumed it, so the contract checked is not the one the offer now
|
|
5163
|
+
* describes. Nothing was written; load the offer and conclude it again.
|
|
5164
|
+
*/
|
|
5165
|
+
readonly CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED";
|
|
3790
5166
|
readonly SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND";
|
|
3791
5167
|
readonly NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT";
|
|
3792
5168
|
readonly SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED";
|
|
3793
5169
|
};
|
|
3794
5170
|
type ContractErrorCode = (typeof CONTRACT_ERROR_CODES)[keyof typeof CONTRACT_ERROR_CODES];
|
|
5171
|
+
/** The parties contracts are concluded with (`SubscriberService`), and their absence. */
|
|
5172
|
+
declare const SUBSCRIBER_ERROR_CODES: {
|
|
5173
|
+
/**
|
|
5174
|
+
* A contract, a plan change or a booking for a tenant that has no
|
|
5175
|
+
* subscriber. Nothing is agreed or charged without the party to it.
|
|
5176
|
+
* Carries `tenantId`.
|
|
5177
|
+
*/
|
|
5178
|
+
readonly SUBSCRIBER_REQUIRED: "SUBSCRIBER_REQUIRED";
|
|
5179
|
+
/** The tenant already has a live subscriber. Carries `tenantId`. */
|
|
5180
|
+
readonly SUBSCRIBER_ALREADY_EXISTS: "SUBSCRIBER_ALREADY_EXISTS";
|
|
5181
|
+
readonly SUBSCRIBER_NOT_FOUND: "SUBSCRIBER_NOT_FOUND";
|
|
5182
|
+
readonly SUBSCRIBER_LEGAL_NAME_REQUIRED: "SUBSCRIBER_LEGAL_NAME_REQUIRED";
|
|
5183
|
+
/**
|
|
5184
|
+
* A detail that has a form — the country, the invoice email — is not in it,
|
|
5185
|
+
* or one sign-up requires — the billing address — is missing. Carries `field`.
|
|
5186
|
+
*/
|
|
5187
|
+
readonly SUBSCRIBER_DETAIL_INVALID: "SUBSCRIBER_DETAIL_INVALID";
|
|
5188
|
+
/** A contact change named a field of the legal identity. Carries `field`. */
|
|
5189
|
+
readonly SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "SUBSCRIBER_IDENTITY_NOT_A_CONTACT";
|
|
5190
|
+
readonly SUBSCRIBER_CORRECTION_REASON_REQUIRED: "SUBSCRIBER_CORRECTION_REASON_REQUIRED";
|
|
5191
|
+
readonly SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "SUBSCRIBER_CORRECTION_ACTOR_REQUIRED";
|
|
5192
|
+
readonly SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "SUBSCRIBER_CORRECTION_CHANGES_NOTHING";
|
|
5193
|
+
/** The operator declared another legal entity: that is a transfer, not an edit. */
|
|
5194
|
+
readonly SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER";
|
|
5195
|
+
};
|
|
5196
|
+
type SubscriberErrorCode = (typeof SUBSCRIBER_ERROR_CODES)[keyof typeof SUBSCRIBER_ERROR_CODES];
|
|
3795
5197
|
/** Self-service registration funnel (`PendingRegistration`). */
|
|
3796
5198
|
declare const REGISTRATION_ERROR_CODES: {
|
|
3797
5199
|
readonly PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND";
|
|
@@ -3820,6 +5222,12 @@ declare const AUTH_ERROR_CODES: {
|
|
|
3820
5222
|
/** Neither `tenantId` nor `userId` could be resolved from the request. */
|
|
3821
5223
|
readonly TENANT_CONTEXT_MISSING: "TENANT_CONTEXT_MISSING";
|
|
3822
5224
|
readonly TENANT_ADMIN_REQUIRED: "TENANT_ADMIN_REQUIRED";
|
|
5225
|
+
/**
|
|
5226
|
+
* The tenant's billing area — its payment method, and later its invoices
|
|
5227
|
+
* and account — needs the billing permission, which the application maps
|
|
5228
|
+
* to its roles and the tenant's administrator holds by default.
|
|
5229
|
+
*/
|
|
5230
|
+
readonly BILLING_PERMISSION_REQUIRED: "BILLING_PERMISSION_REQUIRED";
|
|
3823
5231
|
readonly SUPER_ADMIN_REQUIRED: "SUPER_ADMIN_REQUIRED";
|
|
3824
5232
|
/** TOTP MFA has never been set up for this user. */
|
|
3825
5233
|
readonly MFA_NOT_SET_UP: "MFA_NOT_SET_UP";
|
|
@@ -3850,6 +5258,30 @@ declare const PROMO_ERROR_CODES: {
|
|
|
3850
5258
|
readonly PROMO_MAX_REDEMPTIONS_LOWERED: "PROMO_MAX_REDEMPTIONS_LOWERED";
|
|
3851
5259
|
};
|
|
3852
5260
|
type PromoErrorCode = (typeof PROMO_ERROR_CODES)[keyof typeof PROMO_ERROR_CODES];
|
|
5261
|
+
/** Payment methods and the gateways that confirm them. */
|
|
5262
|
+
declare const PAYMENT_ERROR_CODES: {
|
|
5263
|
+
/**
|
|
5264
|
+
* No gateway account takes new payment methods:
|
|
5265
|
+
* `config/saas.yaml#payments.newPaymentMethods` names none.
|
|
5266
|
+
*/
|
|
5267
|
+
readonly PAYMENTS_NOT_CONFIGURED: "PAYMENTS_NOT_CONFIGURED";
|
|
5268
|
+
/** A callback arrived for an account `config/saas.yaml#payments.accounts` does not name. Carries `account`. */
|
|
5269
|
+
readonly PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "PAYMENT_GATEWAY_ACCOUNT_UNKNOWN";
|
|
5270
|
+
/** A callback the gateway did not send: its signature does not verify. */
|
|
5271
|
+
readonly PAYMENT_CALLBACK_REJECTED: "PAYMENT_CALLBACK_REJECTED";
|
|
5272
|
+
/**
|
|
5273
|
+
* A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
|
|
5274
|
+
* does not name. Carries `field`.
|
|
5275
|
+
*/
|
|
5276
|
+
readonly PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED";
|
|
5277
|
+
};
|
|
5278
|
+
type PaymentErrorCode = (typeof PAYMENT_ERROR_CODES)[keyof typeof PAYMENT_ERROR_CODES];
|
|
5279
|
+
/** Codes of the settings record (`GET /admin/settings`, the acknowledgement). */
|
|
5280
|
+
declare const SETTINGS_ERROR_CODES: {
|
|
5281
|
+
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
5282
|
+
readonly SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND";
|
|
5283
|
+
};
|
|
5284
|
+
type SettingsErrorCode = (typeof SETTINGS_ERROR_CODES)[keyof typeof SETTINGS_ERROR_CODES];
|
|
3853
5285
|
/**
|
|
3854
5286
|
* Every exception code the platform emits, in one object.
|
|
3855
5287
|
*
|
|
@@ -3858,6 +5290,22 @@ type PromoErrorCode = (typeof PROMO_ERROR_CODES)[keyof typeof PROMO_ERROR_CODES]
|
|
|
3858
5290
|
* removing one may not.
|
|
3859
5291
|
*/
|
|
3860
5292
|
declare const PLATFORM_ERROR_CODES: {
|
|
5293
|
+
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
5294
|
+
readonly SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND";
|
|
5295
|
+
/**
|
|
5296
|
+
* No gateway account takes new payment methods:
|
|
5297
|
+
* `config/saas.yaml#payments.newPaymentMethods` names none.
|
|
5298
|
+
*/
|
|
5299
|
+
readonly PAYMENTS_NOT_CONFIGURED: "PAYMENTS_NOT_CONFIGURED";
|
|
5300
|
+
/** A callback arrived for an account `config/saas.yaml#payments.accounts` does not name. Carries `account`. */
|
|
5301
|
+
readonly PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "PAYMENT_GATEWAY_ACCOUNT_UNKNOWN";
|
|
5302
|
+
/** A callback the gateway did not send: its signature does not verify. */
|
|
5303
|
+
readonly PAYMENT_CALLBACK_REJECTED: "PAYMENT_CALLBACK_REJECTED";
|
|
5304
|
+
/**
|
|
5305
|
+
* A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
|
|
5306
|
+
* does not name. Carries `field`.
|
|
5307
|
+
*/
|
|
5308
|
+
readonly PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED";
|
|
3861
5309
|
readonly PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND";
|
|
3862
5310
|
readonly PENDING_REGISTRATION_EXPIRED: "PENDING_REGISTRATION_EXPIRED";
|
|
3863
5311
|
readonly INVALID_REGISTRATION_STATE: "INVALID_REGISTRATION_STATE";
|
|
@@ -3873,20 +5321,55 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3873
5321
|
readonly PLAN_NOT_AVAILABLE: "PLAN_NOT_AVAILABLE";
|
|
3874
5322
|
readonly PLAN_NOT_SELECTED: "PLAN_NOT_SELECTED";
|
|
3875
5323
|
readonly MODEL_NOT_AVAILABLE: "MODEL_NOT_AVAILABLE";
|
|
5324
|
+
/**
|
|
5325
|
+
* A contract, a plan change or a booking for a tenant that has no
|
|
5326
|
+
* subscriber. Nothing is agreed or charged without the party to it.
|
|
5327
|
+
* Carries `tenantId`.
|
|
5328
|
+
*/
|
|
5329
|
+
readonly SUBSCRIBER_REQUIRED: "SUBSCRIBER_REQUIRED";
|
|
5330
|
+
/** The tenant already has a live subscriber. Carries `tenantId`. */
|
|
5331
|
+
readonly SUBSCRIBER_ALREADY_EXISTS: "SUBSCRIBER_ALREADY_EXISTS";
|
|
5332
|
+
readonly SUBSCRIBER_NOT_FOUND: "SUBSCRIBER_NOT_FOUND";
|
|
5333
|
+
readonly SUBSCRIBER_LEGAL_NAME_REQUIRED: "SUBSCRIBER_LEGAL_NAME_REQUIRED";
|
|
5334
|
+
/**
|
|
5335
|
+
* A detail that has a form — the country, the invoice email — is not in it,
|
|
5336
|
+
* or one sign-up requires — the billing address — is missing. Carries `field`.
|
|
5337
|
+
*/
|
|
5338
|
+
readonly SUBSCRIBER_DETAIL_INVALID: "SUBSCRIBER_DETAIL_INVALID";
|
|
5339
|
+
/** A contact change named a field of the legal identity. Carries `field`. */
|
|
5340
|
+
readonly SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "SUBSCRIBER_IDENTITY_NOT_A_CONTACT";
|
|
5341
|
+
readonly SUBSCRIBER_CORRECTION_REASON_REQUIRED: "SUBSCRIBER_CORRECTION_REASON_REQUIRED";
|
|
5342
|
+
readonly SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "SUBSCRIBER_CORRECTION_ACTOR_REQUIRED";
|
|
5343
|
+
readonly SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "SUBSCRIBER_CORRECTION_CHANGES_NOTHING";
|
|
5344
|
+
/** The operator declared another legal entity: that is a transfer, not an edit. */
|
|
5345
|
+
readonly SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER";
|
|
3876
5346
|
readonly CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "CHECKOUT_OFFER_LINE_ITEMS_REQUIRED";
|
|
3877
5347
|
readonly CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED";
|
|
3878
5348
|
readonly CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED";
|
|
3879
5349
|
readonly CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE: "CHECKOUT_OFFER_BUNDLE_VERSION_NOT_BOOKABLE";
|
|
3880
5350
|
readonly CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED: "CHECKOUT_OFFER_FEATURE_DEPENDENCY_UNSATISFIED";
|
|
5351
|
+
readonly CHECKOUT_OFFER_PLAN_NOT_OFFERED: "CHECKOUT_OFFER_PLAN_NOT_OFFERED";
|
|
5352
|
+
readonly CHECKOUT_OFFER_BUNDLE_NOT_OFFERED: "CHECKOUT_OFFER_BUNDLE_NOT_OFFERED";
|
|
5353
|
+
readonly CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED: "CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED";
|
|
5354
|
+
readonly CHECKOUT_OFFER_PRICE_NOT_CURRENT: "CHECKOUT_OFFER_PRICE_NOT_CURRENT";
|
|
3881
5355
|
readonly SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED: "SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED";
|
|
3882
5356
|
readonly SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED";
|
|
3883
5357
|
readonly SUBSCRIPTION_CONTRACT_INVALID_DATE: "SUBSCRIPTION_CONTRACT_INVALID_DATE";
|
|
3884
5358
|
readonly SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "SUBSCRIPTION_CONTRACT_INVALID_WINDOW";
|
|
5359
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH";
|
|
5360
|
+
readonly SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT";
|
|
5361
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH";
|
|
3885
5362
|
readonly SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START";
|
|
3886
5363
|
readonly CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND";
|
|
3887
5364
|
readonly CHECKOUT_OFFER_EXPIRED: "CHECKOUT_OFFER_EXPIRED";
|
|
3888
5365
|
readonly CHECKOUT_OFFER_ALREADY_CONSUMED: "CHECKOUT_OFFER_ALREADY_CONSUMED";
|
|
3889
5366
|
readonly CHECKOUT_OFFER_NOT_CONSUMED: "CHECKOUT_OFFER_NOT_CONSUMED";
|
|
5367
|
+
/**
|
|
5368
|
+
* The offer changed between the checks of `conclude` and the transaction
|
|
5369
|
+
* that consumed it, so the contract checked is not the one the offer now
|
|
5370
|
+
* describes. Nothing was written; load the offer and conclude it again.
|
|
5371
|
+
*/
|
|
5372
|
+
readonly CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED";
|
|
3890
5373
|
readonly SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND";
|
|
3891
5374
|
readonly NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT";
|
|
3892
5375
|
readonly SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED";
|
|
@@ -3899,6 +5382,8 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3899
5382
|
readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
|
|
3900
5383
|
readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
|
|
3901
5384
|
readonly BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE";
|
|
5385
|
+
readonly BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN";
|
|
5386
|
+
readonly BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN";
|
|
3902
5387
|
readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
|
|
3903
5388
|
readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
|
|
3904
5389
|
readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
|
|
@@ -3912,8 +5397,79 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3912
5397
|
readonly PLAN_NOT_IN_CATALOG: "PLAN_NOT_IN_CATALOG";
|
|
3913
5398
|
/** Plan exists but cannot be booked via self-service. */
|
|
3914
5399
|
readonly PLAN_NOT_SELF_SERVICE: "PLAN_NOT_SELF_SERVICE";
|
|
5400
|
+
/**
|
|
5401
|
+
* Plan carries no price for the requested billing cycle, so it is not sold
|
|
5402
|
+
* in it: a plan without a yearly price is a monthly plan.
|
|
5403
|
+
*/
|
|
5404
|
+
readonly PLAN_NOT_SOLD_IN_CYCLE: "PLAN_NOT_SOLD_IN_CYCLE";
|
|
3915
5405
|
/** Plan change refused. Carries `blockers[]` with their own codes. */
|
|
3916
5406
|
readonly PLAN_CHANGE_BLOCKED: "PLAN_CHANGE_BLOCKED";
|
|
5407
|
+
/**
|
|
5408
|
+
* The subscription moved between the read a request was decided on and the
|
|
5409
|
+
* write it attempted, so nothing was written. The caller reloads and asks
|
|
5410
|
+
* again.
|
|
5411
|
+
*/
|
|
5412
|
+
readonly SUBSCRIPTION_CHANGED: "SUBSCRIPTION_CHANGED";
|
|
5413
|
+
/**
|
|
5414
|
+
* The tenant has no subscription to act on.
|
|
5415
|
+
*
|
|
5416
|
+
* `SUBSCRIPTION_NOT_FOUND` states the same fact on the read routes. Both
|
|
5417
|
+
* are already on the wire and a code is renamed only deliberately, so both
|
|
5418
|
+
* are named here rather than one being dropped behind a consumer's back.
|
|
5419
|
+
*/
|
|
5420
|
+
readonly NO_SUBSCRIPTION: "NO_SUBSCRIPTION";
|
|
5421
|
+
/**
|
|
5422
|
+
* The cancellation date the reader was shown is no longer the one the rules
|
|
5423
|
+
* return, so the confirmation is refused rather than silently applied.
|
|
5424
|
+
* Carries the recomputed dates, so the page can re-ask instead of guessing.
|
|
5425
|
+
*/
|
|
5426
|
+
readonly CANCELLATION_TERMS_CHANGED: "CANCELLATION_TERMS_CHANGED";
|
|
5427
|
+
/** The subscription has ended; its plan can no longer be changed. */
|
|
5428
|
+
readonly SUBSCRIPTION_ENDED: "SUBSCRIPTION_ENDED";
|
|
5429
|
+
/** An active special contract blocks self-service plan changes. */
|
|
5430
|
+
readonly PLAN_LOCKED: "PLAN_LOCKED";
|
|
5431
|
+
/** Current usage of one quota exceeds what the target plan allows. */
|
|
5432
|
+
readonly QUOTA_OVER_TARGET: "QUOTA_OVER_TARGET";
|
|
5433
|
+
/** The change drops features the tenant has today. */
|
|
5434
|
+
readonly FEATURE_LOST: "FEATURE_LOST";
|
|
5435
|
+
readonly FEATURES_LOST: "FEATURES_LOST";
|
|
5436
|
+
/** Target plan and cycle already match what is in place. */
|
|
5437
|
+
readonly NO_CHANGE: "NO_CHANGE";
|
|
5438
|
+
/** A shorter cycle cannot start inside the term already running. */
|
|
5439
|
+
readonly CYCLE_SHORTENS_AT_TERM_END: "CYCLE_SHORTENS_AT_TERM_END";
|
|
5440
|
+
/** A cancelled subscription cannot change its billing cycle. */
|
|
5441
|
+
readonly CANCELLATION_LOCKS_THE_CYCLE: "CANCELLATION_LOCKS_THE_CYCLE";
|
|
5442
|
+
/**
|
|
5443
|
+
* A bundle the tenant already holds runs past the cycle they are moving to.
|
|
5444
|
+
*
|
|
5445
|
+
* Its own code rather than `BUNDLE_CYCLE_EXCEEDS_PLAN`, which states the
|
|
5446
|
+
* same rule about a booking that has not been made yet. The two need
|
|
5447
|
+
* different sentences: this one can name the day the obstacle lifts and
|
|
5448
|
+
* tell the reader to cancel the booking, and that advice is wrong for
|
|
5449
|
+
* someone who is only about to book. One template cannot serve both.
|
|
5450
|
+
*/
|
|
5451
|
+
readonly BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE";
|
|
5452
|
+
/**
|
|
5453
|
+
* Features of the previewed bundle are already covered by the plan or by
|
|
5454
|
+
* another booked bundle. A warning rather than a blocker: paying twice is
|
|
5455
|
+
* the customer's decision, and the preview only has to say so first.
|
|
5456
|
+
*/
|
|
5457
|
+
readonly REDUNDANT_FEATURES: "REDUNDANT_FEATURES";
|
|
5458
|
+
/**
|
|
5459
|
+
* A booking's minimum term outlasts the period being cancelled, so the
|
|
5460
|
+
* cancellation takes effect at the end of the term, not of the period.
|
|
5461
|
+
*/
|
|
5462
|
+
readonly MINIMUM_TERM_BINDS: "MINIMUM_TERM_BINDS";
|
|
5463
|
+
/**
|
|
5464
|
+
* The previewed bundle requires features that neither the plan nor an
|
|
5465
|
+
* active booking provides.
|
|
5466
|
+
*
|
|
5467
|
+
* The same string is a `StrictModeWarningCode` in `bundle.types.ts`, where
|
|
5468
|
+
* it names the catalogue-authoring reading of the rule and travels with its
|
|
5469
|
+
* own message. This declaration is the booking preview's blocker, which a
|
|
5470
|
+
* tenant reads and therefore needs a shipped text for.
|
|
5471
|
+
*/
|
|
5472
|
+
readonly BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED";
|
|
3917
5473
|
readonly NO_PENDING_PLAN_VERSION: "NO_PENDING_PLAN_VERSION";
|
|
3918
5474
|
readonly ONBOARDING_CREATE_FAILED: "ONBOARDING_CREATE_FAILED";
|
|
3919
5475
|
readonly BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS";
|
|
@@ -3952,6 +5508,8 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3952
5508
|
readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
|
|
3953
5509
|
readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
|
|
3954
5510
|
readonly BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE";
|
|
5511
|
+
readonly BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE";
|
|
5512
|
+
readonly BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "BUNDLE_VERSION_NOT_PRICED_FOR_PLAN";
|
|
3955
5513
|
readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
|
|
3956
5514
|
readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
|
|
3957
5515
|
readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
|
|
@@ -3979,6 +5537,10 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3979
5537
|
readonly QUOTA_NOT_IN_DISCOVERY_SNAPSHOT: "QUOTA_NOT_IN_DISCOVERY_SNAPSHOT";
|
|
3980
5538
|
readonly DISCOVERY_STATUS_TRANSITION_INVALID: "DISCOVERY_STATUS_TRANSITION_INVALID";
|
|
3981
5539
|
readonly DISCOVERY_NOT_INITIALIZED: "DISCOVERY_NOT_INITIALIZED";
|
|
5540
|
+
/** The uploaded document is not a plan catalog — unparseable, or not an object. */
|
|
5541
|
+
readonly PLAN_CATALOG_UNREADABLE: "PLAN_CATALOG_UNREADABLE";
|
|
5542
|
+
/** It parsed, and then failed the schema or a cross-field rule. */
|
|
5543
|
+
readonly PLAN_CATALOG_INVALID: "PLAN_CATALOG_INVALID";
|
|
3982
5544
|
readonly PROMO_CODE_NOT_FOUND: "PROMO_CODE_NOT_FOUND";
|
|
3983
5545
|
readonly PROMO_CODE_ALREADY_EXISTS: "PROMO_CODE_ALREADY_EXISTS";
|
|
3984
5546
|
readonly PROMO_CODE_HAS_REDEMPTIONS: "PROMO_CODE_HAS_REDEMPTIONS";
|
|
@@ -4001,6 +5563,12 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
4001
5563
|
/** Neither `tenantId` nor `userId` could be resolved from the request. */
|
|
4002
5564
|
readonly TENANT_CONTEXT_MISSING: "TENANT_CONTEXT_MISSING";
|
|
4003
5565
|
readonly TENANT_ADMIN_REQUIRED: "TENANT_ADMIN_REQUIRED";
|
|
5566
|
+
/**
|
|
5567
|
+
* The tenant's billing area — its payment method, and later its invoices
|
|
5568
|
+
* and account — needs the billing permission, which the application maps
|
|
5569
|
+
* to its roles and the tenant's administrator holds by default.
|
|
5570
|
+
*/
|
|
5571
|
+
readonly BILLING_PERMISSION_REQUIRED: "BILLING_PERMISSION_REQUIRED";
|
|
4004
5572
|
readonly SUPER_ADMIN_REQUIRED: "SUPER_ADMIN_REQUIRED";
|
|
4005
5573
|
/** TOTP MFA has never been set up for this user. */
|
|
4006
5574
|
readonly MFA_NOT_SET_UP: "MFA_NOT_SET_UP";
|
|
@@ -4021,7 +5589,7 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
4021
5589
|
/** Email already taken (mapped from `PlatformUserExistsError`). */
|
|
4022
5590
|
readonly EMAIL_EXISTS: "EMAIL_EXISTS";
|
|
4023
5591
|
};
|
|
4024
|
-
type PlatformErrorCode = SetupErrorCode | AuthErrorCode | PromoErrorCode | CatalogErrorCode | BillingErrorCode | ContractErrorCode | RegistrationErrorCode;
|
|
5592
|
+
type PlatformErrorCode = SetupErrorCode | AuthErrorCode | PromoErrorCode | CatalogErrorCode | BillingErrorCode | ContractErrorCode | SubscriberErrorCode | RegistrationErrorCode | PaymentErrorCode | SettingsErrorCode;
|
|
4025
5593
|
/**
|
|
4026
5594
|
* Shape of a coded error response.
|
|
4027
5595
|
*
|
|
@@ -4242,7 +5810,25 @@ interface PendingRegistration {
|
|
|
4242
5810
|
billingCycle: 'MONTHLY' | 'YEARLY' | null;
|
|
4243
5811
|
/** Plaintext code (UI display). Validation runs fresh every time. */
|
|
4244
5812
|
appliedPromoCode: string | null;
|
|
5813
|
+
/**
|
|
5814
|
+
* The billing address and tax identifiers step 4 asks for, which the
|
|
5815
|
+
* subscriber is created with. The address is required before a payment
|
|
5816
|
+
* method is set up; the tax identifiers stay optional.
|
|
5817
|
+
*/
|
|
5818
|
+
addressLine1: string | null;
|
|
5819
|
+
addressLine2: string | null;
|
|
5820
|
+
postalCode: string | null;
|
|
5821
|
+
city: string | null;
|
|
5822
|
+
/** ISO 3166-1 alpha-2, upper case. */
|
|
5823
|
+
country: string | null;
|
|
5824
|
+
vatId: string | null;
|
|
5825
|
+
taxNumber: string | null;
|
|
5826
|
+
/** The gateway's session for the payment method, unique within `checkoutGatewayAccount`. */
|
|
4245
5827
|
checkoutSessionId: string | null;
|
|
5828
|
+
/** The account in `config/saas.yaml#payments.accounts` the session was opened at. */
|
|
5829
|
+
checkoutGatewayAccount: string | null;
|
|
5830
|
+
/** The customer the gateway created for the sign-up, reused when step 4 is repeated. */
|
|
5831
|
+
gatewayCustomerRef: string | null;
|
|
4246
5832
|
checkoutStartedAt: Date | null;
|
|
4247
5833
|
expiresAt: Date;
|
|
4248
5834
|
createdAt: Date;
|
|
@@ -4274,7 +5860,16 @@ interface PendingRegistrationUpdateInput {
|
|
|
4274
5860
|
configJson?: RegistrationConfigSelection | null;
|
|
4275
5861
|
billingCycle?: 'MONTHLY' | 'YEARLY' | null;
|
|
4276
5862
|
appliedPromoCode?: string | null;
|
|
5863
|
+
addressLine1?: string | null;
|
|
5864
|
+
addressLine2?: string | null;
|
|
5865
|
+
postalCode?: string | null;
|
|
5866
|
+
city?: string | null;
|
|
5867
|
+
country?: string | null;
|
|
5868
|
+
vatId?: string | null;
|
|
5869
|
+
taxNumber?: string | null;
|
|
4277
5870
|
checkoutSessionId?: string | null;
|
|
5871
|
+
checkoutGatewayAccount?: string | null;
|
|
5872
|
+
gatewayCustomerRef?: string | null;
|
|
4278
5873
|
checkoutStartedAt?: Date | null;
|
|
4279
5874
|
expiresAt?: Date;
|
|
4280
5875
|
}
|
|
@@ -4282,14 +5877,24 @@ interface PendingRegistrationUpdateInput {
|
|
|
4282
5877
|
interface PendingRegistrationRepository {
|
|
4283
5878
|
findById(id: string): Promise<PendingRegistration | null>;
|
|
4284
5879
|
findByEmail(email: string): Promise<PendingRegistration | null>;
|
|
4285
|
-
/**
|
|
4286
|
-
|
|
5880
|
+
/**
|
|
5881
|
+
* Finds the pending record a gateway session belongs to. A session
|
|
5882
|
+
* identifier is unique only within its account, so both are matched.
|
|
5883
|
+
*/
|
|
5884
|
+
findByCheckoutSession(gatewayAccount: string, sessionId: string): Promise<PendingRegistration | null>;
|
|
4287
5885
|
/**
|
|
4288
5886
|
* Cleanup lookup: all pending records with `expiresAt < now`, max
|
|
4289
5887
|
* `limit` entries per call (batch protection). Ordering irrelevant, the
|
|
4290
5888
|
* cron service iterates sequentially.
|
|
4291
5889
|
*/
|
|
4292
5890
|
findExpired(now: Date, limit: number): Promise<PendingRegistration[]>;
|
|
5891
|
+
/**
|
|
5892
|
+
* Every gateway account a checkout session is still open at: the distinct
|
|
5893
|
+
* `checkoutGatewayAccount` of records in `CHECKOUT_STARTED` whose
|
|
5894
|
+
* `expiresAt` is after `now`. The start refuses when one of them is no
|
|
5895
|
+
* longer configured, because that sign-up's confirmation could not arrive.
|
|
5896
|
+
*/
|
|
5897
|
+
findOpenCheckoutAccounts(now: Date): Promise<string[]>;
|
|
4293
5898
|
create(input: PendingRegistrationCreateInput): Promise<PendingRegistration>;
|
|
4294
5899
|
update(id: string, input: PendingRegistrationUpdateInput): Promise<PendingRegistration>;
|
|
4295
5900
|
/**
|
|
@@ -4299,7 +5904,13 @@ interface PendingRegistrationRepository {
|
|
|
4299
5904
|
* value is the authoritative threshold for the lockout check.
|
|
4300
5905
|
*/
|
|
4301
5906
|
incrementOtpAttemptCount(id: string): Promise<number>;
|
|
4302
|
-
|
|
5907
|
+
/**
|
|
5908
|
+
* Removes the record. With `tx` it is removed on that transaction and comes
|
|
5909
|
+
* back with its rollback — an activation deletes the sign-up on the
|
|
5910
|
+
* transaction that creates the tenant, so a sign-up is either still waiting
|
|
5911
|
+
* or activated, never both.
|
|
5912
|
+
*/
|
|
5913
|
+
delete(id: string, tx?: TransactionContext): Promise<void>;
|
|
4303
5914
|
}
|
|
4304
5915
|
/** Adapter port: detects whether a full user account (verified) exists for this email. */
|
|
4305
5916
|
interface UserAccountLookup {
|
|
@@ -4309,67 +5920,42 @@ interface UserAccountLookup {
|
|
|
4309
5920
|
interface SlugAvailabilityCheck {
|
|
4310
5921
|
isSlugAvailable(slug: string): Promise<boolean>;
|
|
4311
5922
|
}
|
|
4312
|
-
/** Wire format of a created checkout session (provider-agnostic). */
|
|
4313
|
-
interface CheckoutSession {
|
|
4314
|
-
/** Provider-specific session ID (e.g. Stripe `cs_…`). */
|
|
4315
|
-
sessionId: string;
|
|
4316
|
-
/** Payment URL to be opened by the frontend. */
|
|
4317
|
-
checkoutUrl: string;
|
|
4318
|
-
/** Optional: provider name (`stripe`, `dev-stub`) for logging/audit. */
|
|
4319
|
-
provider?: string;
|
|
4320
|
-
}
|
|
4321
|
-
type PaymentEventStatus = 'SUCCEEDED' | 'FAILED';
|
|
4322
|
-
/**
|
|
4323
|
-
* Adapter port: idempotency log for payment webhooks. Stripe (and most
|
|
4324
|
-
* other providers) deliver events at-least-once — the service calls
|
|
4325
|
-
* `tryClaim` as an atomic race guard BEFORE it triggers the final
|
|
4326
|
-
* activation.
|
|
4327
|
-
*/
|
|
4328
|
-
interface PaymentEventLog {
|
|
4329
|
-
/**
|
|
4330
|
-
* Tries to insert an event record via `@unique` INSERT. Returns
|
|
4331
|
-
* `true` if it was newly created (webhook seen for the first time),
|
|
4332
|
-
* `false` if it already exists (duplicate → silently drop).
|
|
4333
|
-
*
|
|
4334
|
-
* Implementations must return a DB unique-constraint-violation error
|
|
4335
|
-
* (Prisma P2002) as `false`.
|
|
4336
|
-
*/
|
|
4337
|
-
tryClaim(eventId: string, payload: {
|
|
4338
|
-
provider: string;
|
|
4339
|
-
sessionId: string | null;
|
|
4340
|
-
status: PaymentEventStatus;
|
|
4341
|
-
rawPayload?: unknown;
|
|
4342
|
-
}): Promise<boolean>;
|
|
4343
|
-
}
|
|
4344
5923
|
interface FinalActivationResult {
|
|
4345
5924
|
userId: string;
|
|
4346
5925
|
tenantId: string;
|
|
4347
5926
|
subscriptionId: string;
|
|
5927
|
+
/**
|
|
5928
|
+
* The subscriber created for the tenant in the same transaction — the party
|
|
5929
|
+
* its contracts are concluded with. `subscriberFromRegistration(pending)`
|
|
5930
|
+
* says what it is created with.
|
|
5931
|
+
*/
|
|
5932
|
+
subscriberId: string;
|
|
5933
|
+
}
|
|
5934
|
+
/** The transaction a sign-up is activated on. */
|
|
5935
|
+
interface RegistrationActivation {
|
|
5936
|
+
/**
|
|
5937
|
+
* Opened by the platform, which has already claimed the gateway's
|
|
5938
|
+
* confirmation on it and records the confirmed payment method on it once
|
|
5939
|
+
* `activate` returns. Every row the activation writes goes through it, so
|
|
5940
|
+
* a failure anywhere rolls back all of it — the claim included, and the
|
|
5941
|
+
* gateway's retry is handled rather than discarded as a duplicate.
|
|
5942
|
+
*/
|
|
5943
|
+
tx: TransactionContext;
|
|
4348
5944
|
}
|
|
4349
5945
|
/**
|
|
4350
|
-
* Adapter port:
|
|
4351
|
-
*
|
|
4352
|
-
* own schema (e.g. Tenant + TenantUser + Role + UserRole +
|
|
4353
|
-
* Subscription).
|
|
5946
|
+
* Adapter port: creates User + Tenant + Subscriber + Subscription once the
|
|
5947
|
+
* gateway confirmed the sign-up's payment method. App-specific — each app has
|
|
5948
|
+
* its own schema (e.g. Tenant + TenantUser + Role + UserRole + Subscription).
|
|
4354
5949
|
*
|
|
4355
|
-
* Implementations
|
|
4356
|
-
*
|
|
5950
|
+
* Implementations write on `activation.tx` and open no transaction of their
|
|
5951
|
+
* own: a write beside it would survive the rollback that undoes the rest. The
|
|
5952
|
+
* subscriber is created there too, before any contract:
|
|
5953
|
+
* `SubscriberService.createForTenant(tenantId, subscriberFromRegistration(pending),
|
|
5954
|
+
* activation.tx)`, or `CheckoutOfferService.conclude` with `subscriber` and
|
|
5955
|
+
* that transaction.
|
|
4357
5956
|
*/
|
|
4358
5957
|
interface ActivationOrchestrator {
|
|
4359
|
-
activate(pending: PendingRegistration): Promise<FinalActivationResult>;
|
|
4360
|
-
}
|
|
4361
|
-
interface HandlePaymentEventInput {
|
|
4362
|
-
eventId: string;
|
|
4363
|
-
sessionId: string | null;
|
|
4364
|
-
provider: string;
|
|
4365
|
-
status: PaymentEventStatus;
|
|
4366
|
-
rawPayload?: unknown;
|
|
4367
|
-
}
|
|
4368
|
-
type HandlePaymentEventReason = 'ALREADY_PROCESSED' | 'PAYMENT_NOT_SUCCEEDED' | 'MISSING_SESSION_ID' | 'PENDING_REGISTRATION_NOT_FOUND' | 'INVALID_STATE';
|
|
4369
|
-
interface HandlePaymentEventResult {
|
|
4370
|
-
activated: boolean;
|
|
4371
|
-
reason?: HandlePaymentEventReason;
|
|
4372
|
-
result?: FinalActivationResult;
|
|
5958
|
+
activate(pending: PendingRegistration, activation: RegistrationActivation): Promise<FinalActivationResult>;
|
|
4373
5959
|
}
|
|
4374
5960
|
interface CleanupResult {
|
|
4375
5961
|
/** Number of deleted PendingRegistration records. */
|
|
@@ -4380,7 +5966,7 @@ interface CleanupResult {
|
|
|
4380
5966
|
*/
|
|
4381
5967
|
moreAvailable: boolean;
|
|
4382
5968
|
}
|
|
4383
|
-
type RegistrationAuditEventType = 'REGISTRATION_STARTED' | 'REGISTRATION_NEUTRAL_ACTIVE_USER' | 'REGISTRATION_NEUTRAL_REPLAY' | 'REGISTRATION_NEUTRAL_EXPIRED' | 'OTP_VERIFIED' | 'OTP_VERIFY_FAILED' | 'OTP_RESEND_REQUESTED' | 'OTP_RATE_LIMIT_HIT' | 'PLAN_SELECTED' | 'CHECKOUT_STARTED' | 'PAYMENT_RECEIVED' | '
|
|
5969
|
+
type RegistrationAuditEventType = 'REGISTRATION_STARTED' | 'REGISTRATION_NEUTRAL_ACTIVE_USER' | 'REGISTRATION_NEUTRAL_REPLAY' | 'REGISTRATION_NEUTRAL_EXPIRED' | 'OTP_VERIFIED' | 'OTP_VERIFY_FAILED' | 'OTP_RESEND_REQUESTED' | 'OTP_RATE_LIMIT_HIT' | 'PLAN_SELECTED' | 'CHECKOUT_STARTED' | 'PAYMENT_RECEIVED' | 'PAYMENT_FAILED' | 'ACTIVATION_COMPLETED' | 'LOGIN_SUCCEEDED' | 'LOGIN_INVALID_CREDENTIALS' | 'LOGIN_ONBOARDING_REQUIRED';
|
|
4384
5970
|
/**
|
|
4385
5971
|
* Context information that the audit layer records per event.
|
|
4386
5972
|
* IP is expected as a hashed fingerprint — no plaintext IPs in the
|
|
@@ -4431,8 +6017,6 @@ interface ConfiguratorModel {
|
|
|
4431
6017
|
popular?: boolean;
|
|
4432
6018
|
}
|
|
4433
6019
|
interface ConfiguratorCatalog {
|
|
4434
|
-
/** Factor `yearlyNet = monthlyNet * cycleDiscount` (typically 10 = 2 months free). */
|
|
4435
|
-
cycleDiscount: number;
|
|
4436
6020
|
currency: string;
|
|
4437
6021
|
vatRate: number;
|
|
4438
6022
|
models: ConfiguratorModel[];
|
|
@@ -4457,6 +6041,10 @@ interface ConfiguratorPriceBreakdown {
|
|
|
4457
6041
|
modelMonthlyNet: number;
|
|
4458
6042
|
subtotalMonthlyNet: number;
|
|
4459
6043
|
subtotalNet: number;
|
|
6044
|
+
/**
|
|
6045
|
+
* Net amount taken off `subtotalNet`: the promo preview's gross discount
|
|
6046
|
+
* converted at `vatRate`.
|
|
6047
|
+
*/
|
|
4460
6048
|
discountAmount: number;
|
|
4461
6049
|
totalNet: number;
|
|
4462
6050
|
vatRate: number;
|
|
@@ -4533,8 +6121,6 @@ interface ConfiguratorPlanMarketing {
|
|
|
4533
6121
|
*/
|
|
4534
6122
|
interface ConfiguratorMarketingProvider {
|
|
4535
6123
|
listPlanMarketing(): ConfiguratorPlanMarketing[];
|
|
4536
|
-
/** Factor `yearlyNet = monthlyNet * cycleDiscount`. Default `10`. */
|
|
4537
|
-
getCycleDiscount(): number;
|
|
4538
6124
|
getVatRate(): number;
|
|
4539
6125
|
getCurrency(): string;
|
|
4540
6126
|
}
|
|
@@ -4555,6 +6141,7 @@ interface RegistrationPromoPreview {
|
|
|
4555
6141
|
reason?: string;
|
|
4556
6142
|
percent?: number;
|
|
4557
6143
|
label?: string;
|
|
6144
|
+
/** The discount in gross, reckoned against `subtotalGross`. */
|
|
4558
6145
|
discountAmount?: number;
|
|
4559
6146
|
}>;
|
|
4560
6147
|
}
|
|
@@ -4626,6 +6213,8 @@ interface PendingRegistrationSnapshot {
|
|
|
4626
6213
|
config: RegistrationConfigSelection | null;
|
|
4627
6214
|
billingCycle: 'MONTHLY' | 'YEARLY' | null;
|
|
4628
6215
|
appliedPromoCode: string | null;
|
|
6216
|
+
/** The billing details step 4 already took, to fill its form again. */
|
|
6217
|
+
billingDetails: RegistrationBillingDetails | null;
|
|
4629
6218
|
checkoutSessionId: string | null;
|
|
4630
6219
|
}
|
|
4631
6220
|
interface ResumeRegistrationResult {
|
|
@@ -4634,27 +6223,6 @@ interface ResumeRegistrationResult {
|
|
|
4634
6223
|
nextStep: RegistrationStep;
|
|
4635
6224
|
snapshot: PendingRegistrationSnapshot;
|
|
4636
6225
|
}
|
|
4637
|
-
/** Adapter port: payment provider (Stripe, Dev-Stub, Mollie, ...). */
|
|
4638
|
-
interface PaymentProvider {
|
|
4639
|
-
/**
|
|
4640
|
-
* Creates a checkout session at the payment provider and returns the URL
|
|
4641
|
-
* that the frontend should redirect to.
|
|
4642
|
-
*
|
|
4643
|
-
* @param params.pendingRegistrationId Stored as `client_reference_id` (or similar)
|
|
4644
|
-
* in the provider — the webhook needs it to link back.
|
|
4645
|
-
* @param params.planId The chosen plan (Stripe price/product mapping lives
|
|
4646
|
-
* in the adapter).
|
|
4647
|
-
* @param params.successUrl Where to go after successful payment.
|
|
4648
|
-
* @param params.cancelUrl Where to go on cancellation.
|
|
4649
|
-
*/
|
|
4650
|
-
createCheckoutSession(params: {
|
|
4651
|
-
pendingRegistrationId: string;
|
|
4652
|
-
planId: string;
|
|
4653
|
-
email: string;
|
|
4654
|
-
successUrl: string;
|
|
4655
|
-
cancelUrl: string;
|
|
4656
|
-
}): Promise<CheckoutSession>;
|
|
4657
|
-
}
|
|
4658
6226
|
/** Adapter port: OTP delivery via email (or another channel). */
|
|
4659
6227
|
interface RegistrationOtpDelivery {
|
|
4660
6228
|
sendVerificationOtp(params: {
|
|
@@ -4720,9 +6288,27 @@ interface SelectPlanResult {
|
|
|
4720
6288
|
nextStep: RegistrationStep;
|
|
4721
6289
|
selectedPlanId: string;
|
|
4722
6290
|
}
|
|
6291
|
+
/**
|
|
6292
|
+
* The billing address and tax identifiers a sign-up gives in step 4, which its
|
|
6293
|
+
* subscriber is created with. The address is required; the tax identifiers
|
|
6294
|
+
* stay optional until the tax adapter says when one is needed.
|
|
6295
|
+
*/
|
|
6296
|
+
interface RegistrationBillingDetails {
|
|
6297
|
+
addressLine1: string;
|
|
6298
|
+
addressLine2?: string | null;
|
|
6299
|
+
postalCode: string;
|
|
6300
|
+
city: string;
|
|
6301
|
+
/** ISO 3166-1 alpha-2, upper case. */
|
|
6302
|
+
country: string;
|
|
6303
|
+
vatId?: string | null;
|
|
6304
|
+
taxNumber?: string | null;
|
|
6305
|
+
}
|
|
4723
6306
|
interface StartCheckoutInput {
|
|
4724
6307
|
pendingRegistrationId: string;
|
|
6308
|
+
billingDetails: RegistrationBillingDetails;
|
|
6309
|
+
/** Where the gateway's form sends the person once the payment method is set up. */
|
|
4725
6310
|
successUrl: string;
|
|
6311
|
+
/** Where the gateway's form sends the person who leaves it. */
|
|
4726
6312
|
cancelUrl: string;
|
|
4727
6313
|
}
|
|
4728
6314
|
interface StartCheckoutResult {
|
|
@@ -4766,6 +6352,306 @@ interface SetupConfirmMfaResponse {
|
|
|
4766
6352
|
ok: boolean;
|
|
4767
6353
|
}
|
|
4768
6354
|
|
|
6355
|
+
/** What the adapter's schema can actually answer about a version's dates. */
|
|
6356
|
+
interface PlanVersionMappingFields {
|
|
6357
|
+
/** `validFrom`/`validUntil` are maintained; otherwise both read as null. */
|
|
6358
|
+
validityWindows: boolean;
|
|
6359
|
+
/** `endsAt` exists; otherwise the field is left off the record entirely. */
|
|
6360
|
+
endsAt: boolean;
|
|
6361
|
+
}
|
|
6362
|
+
/** A `plans` row as either adapter reads it back. */
|
|
6363
|
+
interface CanonicalPlanRow {
|
|
6364
|
+
id: string;
|
|
6365
|
+
planKey: string;
|
|
6366
|
+
label: string;
|
|
6367
|
+
description: string | null;
|
|
6368
|
+
icon: string | null;
|
|
6369
|
+
sortOrder: number;
|
|
6370
|
+
createdAt: Date;
|
|
6371
|
+
updatedAt: Date;
|
|
6372
|
+
deletedAt: Date | null;
|
|
6373
|
+
}
|
|
6374
|
+
/** A `plan_versions` row as either adapter reads it back. */
|
|
6375
|
+
interface CanonicalPlanVersionRow {
|
|
6376
|
+
id: string;
|
|
6377
|
+
version: number;
|
|
6378
|
+
baseVersionId: string | null;
|
|
6379
|
+
features: unknown;
|
|
6380
|
+
quotas: unknown;
|
|
6381
|
+
monthlyNet: unknown;
|
|
6382
|
+
yearlyNet: unknown;
|
|
6383
|
+
marketed: boolean;
|
|
6384
|
+
publishedAt: Date | null;
|
|
6385
|
+
supersededAt: Date | null;
|
|
6386
|
+
publishedChanges: unknown;
|
|
6387
|
+
changeNote: string;
|
|
6388
|
+
nonRegressive: boolean;
|
|
6389
|
+
validFrom?: Date | null;
|
|
6390
|
+
validUntil?: Date | null;
|
|
6391
|
+
endsAt?: Date | null;
|
|
6392
|
+
createdByUserId: string | null;
|
|
6393
|
+
publishedByUserId: string | null;
|
|
6394
|
+
createdAt: Date;
|
|
6395
|
+
updatedAt: Date;
|
|
6396
|
+
}
|
|
6397
|
+
declare function toPlanRow(row: CanonicalPlanRow): PlanRow;
|
|
6398
|
+
/**
|
|
6399
|
+
* `planKey` is passed rather than read off the row: the canonical schema stores
|
|
6400
|
+
* the plan key in `planId`, but an adapter translating a consumer schema with a
|
|
6401
|
+
* real foreign key has to resolve it first, and only the adapter knows which
|
|
6402
|
+
* shape it is looking at.
|
|
6403
|
+
*/
|
|
6404
|
+
declare function toPlanVersionRow(row: CanonicalPlanVersionRow, planKey: string, fields: PlanVersionMappingFields): PlanVersionRow;
|
|
6405
|
+
|
|
6406
|
+
/**
|
|
6407
|
+
* The legal identity of the issuer a catalogue names, or none where it names no
|
|
6408
|
+
* issuer.
|
|
6409
|
+
*
|
|
6410
|
+
* Settled through the same reader as the recorded side, and that symmetry is
|
|
6411
|
+
* the point rather than tidiness: the record is a verbatim copy of the file, so
|
|
6412
|
+
* a value whose two sides were settled differently would differ from itself. A
|
|
6413
|
+
* legal name written with a trailing space would then refuse the SECOND start on
|
|
6414
|
+
* a file nobody touched, and no declaration could make it stop happening.
|
|
6415
|
+
*/
|
|
6416
|
+
declare function issuerIdentityOf(issuer: PlanCatalogIssuer | undefined): LegalIdentity | null;
|
|
6417
|
+
/**
|
|
6418
|
+
* The issuer identity a recorded settings tree holds.
|
|
6419
|
+
*
|
|
6420
|
+
* Read defensively rather than cast: the record is JSON as some earlier version
|
|
6421
|
+
* of this platform wrote it, and a tree without an issuer, or with one whose
|
|
6422
|
+
* legal name is not a name, is read as "no identity was recorded" — which is
|
|
6423
|
+
* the first naming, not a change. The schema keeps a name of only whitespace out
|
|
6424
|
+
* of the file; this keeps one out of a record written before it did.
|
|
6425
|
+
*/
|
|
6426
|
+
declare function recordedIssuerIdentity(settings: AppliedSettingsValues | null | undefined): LegalIdentity | null;
|
|
6427
|
+
/** Why `issuer.correctionOf` does not cover the change it is there to declare. */
|
|
6428
|
+
type IssuerCorrectionFault =
|
|
6429
|
+
/** There is no declaration at all. */
|
|
6430
|
+
{
|
|
6431
|
+
kind: 'absent';
|
|
6432
|
+
}
|
|
6433
|
+
/**
|
|
6434
|
+
* The file names no issuer for a declaration to be about — the block is
|
|
6435
|
+
* gone, or it is there with a name that reads as nothing. Never a
|
|
6436
|
+
* correction, whatever is declared: there is no entity on this side for the
|
|
6437
|
+
* recorded one to be the same as.
|
|
6438
|
+
*/
|
|
6439
|
+
| {
|
|
6440
|
+
kind: 'names-no-issuer';
|
|
6441
|
+
}
|
|
6442
|
+
/**
|
|
6443
|
+
* It names a value the record does not hold. Either the declaration is
|
|
6444
|
+
* stale — it belongs to a correction already applied — or it is about
|
|
6445
|
+
* another entity than the one this installation recorded.
|
|
6446
|
+
*/
|
|
6447
|
+
| {
|
|
6448
|
+
kind: 'names-another-value';
|
|
6449
|
+
field: LegalIdentityField;
|
|
6450
|
+
declared: string | null;
|
|
6451
|
+
recorded: string | null;
|
|
6452
|
+
}
|
|
6453
|
+
/** It says nothing about a field the change moves, so that field is undeclared. */
|
|
6454
|
+
| {
|
|
6455
|
+
kind: 'leaves-a-field-out';
|
|
6456
|
+
field: LegalIdentityField;
|
|
6457
|
+
recorded: string | null;
|
|
6458
|
+
};
|
|
6459
|
+
/** What a start finds when it compares the file's issuer with the recorded one. */
|
|
6460
|
+
type IssuerIdentityChange =
|
|
6461
|
+
/** Neither the record nor the file names an issuer. */
|
|
6462
|
+
{
|
|
6463
|
+
kind: 'none-named';
|
|
6464
|
+
}
|
|
6465
|
+
/** The same entity, whatever the address and the contact details did. */
|
|
6466
|
+
| {
|
|
6467
|
+
kind: 'unchanged';
|
|
6468
|
+
identity: LegalIdentity;
|
|
6469
|
+
}
|
|
6470
|
+
/**
|
|
6471
|
+
* The first issuer this installation names. No contract can have been
|
|
6472
|
+
* concluded under another one, so nothing is declared for it.
|
|
6473
|
+
*/
|
|
6474
|
+
| {
|
|
6475
|
+
kind: 'first-naming';
|
|
6476
|
+
identity: LegalIdentity;
|
|
6477
|
+
}
|
|
6478
|
+
/** The same entity, corrected as the file declares. `current` is never absent. */
|
|
6479
|
+
| {
|
|
6480
|
+
kind: 'corrected';
|
|
6481
|
+
recorded: LegalIdentity;
|
|
6482
|
+
current: LegalIdentity;
|
|
6483
|
+
moved: readonly LegalIdentityField[];
|
|
6484
|
+
reason: string;
|
|
6485
|
+
}
|
|
6486
|
+
/** Another identity, with no declaration that covers it. Refused. */
|
|
6487
|
+
| {
|
|
6488
|
+
kind: 'undeclared';
|
|
6489
|
+
recorded: LegalIdentity;
|
|
6490
|
+
/** `null` where the file names no issuer that has a name — see `names-no-issuer`. */
|
|
6491
|
+
current: LegalIdentity | null;
|
|
6492
|
+
moved: readonly LegalIdentityField[];
|
|
6493
|
+
fault: IssuerCorrectionFault;
|
|
6494
|
+
};
|
|
6495
|
+
/**
|
|
6496
|
+
* What the issuer in the file is, against the identity the record holds.
|
|
6497
|
+
*
|
|
6498
|
+
* `recorded` is `null` on the very first start, and on an installation that
|
|
6499
|
+
* has never named an issuer.
|
|
6500
|
+
*/
|
|
6501
|
+
declare function classifyIssuerChange(recorded: LegalIdentity | null, issuer: PlanCatalogIssuer | undefined): IssuerIdentityChange;
|
|
6502
|
+
|
|
6503
|
+
/** A `subscribers` row as either adapter reads it back. */
|
|
6504
|
+
interface CanonicalSubscriberRow {
|
|
6505
|
+
id: string;
|
|
6506
|
+
customerSequence: number;
|
|
6507
|
+
customerNumberPrefix: string;
|
|
6508
|
+
legalName: string;
|
|
6509
|
+
addressLine1: string | null;
|
|
6510
|
+
addressLine2: string | null;
|
|
6511
|
+
postalCode: string | null;
|
|
6512
|
+
city: string | null;
|
|
6513
|
+
country: string | null;
|
|
6514
|
+
vatId: string | null;
|
|
6515
|
+
taxNumber: string | null;
|
|
6516
|
+
invoiceEmail: string | null;
|
|
6517
|
+
migrated: boolean;
|
|
6518
|
+
createdAt: Date;
|
|
6519
|
+
updatedAt: Date;
|
|
6520
|
+
}
|
|
6521
|
+
/** A `subscriber_corrections` row as either adapter reads it back. */
|
|
6522
|
+
interface CanonicalSubscriberCorrectionRow {
|
|
6523
|
+
id: string;
|
|
6524
|
+
subscriberId: string;
|
|
6525
|
+
previous: unknown;
|
|
6526
|
+
corrected: unknown;
|
|
6527
|
+
reason: string;
|
|
6528
|
+
correctedBy: string;
|
|
6529
|
+
correctedAt: Date;
|
|
6530
|
+
}
|
|
6531
|
+
/**
|
|
6532
|
+
* The customer number a subscriber is known by: the prefix it was assigned
|
|
6533
|
+
* with, then the number. Kept apart in storage so the number orders and the
|
|
6534
|
+
* prefix stays what it was on the day it was assigned.
|
|
6535
|
+
*/
|
|
6536
|
+
declare function formatCustomerNumber(prefix: string, sequence: number): string;
|
|
6537
|
+
/** `tenantId` is the tenant the subscriber's live link names, read beside the row. */
|
|
6538
|
+
declare function toSubscriberRecord(row: CanonicalSubscriberRow, tenantId: string | null): SubscriberRecord;
|
|
6539
|
+
declare function toSubscriberCorrectionRecord(row: CanonicalSubscriberCorrectionRow): SubscriberCorrectionRecord;
|
|
6540
|
+
/**
|
|
6541
|
+
* The part of a correction that changes something: every corrected field whose
|
|
6542
|
+
* stored value differs, with the value it replaces. Both adapters decide this
|
|
6543
|
+
* the same way, under the lock they read `current` with.
|
|
6544
|
+
*/
|
|
6545
|
+
declare function identityCorrectionDelta(current: Pick<SubscriberRecord, LegalIdentityField>, corrected: SubscriberIdentityValues): SubscriberIdentityDelta;
|
|
6546
|
+
/**
|
|
6547
|
+
* Who a new contract is between: the subscriber as it stands, and the issuer as
|
|
6548
|
+
* the running configuration names it — or no issuer, where it names none.
|
|
6549
|
+
*/
|
|
6550
|
+
declare function contractPartiesOf(subscriber: SubscriberRecord, issuer: PlanCatalog['issuer']): SubscriptionContractParties;
|
|
6551
|
+
/**
|
|
6552
|
+
* The subscriber a completed sign-up is created with: the name the tenant was
|
|
6553
|
+
* registered under as its legal name, the address the registration was
|
|
6554
|
+
* verified with as its invoice email, and the billing address and tax
|
|
6555
|
+
* identifiers step 4 took.
|
|
6556
|
+
*/
|
|
6557
|
+
declare function subscriberFromRegistration(pending: Pick<PendingRegistration, 'tenantName' | 'email' | 'addressLine1' | 'addressLine2' | 'postalCode' | 'city' | 'country' | 'vatId' | 'taxNumber'>): NewSubscriberDetails;
|
|
6558
|
+
|
|
6559
|
+
/** The payment method types, in the order a form offers them. */
|
|
6560
|
+
declare const PAYMENT_METHOD_TYPES: readonly PaymentMethodType[];
|
|
6561
|
+
/** A `subscriber_payment_methods` row as either adapter reads it back. */
|
|
6562
|
+
interface CanonicalSubscriberPaymentMethodRow {
|
|
6563
|
+
id: string;
|
|
6564
|
+
subscriberId: string;
|
|
6565
|
+
gatewayAccount: string;
|
|
6566
|
+
provider: string;
|
|
6567
|
+
customerRef: string;
|
|
6568
|
+
paymentMethodRef: string;
|
|
6569
|
+
type: string;
|
|
6570
|
+
brand: string | null;
|
|
6571
|
+
last4: string;
|
|
6572
|
+
expiryMonth: number | null;
|
|
6573
|
+
expiryYear: number | null;
|
|
6574
|
+
country: string | null;
|
|
6575
|
+
bankCode: string | null;
|
|
6576
|
+
mandateReference: string | null;
|
|
6577
|
+
status: string;
|
|
6578
|
+
confirmedAt: Date;
|
|
6579
|
+
replacedAt: Date | null;
|
|
6580
|
+
createdAt: Date;
|
|
6581
|
+
}
|
|
6582
|
+
/**
|
|
6583
|
+
* Reads a row back as a record.
|
|
6584
|
+
*
|
|
6585
|
+
* The two text columns with a closed set of values are checked rather than
|
|
6586
|
+
* cast: a value written by hand or by an older release would otherwise reach a
|
|
6587
|
+
* screen as a type nobody handles.
|
|
6588
|
+
*/
|
|
6589
|
+
declare function toSubscriberPaymentMethodRecord(row: CanonicalSubscriberPaymentMethodRow): SubscriberPaymentMethodRecord;
|
|
6590
|
+
/** The columns a confirmed payment method is written with, and nothing a caller added beside them. */
|
|
6591
|
+
declare function subscriberPaymentMethodColumns(data: RecordSubscriberPaymentMethodData): Omit<CanonicalSubscriberPaymentMethodRow, 'id' | 'status' | 'replacedAt' | 'createdAt'>;
|
|
6592
|
+
|
|
6593
|
+
/** A `subscription_contracts` row as either adapter reads it back. */
|
|
6594
|
+
interface CanonicalContractRow {
|
|
6595
|
+
id: string;
|
|
6596
|
+
tenantId: string;
|
|
6597
|
+
subscriberId: string;
|
|
6598
|
+
subscriberSnapshot: unknown;
|
|
6599
|
+
issuerSnapshot: unknown;
|
|
6600
|
+
partiesMigrated: boolean;
|
|
6601
|
+
status: string;
|
|
6602
|
+
effectiveFrom: Date;
|
|
6603
|
+
effectiveUntil: Date | null;
|
|
6604
|
+
originalOfferId: string | null;
|
|
6605
|
+
originalPlanVersionId: string | null;
|
|
6606
|
+
originalBundleVersionIds: unknown;
|
|
6607
|
+
entitlementSnapshot: unknown;
|
|
6608
|
+
priceSnapshot: unknown;
|
|
6609
|
+
promotionSnapshots: unknown;
|
|
6610
|
+
promoCodeSnapshots: unknown;
|
|
6611
|
+
termsSnapshot: unknown;
|
|
6612
|
+
createdAt: Date;
|
|
6613
|
+
updatedAt: Date;
|
|
6614
|
+
}
|
|
6615
|
+
/** A `contract_line_items` row as either adapter reads it back. */
|
|
6616
|
+
interface CanonicalContractLineItemRow {
|
|
6617
|
+
id: string;
|
|
6618
|
+
contractId: string;
|
|
6619
|
+
kind: string;
|
|
6620
|
+
sourceKey: string;
|
|
6621
|
+
sourceVersionId: string | null;
|
|
6622
|
+
titleSnapshot: string;
|
|
6623
|
+
descriptionSnapshot: string | null;
|
|
6624
|
+
quantity: number;
|
|
6625
|
+
unit: string | null;
|
|
6626
|
+
priceNet: unknown;
|
|
6627
|
+
priceGross: unknown;
|
|
6628
|
+
billingCycle: string;
|
|
6629
|
+
currency: string;
|
|
6630
|
+
taxRate: unknown;
|
|
6631
|
+
taxAmount: unknown;
|
|
6632
|
+
minimumTermUntil: Date | null;
|
|
6633
|
+
featuresSnapshot: unknown;
|
|
6634
|
+
quotaEffectsSnapshot: unknown;
|
|
6635
|
+
metadata: unknown;
|
|
6636
|
+
createdAt: Date;
|
|
6637
|
+
}
|
|
6638
|
+
declare function toSubscriptionContractRecord(row: CanonicalContractRow, lineItems: CanonicalContractLineItemRow[]): SubscriptionContractRecord;
|
|
6639
|
+
declare function toContractLineItemRecord(row: CanonicalContractLineItemRow): ContractLineItemRecord;
|
|
6640
|
+
/**
|
|
6641
|
+
* The columns the issuer check reads off a running contract. A narrow row of
|
|
6642
|
+
* its own, because that query selects four columns rather than the whole
|
|
6643
|
+
* contract with its lines: it runs at every start, and an installation with
|
|
6644
|
+
* thousands of running contracts should not load them to count them.
|
|
6645
|
+
*/
|
|
6646
|
+
interface CanonicalRunningContractRow {
|
|
6647
|
+
id: string;
|
|
6648
|
+
tenantId: string;
|
|
6649
|
+
issuerSnapshot: unknown;
|
|
6650
|
+
effectiveFrom: Date;
|
|
6651
|
+
}
|
|
6652
|
+
/** One running contract, and the legal name on its issuer copy where it has one. */
|
|
6653
|
+
declare function toRunningContractIssuer(row: CanonicalRunningContractRow): RunningContractIssuer;
|
|
6654
|
+
|
|
4769
6655
|
/** Why a version is editable (for UI badges + audit logs). */
|
|
4770
6656
|
type VersionEditableReason = 'draft' | 'pre-active';
|
|
4771
6657
|
interface VersionEditability {
|
|
@@ -4780,6 +6666,30 @@ interface VersionEditability {
|
|
|
4780
6666
|
*/
|
|
4781
6667
|
declare function isVersionEditable(v: VersionedEntityBase, now?: Date): VersionEditability;
|
|
4782
6668
|
|
|
6669
|
+
/** The part of a plan card this rule reads and writes. */
|
|
6670
|
+
interface RecommendablePlan {
|
|
6671
|
+
planKey: string;
|
|
6672
|
+
highlight: boolean;
|
|
6673
|
+
}
|
|
6674
|
+
/**
|
|
6675
|
+
* Leaves the mark on at most one plan, in place, and returns the winner.
|
|
6676
|
+
*
|
|
6677
|
+
* `inRequestedLocale` holds the keys of the plans whose card was described in
|
|
6678
|
+
* the language that was asked for. Where a caller has no fallback to model —
|
|
6679
|
+
* the SuperAdmin edits one language at a time — passing every key, or none,
|
|
6680
|
+
* gives the same answer: the first plan in the list order wins.
|
|
6681
|
+
*
|
|
6682
|
+
* The list order is the caller's, and it is what the reader sees, so the
|
|
6683
|
+
* answer is the first recommended card on the page. Said exactly, because it
|
|
6684
|
+
* is easy to overstate: the tie-break inherits whatever order the caller
|
|
6685
|
+
* arranged, and where two plans are equal by every criterion it sorted on,
|
|
6686
|
+
* that order is the repository's. `PlanRepository.list` promises none, so an
|
|
6687
|
+
* adapter that returns rows in a different order each time would move the mark
|
|
6688
|
+
* between two otherwise indistinguishable plans. The shipped adapters order
|
|
6689
|
+
* totally.
|
|
6690
|
+
*/
|
|
6691
|
+
declare function keepOneRecommended<T extends RecommendablePlan>(plans: T[], inRequestedLocale: ReadonlySet<string>): T | null;
|
|
6692
|
+
|
|
4783
6693
|
declare const ERROR_MESSAGES_EN: Record<PlatformErrorCode, string>;
|
|
4784
6694
|
/** Values available for interpolation into a message template. */
|
|
4785
6695
|
type ErrorMessageParams = Record<string, unknown>;
|
|
@@ -4789,6 +6699,23 @@ type ErrorMessageParams = Record<string, unknown>;
|
|
|
4789
6699
|
* vanishing.
|
|
4790
6700
|
*/
|
|
4791
6701
|
declare function formatErrorMessage(template: string, params?: ErrorMessageParams): string;
|
|
6702
|
+
/**
|
|
6703
|
+
* An error body this function can read.
|
|
6704
|
+
*
|
|
6705
|
+
* `code` is widened past `PlatformErrorCode` on purpose. The function checks
|
|
6706
|
+
* `typeof body.code === 'string'` and resolves whatever it finds, and the
|
|
6707
|
+
* `overrides` parameter exists so a consumer can bring its own codes — one
|
|
6708
|
+
* consumer carries 98 of them against the platform's 135, overlapping in five.
|
|
6709
|
+
* A closed union here would reject exactly the case the parameter is for, and
|
|
6710
|
+
* the cast that works around it is one a reader has to be told is deliberate.
|
|
6711
|
+
*
|
|
6712
|
+
* `PlatformErrorBody` stays closed: a body the *platform* produces really does
|
|
6713
|
+
* carry a platform code. It is the reader that has to accept more. The same
|
|
6714
|
+
* shape appears in `SaLocale` next door, for the same reason.
|
|
6715
|
+
*/
|
|
6716
|
+
type ResolvableErrorBody = Omit<Partial<PlatformErrorBody>, 'code'> & Record<string, unknown> & {
|
|
6717
|
+
code?: PlatformErrorCode | (string & {});
|
|
6718
|
+
};
|
|
4792
6719
|
/**
|
|
4793
6720
|
* Turns an error body into display text.
|
|
4794
6721
|
*
|
|
@@ -4802,8 +6729,8 @@ declare function formatErrorMessage(template: string, params?: ErrorMessageParam
|
|
|
4802
6729
|
* second, so a template may name either without the value being duplicated on
|
|
4803
6730
|
* the wire.
|
|
4804
6731
|
*/
|
|
4805
|
-
declare function resolveErrorMessage(body:
|
|
6732
|
+
declare function resolveErrorMessage(body: ResolvableErrorBody, overrides?: Partial<Record<string, string>>, defaults?: Partial<Record<string, string>>): string;
|
|
4806
6733
|
|
|
4807
6734
|
declare const ERROR_MESSAGES_DE: Record<PlatformErrorCode, string>;
|
|
4808
6735
|
|
|
4809
|
-
export { AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferStatus, type CheckoutSession, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ContractErrorCode, type ContractLineItemKind, type ContractLineItemRecord, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type HandlePaymentEventInput, type HandlePaymentEventReason, type HandlePaymentEventResult, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type KpiCardDef, type KpiDisplayHint, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MfaPort, type NewContractLineItemData, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PasswordHasher, type PasswordResetCliResult, type PaymentEventLog, type PaymentEventStatus, type PaymentProvider, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionFilter, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RedeemPromoInTransactionCallback, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, SETUP_ERROR_CODES, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SetCatalogEntryReviewData, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartRegistrationInput, type StartRegistrationResult, type StrictModeWarning, type StrictModeWarningCode, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, classifyBundleVersionDiff, classifyPlanDiff, collectUnsatisfiedRequires, coverageExcludingSelf, formatErrorMessage, isBundleRedundant, isPlatformUserExistsError, isVersionEditable, missingRequiresFor, pickActivePromo, promoStatus, resolveBundleAvailability, resolveErrorMessage, selectChargeableBundles, startOfUtcDay };
|
|
6736
|
+
export { ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES, AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type AppliedSettingsPort, type AppliedSettingsRecord, type AppliedSettingsValues, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, BUNDLE_PRICE_LOOKUP_LIMIT, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOGUE_KEYS, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CancelSubscriptionInput, type CancelSubscriptionResult, type CancellationNoticePeriods, type CanonicalContractLineItemRow, type CanonicalContractRow, type CanonicalPlanRow, type CanonicalPlanVersionRow, type CanonicalRunningContractRow, type CanonicalSubscriberCorrectionRow, type CanonicalSubscriberPaymentMethodRow, type CanonicalSubscriberRow, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferSelection, type CheckoutOfferSelectionUpdate, type CheckoutOfferStatus, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ConfirmedPaymentMethod, type ContractErrorCode, type ContractIssuerParty, type ContractLineItemKind, type ContractLineItemRecord, type ContractSubscriberParty, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriberData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type EmailPort, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type IssuerCorrectionFault, type IssuerIdentityChange, type KpiCardDef, type KpiDisplayHint, LEGAL_IDENTITY_FIELDS, type LegalIdentity, type LegalIdentityField, MARKETING_PRIORITY_MAX, MARKETING_PRIORITY_MIN, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MaskedPaymentMethod, type MfaPort, type NewContractLineItemData, type NewSettingsChange, type NewSubscriberDetails, type NewSubscriptionContractData, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PAYMENT_ERROR_CODES, PAYMENT_METHOD_TYPES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PartyAddress, type PasswordHasher, type PasswordResetCliResult, PaymentCallbackRejectedError, type PaymentErrorCode, type PaymentEventClaim, type PaymentEventLog, type PaymentGateway, type PaymentGatewayCallback, type PaymentGatewayEvent, type PaymentMethodHolder, type PaymentMethodSetupSession, type PaymentMethodSetupSubject, type PaymentMethodType, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogIssuer, type PlanCatalogIssuerCorrection, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogNotifications, type PlanCatalogPaymentAccount, type PlanCatalogPayments, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanCatalogSettings, type PlanCatalogSubscribers, type PlanCatalogTenantBilling, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMappingFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishBundleVersionMeta, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RecommendablePlan, type RecordSubscriberPaymentMethodData, type RecordSubscriberPaymentMethodOutcome, type RecordSubscriberPaymentMethodResult, type RedeemPromoInTransactionCallback, type RegistrationActivation, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationBillingDetails, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResolvableErrorBody, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, type RunningContractIssuer, type RunningContractIssuers, SETTINGS_ERROR_CODES, SETUP_ERROR_CODES, SUBSCRIBER_ERROR_CODES, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePayments, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SelfServiceBlockedPlans, type SetCatalogEntryReviewData, type SettingsChangeFilter, type SettingsChangeRecord, type SettingsDifference, type SettingsErrorCode, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartPaymentMethodSetupInput, type StartRegistrationInput, type StartRegistrationResult, type StoredBundleStem, type StrictModeWarning, type StrictModeWarningCode, type SubscriberContact, type SubscriberContactChange, type SubscriberCorrectionData, type SubscriberCorrectionRecord, type SubscriberCorrectionResult, type SubscriberDetails, type SubscriberErrorCode, type SubscriberIdentityCorrection, type SubscriberIdentityDelta, type SubscriberIdentityValues, type SubscriberPaymentMethodRecord, type SubscriberPaymentMethodRepository, type SubscriberPaymentMethodSetupData, type SubscriberPaymentMethodSetupMatch, type SubscriberPaymentMethodStatus, type SubscriberRecord, type SubscriberRepository, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractParties, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, bundleDraftDefaults, bundleStemDefaults, canonicalJson, classifyBundleVersionDiff, classifyIssuerChange, classifyPlanDiff, collectUnsatisfiedRequires, contractPartiesOf, coverageExcludingSelf, definedFields, diffSettings, formatCustomerNumber, formatErrorMessage, identityCorrectionDelta, isBundleRedundant, isPaymentCallbackRejectedError, isPlatformUserExistsError, isVersionEditable, issuerIdentityOf, keepOneRecommended, missingRequiresFor, movedIdentityFields, pickActivePromo, planCatalogSettingsOf, previousUtcDay, promoStatus, readQuotaRecord, readQuotaValue, recordedIssuerIdentity, resolveBundleAvailability, resolveErrorMessage, sameLegalIdentity, selectChargeableBundles, settingsSubtreeOf, startOfUtcDay, subscriberFromRegistration, subscriberPaymentMethodColumns, toBundleStemRow, toContractLineItemRecord, toPlanRow, toPlanVersionRow, toRunningContractIssuer, toSubscriberCorrectionRecord, toSubscriberPaymentMethodRecord, toSubscriberRecord, toSubscriptionContractRecord };
|