n8n-nodes-lemonsqueezy 0.2.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -0
  3. package/dist/credentials/LemonSqueezyApi.credentials.d.ts +10 -0
  4. package/dist/credentials/LemonSqueezyApi.credentials.js +41 -0
  5. package/dist/nodes/LemonSqueezy/LemonSqueezy.node.d.ts +5 -0
  6. package/dist/nodes/LemonSqueezy/LemonSqueezy.node.js +358 -0
  7. package/dist/nodes/LemonSqueezy/LemonSqueezyTrigger.node.d.ts +12 -0
  8. package/dist/nodes/LemonSqueezy/LemonSqueezyTrigger.node.js +230 -0
  9. package/dist/nodes/LemonSqueezy/constants.d.ts +89 -0
  10. package/dist/nodes/LemonSqueezy/constants.js +207 -0
  11. package/dist/nodes/LemonSqueezy/helpers.d.ts +28 -0
  12. package/dist/nodes/LemonSqueezy/helpers.js +241 -0
  13. package/dist/nodes/LemonSqueezy/lemonSqueezy.svg +20 -0
  14. package/dist/nodes/LemonSqueezy/resources/checkout.d.ts +3 -0
  15. package/dist/nodes/LemonSqueezy/resources/checkout.js +272 -0
  16. package/dist/nodes/LemonSqueezy/resources/customer.d.ts +3 -0
  17. package/dist/nodes/LemonSqueezy/resources/customer.js +242 -0
  18. package/dist/nodes/LemonSqueezy/resources/discount.d.ts +3 -0
  19. package/dist/nodes/LemonSqueezy/resources/discount.js +210 -0
  20. package/dist/nodes/LemonSqueezy/resources/index.d.ts +15 -0
  21. package/dist/nodes/LemonSqueezy/resources/index.js +76 -0
  22. package/dist/nodes/LemonSqueezy/resources/licenseKey.d.ts +3 -0
  23. package/dist/nodes/LemonSqueezy/resources/licenseKey.js +209 -0
  24. package/dist/nodes/LemonSqueezy/resources/order.d.ts +3 -0
  25. package/dist/nodes/LemonSqueezy/resources/order.js +113 -0
  26. package/dist/nodes/LemonSqueezy/resources/product.d.ts +3 -0
  27. package/dist/nodes/LemonSqueezy/resources/product.js +93 -0
  28. package/dist/nodes/LemonSqueezy/resources/store.d.ts +3 -0
  29. package/dist/nodes/LemonSqueezy/resources/store.js +64 -0
  30. package/dist/nodes/LemonSqueezy/resources/subscription.d.ts +3 -0
  31. package/dist/nodes/LemonSqueezy/resources/subscription.js +196 -0
  32. package/dist/nodes/LemonSqueezy/resources/variant.d.ts +3 -0
  33. package/dist/nodes/LemonSqueezy/resources/variant.js +96 -0
  34. package/dist/nodes/LemonSqueezy/resources/webhook.d.ts +3 -0
  35. package/dist/nodes/LemonSqueezy/resources/webhook.js +206 -0
  36. package/dist/nodes/LemonSqueezy/types.d.ts +364 -0
  37. package/dist/nodes/LemonSqueezy/types.js +2 -0
  38. package/package.json +71 -0
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.lemonSqueezyApiRequest = lemonSqueezyApiRequest;
37
+ exports.lemonSqueezyApiRequestAllItems = lemonSqueezyApiRequestAllItems;
38
+ exports.validateRequiredFields = validateRequiredFields;
39
+ exports.buildFilterParams = buildFilterParams;
40
+ exports.buildJsonApiBody = buildJsonApiBody;
41
+ exports.verifyWebhookSignature = verifyWebhookSignature;
42
+ const crypto = __importStar(require("crypto"));
43
+ const n8n_workflow_1 = require("n8n-workflow");
44
+ const constants_1 = require("./constants");
45
+ /**
46
+ * Sleep for a specified number of milliseconds
47
+ */
48
+ function sleep(ms) {
49
+ return new Promise((resolve) => setTimeout(resolve, ms));
50
+ }
51
+ /**
52
+ * Check if error is a rate limit error
53
+ */
54
+ function isRateLimitError(error) {
55
+ var _a;
56
+ if (error && typeof error === 'object') {
57
+ const err = error;
58
+ return err.statusCode === 429 || ((_a = err.response) === null || _a === void 0 ? void 0 : _a.statusCode) === 429;
59
+ }
60
+ return false;
61
+ }
62
+ /**
63
+ * Check if error is retryable (5xx errors or network errors)
64
+ */
65
+ function isRetryableError(error) {
66
+ var _a;
67
+ if (error && typeof error === 'object') {
68
+ const err = error;
69
+ const statusCode = err.statusCode || ((_a = err.response) === null || _a === void 0 ? void 0 : _a.statusCode);
70
+ if (statusCode && statusCode >= 500 && statusCode < 600) {
71
+ return true;
72
+ }
73
+ if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT' || err.code === 'ECONNREFUSED') {
74
+ return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+ /**
80
+ * Make an authenticated request to the Lemon Squeezy API with retry logic
81
+ */
82
+ async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}) {
83
+ const options = {
84
+ method,
85
+ url: `${constants_1.API_BASE_URL}${endpoint}`,
86
+ qs,
87
+ json: true,
88
+ };
89
+ if (body) {
90
+ options.body = body;
91
+ }
92
+ let lastError;
93
+ for (let attempt = 0; attempt < constants_1.MAX_RETRIES; attempt++) {
94
+ try {
95
+ return (await this.helpers.requestWithAuthentication.call(this, 'lemonSqueezyApi', options));
96
+ }
97
+ catch (error) {
98
+ lastError = error;
99
+ if (isRateLimitError(error)) {
100
+ // Wait for rate limit to reset (usually 60 seconds)
101
+ await sleep(constants_1.RATE_LIMIT_DELAY_MS);
102
+ continue;
103
+ }
104
+ if (isRetryableError(error) && attempt < constants_1.MAX_RETRIES - 1) {
105
+ // Exponential backoff for retryable errors
106
+ await sleep(constants_1.RETRY_DELAY_MS * Math.pow(2, attempt));
107
+ continue;
108
+ }
109
+ // Non-retryable error, throw immediately
110
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
111
+ message: getErrorMessage(error),
112
+ });
113
+ }
114
+ }
115
+ // All retries exhausted
116
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), lastError, {
117
+ message: getErrorMessage(lastError),
118
+ });
119
+ }
120
+ /**
121
+ * Make paginated requests to fetch all items
122
+ */
123
+ async function lemonSqueezyApiRequestAllItems(method, endpoint, qs = {}) {
124
+ var _a;
125
+ const returnData = [];
126
+ let nextPageUrl = `${constants_1.API_BASE_URL}${endpoint}`;
127
+ qs['page[size]'] = constants_1.DEFAULT_PAGE_SIZE;
128
+ do {
129
+ const options = {
130
+ method,
131
+ url: nextPageUrl,
132
+ qs: nextPageUrl.includes('?') ? {} : qs,
133
+ json: true,
134
+ };
135
+ let responseData;
136
+ try {
137
+ responseData = (await this.helpers.requestWithAuthentication.call(this, 'lemonSqueezyApi', options));
138
+ }
139
+ catch (error) {
140
+ if (isRateLimitError(error)) {
141
+ await sleep(constants_1.RATE_LIMIT_DELAY_MS);
142
+ continue;
143
+ }
144
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
145
+ message: getErrorMessage(error),
146
+ });
147
+ }
148
+ returnData.push(...responseData.data);
149
+ nextPageUrl = ((_a = responseData.links) === null || _a === void 0 ? void 0 : _a.next) || null;
150
+ } while (nextPageUrl);
151
+ return returnData;
152
+ }
153
+ /**
154
+ * Extract error message from error object
155
+ */
156
+ function getErrorMessage(error) {
157
+ var _a, _b, _c, _d, _e;
158
+ if (error && typeof error === 'object') {
159
+ const err = error;
160
+ // Check for JSON:API error format
161
+ if ((_c = (_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.errors) === null || _c === void 0 ? void 0 : _c[0]) {
162
+ const apiError = err.response.body.errors[0];
163
+ return apiError.detail || apiError.title || 'Unknown API error';
164
+ }
165
+ if ((_e = (_d = err.response) === null || _d === void 0 ? void 0 : _d.body) === null || _e === void 0 ? void 0 : _e.message) {
166
+ return err.response.body.message;
167
+ }
168
+ if (err.message) {
169
+ return err.message;
170
+ }
171
+ }
172
+ return 'An unknown error occurred';
173
+ }
174
+ /**
175
+ * Validate required fields before making API request
176
+ */
177
+ function validateRequiredFields(fields, requiredFields) {
178
+ const missingFields = [];
179
+ for (const field of requiredFields) {
180
+ if (fields[field] === undefined || fields[field] === null || fields[field] === '') {
181
+ missingFields.push(field);
182
+ }
183
+ }
184
+ if (missingFields.length > 0) {
185
+ throw new Error(`Missing required fields: ${missingFields.join(', ')}`);
186
+ }
187
+ }
188
+ /**
189
+ * Build filter query string parameters
190
+ */
191
+ function buildFilterParams(filters) {
192
+ const qs = {};
193
+ for (const [key, value] of Object.entries(filters)) {
194
+ if (value !== undefined && value !== null && value !== '') {
195
+ // Convert camelCase to snake_case for API
196
+ const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
197
+ qs[`filter[${snakeKey}]`] = value;
198
+ }
199
+ }
200
+ return qs;
201
+ }
202
+ /**
203
+ * Build JSON:API request body
204
+ */
205
+ function buildJsonApiBody(type, attributes, relationships, id) {
206
+ const body = {
207
+ data: {
208
+ type,
209
+ attributes,
210
+ },
211
+ };
212
+ if (id) {
213
+ body.data.id = id;
214
+ }
215
+ if (relationships) {
216
+ const relationshipsObj = {};
217
+ for (const [key, value] of Object.entries(relationships)) {
218
+ relationshipsObj[key] = {
219
+ data: {
220
+ type: value.type,
221
+ id: value.id,
222
+ },
223
+ };
224
+ }
225
+ body.data.relationships = relationshipsObj;
226
+ }
227
+ return body;
228
+ }
229
+ /**
230
+ * Parse webhook signature for validation
231
+ */
232
+ function verifyWebhookSignature(payload, signature, secret) {
233
+ const hmac = crypto.createHmac('sha256', secret);
234
+ const digest = hmac.update(payload).digest('hex');
235
+ try {
236
+ return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
237
+ }
238
+ catch {
239
+ return false;
240
+ }
241
+ }
@@ -0,0 +1,20 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
2
+ <defs>
3
+ <linearGradient id="lemonGradient" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#FFC233"/>
5
+ <stop offset="100%" style="stop-color:#F5A623"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <!-- Lemon body -->
9
+ <ellipse cx="32" cy="34" rx="22" ry="18" fill="url(#lemonGradient)"/>
10
+ <!-- Lemon tip -->
11
+ <path d="M54 34 Q62 34 58 28 Q56 32 54 34" fill="#F5A623"/>
12
+ <path d="M10 34 Q2 34 6 28 Q8 32 10 34" fill="#FFC233"/>
13
+ <!-- Leaf -->
14
+ <path d="M32 16 Q38 8 44 12 Q38 14 36 18 Q34 14 32 16" fill="#7CB342"/>
15
+ <path d="M32 16 Q26 8 20 12 Q26 14 28 18 Q30 14 32 16" fill="#8BC34A"/>
16
+ <!-- Stem -->
17
+ <rect x="30" y="14" width="4" height="6" rx="2" fill="#5D4037"/>
18
+ <!-- Highlight -->
19
+ <ellipse cx="24" cy="30" rx="6" ry="4" fill="rgba(255,255,255,0.3)" transform="rotate(-20 24 30)"/>
20
+ </svg>
@@ -0,0 +1,3 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ export declare const checkoutOperations: INodeProperties;
3
+ export declare const checkoutFields: INodeProperties[];
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkoutFields = exports.checkoutOperations = void 0;
4
+ exports.checkoutOperations = {
5
+ displayName: 'Operation',
6
+ name: 'operation',
7
+ type: 'options',
8
+ noDataExpression: true,
9
+ displayOptions: {
10
+ show: { resource: ['checkout'] },
11
+ },
12
+ options: [
13
+ {
14
+ name: 'Create',
15
+ value: 'create',
16
+ action: 'Create a checkout',
17
+ description: 'Create a new checkout link',
18
+ },
19
+ {
20
+ name: 'Get',
21
+ value: 'get',
22
+ action: 'Get a checkout',
23
+ description: 'Retrieve a single checkout by ID',
24
+ },
25
+ {
26
+ name: 'Get Many',
27
+ value: 'getAll',
28
+ action: 'Get many checkouts',
29
+ description: 'Retrieve multiple checkouts',
30
+ },
31
+ ],
32
+ default: 'create',
33
+ };
34
+ exports.checkoutFields = [
35
+ // Checkout ID for Get operation
36
+ {
37
+ displayName: 'Checkout ID',
38
+ name: 'checkoutId',
39
+ type: 'string',
40
+ required: true,
41
+ default: '',
42
+ description: 'The ID of the checkout to retrieve',
43
+ displayOptions: {
44
+ show: { resource: ['checkout'], operation: ['get'] },
45
+ },
46
+ },
47
+ // Create Fields
48
+ {
49
+ displayName: 'Store ID',
50
+ name: 'checkoutStoreId',
51
+ type: 'string',
52
+ required: true,
53
+ default: '',
54
+ description: 'The ID of the store',
55
+ displayOptions: {
56
+ show: { resource: ['checkout'], operation: ['create'] },
57
+ },
58
+ },
59
+ {
60
+ displayName: 'Variant ID',
61
+ name: 'checkoutVariantId',
62
+ type: 'string',
63
+ required: true,
64
+ default: '',
65
+ description: 'The ID of the variant to checkout',
66
+ displayOptions: {
67
+ show: { resource: ['checkout'], operation: ['create'] },
68
+ },
69
+ },
70
+ {
71
+ displayName: 'Additional Options',
72
+ name: 'additionalOptions',
73
+ type: 'collection',
74
+ placeholder: 'Add Option',
75
+ default: {},
76
+ displayOptions: {
77
+ show: { resource: ['checkout'], operation: ['create'] },
78
+ },
79
+ options: [
80
+ {
81
+ displayName: 'Custom Price',
82
+ name: 'customPrice',
83
+ type: 'number',
84
+ default: 0,
85
+ description: 'Custom price in cents (for pay-what-you-want products)',
86
+ },
87
+ {
88
+ displayName: 'Discount Code',
89
+ name: 'discountCode',
90
+ type: 'string',
91
+ default: '',
92
+ description: 'Discount code to apply to the checkout',
93
+ },
94
+ {
95
+ displayName: 'Customer Email',
96
+ name: 'email',
97
+ type: 'string',
98
+ default: '',
99
+ description: 'Pre-fill customer email',
100
+ },
101
+ {
102
+ displayName: 'Customer Name',
103
+ name: 'name',
104
+ type: 'string',
105
+ default: '',
106
+ description: 'Pre-fill customer name',
107
+ },
108
+ {
109
+ displayName: 'Redirect URL',
110
+ name: 'redirectUrl',
111
+ type: 'string',
112
+ default: '',
113
+ description: 'URL to redirect after successful purchase',
114
+ },
115
+ {
116
+ displayName: 'Receipt Button Text',
117
+ name: 'receiptButtonText',
118
+ type: 'string',
119
+ default: '',
120
+ description: 'Custom text for receipt button',
121
+ },
122
+ {
123
+ displayName: 'Receipt Link URL',
124
+ name: 'receiptLinkUrl',
125
+ type: 'string',
126
+ default: '',
127
+ description: 'Custom URL for receipt button',
128
+ },
129
+ {
130
+ displayName: 'Receipt Thank You Note',
131
+ name: 'receiptThankYouNote',
132
+ type: 'string',
133
+ default: '',
134
+ description: 'Custom thank you note on receipt',
135
+ },
136
+ {
137
+ displayName: 'Custom Data',
138
+ name: 'customData',
139
+ type: 'json',
140
+ default: '{}',
141
+ description: 'Custom data to attach to the checkout (will be passed to webhooks)',
142
+ },
143
+ {
144
+ displayName: 'Expires At',
145
+ name: 'expiresAt',
146
+ type: 'dateTime',
147
+ default: '',
148
+ description: 'When the checkout link expires (ISO 8601 format)',
149
+ },
150
+ {
151
+ displayName: 'Test Mode',
152
+ name: 'testMode',
153
+ type: 'boolean',
154
+ default: false,
155
+ description: 'Whether this is a test checkout',
156
+ },
157
+ ],
158
+ },
159
+ // Checkout Options
160
+ {
161
+ displayName: 'Checkout Display Options',
162
+ name: 'checkoutOptions',
163
+ type: 'collection',
164
+ placeholder: 'Add Display Option',
165
+ default: {},
166
+ displayOptions: {
167
+ show: { resource: ['checkout'], operation: ['create'] },
168
+ },
169
+ options: [
170
+ {
171
+ displayName: 'Dark Mode',
172
+ name: 'dark',
173
+ type: 'boolean',
174
+ default: false,
175
+ description: 'Whether to use dark mode',
176
+ },
177
+ {
178
+ displayName: 'Embed Mode',
179
+ name: 'embed',
180
+ type: 'boolean',
181
+ default: false,
182
+ description: 'Whether checkout is embedded',
183
+ },
184
+ {
185
+ displayName: 'Show Logo',
186
+ name: 'logo',
187
+ type: 'boolean',
188
+ default: true,
189
+ description: 'Whether to show the store logo',
190
+ },
191
+ {
192
+ displayName: 'Show Description',
193
+ name: 'desc',
194
+ type: 'boolean',
195
+ default: true,
196
+ description: 'Whether to show the product description',
197
+ },
198
+ {
199
+ displayName: 'Show Media',
200
+ name: 'media',
201
+ type: 'boolean',
202
+ default: true,
203
+ description: 'Whether to show product media',
204
+ },
205
+ {
206
+ displayName: 'Show Discount Field',
207
+ name: 'discount',
208
+ type: 'boolean',
209
+ default: true,
210
+ description: 'Whether to show the discount code field',
211
+ },
212
+ {
213
+ displayName: 'Button Color',
214
+ name: 'buttonColor',
215
+ type: 'string',
216
+ default: '',
217
+ description: 'Custom button color (hex code)',
218
+ placeholder: '#7c3aed',
219
+ },
220
+ ],
221
+ },
222
+ // Return All
223
+ {
224
+ displayName: 'Return All',
225
+ name: 'returnAll',
226
+ type: 'boolean',
227
+ default: false,
228
+ description: 'Whether to return all results or only up to a given limit',
229
+ displayOptions: {
230
+ show: { resource: ['checkout'], operation: ['getAll'] },
231
+ },
232
+ },
233
+ // Limit
234
+ {
235
+ displayName: 'Limit',
236
+ name: 'limit',
237
+ type: 'number',
238
+ default: 50,
239
+ description: 'Max number of results to return',
240
+ typeOptions: { minValue: 1 },
241
+ displayOptions: {
242
+ show: { resource: ['checkout'], operation: ['getAll'], returnAll: [false] },
243
+ },
244
+ },
245
+ // Filters
246
+ {
247
+ displayName: 'Filters',
248
+ name: 'filters',
249
+ type: 'collection',
250
+ placeholder: 'Add Filter',
251
+ default: {},
252
+ displayOptions: {
253
+ show: { resource: ['checkout'], operation: ['getAll'] },
254
+ },
255
+ options: [
256
+ {
257
+ displayName: 'Store ID',
258
+ name: 'storeId',
259
+ type: 'string',
260
+ default: '',
261
+ description: 'Filter by store ID',
262
+ },
263
+ {
264
+ displayName: 'Variant ID',
265
+ name: 'variantId',
266
+ type: 'string',
267
+ default: '',
268
+ description: 'Filter by variant ID',
269
+ },
270
+ ],
271
+ },
272
+ ];
@@ -0,0 +1,3 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ export declare const customerOperations: INodeProperties;
3
+ export declare const customerFields: INodeProperties[];