owostack 0.1.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/LICENSE +190 -0
- package/README.md +88 -0
- package/dist/index.d.ts +414 -0
- package/dist/index.js +727 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
// src/catalog.ts
|
|
2
|
+
var _featureRegistry = /* @__PURE__ */ new Map();
|
|
3
|
+
var FeatureMethods = class {
|
|
4
|
+
static async check(handle, customer, opts) {
|
|
5
|
+
if (!handle._client) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
`Feature '${handle.slug}' is not bound to an Owostack client. Pass it inside a plan() in the catalog option of new Owostack({}).`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return handle._client.check({
|
|
11
|
+
customer,
|
|
12
|
+
feature: handle.slug,
|
|
13
|
+
...opts
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var BooleanHandle = class {
|
|
18
|
+
slug;
|
|
19
|
+
featureType = "boolean";
|
|
20
|
+
featureName;
|
|
21
|
+
_client = null;
|
|
22
|
+
constructor(slug, name) {
|
|
23
|
+
this.slug = slug;
|
|
24
|
+
this.featureName = name;
|
|
25
|
+
}
|
|
26
|
+
async check(customer, opts) {
|
|
27
|
+
return FeatureMethods.check(this, customer, opts);
|
|
28
|
+
}
|
|
29
|
+
on() {
|
|
30
|
+
return {
|
|
31
|
+
_type: "plan_feature",
|
|
32
|
+
slug: this.slug,
|
|
33
|
+
featureType: "boolean",
|
|
34
|
+
name: this.featureName,
|
|
35
|
+
enabled: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
off() {
|
|
39
|
+
return {
|
|
40
|
+
_type: "plan_feature",
|
|
41
|
+
slug: this.slug,
|
|
42
|
+
featureType: "boolean",
|
|
43
|
+
name: this.featureName,
|
|
44
|
+
enabled: false
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function metered(slug, opts) {
|
|
49
|
+
const callable = (creditCost) => ({ feature: slug, creditCost });
|
|
50
|
+
const handleProps = {
|
|
51
|
+
slug,
|
|
52
|
+
featureType: "metered",
|
|
53
|
+
featureName: opts?.name,
|
|
54
|
+
_client: null,
|
|
55
|
+
async check(customer, checkOpts) {
|
|
56
|
+
return FeatureMethods.check(this, customer, checkOpts);
|
|
57
|
+
},
|
|
58
|
+
async track(customer, value = 1, trackOpts) {
|
|
59
|
+
const handle = this;
|
|
60
|
+
if (!handle._client) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Feature '${slug}' is not bound to an Owostack client.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return handle._client.track({
|
|
66
|
+
customer,
|
|
67
|
+
feature: slug,
|
|
68
|
+
value,
|
|
69
|
+
...trackOpts
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
limit(value, config) {
|
|
73
|
+
return {
|
|
74
|
+
_type: "plan_feature",
|
|
75
|
+
slug,
|
|
76
|
+
featureType: "metered",
|
|
77
|
+
name: opts?.name,
|
|
78
|
+
enabled: true,
|
|
79
|
+
config: { limit: value, reset: "monthly", overage: "block", ...config }
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
included(value, config) {
|
|
83
|
+
return this.limit(value, config);
|
|
84
|
+
},
|
|
85
|
+
unlimited(config) {
|
|
86
|
+
return {
|
|
87
|
+
_type: "plan_feature",
|
|
88
|
+
slug,
|
|
89
|
+
featureType: "metered",
|
|
90
|
+
name: opts?.name,
|
|
91
|
+
enabled: true,
|
|
92
|
+
config: { limit: null, reset: "monthly", overage: "block", ...config }
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
config(configOpts) {
|
|
96
|
+
const isEnabled = configOpts.enabled !== false;
|
|
97
|
+
return {
|
|
98
|
+
_type: "plan_feature",
|
|
99
|
+
slug,
|
|
100
|
+
featureType: "metered",
|
|
101
|
+
name: opts?.name,
|
|
102
|
+
enabled: isEnabled,
|
|
103
|
+
config: { reset: "monthly", overage: "block", ...configOpts }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
Object.assign(callable, handleProps);
|
|
108
|
+
_featureRegistry.set(slug, callable);
|
|
109
|
+
return callable;
|
|
110
|
+
}
|
|
111
|
+
function boolean(slug, opts) {
|
|
112
|
+
const handle = new BooleanHandle(slug, opts?.name);
|
|
113
|
+
_featureRegistry.set(slug, handle);
|
|
114
|
+
return handle;
|
|
115
|
+
}
|
|
116
|
+
var CreditSystemHandle = class {
|
|
117
|
+
slug;
|
|
118
|
+
name;
|
|
119
|
+
description;
|
|
120
|
+
featureCosts;
|
|
121
|
+
constructor(slug, featureCosts, opts) {
|
|
122
|
+
this.slug = slug;
|
|
123
|
+
this.featureCosts = featureCosts;
|
|
124
|
+
this.name = opts?.name;
|
|
125
|
+
this.description = opts?.description;
|
|
126
|
+
}
|
|
127
|
+
credits(amount, config) {
|
|
128
|
+
return {
|
|
129
|
+
_type: "plan_feature",
|
|
130
|
+
slug: this.slug,
|
|
131
|
+
featureType: "metered",
|
|
132
|
+
name: this.name,
|
|
133
|
+
enabled: true,
|
|
134
|
+
config: {
|
|
135
|
+
limit: amount,
|
|
136
|
+
reset: config?.reset || "monthly",
|
|
137
|
+
overage: config?.overage || "block"
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
_buildDefinition() {
|
|
142
|
+
return {
|
|
143
|
+
_type: "credit_system",
|
|
144
|
+
slug: this.slug,
|
|
145
|
+
name: this.name || this.slug,
|
|
146
|
+
description: this.description,
|
|
147
|
+
features: Array.from(this.featureCosts.entries()).map(
|
|
148
|
+
([feature, creditCost]) => ({
|
|
149
|
+
feature,
|
|
150
|
+
creditCost
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var _creditSystemRegistry = /* @__PURE__ */ new Map();
|
|
157
|
+
function creditSystem(slug, opts) {
|
|
158
|
+
const featureCosts = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const featureEntry of opts.features) {
|
|
160
|
+
featureCosts.set(featureEntry.feature, featureEntry.creditCost);
|
|
161
|
+
}
|
|
162
|
+
const handle = new CreditSystemHandle(slug, featureCosts, {
|
|
163
|
+
name: opts.name,
|
|
164
|
+
description: opts.description
|
|
165
|
+
});
|
|
166
|
+
_creditSystemRegistry.set(slug, handle);
|
|
167
|
+
return handle;
|
|
168
|
+
}
|
|
169
|
+
function plan(slug, config) {
|
|
170
|
+
return {
|
|
171
|
+
_type: "plan",
|
|
172
|
+
slug,
|
|
173
|
+
...config
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function slugToName(slug) {
|
|
177
|
+
return slug.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
178
|
+
}
|
|
179
|
+
function buildSyncPayload(catalog, defaultProvider) {
|
|
180
|
+
const featureMap = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const entry of catalog) {
|
|
182
|
+
if (entry._type !== "plan") continue;
|
|
183
|
+
for (const f of entry.features) {
|
|
184
|
+
if (_creditSystemRegistry.has(f.slug)) continue;
|
|
185
|
+
if (!featureMap.has(f.slug)) {
|
|
186
|
+
featureMap.set(f.slug, {
|
|
187
|
+
slug: f.slug,
|
|
188
|
+
type: f.featureType,
|
|
189
|
+
name: f.name || slugToName(f.slug)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const creditSystems = catalog.filter((e) => e._type === "credit_system").map((cs) => ({
|
|
195
|
+
slug: cs.slug,
|
|
196
|
+
name: cs.name,
|
|
197
|
+
description: cs.description,
|
|
198
|
+
features: cs.features
|
|
199
|
+
}));
|
|
200
|
+
for (const entry of catalog) {
|
|
201
|
+
if (entry._type !== "plan") continue;
|
|
202
|
+
for (const f of entry.features) {
|
|
203
|
+
const csHandle = _creditSystemRegistry.get(f.slug);
|
|
204
|
+
if (csHandle && !creditSystems.find((cs) => cs.slug === f.slug)) {
|
|
205
|
+
const def = csHandle._buildDefinition();
|
|
206
|
+
creditSystems.push({
|
|
207
|
+
slug: def.slug,
|
|
208
|
+
name: def.name,
|
|
209
|
+
description: def.description,
|
|
210
|
+
features: def.features
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const plans = catalog.filter((e) => e._type === "plan").map((p) => ({
|
|
216
|
+
slug: p.slug,
|
|
217
|
+
name: p.name,
|
|
218
|
+
description: p.description ?? void 0,
|
|
219
|
+
price: p.price,
|
|
220
|
+
currency: p.currency,
|
|
221
|
+
interval: p.interval,
|
|
222
|
+
planGroup: p.planGroup ?? void 0,
|
|
223
|
+
trialDays: p.trialDays ?? void 0,
|
|
224
|
+
provider: p.provider ?? void 0,
|
|
225
|
+
metadata: p.metadata ?? void 0,
|
|
226
|
+
features: p.features.map((f) => ({
|
|
227
|
+
slug: f.slug,
|
|
228
|
+
enabled: f.enabled,
|
|
229
|
+
// Boolean features have no limit concept (null), metered features use limit from config
|
|
230
|
+
limit: f.featureType === "boolean" ? null : f.config?.limit ?? null,
|
|
231
|
+
// Boolean features have no reset interval
|
|
232
|
+
...f.featureType !== "boolean" && {
|
|
233
|
+
reset: f.config?.reset || "monthly"
|
|
234
|
+
},
|
|
235
|
+
...f.config?.overage && { overage: f.config.overage },
|
|
236
|
+
...f.config?.overagePrice !== void 0 && {
|
|
237
|
+
overagePrice: f.config.overagePrice
|
|
238
|
+
},
|
|
239
|
+
...f.config?.maxOverageUnits !== void 0 && {
|
|
240
|
+
maxOverageUnits: f.config.maxOverageUnits
|
|
241
|
+
},
|
|
242
|
+
...f.config?.billingUnits !== void 0 && {
|
|
243
|
+
billingUnits: f.config.billingUnits
|
|
244
|
+
},
|
|
245
|
+
...f.config?.creditCost !== void 0 && {
|
|
246
|
+
creditCost: f.config.creditCost
|
|
247
|
+
}
|
|
248
|
+
}))
|
|
249
|
+
}));
|
|
250
|
+
return {
|
|
251
|
+
defaultProvider,
|
|
252
|
+
features: Array.from(featureMap.values()),
|
|
253
|
+
creditSystems,
|
|
254
|
+
plans
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function bindFeatureHandles(client, catalog) {
|
|
258
|
+
if (catalog) {
|
|
259
|
+
const slugsInCatalog = /* @__PURE__ */ new Set();
|
|
260
|
+
for (const entry of catalog) {
|
|
261
|
+
if (entry._type === "plan") {
|
|
262
|
+
for (const f of entry.features) {
|
|
263
|
+
slugsInCatalog.add(f.slug);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const [slug, handle] of _featureRegistry) {
|
|
268
|
+
if (slugsInCatalog.has(slug)) {
|
|
269
|
+
handle._client = client;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
for (const handle of _featureRegistry.values()) {
|
|
274
|
+
handle._client = client;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/index.ts
|
|
280
|
+
var Owostack = class {
|
|
281
|
+
/** @internal — exposed for CLI tooling */
|
|
282
|
+
_config;
|
|
283
|
+
apiUrl;
|
|
284
|
+
/** Billing: unbilled usage, invoices, and invoice generation */
|
|
285
|
+
billing;
|
|
286
|
+
/** Wallet: payment methods — callable + namespace */
|
|
287
|
+
wallet;
|
|
288
|
+
constructor(config) {
|
|
289
|
+
this._config = config;
|
|
290
|
+
this.apiUrl = this.resolveApiUrl(config);
|
|
291
|
+
this.billing = new BillingNamespace(this);
|
|
292
|
+
this.wallet = buildWalletFn(this);
|
|
293
|
+
this.plans = buildPlansFn(this);
|
|
294
|
+
if (config.catalog && config.catalog.length > 0) {
|
|
295
|
+
bindFeatureHandles(this, config.catalog);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
resolveApiUrl(config) {
|
|
299
|
+
if (config.apiUrl) {
|
|
300
|
+
return config.apiUrl;
|
|
301
|
+
}
|
|
302
|
+
if (config.mode === "sandbox") {
|
|
303
|
+
return "https://sandbox.owostack.com";
|
|
304
|
+
}
|
|
305
|
+
if (config.mode === "live") {
|
|
306
|
+
return "https://api.owostack.com";
|
|
307
|
+
}
|
|
308
|
+
return "https://api.owostack.com";
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Override the API key at runtime (used by CLI).
|
|
312
|
+
* @internal
|
|
313
|
+
*/
|
|
314
|
+
setSecretKey(key) {
|
|
315
|
+
this._config.secretKey = key;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Override the API URL at runtime (used by CLI).
|
|
319
|
+
* @internal
|
|
320
|
+
*/
|
|
321
|
+
setApiUrl(url) {
|
|
322
|
+
this._config.apiUrl = url;
|
|
323
|
+
this.apiUrl = url;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* sync() - Push catalog to the API
|
|
327
|
+
*
|
|
328
|
+
* Reconciles features and plans defined in the catalog with the server.
|
|
329
|
+
* Idempotent — safe to call multiple times with the same config.
|
|
330
|
+
* Only creates/updates; never deletes.
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```ts
|
|
334
|
+
* const result = await owo.sync();
|
|
335
|
+
* console.log(`Created ${result.features.created.length} features`);
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
async sync() {
|
|
339
|
+
if (!this._config.catalog || this._config.catalog.length === 0) {
|
|
340
|
+
return {
|
|
341
|
+
success: true,
|
|
342
|
+
features: { created: [], updated: [], unchanged: [] },
|
|
343
|
+
creditSystems: { created: [], updated: [], unchanged: [] },
|
|
344
|
+
plans: { created: [], updated: [], unchanged: [] },
|
|
345
|
+
warnings: ["No catalog entries to sync."]
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const payload = buildSyncPayload(
|
|
349
|
+
this._config.catalog,
|
|
350
|
+
this._config.provider
|
|
351
|
+
);
|
|
352
|
+
const response = await this.post("/sync", payload);
|
|
353
|
+
return response;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* attach() - Checkout & Subscription Management
|
|
357
|
+
*
|
|
358
|
+
* Creates checkout sessions, handles upgrades/downgrades, manages subscriptions.
|
|
359
|
+
* Auto-creates the customer if customerData is provided and the customer doesn't exist.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```ts
|
|
363
|
+
* const result = await owo.attach({
|
|
364
|
+
* customer: 'user_123',
|
|
365
|
+
* product: 'pro_plan',
|
|
366
|
+
* customerData: { email: 'user@example.com', name: 'Jane' },
|
|
367
|
+
* });
|
|
368
|
+
*
|
|
369
|
+
* // Redirect user to checkout
|
|
370
|
+
* window.location.href = result.url;
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
async attach(params) {
|
|
374
|
+
const response = await this.post("/attach", params);
|
|
375
|
+
return response;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* check() - Feature Gating & Access Control
|
|
379
|
+
*
|
|
380
|
+
* Queries whether a customer can access a feature based on their plan,
|
|
381
|
+
* payment status, and usage limits.
|
|
382
|
+
*
|
|
383
|
+
* Pass sendEvent: true to atomically check AND track usage in one call.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* const access = await owo.check({
|
|
388
|
+
* customer: 'user_123',
|
|
389
|
+
* feature: 'api_calls',
|
|
390
|
+
* customerData: { email: 'user@example.com' },
|
|
391
|
+
* });
|
|
392
|
+
*
|
|
393
|
+
* if (!access.allowed) {
|
|
394
|
+
* throw new Error(access.code);
|
|
395
|
+
* }
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
async check(params) {
|
|
399
|
+
const response = await this.post("/check", params);
|
|
400
|
+
return response;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* track() - Usage Metering & Billing
|
|
404
|
+
*
|
|
405
|
+
* Records usage events, decrements quotas, and triggers billing for
|
|
406
|
+
* pay-as-you-go features.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* await owo.track({
|
|
411
|
+
* customer: 'user_123',
|
|
412
|
+
* feature: 'api_calls',
|
|
413
|
+
* value: 1,
|
|
414
|
+
* customerData: { email: 'user@example.com' },
|
|
415
|
+
* });
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
async track(params) {
|
|
419
|
+
const response = await this.post("/track", params);
|
|
420
|
+
return response;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* addon() - Purchase Add-on Credit Pack
|
|
424
|
+
*
|
|
425
|
+
* Buy additional credits scoped to a credit system.
|
|
426
|
+
* If the customer has a card on file, charges immediately.
|
|
427
|
+
* Otherwise returns a checkout URL.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* ```ts
|
|
431
|
+
* const result = await owo.addon({
|
|
432
|
+
* customer: 'user_123',
|
|
433
|
+
* pack: '250-credits',
|
|
434
|
+
* quantity: 2,
|
|
435
|
+
* });
|
|
436
|
+
*
|
|
437
|
+
* if (result.requiresCheckout) {
|
|
438
|
+
* window.location.href = result.checkoutUrl!;
|
|
439
|
+
* } else {
|
|
440
|
+
* console.log(`Balance: ${result.balance} credits`);
|
|
441
|
+
* }
|
|
442
|
+
* ```
|
|
443
|
+
*/
|
|
444
|
+
async addon(params) {
|
|
445
|
+
const response = await this.post("/addon", params);
|
|
446
|
+
return response;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* customer() - Create or resolve a customer
|
|
450
|
+
*
|
|
451
|
+
* Creates a new customer or resolves an existing one by email or ID.
|
|
452
|
+
* If customerData is provided and customer doesn't exist, creates a new customer.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* // Create new customer
|
|
457
|
+
* const customer = await owo.customer({
|
|
458
|
+
* email: 'org@acme.com',
|
|
459
|
+
* name: 'Acme Corp',
|
|
460
|
+
* metadata: { plan: 'enterprise' }
|
|
461
|
+
* });
|
|
462
|
+
*
|
|
463
|
+
* // Get existing customer
|
|
464
|
+
* const existing = await owo.customer({ email: 'org@acme.com' });
|
|
465
|
+
* ```
|
|
466
|
+
*/
|
|
467
|
+
async customer(params) {
|
|
468
|
+
const response = await this.post("/customers", params);
|
|
469
|
+
return response;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* addEntity() - Add a feature entity (e.g., seat)
|
|
473
|
+
*
|
|
474
|
+
* Creates a new entity scoped to a feature. Validates against the feature limit.
|
|
475
|
+
* Throws if the limit would be exceeded.
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```ts
|
|
479
|
+
* await owo.addEntity({
|
|
480
|
+
* customer: 'org@acme.com',
|
|
481
|
+
* feature: 'seats',
|
|
482
|
+
* entity: 'user_123',
|
|
483
|
+
* name: 'John Doe',
|
|
484
|
+
* metadata: { role: 'admin' }
|
|
485
|
+
* });
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
async addEntity(params) {
|
|
489
|
+
const response = await this.post("/entities", params);
|
|
490
|
+
return response;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* removeEntity() - Remove a feature entity
|
|
494
|
+
*
|
|
495
|
+
* Removes an entity and frees up the slot for the feature limit.
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* await owo.removeEntity({
|
|
500
|
+
* customer: 'org@acme.com',
|
|
501
|
+
* feature: 'seats',
|
|
502
|
+
* entity: 'user_123'
|
|
503
|
+
* });
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
async removeEntity(params) {
|
|
507
|
+
const response = await this.post("/entities/remove", params);
|
|
508
|
+
return response;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* listEntities() - List feature entities
|
|
512
|
+
*
|
|
513
|
+
* Returns all entities for a customer, optionally filtered by feature.
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* ```ts
|
|
517
|
+
* // List all entities
|
|
518
|
+
* const { entities } = await owo.listEntities({ customer: 'org@acme.com' });
|
|
519
|
+
*
|
|
520
|
+
* // List seats only
|
|
521
|
+
* const { entities: seats } = await owo.listEntities({
|
|
522
|
+
* customer: 'org@acme.com',
|
|
523
|
+
* feature: 'seats'
|
|
524
|
+
* });
|
|
525
|
+
* ```
|
|
526
|
+
*/
|
|
527
|
+
async listEntities(params) {
|
|
528
|
+
const query = { customer: params.customer };
|
|
529
|
+
if (params.feature) query.feature = params.feature;
|
|
530
|
+
const response = await this.get("/entities", query);
|
|
531
|
+
return response;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* plans() - List Plans
|
|
535
|
+
*
|
|
536
|
+
* Returns all active plans for the organization. Useful for building
|
|
537
|
+
* pricing pages and plan selection UIs.
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* ```ts
|
|
541
|
+
* const { plans } = await owo.plans();
|
|
542
|
+
* plans.forEach(p => console.log(p.name, p.price));
|
|
543
|
+
*
|
|
544
|
+
* // Filter by group
|
|
545
|
+
* const { plans: support } = await owo.plans({ group: 'support' });
|
|
546
|
+
*
|
|
547
|
+
* // Get a single plan by slug
|
|
548
|
+
* const plan = await owo.plans.get('pro');
|
|
549
|
+
* ```
|
|
550
|
+
*/
|
|
551
|
+
plans;
|
|
552
|
+
/**
|
|
553
|
+
* Internal POST request handler
|
|
554
|
+
* @internal
|
|
555
|
+
*/
|
|
556
|
+
async post(endpoint, body) {
|
|
557
|
+
const response = await fetch(`${this.apiUrl}${endpoint}`, {
|
|
558
|
+
method: "POST",
|
|
559
|
+
headers: {
|
|
560
|
+
"Content-Type": "application/json",
|
|
561
|
+
Authorization: `Bearer ${this._config.secretKey}`
|
|
562
|
+
},
|
|
563
|
+
body: JSON.stringify(body)
|
|
564
|
+
});
|
|
565
|
+
if (!response.ok) {
|
|
566
|
+
const resp = await response.json();
|
|
567
|
+
const errorData = resp.error || resp;
|
|
568
|
+
throw new OwostackError(
|
|
569
|
+
errorData.code || "unknown_error",
|
|
570
|
+
errorData.message || errorData.error || "Request failed"
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
return response.json();
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Internal GET request handler
|
|
577
|
+
* @internal
|
|
578
|
+
*/
|
|
579
|
+
async get(endpoint, query) {
|
|
580
|
+
const url = new URL(`${this.apiUrl}${endpoint}`);
|
|
581
|
+
if (query) {
|
|
582
|
+
for (const [key, value] of Object.entries(query)) {
|
|
583
|
+
if (value !== void 0 && value !== "")
|
|
584
|
+
url.searchParams.set(key, value);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const response = await fetch(url.toString(), {
|
|
588
|
+
method: "GET",
|
|
589
|
+
headers: {
|
|
590
|
+
Authorization: `Bearer ${this._config.secretKey}`
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
if (!response.ok) {
|
|
594
|
+
const resp = await response.json();
|
|
595
|
+
const errorData = resp.error || resp;
|
|
596
|
+
throw new OwostackError(
|
|
597
|
+
errorData.code || "unknown_error",
|
|
598
|
+
errorData.message || errorData.error || "Request failed"
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return response.json();
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
function buildPlansFn(client) {
|
|
605
|
+
const fn = ((params) => {
|
|
606
|
+
const query = {};
|
|
607
|
+
if (params?.group) query.group = params.group;
|
|
608
|
+
if (params?.interval) query.interval = params.interval;
|
|
609
|
+
if (params?.currency) query.currency = params.currency;
|
|
610
|
+
if (params?.includeInactive) query.includeInactive = "true";
|
|
611
|
+
return client.get("/plans", query);
|
|
612
|
+
});
|
|
613
|
+
fn.get = async (slug) => {
|
|
614
|
+
const response = await client.get(`/plans/${encodeURIComponent(slug)}`);
|
|
615
|
+
return response.plan;
|
|
616
|
+
};
|
|
617
|
+
return fn;
|
|
618
|
+
}
|
|
619
|
+
function buildWalletFn(client) {
|
|
620
|
+
const fn = ((customer) => client.get("/wallet", { customer }));
|
|
621
|
+
fn.setup = (customer, opts) => client.post("/wallet/setup", {
|
|
622
|
+
customer,
|
|
623
|
+
...opts
|
|
624
|
+
});
|
|
625
|
+
fn.list = (customer) => client.get("/wallet", { customer });
|
|
626
|
+
fn.remove = (customer, id) => client.post("/wallet/remove", {
|
|
627
|
+
customer,
|
|
628
|
+
id
|
|
629
|
+
});
|
|
630
|
+
return fn;
|
|
631
|
+
}
|
|
632
|
+
var BillingNamespace = class {
|
|
633
|
+
constructor(client) {
|
|
634
|
+
this.client = client;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* usage() - Get Unbilled Overage Usage
|
|
638
|
+
*
|
|
639
|
+
* Returns a breakdown of all billable usage that hasn't been invoiced yet.
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```ts
|
|
643
|
+
* const usage = await owo.billing.usage({ customer: 'user_123' });
|
|
644
|
+
* console.log(`Owes: ${usage.currency} ${usage.totalEstimated / 100}`);
|
|
645
|
+
* ```
|
|
646
|
+
*/
|
|
647
|
+
async usage(params) {
|
|
648
|
+
const response = await this.client.get("/billing/usage", {
|
|
649
|
+
customer: params.customer
|
|
650
|
+
});
|
|
651
|
+
return response;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* invoice() - Generate an Invoice
|
|
655
|
+
*
|
|
656
|
+
* Creates an invoice for the customer's unbilled overage usage.
|
|
657
|
+
* Fails if there's no unbilled usage to invoice.
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```ts
|
|
661
|
+
* const result = await owo.billing.invoice({ customer: 'user_123' });
|
|
662
|
+
* console.log(`Invoice ${result.invoice.number}: ${result.invoice.total}`);
|
|
663
|
+
* ```
|
|
664
|
+
*/
|
|
665
|
+
async invoice(params) {
|
|
666
|
+
const response = await this.client.post("/billing/invoice", params);
|
|
667
|
+
return response;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* invoices() - List Past Invoices
|
|
671
|
+
*
|
|
672
|
+
* Returns all invoices for a customer.
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```ts
|
|
676
|
+
* const result = await owo.billing.invoices({ customer: 'user_123' });
|
|
677
|
+
* result.invoices.forEach(inv => console.log(inv.number, inv.status));
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
async invoices(params) {
|
|
681
|
+
const response = await this.client.get("/billing/invoices", {
|
|
682
|
+
customer: params.customer
|
|
683
|
+
});
|
|
684
|
+
return response;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* pay() - Pay an Invoice
|
|
688
|
+
*
|
|
689
|
+
* Attempts to auto-charge the customer's saved payment method.
|
|
690
|
+
* If no card on file or charge fails, returns a checkout URL instead.
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```ts
|
|
694
|
+
* const result = await owo.billing.pay({ invoiceId: 'inv_xxx' });
|
|
695
|
+
* if (!result.paid) {
|
|
696
|
+
* // Redirect customer to checkout
|
|
697
|
+
* window.location.href = result.checkoutUrl!;
|
|
698
|
+
* }
|
|
699
|
+
* ```
|
|
700
|
+
*/
|
|
701
|
+
async pay(params) {
|
|
702
|
+
const response = await this.client.post(
|
|
703
|
+
`/billing/invoice/${encodeURIComponent(params.invoiceId)}/pay`,
|
|
704
|
+
{ callbackUrl: params.callbackUrl }
|
|
705
|
+
);
|
|
706
|
+
return response;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
var OwostackError = class extends Error {
|
|
710
|
+
code;
|
|
711
|
+
constructor(code, message) {
|
|
712
|
+
super(message);
|
|
713
|
+
this.name = "OwostackError";
|
|
714
|
+
this.code = code;
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
export {
|
|
718
|
+
BooleanHandle,
|
|
719
|
+
CreditSystemHandle,
|
|
720
|
+
Owostack,
|
|
721
|
+
OwostackError,
|
|
722
|
+
boolean,
|
|
723
|
+
buildSyncPayload,
|
|
724
|
+
creditSystem,
|
|
725
|
+
metered,
|
|
726
|
+
plan
|
|
727
|
+
};
|