n8n-nodes-lemonsqueezy 0.7.1 → 0.8.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 +24 -18
- package/dist/nodes/LemonSqueezy/LemonSqueezy.node.js +44 -4
- package/dist/nodes/LemonSqueezy/constants.js +2 -0
- package/dist/nodes/LemonSqueezy/helpers.d.ts +17 -8
- package/dist/nodes/LemonSqueezy/helpers.js +43 -40
- package/dist/nodes/LemonSqueezy/resources/customer.js +6 -4
- package/dist/nodes/LemonSqueezy/resources/discount.js +92 -5
- package/dist/nodes/LemonSqueezy/resources/file.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/file.js +86 -0
- package/dist/nodes/LemonSqueezy/resources/index.d.ts +2 -1
- package/dist/nodes/LemonSqueezy/resources/index.js +7 -1
- package/dist/nodes/LemonSqueezy/types.d.ts +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,8 +55,9 @@ The main node for interacting with the Lemon Squeezy API.
|
|
|
55
55
|
|----------|------------|
|
|
56
56
|
| **Checkout** | Create, Get, Get Many |
|
|
57
57
|
| **Customer** | Create, Update, Delete, Get, Get Many |
|
|
58
|
-
| **Discount** | Create, Delete, Get, Get Many |
|
|
58
|
+
| **Discount** | Create, Update, Delete, Get, Get Many |
|
|
59
59
|
| **Discount Redemption** | Get, Get Many |
|
|
60
|
+
| **File** | Get, Get Many |
|
|
60
61
|
| **License Key** | Get, Get Many, Update, Validate, Activate, Deactivate |
|
|
61
62
|
| **License Key Instance** | Get, Get Many |
|
|
62
63
|
| **Order** | Get, Get Many, Refund |
|
|
@@ -204,10 +205,9 @@ The webhook trigger includes built-in security features:
|
|
|
204
205
|
|
|
205
206
|
The node includes built-in error handling with detailed messages:
|
|
206
207
|
|
|
207
|
-
- **Rate Limiting**: Automatically waits and retries when rate limited (429 errors)
|
|
208
|
-
- **Retry Logic**: Retries failed requests with exponential backoff for 5xx errors
|
|
209
208
|
- **Continue on Fail**: Enable to process remaining items even if some fail
|
|
210
209
|
- **Detailed Errors**: Field-level error details for validation failures
|
|
210
|
+
- **Workflow Retry**: Use n8n's built-in workflow error handling for retry logic
|
|
211
211
|
|
|
212
212
|
### Error Code Reference
|
|
213
213
|
|
|
@@ -245,20 +245,13 @@ The node includes built-in error handling with detailed messages:
|
|
|
245
245
|
|
|
246
246
|
### Rate Limiting Issues
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
If you encounter rate limiting (429 errors):
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
If you're hitting rate limits frequently:
|
|
257
|
-
|
|
258
|
-
1. Reduce the frequency of API calls
|
|
259
|
-
2. Use "Return All" sparingly for large datasets
|
|
260
|
-
3. Consider caching responses where appropriate
|
|
261
|
-
4. Space out bulk operations with delays
|
|
250
|
+
1. Configure n8n's workflow error handling to retry on failure
|
|
251
|
+
2. Reduce the frequency of API calls
|
|
252
|
+
3. Use "Return All" sparingly for large datasets
|
|
253
|
+
4. Consider caching responses where appropriate
|
|
254
|
+
5. Space out bulk operations using the Wait node
|
|
262
255
|
|
|
263
256
|
### Validation Errors
|
|
264
257
|
|
|
@@ -311,12 +304,25 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
311
304
|
|
|
312
305
|
## Changelog
|
|
313
306
|
|
|
314
|
-
### v0.
|
|
307
|
+
### v0.8.0
|
|
308
|
+
|
|
309
|
+
**New Features:**
|
|
310
|
+
- Added **File resource** for accessing product files (digital downloads)
|
|
311
|
+
- Added **Discount Update operation** for modifying existing discount codes
|
|
312
|
+
- Added discount amount validation (0-100 for percent, positive integer for fixed)
|
|
313
|
+
- Webhook URLs now require HTTPS (Lemon Squeezy requirement)
|
|
314
|
+
|
|
315
|
+
**Improvements:**
|
|
316
|
+
- Enhanced field descriptions with examples and placeholders
|
|
317
|
+
- Added API limit hints (max 100 per page) to limit fields
|
|
318
|
+
- 178 tests with comprehensive coverage
|
|
319
|
+
|
|
320
|
+
### v0.7.2
|
|
315
321
|
|
|
316
322
|
**n8n Community Package Compliance:**
|
|
317
323
|
- Resolved all n8n community package scanner ESLint violations
|
|
318
324
|
- Replaced deprecated `requestWithAuthentication` with `httpRequestWithAuthentication`
|
|
319
|
-
-
|
|
325
|
+
- Removed restricted globals (use n8n's built-in workflow retry for error handling)
|
|
320
326
|
|
|
321
327
|
### v0.7.0
|
|
322
328
|
|
|
@@ -22,6 +22,8 @@ async function handleCreate(ctx, resource, itemIndex) {
|
|
|
22
22
|
const amount = ctx.getNodeParameter('discountAmount', itemIndex);
|
|
23
23
|
const amountType = ctx.getNodeParameter('discountAmountType', itemIndex);
|
|
24
24
|
const additionalOptions = ctx.getNodeParameter('additionalOptions', itemIndex, {});
|
|
25
|
+
// Validate discount amount based on type
|
|
26
|
+
(0, helpers_1.validateDiscountAmount)(amount, amountType);
|
|
25
27
|
const attributes = {
|
|
26
28
|
name,
|
|
27
29
|
code,
|
|
@@ -169,8 +171,8 @@ async function handleCreate(ctx, resource, itemIndex) {
|
|
|
169
171
|
const events = ctx.getNodeParameter('webhookEvents', itemIndex);
|
|
170
172
|
const secret = ctx.getNodeParameter('webhookSecret', itemIndex);
|
|
171
173
|
const additionalOptions = ctx.getNodeParameter('additionalOptions', itemIndex, {});
|
|
172
|
-
// Validate URL before API call
|
|
173
|
-
(0, helpers_1.validateField)('url', url, '
|
|
174
|
+
// Validate URL before API call - Lemon Squeezy requires HTTPS for webhooks
|
|
175
|
+
(0, helpers_1.validateField)('url', url, 'httpsUrl');
|
|
174
176
|
// Validate webhook secret minimum length for security (32+ chars recommended)
|
|
175
177
|
if (secret.length < 32) {
|
|
176
178
|
throw new Error('Webhook secret must be at least 32 characters for security. Generate one using: openssl rand -hex 32');
|
|
@@ -263,8 +265,8 @@ async function handleUpdate(ctx, resource, itemIndex) {
|
|
|
263
265
|
const updateFields = ctx.getNodeParameter('updateFields', itemIndex);
|
|
264
266
|
const attributes = {};
|
|
265
267
|
if (updateFields.url) {
|
|
266
|
-
// Validate URL before API call
|
|
267
|
-
(0, helpers_1.validateField)('url', updateFields.url, '
|
|
268
|
+
// Validate URL before API call - Lemon Squeezy requires HTTPS for webhooks
|
|
269
|
+
(0, helpers_1.validateField)('url', updateFields.url, 'httpsUrl');
|
|
268
270
|
attributes.url = updateFields.url;
|
|
269
271
|
}
|
|
270
272
|
if (updateFields.events) {
|
|
@@ -280,6 +282,44 @@ async function handleUpdate(ctx, resource, itemIndex) {
|
|
|
280
282
|
const body = (0, helpers_1.buildJsonApiBody)('webhooks', attributes, undefined, webhookId);
|
|
281
283
|
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'PATCH', `/webhooks/${webhookId}`, body);
|
|
282
284
|
}
|
|
285
|
+
if (resource === 'discount') {
|
|
286
|
+
const discountId = ctx.getNodeParameter('discountId', itemIndex);
|
|
287
|
+
const updateFields = ctx.getNodeParameter('updateFields', itemIndex);
|
|
288
|
+
const attributes = {};
|
|
289
|
+
if (updateFields.name) {
|
|
290
|
+
attributes.name = updateFields.name;
|
|
291
|
+
}
|
|
292
|
+
if (updateFields.code) {
|
|
293
|
+
attributes.code = updateFields.code;
|
|
294
|
+
}
|
|
295
|
+
if (updateFields.amount !== undefined) {
|
|
296
|
+
// Validate discount amount if both amount and type are being updated
|
|
297
|
+
const amountType = updateFields.amountType || 'percent';
|
|
298
|
+
(0, helpers_1.validateDiscountAmount)(updateFields.amount, amountType);
|
|
299
|
+
attributes.amount = updateFields.amount;
|
|
300
|
+
}
|
|
301
|
+
if (updateFields.amountType) {
|
|
302
|
+
attributes.amount_type = updateFields.amountType;
|
|
303
|
+
}
|
|
304
|
+
if (updateFields.duration) {
|
|
305
|
+
attributes.duration = updateFields.duration;
|
|
306
|
+
}
|
|
307
|
+
if (updateFields.durationInMonths !== undefined) {
|
|
308
|
+
attributes.duration_in_months = updateFields.durationInMonths;
|
|
309
|
+
}
|
|
310
|
+
if (updateFields.maxRedemptions !== undefined) {
|
|
311
|
+
attributes.max_redemptions = updateFields.maxRedemptions;
|
|
312
|
+
attributes.is_limited_redemptions = updateFields.maxRedemptions > 0;
|
|
313
|
+
}
|
|
314
|
+
if (updateFields.startsAt) {
|
|
315
|
+
attributes.starts_at = updateFields.startsAt;
|
|
316
|
+
}
|
|
317
|
+
if (updateFields.expiresAt) {
|
|
318
|
+
attributes.expires_at = updateFields.expiresAt;
|
|
319
|
+
}
|
|
320
|
+
const body = (0, helpers_1.buildJsonApiBody)('discounts', attributes, undefined, discountId);
|
|
321
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'PATCH', `/discounts/${discountId}`, body);
|
|
322
|
+
}
|
|
283
323
|
throw new Error(`Update operation not supported for resource: ${resource}`);
|
|
284
324
|
}
|
|
285
325
|
class LemonSqueezy {
|
|
@@ -31,6 +31,7 @@ exports.RESOURCE_ENDPOINTS = {
|
|
|
31
31
|
webhook: 'webhooks',
|
|
32
32
|
usageRecord: 'usage-records',
|
|
33
33
|
user: 'users',
|
|
34
|
+
file: 'files',
|
|
34
35
|
};
|
|
35
36
|
/**
|
|
36
37
|
* Resource to ID parameter mapping
|
|
@@ -51,6 +52,7 @@ exports.RESOURCE_ID_PARAMS = {
|
|
|
51
52
|
checkout: 'checkoutId',
|
|
52
53
|
webhook: 'webhookId',
|
|
53
54
|
usageRecord: 'usageRecordId',
|
|
55
|
+
file: 'fileId',
|
|
54
56
|
};
|
|
55
57
|
/**
|
|
56
58
|
* Webhook event types with descriptions
|
|
@@ -41,6 +41,7 @@ export declare function isValidEmail(email: string): boolean;
|
|
|
41
41
|
* This prevents Server-Side Request Forgery (SSRF) attacks.
|
|
42
42
|
*
|
|
43
43
|
* @param url - The URL to validate
|
|
44
|
+
* @param requireHttps - If true, only HTTPS URLs are allowed (default: false)
|
|
44
45
|
* @returns True if the URL is valid and safe, false otherwise
|
|
45
46
|
*
|
|
46
47
|
* @example
|
|
@@ -48,8 +49,9 @@ export declare function isValidEmail(email: string): boolean;
|
|
|
48
49
|
* isValidUrl('http://localhost:3000') // false (internal)
|
|
49
50
|
* isValidUrl('ftp://files.example.com') // false (non-http protocol)
|
|
50
51
|
* isValidUrl('http://169.254.169.254') // false (AWS metadata)
|
|
52
|
+
* isValidUrl('http://example.com', true) // false (HTTPS required)
|
|
51
53
|
*/
|
|
52
|
-
export declare function isValidUrl(url: string): boolean;
|
|
54
|
+
export declare function isValidUrl(url: string, requireHttps?: boolean): boolean;
|
|
53
55
|
/**
|
|
54
56
|
* Validates ISO 8601 date format.
|
|
55
57
|
*
|
|
@@ -88,6 +90,7 @@ export declare function isPositiveInteger(value: unknown): boolean;
|
|
|
88
90
|
* - 'required': Ensures value is not empty/null/undefined
|
|
89
91
|
* - 'email': RFC 5322 compliant email validation
|
|
90
92
|
* - 'url': Safe URL validation with SSRF protection
|
|
93
|
+
* - 'httpsUrl': Safe URL validation requiring HTTPS (for webhooks)
|
|
91
94
|
* - 'date': ISO 8601 date format validation
|
|
92
95
|
* - 'positiveInteger': Positive integer validation
|
|
93
96
|
*
|
|
@@ -99,8 +102,9 @@ export declare function isPositiveInteger(value: unknown): boolean;
|
|
|
99
102
|
* @example
|
|
100
103
|
* validateField('email', 'user@example.com', 'email') // passes
|
|
101
104
|
* validateField('email', 'invalid', 'email') // throws "email must be a valid email address"
|
|
105
|
+
* validateField('webhookUrl', 'http://example.com', 'httpsUrl') // throws "webhookUrl must be a valid HTTPS URL"
|
|
102
106
|
*/
|
|
103
|
-
export declare function validateField(fieldName: string, value: unknown, validationType: 'email' | 'url' | 'date' | 'positiveInteger' | 'required'): void;
|
|
107
|
+
export declare function validateField(fieldName: string, value: unknown, validationType: 'email' | 'url' | 'httpsUrl' | 'date' | 'positiveInteger' | 'required'): void;
|
|
104
108
|
/**
|
|
105
109
|
* Safely parses a JSON string with descriptive error handling.
|
|
106
110
|
*
|
|
@@ -119,17 +123,22 @@ export declare function validateField(fieldName: string, value: unknown, validat
|
|
|
119
123
|
*/
|
|
120
124
|
export declare function safeJsonParse<T = unknown>(jsonString: string, fieldName: string): T;
|
|
121
125
|
/**
|
|
122
|
-
*
|
|
126
|
+
* Validates discount amount based on the amount type.
|
|
123
127
|
*
|
|
124
|
-
*
|
|
128
|
+
* - For 'percent' type: amount must be between 0 and 100 (inclusive)
|
|
129
|
+
* - For 'fixed' type: amount must be a positive integer (in cents)
|
|
125
130
|
*
|
|
126
|
-
* @param
|
|
127
|
-
* @
|
|
131
|
+
* @param amount - The discount amount to validate
|
|
132
|
+
* @param amountType - The type of discount ('percent' or 'fixed')
|
|
133
|
+
* @throws Error if the amount is invalid for the given type
|
|
128
134
|
*
|
|
129
135
|
* @example
|
|
130
|
-
*
|
|
136
|
+
* validateDiscountAmount(50, 'percent') // passes (50%)
|
|
137
|
+
* validateDiscountAmount(150, 'percent') // throws "Percent discount must be between 0 and 100"
|
|
138
|
+
* validateDiscountAmount(1000, 'fixed') // passes ($10.00 in cents)
|
|
139
|
+
* validateDiscountAmount(-100, 'fixed') // throws "Fixed discount amount must be a positive integer"
|
|
131
140
|
*/
|
|
132
|
-
export declare function
|
|
141
|
+
export declare function validateDiscountAmount(amount: number, amountType: string): void;
|
|
133
142
|
/**
|
|
134
143
|
* Checks if an error is a rate limit error (HTTP 429).
|
|
135
144
|
*
|
|
@@ -51,7 +51,7 @@ exports.isValidIsoDate = isValidIsoDate;
|
|
|
51
51
|
exports.isPositiveInteger = isPositiveInteger;
|
|
52
52
|
exports.validateField = validateField;
|
|
53
53
|
exports.safeJsonParse = safeJsonParse;
|
|
54
|
-
exports.
|
|
54
|
+
exports.validateDiscountAmount = validateDiscountAmount;
|
|
55
55
|
exports.isRateLimitError = isRateLimitError;
|
|
56
56
|
exports.isRetryableError = isRetryableError;
|
|
57
57
|
exports.lemonSqueezyApiRequest = lemonSqueezyApiRequest;
|
|
@@ -102,6 +102,7 @@ function isValidEmail(email) {
|
|
|
102
102
|
* This prevents Server-Side Request Forgery (SSRF) attacks.
|
|
103
103
|
*
|
|
104
104
|
* @param url - The URL to validate
|
|
105
|
+
* @param requireHttps - If true, only HTTPS URLs are allowed (default: false)
|
|
105
106
|
* @returns True if the URL is valid and safe, false otherwise
|
|
106
107
|
*
|
|
107
108
|
* @example
|
|
@@ -109,10 +110,15 @@ function isValidEmail(email) {
|
|
|
109
110
|
* isValidUrl('http://localhost:3000') // false (internal)
|
|
110
111
|
* isValidUrl('ftp://files.example.com') // false (non-http protocol)
|
|
111
112
|
* isValidUrl('http://169.254.169.254') // false (AWS metadata)
|
|
113
|
+
* isValidUrl('http://example.com', true) // false (HTTPS required)
|
|
112
114
|
*/
|
|
113
|
-
function isValidUrl(url) {
|
|
115
|
+
function isValidUrl(url, requireHttps = false) {
|
|
114
116
|
try {
|
|
115
117
|
const parsedUrl = new URL(url);
|
|
118
|
+
// If HTTPS is required, reject HTTP URLs
|
|
119
|
+
if (requireHttps && parsedUrl.protocol !== 'https:') {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
116
122
|
// Only allow http and https protocols (security: prevent file://, javascript:, etc.)
|
|
117
123
|
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
|
118
124
|
return false;
|
|
@@ -200,6 +206,7 @@ function isPositiveInteger(value) {
|
|
|
200
206
|
* - 'required': Ensures value is not empty/null/undefined
|
|
201
207
|
* - 'email': RFC 5322 compliant email validation
|
|
202
208
|
* - 'url': Safe URL validation with SSRF protection
|
|
209
|
+
* - 'httpsUrl': Safe URL validation requiring HTTPS (for webhooks)
|
|
203
210
|
* - 'date': ISO 8601 date format validation
|
|
204
211
|
* - 'positiveInteger': Positive integer validation
|
|
205
212
|
*
|
|
@@ -211,6 +218,7 @@ function isPositiveInteger(value) {
|
|
|
211
218
|
* @example
|
|
212
219
|
* validateField('email', 'user@example.com', 'email') // passes
|
|
213
220
|
* validateField('email', 'invalid', 'email') // throws "email must be a valid email address"
|
|
221
|
+
* validateField('webhookUrl', 'http://example.com', 'httpsUrl') // throws "webhookUrl must be a valid HTTPS URL"
|
|
214
222
|
*/
|
|
215
223
|
function validateField(fieldName, value, validationType) {
|
|
216
224
|
if (validationType === 'required') {
|
|
@@ -234,6 +242,11 @@ function validateField(fieldName, value, validationType) {
|
|
|
234
242
|
throw new Error(`${fieldName} must be a valid URL`);
|
|
235
243
|
}
|
|
236
244
|
break;
|
|
245
|
+
case 'httpsUrl':
|
|
246
|
+
if (typeof value !== 'string' || !isValidUrl(value, true)) {
|
|
247
|
+
throw new Error(`${fieldName} must be a valid HTTPS URL (Lemon Squeezy requires HTTPS for webhooks)`);
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
237
250
|
case 'date':
|
|
238
251
|
if (typeof value !== 'string' || !isValidIsoDate(value)) {
|
|
239
252
|
throw new Error(`${fieldName} must be a valid ISO 8601 date`);
|
|
@@ -270,21 +283,33 @@ function safeJsonParse(jsonString, fieldName) {
|
|
|
270
283
|
throw new Error(`${fieldName} contains invalid JSON`);
|
|
271
284
|
}
|
|
272
285
|
}
|
|
273
|
-
// Reference to avoid direct global usage (n8n linter restriction)
|
|
274
|
-
const setTimeoutRef = globalThis.setTimeout;
|
|
275
286
|
/**
|
|
276
|
-
*
|
|
287
|
+
* Validates discount amount based on the amount type.
|
|
277
288
|
*
|
|
278
|
-
*
|
|
289
|
+
* - For 'percent' type: amount must be between 0 and 100 (inclusive)
|
|
290
|
+
* - For 'fixed' type: amount must be a positive integer (in cents)
|
|
279
291
|
*
|
|
280
|
-
* @param
|
|
281
|
-
* @
|
|
292
|
+
* @param amount - The discount amount to validate
|
|
293
|
+
* @param amountType - The type of discount ('percent' or 'fixed')
|
|
294
|
+
* @throws Error if the amount is invalid for the given type
|
|
282
295
|
*
|
|
283
296
|
* @example
|
|
284
|
-
*
|
|
297
|
+
* validateDiscountAmount(50, 'percent') // passes (50%)
|
|
298
|
+
* validateDiscountAmount(150, 'percent') // throws "Percent discount must be between 0 and 100"
|
|
299
|
+
* validateDiscountAmount(1000, 'fixed') // passes ($10.00 in cents)
|
|
300
|
+
* validateDiscountAmount(-100, 'fixed') // throws "Fixed discount amount must be a positive integer"
|
|
285
301
|
*/
|
|
286
|
-
function
|
|
287
|
-
|
|
302
|
+
function validateDiscountAmount(amount, amountType) {
|
|
303
|
+
if (amountType === 'percent') {
|
|
304
|
+
if (amount < 0 || amount > 100) {
|
|
305
|
+
throw new Error('Percent discount must be between 0 and 100');
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else if (amountType === 'fixed') {
|
|
309
|
+
if (!Number.isInteger(amount) || amount < 0) {
|
|
310
|
+
throw new Error('Fixed discount amount must be a positive integer (in cents)');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
288
313
|
}
|
|
289
314
|
/**
|
|
290
315
|
* Checks if an error is a rate limit error (HTTP 429).
|
|
@@ -378,32 +403,14 @@ async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}, timeout =
|
|
|
378
403
|
if (body) {
|
|
379
404
|
options.body = body;
|
|
380
405
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (isRateLimitError(error)) {
|
|
389
|
-
await sleep(constants_1.RATE_LIMIT_DELAY_MS);
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (isRetryableError(error) && attempt < constants_1.MAX_RETRIES - 1) {
|
|
393
|
-
const delayMs = constants_1.RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
394
|
-
await sleep(delayMs);
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
// Non-retryable error, throw immediately
|
|
398
|
-
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
|
|
399
|
-
message: getErrorMessage(error),
|
|
400
|
-
});
|
|
401
|
-
}
|
|
406
|
+
try {
|
|
407
|
+
return (await this.helpers.httpRequestWithAuthentication.call(this, 'lemonSqueezyApi', options));
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
|
|
411
|
+
message: getErrorMessage(error),
|
|
412
|
+
});
|
|
402
413
|
}
|
|
403
|
-
// All retries exhausted
|
|
404
|
-
throw new n8n_workflow_1.NodeApiError(this.getNode(), lastError, {
|
|
405
|
-
message: getErrorMessage(lastError),
|
|
406
|
-
});
|
|
407
414
|
}
|
|
408
415
|
/**
|
|
409
416
|
* Makes paginated requests to fetch all items from a Lemon Squeezy API endpoint.
|
|
@@ -460,10 +467,6 @@ async function lemonSqueezyApiRequestAllItems(method, endpoint, qs = {}, paginat
|
|
|
460
467
|
responseData = (await this.helpers.httpRequestWithAuthentication.call(this, 'lemonSqueezyApi', options));
|
|
461
468
|
}
|
|
462
469
|
catch (error) {
|
|
463
|
-
if (isRateLimitError(error)) {
|
|
464
|
-
await sleep(constants_1.RATE_LIMIT_DELAY_MS);
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
470
|
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
|
|
468
471
|
message: getErrorMessage(error),
|
|
469
472
|
});
|
|
@@ -52,7 +52,8 @@ exports.customerFields = [
|
|
|
52
52
|
type: 'string',
|
|
53
53
|
required: true,
|
|
54
54
|
default: '',
|
|
55
|
-
|
|
55
|
+
placeholder: 'e.g., 12345',
|
|
56
|
+
description: 'The ID of the customer (numeric string)',
|
|
56
57
|
displayOptions: {
|
|
57
58
|
show: { resource: ['customer'], operation: ['get', 'update', 'delete'] },
|
|
58
59
|
},
|
|
@@ -64,7 +65,8 @@ exports.customerFields = [
|
|
|
64
65
|
type: 'string',
|
|
65
66
|
required: true,
|
|
66
67
|
default: '',
|
|
67
|
-
|
|
68
|
+
placeholder: 'e.g., 12345',
|
|
69
|
+
description: 'The ID of the store this customer belongs to. Find this in your <a href="https://app.lemonsqueezy.com/settings/stores" target="_blank">Lemon Squeezy Dashboard</a>.',
|
|
68
70
|
displayOptions: {
|
|
69
71
|
show: { resource: ['customer'], operation: ['create'] },
|
|
70
72
|
},
|
|
@@ -198,8 +200,8 @@ exports.customerFields = [
|
|
|
198
200
|
name: 'limit',
|
|
199
201
|
type: 'number',
|
|
200
202
|
default: 50,
|
|
201
|
-
description: 'Max number of results to return',
|
|
202
|
-
typeOptions: { minValue: 1 },
|
|
203
|
+
description: 'Max number of results to return (API maximum is 100 per page)',
|
|
204
|
+
typeOptions: { minValue: 1, maxValue: 100 },
|
|
203
205
|
displayOptions: {
|
|
204
206
|
show: { resource: ['customer'], operation: ['getAll'], returnAll: [false] },
|
|
205
207
|
},
|
|
@@ -35,20 +35,27 @@ exports.discountOperations = {
|
|
|
35
35
|
action: 'Get many discounts',
|
|
36
36
|
description: 'Retrieve multiple discounts',
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Update',
|
|
40
|
+
value: 'update',
|
|
41
|
+
action: 'Update a discount',
|
|
42
|
+
description: 'Update an existing discount code',
|
|
43
|
+
},
|
|
38
44
|
],
|
|
39
45
|
default: 'getAll',
|
|
40
46
|
};
|
|
41
47
|
exports.discountFields = [
|
|
42
|
-
// Discount ID for Get/Delete operations
|
|
48
|
+
// Discount ID for Get/Update/Delete operations
|
|
43
49
|
{
|
|
44
50
|
displayName: 'Discount ID',
|
|
45
51
|
name: 'discountId',
|
|
46
52
|
type: 'string',
|
|
47
53
|
required: true,
|
|
48
54
|
default: '',
|
|
49
|
-
|
|
55
|
+
placeholder: 'e.g., 12345',
|
|
56
|
+
description: 'The ID of the discount (numeric string)',
|
|
50
57
|
displayOptions: {
|
|
51
|
-
show: { resource: ['discount'], operation: ['get', 'delete'] },
|
|
58
|
+
show: { resource: ['discount'], operation: ['get', 'update', 'delete'] },
|
|
52
59
|
},
|
|
53
60
|
},
|
|
54
61
|
// Create Fields
|
|
@@ -164,6 +171,86 @@ exports.discountFields = [
|
|
|
164
171
|
},
|
|
165
172
|
],
|
|
166
173
|
},
|
|
174
|
+
// Update Fields
|
|
175
|
+
{
|
|
176
|
+
displayName: 'Update Fields',
|
|
177
|
+
name: 'updateFields',
|
|
178
|
+
type: 'collection',
|
|
179
|
+
placeholder: 'Add Field',
|
|
180
|
+
default: {},
|
|
181
|
+
displayOptions: {
|
|
182
|
+
show: { resource: ['discount'], operation: ['update'] },
|
|
183
|
+
},
|
|
184
|
+
options: [
|
|
185
|
+
{
|
|
186
|
+
displayName: 'Name',
|
|
187
|
+
name: 'name',
|
|
188
|
+
type: 'string',
|
|
189
|
+
default: '',
|
|
190
|
+
description: 'Internal name for the discount (not visible to customers)',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
displayName: 'Code',
|
|
194
|
+
name: 'code',
|
|
195
|
+
type: 'string',
|
|
196
|
+
default: '',
|
|
197
|
+
description: 'The discount code customers will use at checkout',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
displayName: 'Amount',
|
|
201
|
+
name: 'amount',
|
|
202
|
+
type: 'number',
|
|
203
|
+
default: 0,
|
|
204
|
+
description: 'Discount amount (percentage 0-100 for percent type, or fixed amount in cents for fixed type)',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
displayName: 'Amount Type',
|
|
208
|
+
name: 'amountType',
|
|
209
|
+
type: 'options',
|
|
210
|
+
options: constants_1.DISCOUNT_AMOUNT_TYPES,
|
|
211
|
+
default: 'percent',
|
|
212
|
+
description: 'Whether the discount is a percentage or fixed amount',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
displayName: 'Duration',
|
|
216
|
+
name: 'duration',
|
|
217
|
+
type: 'options',
|
|
218
|
+
options: constants_1.DISCOUNT_DURATION_TYPES,
|
|
219
|
+
default: 'once',
|
|
220
|
+
description: 'How long the discount should apply for subscriptions',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
displayName: 'Duration In Months',
|
|
224
|
+
name: 'durationInMonths',
|
|
225
|
+
type: 'number',
|
|
226
|
+
default: 1,
|
|
227
|
+
description: 'Number of months the discount applies (only for "repeating" duration)',
|
|
228
|
+
typeOptions: { minValue: 1 },
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
displayName: 'Max Redemptions',
|
|
232
|
+
name: 'maxRedemptions',
|
|
233
|
+
type: 'number',
|
|
234
|
+
default: 0,
|
|
235
|
+
description: 'Maximum number of times this discount can be used (0 for unlimited)',
|
|
236
|
+
typeOptions: { minValue: 0 },
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
displayName: 'Starts At',
|
|
240
|
+
name: 'startsAt',
|
|
241
|
+
type: 'dateTime',
|
|
242
|
+
default: '',
|
|
243
|
+
description: 'When the discount becomes active (ISO 8601 format, e.g., 2024-01-15T10:30:00Z)',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
displayName: 'Expires At',
|
|
247
|
+
name: 'expiresAt',
|
|
248
|
+
type: 'dateTime',
|
|
249
|
+
default: '',
|
|
250
|
+
description: 'When the discount expires (ISO 8601 format, e.g., 2024-12-31T23:59:59Z)',
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
167
254
|
// Return All
|
|
168
255
|
{
|
|
169
256
|
displayName: 'Return All',
|
|
@@ -181,8 +268,8 @@ exports.discountFields = [
|
|
|
181
268
|
name: 'limit',
|
|
182
269
|
type: 'number',
|
|
183
270
|
default: 50,
|
|
184
|
-
description: 'Max number of results to return',
|
|
185
|
-
typeOptions: { minValue: 1 },
|
|
271
|
+
description: 'Max number of results to return (API maximum is 100 per page)',
|
|
272
|
+
typeOptions: { minValue: 1, maxValue: 100 },
|
|
186
273
|
displayOptions: {
|
|
187
274
|
show: { resource: ['discount'], operation: ['getAll'], returnAll: [false] },
|
|
188
275
|
},
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fileFields = exports.fileOperations = void 0;
|
|
4
|
+
exports.fileOperations = {
|
|
5
|
+
displayName: 'Operation',
|
|
6
|
+
name: 'operation',
|
|
7
|
+
type: 'options',
|
|
8
|
+
noDataExpression: true,
|
|
9
|
+
displayOptions: {
|
|
10
|
+
show: { resource: ['file'] },
|
|
11
|
+
},
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
name: 'Get',
|
|
15
|
+
value: 'get',
|
|
16
|
+
action: 'Get a file',
|
|
17
|
+
description: 'Retrieve a single file by ID',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'Get Many',
|
|
21
|
+
value: 'getAll',
|
|
22
|
+
action: 'Get many files',
|
|
23
|
+
description: 'Retrieve multiple files',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
default: 'getAll',
|
|
27
|
+
};
|
|
28
|
+
exports.fileFields = [
|
|
29
|
+
// File ID for Get operation
|
|
30
|
+
{
|
|
31
|
+
displayName: 'File ID',
|
|
32
|
+
name: 'fileId',
|
|
33
|
+
type: 'string',
|
|
34
|
+
required: true,
|
|
35
|
+
default: '',
|
|
36
|
+
placeholder: 'e.g., 12345',
|
|
37
|
+
description: 'The ID of the file (numeric string)',
|
|
38
|
+
displayOptions: {
|
|
39
|
+
show: { resource: ['file'], operation: ['get'] },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
// Return All
|
|
43
|
+
{
|
|
44
|
+
displayName: 'Return All',
|
|
45
|
+
name: 'returnAll',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
default: false,
|
|
48
|
+
description: 'Whether to return all results or only up to a given limit',
|
|
49
|
+
displayOptions: {
|
|
50
|
+
show: { resource: ['file'], operation: ['getAll'] },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
// Limit
|
|
54
|
+
{
|
|
55
|
+
displayName: 'Limit',
|
|
56
|
+
name: 'limit',
|
|
57
|
+
type: 'number',
|
|
58
|
+
default: 50,
|
|
59
|
+
description: 'Max number of results to return (API maximum is 100 per page)',
|
|
60
|
+
typeOptions: { minValue: 1, maxValue: 100 },
|
|
61
|
+
displayOptions: {
|
|
62
|
+
show: { resource: ['file'], operation: ['getAll'], returnAll: [false] },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
// Filters
|
|
66
|
+
{
|
|
67
|
+
displayName: 'Filters',
|
|
68
|
+
name: 'filters',
|
|
69
|
+
type: 'collection',
|
|
70
|
+
placeholder: 'Add Filter',
|
|
71
|
+
default: {},
|
|
72
|
+
displayOptions: {
|
|
73
|
+
show: { resource: ['file'], operation: ['getAll'] },
|
|
74
|
+
},
|
|
75
|
+
options: [
|
|
76
|
+
{
|
|
77
|
+
displayName: 'Variant ID',
|
|
78
|
+
name: 'variantId',
|
|
79
|
+
type: 'string',
|
|
80
|
+
default: '',
|
|
81
|
+
placeholder: 'e.g., 12345',
|
|
82
|
+
description: 'Filter files by variant ID',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
];
|
|
@@ -15,7 +15,8 @@ import { checkoutOperations, checkoutFields } from './checkout';
|
|
|
15
15
|
import { webhookOperations, webhookFields } from './webhook';
|
|
16
16
|
import { usageRecordOperations, usageRecordFields } from './usageRecord';
|
|
17
17
|
import { userOperations, userFields } from './user';
|
|
18
|
+
import { fileOperations, fileFields } from './file';
|
|
18
19
|
export declare const resourceProperty: INodeProperties;
|
|
19
20
|
export declare const allOperations: INodeProperties[];
|
|
20
21
|
export declare const allFields: INodeProperties[];
|
|
21
|
-
export { productOperations, productFields, orderOperations, orderFields, orderItemOperations, orderItemFields, subscriptionOperations, subscriptionFields, subscriptionInvoiceOperations, subscriptionInvoiceFields, customerOperations, customerFields, licenseKeyOperations, licenseKeyFields, licenseKeyInstanceOperations, licenseKeyInstanceFields, discountOperations, discountFields, discountRedemptionOperations, discountRedemptionFields, storeOperations, storeFields, variantOperations, variantFields, checkoutOperations, checkoutFields, webhookOperations, webhookFields, usageRecordOperations, usageRecordFields, userOperations, userFields, };
|
|
22
|
+
export { productOperations, productFields, orderOperations, orderFields, orderItemOperations, orderItemFields, subscriptionOperations, subscriptionFields, subscriptionInvoiceOperations, subscriptionInvoiceFields, customerOperations, customerFields, licenseKeyOperations, licenseKeyFields, licenseKeyInstanceOperations, licenseKeyInstanceFields, discountOperations, discountFields, discountRedemptionOperations, discountRedemptionFields, storeOperations, storeFields, variantOperations, variantFields, checkoutOperations, checkoutFields, webhookOperations, webhookFields, usageRecordOperations, usageRecordFields, userOperations, userFields, fileOperations, fileFields, };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.userFields = exports.userOperations = exports.usageRecordFields = exports.usageRecordOperations = exports.webhookFields = exports.webhookOperations = exports.checkoutFields = exports.checkoutOperations = exports.variantFields = exports.variantOperations = exports.storeFields = exports.storeOperations = exports.discountRedemptionFields = exports.discountRedemptionOperations = exports.discountFields = exports.discountOperations = exports.licenseKeyInstanceFields = exports.licenseKeyInstanceOperations = exports.licenseKeyFields = exports.licenseKeyOperations = exports.customerFields = exports.customerOperations = exports.subscriptionInvoiceFields = exports.subscriptionInvoiceOperations = exports.subscriptionFields = exports.subscriptionOperations = exports.orderItemFields = exports.orderItemOperations = exports.orderFields = exports.orderOperations = exports.productFields = exports.productOperations = exports.allFields = exports.allOperations = exports.resourceProperty = void 0;
|
|
3
|
+
exports.fileFields = exports.fileOperations = exports.userFields = exports.userOperations = exports.usageRecordFields = exports.usageRecordOperations = exports.webhookFields = exports.webhookOperations = exports.checkoutFields = exports.checkoutOperations = exports.variantFields = exports.variantOperations = exports.storeFields = exports.storeOperations = exports.discountRedemptionFields = exports.discountRedemptionOperations = exports.discountFields = exports.discountOperations = exports.licenseKeyInstanceFields = exports.licenseKeyInstanceOperations = exports.licenseKeyFields = exports.licenseKeyOperations = exports.customerFields = exports.customerOperations = exports.subscriptionInvoiceFields = exports.subscriptionInvoiceOperations = exports.subscriptionFields = exports.subscriptionOperations = exports.orderItemFields = exports.orderItemOperations = exports.orderFields = exports.orderOperations = exports.productFields = exports.productOperations = exports.allFields = exports.allOperations = exports.resourceProperty = void 0;
|
|
4
4
|
const product_1 = require("./product");
|
|
5
5
|
Object.defineProperty(exports, "productOperations", { enumerable: true, get: function () { return product_1.productOperations; } });
|
|
6
6
|
Object.defineProperty(exports, "productFields", { enumerable: true, get: function () { return product_1.productFields; } });
|
|
@@ -49,6 +49,9 @@ Object.defineProperty(exports, "usageRecordFields", { enumerable: true, get: fun
|
|
|
49
49
|
const user_1 = require("./user");
|
|
50
50
|
Object.defineProperty(exports, "userOperations", { enumerable: true, get: function () { return user_1.userOperations; } });
|
|
51
51
|
Object.defineProperty(exports, "userFields", { enumerable: true, get: function () { return user_1.userFields; } });
|
|
52
|
+
const file_1 = require("./file");
|
|
53
|
+
Object.defineProperty(exports, "fileOperations", { enumerable: true, get: function () { return file_1.fileOperations; } });
|
|
54
|
+
Object.defineProperty(exports, "fileFields", { enumerable: true, get: function () { return file_1.fileFields; } });
|
|
52
55
|
const shared_1 = require("./shared");
|
|
53
56
|
exports.resourceProperty = {
|
|
54
57
|
displayName: 'Resource',
|
|
@@ -60,6 +63,7 @@ exports.resourceProperty = {
|
|
|
60
63
|
{ name: 'Customer', value: 'customer' },
|
|
61
64
|
{ name: 'Discount', value: 'discount' },
|
|
62
65
|
{ name: 'Discount Redemption', value: 'discountRedemption' },
|
|
66
|
+
{ name: 'File', value: 'file' },
|
|
63
67
|
{ name: 'License Key', value: 'licenseKey' },
|
|
64
68
|
{ name: 'License Key Instance', value: 'licenseKeyInstance' },
|
|
65
69
|
{ name: 'Order', value: 'order' },
|
|
@@ -92,6 +96,7 @@ exports.allOperations = [
|
|
|
92
96
|
webhook_1.webhookOperations,
|
|
93
97
|
...usageRecord_1.usageRecordOperations,
|
|
94
98
|
...user_1.userOperations,
|
|
99
|
+
file_1.fileOperations,
|
|
95
100
|
];
|
|
96
101
|
exports.allFields = [
|
|
97
102
|
...product_1.productFields,
|
|
@@ -118,4 +123,5 @@ exports.allFields = [
|
|
|
118
123
|
...webhook_1.webhookFields,
|
|
119
124
|
...usageRecord_1.usageRecordFields,
|
|
120
125
|
...user_1.userFields,
|
|
126
|
+
...file_1.fileFields,
|
|
121
127
|
];
|
|
@@ -325,6 +325,22 @@ export interface LicenseKeyInstanceAttributes extends BaseAttributes {
|
|
|
325
325
|
identifier: string;
|
|
326
326
|
name: string;
|
|
327
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* File attributes
|
|
330
|
+
*/
|
|
331
|
+
export interface FileAttributes extends BaseAttributes {
|
|
332
|
+
variant_id: number;
|
|
333
|
+
identifier: string;
|
|
334
|
+
name: string;
|
|
335
|
+
extension: string;
|
|
336
|
+
download_url: string;
|
|
337
|
+
size: number;
|
|
338
|
+
size_formatted: string;
|
|
339
|
+
version: string;
|
|
340
|
+
sort: number;
|
|
341
|
+
status: 'draft' | 'published';
|
|
342
|
+
test_mode: boolean;
|
|
343
|
+
}
|
|
328
344
|
/**
|
|
329
345
|
* Resource types
|
|
330
346
|
*/
|
|
@@ -339,6 +355,7 @@ export type Discount = JsonApiResource<'discounts', DiscountAttributes>;
|
|
|
339
355
|
export type Checkout = JsonApiResource<'checkouts', CheckoutAttributes>;
|
|
340
356
|
export type Webhook = JsonApiResource<'webhooks', WebhookAttributes>;
|
|
341
357
|
export type LicenseKeyInstance = JsonApiResource<'license-key-instances', LicenseKeyInstanceAttributes>;
|
|
358
|
+
export type File = JsonApiResource<'files', FileAttributes>;
|
|
342
359
|
/**
|
|
343
360
|
* Webhook event types
|
|
344
361
|
*/
|
|
@@ -413,7 +430,7 @@ export interface PaginationOptions {
|
|
|
413
430
|
/**
|
|
414
431
|
* Resource name to endpoint mapping
|
|
415
432
|
*/
|
|
416
|
-
export type ResourceName = 'product' | 'order' | 'subscription' | 'customer' | 'licenseKey' | 'discount' | 'store' | 'variant' | 'checkout' | 'webhook' | 'licenseKeyInstance';
|
|
433
|
+
export type ResourceName = 'product' | 'order' | 'subscription' | 'customer' | 'licenseKey' | 'discount' | 'store' | 'variant' | 'checkout' | 'webhook' | 'licenseKeyInstance' | 'file';
|
|
417
434
|
/**
|
|
418
435
|
* Operation types
|
|
419
436
|
*/
|