@beinfi/pulse-sdk 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/README.md +103 -0
- package/dist/ai.d.mts +934 -0
- package/dist/ai.d.ts +934 -0
- package/dist/ai.js +73 -0
- package/dist/ai.mjs +48 -0
- package/dist/checkout.js +1 -0
- package/dist/index.d.mts +1094 -0
- package/dist/index.d.ts +1094 -0
- package/dist/index.js +712 -0
- package/dist/index.mjs +679 -0
- package/package.json +83 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var PulseError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "PulseError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var PulseApiError = class extends PulseError {
|
|
9
|
+
/** HTTP status code (e.g. 400, 404, 500). */
|
|
10
|
+
status;
|
|
11
|
+
/** Machine-readable error code (e.g. `"unauthorized"`, `"not_found"`). */
|
|
12
|
+
errorCode;
|
|
13
|
+
/** Rate limit info from response headers, if available. */
|
|
14
|
+
rateLimit;
|
|
15
|
+
constructor(status, errorCode, message, rateLimit) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "PulseApiError";
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.errorCode = errorCode;
|
|
20
|
+
this.rateLimit = rateLimit;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var PulseAuthenticationError = class extends PulseApiError {
|
|
24
|
+
constructor(message = "Invalid API key") {
|
|
25
|
+
super(401, "unauthorized", message);
|
|
26
|
+
this.name = "PulseAuthenticationError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var PulseRateLimitError = class extends PulseApiError {
|
|
30
|
+
/** Number of seconds to wait before retrying. */
|
|
31
|
+
retryAfter;
|
|
32
|
+
constructor(retryAfter, rateLimit) {
|
|
33
|
+
super(429, "rate_limit_exceeded", "Rate limit exceeded", rateLimit);
|
|
34
|
+
this.name = "PulseRateLimitError";
|
|
35
|
+
this.retryAfter = retryAfter;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/client.ts
|
|
40
|
+
var DEFAULT_BASE_URL = "https://api.beinfi.com";
|
|
41
|
+
var HttpClient = class {
|
|
42
|
+
apiKey;
|
|
43
|
+
baseUrl;
|
|
44
|
+
constructor(apiKey, baseUrl) {
|
|
45
|
+
if (!apiKey.startsWith("sk_live_")) {
|
|
46
|
+
throw new PulseError(
|
|
47
|
+
'Invalid API key format. Keys must start with "sk_live_"'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
this.apiKey = apiKey;
|
|
51
|
+
this.baseUrl = (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
52
|
+
}
|
|
53
|
+
async request(method, path, options) {
|
|
54
|
+
let url = `${this.baseUrl}/api/v1${path}`;
|
|
55
|
+
if (options?.query) {
|
|
56
|
+
const params = new URLSearchParams();
|
|
57
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
58
|
+
if (value !== void 0) {
|
|
59
|
+
params.set(key, String(value));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const qs = params.toString();
|
|
63
|
+
if (qs) url += `?${qs}`;
|
|
64
|
+
}
|
|
65
|
+
const headers = {
|
|
66
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
67
|
+
"Content-Type": "application/json"
|
|
68
|
+
};
|
|
69
|
+
const response = await fetch(url, {
|
|
70
|
+
method,
|
|
71
|
+
headers,
|
|
72
|
+
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
73
|
+
});
|
|
74
|
+
const rateLimit = this.parseRateLimit(response.headers);
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
this.handleError(response.status, await this.safeJson(response), rateLimit);
|
|
77
|
+
}
|
|
78
|
+
if (response.status === 204) {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
const json = await response.json();
|
|
82
|
+
if (json && typeof json === "object" && "data" in json) {
|
|
83
|
+
return json.data;
|
|
84
|
+
}
|
|
85
|
+
return json;
|
|
86
|
+
}
|
|
87
|
+
parseRateLimit(headers) {
|
|
88
|
+
const limit = headers.get("X-RateLimit-Limit");
|
|
89
|
+
const remaining = headers.get("X-RateLimit-Remaining");
|
|
90
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
91
|
+
if (limit && remaining && reset) {
|
|
92
|
+
return {
|
|
93
|
+
limit: Number(limit),
|
|
94
|
+
remaining: Number(remaining),
|
|
95
|
+
reset: Number(reset)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
async safeJson(response) {
|
|
101
|
+
try {
|
|
102
|
+
return await response.json();
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
handleError(status, body, rateLimit) {
|
|
108
|
+
const errorCode = body?.error ?? "unknown_error";
|
|
109
|
+
const message = body?.message ?? `Request failed with status ${status}`;
|
|
110
|
+
if (status === 401) {
|
|
111
|
+
throw new PulseAuthenticationError(message);
|
|
112
|
+
}
|
|
113
|
+
if (status === 429) {
|
|
114
|
+
const retryAfter = rateLimit?.reset ? Math.max(0, rateLimit.reset - Math.floor(Date.now() / 1e3)) : 60;
|
|
115
|
+
throw new PulseRateLimitError(retryAfter, rateLimit);
|
|
116
|
+
}
|
|
117
|
+
throw new PulseApiError(status, errorCode, message, rateLimit);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/resources/payment-links.ts
|
|
122
|
+
var PaymentLinksResource = class {
|
|
123
|
+
constructor(client) {
|
|
124
|
+
this.client = client;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create a new payment link.
|
|
128
|
+
*
|
|
129
|
+
* @param params - Payment link parameters (title, amount, optional currency and description).
|
|
130
|
+
* @returns The created payment link object.
|
|
131
|
+
* @throws {PulseAuthenticationError} If the API key is invalid.
|
|
132
|
+
* @throws {PulseApiError} If validation fails (e.g. invalid currency).
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const link = await pulse.paymentLinks.create({
|
|
137
|
+
* title: 'Web Development',
|
|
138
|
+
* amount: '150.00',
|
|
139
|
+
* currency: 'USD',
|
|
140
|
+
* description: 'Landing page development',
|
|
141
|
+
* })
|
|
142
|
+
* console.log(link.id, link.slug)
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
async create(params) {
|
|
146
|
+
return this.client.request("POST", "/payment-links", {
|
|
147
|
+
body: params
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* List payment links for the authenticated user.
|
|
152
|
+
*
|
|
153
|
+
* @param params - Optional pagination parameters (limit, offset).
|
|
154
|
+
* @returns Array of payment link objects.
|
|
155
|
+
* @throws {PulseAuthenticationError} If the API key is invalid.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const links = await pulse.paymentLinks.list({ limit: 10, offset: 0 })
|
|
160
|
+
* for (const link of links) {
|
|
161
|
+
* console.log(link.title, link.amount, link.status)
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
async list(params) {
|
|
166
|
+
return this.client.request("GET", "/payment-links", {
|
|
167
|
+
query: {
|
|
168
|
+
limit: params?.limit,
|
|
169
|
+
offset: params?.offset
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get a single payment link by ID.
|
|
175
|
+
*
|
|
176
|
+
* @param linkId - The payment link UUID.
|
|
177
|
+
* @returns The payment link object.
|
|
178
|
+
* @throws {PulseAuthenticationError} If the API key is invalid.
|
|
179
|
+
* @throws {PulseApiError} With status 404 if the link doesn't exist.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* const link = await pulse.paymentLinks.get('abc-123-def')
|
|
184
|
+
* console.log(link.title, link.amount)
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
async get(linkId) {
|
|
188
|
+
return this.client.request("GET", `/payment-links/${linkId}`);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* List payment intents (payment attempts) for a specific payment link.
|
|
192
|
+
*
|
|
193
|
+
* @param linkId - The payment link UUID.
|
|
194
|
+
* @returns Array of payment intent objects.
|
|
195
|
+
* @throws {PulseAuthenticationError} If the API key is invalid.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* const intents = await pulse.paymentLinks.listIntents('abc-123-def')
|
|
200
|
+
* const confirmed = intents.filter(i => i.status === 'confirmed')
|
|
201
|
+
* console.log(`${confirmed.length} confirmed payments`)
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
async listIntents(linkId) {
|
|
205
|
+
return this.client.request(
|
|
206
|
+
"GET",
|
|
207
|
+
`/payment-links/${linkId}/intents`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/resources/webhooks.ts
|
|
213
|
+
var WebhooksResource = class {
|
|
214
|
+
constructor(client) {
|
|
215
|
+
this.client = client;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Create a new webhook subscription.
|
|
219
|
+
* The response includes a `secret` field (64-char hex) used to verify signatures.
|
|
220
|
+
* **Store this secret securely** — it is only returned once at creation time.
|
|
221
|
+
*
|
|
222
|
+
* @param params - Webhook URL and event types to subscribe to.
|
|
223
|
+
* @returns The created webhook with its signing secret.
|
|
224
|
+
* @throws {PulseAuthenticationError} If the API key is invalid.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* const wh = await pulse.webhooks.create({
|
|
229
|
+
* url: 'https://example.com/webhook',
|
|
230
|
+
* events: ['payment.confirmed'],
|
|
231
|
+
* })
|
|
232
|
+
* // Save wh.secret to your environment/secrets manager
|
|
233
|
+
* console.log('Webhook secret:', wh.secret)
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
async create(params) {
|
|
237
|
+
return this.client.request("POST", "/webhooks", {
|
|
238
|
+
body: params
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* List all webhook subscriptions for the authenticated user.
|
|
243
|
+
*
|
|
244
|
+
* @returns Array of webhook objects (without secrets).
|
|
245
|
+
* @throws {PulseAuthenticationError} If the API key is invalid.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* const webhooks = await pulse.webhooks.list()
|
|
250
|
+
* for (const wh of webhooks) {
|
|
251
|
+
* console.log(wh.url, wh.events, wh.isActive)
|
|
252
|
+
* }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
async list() {
|
|
256
|
+
return this.client.request("GET", "/webhooks");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Delete a webhook subscription.
|
|
260
|
+
*
|
|
261
|
+
* @param id - The webhook UUID to delete.
|
|
262
|
+
* @throws {PulseAuthenticationError} If the API key is invalid.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* await pulse.webhooks.delete('webhook-id')
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
async delete(id) {
|
|
270
|
+
return this.client.request("DELETE", `/webhooks/${id}`);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/resources/metering.ts
|
|
275
|
+
function generateId() {
|
|
276
|
+
const hex = "0123456789abcdef";
|
|
277
|
+
let id = "";
|
|
278
|
+
for (let i = 0; i < 32; i++) {
|
|
279
|
+
id += hex[Math.floor(Math.random() * 16)];
|
|
280
|
+
if (i === 7 || i === 11 || i === 15 || i === 19) id += "-";
|
|
281
|
+
}
|
|
282
|
+
return id;
|
|
283
|
+
}
|
|
284
|
+
var MeteringResource = class {
|
|
285
|
+
constructor(client) {
|
|
286
|
+
this.client = client;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Track a single usage event.
|
|
290
|
+
*
|
|
291
|
+
* @param params - Event parameters (meterId, customerId, value).
|
|
292
|
+
* @returns The created event object.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* await pulse.metering.track({
|
|
297
|
+
* meterId: 'tokens',
|
|
298
|
+
* customerId: 'user_123',
|
|
299
|
+
* value: 1500,
|
|
300
|
+
* metadata: { model: 'gpt-4' }
|
|
301
|
+
* })
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
async track(params) {
|
|
305
|
+
return this.client.request(
|
|
306
|
+
"POST",
|
|
307
|
+
"/metering/events",
|
|
308
|
+
{
|
|
309
|
+
body: {
|
|
310
|
+
eventId: params.eventId || generateId(),
|
|
311
|
+
meterId: params.meterId,
|
|
312
|
+
customerId: params.customerId,
|
|
313
|
+
value: typeof params.value === "number" ? String(params.value) : params.value,
|
|
314
|
+
timestamp: params.timestamp?.toISOString(),
|
|
315
|
+
metadata: params.metadata
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Track multiple usage events in a single request.
|
|
322
|
+
*
|
|
323
|
+
* @param events - Array of event parameters.
|
|
324
|
+
* @returns Batch result with accepted/failed counts.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* await pulse.metering.trackBatch([
|
|
329
|
+
* { meterId: 'tokens', customerId: 'user_1', value: 500 },
|
|
330
|
+
* { meterId: 'tokens', customerId: 'user_2', value: 1200 },
|
|
331
|
+
* ])
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
async trackBatch(events) {
|
|
335
|
+
return this.client.request(
|
|
336
|
+
"POST",
|
|
337
|
+
"/metering/events/batch",
|
|
338
|
+
{
|
|
339
|
+
body: {
|
|
340
|
+
events: events.map((e) => ({
|
|
341
|
+
eventId: e.eventId || generateId(),
|
|
342
|
+
meterId: e.meterId,
|
|
343
|
+
customerId: e.customerId,
|
|
344
|
+
value: typeof e.value === "number" ? String(e.value) : e.value,
|
|
345
|
+
timestamp: e.timestamp?.toISOString(),
|
|
346
|
+
metadata: e.metadata
|
|
347
|
+
}))
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Query aggregated usage data.
|
|
354
|
+
*
|
|
355
|
+
* @param query - Optional filters (customerId, date range).
|
|
356
|
+
* @returns Aggregated usage by meter.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* const usage = await pulse.metering.getUsage({ customerId: 'user_123' })
|
|
361
|
+
* for (const item of usage.data) {
|
|
362
|
+
* console.log(item.meterName, item.totalValue, item.totalAmount)
|
|
363
|
+
* }
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
async getUsage(query) {
|
|
367
|
+
return this.client.request("GET", "/metering/usage", {
|
|
368
|
+
query: {
|
|
369
|
+
customerId: query?.customerId,
|
|
370
|
+
startDate: query?.startDate,
|
|
371
|
+
endDate: query?.endDate
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* List all products.
|
|
377
|
+
*
|
|
378
|
+
* @returns Array of product objects with meters.
|
|
379
|
+
*/
|
|
380
|
+
async listProducts() {
|
|
381
|
+
return this.client.request("GET", "/metering/products");
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Create a new product.
|
|
385
|
+
*
|
|
386
|
+
* @param data - Product data (name, optional description).
|
|
387
|
+
* @returns The created product object.
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* ```typescript
|
|
391
|
+
* const product = await pulse.metering.createProduct({
|
|
392
|
+
* name: 'AI Agent',
|
|
393
|
+
* description: 'Usage-based AI agent billing',
|
|
394
|
+
* })
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
async createProduct(data) {
|
|
398
|
+
return this.client.request(
|
|
399
|
+
"POST",
|
|
400
|
+
"/metering/products",
|
|
401
|
+
{ body: data }
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Create a new meter on a product.
|
|
406
|
+
*
|
|
407
|
+
* @param productId - The product UUID.
|
|
408
|
+
* @param data - Meter data (name, displayName, unit, unitPrice).
|
|
409
|
+
* @returns The created meter object.
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* const meter = await pulse.metering.createMeter('product-id', {
|
|
414
|
+
* name: 'tokens',
|
|
415
|
+
* displayName: 'AI Tokens',
|
|
416
|
+
* unit: 'token',
|
|
417
|
+
* unitPrice: '0.0001',
|
|
418
|
+
* })
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
async createMeter(productId, data) {
|
|
422
|
+
return this.client.request(
|
|
423
|
+
"POST",
|
|
424
|
+
`/metering/products/${productId}/meters`,
|
|
425
|
+
{ body: data }
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Create a customer for a product.
|
|
430
|
+
*
|
|
431
|
+
* @param productId - The product UUID.
|
|
432
|
+
* @param data - Customer data (externalId, name, email, metadata).
|
|
433
|
+
* @returns The created customer object.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* ```typescript
|
|
437
|
+
* const customer = await pulse.metering.createCustomer('product-id', {
|
|
438
|
+
* externalId: 'user_123',
|
|
439
|
+
* name: 'John Doe',
|
|
440
|
+
* email: 'john@example.com',
|
|
441
|
+
* })
|
|
442
|
+
* ```
|
|
443
|
+
*/
|
|
444
|
+
async createCustomer(productId, data) {
|
|
445
|
+
return this.client.request(
|
|
446
|
+
"POST",
|
|
447
|
+
`/metering/products/${productId}/customers`,
|
|
448
|
+
{ body: data }
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get customer usage for a specific product.
|
|
453
|
+
*
|
|
454
|
+
* @param productId - The product UUID.
|
|
455
|
+
* @param customerId - The customer external ID.
|
|
456
|
+
* @param query - Optional date range filters.
|
|
457
|
+
* @returns Aggregated usage by meter for the customer.
|
|
458
|
+
*/
|
|
459
|
+
async getCustomerUsage(productId, customerId, query) {
|
|
460
|
+
return this.client.request(
|
|
461
|
+
"GET",
|
|
462
|
+
`/metering/products/${productId}/customers/${customerId}/usage`,
|
|
463
|
+
{
|
|
464
|
+
query: {
|
|
465
|
+
startDate: query?.startDate,
|
|
466
|
+
endDate: query?.endDate
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Create a session for tracking multiple events for a customer.
|
|
473
|
+
* Events are accumulated and sent as a batch when `.end()` is called.
|
|
474
|
+
*
|
|
475
|
+
* @param customerId - The customer external ID.
|
|
476
|
+
* @returns A session instance.
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* ```typescript
|
|
480
|
+
* const session = pulse.metering.session('user_123')
|
|
481
|
+
* session.track('tokens', 500)
|
|
482
|
+
* session.track('tokens', 300)
|
|
483
|
+
* session.track('requests', 1)
|
|
484
|
+
* await session.end() // sends batch: tokens=800, requests=1
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
session(customerId) {
|
|
488
|
+
return new MeteringSession(this, customerId);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
var MeteringSession = class {
|
|
492
|
+
constructor(metering, customerId) {
|
|
493
|
+
this.metering = metering;
|
|
494
|
+
this.customerId = customerId;
|
|
495
|
+
}
|
|
496
|
+
events = [];
|
|
497
|
+
/**
|
|
498
|
+
* Queue a tracking event in this session.
|
|
499
|
+
*/
|
|
500
|
+
track(meterId, value, metadata) {
|
|
501
|
+
this.events.push({
|
|
502
|
+
meterId,
|
|
503
|
+
customerId: this.customerId,
|
|
504
|
+
value,
|
|
505
|
+
metadata
|
|
506
|
+
});
|
|
507
|
+
return this;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Send all accumulated events as a batch and clear the session.
|
|
511
|
+
*/
|
|
512
|
+
async end() {
|
|
513
|
+
if (this.events.length === 0) {
|
|
514
|
+
return { accepted: 0, failed: 0, results: [] };
|
|
515
|
+
}
|
|
516
|
+
const result = await this.metering.trackBatch(this.events);
|
|
517
|
+
this.events = [];
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// src/webhooks/verify.ts
|
|
523
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
524
|
+
function verifyWebhookSignature(rawBody, signatureHeader, secret) {
|
|
525
|
+
const expectedSig = createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
526
|
+
const receivedSig = signatureHeader.startsWith("sha256=") ? signatureHeader.slice(7) : signatureHeader;
|
|
527
|
+
if (expectedSig.length !== receivedSig.length) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
return timingSafeEqual(
|
|
531
|
+
Buffer.from(expectedSig, "hex"),
|
|
532
|
+
Buffer.from(receivedSig, "hex")
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/checkout/checkout.ts
|
|
537
|
+
var DEFAULT_BASE_URL2 = "https://pulse.beinfi.com";
|
|
538
|
+
function mountCheckout(selector, options) {
|
|
539
|
+
const container = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
540
|
+
if (!container) {
|
|
541
|
+
throw new Error(`[Pulse Checkout] Container not found: ${selector}`);
|
|
542
|
+
}
|
|
543
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
544
|
+
const allowedOrigin = new URL(baseUrl).origin;
|
|
545
|
+
const handlers = {};
|
|
546
|
+
function emit(event, ...args) {
|
|
547
|
+
for (const handler of handlers[event] ?? []) {
|
|
548
|
+
handler(...args);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const iframe = document.createElement("iframe");
|
|
552
|
+
iframe.src = `${baseUrl}/embed/pay/${options.linkId}`;
|
|
553
|
+
iframe.style.width = "100%";
|
|
554
|
+
iframe.style.border = "none";
|
|
555
|
+
iframe.style.colorScheme = "normal";
|
|
556
|
+
iframe.setAttribute("allowtransparency", "true");
|
|
557
|
+
iframe.allow = "clipboard-write";
|
|
558
|
+
function handleMessage(event) {
|
|
559
|
+
if (event.origin !== allowedOrigin) return;
|
|
560
|
+
const { type, ...data } = event.data ?? {};
|
|
561
|
+
switch (type) {
|
|
562
|
+
case "pulse:ready":
|
|
563
|
+
if (options.theme) {
|
|
564
|
+
iframe.contentWindow?.postMessage(
|
|
565
|
+
{ type: "pulse:config", theme: options.theme },
|
|
566
|
+
allowedOrigin
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
options.onReady?.();
|
|
570
|
+
emit("ready");
|
|
571
|
+
break;
|
|
572
|
+
case "pulse:resize":
|
|
573
|
+
if (typeof data.height === "number") {
|
|
574
|
+
iframe.style.height = `${data.height}px`;
|
|
575
|
+
}
|
|
576
|
+
break;
|
|
577
|
+
case "pulse:success": {
|
|
578
|
+
const payment = data.payment;
|
|
579
|
+
options.onSuccess?.(payment);
|
|
580
|
+
emit("success", payment);
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
case "pulse:error": {
|
|
584
|
+
const error = data.error;
|
|
585
|
+
options.onError?.(error);
|
|
586
|
+
emit("error", error);
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
window.addEventListener("message", handleMessage);
|
|
592
|
+
container.appendChild(iframe);
|
|
593
|
+
return {
|
|
594
|
+
unmount() {
|
|
595
|
+
window.removeEventListener("message", handleMessage);
|
|
596
|
+
iframe.remove();
|
|
597
|
+
options.onClose?.();
|
|
598
|
+
emit("close");
|
|
599
|
+
},
|
|
600
|
+
on(event, handler) {
|
|
601
|
+
if (!handlers[event]) handlers[event] = [];
|
|
602
|
+
handlers[event].push(handler);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/index.ts
|
|
608
|
+
var Pulse = class {
|
|
609
|
+
/** Resource for creating, listing, and fetching payment links. */
|
|
610
|
+
paymentLinks;
|
|
611
|
+
/** Resource for creating, listing, and deleting webhook subscriptions. */
|
|
612
|
+
webhooks;
|
|
613
|
+
/** Resource for usage-based metering, tracking events, and querying usage. */
|
|
614
|
+
metering;
|
|
615
|
+
client;
|
|
616
|
+
/**
|
|
617
|
+
* Static utilities for verifying webhook signatures.
|
|
618
|
+
* Does not require a Pulse instance — useful in webhook handler endpoints.
|
|
619
|
+
*
|
|
620
|
+
* @example
|
|
621
|
+
* ```typescript
|
|
622
|
+
* const isValid = Pulse.webhooks.verifySignature(
|
|
623
|
+
* rawBody,
|
|
624
|
+
* req.headers['x-pulse-signature'],
|
|
625
|
+
* process.env.PULSE_WEBHOOK_SECRET
|
|
626
|
+
* )
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
static webhooks = {
|
|
630
|
+
verifySignature: verifyWebhookSignature
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* Static utilities for mounting the checkout widget.
|
|
634
|
+
* Browser-only — embeds an iframe-based checkout for a payment link.
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* ```typescript
|
|
638
|
+
* const instance = Pulse.checkout.mount('#checkout', {
|
|
639
|
+
* linkId: 'abc-123',
|
|
640
|
+
* onSuccess: (payment) => console.log('Paid!', payment),
|
|
641
|
+
* })
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
644
|
+
static checkout = {
|
|
645
|
+
mount: mountCheckout
|
|
646
|
+
};
|
|
647
|
+
/**
|
|
648
|
+
* Create a new Pulse SDK client.
|
|
649
|
+
*
|
|
650
|
+
* @param config - Either an API key string (`"sk_live_..."`) or a {@link PulseConfig} object.
|
|
651
|
+
* @throws {PulseError} If the API key doesn't start with `"sk_live_"`.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* ```typescript
|
|
655
|
+
* // String shorthand
|
|
656
|
+
* const pulse = new Pulse('sk_live_...')
|
|
657
|
+
*
|
|
658
|
+
* // Config object
|
|
659
|
+
* const pulse = new Pulse({ apiKey: 'sk_live_...', baseUrl: 'https://api.beinfi.com' })
|
|
660
|
+
* ```
|
|
661
|
+
*/
|
|
662
|
+
constructor(config) {
|
|
663
|
+
const apiKey = typeof config === "string" ? config : config.apiKey;
|
|
664
|
+
const baseUrl = typeof config === "string" ? void 0 : config.baseUrl;
|
|
665
|
+
this.client = new HttpClient(apiKey, baseUrl);
|
|
666
|
+
this.paymentLinks = new PaymentLinksResource(this.client);
|
|
667
|
+
this.webhooks = new WebhooksResource(this.client);
|
|
668
|
+
this.metering = new MeteringResource(this.client);
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
export {
|
|
672
|
+
Pulse,
|
|
673
|
+
PulseApiError,
|
|
674
|
+
PulseAuthenticationError,
|
|
675
|
+
PulseError,
|
|
676
|
+
PulseRateLimitError,
|
|
677
|
+
mountCheckout,
|
|
678
|
+
verifyWebhookSignature
|
|
679
|
+
};
|