@beinfi/n8n-nodes-pulse 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.
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
2
+ <rect width="48" height="48" rx="12" fill="#6366f1"/>
3
+ <path d="M8 24h8l4-12 8 24 4-12h8" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,13 @@
1
+ import type { IAuthenticateGeneric, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ /**
3
+ * Credential type for the Pulse Payment & Metering API.
4
+ *
5
+ * Uses Bearer token authentication with an API key (sk_live_...).
6
+ */
7
+ export declare class PulseApi implements ICredentialType {
8
+ name: string;
9
+ displayName: string;
10
+ documentationUrl: string;
11
+ properties: INodeProperties[];
12
+ authenticate: IAuthenticateGeneric;
13
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PulseApi = void 0;
4
+ /**
5
+ * Credential type for the Pulse Payment & Metering API.
6
+ *
7
+ * Uses Bearer token authentication with an API key (sk_live_...).
8
+ */
9
+ class PulseApi {
10
+ name = 'pulseApi';
11
+ displayName = 'Pulse API';
12
+ documentationUrl = 'https://docs.beinfi.com';
13
+ properties = [
14
+ {
15
+ displayName: 'API Key',
16
+ name: 'apiKey',
17
+ type: 'string',
18
+ typeOptions: { password: true },
19
+ default: '',
20
+ required: true,
21
+ placeholder: 'sk_live_...',
22
+ description: 'Your Pulse API key from the dashboard',
23
+ },
24
+ {
25
+ displayName: 'Base URL',
26
+ name: 'baseUrl',
27
+ type: 'string',
28
+ default: 'https://api.beinfi.com/api/v1',
29
+ description: 'Base URL for the Pulse API (change only for staging/self-hosted)',
30
+ },
31
+ ];
32
+ authenticate = {
33
+ type: 'generic',
34
+ properties: {
35
+ headers: {
36
+ Authorization: '=Bearer {{$credentials.apiKey}}',
37
+ },
38
+ },
39
+ };
40
+ }
41
+ exports.PulseApi = PulseApi;
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeType, INodeTypeDescription, INodeExecutionData } from 'n8n-workflow';
2
+ export declare class Pulse implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,637 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Pulse = void 0;
4
+ class Pulse {
5
+ description = {
6
+ displayName: 'Pulse',
7
+ name: 'pulse',
8
+ icon: 'file:pulse.svg',
9
+ group: ['transform'],
10
+ version: 1,
11
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
12
+ description: 'Interact with Pulse Payment & Metering API',
13
+ defaults: { name: 'Pulse' },
14
+ inputs: ['main'],
15
+ outputs: ['main'],
16
+ credentials: [
17
+ {
18
+ name: 'pulseApi',
19
+ required: true,
20
+ },
21
+ ],
22
+ properties: [
23
+ // ─── Resource ────────────────────────────────────────────
24
+ {
25
+ displayName: 'Resource',
26
+ name: 'resource',
27
+ type: 'options',
28
+ noDataExpression: true,
29
+ options: [
30
+ { name: 'Payment Link', value: 'paymentLink' },
31
+ { name: 'Webhook', value: 'webhook' },
32
+ { name: 'Metering', value: 'metering' },
33
+ ],
34
+ default: 'paymentLink',
35
+ },
36
+ // ─── Payment Link Operations ─────────────────────────────
37
+ {
38
+ displayName: 'Operation',
39
+ name: 'operation',
40
+ type: 'options',
41
+ noDataExpression: true,
42
+ displayOptions: { show: { resource: ['paymentLink'] } },
43
+ options: [
44
+ {
45
+ name: 'Create',
46
+ value: 'create',
47
+ action: 'Create a payment link',
48
+ description: 'Create a new payment link',
49
+ },
50
+ {
51
+ name: 'Get',
52
+ value: 'get',
53
+ action: 'Get a payment link',
54
+ description: 'Retrieve a payment link by ID',
55
+ },
56
+ {
57
+ name: 'List',
58
+ value: 'list',
59
+ action: 'List payment links',
60
+ description: 'List all payment links',
61
+ },
62
+ {
63
+ name: 'List Intents',
64
+ value: 'listIntents',
65
+ action: 'List payment intents',
66
+ description: 'List payment attempts for a link',
67
+ },
68
+ ],
69
+ default: 'create',
70
+ },
71
+ // Payment Link: Create fields
72
+ {
73
+ displayName: 'Title',
74
+ name: 'title',
75
+ type: 'string',
76
+ required: true,
77
+ default: '',
78
+ placeholder: 'Order #42',
79
+ displayOptions: { show: { resource: ['paymentLink'], operation: ['create'] } },
80
+ description: 'Display title shown to the payer',
81
+ },
82
+ {
83
+ displayName: 'Amount',
84
+ name: 'amount',
85
+ type: 'string',
86
+ required: true,
87
+ default: '',
88
+ placeholder: '99.90',
89
+ displayOptions: { show: { resource: ['paymentLink'], operation: ['create'] } },
90
+ description: 'Payment amount as decimal string (e.g. "99.90")',
91
+ },
92
+ {
93
+ displayName: 'Currency',
94
+ name: 'currency',
95
+ type: 'options',
96
+ options: [
97
+ { name: 'USD', value: 'USD' },
98
+ { name: 'BRL', value: 'BRL' },
99
+ ],
100
+ default: 'USD',
101
+ displayOptions: { show: { resource: ['paymentLink'], operation: ['create'] } },
102
+ description: 'Currency for the payment',
103
+ },
104
+ {
105
+ displayName: 'Description',
106
+ name: 'linkDescription',
107
+ type: 'string',
108
+ default: '',
109
+ displayOptions: { show: { resource: ['paymentLink'], operation: ['create'] } },
110
+ description: 'Optional description shown on the checkout page',
111
+ },
112
+ // Payment Link: Get / List Intents
113
+ {
114
+ displayName: 'Payment Link ID',
115
+ name: 'paymentLinkId',
116
+ type: 'string',
117
+ required: true,
118
+ default: '',
119
+ displayOptions: {
120
+ show: { resource: ['paymentLink'], operation: ['get', 'listIntents'] },
121
+ },
122
+ description: 'The payment link UUID',
123
+ },
124
+ // Payment Link: List options
125
+ {
126
+ displayName: 'Limit',
127
+ name: 'limit',
128
+ type: 'number',
129
+ default: 20,
130
+ typeOptions: { minValue: 1, maxValue: 100 },
131
+ displayOptions: { show: { resource: ['paymentLink'], operation: ['list'] } },
132
+ description: 'Max number of results to return',
133
+ },
134
+ {
135
+ displayName: 'Offset',
136
+ name: 'offset',
137
+ type: 'number',
138
+ default: 0,
139
+ typeOptions: { minValue: 0 },
140
+ displayOptions: { show: { resource: ['paymentLink'], operation: ['list'] } },
141
+ description: 'Number of results to skip (pagination)',
142
+ },
143
+ // ─── Webhook Operations ──────────────────────────────────
144
+ {
145
+ displayName: 'Operation',
146
+ name: 'operation',
147
+ type: 'options',
148
+ noDataExpression: true,
149
+ displayOptions: { show: { resource: ['webhook'] } },
150
+ options: [
151
+ {
152
+ name: 'Create',
153
+ value: 'create',
154
+ action: 'Create a webhook',
155
+ description: 'Subscribe to webhook events',
156
+ },
157
+ {
158
+ name: 'List',
159
+ value: 'list',
160
+ action: 'List webhooks',
161
+ description: 'List all webhook subscriptions',
162
+ },
163
+ {
164
+ name: 'Delete',
165
+ value: 'delete',
166
+ action: 'Delete a webhook',
167
+ description: 'Remove a webhook subscription',
168
+ },
169
+ ],
170
+ default: 'create',
171
+ },
172
+ // Webhook: Create fields
173
+ {
174
+ displayName: 'URL',
175
+ name: 'webhookUrl',
176
+ type: 'string',
177
+ required: true,
178
+ default: '',
179
+ placeholder: 'https://example.com/webhook',
180
+ displayOptions: { show: { resource: ['webhook'], operation: ['create'] } },
181
+ description: 'HTTPS endpoint that will receive webhook POSTs',
182
+ },
183
+ {
184
+ displayName: 'Events',
185
+ name: 'events',
186
+ type: 'multiOptions',
187
+ required: true,
188
+ options: [
189
+ { name: 'Payment Confirmed', value: 'payment.confirmed' },
190
+ { name: 'Payment Failed', value: 'payment.failed' },
191
+ { name: 'Payment Expired', value: 'payment.expired' },
192
+ ],
193
+ default: ['payment.confirmed'],
194
+ displayOptions: { show: { resource: ['webhook'], operation: ['create'] } },
195
+ description: 'Event types to subscribe to',
196
+ },
197
+ // Webhook: Delete
198
+ {
199
+ displayName: 'Webhook ID',
200
+ name: 'webhookId',
201
+ type: 'string',
202
+ required: true,
203
+ default: '',
204
+ displayOptions: { show: { resource: ['webhook'], operation: ['delete'] } },
205
+ description: 'The webhook UUID to delete',
206
+ },
207
+ // ─── Metering Operations ─────────────────────────────────
208
+ {
209
+ displayName: 'Operation',
210
+ name: 'operation',
211
+ type: 'options',
212
+ noDataExpression: true,
213
+ displayOptions: { show: { resource: ['metering'] } },
214
+ options: [
215
+ {
216
+ name: 'Track Event',
217
+ value: 'trackEvent',
218
+ action: 'Track a usage event',
219
+ description: 'Record a single usage event',
220
+ },
221
+ {
222
+ name: 'Track Batch',
223
+ value: 'trackBatch',
224
+ action: 'Track batch of usage events',
225
+ description: 'Record multiple usage events at once',
226
+ },
227
+ {
228
+ name: 'Get Usage',
229
+ value: 'getUsage',
230
+ action: 'Get aggregated usage',
231
+ description: 'Query aggregated usage data',
232
+ },
233
+ {
234
+ name: 'Get Customer Usage',
235
+ value: 'getCustomerUsage',
236
+ action: 'Get customer usage',
237
+ description: 'Query usage for a specific customer',
238
+ },
239
+ {
240
+ name: 'List Products',
241
+ value: 'listProducts',
242
+ action: 'List metering products',
243
+ description: 'List all metering products',
244
+ },
245
+ {
246
+ name: 'Create Product',
247
+ value: 'createProduct',
248
+ action: 'Create a metering product',
249
+ description: 'Create a new product for metering',
250
+ },
251
+ {
252
+ name: 'Create Meter',
253
+ value: 'createMeter',
254
+ action: 'Create a meter',
255
+ description: 'Add a meter to a product',
256
+ },
257
+ {
258
+ name: 'Create Customer',
259
+ value: 'createCustomer',
260
+ action: 'Create a customer',
261
+ description: 'Register a customer on a product',
262
+ },
263
+ ],
264
+ default: 'trackEvent',
265
+ },
266
+ // Metering: Track Event
267
+ {
268
+ displayName: 'Meter ID',
269
+ name: 'meterId',
270
+ type: 'string',
271
+ required: true,
272
+ default: '',
273
+ displayOptions: { show: { resource: ['metering'], operation: ['trackEvent'] } },
274
+ description: 'Meter ID or slug to track against',
275
+ },
276
+ {
277
+ displayName: 'Customer ID',
278
+ name: 'customerId',
279
+ type: 'string',
280
+ required: true,
281
+ default: '',
282
+ displayOptions: { show: { resource: ['metering'], operation: ['trackEvent'] } },
283
+ description: 'Your customer external ID',
284
+ },
285
+ {
286
+ displayName: 'Value',
287
+ name: 'value',
288
+ type: 'number',
289
+ required: true,
290
+ default: 1,
291
+ displayOptions: { show: { resource: ['metering'], operation: ['trackEvent'] } },
292
+ description: 'Quantity consumed (e.g. number of tokens)',
293
+ },
294
+ {
295
+ displayName: 'Event ID',
296
+ name: 'eventId',
297
+ type: 'string',
298
+ default: '',
299
+ displayOptions: { show: { resource: ['metering'], operation: ['trackEvent'] } },
300
+ description: 'Idempotency key (auto-generated if empty)',
301
+ },
302
+ // Metering: Track Batch
303
+ {
304
+ displayName: 'Events (JSON)',
305
+ name: 'batchEvents',
306
+ type: 'json',
307
+ required: true,
308
+ default: '[\n { "meterId": "", "customerId": "", "value": 1 }\n]',
309
+ displayOptions: { show: { resource: ['metering'], operation: ['trackBatch'] } },
310
+ description: 'JSON array of events. Each: { meterId, customerId, value, eventId?, metadata? }',
311
+ },
312
+ // Metering: Get Usage
313
+ {
314
+ displayName: 'Customer ID',
315
+ name: 'usageCustomerId',
316
+ type: 'string',
317
+ default: '',
318
+ displayOptions: { show: { resource: ['metering'], operation: ['getUsage'] } },
319
+ description: 'Filter usage by customer (optional)',
320
+ },
321
+ {
322
+ displayName: 'Start Date',
323
+ name: 'startDate',
324
+ type: 'string',
325
+ default: '',
326
+ placeholder: '2024-01-01',
327
+ displayOptions: {
328
+ show: {
329
+ resource: ['metering'],
330
+ operation: ['getUsage', 'getCustomerUsage'],
331
+ },
332
+ },
333
+ description: 'Start date filter (ISO 8601)',
334
+ },
335
+ {
336
+ displayName: 'End Date',
337
+ name: 'endDate',
338
+ type: 'string',
339
+ default: '',
340
+ placeholder: '2024-12-31',
341
+ displayOptions: {
342
+ show: {
343
+ resource: ['metering'],
344
+ operation: ['getUsage', 'getCustomerUsage'],
345
+ },
346
+ },
347
+ description: 'End date filter (ISO 8601)',
348
+ },
349
+ // Metering: Get Customer Usage / Create Meter / Create Customer — shared Product ID
350
+ {
351
+ displayName: 'Product ID',
352
+ name: 'productId',
353
+ type: 'string',
354
+ required: true,
355
+ default: '',
356
+ displayOptions: {
357
+ show: {
358
+ resource: ['metering'],
359
+ operation: ['getCustomerUsage', 'createMeter', 'createCustomer'],
360
+ },
361
+ },
362
+ description: 'The product UUID',
363
+ },
364
+ // Metering: Get Customer Usage
365
+ {
366
+ displayName: 'Customer ID',
367
+ name: 'customerUsageCustomerId',
368
+ type: 'string',
369
+ required: true,
370
+ default: '',
371
+ displayOptions: {
372
+ show: { resource: ['metering'], operation: ['getCustomerUsage'] },
373
+ },
374
+ description: 'The customer external ID',
375
+ },
376
+ // Metering: Create Product
377
+ {
378
+ displayName: 'Name',
379
+ name: 'productName',
380
+ type: 'string',
381
+ required: true,
382
+ default: '',
383
+ displayOptions: { show: { resource: ['metering'], operation: ['createProduct'] } },
384
+ description: 'Product name',
385
+ },
386
+ {
387
+ displayName: 'Description',
388
+ name: 'productDescription',
389
+ type: 'string',
390
+ default: '',
391
+ displayOptions: { show: { resource: ['metering'], operation: ['createProduct'] } },
392
+ description: 'Optional product description',
393
+ },
394
+ // Metering: Create Meter
395
+ {
396
+ displayName: 'Name (Slug)',
397
+ name: 'meterName',
398
+ type: 'string',
399
+ required: true,
400
+ default: '',
401
+ placeholder: 'tokens',
402
+ displayOptions: { show: { resource: ['metering'], operation: ['createMeter'] } },
403
+ description: 'Meter slug identifier',
404
+ },
405
+ {
406
+ displayName: 'Display Name',
407
+ name: 'meterDisplayName',
408
+ type: 'string',
409
+ required: true,
410
+ default: '',
411
+ placeholder: 'AI Tokens',
412
+ displayOptions: { show: { resource: ['metering'], operation: ['createMeter'] } },
413
+ description: 'Human-readable meter name',
414
+ },
415
+ {
416
+ displayName: 'Unit',
417
+ name: 'meterUnit',
418
+ type: 'string',
419
+ required: true,
420
+ default: '',
421
+ placeholder: 'token',
422
+ displayOptions: { show: { resource: ['metering'], operation: ['createMeter'] } },
423
+ description: 'Unit of measurement',
424
+ },
425
+ {
426
+ displayName: 'Unit Price',
427
+ name: 'meterUnitPrice',
428
+ type: 'string',
429
+ required: true,
430
+ default: '',
431
+ placeholder: '0.0001',
432
+ displayOptions: { show: { resource: ['metering'], operation: ['createMeter'] } },
433
+ description: 'Cost per unit as decimal string',
434
+ },
435
+ // Metering: Create Customer
436
+ {
437
+ displayName: 'External ID',
438
+ name: 'externalId',
439
+ type: 'string',
440
+ required: true,
441
+ default: '',
442
+ displayOptions: {
443
+ show: { resource: ['metering'], operation: ['createCustomer'] },
444
+ },
445
+ description: 'Your unique customer identifier',
446
+ },
447
+ {
448
+ displayName: 'Name',
449
+ name: 'customerName',
450
+ type: 'string',
451
+ default: '',
452
+ displayOptions: {
453
+ show: { resource: ['metering'], operation: ['createCustomer'] },
454
+ },
455
+ description: 'Customer display name',
456
+ },
457
+ {
458
+ displayName: 'Email',
459
+ name: 'customerEmail',
460
+ type: 'string',
461
+ default: '',
462
+ placeholder: 'customer@example.com',
463
+ displayOptions: {
464
+ show: { resource: ['metering'], operation: ['createCustomer'] },
465
+ },
466
+ description: 'Customer email address',
467
+ },
468
+ ],
469
+ };
470
+ async execute() {
471
+ const items = this.getInputData();
472
+ const returnData = [];
473
+ const resource = this.getNodeParameter('resource', 0);
474
+ const operation = this.getNodeParameter('operation', 0);
475
+ const credentials = await this.getCredentials('pulseApi');
476
+ const baseUrl = credentials.baseUrl || 'https://api.beinfi.com/api/v1';
477
+ for (let i = 0; i < items.length; i++) {
478
+ try {
479
+ let response;
480
+ if (resource === 'paymentLink') {
481
+ response = await executePaymentLink.call(this, operation, baseUrl, i);
482
+ }
483
+ else if (resource === 'webhook') {
484
+ response = await executeWebhook.call(this, operation, baseUrl, i);
485
+ }
486
+ else {
487
+ response = await executeMetering.call(this, operation, baseUrl, i);
488
+ }
489
+ // Unwrap Pulse API { data } envelope
490
+ const result = response.data ?? response;
491
+ const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(result), { itemData: { item: i } });
492
+ returnData.push(...executionData);
493
+ }
494
+ catch (error) {
495
+ if (this.continueOnFail()) {
496
+ returnData.push({
497
+ json: { error: error.message },
498
+ pairedItem: { item: i },
499
+ });
500
+ continue;
501
+ }
502
+ throw error;
503
+ }
504
+ }
505
+ return [returnData];
506
+ }
507
+ }
508
+ exports.Pulse = Pulse;
509
+ // ─── Payment Link Handlers ──────────────────────────────────────
510
+ async function executePaymentLink(operation, baseUrl, i) {
511
+ if (operation === 'create') {
512
+ const body = {
513
+ title: this.getNodeParameter('title', i),
514
+ amount: this.getNodeParameter('amount', i),
515
+ currency: this.getNodeParameter('currency', i),
516
+ };
517
+ const desc = this.getNodeParameter('linkDescription', i);
518
+ if (desc)
519
+ body.description = desc;
520
+ return makeRequest.call(this, 'POST', `${baseUrl}/payment-links`, body);
521
+ }
522
+ if (operation === 'get') {
523
+ const id = this.getNodeParameter('paymentLinkId', i);
524
+ return makeRequest.call(this, 'GET', `${baseUrl}/payment-links/${id}`);
525
+ }
526
+ if (operation === 'listIntents') {
527
+ const id = this.getNodeParameter('paymentLinkId', i);
528
+ return makeRequest.call(this, 'GET', `${baseUrl}/payment-links/${id}/intents`);
529
+ }
530
+ // list
531
+ const qs = {
532
+ limit: this.getNodeParameter('limit', i),
533
+ offset: this.getNodeParameter('offset', i),
534
+ };
535
+ return makeRequest.call(this, 'GET', `${baseUrl}/payment-links`, undefined, qs);
536
+ }
537
+ // ─── Webhook Handlers ───────────────────────────────────────────
538
+ async function executeWebhook(operation, baseUrl, i) {
539
+ if (operation === 'create') {
540
+ return makeRequest.call(this, 'POST', `${baseUrl}/webhooks`, {
541
+ url: this.getNodeParameter('webhookUrl', i),
542
+ events: this.getNodeParameter('events', i),
543
+ });
544
+ }
545
+ if (operation === 'delete') {
546
+ const id = this.getNodeParameter('webhookId', i);
547
+ return makeRequest.call(this, 'DELETE', `${baseUrl}/webhooks/${id}`);
548
+ }
549
+ // list
550
+ return makeRequest.call(this, 'GET', `${baseUrl}/webhooks`);
551
+ }
552
+ // ─── Metering Handlers ──────────────────────────────────────────
553
+ async function executeMetering(operation, baseUrl, i) {
554
+ if (operation === 'trackEvent') {
555
+ const body = {
556
+ meterId: this.getNodeParameter('meterId', i),
557
+ customerId: this.getNodeParameter('customerId', i),
558
+ value: String(this.getNodeParameter('value', i)),
559
+ };
560
+ const eventId = this.getNodeParameter('eventId', i);
561
+ if (eventId)
562
+ body.eventId = eventId;
563
+ return makeRequest.call(this, 'POST', `${baseUrl}/metering/events`, body);
564
+ }
565
+ if (operation === 'trackBatch') {
566
+ const raw = this.getNodeParameter('batchEvents', i);
567
+ const events = typeof raw === 'string' ? JSON.parse(raw) : raw;
568
+ return makeRequest.call(this, 'POST', `${baseUrl}/metering/events/batch`, { events });
569
+ }
570
+ if (operation === 'getUsage') {
571
+ const qs = {};
572
+ const cid = this.getNodeParameter('usageCustomerId', i);
573
+ const start = this.getNodeParameter('startDate', i);
574
+ const end = this.getNodeParameter('endDate', i);
575
+ if (cid)
576
+ qs.customerId = cid;
577
+ if (start)
578
+ qs.startDate = start;
579
+ if (end)
580
+ qs.endDate = end;
581
+ return makeRequest.call(this, 'GET', `${baseUrl}/metering/usage`, undefined, qs);
582
+ }
583
+ if (operation === 'getCustomerUsage') {
584
+ const productId = this.getNodeParameter('productId', i);
585
+ const customerId = this.getNodeParameter('customerUsageCustomerId', i);
586
+ const qs = {};
587
+ const start = this.getNodeParameter('startDate', i);
588
+ const end = this.getNodeParameter('endDate', i);
589
+ if (start)
590
+ qs.startDate = start;
591
+ if (end)
592
+ qs.endDate = end;
593
+ return makeRequest.call(this, 'GET', `${baseUrl}/metering/products/${productId}/customers/${customerId}/usage`, undefined, qs);
594
+ }
595
+ if (operation === 'listProducts') {
596
+ return makeRequest.call(this, 'GET', `${baseUrl}/metering/products`);
597
+ }
598
+ if (operation === 'createProduct') {
599
+ const body = {
600
+ name: this.getNodeParameter('productName', i),
601
+ };
602
+ const desc = this.getNodeParameter('productDescription', i);
603
+ if (desc)
604
+ body.description = desc;
605
+ return makeRequest.call(this, 'POST', `${baseUrl}/metering/products`, body);
606
+ }
607
+ if (operation === 'createMeter') {
608
+ const productId = this.getNodeParameter('productId', i);
609
+ return makeRequest.call(this, 'POST', `${baseUrl}/metering/products/${productId}/meters`, {
610
+ name: this.getNodeParameter('meterName', i),
611
+ displayName: this.getNodeParameter('meterDisplayName', i),
612
+ unit: this.getNodeParameter('meterUnit', i),
613
+ unitPrice: this.getNodeParameter('meterUnitPrice', i),
614
+ });
615
+ }
616
+ // createCustomer
617
+ const productId = this.getNodeParameter('productId', i);
618
+ const body = {
619
+ externalId: this.getNodeParameter('externalId', i),
620
+ };
621
+ const name = this.getNodeParameter('customerName', i);
622
+ const email = this.getNodeParameter('customerEmail', i);
623
+ if (name)
624
+ body.name = name;
625
+ if (email)
626
+ body.email = email;
627
+ return makeRequest.call(this, 'POST', `${baseUrl}/metering/products/${productId}/customers`, body);
628
+ }
629
+ // ─── HTTP Helper ────────────────────────────────────────────────
630
+ async function makeRequest(method, url, body, qs) {
631
+ const options = { method, url, json: true };
632
+ if (body)
633
+ options.body = body;
634
+ if (qs)
635
+ options.qs = qs;
636
+ return this.helpers.httpRequestWithAuthentication.call(this, 'pulseApi', options);
637
+ }
@@ -0,0 +1,12 @@
1
+ import type { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
2
+ export declare class PulseTrigger implements INodeType {
3
+ description: INodeTypeDescription;
4
+ webhookMethods: {
5
+ default: {
6
+ checkExists(this: IHookFunctions): Promise<boolean>;
7
+ create(this: IHookFunctions): Promise<boolean>;
8
+ delete(this: IHookFunctions): Promise<boolean>;
9
+ };
10
+ };
11
+ webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
12
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PulseTrigger = void 0;
4
+ const crypto_1 = require("crypto");
5
+ class PulseTrigger {
6
+ description = {
7
+ displayName: 'Pulse Trigger',
8
+ name: 'pulseTrigger',
9
+ icon: 'file:pulse.svg',
10
+ group: ['trigger'],
11
+ version: 1,
12
+ description: 'Starts workflow when a Pulse payment event occurs',
13
+ defaults: { name: 'Pulse Trigger' },
14
+ inputs: [],
15
+ outputs: ['main'],
16
+ credentials: [
17
+ {
18
+ name: 'pulseApi',
19
+ required: true,
20
+ },
21
+ ],
22
+ webhooks: [
23
+ {
24
+ name: 'default',
25
+ httpMethod: 'POST',
26
+ responseMode: 'onReceived',
27
+ path: 'pulse',
28
+ },
29
+ ],
30
+ properties: [
31
+ {
32
+ displayName: 'Events',
33
+ name: 'events',
34
+ type: 'multiOptions',
35
+ required: true,
36
+ options: [
37
+ { name: 'Payment Confirmed', value: 'payment.confirmed' },
38
+ { name: 'Payment Failed', value: 'payment.failed' },
39
+ { name: 'Payment Expired', value: 'payment.expired' },
40
+ ],
41
+ default: ['payment.confirmed'],
42
+ description: 'Which payment events should trigger this workflow',
43
+ },
44
+ ],
45
+ };
46
+ webhookMethods = {
47
+ default: {
48
+ async checkExists() {
49
+ const staticData = this.getWorkflowStaticData('node');
50
+ if (!staticData.webhookId)
51
+ return false;
52
+ // Verify the webhook still exists on Pulse
53
+ const credentials = await this.getCredentials('pulseApi');
54
+ const baseUrl = credentials.baseUrl || 'https://api.beinfi.com/api/v1';
55
+ try {
56
+ const response = (await this.helpers.httpRequestWithAuthentication.call(this, 'pulseApi', {
57
+ method: 'GET',
58
+ url: `${baseUrl}/webhooks`,
59
+ json: true,
60
+ }));
61
+ const webhooks = (response.data ?? response);
62
+ return Array.isArray(webhooks) &&
63
+ webhooks.some((wh) => wh.id === staticData.webhookId);
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ },
69
+ async create() {
70
+ const webhookUrl = this.getNodeWebhookUrl('default');
71
+ const events = this.getNodeParameter('events');
72
+ const credentials = await this.getCredentials('pulseApi');
73
+ const baseUrl = credentials.baseUrl || 'https://api.beinfi.com/api/v1';
74
+ const response = (await this.helpers.httpRequestWithAuthentication.call(this, 'pulseApi', {
75
+ method: 'POST',
76
+ url: `${baseUrl}/webhooks`,
77
+ body: { url: webhookUrl, events },
78
+ json: true,
79
+ }));
80
+ const data = (response.data ?? response);
81
+ const staticData = this.getWorkflowStaticData('node');
82
+ staticData.webhookId = data.id;
83
+ staticData.webhookSecret = data.secret;
84
+ return true;
85
+ },
86
+ async delete() {
87
+ const staticData = this.getWorkflowStaticData('node');
88
+ const webhookId = staticData.webhookId;
89
+ if (!webhookId)
90
+ return true;
91
+ const credentials = await this.getCredentials('pulseApi');
92
+ const baseUrl = credentials.baseUrl || 'https://api.beinfi.com/api/v1';
93
+ try {
94
+ await this.helpers.httpRequestWithAuthentication.call(this, 'pulseApi', {
95
+ method: 'DELETE',
96
+ url: `${baseUrl}/webhooks/${webhookId}`,
97
+ json: true,
98
+ });
99
+ }
100
+ catch {
101
+ // Webhook may already be deleted — ignore cleanup errors
102
+ }
103
+ delete staticData.webhookId;
104
+ delete staticData.webhookSecret;
105
+ return true;
106
+ },
107
+ },
108
+ };
109
+ async webhook() {
110
+ const req = this.getRequestObject();
111
+ const body = req.body;
112
+ // Verify HMAC signature if we have the secret from auto-registration
113
+ const staticData = this.getWorkflowStaticData('node');
114
+ const secret = staticData.webhookSecret;
115
+ if (secret) {
116
+ const signature = req.headers['x-pulse-signature'];
117
+ if (signature) {
118
+ const rawBody = req.rawBody?.toString() ??
119
+ JSON.stringify(body);
120
+ const expected = 'sha256=' +
121
+ (0, crypto_1.createHmac)('sha256', secret).update(rawBody).digest('hex');
122
+ if (signature !== expected) {
123
+ return {
124
+ webhookResponse: 'Invalid signature',
125
+ };
126
+ }
127
+ }
128
+ }
129
+ return {
130
+ workflowData: [this.helpers.returnJsonArray(body)],
131
+ };
132
+ }
133
+ }
134
+ exports.PulseTrigger = PulseTrigger;
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@beinfi/n8n-nodes-pulse",
3
+ "version": "0.1.0",
4
+ "description": "n8n community node for Pulse Payment & Metering API",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Be Infinitum"
8
+ },
9
+ "keywords": [
10
+ "n8n-community-node-package",
11
+ "n8n",
12
+ "pulse",
13
+ "payment",
14
+ "metering",
15
+ "billing"
16
+ ],
17
+ "main": "dist/nodes/Pulse/Pulse.node.js",
18
+ "n8n": {
19
+ "n8nNodesApiVersion": 1,
20
+ "credentials": [
21
+ "dist/credentials/PulseApi.credentials.js"
22
+ ],
23
+ "nodes": [
24
+ "dist/nodes/Pulse/Pulse.node.js",
25
+ "dist/nodes/Pulse/PulseTrigger.node.js"
26
+ ]
27
+ },
28
+ "scripts": {
29
+ "build": "tsc && copyfiles -u 1 \"nodes/**/*.svg\" dist/",
30
+ "dev": "tsc --watch",
31
+ "lint": "tsc --noEmit",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "devDependencies": {
38
+ "copyfiles": "^2.4.1",
39
+ "n8n-workflow": "^1.0.0",
40
+ "typescript": "^5.6.0"
41
+ },
42
+ "peerDependencies": {
43
+ "n8n-workflow": ">=1.0.0"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }