@fonderie/billing 1.0.0 → 1.1.1
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/brain/outcomes.md +91 -0
- package/brain/signatures.md +268 -0
- package/dist/index.cjs +41 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +47 -5
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +3 -0
- package/package.json +11 -7
- package/dist/migrations/sql/001_billing.sql +0 -38
- package/dist/migrations/sql/002_billing_plan_fields.sql +0 -5
- package/dist/migrations/sql/003_drop_limits.sql +0 -1
- package/dist/migrations/sql/004_polymorphic_subscribers.sql +0 -49
- package/dist/migrations/sql/005_billing_notifications.sql +0 -13
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/billing — outcomes
|
|
4
|
+
|
|
5
|
+
What this package does to a running app: tables its migrations create,
|
|
6
|
+
rows it seeds, routes it registers. Generated from the migration SQL and
|
|
7
|
+
route tables in source — trust this file instead of reading `dist/` or
|
|
8
|
+
downloading tarballs.
|
|
9
|
+
|
|
10
|
+
## Database tables (after all migrations)
|
|
11
|
+
|
|
12
|
+
### `fonderie_billing_notifications`
|
|
13
|
+
|
|
14
|
+
```sql
|
|
15
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
16
|
+
subscriber_type TEXT NOT NULL
|
|
17
|
+
subscriber_id UUID NOT NULL
|
|
18
|
+
policy_key TEXT NOT NULL
|
|
19
|
+
notification TEXT NOT NULL
|
|
20
|
+
window_key TEXT NOT NULL
|
|
21
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
22
|
+
-- CONSTRAINT fonderie_billing_notifications_unique UNIQUE (subscriber_type, subscriber_id, policy_key, notification, window_key)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### `fonderie_plans`
|
|
26
|
+
|
|
27
|
+
```sql
|
|
28
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
29
|
+
name TEXT NOT NULL UNIQUE
|
|
30
|
+
seats INT
|
|
31
|
+
trial_days INT NOT NULL DEFAULT 0
|
|
32
|
+
monthly_amount INT
|
|
33
|
+
monthly_price_id TEXT
|
|
34
|
+
yearly_amount INT
|
|
35
|
+
yearly_price_id TEXT
|
|
36
|
+
active BOOLEAN NOT NULL DEFAULT true
|
|
37
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
38
|
+
description TEXT
|
|
39
|
+
tier INT NOT NULL DEFAULT 0
|
|
40
|
+
features JSONB NOT NULL DEFAULT '[]'
|
|
41
|
+
metadata JSONB NOT NULL DEFAULT '{}'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `fonderie_subscriptions`
|
|
45
|
+
|
|
46
|
+
```sql
|
|
47
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
48
|
+
plan TEXT NOT NULL
|
|
49
|
+
interval TEXT NOT NULL DEFAULT 'month'
|
|
50
|
+
status TEXT NOT NULL DEFAULT 'incomplete'
|
|
51
|
+
provider_customer_id TEXT
|
|
52
|
+
provider_subscription_id TEXT
|
|
53
|
+
current_period_start TIMESTAMPTZ
|
|
54
|
+
current_period_end TIMESTAMPTZ
|
|
55
|
+
cancel_at_period_end BOOLEAN NOT NULL DEFAULT false
|
|
56
|
+
trial_ends_at TIMESTAMPTZ
|
|
57
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
58
|
+
subscriber_type TEXT NOT NULL
|
|
59
|
+
subscriber_id UUID NOT NULL
|
|
60
|
+
CONSTRAINT fonderie_subscriptions_subscriber_unique UNIQUE (subscriber_type, subscriber_id)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `fonderie_usage_records`
|
|
64
|
+
|
|
65
|
+
```sql
|
|
66
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
67
|
+
metric TEXT NOT NULL
|
|
68
|
+
quantity INT NOT NULL DEFAULT 1
|
|
69
|
+
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
70
|
+
subscriber_type TEXT NOT NULL
|
|
71
|
+
subscriber_id UUID NOT NULL
|
|
72
|
+
CONSTRAINT fonderie_usage_records_subscriber_type_check CHECK (subscriber_type IN ('user', 'workspace'))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Raw SQL ships in `node_modules/@fonderie/billing/dist/migrations/sql/` — read it there if you must; never download tarballs.
|
|
76
|
+
|
|
77
|
+
## HTTP routes registered
|
|
78
|
+
|
|
79
|
+
| Method | Path | Middleware chain (auth / validation / handler) |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| POST | `/billing/checkout` | `requireAuth → validate(checkoutSchema) → checkout.createSession` |
|
|
82
|
+
| POST | `/billing/portal` | `requireAuth → checkout.createPortal` |
|
|
83
|
+
| GET | `/billing/subscription` | `requireAuth → subscription.get` |
|
|
84
|
+
| POST | `/billing/usage` | `requireAuth → validate(recordUsageSchema) → usage.record` |
|
|
85
|
+
| GET | `/billing/usage/:metric` | `requireAuth → usage.get` |
|
|
86
|
+
| POST | `/billing/webhook` | `webhook.handle` |
|
|
87
|
+
| GET | `/plans` | `plan.list` |
|
|
88
|
+
| POST | `/plans` | `validate(createPlanSchema) → plan.create` |
|
|
89
|
+
| DELETE | `/plans/:planId` | `plan.delete` |
|
|
90
|
+
| GET | `/plans/:planId` | `plan.get` |
|
|
91
|
+
| PUT | `/plans/:planId` | `validate(updatePlanSchema) → plan.update` |
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/billing — signatures
|
|
4
|
+
|
|
5
|
+
## @fonderie/billing
|
|
6
|
+
|
|
7
|
+
Subpath exports: `@fonderie/billing/types`, `@fonderie/billing/middleware`, `@fonderie/billing/migrations`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
new BillingModule(store: IStoreAdapter, config: IBillingConfig): BillingModule
|
|
11
|
+
.name: "@fonderie/billing"
|
|
12
|
+
.deps: string[]
|
|
13
|
+
.install(app: IFonderieApp): Promise<void>
|
|
14
|
+
|
|
15
|
+
new StripeProvider(secretKey: string, webhookSecret?: string | undefined): StripeProvider
|
|
16
|
+
.name: "stripe"
|
|
17
|
+
.createCustomer(opts: { email: string; subscriberType: SubscriberType; subscriberId: string; userId: string; }): Promise<{ customerId: string; }>
|
|
18
|
+
.createCheckoutSession(opts: { customerId: string; priceId: string; subscriberType: SubscriberType; subscriberId: string; trialDays?: number; successUrl: string; cancelUrl: string; }): Promise<{ url: string; }>
|
|
19
|
+
.createPortalSession(opts: { customerId: string; returnUrl: string; }): Promise<{ url: string; }>
|
|
20
|
+
.constructEvent(opts: { payload: string; signature: string; secret: string; }): Promise<IBillingEvent>
|
|
21
|
+
|
|
22
|
+
function requirePlan(plans: string | string[], store: IStoreAdapter): Middleware
|
|
23
|
+
|
|
24
|
+
function withBilling(store: IStoreAdapter, config: IBillingConfig, backend: ICounterBackend): Middleware
|
|
25
|
+
|
|
26
|
+
function hasFeature(ctx: IFonderieContext, key: string): boolean
|
|
27
|
+
|
|
28
|
+
function getPlanLimit(ctx: IFonderieContext, key: string): number | null
|
|
29
|
+
|
|
30
|
+
function getLimitStatus(ctx: IFonderieContext, key: string): IPolicyStatus | null
|
|
31
|
+
|
|
32
|
+
function requireFeature(key: string): Middleware
|
|
33
|
+
|
|
34
|
+
const MESSAGE_KEYS: { readonly limitWarning: "billing.limit-warning"; readonly limitReached: "billing.limit-reached"; readonly limitBlocked: "billing.limit-blocked"; }
|
|
35
|
+
|
|
36
|
+
interface IBillingConfig {
|
|
37
|
+
provider: IBillingProvider;
|
|
38
|
+
plans: IBillingPlan[];
|
|
39
|
+
successUrl: string;
|
|
40
|
+
cancelUrl: string;
|
|
41
|
+
webhookSecret?: string;
|
|
42
|
+
rateLimit?: {
|
|
43
|
+
backend?: RateLimitBackendConfig;
|
|
44
|
+
};
|
|
45
|
+
notifications?: IBillingNotificationsConfig;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface IBillingPlan {
|
|
49
|
+
name: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
tier?: number;
|
|
52
|
+
trialDays?: number;
|
|
53
|
+
monthly?: IBillingPlanPrice;
|
|
54
|
+
yearly?: IBillingPlanPrice;
|
|
55
|
+
defaults?: IBillingPlanDefaults;
|
|
56
|
+
policy?: Record<string, PolicyEntry>;
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface IBillingPlanDefaults {
|
|
61
|
+
warnAt?: number;
|
|
62
|
+
buffer?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type RateLimitBackendConfig = 'memory' | 'db' | ICounterBackend;
|
|
66
|
+
|
|
67
|
+
interface IBillingNotificationsConfig {
|
|
68
|
+
warnAt?: boolean;
|
|
69
|
+
softHit?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type BillingMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
|
|
73
|
+
|
|
74
|
+
new MemoryCounterBackend(): MemoryCounterBackend
|
|
75
|
+
.increment(key: string, windowMs: number | null, quantity?: number): Promise<number>
|
|
76
|
+
.get(key: string, windowMs: number | null): Promise<number>
|
|
77
|
+
|
|
78
|
+
new DBCounterBackend(store: IStoreAdapter): DBCounterBackend
|
|
79
|
+
.increment(key: string, windowMs: number | null, quantity?: number): Promise<number>
|
|
80
|
+
.get(key: string, windowMs: number | null): Promise<number>
|
|
81
|
+
|
|
82
|
+
interface ICounterBackend {
|
|
83
|
+
increment(key: string, windowMs: number | null, quantity?: number): Promise<number>;
|
|
84
|
+
get(key: string, windowMs: number | null): Promise<number>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface IBillingProvider {
|
|
88
|
+
name: string;
|
|
89
|
+
createCustomer(opts: {
|
|
90
|
+
email: string;
|
|
91
|
+
subscriberType: SubscriberType;
|
|
92
|
+
subscriberId: string;
|
|
93
|
+
userId: string;
|
|
94
|
+
}): Promise<{
|
|
95
|
+
customerId: string;
|
|
96
|
+
}>;
|
|
97
|
+
createCheckoutSession(opts: {
|
|
98
|
+
customerId: string;
|
|
99
|
+
priceId: string;
|
|
100
|
+
subscriberType: SubscriberType;
|
|
101
|
+
subscriberId: string;
|
|
102
|
+
trialDays?: number;
|
|
103
|
+
successUrl: string;
|
|
104
|
+
cancelUrl: string;
|
|
105
|
+
}): Promise<{
|
|
106
|
+
url: string;
|
|
107
|
+
}>;
|
|
108
|
+
createPortalSession(opts: {
|
|
109
|
+
customerId: string;
|
|
110
|
+
returnUrl: string;
|
|
111
|
+
}): Promise<{
|
|
112
|
+
url: string;
|
|
113
|
+
}>;
|
|
114
|
+
constructEvent(opts: {
|
|
115
|
+
payload: string;
|
|
116
|
+
signature: string;
|
|
117
|
+
secret: string;
|
|
118
|
+
}): Promise<IBillingEvent>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface IBillingEvent {
|
|
122
|
+
type: string;
|
|
123
|
+
subscription: INormalizedSubscription | null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface IPlan {
|
|
127
|
+
id: string;
|
|
128
|
+
name: string;
|
|
129
|
+
seats: number | null;
|
|
130
|
+
trialDays: number;
|
|
131
|
+
monthlyAmount: number | null;
|
|
132
|
+
monthlyPriceId: string | null;
|
|
133
|
+
yearlyAmount: number | null;
|
|
134
|
+
yearlyPriceId: string | null;
|
|
135
|
+
description: string | null;
|
|
136
|
+
tier: number;
|
|
137
|
+
features: IPlanFeature[];
|
|
138
|
+
metadata: Record<string, unknown>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface ISubscription {
|
|
142
|
+
id: string;
|
|
143
|
+
subscriberType: SubscriberType;
|
|
144
|
+
subscriberId: string;
|
|
145
|
+
plan: string;
|
|
146
|
+
interval: 'month' | 'year';
|
|
147
|
+
status: SubscriptionStatus;
|
|
148
|
+
providerCustomerId: string | null;
|
|
149
|
+
providerSubscriptionId: string | null;
|
|
150
|
+
currentPeriodStart: string | null;
|
|
151
|
+
currentPeriodEnd: string | null;
|
|
152
|
+
cancelAtPeriodEnd: boolean;
|
|
153
|
+
trialEndsAt: string | null;
|
|
154
|
+
createdAt: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface IUsageRecord {
|
|
158
|
+
id: string;
|
|
159
|
+
subscriberType: SubscriberType;
|
|
160
|
+
subscriberId: string;
|
|
161
|
+
metric: string;
|
|
162
|
+
quantity: number;
|
|
163
|
+
recordedAt: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
type SubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'incomplete' | 'paused';
|
|
167
|
+
|
|
168
|
+
type PolicyEntry = {
|
|
169
|
+
enabled: boolean;
|
|
170
|
+
} | {
|
|
171
|
+
limit: number | null;
|
|
172
|
+
buffer?: number;
|
|
173
|
+
warnAt?: number;
|
|
174
|
+
window?: string;
|
|
175
|
+
unit?: string;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
type LimitStatus = 'ok' | 'warning' | 'over_limit' | 'blocked';
|
|
179
|
+
|
|
180
|
+
type IPolicyStatus = {
|
|
181
|
+
type: 'feature';
|
|
182
|
+
enabled: boolean;
|
|
183
|
+
} | {
|
|
184
|
+
type: 'counter';
|
|
185
|
+
limit: number | null;
|
|
186
|
+
used: number;
|
|
187
|
+
status: LimitStatus;
|
|
188
|
+
resetsAt: string | null;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
interface IBillingContext {
|
|
192
|
+
subscriber: {
|
|
193
|
+
type: SubscriberType;
|
|
194
|
+
id: string;
|
|
195
|
+
};
|
|
196
|
+
plan: string;
|
|
197
|
+
active: boolean;
|
|
198
|
+
statuses: Record<string, IPolicyStatus>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface IPlanDTO {
|
|
202
|
+
id: string;
|
|
203
|
+
planId: string;
|
|
204
|
+
name: string;
|
|
205
|
+
description: string;
|
|
206
|
+
tier: number;
|
|
207
|
+
seats: number | null;
|
|
208
|
+
trialDays: number;
|
|
209
|
+
pricing: {
|
|
210
|
+
monthly: number;
|
|
211
|
+
yearly: number;
|
|
212
|
+
currency: string;
|
|
213
|
+
};
|
|
214
|
+
features: IPlanFeature[];
|
|
215
|
+
metadata: Record<string, unknown>;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
interface ISubscriptionDTO {
|
|
219
|
+
id: string;
|
|
220
|
+
subscriberType: SubscriberType;
|
|
221
|
+
subscriberId: string;
|
|
222
|
+
plan: string;
|
|
223
|
+
interval: string;
|
|
224
|
+
status: string;
|
|
225
|
+
cancelAtPeriodEnd: boolean;
|
|
226
|
+
currentPeriodStart: string | null;
|
|
227
|
+
currentPeriodEnd: string | null;
|
|
228
|
+
trialEndsAt: string | null;
|
|
229
|
+
createdAt: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface IUsageRecordDTO {
|
|
233
|
+
id: string;
|
|
234
|
+
subscriberType: SubscriberType;
|
|
235
|
+
subscriberId: string;
|
|
236
|
+
metric: string;
|
|
237
|
+
quantity: number;
|
|
238
|
+
recordedAt: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function toPlanDTO(plan: IPlan): IPlanDTO
|
|
242
|
+
|
|
243
|
+
function toSubscriptionDTO(sub: ISubscription): ISubscriptionDTO
|
|
244
|
+
|
|
245
|
+
function toUsageRecordDTO(record: IUsageRecord): IUsageRecordDTO
|
|
246
|
+
|
|
247
|
+
function recordUsage(opts: { subscriberType: SubscriberType; subscriberId: string; metric: string; quantity: number; }, store: IStoreAdapter): Promise<void>
|
|
248
|
+
|
|
249
|
+
function getUsage(subscriberType: SubscriberType, subscriberId: string, metric: string, since: Date, store: IStoreAdapter): Promise<number>
|
|
250
|
+
|
|
251
|
+
function getPlans(config: IBillingConfig): IBillingPlan[]
|
|
252
|
+
|
|
253
|
+
function getPlanByName(name: string, config: IBillingConfig): IBillingPlan | null
|
|
254
|
+
|
|
255
|
+
function getDBPlans(store: IStoreAdapter): Promise<IPlan[]>
|
|
256
|
+
|
|
257
|
+
function getPlanById(id: string, store: IStoreAdapter): Promise<IPlan | null>
|
|
258
|
+
|
|
259
|
+
function createPlan(data: { name: string; description?: string | null; tier?: number; seats?: number | null; trialDays?: number; features?: unknown; metadata?: unknown; monthlyAmount?: number | null; monthlyPriceId?: string | null; yearlyAmount?: number | null; yearlyPriceId?: string | null; }, store: IStoreAdapter): Promise<...>
|
|
260
|
+
|
|
261
|
+
function updatePlan(id: string, data: Partial<Omit<IPlan, "id">>, store: IStoreAdapter): Promise<IPlan | null>
|
|
262
|
+
|
|
263
|
+
function deletePlan(id: string, store: IStoreAdapter): Promise<boolean>
|
|
264
|
+
|
|
265
|
+
function getSubscription(subscriberType: SubscriberType, subscriberId: string, store: IStoreAdapter): Promise<ISubscription | null>
|
|
266
|
+
|
|
267
|
+
namespace schemas — exports: checkoutSchema, createPlanSchema, recordUsageSchema, updatePlanSchema
|
|
268
|
+
```
|
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
recordUsage: () => recordUsage,
|
|
40
40
|
requireFeature: () => requireFeature,
|
|
41
41
|
requirePlan: () => requirePlan,
|
|
42
|
+
schemas: () => schemas_exports,
|
|
42
43
|
toPlanDTO: () => toPlanDTO,
|
|
43
44
|
toSubscriptionDTO: () => toSubscriptionDTO,
|
|
44
45
|
toUsageRecordDTO: () => toUsageRecordDTO,
|
|
@@ -50,6 +51,41 @@ module.exports = __toCommonJS(index_exports);
|
|
|
50
51
|
// src/routes.ts
|
|
51
52
|
var import_middlewares = require("@fonderie/core/middlewares");
|
|
52
53
|
|
|
54
|
+
// src/schemas.ts
|
|
55
|
+
var schemas_exports = {};
|
|
56
|
+
__export(schemas_exports, {
|
|
57
|
+
checkoutSchema: () => checkoutSchema,
|
|
58
|
+
createPlanSchema: () => createPlanSchema,
|
|
59
|
+
recordUsageSchema: () => recordUsageSchema,
|
|
60
|
+
updatePlanSchema: () => updatePlanSchema
|
|
61
|
+
});
|
|
62
|
+
var import_zod = require("zod");
|
|
63
|
+
var planFields = {
|
|
64
|
+
description: import_zod.z.string().max(2e3).nullable().optional(),
|
|
65
|
+
tier: import_zod.z.number().int().min(0).optional(),
|
|
66
|
+
seats: import_zod.z.number().int().min(0).nullable().optional(),
|
|
67
|
+
trialDays: import_zod.z.number().int().min(0).optional(),
|
|
68
|
+
monthlyAmount: import_zod.z.number().min(0).nullable().optional(),
|
|
69
|
+
monthlyPriceId: import_zod.z.string().max(200).nullable().optional(),
|
|
70
|
+
yearlyAmount: import_zod.z.number().min(0).nullable().optional(),
|
|
71
|
+
yearlyPriceId: import_zod.z.string().max(200).nullable().optional(),
|
|
72
|
+
features: import_zod.z.unknown().optional(),
|
|
73
|
+
metadata: import_zod.z.unknown().optional()
|
|
74
|
+
};
|
|
75
|
+
var createPlanSchema = import_zod.z.object({
|
|
76
|
+
name: import_zod.z.string().trim().min(1, "name is required").max(200),
|
|
77
|
+
...planFields
|
|
78
|
+
});
|
|
79
|
+
var updatePlanSchema = import_zod.z.object({ name: import_zod.z.string().trim().min(1).max(200).optional(), ...planFields }).refine((o) => Object.values(o).some((v) => v !== void 0), "Provide at least one field");
|
|
80
|
+
var checkoutSchema = import_zod.z.object({
|
|
81
|
+
plan: import_zod.z.string().min(1, "plan is required"),
|
|
82
|
+
interval: import_zod.z.enum(["month", "year"]).optional()
|
|
83
|
+
});
|
|
84
|
+
var recordUsageSchema = import_zod.z.object({
|
|
85
|
+
metric: import_zod.z.string().min(1, "metric is required").max(100),
|
|
86
|
+
quantity: import_zod.z.number().min(0).optional()
|
|
87
|
+
});
|
|
88
|
+
|
|
53
89
|
// src/controllers/plan.controller.ts
|
|
54
90
|
var import_core = require("@fonderie/core");
|
|
55
91
|
|
|
@@ -744,15 +780,15 @@ function buildBillingRoutes(store, config) {
|
|
|
744
780
|
["GET", "/plans", plan.list],
|
|
745
781
|
["GET", "/plans/:planId", plan.get],
|
|
746
782
|
// Plans — admin write (caller is responsible for authorization)
|
|
747
|
-
["POST", "/plans", plan.create],
|
|
748
|
-
["PUT", "/plans/:planId", plan.update],
|
|
783
|
+
["POST", "/plans", (0, import_middlewares.validate)(createPlanSchema), plan.create],
|
|
784
|
+
["PUT", "/plans/:planId", (0, import_middlewares.validate)(updatePlanSchema), plan.update],
|
|
749
785
|
["DELETE", "/plans/:planId", plan.delete],
|
|
750
786
|
// Billing — subscriber resolved from X-Workspace-ID header (workspace) or session (user)
|
|
751
787
|
// Workspace membership is verified automatically by the withBilling global middleware
|
|
752
788
|
["GET", "/billing/subscription", import_middlewares.requireAuth, subscription.get],
|
|
753
|
-
["POST", "/billing/checkout", import_middlewares.requireAuth, checkout.createSession],
|
|
789
|
+
["POST", "/billing/checkout", import_middlewares.requireAuth, (0, import_middlewares.validate)(checkoutSchema), checkout.createSession],
|
|
754
790
|
["POST", "/billing/portal", import_middlewares.requireAuth, checkout.createPortal],
|
|
755
|
-
["POST", "/billing/usage", import_middlewares.requireAuth, usage.record],
|
|
791
|
+
["POST", "/billing/usage", import_middlewares.requireAuth, (0, import_middlewares.validate)(recordUsageSchema), usage.record],
|
|
756
792
|
["GET", "/billing/usage/:metric", import_middlewares.requireAuth, usage.get],
|
|
757
793
|
// Webhook — signature verified inside the handler
|
|
758
794
|
["POST", "/billing/webhook", webhook.handle]
|
|
@@ -1161,6 +1197,7 @@ function requireFeature(key) {
|
|
|
1161
1197
|
recordUsage,
|
|
1162
1198
|
requireFeature,
|
|
1163
1199
|
requirePlan,
|
|
1200
|
+
schemas,
|
|
1164
1201
|
toPlanDTO,
|
|
1165
1202
|
toSubscriptionDTO,
|
|
1166
1203
|
toUsageRecordDTO,
|