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.
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/credentials/LemonSqueezyApi.credentials.d.ts +10 -0
- package/dist/credentials/LemonSqueezyApi.credentials.js +41 -0
- package/dist/nodes/LemonSqueezy/LemonSqueezy.node.d.ts +5 -0
- package/dist/nodes/LemonSqueezy/LemonSqueezy.node.js +358 -0
- package/dist/nodes/LemonSqueezy/LemonSqueezyTrigger.node.d.ts +12 -0
- package/dist/nodes/LemonSqueezy/LemonSqueezyTrigger.node.js +230 -0
- package/dist/nodes/LemonSqueezy/constants.d.ts +89 -0
- package/dist/nodes/LemonSqueezy/constants.js +207 -0
- package/dist/nodes/LemonSqueezy/helpers.d.ts +28 -0
- package/dist/nodes/LemonSqueezy/helpers.js +241 -0
- package/dist/nodes/LemonSqueezy/lemonSqueezy.svg +20 -0
- package/dist/nodes/LemonSqueezy/resources/checkout.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/checkout.js +272 -0
- package/dist/nodes/LemonSqueezy/resources/customer.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/customer.js +242 -0
- package/dist/nodes/LemonSqueezy/resources/discount.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/discount.js +210 -0
- package/dist/nodes/LemonSqueezy/resources/index.d.ts +15 -0
- package/dist/nodes/LemonSqueezy/resources/index.js +76 -0
- package/dist/nodes/LemonSqueezy/resources/licenseKey.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/licenseKey.js +209 -0
- package/dist/nodes/LemonSqueezy/resources/order.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/order.js +113 -0
- package/dist/nodes/LemonSqueezy/resources/product.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/product.js +93 -0
- package/dist/nodes/LemonSqueezy/resources/store.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/store.js +64 -0
- package/dist/nodes/LemonSqueezy/resources/subscription.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/subscription.js +196 -0
- package/dist/nodes/LemonSqueezy/resources/variant.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/variant.js +96 -0
- package/dist/nodes/LemonSqueezy/resources/webhook.d.ts +3 -0
- package/dist/nodes/LemonSqueezy/resources/webhook.js +206 -0
- package/dist/nodes/LemonSqueezy/types.d.ts +364 -0
- package/dist/nodes/LemonSqueezy/types.js +2 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jan Marc Coloma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# n8n-nodes-lemonsqueezy
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/n8n-nodes-lemonsqueezy)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
An [n8n](https://n8n.io/) community node for [Lemon Squeezy](https://lemonsqueezy.com) - a platform for selling digital products, subscriptions, and software licenses.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Full CRUD Operations** - Create, read, update, and delete operations for all major resources
|
|
11
|
+
- **Webhook Trigger** - Real-time event notifications for orders, subscriptions, and license keys
|
|
12
|
+
- **License Key Management** - Validate, activate, and deactivate license keys
|
|
13
|
+
- **Checkout Links** - Create dynamic checkout URLs with custom options
|
|
14
|
+
- **Rate Limiting** - Built-in retry logic with exponential backoff
|
|
15
|
+
- **Type Safety** - Full TypeScript support with comprehensive type definitions
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Community Nodes (Recommended)
|
|
20
|
+
|
|
21
|
+
1. Go to **Settings** > **Community Nodes** in your n8n instance
|
|
22
|
+
2. Select **Install**
|
|
23
|
+
3. Enter `n8n-nodes-lemonsqueezy`
|
|
24
|
+
4. Click **Install**
|
|
25
|
+
|
|
26
|
+
### npm
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install n8n-nodes-lemonsqueezy
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Credentials
|
|
33
|
+
|
|
34
|
+
To use this node, you need a Lemon Squeezy API key:
|
|
35
|
+
|
|
36
|
+
1. Log in to your [Lemon Squeezy Dashboard](https://app.lemonsqueezy.com)
|
|
37
|
+
2. Go to **Settings** → **API**
|
|
38
|
+
3. Click **Create API Key**
|
|
39
|
+
4. Copy the generated key and use it in n8n
|
|
40
|
+
|
|
41
|
+
## Nodes
|
|
42
|
+
|
|
43
|
+
### Lemon Squeezy
|
|
44
|
+
|
|
45
|
+
The main node for interacting with the Lemon Squeezy API.
|
|
46
|
+
|
|
47
|
+
#### Resources & Operations
|
|
48
|
+
|
|
49
|
+
| Resource | Operations |
|
|
50
|
+
|----------|------------|
|
|
51
|
+
| **Checkout** | Create, Get, Get Many |
|
|
52
|
+
| **Customer** | Create, Update, Delete, Get, Get Many |
|
|
53
|
+
| **Discount** | Create, Delete, Get, Get Many |
|
|
54
|
+
| **License Key** | Get, Get Many, Update, Validate, Activate, Deactivate |
|
|
55
|
+
| **Order** | Get, Get Many, Refund |
|
|
56
|
+
| **Product** | Get, Get Many |
|
|
57
|
+
| **Store** | Get, Get Many |
|
|
58
|
+
| **Subscription** | Get, Get Many, Update, Cancel, Resume |
|
|
59
|
+
| **Variant** | Get, Get Many |
|
|
60
|
+
| **Webhook** | Create, Update, Delete, Get, Get Many |
|
|
61
|
+
|
|
62
|
+
### Lemon Squeezy Trigger
|
|
63
|
+
|
|
64
|
+
Webhook trigger node for receiving real-time events.
|
|
65
|
+
|
|
66
|
+
#### Supported Events
|
|
67
|
+
|
|
68
|
+
- `order_created` - New order placed
|
|
69
|
+
- `order_refunded` - Order refunded
|
|
70
|
+
- `subscription_created` - New subscription started
|
|
71
|
+
- `subscription_updated` - Subscription modified
|
|
72
|
+
- `subscription_cancelled` - Subscription cancelled
|
|
73
|
+
- `subscription_resumed` - Paused subscription resumed
|
|
74
|
+
- `subscription_paused` - Subscription paused
|
|
75
|
+
- `subscription_expired` - Subscription expired
|
|
76
|
+
- `subscription_payment_success` - Subscription payment succeeded
|
|
77
|
+
- `subscription_payment_failed` - Subscription payment failed
|
|
78
|
+
- `subscription_payment_recovered` - Failed payment recovered
|
|
79
|
+
- `subscription_payment_refunded` - Subscription payment refunded
|
|
80
|
+
- `license_key_created` - License key generated
|
|
81
|
+
- `license_key_updated` - License key modified
|
|
82
|
+
|
|
83
|
+
## Example Workflows
|
|
84
|
+
|
|
85
|
+
### 1. New Order Notification to Slack
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Lemon Squeezy Trigger (order_created) → Slack (Send Message)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Notify your team instantly when a new order comes in.
|
|
92
|
+
|
|
93
|
+
### 2. Subscription Churn Prevention
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Schedule Trigger → Lemon Squeezy (Get Subscriptions, status=past_due) → Send Email
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Automatically reach out to customers with failed payments.
|
|
100
|
+
|
|
101
|
+
### 3. License Key Validation API
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Webhook → Lemon Squeezy (Validate License Key) → Respond to Webhook
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Build a license validation endpoint for your software.
|
|
108
|
+
|
|
109
|
+
### 4. Dynamic Checkout Link Generation
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
HTTP Request → Lemon Squeezy (Create Checkout) → Return Checkout URL
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Create personalized checkout links with pre-filled customer data.
|
|
116
|
+
|
|
117
|
+
### 5. Customer Sync to CRM
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Lemon Squeezy Trigger (order_created) → Lemon Squeezy (Get Customer) → HubSpot (Create Contact)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Automatically sync new customers to your CRM.
|
|
124
|
+
|
|
125
|
+
## Filtering
|
|
126
|
+
|
|
127
|
+
Most "Get Many" operations support filtering:
|
|
128
|
+
|
|
129
|
+
| Filter | Description | Available On |
|
|
130
|
+
|--------|-------------|--------------|
|
|
131
|
+
| `storeId` | Filter by store | All resources |
|
|
132
|
+
| `status` | Filter by status | Orders, Subscriptions, Customers, License Keys |
|
|
133
|
+
| `email` | Filter by email | Orders, Customers |
|
|
134
|
+
| `productId` | Filter by product | Subscriptions, License Keys, Variants |
|
|
135
|
+
| `variantId` | Filter by variant | Subscriptions, Checkouts |
|
|
136
|
+
| `orderId` | Filter by order | Subscriptions, License Keys |
|
|
137
|
+
|
|
138
|
+
## Error Handling
|
|
139
|
+
|
|
140
|
+
The node includes built-in error handling:
|
|
141
|
+
|
|
142
|
+
- **Rate Limiting**: Automatically waits and retries when rate limited (429 errors)
|
|
143
|
+
- **Retry Logic**: Retries failed requests with exponential backoff for 5xx errors
|
|
144
|
+
- **Continue on Fail**: Enable to process remaining items even if some fail
|
|
145
|
+
|
|
146
|
+
## Troubleshooting
|
|
147
|
+
|
|
148
|
+
### "Invalid API Key" Error
|
|
149
|
+
|
|
150
|
+
1. Verify your API key is correct in the credentials
|
|
151
|
+
2. Check if the API key has been revoked in Lemon Squeezy
|
|
152
|
+
3. Ensure the key has appropriate permissions
|
|
153
|
+
|
|
154
|
+
### "Resource Not Found" (404) Error
|
|
155
|
+
|
|
156
|
+
1. Verify the resource ID is correct
|
|
157
|
+
2. Check if the resource exists in Lemon Squeezy
|
|
158
|
+
3. Ensure you're using the correct resource type
|
|
159
|
+
|
|
160
|
+
### Webhook Not Receiving Events
|
|
161
|
+
|
|
162
|
+
1. Verify the webhook URL is publicly accessible
|
|
163
|
+
2. Check if your n8n instance has HTTPS enabled
|
|
164
|
+
3. Verify the webhook secret matches
|
|
165
|
+
4. Check the webhook events are enabled in Lemon Squeezy
|
|
166
|
+
|
|
167
|
+
### Rate Limiting Issues
|
|
168
|
+
|
|
169
|
+
The node handles rate limiting automatically, but if you're hitting limits frequently:
|
|
170
|
+
|
|
171
|
+
1. Reduce the frequency of API calls
|
|
172
|
+
2. Use "Return All" sparingly for large datasets
|
|
173
|
+
3. Consider caching responses where appropriate
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Install dependencies
|
|
179
|
+
npm install
|
|
180
|
+
|
|
181
|
+
# Build the node
|
|
182
|
+
npm run build
|
|
183
|
+
|
|
184
|
+
# Run tests
|
|
185
|
+
npm test
|
|
186
|
+
|
|
187
|
+
# Run linter
|
|
188
|
+
npm run lint
|
|
189
|
+
|
|
190
|
+
# Format code
|
|
191
|
+
npm run format
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
197
|
+
|
|
198
|
+
1. Fork the repository
|
|
199
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
200
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
201
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
202
|
+
5. Open a Pull Request
|
|
203
|
+
|
|
204
|
+
## Resources
|
|
205
|
+
|
|
206
|
+
- [Lemon Squeezy API Documentation](https://docs.lemonsqueezy.com/api)
|
|
207
|
+
- [n8n Community Nodes Documentation](https://docs.n8n.io/integrations/community-nodes/)
|
|
208
|
+
- [n8n Community Forum](https://community.n8n.io/)
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
[MIT](LICENSE)
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
Made with 🍋 by [Jan Marc Coloma](https://github.com/janmaaarc)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class LemonSqueezyApi implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
icon: "file:lemonSqueezy.svg";
|
|
6
|
+
documentationUrl: string;
|
|
7
|
+
properties: INodeProperties[];
|
|
8
|
+
authenticate: IAuthenticateGeneric;
|
|
9
|
+
test: ICredentialTestRequest;
|
|
10
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LemonSqueezyApi = void 0;
|
|
4
|
+
class LemonSqueezyApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'lemonSqueezyApi';
|
|
7
|
+
this.displayName = 'Lemon Squeezy API';
|
|
8
|
+
this.icon = 'file:lemonSqueezy.svg';
|
|
9
|
+
this.documentationUrl = 'https://docs.lemonsqueezy.com/api';
|
|
10
|
+
this.properties = [
|
|
11
|
+
{
|
|
12
|
+
displayName: 'API Key',
|
|
13
|
+
name: 'apiKey',
|
|
14
|
+
type: 'string',
|
|
15
|
+
typeOptions: { password: true },
|
|
16
|
+
default: '',
|
|
17
|
+
required: true,
|
|
18
|
+
placeholder: 'lsq_xxxxxxxxxxxxxxxxxxxxxxxx',
|
|
19
|
+
hint: 'Found in Settings → API in your dashboard',
|
|
20
|
+
description: 'Your Lemon Squeezy API key. <a href="https://app.lemonsqueezy.com/settings/api" target="_blank">Generate one here</a>.',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
this.authenticate = {
|
|
24
|
+
type: 'generic',
|
|
25
|
+
properties: {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: '=Bearer {{$credentials.apiKey}}',
|
|
28
|
+
Accept: 'application/vnd.api+json',
|
|
29
|
+
'Content-Type': 'application/vnd.api+json',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
this.test = {
|
|
34
|
+
request: {
|
|
35
|
+
baseURL: 'https://api.lemonsqueezy.com/v1',
|
|
36
|
+
url: '/users/me',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.LemonSqueezyApi = LemonSqueezyApi;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class LemonSqueezy implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LemonSqueezy = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const helpers_1 = require("./helpers");
|
|
6
|
+
const resources_1 = require("./resources");
|
|
7
|
+
async function handleCreate(ctx, resource, itemIndex) {
|
|
8
|
+
if (resource === 'customer') {
|
|
9
|
+
const storeId = ctx.getNodeParameter('customerStoreId', itemIndex);
|
|
10
|
+
const name = ctx.getNodeParameter('customerName', itemIndex);
|
|
11
|
+
const email = ctx.getNodeParameter('customerEmail', itemIndex);
|
|
12
|
+
const additionalFields = ctx.getNodeParameter('additionalFields', itemIndex);
|
|
13
|
+
const body = (0, helpers_1.buildJsonApiBody)('customers', { name, email, ...additionalFields }, { store: { type: 'stores', id: storeId } });
|
|
14
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'POST', '/customers', body);
|
|
15
|
+
}
|
|
16
|
+
if (resource === 'discount') {
|
|
17
|
+
const storeId = ctx.getNodeParameter('discountStoreId', itemIndex);
|
|
18
|
+
const name = ctx.getNodeParameter('discountName', itemIndex);
|
|
19
|
+
const code = ctx.getNodeParameter('discountCode', itemIndex);
|
|
20
|
+
const amount = ctx.getNodeParameter('discountAmount', itemIndex);
|
|
21
|
+
const amountType = ctx.getNodeParameter('discountAmountType', itemIndex);
|
|
22
|
+
const additionalOptions = ctx.getNodeParameter('additionalOptions', itemIndex, {});
|
|
23
|
+
const attributes = {
|
|
24
|
+
name,
|
|
25
|
+
code,
|
|
26
|
+
amount,
|
|
27
|
+
amount_type: amountType,
|
|
28
|
+
};
|
|
29
|
+
if (additionalOptions.duration) {
|
|
30
|
+
attributes.duration = additionalOptions.duration;
|
|
31
|
+
}
|
|
32
|
+
if (additionalOptions.durationInMonths) {
|
|
33
|
+
attributes.duration_in_months = additionalOptions.durationInMonths;
|
|
34
|
+
}
|
|
35
|
+
if (additionalOptions.maxRedemptions) {
|
|
36
|
+
attributes.max_redemptions = additionalOptions.maxRedemptions;
|
|
37
|
+
attributes.is_limited_redemptions = true;
|
|
38
|
+
}
|
|
39
|
+
if (additionalOptions.startsAt) {
|
|
40
|
+
attributes.starts_at = additionalOptions.startsAt;
|
|
41
|
+
}
|
|
42
|
+
if (additionalOptions.expiresAt) {
|
|
43
|
+
attributes.expires_at = additionalOptions.expiresAt;
|
|
44
|
+
}
|
|
45
|
+
if (additionalOptions.testMode !== undefined) {
|
|
46
|
+
attributes.test_mode = additionalOptions.testMode;
|
|
47
|
+
}
|
|
48
|
+
const body = (0, helpers_1.buildJsonApiBody)('discounts', attributes, {
|
|
49
|
+
store: { type: 'stores', id: storeId },
|
|
50
|
+
});
|
|
51
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'POST', '/discounts', body);
|
|
52
|
+
}
|
|
53
|
+
if (resource === 'checkout') {
|
|
54
|
+
const storeId = ctx.getNodeParameter('checkoutStoreId', itemIndex);
|
|
55
|
+
const variantId = ctx.getNodeParameter('checkoutVariantId', itemIndex);
|
|
56
|
+
const additionalOptions = ctx.getNodeParameter('additionalOptions', itemIndex, {});
|
|
57
|
+
const checkoutOptions = ctx.getNodeParameter('checkoutOptions', itemIndex, {});
|
|
58
|
+
const attributes = {};
|
|
59
|
+
const checkoutData = {};
|
|
60
|
+
const productOptions = {};
|
|
61
|
+
const checkoutOptionsObj = {};
|
|
62
|
+
if (additionalOptions.customPrice) {
|
|
63
|
+
attributes.custom_price = additionalOptions.customPrice;
|
|
64
|
+
}
|
|
65
|
+
if (additionalOptions.email) {
|
|
66
|
+
checkoutData.email = additionalOptions.email;
|
|
67
|
+
}
|
|
68
|
+
if (additionalOptions.name) {
|
|
69
|
+
checkoutData.name = additionalOptions.name;
|
|
70
|
+
}
|
|
71
|
+
if (additionalOptions.discountCode) {
|
|
72
|
+
checkoutData.discount_code = additionalOptions.discountCode;
|
|
73
|
+
}
|
|
74
|
+
if (additionalOptions.redirectUrl) {
|
|
75
|
+
productOptions.redirect_url = additionalOptions.redirectUrl;
|
|
76
|
+
}
|
|
77
|
+
if (additionalOptions.receiptButtonText) {
|
|
78
|
+
productOptions.receipt_button_text = additionalOptions.receiptButtonText;
|
|
79
|
+
}
|
|
80
|
+
if (additionalOptions.receiptLinkUrl) {
|
|
81
|
+
productOptions.receipt_link_url = additionalOptions.receiptLinkUrl;
|
|
82
|
+
}
|
|
83
|
+
if (additionalOptions.receiptThankYouNote) {
|
|
84
|
+
productOptions.receipt_thank_you_note = additionalOptions.receiptThankYouNote;
|
|
85
|
+
}
|
|
86
|
+
if (additionalOptions.customData) {
|
|
87
|
+
try {
|
|
88
|
+
checkoutData.custom = JSON.parse(additionalOptions.customData);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
checkoutData.custom = additionalOptions.customData;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (additionalOptions.expiresAt) {
|
|
95
|
+
attributes.expires_at = additionalOptions.expiresAt;
|
|
96
|
+
}
|
|
97
|
+
if (additionalOptions.testMode !== undefined) {
|
|
98
|
+
attributes.test_mode = additionalOptions.testMode;
|
|
99
|
+
}
|
|
100
|
+
if (checkoutOptions.dark !== undefined) {
|
|
101
|
+
checkoutOptionsObj.dark = checkoutOptions.dark;
|
|
102
|
+
}
|
|
103
|
+
if (checkoutOptions.embed !== undefined) {
|
|
104
|
+
checkoutOptionsObj.embed = checkoutOptions.embed;
|
|
105
|
+
}
|
|
106
|
+
if (checkoutOptions.logo !== undefined) {
|
|
107
|
+
checkoutOptionsObj.logo = checkoutOptions.logo;
|
|
108
|
+
}
|
|
109
|
+
if (checkoutOptions.desc !== undefined) {
|
|
110
|
+
checkoutOptionsObj.desc = checkoutOptions.desc;
|
|
111
|
+
}
|
|
112
|
+
if (checkoutOptions.media !== undefined) {
|
|
113
|
+
checkoutOptionsObj.media = checkoutOptions.media;
|
|
114
|
+
}
|
|
115
|
+
if (checkoutOptions.discount !== undefined) {
|
|
116
|
+
checkoutOptionsObj.discount = checkoutOptions.discount;
|
|
117
|
+
}
|
|
118
|
+
if (checkoutOptions.buttonColor) {
|
|
119
|
+
checkoutOptionsObj.button_color = checkoutOptions.buttonColor;
|
|
120
|
+
}
|
|
121
|
+
if (Object.keys(checkoutData).length > 0) {
|
|
122
|
+
attributes.checkout_data = checkoutData;
|
|
123
|
+
}
|
|
124
|
+
if (Object.keys(productOptions).length > 0) {
|
|
125
|
+
attributes.product_options = productOptions;
|
|
126
|
+
}
|
|
127
|
+
if (Object.keys(checkoutOptionsObj).length > 0) {
|
|
128
|
+
attributes.checkout_options = checkoutOptionsObj;
|
|
129
|
+
}
|
|
130
|
+
const body = (0, helpers_1.buildJsonApiBody)('checkouts', attributes, {
|
|
131
|
+
store: { type: 'stores', id: storeId },
|
|
132
|
+
variant: { type: 'variants', id: variantId },
|
|
133
|
+
});
|
|
134
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'POST', '/checkouts', body);
|
|
135
|
+
}
|
|
136
|
+
if (resource === 'webhook') {
|
|
137
|
+
const storeId = ctx.getNodeParameter('webhookStoreId', itemIndex);
|
|
138
|
+
const url = ctx.getNodeParameter('webhookUrl', itemIndex);
|
|
139
|
+
const events = ctx.getNodeParameter('webhookEvents', itemIndex);
|
|
140
|
+
const secret = ctx.getNodeParameter('webhookSecret', itemIndex);
|
|
141
|
+
const additionalOptions = ctx.getNodeParameter('additionalOptions', itemIndex, {});
|
|
142
|
+
const attributes = { url, events, secret };
|
|
143
|
+
if (additionalOptions.testMode !== undefined) {
|
|
144
|
+
attributes.test_mode = additionalOptions.testMode;
|
|
145
|
+
}
|
|
146
|
+
const body = (0, helpers_1.buildJsonApiBody)('webhooks', attributes, {
|
|
147
|
+
store: { type: 'stores', id: storeId },
|
|
148
|
+
});
|
|
149
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'POST', '/webhooks', body);
|
|
150
|
+
}
|
|
151
|
+
throw new Error(`Create operation not supported for resource: ${resource}`);
|
|
152
|
+
}
|
|
153
|
+
async function handleUpdate(ctx, resource, itemIndex) {
|
|
154
|
+
if (resource === 'subscription') {
|
|
155
|
+
const subscriptionId = ctx.getNodeParameter('subscriptionId', itemIndex);
|
|
156
|
+
const updateFields = ctx.getNodeParameter('updateFields', itemIndex);
|
|
157
|
+
const attributes = {};
|
|
158
|
+
if (updateFields.variantId) {
|
|
159
|
+
attributes.variant_id = updateFields.variantId;
|
|
160
|
+
}
|
|
161
|
+
if (updateFields.pause !== undefined && updateFields.pause !== '') {
|
|
162
|
+
attributes.pause = { mode: updateFields.pause };
|
|
163
|
+
}
|
|
164
|
+
else if (updateFields.pause === '') {
|
|
165
|
+
attributes.pause = null;
|
|
166
|
+
}
|
|
167
|
+
if (updateFields.cancelled !== undefined) {
|
|
168
|
+
attributes.cancelled = updateFields.cancelled;
|
|
169
|
+
}
|
|
170
|
+
if (updateFields.billingAnchor) {
|
|
171
|
+
attributes.billing_anchor = updateFields.billingAnchor;
|
|
172
|
+
}
|
|
173
|
+
if (updateFields.invoiceImmediately !== undefined) {
|
|
174
|
+
attributes.invoice_immediately = updateFields.invoiceImmediately;
|
|
175
|
+
}
|
|
176
|
+
if (updateFields.disableProrations !== undefined) {
|
|
177
|
+
attributes.disable_prorations = updateFields.disableProrations;
|
|
178
|
+
}
|
|
179
|
+
const body = (0, helpers_1.buildJsonApiBody)('subscriptions', attributes, undefined, subscriptionId);
|
|
180
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'PATCH', `/subscriptions/${subscriptionId}`, body);
|
|
181
|
+
}
|
|
182
|
+
if (resource === 'customer') {
|
|
183
|
+
const customerId = ctx.getNodeParameter('customerId', itemIndex);
|
|
184
|
+
const updateFields = ctx.getNodeParameter('updateFields', itemIndex);
|
|
185
|
+
const attributes = {};
|
|
186
|
+
if (updateFields.name) {
|
|
187
|
+
attributes.name = updateFields.name;
|
|
188
|
+
}
|
|
189
|
+
if (updateFields.email) {
|
|
190
|
+
attributes.email = updateFields.email;
|
|
191
|
+
}
|
|
192
|
+
if (updateFields.city) {
|
|
193
|
+
attributes.city = updateFields.city;
|
|
194
|
+
}
|
|
195
|
+
if (updateFields.country) {
|
|
196
|
+
attributes.country = updateFields.country;
|
|
197
|
+
}
|
|
198
|
+
if (updateFields.region) {
|
|
199
|
+
attributes.region = updateFields.region;
|
|
200
|
+
}
|
|
201
|
+
if (updateFields.status) {
|
|
202
|
+
attributes.status = updateFields.status;
|
|
203
|
+
}
|
|
204
|
+
const body = (0, helpers_1.buildJsonApiBody)('customers', attributes, undefined, customerId);
|
|
205
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'PATCH', `/customers/${customerId}`, body);
|
|
206
|
+
}
|
|
207
|
+
if (resource === 'licenseKey') {
|
|
208
|
+
const licenseKeyId = ctx.getNodeParameter('licenseKeyId', itemIndex);
|
|
209
|
+
const updateFields = ctx.getNodeParameter('updateFields', itemIndex);
|
|
210
|
+
const attributes = {};
|
|
211
|
+
if (updateFields.activationLimit !== undefined) {
|
|
212
|
+
attributes.activation_limit = updateFields.activationLimit;
|
|
213
|
+
}
|
|
214
|
+
if (updateFields.disabled !== undefined) {
|
|
215
|
+
attributes.disabled = updateFields.disabled;
|
|
216
|
+
}
|
|
217
|
+
if (updateFields.expiresAt) {
|
|
218
|
+
attributes.expires_at = updateFields.expiresAt;
|
|
219
|
+
}
|
|
220
|
+
const body = (0, helpers_1.buildJsonApiBody)('license-keys', attributes, undefined, licenseKeyId);
|
|
221
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'PATCH', `/license-keys/${licenseKeyId}`, body);
|
|
222
|
+
}
|
|
223
|
+
if (resource === 'webhook') {
|
|
224
|
+
const webhookId = ctx.getNodeParameter('webhookId', itemIndex);
|
|
225
|
+
const updateFields = ctx.getNodeParameter('updateFields', itemIndex);
|
|
226
|
+
const attributes = {};
|
|
227
|
+
if (updateFields.url) {
|
|
228
|
+
attributes.url = updateFields.url;
|
|
229
|
+
}
|
|
230
|
+
if (updateFields.events) {
|
|
231
|
+
attributes.events = updateFields.events;
|
|
232
|
+
}
|
|
233
|
+
if (updateFields.secret) {
|
|
234
|
+
attributes.secret = updateFields.secret;
|
|
235
|
+
}
|
|
236
|
+
const body = (0, helpers_1.buildJsonApiBody)('webhooks', attributes, undefined, webhookId);
|
|
237
|
+
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'PATCH', `/webhooks/${webhookId}`, body);
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`Update operation not supported for resource: ${resource}`);
|
|
240
|
+
}
|
|
241
|
+
class LemonSqueezy {
|
|
242
|
+
constructor() {
|
|
243
|
+
this.description = {
|
|
244
|
+
displayName: 'Lemon Squeezy',
|
|
245
|
+
name: 'lemonSqueezy',
|
|
246
|
+
icon: 'file:lemonSqueezy.svg',
|
|
247
|
+
group: ['transform'],
|
|
248
|
+
version: 1,
|
|
249
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
250
|
+
description: 'Interact with Lemon Squeezy API',
|
|
251
|
+
defaults: {
|
|
252
|
+
name: 'Lemon Squeezy',
|
|
253
|
+
},
|
|
254
|
+
inputs: ['main'],
|
|
255
|
+
outputs: ['main'],
|
|
256
|
+
credentials: [
|
|
257
|
+
{
|
|
258
|
+
name: 'lemonSqueezyApi',
|
|
259
|
+
required: true,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
properties: [resources_1.resourceProperty, ...resources_1.allOperations, ...resources_1.allFields],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async execute() {
|
|
266
|
+
const items = this.getInputData();
|
|
267
|
+
const returnData = [];
|
|
268
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
269
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
270
|
+
for (let i = 0; i < items.length; i++) {
|
|
271
|
+
try {
|
|
272
|
+
let responseData;
|
|
273
|
+
const endpoint = constants_1.RESOURCE_ENDPOINTS[resource];
|
|
274
|
+
if (operation === 'get') {
|
|
275
|
+
const idParam = constants_1.RESOURCE_ID_PARAMS[resource];
|
|
276
|
+
const id = this.getNodeParameter(idParam, i);
|
|
277
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'GET', `/${endpoint}/${id}`);
|
|
278
|
+
}
|
|
279
|
+
else if (operation === 'getAll') {
|
|
280
|
+
const returnAll = this.getNodeParameter('returnAll', i);
|
|
281
|
+
const filters = this.getNodeParameter('filters', i, {});
|
|
282
|
+
const qs = (0, helpers_1.buildFilterParams)(filters);
|
|
283
|
+
if (returnAll) {
|
|
284
|
+
responseData = await helpers_1.lemonSqueezyApiRequestAllItems.call(this, 'GET', `/${endpoint}`, qs);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const limit = this.getNodeParameter('limit', i);
|
|
288
|
+
qs['page[size]'] = limit;
|
|
289
|
+
const response = await helpers_1.lemonSqueezyApiRequest.call(this, 'GET', `/${endpoint}`, undefined, qs);
|
|
290
|
+
responseData = response.data;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if (operation === 'create') {
|
|
294
|
+
responseData = await handleCreate(this, resource, i);
|
|
295
|
+
}
|
|
296
|
+
else if (operation === 'update') {
|
|
297
|
+
responseData = await handleUpdate(this, resource, i);
|
|
298
|
+
}
|
|
299
|
+
else if (operation === 'delete') {
|
|
300
|
+
const idParam = constants_1.RESOURCE_ID_PARAMS[resource];
|
|
301
|
+
const id = this.getNodeParameter(idParam, i);
|
|
302
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'DELETE', `/${endpoint}/${id}`);
|
|
303
|
+
}
|
|
304
|
+
else if (operation === 'cancel' && resource === 'subscription') {
|
|
305
|
+
const subscriptionId = this.getNodeParameter('subscriptionId', i);
|
|
306
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'DELETE', `/subscriptions/${subscriptionId}`);
|
|
307
|
+
}
|
|
308
|
+
else if (operation === 'resume' && resource === 'subscription') {
|
|
309
|
+
const subscriptionId = this.getNodeParameter('subscriptionId', i);
|
|
310
|
+
const body = (0, helpers_1.buildJsonApiBody)('subscriptions', { pause: null }, undefined, subscriptionId);
|
|
311
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'PATCH', `/subscriptions/${subscriptionId}`, body);
|
|
312
|
+
}
|
|
313
|
+
else if (operation === 'refund' && resource === 'order') {
|
|
314
|
+
const orderId = this.getNodeParameter('orderId', i);
|
|
315
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'POST', `/orders/${orderId}/refund`);
|
|
316
|
+
}
|
|
317
|
+
else if (resource === 'licenseKey') {
|
|
318
|
+
if (operation === 'validate') {
|
|
319
|
+
const licenseKey = this.getNodeParameter('licenseKey', i);
|
|
320
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'POST', '/licenses/validate', {
|
|
321
|
+
license_key: licenseKey,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
else if (operation === 'activate') {
|
|
325
|
+
const licenseKey = this.getNodeParameter('licenseKey', i);
|
|
326
|
+
const instanceName = this.getNodeParameter('instanceName', i);
|
|
327
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'POST', '/licenses/activate', {
|
|
328
|
+
license_key: licenseKey,
|
|
329
|
+
instance_name: instanceName,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
else if (operation === 'deactivate') {
|
|
333
|
+
const licenseKey = this.getNodeParameter('licenseKey', i);
|
|
334
|
+
const instanceId = this.getNodeParameter('instanceId', i);
|
|
335
|
+
responseData = await helpers_1.lemonSqueezyApiRequest.call(this, 'POST', '/licenses/deactivate', {
|
|
336
|
+
license_key: licenseKey,
|
|
337
|
+
instance_id: instanceId,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
|
|
342
|
+
returnData.push(...executionData);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
if (this.continueOnFail()) {
|
|
346
|
+
returnData.push({
|
|
347
|
+
json: { error: error.message },
|
|
348
|
+
pairedItem: { item: i },
|
|
349
|
+
});
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return [returnData];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
exports.LemonSqueezy = LemonSqueezy;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IWebhookFunctions, IHookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
|
|
2
|
+
export declare class LemonSqueezyTrigger 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
|
+
}
|