lightspeed-retail-sdk 2.0.7 → 2.0.8
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 +5 -1
- package/index.cjs +646 -0
- package/{index.js → index.mjs} +30 -7
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A JavaScript SDK for interacting with the Lightspeed Retail API. This SDK provides a convenient way to access Lightspeed Retail's functionalities, including customer, item, order management, and more.
|
|
4
4
|
|
|
5
|
+
## Update
|
|
6
|
+
|
|
7
|
+
I have updated this package so it can be used with either index.cjs or index.mjs for imports.
|
|
8
|
+
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
11
|
- Easy-to-use methods for interacting with various Lightspeed Retail endpoints.
|
|
@@ -19,7 +23,7 @@ npm install lightspeed-retail-sdk
|
|
|
19
23
|
## Get started:
|
|
20
24
|
|
|
21
25
|
```
|
|
22
|
-
import LightspeedRetailSDK from "lightspeed-retail-sdk";
|
|
26
|
+
import LightspeedRetailSDK from "lightspeed-retail-sdk/index.mjs";
|
|
23
27
|
|
|
24
28
|
const api = new LightspeedRetailSDK({
|
|
25
29
|
accountID: "Your Account No.",
|
package/index.cjs
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
|
|
3
|
+
const operationUnits = { GET: 1, POST: 10, PUT: 10 };
|
|
4
|
+
const getRequestUnits = (operation) => operationUnits[operation] || 10;
|
|
5
|
+
|
|
6
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
|
|
8
|
+
class LightspeedRetailSDK {
|
|
9
|
+
static BASE_URL = "https://api.lightspeedapp.com/API/V3/Account";
|
|
10
|
+
static TOKEN_URL = "https://cloud.lightspeedapp.com/oauth/access_token.php";
|
|
11
|
+
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
const { clientID, clientSecret, refreshToken, accountID } = opts;
|
|
14
|
+
|
|
15
|
+
this.clientID = clientID;
|
|
16
|
+
this.clientSecret = clientSecret;
|
|
17
|
+
this.refreshToken = refreshToken;
|
|
18
|
+
this.accountID = accountID;
|
|
19
|
+
this.baseUrl = LightspeedRetailSDK.BASE_URL;
|
|
20
|
+
this.tokenUrl = LightspeedRetailSDK.TOKEN_URL;
|
|
21
|
+
this.maxRetries = 3;
|
|
22
|
+
this.lastResponse = null;
|
|
23
|
+
this.token = null;
|
|
24
|
+
this.tokenExpiry = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// handleError function to handle errors
|
|
28
|
+
handleError(context, err, shouldThrow = true) {
|
|
29
|
+
// Context includes information about where the error occurred
|
|
30
|
+
const detailedMessage = `Error in ${context}: ${err.message}`;
|
|
31
|
+
|
|
32
|
+
// Log the error message
|
|
33
|
+
console.error(detailedMessage);
|
|
34
|
+
|
|
35
|
+
// Log the stack trace if available
|
|
36
|
+
if (err.stack) {
|
|
37
|
+
console.error("Stack trace:", err.stack);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If the error has response data, log it
|
|
41
|
+
if (err.response) {
|
|
42
|
+
console.error("Error response:", {
|
|
43
|
+
status: err.response.status,
|
|
44
|
+
headers: err.response.headers,
|
|
45
|
+
data: err.response.data,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Optionally rethrow the error with the detailed message
|
|
50
|
+
if (shouldThrow) {
|
|
51
|
+
throw new Error(detailedMessage);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update the last response
|
|
56
|
+
setLastResponse = (response) => (this.lastResponse = response);
|
|
57
|
+
|
|
58
|
+
// Handle rate limits
|
|
59
|
+
handleRateLimit = async (options) => {
|
|
60
|
+
if (!this.lastResponse) return null;
|
|
61
|
+
|
|
62
|
+
const { method } = options;
|
|
63
|
+
const requestUnits = getRequestUnits(method);
|
|
64
|
+
const rateHeader = this.lastResponse.headers["x-ls-api-bucket-level"];
|
|
65
|
+
|
|
66
|
+
if (!rateHeader) return null;
|
|
67
|
+
|
|
68
|
+
const [used, available] = rateHeader.split("/");
|
|
69
|
+
const availableUnits = available - used;
|
|
70
|
+
if (requestUnits <= availableUnits) return 0;
|
|
71
|
+
|
|
72
|
+
const dripRate = parseInt(this.lastResponse.headers["x-ls-api-drip-rate"], 10);
|
|
73
|
+
|
|
74
|
+
// Check if dripRate is a valid number greater than 0
|
|
75
|
+
if (isNaN(dripRate) || dripRate <= 0) {
|
|
76
|
+
this.handleError.error("Invalid drip rate received from API");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const unitWait = requestUnits - availableUnits;
|
|
80
|
+
const delay = Math.ceil((unitWait / dripRate) * 1000);
|
|
81
|
+
await sleep(delay);
|
|
82
|
+
|
|
83
|
+
return unitWait;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Get a new token
|
|
87
|
+
getToken = async () => {
|
|
88
|
+
const now = new Date();
|
|
89
|
+
const bufferTime = 1 * 60 * 1000; // 1 minute buffer
|
|
90
|
+
|
|
91
|
+
// Check if the token exists and is still valid
|
|
92
|
+
if (this.token && this.tokenExpiry.getTime() - now.getTime() > bufferTime) {
|
|
93
|
+
return this.token;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Fetch a new token if needed
|
|
97
|
+
const body = {
|
|
98
|
+
grant_type: "refresh_token",
|
|
99
|
+
client_id: this.clientID,
|
|
100
|
+
client_secret: this.clientSecret,
|
|
101
|
+
refresh_token: this.refreshToken,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await axios({
|
|
106
|
+
url: this.tokenUrl,
|
|
107
|
+
method: "post",
|
|
108
|
+
headers: {
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
},
|
|
111
|
+
data: JSON.stringify(body),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const tokenData = await response.data;
|
|
115
|
+
|
|
116
|
+
// Set token and expiry time
|
|
117
|
+
this.token = tokenData.access_token;
|
|
118
|
+
this.tokenExpiry = new Date(now.getTime() + tokenData.expires_in * 1000);
|
|
119
|
+
|
|
120
|
+
return this.token;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return this.handleError("GET TOKEN ERROR", error);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Fetch a resource
|
|
127
|
+
executeApiRequest = async (options, retries = 0) => {
|
|
128
|
+
await this.handleRateLimit(options);
|
|
129
|
+
|
|
130
|
+
const token = await this.getToken();
|
|
131
|
+
if (!token) throw new Error("Error Fetching Token");
|
|
132
|
+
|
|
133
|
+
// Set common headers
|
|
134
|
+
options.headers = {
|
|
135
|
+
Authorization: `Bearer ${token}`,
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
...options.headers, // Merge with any additional headers passed in options
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const res = await axios(options);
|
|
142
|
+
this.lastResponse = res;
|
|
143
|
+
|
|
144
|
+
// Return the data
|
|
145
|
+
if (options.method === "GET") {
|
|
146
|
+
return {
|
|
147
|
+
data: res.data,
|
|
148
|
+
next: res.next,
|
|
149
|
+
previous: res.previous,
|
|
150
|
+
};
|
|
151
|
+
} else {
|
|
152
|
+
return res.data; // For POST and PUT, typically just return the data
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (this.isRetryableError(err) && retries < this.maxRetries) {
|
|
156
|
+
this.handleError(`Network Error Retrying in 2 seconds...`, err.message, false);
|
|
157
|
+
await sleep(2000);
|
|
158
|
+
return this.executeApiRequest(options, retries + 1);
|
|
159
|
+
} else {
|
|
160
|
+
this.handleError(`Failed Request statusText: ${err.response?.statusText}`);
|
|
161
|
+
this.handleError(`Failed data: ${err.response?.data}`);
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Get paginated data
|
|
168
|
+
async getAllData(options) {
|
|
169
|
+
let allData = [];
|
|
170
|
+
while (options.url) {
|
|
171
|
+
const { data } = await this.executeApiRequest(options);
|
|
172
|
+
let next = data["@attributes"].next;
|
|
173
|
+
let selectDataArray = Object.keys(data)[1];
|
|
174
|
+
let selectedData = data[selectDataArray];
|
|
175
|
+
allData = allData.concat(selectedData);
|
|
176
|
+
options.url = next;
|
|
177
|
+
}
|
|
178
|
+
return allData;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check if error is retryable
|
|
182
|
+
isRetryableError = (err) => {
|
|
183
|
+
if (!err.response) {
|
|
184
|
+
// No response (network error or timeout)
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Retry for server errors (500-599)
|
|
189
|
+
return err.response.status >= 500 && err.response.status <= 599;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Get customer by ID
|
|
193
|
+
async getCustomer(id, relations) {
|
|
194
|
+
const options = {
|
|
195
|
+
url: `${this.baseUrl}/${this.accountID}/Customer/${id}.json`,
|
|
196
|
+
method: "GET",
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (!id) return this.handleError("You need to provide a customerID");
|
|
200
|
+
|
|
201
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const response = await this.getAllData(options);
|
|
205
|
+
return response;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return this.handleError("GET CUSTOMERS ERROR", error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Get all customers
|
|
212
|
+
async getCustomers(relations) {
|
|
213
|
+
const options = {
|
|
214
|
+
url: `${this.baseUrl}/${this.accountID}/Customer.json`,
|
|
215
|
+
method: "GET",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const response = await this.getAllData(options);
|
|
222
|
+
return response;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return this.handleError("GET CUSTOMERS ERROR", error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Get item by ID
|
|
229
|
+
async getItem(id, relations) {
|
|
230
|
+
const options = {
|
|
231
|
+
url: `${this.baseUrl}/${this.accountID}/Item/${id}.json`,
|
|
232
|
+
method: "GET",
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
if (!id) return this.handleError("You need to provide a itemID");
|
|
236
|
+
|
|
237
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const response = await this.getAllData(options);
|
|
241
|
+
return response;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return this.handleError("GET ITEM ERROR", error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Get multiple items by ID
|
|
248
|
+
async getMultipleItems(items, relations) {
|
|
249
|
+
const options = {
|
|
250
|
+
url: `${this.baseUrl}/${this.accountID}/Item.json`,
|
|
251
|
+
method: "GET",
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (!items) this.handleError("You need to provide itemID's");
|
|
255
|
+
|
|
256
|
+
if (items) options.url = options.url + `?itemID=IN,${items}`;
|
|
257
|
+
|
|
258
|
+
if (relations) options.url = options.url + `&load_relations=${relations}`;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const response = await this.getAllData(options);
|
|
262
|
+
return response;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return this.handleError("GET ITEMS ERROR", error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Get all items
|
|
269
|
+
async getItems(relations) {
|
|
270
|
+
const options = {
|
|
271
|
+
url: `${this.baseUrl}/${this.accountID}/Item.json`,
|
|
272
|
+
method: "GET",
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const response = await this.getAllData(options);
|
|
279
|
+
return response;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return this.handleError("GET ITEMS ERROR", error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Get all items by vendor
|
|
286
|
+
async getvendorItems(vendorID, relations) {
|
|
287
|
+
const options = {
|
|
288
|
+
url: `${this.baseUrl}/${this.accountID}/Item.json?defaultVendorID=${vendorID}`,
|
|
289
|
+
method: "GET",
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (relations) options.url = options.url + `&load_relations=${relations}`;
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const response = await this.getAllData(options);
|
|
296
|
+
return response;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return this.handleError("GET ITEMS ERROR", error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Get all Matrix Items
|
|
303
|
+
async getMatrixItems(relations) {
|
|
304
|
+
const options = {
|
|
305
|
+
url: `${this.baseUrl}/${this.accountID}/ItemMatrix.json`,
|
|
306
|
+
method: "GET",
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const response = await this.getAllData(options);
|
|
313
|
+
return response;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
return this.handleError("GET ITEM ERROR", error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Get Matrix Item by ID
|
|
320
|
+
async getMatrixItem(id, relations) {
|
|
321
|
+
const options = {
|
|
322
|
+
url: `${this.baseUrl}/${this.accountID}/ItemMatrix/${id}.json`,
|
|
323
|
+
method: "GET",
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
if (!id) return this.handleError("You need to provide a itemID");
|
|
327
|
+
|
|
328
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const response = await this.getAllData(options);
|
|
332
|
+
return response;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return this.handleError("GET ITEM ERROR", error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Get category by ID
|
|
339
|
+
async getCategory(id, relations) {
|
|
340
|
+
const options = {
|
|
341
|
+
url: `${this.baseUrl}/${this.accountID}/Category/${id}.json`,
|
|
342
|
+
method: "GET",
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (!id) return this.handleError("You need to provide a categoryID");
|
|
346
|
+
|
|
347
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const response = await this.getAllData(options);
|
|
351
|
+
return response;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return this.handleError("GET CATEGORY ERROR", error);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Get all categories
|
|
358
|
+
async getCategories(relations) {
|
|
359
|
+
const options = {
|
|
360
|
+
url: `${this.baseUrl}/${this.accountID}/Category.json`,
|
|
361
|
+
method: "GET",
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const response = await this.getAllData(options);
|
|
368
|
+
return response;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return this.handleError("GET CATEGORIES ERROR", error);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Get Manufacturer by ID
|
|
375
|
+
async getManufacturer(id, relations) {
|
|
376
|
+
const options = {
|
|
377
|
+
url: `${this.baseUrl}/${this.accountID}/Manufacturer/${id}.json`,
|
|
378
|
+
method: "GET",
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
if (!id) return this.handleError("You need to provide a manufacturerID");
|
|
382
|
+
|
|
383
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
const response = await this.getAllData(options);
|
|
387
|
+
return response;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return this.handleError("GET MANUFACTURER ERROR", error);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Get all manufacturers
|
|
394
|
+
async getManufacturers(relations) {
|
|
395
|
+
const options = {
|
|
396
|
+
url: `${this.baseUrl}/${this.accountID}/Manufacturer.json`,
|
|
397
|
+
method: "GET",
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
const response = await this.getAllData(options);
|
|
404
|
+
return response;
|
|
405
|
+
} catch (error) {
|
|
406
|
+
return this.handleError("GET MANUFACTURERS ERROR", error);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Get order by ID
|
|
411
|
+
async getOrder(id, relations) {
|
|
412
|
+
const options = {
|
|
413
|
+
url: `${this.baseUrl}/${this.accountID}/Order/${id}.json`,
|
|
414
|
+
method: "GET",
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
if (!id) return this.handleError("You need to provide a orderID");
|
|
418
|
+
|
|
419
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
const response = await this.getAllData(options);
|
|
423
|
+
return response;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return this.handleError("GET ORDER ERROR", error);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Get all orders
|
|
430
|
+
async getOrders(relations) {
|
|
431
|
+
const options = {
|
|
432
|
+
url: `${this.baseUrl}/${this.accountID}/Order.json`,
|
|
433
|
+
method: "GET",
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
const response = await this.getAllData(options);
|
|
440
|
+
return response;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
return this.handleError("GET ORDERS ERROR", error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Get all orders by vendor
|
|
447
|
+
async getOrdersByVendorID(id, relations) {
|
|
448
|
+
const options = {
|
|
449
|
+
url: `${this.baseUrl}/${this.accountID}/Order.json?load_relations=["Vendor"]&vendorID=${id}`,
|
|
450
|
+
method: "GET",
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
if (!id) return this.handleError("You need to provide a vendorID");
|
|
454
|
+
|
|
455
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const response = await this.getAllData(options);
|
|
459
|
+
return response;
|
|
460
|
+
} catch (error) {
|
|
461
|
+
return this.handleError("GET ORDER ERROR", error);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Get all open orders by vendor
|
|
466
|
+
async getOpenOrdersByVendorID(id, relations) {
|
|
467
|
+
const options = {
|
|
468
|
+
url: `${this.baseUrl}/${this.accountID}/Order.json?load_relations=["Vendor", "OrderLines"]&vendorID=${id}&complete=false`,
|
|
469
|
+
method: "GET",
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
if (!id) return this.handleError("You need to provide a vendorID");
|
|
473
|
+
|
|
474
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
const response = await this.getAllData(options);
|
|
478
|
+
return response;
|
|
479
|
+
} catch (error) {
|
|
480
|
+
return this.handleError("GET ORDER ERROR", error);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Get vendor by ID
|
|
485
|
+
async getVendor(id, relations) {
|
|
486
|
+
const options = {
|
|
487
|
+
url: `${this.baseUrl}/${this.accountID}/Vendor/${id}.json`,
|
|
488
|
+
method: "GET",
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
if (!id) return this.handleError("You need to provide a vendorID");
|
|
492
|
+
|
|
493
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const response = await this.getAllData(options);
|
|
497
|
+
return response;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
return this.handleError("GET VENDOR ERROR", error);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Get all vendors
|
|
504
|
+
async getVendors(relations) {
|
|
505
|
+
const options = {
|
|
506
|
+
url: `${this.baseUrl}/${this.accountID}/Vendor.json`,
|
|
507
|
+
method: "GET",
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
const response = await this.getAllData(options);
|
|
514
|
+
return response;
|
|
515
|
+
} catch (error) {
|
|
516
|
+
return this.handleError("GET VENDORS ERROR", error);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Get sale by ID
|
|
521
|
+
async getSale(id, relations) {
|
|
522
|
+
const options = {
|
|
523
|
+
url: `${this.baseUrl}/${this.accountID}/Sale/${id}.json`,
|
|
524
|
+
method: "GET",
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
if (!id) return this.handleError("You need to provide a saleID");
|
|
528
|
+
|
|
529
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const response = await this.getAllData(options);
|
|
533
|
+
return response;
|
|
534
|
+
} catch (error) {
|
|
535
|
+
return this.handleError("GET SALE ERROR", error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Get all sales
|
|
540
|
+
async getSales(relations) {
|
|
541
|
+
const options = {
|
|
542
|
+
url: `${this.baseUrl}/${this.accountID}/Sale.json`,
|
|
543
|
+
method: "GET",
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const response = await this.getAllData(options);
|
|
550
|
+
return response;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
return this.handleError("GET SALES ERROR", error);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async getMultipleSales(saleIDs, relations) {
|
|
557
|
+
const options = {
|
|
558
|
+
url: `${this.baseUrl}/${this.accountID}/Sale.json?saleID=IN,${saleIDs}`,
|
|
559
|
+
method: "GET",
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
if (relations) options.url = options.url + `?load_relations=${relations}`;
|
|
563
|
+
|
|
564
|
+
try {
|
|
565
|
+
const response = await this.getAllData(options);
|
|
566
|
+
return response;
|
|
567
|
+
} catch (error) {
|
|
568
|
+
return this.handleError("GET SALE ERROR", error);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async getSaleLinesByItem(itemID, relations) {
|
|
573
|
+
const options = {
|
|
574
|
+
url: `${this.baseUrl}/${this.accountID}/SaleLine.json?itemID=${itemID}`,
|
|
575
|
+
method: "GET",
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
if (relations) options.url = options.url + `&load_relations=${relations}`;
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
const response = await this.getAllData(options);
|
|
582
|
+
return response;
|
|
583
|
+
} catch (error) {
|
|
584
|
+
return this.handleError("GET SALE ERROR", error);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Get sales lines by item ID's and date range
|
|
589
|
+
async getSaleLinesByItems(ids, startDate = undefined, endDate = undefined, relations) {
|
|
590
|
+
const options = {
|
|
591
|
+
url: `${this.baseUrl}/${this.accountID}/SaleLine.json?itemID=IN,[${ids}]`,
|
|
592
|
+
method: "GET",
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
if (!ids) return this.handleError("You need to provide itemIDs");
|
|
596
|
+
if (startDate && !endDate) return this.handleError("You need to provide an end date");
|
|
597
|
+
if (endDate && !startDate)
|
|
598
|
+
return this.handleError("You need to provide a start date");
|
|
599
|
+
|
|
600
|
+
if (relations) options.url = options.url + `&load_relations=${relations}`;
|
|
601
|
+
|
|
602
|
+
if (startDate && endDate)
|
|
603
|
+
options.url =
|
|
604
|
+
options.url + `&timeStamp=%3E%3C%2C${startDate}%2C${endDate}&sort=timeStamp`;
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
const response = await this.getAllData(options);
|
|
608
|
+
return response;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
return this.handleError("GET SALE ERROR", error);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Get sales lines by vendor ID's and date range
|
|
615
|
+
async getSaleLinesByVendorID(
|
|
616
|
+
id,
|
|
617
|
+
startDate = undefined,
|
|
618
|
+
endDate = undefined,
|
|
619
|
+
relations
|
|
620
|
+
) {
|
|
621
|
+
const options = {
|
|
622
|
+
url: `${this.baseUrl}/${this.accountID}/SaleLine.json?load_relations=${
|
|
623
|
+
relations ? relations : `["Item"]`
|
|
624
|
+
}&Item.defaultVendorID=${id}`,
|
|
625
|
+
method: "GET",
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
if (!id) return this.handleError("You need to provide a vendorID");
|
|
629
|
+
if (startDate && !endDate) return this.handleError("You need to provide an end date");
|
|
630
|
+
if (endDate && !startDate)
|
|
631
|
+
return this.handleError("You need to provide a start date");
|
|
632
|
+
|
|
633
|
+
if (startDate && endDate)
|
|
634
|
+
options.url =
|
|
635
|
+
options.url + `&timeStamp=%3E%3C%2C${startDate}%2C${endDate}&sort=timeStamp`;
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
const response = await this.getAllData(options);
|
|
639
|
+
return response;
|
|
640
|
+
} catch (error) {
|
|
641
|
+
return this.handleError("GET SALE ERROR", error);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
module.exports = LightspeedRetailSDK;
|
package/{index.js → index.mjs}
RENAMED
|
@@ -6,6 +6,9 @@ const getRequestUnits = (operation) => operationUnits[operation] || 10;
|
|
|
6
6
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
7
|
|
|
8
8
|
class LightspeedRetailSDK {
|
|
9
|
+
static BASE_URL = "https://api.lightspeedapp.com/API/V3/Account";
|
|
10
|
+
static TOKEN_URL = "https://cloud.lightspeedapp.com/oauth/access_token.php";
|
|
11
|
+
|
|
9
12
|
constructor(opts) {
|
|
10
13
|
const { clientID, clientSecret, refreshToken, accountID } = opts;
|
|
11
14
|
|
|
@@ -13,7 +16,8 @@ class LightspeedRetailSDK {
|
|
|
13
16
|
this.clientSecret = clientSecret;
|
|
14
17
|
this.refreshToken = refreshToken;
|
|
15
18
|
this.accountID = accountID;
|
|
16
|
-
this.baseUrl =
|
|
19
|
+
this.baseUrl = LightspeedRetailSDK.BASE_URL;
|
|
20
|
+
this.tokenUrl = LightspeedRetailSDK.TOKEN_URL;
|
|
17
21
|
this.maxRetries = 3;
|
|
18
22
|
this.lastResponse = null;
|
|
19
23
|
this.token = null;
|
|
@@ -21,12 +25,30 @@ class LightspeedRetailSDK {
|
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
// handleError function to handle errors
|
|
24
|
-
handleError(
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
handleError(context, err, shouldThrow = true) {
|
|
29
|
+
// Context includes information about where the error occurred
|
|
30
|
+
const detailedMessage = `Error in ${context}: ${err.message}`;
|
|
31
|
+
|
|
32
|
+
// Log the error message
|
|
33
|
+
console.error(detailedMessage);
|
|
34
|
+
|
|
35
|
+
// Log the stack trace if available
|
|
36
|
+
if (err.stack) {
|
|
37
|
+
console.error("Stack trace:", err.stack);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If the error has response data, log it
|
|
41
|
+
if (err.response) {
|
|
42
|
+
console.error("Error response:", {
|
|
43
|
+
status: err.response.status,
|
|
44
|
+
headers: err.response.headers,
|
|
45
|
+
data: err.response.data,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
27
48
|
|
|
49
|
+
// Optionally rethrow the error with the detailed message
|
|
28
50
|
if (shouldThrow) {
|
|
29
|
-
throw new Error(
|
|
51
|
+
throw new Error(detailedMessage);
|
|
30
52
|
}
|
|
31
53
|
}
|
|
32
54
|
|
|
@@ -64,9 +86,10 @@ class LightspeedRetailSDK {
|
|
|
64
86
|
// Get a new token
|
|
65
87
|
getToken = async () => {
|
|
66
88
|
const now = new Date();
|
|
89
|
+
const bufferTime = 1 * 60 * 1000; // 1 minute buffer
|
|
67
90
|
|
|
68
91
|
// Check if the token exists and is still valid
|
|
69
|
-
if (this.token && this.tokenExpiry
|
|
92
|
+
if (this.token && this.tokenExpiry.getTime() - now.getTime() > bufferTime) {
|
|
70
93
|
return this.token;
|
|
71
94
|
}
|
|
72
95
|
|
|
@@ -80,7 +103,7 @@ class LightspeedRetailSDK {
|
|
|
80
103
|
|
|
81
104
|
try {
|
|
82
105
|
const response = await axios({
|
|
83
|
-
url:
|
|
106
|
+
url: this.tokenUrl,
|
|
84
107
|
method: "post",
|
|
85
108
|
headers: {
|
|
86
109
|
"Content-Type": "application/json",
|
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightspeed-retail-sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "Another unofficial Lightspeed Retail API SDK for Node.js",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.cjs",
|
|
7
|
+
"module": "index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"require": "./index.cjs",
|
|
11
|
+
"default": "./index.mjs"
|
|
12
|
+
}
|
|
8
13
|
},
|
|
9
|
-
"author": "
|
|
14
|
+
"author": "https://github.com/darrylmorley",
|
|
10
15
|
"license": "ISC",
|
|
11
16
|
"dependencies": {
|
|
12
17
|
"axios": "^1.3.4",
|
|
13
|
-
"
|
|
14
|
-
"retry": "^0.13.1"
|
|
18
|
+
"dotenv": "^16.4.4"
|
|
15
19
|
},
|
|
16
|
-
"type": "module",
|
|
17
20
|
"repository": {
|
|
18
21
|
"type": "git",
|
|
19
|
-
"url": "https://github.com/darrylmorley/lightspeed-retail-sdk"
|
|
22
|
+
"url": "git+https://github.com/darrylmorley/lightspeed-retail-sdk.git"
|
|
20
23
|
},
|
|
21
24
|
"keywords": [
|
|
22
25
|
"lightspeed retail",
|
|
23
26
|
"node"
|
|
24
|
-
]
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"dotenv": "^16.3.1"
|
|
27
|
-
}
|
|
27
|
+
]
|
|
28
28
|
}
|