lightspeed-retail-sdk 2.0.6 → 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 +55 -30
- package/index.cjs +646 -0
- package/{index.js → index.mjs} +109 -48
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
# Another Unofficial Lightspeed Retail V3 API SDK
|
|
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
|
-
|
|
5
|
+
## Update
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
We currently have the following methods available,
|
|
7
|
+
I have updated this package so it can be used with either index.cjs or index.mjs for imports.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
getManufacturers(relations?: string)
|
|
23
|
-
getOrder(id: int, relations?: string)
|
|
24
|
-
getOrders(relations?: string)
|
|
25
|
-
getOrdersByVendorID(id: int, relations?: string)
|
|
26
|
-
getOpenOrdersByVendorID(id: int, relations?: string)
|
|
27
|
-
getVendor(id: int, relations?: string)
|
|
28
|
-
getVendors(relations?: string)
|
|
29
|
-
getSale(id: int, relations?: string)
|
|
30
|
-
getSales(relations?: string)
|
|
31
|
-
getMultipleSales(ids: string = "[102, 103]", relations?: string)
|
|
32
|
-
getSaleLinesByItem(id: int, relations?: string)
|
|
33
|
-
getSaleLinesByItems(ids: string = `39, 2126, 3505`, startDate?, endDate?, relations?: string)
|
|
34
|
-
getSaleLinesByVendorID(id: int, startDate?, endDate?, relations?: string)
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Easy-to-use methods for interacting with various Lightspeed Retail endpoints.
|
|
12
|
+
- Built-in handling of API rate limits.
|
|
13
|
+
- Automatic token management for authentication.
|
|
14
|
+
- Support for paginated responses from the Lightspeed API.
|
|
15
|
+
- Retry logic for handling transient network issues.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install lightspeed-retail-sdk
|
|
35
21
|
```
|
|
36
22
|
|
|
37
23
|
## Get started:
|
|
38
24
|
|
|
39
25
|
```
|
|
40
|
-
import LightspeedRetailSDK from "lightspeed-retail-sdk";
|
|
26
|
+
import LightspeedRetailSDK from "lightspeed-retail-sdk/index.mjs";
|
|
41
27
|
|
|
42
28
|
const api = new LightspeedRetailSDK({
|
|
43
29
|
accountID: "Your Account No.",
|
|
@@ -58,6 +44,45 @@ console.log(item);
|
|
|
58
44
|
7497 being the itemID. You can pass required relations as above.
|
|
59
45
|
```
|
|
60
46
|
|
|
47
|
+
## Methods
|
|
48
|
+
|
|
49
|
+
- `getCustomer(id, relations)`: Fetches a specific customer by ID. Optionally, related data can be included.
|
|
50
|
+
- `getCustomers(relations)`: Retrieves all customers. Optionally, related data can be included.
|
|
51
|
+
- `getItem(id, relations)`: Fetches a specific item by ID. Optionally, related data can be included.
|
|
52
|
+
- `getMultipleItems(items, relations)`: Retrieves multiple items by their IDs. Optionally, related data can be included.
|
|
53
|
+
- `getItems(relations)`: Retrieves all items. Optionally, related data can be included.
|
|
54
|
+
- `getvendorItems(vendorID, relations)`: Retrieves all items for a specific vendor. Optionally, related data can be included.
|
|
55
|
+
- `getMatrixItems(relations)`: Fetches all matrix items. Optionally, related data can be included.
|
|
56
|
+
- `getMatrixItem(id, relations)`: Fetches a specific matrix item by ID. Optionally, related data can be included.
|
|
57
|
+
- `getCategory(id, relations)`: Retrieves a specific category by ID. Optionally, related data can be included.
|
|
58
|
+
- `getCategories(relations)`: Retrieves all categories. Optionally, related data can be included.
|
|
59
|
+
- `getManufacturer(id, relations)`: Fetches a specific manufacturer by ID. Optionally, related data can be included.
|
|
60
|
+
- `getManufacturers(relations)`: Retrieves all manufacturers. Optionally, related data can be included.
|
|
61
|
+
- `getOrder(id, relations)`: Fetches a specific order by ID. Optionally, related data can be included.
|
|
62
|
+
- `getOrders(relations)`: Retrieves all orders. Optionally, related data can be included.
|
|
63
|
+
- `getOrdersByVendorID(id, relations)`: Retrieves all orders for a specific vendor. Optionally, related data can be included.
|
|
64
|
+
- `getOpenOrdersByVendorID(id, relations)`: Fetches all open orders for a specific vendor. Optionally, related data can be included.
|
|
65
|
+
- `getVendor(id, relations)`: Fetches a specific vendor by ID. Optionally, related data can be included.
|
|
66
|
+
- `getVendors(relations)`: Retrieves all vendors. Optionally, related data can be included.
|
|
67
|
+
- `getSale(id, relations)`: Fetches a specific sale by ID. Optionally, related data can be included.
|
|
68
|
+
- `getSales(relations)`: Retrieves all sales. Optionally, related data can be included.
|
|
69
|
+
- `getMultipleSales(saleIDs, relations)`: Fetches multiple sales by their IDs. Optionally, related data can be included.
|
|
70
|
+
- `getSaleLinesByItem(itemID, relations)`: Retrieves sale lines for a specific item. Optionally, related data can be included.
|
|
71
|
+
- `getSaleLinesByItems(ids, startDate, endDate, relations)`: Retrieves sale lines for multiple items, filtered by date range. Optionally, related data can be included.
|
|
72
|
+
- `getSaleLinesByVendorID(id, startDate, endDate, relations)`: Fetches sale lines for a specific vendor, filtered by date range. Optionally, related data can be included.
|
|
73
|
+
|
|
74
|
+
## Contributing
|
|
75
|
+
|
|
76
|
+
Contributions are welcome!
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
81
|
+
|
|
82
|
+
## Disclaimer
|
|
83
|
+
|
|
84
|
+
This SDK is not officially affiliated with Lightspeed HQ and is provided "as is" with no warranty.
|
|
85
|
+
|
|
61
86
|
## More Info
|
|
62
87
|
|
|
63
88
|
The documentation for the Lightspeed Retail API can be found at https://developers.lightspeedhq.com/retail/introduction/introduction/
|
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
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const getRequestUnits = (operation) =>
|
|
5
|
-
switch (operation) {
|
|
6
|
-
case "GET":
|
|
7
|
-
return 1;
|
|
8
|
-
case "POST":
|
|
9
|
-
return 10;
|
|
10
|
-
case "PUT":
|
|
11
|
-
return 10;
|
|
12
|
-
default:
|
|
13
|
-
return 10;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
3
|
+
const operationUnits = { GET: 1, POST: 10, PUT: 10 };
|
|
4
|
+
const getRequestUnits = (operation) => operationUnits[operation] || 10;
|
|
16
5
|
|
|
17
6
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
7
|
|
|
19
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
|
+
|
|
20
12
|
constructor(opts) {
|
|
21
13
|
const { clientID, clientSecret, refreshToken, accountID } = opts;
|
|
22
14
|
|
|
@@ -24,23 +16,50 @@ class LightspeedRetailSDK {
|
|
|
24
16
|
this.clientSecret = clientSecret;
|
|
25
17
|
this.refreshToken = refreshToken;
|
|
26
18
|
this.accountID = accountID;
|
|
27
|
-
this.baseUrl =
|
|
19
|
+
this.baseUrl = LightspeedRetailSDK.BASE_URL;
|
|
20
|
+
this.tokenUrl = LightspeedRetailSDK.TOKEN_URL;
|
|
28
21
|
this.maxRetries = 3;
|
|
29
22
|
this.lastResponse = null;
|
|
23
|
+
this.token = null;
|
|
24
|
+
this.tokenExpiry = null;
|
|
30
25
|
}
|
|
31
26
|
|
|
32
|
-
handleError
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
}
|
|
35
53
|
}
|
|
36
54
|
|
|
55
|
+
// Update the last response
|
|
37
56
|
setLastResponse = (response) => (this.lastResponse = response);
|
|
38
57
|
|
|
58
|
+
// Handle rate limits
|
|
39
59
|
handleRateLimit = async (options) => {
|
|
40
60
|
if (!this.lastResponse) return null;
|
|
41
61
|
|
|
42
62
|
const { method } = options;
|
|
43
|
-
|
|
44
63
|
const requestUnits = getRequestUnits(method);
|
|
45
64
|
const rateHeader = this.lastResponse.headers["x-ls-api-bucket-level"];
|
|
46
65
|
|
|
@@ -50,7 +69,13 @@ class LightspeedRetailSDK {
|
|
|
50
69
|
const availableUnits = available - used;
|
|
51
70
|
if (requestUnits <= availableUnits) return 0;
|
|
52
71
|
|
|
53
|
-
const dripRate = this.lastResponse.headers["x-ls-api-drip-rate"];
|
|
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
|
+
|
|
54
79
|
const unitWait = requestUnits - availableUnits;
|
|
55
80
|
const delay = Math.ceil((unitWait / dripRate) * 1000);
|
|
56
81
|
await sleep(delay);
|
|
@@ -58,7 +83,17 @@ class LightspeedRetailSDK {
|
|
|
58
83
|
return unitWait;
|
|
59
84
|
};
|
|
60
85
|
|
|
86
|
+
// Get a new token
|
|
61
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
|
|
62
97
|
const body = {
|
|
63
98
|
grant_type: "refresh_token",
|
|
64
99
|
client_id: this.clientID,
|
|
@@ -66,68 +101,94 @@ class LightspeedRetailSDK {
|
|
|
66
101
|
refresh_token: this.refreshToken,
|
|
67
102
|
};
|
|
68
103
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
+
}
|
|
82
124
|
};
|
|
83
125
|
|
|
84
|
-
|
|
85
|
-
|
|
126
|
+
// Fetch a resource
|
|
127
|
+
executeApiRequest = async (options, retries = 0) => {
|
|
128
|
+
await this.handleRateLimit(options);
|
|
86
129
|
|
|
87
130
|
const token = await this.getToken();
|
|
88
|
-
|
|
89
131
|
if (!token) throw new Error("Error Fetching Token");
|
|
90
132
|
|
|
133
|
+
// Set common headers
|
|
91
134
|
options.headers = {
|
|
92
135
|
Authorization: `Bearer ${token}`,
|
|
93
136
|
"Content-Type": "application/json",
|
|
137
|
+
...options.headers, // Merge with any additional headers passed in options
|
|
94
138
|
};
|
|
95
139
|
|
|
96
140
|
try {
|
|
97
141
|
const res = await axios(options);
|
|
98
142
|
this.lastResponse = res;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
}
|
|
104
154
|
} catch (err) {
|
|
105
|
-
if (retries < this.maxRetries) {
|
|
106
|
-
|
|
107
|
-
await
|
|
108
|
-
return
|
|
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);
|
|
109
159
|
} else {
|
|
110
|
-
|
|
111
|
-
|
|
160
|
+
this.handleError(`Failed Request statusText: ${err.response?.statusText}`);
|
|
161
|
+
this.handleError(`Failed data: ${err.response?.data}`);
|
|
112
162
|
throw err;
|
|
113
163
|
}
|
|
114
164
|
}
|
|
115
165
|
};
|
|
116
166
|
|
|
167
|
+
// Get paginated data
|
|
117
168
|
async getAllData(options) {
|
|
118
169
|
let allData = [];
|
|
119
170
|
while (options.url) {
|
|
120
|
-
const { data } = await this.
|
|
171
|
+
const { data } = await this.executeApiRequest(options);
|
|
121
172
|
let next = data["@attributes"].next;
|
|
122
173
|
let selectDataArray = Object.keys(data)[1];
|
|
123
174
|
let selectedData = data[selectDataArray];
|
|
124
175
|
allData = allData.concat(selectedData);
|
|
125
176
|
options.url = next;
|
|
126
177
|
}
|
|
127
|
-
// console.log(allData);
|
|
128
178
|
return allData;
|
|
129
179
|
}
|
|
130
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
|
+
|
|
131
192
|
// Get customer by ID
|
|
132
193
|
async getCustomer(id, relations) {
|
|
133
194
|
const options = {
|
package/package.json
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
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",
|