@auth-gate/billing 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-AHCLNQ6P.mjs +482 -0
- package/dist/chunk-I4E63NIC.mjs +24 -0
- package/dist/cli.cjs +1365 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +821 -0
- package/dist/index.cjs +557 -0
- package/dist/index.d.cts +302 -0
- package/dist/index.d.ts +302 -0
- package/dist/index.mjs +49 -0
- package/dist/pull-from-stripe-GX2Y5XMC.mjs +49 -0
- package/package.json +44 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/** A tier definition for tiered/graduated pricing. */
|
|
2
|
+
interface PriceTier {
|
|
3
|
+
/** Upper bound of this tier (units). Use Infinity or omit for the last tier. */
|
|
4
|
+
upTo?: number | "inf";
|
|
5
|
+
/** Per-unit amount in cents for this tier. */
|
|
6
|
+
unitAmount: number;
|
|
7
|
+
/** Flat fee in cents for this tier (optional). */
|
|
8
|
+
flatAmount?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Base price fields shared by all pricing types. */
|
|
11
|
+
interface PriceBase {
|
|
12
|
+
/** ISO 4217 currency code (e.g., "usd") */
|
|
13
|
+
currency: string;
|
|
14
|
+
/** Billing interval */
|
|
15
|
+
interval: "monthly" | "yearly";
|
|
16
|
+
/** Billing interval count (default: 1). E.g., 2 + "monthly" = every 2 months */
|
|
17
|
+
intervalCount?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Standard recurring price (flat-rate). Type is optional for backward compat. */
|
|
20
|
+
interface RecurringPriceConfig extends PriceBase {
|
|
21
|
+
type?: "recurring";
|
|
22
|
+
/** Price amount in cents (e.g., 1999 = $19.99) */
|
|
23
|
+
amount: number;
|
|
24
|
+
}
|
|
25
|
+
/** Per-seat pricing — amount per seat per interval. */
|
|
26
|
+
interface PerSeatPriceConfig extends PriceBase {
|
|
27
|
+
type: "per_seat";
|
|
28
|
+
/** Price amount per seat in cents */
|
|
29
|
+
amount: number;
|
|
30
|
+
/** Usage metric that tracks seat count */
|
|
31
|
+
metric: string;
|
|
32
|
+
}
|
|
33
|
+
/** Usage-based metered pricing — charged based on consumption. */
|
|
34
|
+
interface MeteredPriceConfig extends PriceBase {
|
|
35
|
+
type: "metered";
|
|
36
|
+
/** Usage metric name */
|
|
37
|
+
metric: string;
|
|
38
|
+
/** Tiers for graduated or volume pricing */
|
|
39
|
+
tiers: PriceTier[];
|
|
40
|
+
/** How tiers are applied */
|
|
41
|
+
tierMode: "graduated" | "volume";
|
|
42
|
+
}
|
|
43
|
+
/** Tiered pricing without a specific metric — uses flat tiers. */
|
|
44
|
+
interface TieredPriceConfig extends PriceBase {
|
|
45
|
+
type: "tiered";
|
|
46
|
+
/** Tiers for graduated or volume pricing */
|
|
47
|
+
tiers: PriceTier[];
|
|
48
|
+
/** How tiers are applied */
|
|
49
|
+
tierMode: "graduated" | "volume";
|
|
50
|
+
}
|
|
51
|
+
/** Union of all price config types. */
|
|
52
|
+
type PriceConfig = RecurringPriceConfig | PerSeatPriceConfig | MeteredPriceConfig | TieredPriceConfig;
|
|
53
|
+
/** Grandfathering strategy when a plan's price changes. */
|
|
54
|
+
type GrandfatheringStrategy = "keep_price" | "migrate_at_renewal" | "migrate_immediately";
|
|
55
|
+
/** A plan (product) in the billing config. */
|
|
56
|
+
interface PlanConfig {
|
|
57
|
+
/** Display name for the plan */
|
|
58
|
+
name: string;
|
|
59
|
+
/** Optional description */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Feature flags this plan grants (e.g., ["analytics", "api_access"]) */
|
|
62
|
+
features?: string[];
|
|
63
|
+
/** Typed entitlements: { featureKey: true | { limit: number } } */
|
|
64
|
+
entitlements?: Record<string, EntitlementValue>;
|
|
65
|
+
/** Prices for this plan */
|
|
66
|
+
prices: PriceConfig[];
|
|
67
|
+
/** Previous config key if this plan was renamed. */
|
|
68
|
+
renamedFrom?: string;
|
|
69
|
+
/** How to handle existing subscribers when prices change. Default: "keep_price". */
|
|
70
|
+
grandfathering?: GrandfatheringStrategy;
|
|
71
|
+
}
|
|
72
|
+
/** Feature definition in the features registry. */
|
|
73
|
+
interface FeatureConfig {
|
|
74
|
+
/** Feature type: boolean (on/off) or metered (usage-based with limits) */
|
|
75
|
+
type: "boolean" | "metered";
|
|
76
|
+
}
|
|
77
|
+
/** Entitlement value: true for boolean features, or { limit: number } for metered features. */
|
|
78
|
+
type EntitlementValue = true | {
|
|
79
|
+
limit: number;
|
|
80
|
+
};
|
|
81
|
+
/** A migration directive: move subscribers from one plan to another. */
|
|
82
|
+
interface MigrationConfig {
|
|
83
|
+
/** Source plan config key (must exist in DB) */
|
|
84
|
+
from: string;
|
|
85
|
+
/** Target plan config key (must exist in config.plans) */
|
|
86
|
+
to: string;
|
|
87
|
+
/** Explicit price mapping: { oldPriceConfigKey: newPriceConfigKey }. If omitted, matches by (interval, currency). */
|
|
88
|
+
priceMapping?: Record<string, string>;
|
|
89
|
+
}
|
|
90
|
+
/** Root billing config object. */
|
|
91
|
+
interface BillingConfig {
|
|
92
|
+
/** Typed features registry. When defined, entitlements are validated against it. */
|
|
93
|
+
features?: Record<string, FeatureConfig>;
|
|
94
|
+
plans: Record<string, PlanConfig>;
|
|
95
|
+
/** Migration directives: move subscribers between plans during sync. */
|
|
96
|
+
migrations?: MigrationConfig[];
|
|
97
|
+
}
|
|
98
|
+
/** Return type of defineBilling() — same as input but branded for type narrowing. */
|
|
99
|
+
type BillingDefinition = BillingConfig;
|
|
100
|
+
/** Extract literal plan keys from a billing config type. */
|
|
101
|
+
type InferPlanKeys<T> = T extends {
|
|
102
|
+
plans: infer P;
|
|
103
|
+
} ? keyof P & string : never;
|
|
104
|
+
/** Extract literal feature keys from a billing config type. */
|
|
105
|
+
type InferFeatureKeys<T> = T extends {
|
|
106
|
+
features: infer F;
|
|
107
|
+
} ? keyof F & string : never;
|
|
108
|
+
/** Extract only the metered feature keys from a billing config type. */
|
|
109
|
+
type InferMeteredFeatures<T> = T extends {
|
|
110
|
+
features: infer F;
|
|
111
|
+
} ? {
|
|
112
|
+
[K in keyof F & string]: F[K] extends {
|
|
113
|
+
type: "metered";
|
|
114
|
+
} ? K : never;
|
|
115
|
+
}[keyof F & string] : never;
|
|
116
|
+
/** Structured plan object: `billing.plans.pro.key`, `.name`, `.entitlements` */
|
|
117
|
+
type StructuredPlan<T, K extends string> = {
|
|
118
|
+
readonly key: K;
|
|
119
|
+
readonly name: T extends {
|
|
120
|
+
plans: infer P;
|
|
121
|
+
} ? K extends keyof P ? P[K] extends {
|
|
122
|
+
name: infer N;
|
|
123
|
+
} ? N : string : string : string;
|
|
124
|
+
readonly entitlements: T extends {
|
|
125
|
+
plans: infer P;
|
|
126
|
+
} ? K extends keyof P ? P[K] extends {
|
|
127
|
+
entitlements: infer E;
|
|
128
|
+
} ? Readonly<E> : {} : {} : {};
|
|
129
|
+
};
|
|
130
|
+
/** Map of plan keys to their structured plan objects. */
|
|
131
|
+
type StructuredPlans<T> = {
|
|
132
|
+
readonly [K in InferPlanKeys<T>]: StructuredPlan<T, K>;
|
|
133
|
+
};
|
|
134
|
+
/** Structured feature object: `billing.features.api_calls.key`, `.type` */
|
|
135
|
+
type StructuredFeature<T, K extends string> = {
|
|
136
|
+
readonly key: K;
|
|
137
|
+
readonly type: T extends {
|
|
138
|
+
features: infer F;
|
|
139
|
+
} ? K extends keyof F ? F[K] extends {
|
|
140
|
+
type: infer FT;
|
|
141
|
+
} ? FT : string : string : string;
|
|
142
|
+
};
|
|
143
|
+
/** Map of feature keys to their structured feature objects. */
|
|
144
|
+
type StructuredFeatures<T> = T extends {
|
|
145
|
+
features: infer F;
|
|
146
|
+
} ? {
|
|
147
|
+
readonly [K in keyof F & string]: StructuredFeature<T, K>;
|
|
148
|
+
} : {};
|
|
149
|
+
/**
|
|
150
|
+
* Typed billing definition returned by `defineBilling()`.
|
|
151
|
+
*
|
|
152
|
+
* Carries phantom types that downstream factories use to constrain
|
|
153
|
+
* plan/feature string parameters — enabling full IDE autocomplete.
|
|
154
|
+
*
|
|
155
|
+
* Plans and features are structured objects with `.key`, `.name`, `.entitlements` (plans)
|
|
156
|
+
* and `.key`, `.type` (features) for dot-notation access.
|
|
157
|
+
*/
|
|
158
|
+
interface TypedBilling<T extends BillingConfig> {
|
|
159
|
+
/** The raw billing config. Access plan objects, prices, etc. via `billing._config.plans.pro`. */
|
|
160
|
+
readonly _config: T;
|
|
161
|
+
/**
|
|
162
|
+
* Phantom type carrier — never read at runtime.
|
|
163
|
+
* Use `B["_types"]["planKey"]` to extract the plan key union from a `TypedBilling`.
|
|
164
|
+
*/
|
|
165
|
+
readonly _types: {
|
|
166
|
+
readonly planKey: InferPlanKeys<T>;
|
|
167
|
+
readonly featureKey: InferFeatureKeys<T>;
|
|
168
|
+
readonly meteredFeature: InferMeteredFeatures<T>;
|
|
169
|
+
};
|
|
170
|
+
/** Structured plan objects: `billing.plans.pro.key` is `"pro"`, `.name` is `"Pro"`, etc. */
|
|
171
|
+
readonly plans: StructuredPlans<T>;
|
|
172
|
+
/** Structured feature objects: `billing.features.api_calls.key` is `"api_calls"`, `.type` is `"metered"`, etc. */
|
|
173
|
+
readonly features: StructuredFeatures<T>;
|
|
174
|
+
}
|
|
175
|
+
/** Extract the plan key union from a `TypedBilling` instance type. Falls back to `string`. */
|
|
176
|
+
type PlanKey<B> = B extends TypedBilling<infer T> ? InferPlanKeys<T> : string;
|
|
177
|
+
/** Extract the feature key union from a `TypedBilling` instance type. Falls back to `string`. */
|
|
178
|
+
type FeatureKey<B> = B extends TypedBilling<infer T> ? InferFeatureKeys<T> : string;
|
|
179
|
+
|
|
180
|
+
declare function validateConfig(config: unknown): BillingConfig;
|
|
181
|
+
/**
|
|
182
|
+
* Normalize config by converting legacy `features: string[]` to typed `entitlements`.
|
|
183
|
+
* Does not mutate the input — returns a new config.
|
|
184
|
+
*/
|
|
185
|
+
declare function normalizeConfig(config: BillingConfig): BillingConfig;
|
|
186
|
+
|
|
187
|
+
interface ServerProduct {
|
|
188
|
+
id: string;
|
|
189
|
+
configKey: string | null;
|
|
190
|
+
name: string;
|
|
191
|
+
description: string | null;
|
|
192
|
+
features: string[];
|
|
193
|
+
entitlements?: Record<string, EntitlementValue> | null;
|
|
194
|
+
version?: number;
|
|
195
|
+
grandfathering?: string;
|
|
196
|
+
isActive: boolean;
|
|
197
|
+
managedBy: string;
|
|
198
|
+
stripeProductId: string | null;
|
|
199
|
+
previousConfigKeys: string[];
|
|
200
|
+
prices: ServerPrice[];
|
|
201
|
+
}
|
|
202
|
+
interface ServerPrice {
|
|
203
|
+
id: string;
|
|
204
|
+
configKey: string | null;
|
|
205
|
+
amount: number | null;
|
|
206
|
+
currency: string;
|
|
207
|
+
interval: string;
|
|
208
|
+
intervalCount: number;
|
|
209
|
+
isActive: boolean;
|
|
210
|
+
stripePriceId: string | null;
|
|
211
|
+
}
|
|
212
|
+
interface ServerState {
|
|
213
|
+
products: ServerProduct[];
|
|
214
|
+
}
|
|
215
|
+
type PlanOp = {
|
|
216
|
+
type: "create";
|
|
217
|
+
key: string;
|
|
218
|
+
plan: BillingConfig["plans"][string];
|
|
219
|
+
} | {
|
|
220
|
+
type: "update";
|
|
221
|
+
key: string;
|
|
222
|
+
plan: BillingConfig["plans"][string];
|
|
223
|
+
existing: ServerProduct;
|
|
224
|
+
changes: string[];
|
|
225
|
+
versionBump?: boolean;
|
|
226
|
+
grandfathering?: GrandfatheringStrategy;
|
|
227
|
+
} | {
|
|
228
|
+
type: "archive";
|
|
229
|
+
key: string;
|
|
230
|
+
existing: ServerProduct;
|
|
231
|
+
activeSubscribers: number;
|
|
232
|
+
} | {
|
|
233
|
+
type: "rename";
|
|
234
|
+
key: string;
|
|
235
|
+
oldKey: string;
|
|
236
|
+
plan: BillingConfig["plans"][string];
|
|
237
|
+
existing: ServerProduct;
|
|
238
|
+
activeSubscribers: number;
|
|
239
|
+
};
|
|
240
|
+
type PriceOp = {
|
|
241
|
+
type: "create";
|
|
242
|
+
planKey: string;
|
|
243
|
+
price: PriceConfig;
|
|
244
|
+
configKey: string;
|
|
245
|
+
} | {
|
|
246
|
+
type: "archive";
|
|
247
|
+
planKey: string;
|
|
248
|
+
existing: ServerPrice;
|
|
249
|
+
configKey: string;
|
|
250
|
+
};
|
|
251
|
+
interface DiffResult {
|
|
252
|
+
planOps: PlanOp[];
|
|
253
|
+
priceOps: PriceOp[];
|
|
254
|
+
hasDestructive: boolean;
|
|
255
|
+
}
|
|
256
|
+
declare function priceConfigKey(planKey: string, price: PriceConfig): string;
|
|
257
|
+
declare function computeDiff(config: BillingConfig, server: ServerState, subscriberCounts: Record<string, number>): DiffResult;
|
|
258
|
+
|
|
259
|
+
interface PullOptions {
|
|
260
|
+
includeDashboard?: boolean;
|
|
261
|
+
}
|
|
262
|
+
interface PullResult {
|
|
263
|
+
config: BillingConfig;
|
|
264
|
+
dashboardPlans: Array<{
|
|
265
|
+
name: string;
|
|
266
|
+
suggestedKey: string;
|
|
267
|
+
}>;
|
|
268
|
+
}
|
|
269
|
+
declare function slugify(name: string): string;
|
|
270
|
+
declare function serverStateToBillingConfig(state: ServerState, options?: PullOptions): PullResult;
|
|
271
|
+
declare function renderConfigAsTypeScript(result: PullResult): string;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Define your billing plans. Use this in your `authgate.billing.ts` config file.
|
|
275
|
+
*
|
|
276
|
+
* Returns a `TypedBilling<T>` object that carries plan and feature keys
|
|
277
|
+
* as literal types — enabling full IDE autocomplete across hooks, helpers,
|
|
278
|
+
* and test utilities.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* export const billing = defineBilling({
|
|
283
|
+
* features: {
|
|
284
|
+
* api_calls: { type: "metered" },
|
|
285
|
+
* analytics: { type: "boolean" },
|
|
286
|
+
* },
|
|
287
|
+
* plans: {
|
|
288
|
+
* free: { name: "Free", prices: [...] },
|
|
289
|
+
* pro: { name: "Pro", prices: [...] },
|
|
290
|
+
* },
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* billing.plans.pro.key // "pro" — autocomplete + literal type
|
|
294
|
+
* billing.plans.pro.name // "Pro"
|
|
295
|
+
* billing.plans.pro.entitlements // { api_calls: { limit: 10000 }, analytics: true }
|
|
296
|
+
* billing.features.api_calls.key // "api_calls"
|
|
297
|
+
* billing.features.api_calls.type // "metered"
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
declare function defineBilling<const T extends BillingConfig>(config: T): TypedBilling<T>;
|
|
301
|
+
|
|
302
|
+
export { type BillingConfig, type BillingDefinition, type DiffResult, type EntitlementValue, type FeatureConfig, type FeatureKey, type GrandfatheringStrategy, type InferFeatureKeys, type InferMeteredFeatures, type InferPlanKeys, type MeteredPriceConfig, type PerSeatPriceConfig, type PlanConfig, type PlanKey, type PlanOp, type PriceConfig, type PriceOp, type PriceTier, type PullOptions, type PullResult, type RecurringPriceConfig, type ServerPrice, type ServerProduct, type ServerState, type TieredPriceConfig, type TypedBilling, computeDiff, defineBilling, normalizeConfig, priceConfigKey, renderConfigAsTypeScript, serverStateToBillingConfig, slugify, validateConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/** A tier definition for tiered/graduated pricing. */
|
|
2
|
+
interface PriceTier {
|
|
3
|
+
/** Upper bound of this tier (units). Use Infinity or omit for the last tier. */
|
|
4
|
+
upTo?: number | "inf";
|
|
5
|
+
/** Per-unit amount in cents for this tier. */
|
|
6
|
+
unitAmount: number;
|
|
7
|
+
/** Flat fee in cents for this tier (optional). */
|
|
8
|
+
flatAmount?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Base price fields shared by all pricing types. */
|
|
11
|
+
interface PriceBase {
|
|
12
|
+
/** ISO 4217 currency code (e.g., "usd") */
|
|
13
|
+
currency: string;
|
|
14
|
+
/** Billing interval */
|
|
15
|
+
interval: "monthly" | "yearly";
|
|
16
|
+
/** Billing interval count (default: 1). E.g., 2 + "monthly" = every 2 months */
|
|
17
|
+
intervalCount?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Standard recurring price (flat-rate). Type is optional for backward compat. */
|
|
20
|
+
interface RecurringPriceConfig extends PriceBase {
|
|
21
|
+
type?: "recurring";
|
|
22
|
+
/** Price amount in cents (e.g., 1999 = $19.99) */
|
|
23
|
+
amount: number;
|
|
24
|
+
}
|
|
25
|
+
/** Per-seat pricing — amount per seat per interval. */
|
|
26
|
+
interface PerSeatPriceConfig extends PriceBase {
|
|
27
|
+
type: "per_seat";
|
|
28
|
+
/** Price amount per seat in cents */
|
|
29
|
+
amount: number;
|
|
30
|
+
/** Usage metric that tracks seat count */
|
|
31
|
+
metric: string;
|
|
32
|
+
}
|
|
33
|
+
/** Usage-based metered pricing — charged based on consumption. */
|
|
34
|
+
interface MeteredPriceConfig extends PriceBase {
|
|
35
|
+
type: "metered";
|
|
36
|
+
/** Usage metric name */
|
|
37
|
+
metric: string;
|
|
38
|
+
/** Tiers for graduated or volume pricing */
|
|
39
|
+
tiers: PriceTier[];
|
|
40
|
+
/** How tiers are applied */
|
|
41
|
+
tierMode: "graduated" | "volume";
|
|
42
|
+
}
|
|
43
|
+
/** Tiered pricing without a specific metric — uses flat tiers. */
|
|
44
|
+
interface TieredPriceConfig extends PriceBase {
|
|
45
|
+
type: "tiered";
|
|
46
|
+
/** Tiers for graduated or volume pricing */
|
|
47
|
+
tiers: PriceTier[];
|
|
48
|
+
/** How tiers are applied */
|
|
49
|
+
tierMode: "graduated" | "volume";
|
|
50
|
+
}
|
|
51
|
+
/** Union of all price config types. */
|
|
52
|
+
type PriceConfig = RecurringPriceConfig | PerSeatPriceConfig | MeteredPriceConfig | TieredPriceConfig;
|
|
53
|
+
/** Grandfathering strategy when a plan's price changes. */
|
|
54
|
+
type GrandfatheringStrategy = "keep_price" | "migrate_at_renewal" | "migrate_immediately";
|
|
55
|
+
/** A plan (product) in the billing config. */
|
|
56
|
+
interface PlanConfig {
|
|
57
|
+
/** Display name for the plan */
|
|
58
|
+
name: string;
|
|
59
|
+
/** Optional description */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Feature flags this plan grants (e.g., ["analytics", "api_access"]) */
|
|
62
|
+
features?: string[];
|
|
63
|
+
/** Typed entitlements: { featureKey: true | { limit: number } } */
|
|
64
|
+
entitlements?: Record<string, EntitlementValue>;
|
|
65
|
+
/** Prices for this plan */
|
|
66
|
+
prices: PriceConfig[];
|
|
67
|
+
/** Previous config key if this plan was renamed. */
|
|
68
|
+
renamedFrom?: string;
|
|
69
|
+
/** How to handle existing subscribers when prices change. Default: "keep_price". */
|
|
70
|
+
grandfathering?: GrandfatheringStrategy;
|
|
71
|
+
}
|
|
72
|
+
/** Feature definition in the features registry. */
|
|
73
|
+
interface FeatureConfig {
|
|
74
|
+
/** Feature type: boolean (on/off) or metered (usage-based with limits) */
|
|
75
|
+
type: "boolean" | "metered";
|
|
76
|
+
}
|
|
77
|
+
/** Entitlement value: true for boolean features, or { limit: number } for metered features. */
|
|
78
|
+
type EntitlementValue = true | {
|
|
79
|
+
limit: number;
|
|
80
|
+
};
|
|
81
|
+
/** A migration directive: move subscribers from one plan to another. */
|
|
82
|
+
interface MigrationConfig {
|
|
83
|
+
/** Source plan config key (must exist in DB) */
|
|
84
|
+
from: string;
|
|
85
|
+
/** Target plan config key (must exist in config.plans) */
|
|
86
|
+
to: string;
|
|
87
|
+
/** Explicit price mapping: { oldPriceConfigKey: newPriceConfigKey }. If omitted, matches by (interval, currency). */
|
|
88
|
+
priceMapping?: Record<string, string>;
|
|
89
|
+
}
|
|
90
|
+
/** Root billing config object. */
|
|
91
|
+
interface BillingConfig {
|
|
92
|
+
/** Typed features registry. When defined, entitlements are validated against it. */
|
|
93
|
+
features?: Record<string, FeatureConfig>;
|
|
94
|
+
plans: Record<string, PlanConfig>;
|
|
95
|
+
/** Migration directives: move subscribers between plans during sync. */
|
|
96
|
+
migrations?: MigrationConfig[];
|
|
97
|
+
}
|
|
98
|
+
/** Return type of defineBilling() — same as input but branded for type narrowing. */
|
|
99
|
+
type BillingDefinition = BillingConfig;
|
|
100
|
+
/** Extract literal plan keys from a billing config type. */
|
|
101
|
+
type InferPlanKeys<T> = T extends {
|
|
102
|
+
plans: infer P;
|
|
103
|
+
} ? keyof P & string : never;
|
|
104
|
+
/** Extract literal feature keys from a billing config type. */
|
|
105
|
+
type InferFeatureKeys<T> = T extends {
|
|
106
|
+
features: infer F;
|
|
107
|
+
} ? keyof F & string : never;
|
|
108
|
+
/** Extract only the metered feature keys from a billing config type. */
|
|
109
|
+
type InferMeteredFeatures<T> = T extends {
|
|
110
|
+
features: infer F;
|
|
111
|
+
} ? {
|
|
112
|
+
[K in keyof F & string]: F[K] extends {
|
|
113
|
+
type: "metered";
|
|
114
|
+
} ? K : never;
|
|
115
|
+
}[keyof F & string] : never;
|
|
116
|
+
/** Structured plan object: `billing.plans.pro.key`, `.name`, `.entitlements` */
|
|
117
|
+
type StructuredPlan<T, K extends string> = {
|
|
118
|
+
readonly key: K;
|
|
119
|
+
readonly name: T extends {
|
|
120
|
+
plans: infer P;
|
|
121
|
+
} ? K extends keyof P ? P[K] extends {
|
|
122
|
+
name: infer N;
|
|
123
|
+
} ? N : string : string : string;
|
|
124
|
+
readonly entitlements: T extends {
|
|
125
|
+
plans: infer P;
|
|
126
|
+
} ? K extends keyof P ? P[K] extends {
|
|
127
|
+
entitlements: infer E;
|
|
128
|
+
} ? Readonly<E> : {} : {} : {};
|
|
129
|
+
};
|
|
130
|
+
/** Map of plan keys to their structured plan objects. */
|
|
131
|
+
type StructuredPlans<T> = {
|
|
132
|
+
readonly [K in InferPlanKeys<T>]: StructuredPlan<T, K>;
|
|
133
|
+
};
|
|
134
|
+
/** Structured feature object: `billing.features.api_calls.key`, `.type` */
|
|
135
|
+
type StructuredFeature<T, K extends string> = {
|
|
136
|
+
readonly key: K;
|
|
137
|
+
readonly type: T extends {
|
|
138
|
+
features: infer F;
|
|
139
|
+
} ? K extends keyof F ? F[K] extends {
|
|
140
|
+
type: infer FT;
|
|
141
|
+
} ? FT : string : string : string;
|
|
142
|
+
};
|
|
143
|
+
/** Map of feature keys to their structured feature objects. */
|
|
144
|
+
type StructuredFeatures<T> = T extends {
|
|
145
|
+
features: infer F;
|
|
146
|
+
} ? {
|
|
147
|
+
readonly [K in keyof F & string]: StructuredFeature<T, K>;
|
|
148
|
+
} : {};
|
|
149
|
+
/**
|
|
150
|
+
* Typed billing definition returned by `defineBilling()`.
|
|
151
|
+
*
|
|
152
|
+
* Carries phantom types that downstream factories use to constrain
|
|
153
|
+
* plan/feature string parameters — enabling full IDE autocomplete.
|
|
154
|
+
*
|
|
155
|
+
* Plans and features are structured objects with `.key`, `.name`, `.entitlements` (plans)
|
|
156
|
+
* and `.key`, `.type` (features) for dot-notation access.
|
|
157
|
+
*/
|
|
158
|
+
interface TypedBilling<T extends BillingConfig> {
|
|
159
|
+
/** The raw billing config. Access plan objects, prices, etc. via `billing._config.plans.pro`. */
|
|
160
|
+
readonly _config: T;
|
|
161
|
+
/**
|
|
162
|
+
* Phantom type carrier — never read at runtime.
|
|
163
|
+
* Use `B["_types"]["planKey"]` to extract the plan key union from a `TypedBilling`.
|
|
164
|
+
*/
|
|
165
|
+
readonly _types: {
|
|
166
|
+
readonly planKey: InferPlanKeys<T>;
|
|
167
|
+
readonly featureKey: InferFeatureKeys<T>;
|
|
168
|
+
readonly meteredFeature: InferMeteredFeatures<T>;
|
|
169
|
+
};
|
|
170
|
+
/** Structured plan objects: `billing.plans.pro.key` is `"pro"`, `.name` is `"Pro"`, etc. */
|
|
171
|
+
readonly plans: StructuredPlans<T>;
|
|
172
|
+
/** Structured feature objects: `billing.features.api_calls.key` is `"api_calls"`, `.type` is `"metered"`, etc. */
|
|
173
|
+
readonly features: StructuredFeatures<T>;
|
|
174
|
+
}
|
|
175
|
+
/** Extract the plan key union from a `TypedBilling` instance type. Falls back to `string`. */
|
|
176
|
+
type PlanKey<B> = B extends TypedBilling<infer T> ? InferPlanKeys<T> : string;
|
|
177
|
+
/** Extract the feature key union from a `TypedBilling` instance type. Falls back to `string`. */
|
|
178
|
+
type FeatureKey<B> = B extends TypedBilling<infer T> ? InferFeatureKeys<T> : string;
|
|
179
|
+
|
|
180
|
+
declare function validateConfig(config: unknown): BillingConfig;
|
|
181
|
+
/**
|
|
182
|
+
* Normalize config by converting legacy `features: string[]` to typed `entitlements`.
|
|
183
|
+
* Does not mutate the input — returns a new config.
|
|
184
|
+
*/
|
|
185
|
+
declare function normalizeConfig(config: BillingConfig): BillingConfig;
|
|
186
|
+
|
|
187
|
+
interface ServerProduct {
|
|
188
|
+
id: string;
|
|
189
|
+
configKey: string | null;
|
|
190
|
+
name: string;
|
|
191
|
+
description: string | null;
|
|
192
|
+
features: string[];
|
|
193
|
+
entitlements?: Record<string, EntitlementValue> | null;
|
|
194
|
+
version?: number;
|
|
195
|
+
grandfathering?: string;
|
|
196
|
+
isActive: boolean;
|
|
197
|
+
managedBy: string;
|
|
198
|
+
stripeProductId: string | null;
|
|
199
|
+
previousConfigKeys: string[];
|
|
200
|
+
prices: ServerPrice[];
|
|
201
|
+
}
|
|
202
|
+
interface ServerPrice {
|
|
203
|
+
id: string;
|
|
204
|
+
configKey: string | null;
|
|
205
|
+
amount: number | null;
|
|
206
|
+
currency: string;
|
|
207
|
+
interval: string;
|
|
208
|
+
intervalCount: number;
|
|
209
|
+
isActive: boolean;
|
|
210
|
+
stripePriceId: string | null;
|
|
211
|
+
}
|
|
212
|
+
interface ServerState {
|
|
213
|
+
products: ServerProduct[];
|
|
214
|
+
}
|
|
215
|
+
type PlanOp = {
|
|
216
|
+
type: "create";
|
|
217
|
+
key: string;
|
|
218
|
+
plan: BillingConfig["plans"][string];
|
|
219
|
+
} | {
|
|
220
|
+
type: "update";
|
|
221
|
+
key: string;
|
|
222
|
+
plan: BillingConfig["plans"][string];
|
|
223
|
+
existing: ServerProduct;
|
|
224
|
+
changes: string[];
|
|
225
|
+
versionBump?: boolean;
|
|
226
|
+
grandfathering?: GrandfatheringStrategy;
|
|
227
|
+
} | {
|
|
228
|
+
type: "archive";
|
|
229
|
+
key: string;
|
|
230
|
+
existing: ServerProduct;
|
|
231
|
+
activeSubscribers: number;
|
|
232
|
+
} | {
|
|
233
|
+
type: "rename";
|
|
234
|
+
key: string;
|
|
235
|
+
oldKey: string;
|
|
236
|
+
plan: BillingConfig["plans"][string];
|
|
237
|
+
existing: ServerProduct;
|
|
238
|
+
activeSubscribers: number;
|
|
239
|
+
};
|
|
240
|
+
type PriceOp = {
|
|
241
|
+
type: "create";
|
|
242
|
+
planKey: string;
|
|
243
|
+
price: PriceConfig;
|
|
244
|
+
configKey: string;
|
|
245
|
+
} | {
|
|
246
|
+
type: "archive";
|
|
247
|
+
planKey: string;
|
|
248
|
+
existing: ServerPrice;
|
|
249
|
+
configKey: string;
|
|
250
|
+
};
|
|
251
|
+
interface DiffResult {
|
|
252
|
+
planOps: PlanOp[];
|
|
253
|
+
priceOps: PriceOp[];
|
|
254
|
+
hasDestructive: boolean;
|
|
255
|
+
}
|
|
256
|
+
declare function priceConfigKey(planKey: string, price: PriceConfig): string;
|
|
257
|
+
declare function computeDiff(config: BillingConfig, server: ServerState, subscriberCounts: Record<string, number>): DiffResult;
|
|
258
|
+
|
|
259
|
+
interface PullOptions {
|
|
260
|
+
includeDashboard?: boolean;
|
|
261
|
+
}
|
|
262
|
+
interface PullResult {
|
|
263
|
+
config: BillingConfig;
|
|
264
|
+
dashboardPlans: Array<{
|
|
265
|
+
name: string;
|
|
266
|
+
suggestedKey: string;
|
|
267
|
+
}>;
|
|
268
|
+
}
|
|
269
|
+
declare function slugify(name: string): string;
|
|
270
|
+
declare function serverStateToBillingConfig(state: ServerState, options?: PullOptions): PullResult;
|
|
271
|
+
declare function renderConfigAsTypeScript(result: PullResult): string;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Define your billing plans. Use this in your `authgate.billing.ts` config file.
|
|
275
|
+
*
|
|
276
|
+
* Returns a `TypedBilling<T>` object that carries plan and feature keys
|
|
277
|
+
* as literal types — enabling full IDE autocomplete across hooks, helpers,
|
|
278
|
+
* and test utilities.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* export const billing = defineBilling({
|
|
283
|
+
* features: {
|
|
284
|
+
* api_calls: { type: "metered" },
|
|
285
|
+
* analytics: { type: "boolean" },
|
|
286
|
+
* },
|
|
287
|
+
* plans: {
|
|
288
|
+
* free: { name: "Free", prices: [...] },
|
|
289
|
+
* pro: { name: "Pro", prices: [...] },
|
|
290
|
+
* },
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* billing.plans.pro.key // "pro" — autocomplete + literal type
|
|
294
|
+
* billing.plans.pro.name // "Pro"
|
|
295
|
+
* billing.plans.pro.entitlements // { api_calls: { limit: 10000 }, analytics: true }
|
|
296
|
+
* billing.features.api_calls.key // "api_calls"
|
|
297
|
+
* billing.features.api_calls.type // "metered"
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
declare function defineBilling<const T extends BillingConfig>(config: T): TypedBilling<T>;
|
|
301
|
+
|
|
302
|
+
export { type BillingConfig, type BillingDefinition, type DiffResult, type EntitlementValue, type FeatureConfig, type FeatureKey, type GrandfatheringStrategy, type InferFeatureKeys, type InferMeteredFeatures, type InferPlanKeys, type MeteredPriceConfig, type PerSeatPriceConfig, type PlanConfig, type PlanKey, type PlanOp, type PriceConfig, type PriceOp, type PriceTier, type PullOptions, type PullResult, type RecurringPriceConfig, type ServerPrice, type ServerProduct, type ServerState, type TieredPriceConfig, type TypedBilling, computeDiff, defineBilling, normalizeConfig, priceConfigKey, renderConfigAsTypeScript, serverStateToBillingConfig, slugify, validateConfig };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeDiff,
|
|
3
|
+
normalizeConfig,
|
|
4
|
+
priceConfigKey,
|
|
5
|
+
renderConfigAsTypeScript,
|
|
6
|
+
serverStateToBillingConfig,
|
|
7
|
+
slugify,
|
|
8
|
+
validateConfig
|
|
9
|
+
} from "./chunk-AHCLNQ6P.mjs";
|
|
10
|
+
import "./chunk-I4E63NIC.mjs";
|
|
11
|
+
|
|
12
|
+
// src/index.ts
|
|
13
|
+
function defineBilling(config) {
|
|
14
|
+
var _a;
|
|
15
|
+
const plans = {};
|
|
16
|
+
for (const [key, plan] of Object.entries(config.plans)) {
|
|
17
|
+
plans[key] = {
|
|
18
|
+
key,
|
|
19
|
+
name: plan.name,
|
|
20
|
+
entitlements: (_a = plan.entitlements) != null ? _a : {}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const features = {};
|
|
24
|
+
if (config.features) {
|
|
25
|
+
for (const [key, feature] of Object.entries(config.features)) {
|
|
26
|
+
features[key] = {
|
|
27
|
+
key,
|
|
28
|
+
type: feature.type
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
_config: config,
|
|
34
|
+
_types: void 0,
|
|
35
|
+
// phantom — never read at runtime
|
|
36
|
+
plans,
|
|
37
|
+
features
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
computeDiff,
|
|
42
|
+
defineBilling,
|
|
43
|
+
normalizeConfig,
|
|
44
|
+
priceConfigKey,
|
|
45
|
+
renderConfigAsTypeScript,
|
|
46
|
+
serverStateToBillingConfig,
|
|
47
|
+
slugify,
|
|
48
|
+
validateConfig
|
|
49
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import "./chunk-I4E63NIC.mjs";
|
|
2
|
+
|
|
3
|
+
// src/pull-from-stripe.ts
|
|
4
|
+
async function fetchStripeState(stripeKey) {
|
|
5
|
+
var _a, _b, _c, _d, _e;
|
|
6
|
+
const Stripe = (await import("stripe")).default;
|
|
7
|
+
const stripe = new Stripe(stripeKey);
|
|
8
|
+
const [products, prices] = await Promise.all([
|
|
9
|
+
stripe.products.list({ active: true, limit: 100 }),
|
|
10
|
+
stripe.prices.list({ active: true, limit: 100 })
|
|
11
|
+
]);
|
|
12
|
+
const pricesByProduct = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const sp of prices.data) {
|
|
14
|
+
const productId = typeof sp.product === "string" ? sp.product : sp.product.id;
|
|
15
|
+
const mapped = {
|
|
16
|
+
id: sp.id,
|
|
17
|
+
configKey: null,
|
|
18
|
+
amount: sp.unit_amount,
|
|
19
|
+
currency: sp.currency,
|
|
20
|
+
interval: (_b = (_a = sp.recurring) == null ? void 0 : _a.interval) != null ? _b : "monthly",
|
|
21
|
+
intervalCount: (_d = (_c = sp.recurring) == null ? void 0 : _c.interval_count) != null ? _d : 1,
|
|
22
|
+
isActive: sp.active,
|
|
23
|
+
stripePriceId: sp.id
|
|
24
|
+
};
|
|
25
|
+
pricesByProduct.set(productId, [
|
|
26
|
+
...(_e = pricesByProduct.get(productId)) != null ? _e : [],
|
|
27
|
+
mapped
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
const serverProducts = products.data.map((p) => {
|
|
31
|
+
var _a2;
|
|
32
|
+
return {
|
|
33
|
+
id: p.id,
|
|
34
|
+
configKey: null,
|
|
35
|
+
name: p.name,
|
|
36
|
+
description: p.description,
|
|
37
|
+
features: [],
|
|
38
|
+
isActive: p.active,
|
|
39
|
+
managedBy: "config",
|
|
40
|
+
stripeProductId: p.id,
|
|
41
|
+
previousConfigKeys: [],
|
|
42
|
+
prices: (_a2 = pricesByProduct.get(p.id)) != null ? _a2 : []
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
return { products: serverProducts };
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
fetchStripeState
|
|
49
|
+
};
|