@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.
- package/dist/Pulse/pulse.svg +4 -0
- package/dist/credentials/PulseApi.credentials.d.ts +13 -0
- package/dist/credentials/PulseApi.credentials.js +41 -0
- package/dist/nodes/Pulse/Pulse.node.d.ts +5 -0
- package/dist/nodes/Pulse/Pulse.node.js +637 -0
- package/dist/nodes/Pulse/PulseTrigger.node.d.ts +12 -0
- package/dist/nodes/Pulse/PulseTrigger.node.js +134 -0
- package/package.json +48 -0
|
@@ -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,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
|
+
}
|