@commercengine/pos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +552 -0
- package/dist/index.d.ts +5040 -0
- package/dist/index.js +1152 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1152 @@
|
|
|
1
|
+
import { BaseAPIClient, ResponseUtils, getPathnameFromUrl } from "@commercengine/sdk-core";
|
|
2
|
+
import { decodeJwt } from "jose";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/utils/jwt.ts
|
|
5
|
+
/**
|
|
6
|
+
* Decode and extract user information from a JWT token
|
|
7
|
+
*
|
|
8
|
+
* @param token - The JWT token to decode
|
|
9
|
+
* @returns User information or null if token is invalid
|
|
10
|
+
*/
|
|
11
|
+
function extractUserInfoFromToken(token) {
|
|
12
|
+
try {
|
|
13
|
+
const payload = decodeJwt(token);
|
|
14
|
+
return {
|
|
15
|
+
id: payload.ulid,
|
|
16
|
+
username: payload.username,
|
|
17
|
+
email: payload.email,
|
|
18
|
+
phone: payload.phone,
|
|
19
|
+
firstName: payload.first_name,
|
|
20
|
+
lastName: payload.last_name,
|
|
21
|
+
storeId: payload.store_id,
|
|
22
|
+
isLoggedIn: payload.is_logged_in,
|
|
23
|
+
customerId: payload.customer_id,
|
|
24
|
+
customerGroupId: payload.customer_group_id,
|
|
25
|
+
device: {
|
|
26
|
+
deviceId: payload.device.device_id,
|
|
27
|
+
deviceName: payload.device.device_name
|
|
28
|
+
},
|
|
29
|
+
location: {
|
|
30
|
+
id: payload.location.id,
|
|
31
|
+
name: payload.location.name
|
|
32
|
+
},
|
|
33
|
+
role: {
|
|
34
|
+
id: payload.role.id,
|
|
35
|
+
name: payload.role.name
|
|
36
|
+
},
|
|
37
|
+
tenantId: payload.tenant_id,
|
|
38
|
+
channel: {
|
|
39
|
+
id: payload.channel.id,
|
|
40
|
+
name: payload.channel.name,
|
|
41
|
+
type: payload.channel.type
|
|
42
|
+
},
|
|
43
|
+
sessionId: payload.sid,
|
|
44
|
+
tokenExpiry: /* @__PURE__ */ new Date(payload.exp * 1e3),
|
|
45
|
+
tokenIssuedAt: /* @__PURE__ */ new Date(payload.iat * 1e3)
|
|
46
|
+
};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn("Failed to decode JWT token:", error);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a JWT token is expired
|
|
54
|
+
*
|
|
55
|
+
* @param token - The JWT token to check
|
|
56
|
+
* @param bufferSeconds - Buffer time in seconds (default: 30)
|
|
57
|
+
* @returns True if token is expired or will expire within buffer time
|
|
58
|
+
*/
|
|
59
|
+
function isTokenExpired(token, bufferSeconds = 30) {
|
|
60
|
+
try {
|
|
61
|
+
const payload = decodeJwt(token);
|
|
62
|
+
if (!payload.exp) return true;
|
|
63
|
+
const currentTime = Math.floor(Date.now() / 1e3);
|
|
64
|
+
const expiryTime = payload.exp;
|
|
65
|
+
return currentTime >= expiryTime - bufferSeconds;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.warn("Failed to decode JWT token:", error);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the user ID from a JWT token
|
|
73
|
+
*
|
|
74
|
+
* @param token - The JWT token
|
|
75
|
+
* @returns User ID (ulid) or null if token is invalid
|
|
76
|
+
*/
|
|
77
|
+
function getUserIdFromToken(token) {
|
|
78
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
79
|
+
return userInfo?.id || null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if user is logged in based on JWT token
|
|
83
|
+
*
|
|
84
|
+
* @param token - The JWT token
|
|
85
|
+
* @returns True if user is logged in, false otherwise
|
|
86
|
+
*/
|
|
87
|
+
function isUserLoggedIn(token) {
|
|
88
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
89
|
+
return userInfo?.isLoggedIn || false;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if user is authenticated (has valid POS token)
|
|
93
|
+
* Checks the token for isLoggedIn status
|
|
94
|
+
*
|
|
95
|
+
* @param token - The JWT token
|
|
96
|
+
* @returns True if user is authenticated with valid POS access
|
|
97
|
+
*/
|
|
98
|
+
function isUserAuthenticated(token) {
|
|
99
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
100
|
+
return !!userInfo?.isLoggedIn;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the device ID from a POS JWT token
|
|
104
|
+
*
|
|
105
|
+
* @param token - The JWT token
|
|
106
|
+
* @returns Device ID or null if token is invalid
|
|
107
|
+
*/
|
|
108
|
+
function getDeviceIdFromToken(token) {
|
|
109
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
110
|
+
return userInfo?.device?.deviceId || null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the location ID from a POS JWT token
|
|
114
|
+
*
|
|
115
|
+
* @param token - The JWT token
|
|
116
|
+
* @returns Location ID or null if token is invalid
|
|
117
|
+
*/
|
|
118
|
+
function getLocationIdFromToken(token) {
|
|
119
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
120
|
+
return userInfo?.location?.id || null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get the role information from a POS JWT token
|
|
124
|
+
*
|
|
125
|
+
* @param token - The JWT token
|
|
126
|
+
* @returns Role object with id and name, or null if token is invalid
|
|
127
|
+
*/
|
|
128
|
+
function getRoleFromToken(token) {
|
|
129
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
130
|
+
return userInfo?.role || null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get the tenant ID from a POS JWT token
|
|
134
|
+
*
|
|
135
|
+
* @param token - The JWT token
|
|
136
|
+
* @returns Tenant ID or null if token is invalid
|
|
137
|
+
*/
|
|
138
|
+
function getTenantIdFromToken(token) {
|
|
139
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
140
|
+
return userInfo?.tenantId || null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/lib/utils/auth.ts
|
|
145
|
+
/**
|
|
146
|
+
* Check if a URL path is a POS login endpoint that should use API key
|
|
147
|
+
* Based on POS spec, only these 5 endpoints use X-Api-Key:
|
|
148
|
+
* - /pos/auth/login/email
|
|
149
|
+
* - /pos/auth/login/phone
|
|
150
|
+
* - /pos/auth/login/whatsapp
|
|
151
|
+
* - /pos/auth/pair-device
|
|
152
|
+
* - /pos/auth/verify-otp
|
|
153
|
+
*/
|
|
154
|
+
function isApiKeyEndpoint(pathname) {
|
|
155
|
+
const apiKeyEndpoints = [
|
|
156
|
+
"/pos/auth/login/email",
|
|
157
|
+
"/pos/auth/login/phone",
|
|
158
|
+
"/pos/auth/login/whatsapp",
|
|
159
|
+
"/pos/auth/pair-device",
|
|
160
|
+
"/pos/auth/verify-otp"
|
|
161
|
+
];
|
|
162
|
+
return apiKeyEndpoints.some((endpoint) => pathname.includes(endpoint));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check if a URL path is a POS endpoint that returns tokens
|
|
166
|
+
* Based on POS spec, only these endpoints return tokens:
|
|
167
|
+
* - /pos/auth/verify-otp (after OTP verification)
|
|
168
|
+
* - /pos/auth/refresh-token
|
|
169
|
+
*/
|
|
170
|
+
function isTokenReturningEndpoint(pathname) {
|
|
171
|
+
const tokenEndpoints = ["/pos/auth/verify-otp", "/pos/auth/refresh-token"];
|
|
172
|
+
return tokenEndpoints.some((endpoint) => pathname.includes(endpoint));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Check if a URL path is a POS logout endpoint
|
|
176
|
+
*/
|
|
177
|
+
function isLogoutEndpoint(pathname) {
|
|
178
|
+
return pathname.includes("/pos/auth/logout") || pathname.includes("/pos/logout");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/lib/middleware/auth.ts
|
|
183
|
+
/**
|
|
184
|
+
* Simple in-memory token storage implementation
|
|
185
|
+
*/
|
|
186
|
+
var MemoryTokenStorage = class {
|
|
187
|
+
accessToken = null;
|
|
188
|
+
refreshToken = null;
|
|
189
|
+
async getAccessToken() {
|
|
190
|
+
return this.accessToken;
|
|
191
|
+
}
|
|
192
|
+
async setAccessToken(token) {
|
|
193
|
+
this.accessToken = token;
|
|
194
|
+
}
|
|
195
|
+
async getRefreshToken() {
|
|
196
|
+
return this.refreshToken;
|
|
197
|
+
}
|
|
198
|
+
async setRefreshToken(token) {
|
|
199
|
+
this.refreshToken = token;
|
|
200
|
+
}
|
|
201
|
+
async clearTokens() {
|
|
202
|
+
this.accessToken = null;
|
|
203
|
+
this.refreshToken = null;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Browser localStorage token storage implementation for POS
|
|
208
|
+
*/
|
|
209
|
+
var BrowserTokenStorage = class {
|
|
210
|
+
accessTokenKey;
|
|
211
|
+
refreshTokenKey;
|
|
212
|
+
constructor(prefix = "pos_") {
|
|
213
|
+
this.accessTokenKey = `${prefix}access_token`;
|
|
214
|
+
this.refreshTokenKey = `${prefix}refresh_token`;
|
|
215
|
+
}
|
|
216
|
+
async getAccessToken() {
|
|
217
|
+
if (typeof localStorage === "undefined") return null;
|
|
218
|
+
return localStorage.getItem(this.accessTokenKey);
|
|
219
|
+
}
|
|
220
|
+
async setAccessToken(token) {
|
|
221
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(this.accessTokenKey, token);
|
|
222
|
+
}
|
|
223
|
+
async getRefreshToken() {
|
|
224
|
+
if (typeof localStorage === "undefined") return null;
|
|
225
|
+
return localStorage.getItem(this.refreshTokenKey);
|
|
226
|
+
}
|
|
227
|
+
async setRefreshToken(token) {
|
|
228
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(this.refreshTokenKey, token);
|
|
229
|
+
}
|
|
230
|
+
async clearTokens() {
|
|
231
|
+
if (typeof localStorage !== "undefined") {
|
|
232
|
+
localStorage.removeItem(this.accessTokenKey);
|
|
233
|
+
localStorage.removeItem(this.refreshTokenKey);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Create POS authentication middleware for openapi-fetch
|
|
239
|
+
*
|
|
240
|
+
* POS Authentication Rules:
|
|
241
|
+
* 1. API Key endpoints (X-Api-Key): login/email, login/phone, login/whatsapp, pair-device, verify-otp
|
|
242
|
+
* 2. Bearer token endpoints: All other endpoints
|
|
243
|
+
* 3. Token returning endpoints: verify-otp, refresh-token
|
|
244
|
+
*/
|
|
245
|
+
function createPosAuthMiddleware(config) {
|
|
246
|
+
let isRefreshing = false;
|
|
247
|
+
let refreshPromise = null;
|
|
248
|
+
let hasAssessedTokens = false;
|
|
249
|
+
const assessTokenStateOnce = async () => {
|
|
250
|
+
if (hasAssessedTokens) return;
|
|
251
|
+
hasAssessedTokens = true;
|
|
252
|
+
try {
|
|
253
|
+
const accessToken = await config.tokenStorage.getAccessToken();
|
|
254
|
+
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
255
|
+
if (accessToken && !isTokenExpired(accessToken)) return;
|
|
256
|
+
if (!accessToken && refreshToken) {
|
|
257
|
+
await config.tokenStorage.clearTokens();
|
|
258
|
+
config.onTokensCleared?.();
|
|
259
|
+
console.info("Cleaned up orphaned refresh token in POS");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (accessToken && refreshToken && !isTokenExpired(refreshToken)) {
|
|
263
|
+
try {
|
|
264
|
+
await refreshTokens();
|
|
265
|
+
console.info("POS tokens refreshed proactively on startup");
|
|
266
|
+
} catch (error) {
|
|
267
|
+
await config.tokenStorage.clearTokens();
|
|
268
|
+
config.onTokensCleared?.();
|
|
269
|
+
console.info("POS tokens cleared after failed refresh on startup");
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (accessToken && isTokenExpired(accessToken) || refreshToken && isTokenExpired(refreshToken)) {
|
|
274
|
+
await config.tokenStorage.clearTokens();
|
|
275
|
+
config.onTokensCleared?.();
|
|
276
|
+
console.info("POS stale tokens cleared on startup - user needs to re-authenticate");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!accessToken && !refreshToken) return;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.warn("POS token state assessment failed:", error);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const refreshTokens = async () => {
|
|
285
|
+
if (isRefreshing && refreshPromise) return refreshPromise;
|
|
286
|
+
isRefreshing = true;
|
|
287
|
+
refreshPromise = (async () => {
|
|
288
|
+
try {
|
|
289
|
+
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
290
|
+
if (!refreshToken || isTokenExpired(refreshToken)) throw new Error("No valid refresh token available");
|
|
291
|
+
let newTokens;
|
|
292
|
+
if (config.refreshTokenFn) newTokens = await config.refreshTokenFn(refreshToken);
|
|
293
|
+
else {
|
|
294
|
+
const response = await fetch(`${config.baseUrl}/pos/auth/refresh-token`, {
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers: {
|
|
297
|
+
"Content-Type": "application/json",
|
|
298
|
+
Authorization: `Bearer ${await config.tokenStorage.getAccessToken()}`
|
|
299
|
+
},
|
|
300
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
301
|
+
});
|
|
302
|
+
if (!response.ok) throw new Error(`POS token refresh failed: ${response.status}`);
|
|
303
|
+
const data = await response.json();
|
|
304
|
+
newTokens = data.content || data;
|
|
305
|
+
}
|
|
306
|
+
await config.tokenStorage.setAccessToken(newTokens.access_token);
|
|
307
|
+
await config.tokenStorage.setRefreshToken(newTokens.refresh_token);
|
|
308
|
+
config.onTokensUpdated?.(newTokens.access_token, newTokens.refresh_token);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error("POS token refresh failed:", error);
|
|
311
|
+
await config.tokenStorage.clearTokens();
|
|
312
|
+
config.onTokensCleared?.();
|
|
313
|
+
throw error;
|
|
314
|
+
} finally {
|
|
315
|
+
isRefreshing = false;
|
|
316
|
+
refreshPromise = null;
|
|
317
|
+
}
|
|
318
|
+
})();
|
|
319
|
+
return refreshPromise;
|
|
320
|
+
};
|
|
321
|
+
return {
|
|
322
|
+
async onRequest({ request }) {
|
|
323
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
324
|
+
await assessTokenStateOnce();
|
|
325
|
+
if (isApiKeyEndpoint(pathname)) {
|
|
326
|
+
request.headers.set("X-Api-Key", config.apiKey);
|
|
327
|
+
return request;
|
|
328
|
+
}
|
|
329
|
+
let accessToken = await config.tokenStorage.getAccessToken();
|
|
330
|
+
if (accessToken && isTokenExpired(accessToken)) try {
|
|
331
|
+
await refreshTokens();
|
|
332
|
+
accessToken = await config.tokenStorage.getAccessToken();
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.warn("Token refresh failed:", error);
|
|
335
|
+
accessToken = null;
|
|
336
|
+
}
|
|
337
|
+
if (accessToken) request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
338
|
+
return request;
|
|
339
|
+
},
|
|
340
|
+
async onResponse({ request, response }) {
|
|
341
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
342
|
+
if (response.ok && isTokenReturningEndpoint(pathname)) try {
|
|
343
|
+
const data = await response.clone().json();
|
|
344
|
+
const content = data.content || data;
|
|
345
|
+
if (content?.access_token && content?.refresh_token) {
|
|
346
|
+
await config.tokenStorage.setAccessToken(content.access_token);
|
|
347
|
+
await config.tokenStorage.setRefreshToken(content.refresh_token);
|
|
348
|
+
config.onTokensUpdated?.(content.access_token, content.refresh_token);
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.warn("Failed to extract tokens from POS response:", error);
|
|
352
|
+
}
|
|
353
|
+
else if (response.ok && isLogoutEndpoint(pathname)) {
|
|
354
|
+
await config.tokenStorage.clearTokens();
|
|
355
|
+
config.onTokensCleared?.();
|
|
356
|
+
}
|
|
357
|
+
if (response.status === 401 && !isApiKeyEndpoint(pathname)) {
|
|
358
|
+
const currentToken = await config.tokenStorage.getAccessToken();
|
|
359
|
+
if (currentToken && isTokenExpired(currentToken, 0)) try {
|
|
360
|
+
await refreshTokens();
|
|
361
|
+
const newToken = await config.tokenStorage.getAccessToken();
|
|
362
|
+
if (newToken) {
|
|
363
|
+
const retryRequest = request.clone();
|
|
364
|
+
retryRequest.headers.set("Authorization", `Bearer ${newToken}`);
|
|
365
|
+
return fetch(retryRequest);
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.warn("POS token refresh failed on 401 response:", error);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return response;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Helper function to create POS auth middleware with sensible defaults
|
|
377
|
+
*/
|
|
378
|
+
function createDefaultPosAuthMiddleware(options) {
|
|
379
|
+
const tokenStorage = options.tokenStorage || (typeof localStorage !== "undefined" ? new BrowserTokenStorage() : new MemoryTokenStorage());
|
|
380
|
+
return createPosAuthMiddleware({
|
|
381
|
+
tokenStorage,
|
|
382
|
+
apiKey: options.apiKey,
|
|
383
|
+
baseUrl: options.baseUrl,
|
|
384
|
+
onTokensUpdated: options.onTokensUpdated,
|
|
385
|
+
onTokensCleared: options.onTokensCleared
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/lib/utils/url.ts
|
|
391
|
+
/**
|
|
392
|
+
* Environment configuration for POS API
|
|
393
|
+
*/
|
|
394
|
+
let Environment = /* @__PURE__ */ function(Environment$1) {
|
|
395
|
+
Environment$1["Development"] = "dev";
|
|
396
|
+
Environment$1["Staging"] = "staging";
|
|
397
|
+
Environment$1["Production"] = "production";
|
|
398
|
+
return Environment$1;
|
|
399
|
+
}({});
|
|
400
|
+
/**
|
|
401
|
+
* Build the base URL for POS API requests
|
|
402
|
+
*
|
|
403
|
+
* @param config - URL configuration
|
|
404
|
+
* @returns The base URL for POS API requests
|
|
405
|
+
*/
|
|
406
|
+
function buildPosURL(config) {
|
|
407
|
+
if (config.baseUrl) return config.baseUrl.replace(/\/$/, "");
|
|
408
|
+
const env = config.environment || Environment.Production;
|
|
409
|
+
switch (env) {
|
|
410
|
+
case Environment.Staging: return `https://staging.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
411
|
+
case Environment.Production:
|
|
412
|
+
default: return `https://prod.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/lib/client.ts
|
|
418
|
+
/**
|
|
419
|
+
* POS API client that extends the generic BaseAPIClient
|
|
420
|
+
* Adds Commerce Engine POS specific authentication and token management
|
|
421
|
+
*/
|
|
422
|
+
var PosAPIClient = class PosAPIClient extends BaseAPIClient {
|
|
423
|
+
config;
|
|
424
|
+
initializationPromise = null;
|
|
425
|
+
/**
|
|
426
|
+
* Validate required configuration parameters
|
|
427
|
+
* @param config - Configuration to validate
|
|
428
|
+
* @throws Error with descriptive message if validation fails
|
|
429
|
+
*/
|
|
430
|
+
static validateConfig(config) {
|
|
431
|
+
const errors = [];
|
|
432
|
+
if (!config.storeId?.trim()) errors.push("storeId is required and cannot be empty");
|
|
433
|
+
if (!config.apiKey?.trim()) errors.push("apiKey is required for POS authentication (needed for login, device pairing, and token refresh fallbacks)");
|
|
434
|
+
if (config.accessToken && !config.tokenStorage && !config.refreshToken) errors.push("When providing accessToken without tokenStorage, consider providing refreshToken for token refresh capability");
|
|
435
|
+
if (config.refreshToken && !config.accessToken) errors.push("refreshToken cannot be provided without accessToken (orphaned refresh token)");
|
|
436
|
+
if (config.refreshToken && !config.tokenStorage) errors.push("refreshToken requires tokenStorage for automatic token management");
|
|
437
|
+
if ((config.onTokensUpdated || config.onTokensCleared) && !config.tokenStorage) errors.push("Token callbacks (onTokensUpdated/onTokensCleared) require tokenStorage for automatic token management");
|
|
438
|
+
if (config.baseUrl && !PosAPIClient.isValidUrl(config.baseUrl)) errors.push("baseUrl must be a valid HTTP/HTTPS URL");
|
|
439
|
+
if (errors.length > 0) throw new Error(`POS SDK configuration validation failed:\n- ${errors.join("\n- ")}`);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Check if a string is a valid URL
|
|
443
|
+
* @param url - URL string to validate
|
|
444
|
+
* @returns True if valid URL
|
|
445
|
+
*/
|
|
446
|
+
static isValidUrl(url) {
|
|
447
|
+
try {
|
|
448
|
+
const parsed = new URL(url);
|
|
449
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
450
|
+
} catch {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Create a new PosAPIClient
|
|
456
|
+
*
|
|
457
|
+
* @param config - Configuration for the API client
|
|
458
|
+
*/
|
|
459
|
+
constructor(config) {
|
|
460
|
+
PosAPIClient.validateConfig(config);
|
|
461
|
+
const baseUrl = buildPosURL({
|
|
462
|
+
storeId: config.storeId,
|
|
463
|
+
environment: config.environment,
|
|
464
|
+
baseUrl: config.baseUrl
|
|
465
|
+
});
|
|
466
|
+
const headerTransformations = { customer_group_id: "x-customer-group-id" };
|
|
467
|
+
super({
|
|
468
|
+
baseUrl,
|
|
469
|
+
timeout: config.timeout,
|
|
470
|
+
defaultHeaders: config.defaultHeaders,
|
|
471
|
+
debug: config.debug,
|
|
472
|
+
logger: config.logger
|
|
473
|
+
}, baseUrl, headerTransformations);
|
|
474
|
+
this.config = { ...config };
|
|
475
|
+
this.setupPosAuth();
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Set up POS-specific authentication middleware
|
|
479
|
+
*/
|
|
480
|
+
setupPosAuth() {
|
|
481
|
+
const config = this.config;
|
|
482
|
+
if (config.tokenStorage) {
|
|
483
|
+
const authMiddleware = createDefaultPosAuthMiddleware({
|
|
484
|
+
apiKey: config.apiKey,
|
|
485
|
+
baseUrl: this.getBaseUrl(),
|
|
486
|
+
tokenStorage: config.tokenStorage,
|
|
487
|
+
onTokensUpdated: config.onTokensUpdated,
|
|
488
|
+
onTokensCleared: config.onTokensCleared
|
|
489
|
+
});
|
|
490
|
+
this.client.use(authMiddleware);
|
|
491
|
+
if (config.accessToken) {
|
|
492
|
+
this.initializationPromise = this.initializeTokens(config.accessToken, config.refreshToken);
|
|
493
|
+
config.accessToken = void 0;
|
|
494
|
+
config.refreshToken = void 0;
|
|
495
|
+
}
|
|
496
|
+
} else this.client.use({ onRequest: async ({ request }) => {
|
|
497
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
498
|
+
if (isApiKeyEndpoint(pathname)) {
|
|
499
|
+
request.headers.set("X-Api-Key", config.apiKey);
|
|
500
|
+
return request;
|
|
501
|
+
}
|
|
502
|
+
if (config.accessToken) request.headers.set("Authorization", `Bearer ${config.accessToken}`);
|
|
503
|
+
else if (config.apiKey) request.headers.set("X-Api-Key", config.apiKey);
|
|
504
|
+
return request;
|
|
505
|
+
} });
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Get the authorization header value
|
|
509
|
+
* If using token storage, gets the current token from storage
|
|
510
|
+
* Otherwise returns the manual token
|
|
511
|
+
*
|
|
512
|
+
* @returns The Authorization header value or empty string if no token is set
|
|
513
|
+
*/
|
|
514
|
+
async getAuthorizationHeader() {
|
|
515
|
+
if (this.config.tokenStorage && this.initializationPromise) await this.initializationPromise;
|
|
516
|
+
if (this.config.tokenStorage) {
|
|
517
|
+
const token = await this.config.tokenStorage.getAccessToken();
|
|
518
|
+
return token ? `Bearer ${token}` : "";
|
|
519
|
+
}
|
|
520
|
+
return this.config.accessToken ? `Bearer ${this.config.accessToken}` : "";
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Set authentication tokens
|
|
524
|
+
*
|
|
525
|
+
* @param accessToken - The access token (required)
|
|
526
|
+
* @param refreshToken - The refresh token (optional)
|
|
527
|
+
*
|
|
528
|
+
* Behavior:
|
|
529
|
+
* - If tokenStorage is provided: Stores tokens for automatic management
|
|
530
|
+
* - If tokenStorage is not provided: Only stores access token for manual management
|
|
531
|
+
*/
|
|
532
|
+
async setTokens(accessToken, refreshToken) {
|
|
533
|
+
if (this.config.tokenStorage) {
|
|
534
|
+
await this.config.tokenStorage.setAccessToken(accessToken);
|
|
535
|
+
if (refreshToken) await this.config.tokenStorage.setRefreshToken(refreshToken);
|
|
536
|
+
} else {
|
|
537
|
+
this.config.accessToken = accessToken;
|
|
538
|
+
if (refreshToken) console.warn("Refresh token provided but ignored in manual token management mode. Use tokenStorage for automatic management.");
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Clear all authentication tokens
|
|
543
|
+
*
|
|
544
|
+
* Behavior:
|
|
545
|
+
* - If tokenStorage is provided: Clears both access and refresh tokens from storage
|
|
546
|
+
* - If tokenStorage is not provided: Clears the manual access token
|
|
547
|
+
*/
|
|
548
|
+
async clearTokens() {
|
|
549
|
+
if (this.config.tokenStorage) await this.config.tokenStorage.clearTokens();
|
|
550
|
+
else this.config.accessToken = void 0;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Set the X-Api-Key header
|
|
554
|
+
*
|
|
555
|
+
* @param apiKey - The API key to set
|
|
556
|
+
*/
|
|
557
|
+
setApiKey(apiKey) {
|
|
558
|
+
this.config.apiKey = apiKey;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Initialize tokens in storage (private helper method)
|
|
562
|
+
*/
|
|
563
|
+
async initializeTokens(accessToken, refreshToken) {
|
|
564
|
+
try {
|
|
565
|
+
if (this.config.tokenStorage) {
|
|
566
|
+
await this.config.tokenStorage.setAccessToken(accessToken);
|
|
567
|
+
if (refreshToken) await this.config.tokenStorage.setRefreshToken(refreshToken);
|
|
568
|
+
}
|
|
569
|
+
} catch (error) {
|
|
570
|
+
console.warn("Failed to initialize tokens in storage:", error);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/lib/pos.ts
|
|
577
|
+
/**
|
|
578
|
+
* Client for interacting with POS endpoints
|
|
579
|
+
* Single client that handles all POS operations including auth, cart, orders, etc.
|
|
580
|
+
*/
|
|
581
|
+
var PosClient = class extends PosAPIClient {
|
|
582
|
+
/**
|
|
583
|
+
* Login with email address for POS device
|
|
584
|
+
* @param body - Login credentials containing device ID and email
|
|
585
|
+
* @returns Promise with OTP token and action
|
|
586
|
+
*/
|
|
587
|
+
async loginWithEmail(body) {
|
|
588
|
+
return this.executeRequest(() => this.client.POST("/pos/auth/login/email", { body }));
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Login with phone number for POS device
|
|
592
|
+
* @param body - Login credentials containing device ID and phone
|
|
593
|
+
* @returns Promise with OTP token and action
|
|
594
|
+
*/
|
|
595
|
+
async loginWithPhone(body) {
|
|
596
|
+
return this.executeRequest(() => this.client.POST("/pos/auth/login/phone", { body }));
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Login with WhatsApp for POS device
|
|
600
|
+
* @param body - Login credentials containing device ID and phone
|
|
601
|
+
* @returns Promise with OTP token and action
|
|
602
|
+
*/
|
|
603
|
+
async loginWithWhatsapp(body) {
|
|
604
|
+
return this.executeRequest(() => this.client.POST("/pos/auth/login/whatsapp", { body }));
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Pair POS device with pairing code
|
|
608
|
+
* @param body - Pairing code received via phone/email
|
|
609
|
+
* @returns Promise with device information
|
|
610
|
+
*/
|
|
611
|
+
async pairDevice(body) {
|
|
612
|
+
return this.executeRequest(() => this.client.POST("/pos/auth/pair-device", { body }));
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Verify OTP for POS login
|
|
616
|
+
* @param body - OTP verification data
|
|
617
|
+
* @returns Promise with user info and tokens
|
|
618
|
+
*/
|
|
619
|
+
async verifyOtp(body) {
|
|
620
|
+
return this.executeRequest(() => this.client.POST("/pos/auth/verify-otp", { body }));
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Refresh POS access token
|
|
624
|
+
* @param body - Refresh token data
|
|
625
|
+
* @returns Promise with new tokens
|
|
626
|
+
*/
|
|
627
|
+
async refreshAccessToken(body) {
|
|
628
|
+
return this.executeRequest(() => this.client.POST("/pos/auth/refresh-token", { body }));
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Logout from POS device
|
|
632
|
+
* @returns Promise with logout confirmation
|
|
633
|
+
*/
|
|
634
|
+
async logout() {
|
|
635
|
+
return this.executeRequest(() => this.client.POST("/pos/auth/logout"));
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Create a new cart
|
|
639
|
+
* @param body - Cart creation data with items
|
|
640
|
+
* @returns Promise with created cart
|
|
641
|
+
*/
|
|
642
|
+
async createCart(body) {
|
|
643
|
+
return this.executeRequest(() => this.client.POST("/pos/carts", { body }));
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Get cart details
|
|
647
|
+
* @param pathParams - Cart ID
|
|
648
|
+
* @returns Promise with cart details
|
|
649
|
+
*/
|
|
650
|
+
async getCart(pathParams) {
|
|
651
|
+
return this.executeRequest(() => this.client.GET("/pos/carts/{id}", { params: { path: pathParams } }));
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Delete cart (remove all items)
|
|
655
|
+
* @param pathParams - Cart ID
|
|
656
|
+
* @returns Promise with deletion confirmation
|
|
657
|
+
*/
|
|
658
|
+
async deleteCart(pathParams) {
|
|
659
|
+
return this.executeRequest(() => this.client.DELETE("/pos/carts/{id}", { params: { path: pathParams } }));
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Add/update cart item (set quantity to 0 to remove)
|
|
663
|
+
* @param pathParams - Cart ID
|
|
664
|
+
* @param body - Item update data
|
|
665
|
+
* @returns Promise with updated cart
|
|
666
|
+
*/
|
|
667
|
+
async updateCart(pathParams, body) {
|
|
668
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/items", {
|
|
669
|
+
params: { path: pathParams },
|
|
670
|
+
body
|
|
671
|
+
}));
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Update cart address (billing/shipping)
|
|
675
|
+
* @param pathParams - Cart ID
|
|
676
|
+
* @param body - Address data (registered user IDs or guest addresses)
|
|
677
|
+
* @returns Promise with updated cart
|
|
678
|
+
*/
|
|
679
|
+
async createCartAddress(pathParams, body) {
|
|
680
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/address", {
|
|
681
|
+
params: { path: pathParams },
|
|
682
|
+
body
|
|
683
|
+
}));
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* List all available coupons
|
|
687
|
+
* @param headers - Optional header parameters (customer_group_id, etc.)
|
|
688
|
+
* @returns Promise with available coupons
|
|
689
|
+
*/
|
|
690
|
+
async listCoupons(headers) {
|
|
691
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
692
|
+
return this.executeRequest(() => this.client.GET("/pos/carts/available-coupons", { params: { header: mergedHeaders } }));
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* List all available promotions
|
|
696
|
+
* @param headers - Optional header parameters (customer_group_id, etc.)
|
|
697
|
+
* @returns Promise with available promotions
|
|
698
|
+
*/
|
|
699
|
+
async listPromotions(headers) {
|
|
700
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
701
|
+
return this.executeRequest(() => this.client.GET("/pos/carts/available-promotions", { params: { header: mergedHeaders } }));
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Apply coupon to cart
|
|
705
|
+
* @param pathParams - Cart ID
|
|
706
|
+
* @param body - Coupon code
|
|
707
|
+
* @returns Promise with updated cart
|
|
708
|
+
*/
|
|
709
|
+
async applyCoupon(pathParams, body) {
|
|
710
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/coupon", {
|
|
711
|
+
params: { path: pathParams },
|
|
712
|
+
body
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Remove coupon from cart
|
|
717
|
+
* @param pathParams - Cart ID
|
|
718
|
+
* @returns Promise with updated cart
|
|
719
|
+
*/
|
|
720
|
+
async removeCoupon(pathParams) {
|
|
721
|
+
return this.executeRequest(() => this.client.DELETE("/pos/carts/{id}/coupon", { params: { path: pathParams } }));
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Evaluate applicable/inapplicable coupons for cart
|
|
725
|
+
* @param pathParams - Cart ID
|
|
726
|
+
* @returns Promise with coupon evaluation results
|
|
727
|
+
*/
|
|
728
|
+
async evaluateCoupons(pathParams) {
|
|
729
|
+
return this.executeRequest(() => this.client.GET("/pos/carts/{id}/evaluate-coupons", { params: { path: pathParams } }));
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Evaluate applicable/inapplicable promotions for cart
|
|
733
|
+
* @param pathParams - Cart ID
|
|
734
|
+
* @returns Promise with promotion evaluation results
|
|
735
|
+
*/
|
|
736
|
+
async evaluatePromotions(pathParams) {
|
|
737
|
+
return this.executeRequest(() => this.client.GET("/pos/carts/{id}/evaluate-promotions", { params: { path: pathParams } }));
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Apply credit balance to cart
|
|
741
|
+
* @param pathParams - Cart ID
|
|
742
|
+
* @param body - Credit balance amount to use
|
|
743
|
+
* @returns Promise with updated cart
|
|
744
|
+
*/
|
|
745
|
+
async redeemCreditBalance(pathParams, body) {
|
|
746
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/credit-balance", {
|
|
747
|
+
params: { path: pathParams },
|
|
748
|
+
body
|
|
749
|
+
}));
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Remove credit balance from cart
|
|
753
|
+
* @param pathParams - Cart ID
|
|
754
|
+
* @returns Promise with updated cart
|
|
755
|
+
*/
|
|
756
|
+
async removeCreditBalance(pathParams) {
|
|
757
|
+
return this.executeRequest(() => this.client.DELETE("/pos/carts/{id}/credit-balance", { params: { path: pathParams } }));
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Redeem gift card for cart
|
|
761
|
+
* @param pathParams - Cart ID
|
|
762
|
+
* @param body - Gift card code
|
|
763
|
+
* @returns Promise with updated cart
|
|
764
|
+
*/
|
|
765
|
+
async redeemGiftCard(pathParams, body) {
|
|
766
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/gift-card", {
|
|
767
|
+
params: { path: pathParams },
|
|
768
|
+
body
|
|
769
|
+
}));
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Remove gift card from cart
|
|
773
|
+
* @param pathParams - Cart ID
|
|
774
|
+
* @returns Promise with updated cart
|
|
775
|
+
*/
|
|
776
|
+
async removeGiftCard(pathParams) {
|
|
777
|
+
return this.executeRequest(() => this.client.DELETE("/pos/carts/{id}/gift-card", { params: { path: pathParams } }));
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Redeem loyalty points for cart
|
|
781
|
+
* @param pathParams - Cart ID
|
|
782
|
+
* @param body - Loyalty points to redeem
|
|
783
|
+
* @returns Promise with updated cart
|
|
784
|
+
*/
|
|
785
|
+
async redeemLoyaltyPoints(pathParams, body) {
|
|
786
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/loyalty-points", {
|
|
787
|
+
params: { path: pathParams },
|
|
788
|
+
body
|
|
789
|
+
}));
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Remove loyalty points from cart
|
|
793
|
+
* @param pathParams - Cart ID
|
|
794
|
+
* @returns Promise with updated cart
|
|
795
|
+
*/
|
|
796
|
+
async removeLoyaltyPoints(pathParams) {
|
|
797
|
+
return this.executeRequest(() => this.client.DELETE("/pos/carts/{id}/loyalty-points", { params: { path: pathParams } }));
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Update cart fulfillment preference (delivery/pickup)
|
|
801
|
+
* @param pathParams - Cart ID
|
|
802
|
+
* @param body - Fulfillment preference data
|
|
803
|
+
* @returns Promise with confirmation
|
|
804
|
+
*/
|
|
805
|
+
async updateFulfillmentPreference(pathParams, body) {
|
|
806
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/fulfillment-preference", {
|
|
807
|
+
params: { path: pathParams },
|
|
808
|
+
body
|
|
809
|
+
}));
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get fulfillment options for cart
|
|
813
|
+
* @param body - Cart data for fulfillment calculation
|
|
814
|
+
* @returns Promise with fulfillment options
|
|
815
|
+
*/
|
|
816
|
+
async getFulfillmentOptions(body) {
|
|
817
|
+
return this.executeRequest(() => this.client.POST("/pos/fulfillment-options", { body }));
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Update cart customer information
|
|
821
|
+
* @param pathParams - Cart ID
|
|
822
|
+
* @param body - Customer update data
|
|
823
|
+
* @returns Promise with updated cart
|
|
824
|
+
*/
|
|
825
|
+
async updateCartCustomer(pathParams, body) {
|
|
826
|
+
return this.executeRequest(() => this.client.POST("/pos/carts/{id}/update-customer", {
|
|
827
|
+
params: { path: pathParams },
|
|
828
|
+
body
|
|
829
|
+
}));
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Create order from cart
|
|
833
|
+
* @param body - Order creation data
|
|
834
|
+
* @returns Promise with created order
|
|
835
|
+
*/
|
|
836
|
+
async createOrder(body) {
|
|
837
|
+
return this.executeRequest(() => this.client.POST("/pos/orders", { body }));
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* List all categories
|
|
841
|
+
* @param query - Optional query parameters for filtering categories
|
|
842
|
+
* @returns Promise with list of categories
|
|
843
|
+
*/
|
|
844
|
+
async listCategories(query) {
|
|
845
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/categories", { params: { query } }));
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* List all products
|
|
849
|
+
* @param query - Optional query parameters for filtering products
|
|
850
|
+
* @param headers - Optional header parameters
|
|
851
|
+
* @returns Promise with list of products
|
|
852
|
+
*/
|
|
853
|
+
async listProducts(query, headers) {
|
|
854
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
855
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products", { params: {
|
|
856
|
+
query,
|
|
857
|
+
header: mergedHeaders
|
|
858
|
+
} }));
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* List cross-sell products
|
|
862
|
+
* @param query - Query parameters with product IDs for cross-sell recommendations
|
|
863
|
+
* @param headers - Optional header parameters
|
|
864
|
+
* @returns Promise with cross-sell products
|
|
865
|
+
*/
|
|
866
|
+
async listCrosssellProducts(query, headers) {
|
|
867
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
868
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products/cross-sell", { params: {
|
|
869
|
+
query,
|
|
870
|
+
header: mergedHeaders
|
|
871
|
+
} }));
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Search products
|
|
875
|
+
* @param body - Search criteria and parameters
|
|
876
|
+
* @param headers - Optional header parameters
|
|
877
|
+
* @returns Promise with search results
|
|
878
|
+
*/
|
|
879
|
+
async searchProducts(body, headers) {
|
|
880
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
881
|
+
return this.executeRequest(() => this.client.POST("/pos/catalog/products/search", {
|
|
882
|
+
body,
|
|
883
|
+
params: { header: mergedHeaders }
|
|
884
|
+
}));
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* List similar products
|
|
888
|
+
* @param query - Query parameters with product ID for similarity recommendations
|
|
889
|
+
* @param headers - Optional header parameters
|
|
890
|
+
* @returns Promise with similar products
|
|
891
|
+
*/
|
|
892
|
+
async listSimilarProducts(query, headers) {
|
|
893
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
894
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products/similar", { params: {
|
|
895
|
+
query,
|
|
896
|
+
header: mergedHeaders
|
|
897
|
+
} }));
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* List up-sell products
|
|
901
|
+
* @param query - Query parameters with product IDs for up-sell recommendations
|
|
902
|
+
* @param headers - Optional header parameters
|
|
903
|
+
* @returns Promise with up-sell products
|
|
904
|
+
*/
|
|
905
|
+
async listUpsellProducts(query, headers) {
|
|
906
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
907
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products/up-sell", { params: {
|
|
908
|
+
query,
|
|
909
|
+
header: mergedHeaders
|
|
910
|
+
} }));
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Get product details
|
|
914
|
+
* @param pathParams - Product ID or slug
|
|
915
|
+
* @param headers - Optional header parameters
|
|
916
|
+
* @returns Promise with product details
|
|
917
|
+
*/
|
|
918
|
+
async getProductDetail(pathParams, headers) {
|
|
919
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
920
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products/{product_id_or_slug}", { params: {
|
|
921
|
+
path: pathParams,
|
|
922
|
+
header: mergedHeaders
|
|
923
|
+
} }));
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* List product reviews
|
|
927
|
+
* @param pathParams - Product ID
|
|
928
|
+
* @param query - Optional query parameters for filtering reviews
|
|
929
|
+
* @returns Promise with product reviews
|
|
930
|
+
*/
|
|
931
|
+
async listProductReviews(pathParams, query) {
|
|
932
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products/{product_id}/reviews", { params: {
|
|
933
|
+
path: pathParams,
|
|
934
|
+
query
|
|
935
|
+
} }));
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* List product variants
|
|
939
|
+
* @param pathParams - Product ID
|
|
940
|
+
* @param headers - Optional header parameters
|
|
941
|
+
* @returns Promise with product variants
|
|
942
|
+
*/
|
|
943
|
+
async listProductVariants(pathParams, headers) {
|
|
944
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
945
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products/{product_id}/variants", { params: {
|
|
946
|
+
path: pathParams,
|
|
947
|
+
header: mergedHeaders
|
|
948
|
+
} }));
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Get variant details
|
|
952
|
+
* @param pathParams - Product ID and variant ID
|
|
953
|
+
* @param headers - Optional header parameters
|
|
954
|
+
* @returns Promise with variant details
|
|
955
|
+
*/
|
|
956
|
+
async getVariantDetail(pathParams, headers) {
|
|
957
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
958
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/products/{product_id}/variants/{variant_id}", { params: {
|
|
959
|
+
path: pathParams,
|
|
960
|
+
header: mergedHeaders
|
|
961
|
+
} }));
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* List all SKUs
|
|
965
|
+
* @param query - Optional query parameters for filtering SKUs
|
|
966
|
+
* @param headers - Optional header parameters
|
|
967
|
+
* @returns Promise with list of SKUs
|
|
968
|
+
*/
|
|
969
|
+
async listSkus(query, headers) {
|
|
970
|
+
const mergedHeaders = this.mergeHeaders(headers);
|
|
971
|
+
return this.executeRequest(() => this.client.GET("/pos/catalog/skus", { params: {
|
|
972
|
+
query,
|
|
973
|
+
header: mergedHeaders
|
|
974
|
+
} }));
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
//#endregion
|
|
979
|
+
//#region src/index.ts
|
|
980
|
+
/**
|
|
981
|
+
* Main SDK class for the POS API
|
|
982
|
+
* Provides access to all POS endpoints through a single client
|
|
983
|
+
*/
|
|
984
|
+
var PosSDK = class {
|
|
985
|
+
/**
|
|
986
|
+
* Client for all POS operations (auth, cart, orders, etc.)
|
|
987
|
+
*/
|
|
988
|
+
pos;
|
|
989
|
+
/**
|
|
990
|
+
* Create a new PosSDK instance
|
|
991
|
+
*
|
|
992
|
+
* @param options - Configuration options for the SDK
|
|
993
|
+
*/
|
|
994
|
+
constructor(options) {
|
|
995
|
+
const config = {
|
|
996
|
+
storeId: options.storeId,
|
|
997
|
+
environment: options.environment,
|
|
998
|
+
baseUrl: options.baseUrl,
|
|
999
|
+
accessToken: options.accessToken,
|
|
1000
|
+
refreshToken: options.refreshToken,
|
|
1001
|
+
apiKey: options.apiKey,
|
|
1002
|
+
timeout: options.timeout,
|
|
1003
|
+
tokenStorage: options.tokenStorage,
|
|
1004
|
+
onTokensUpdated: options.onTokensUpdated,
|
|
1005
|
+
onTokensCleared: options.onTokensCleared,
|
|
1006
|
+
defaultHeaders: options.defaultHeaders,
|
|
1007
|
+
debug: options.debug,
|
|
1008
|
+
logger: options.logger
|
|
1009
|
+
};
|
|
1010
|
+
this.pos = new PosClient(config);
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Set authentication tokens for the client
|
|
1014
|
+
*
|
|
1015
|
+
* @param accessToken - The access token (required)
|
|
1016
|
+
* @param refreshToken - The refresh token (optional)
|
|
1017
|
+
*
|
|
1018
|
+
* Behavior:
|
|
1019
|
+
* - If tokenStorage is provided: Stores tokens for automatic management
|
|
1020
|
+
* - If tokenStorage is not provided: Only stores access token for manual management
|
|
1021
|
+
*/
|
|
1022
|
+
async setTokens(accessToken, refreshToken) {
|
|
1023
|
+
await this.pos.setTokens(accessToken, refreshToken);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Clear all authentication tokens from the client
|
|
1027
|
+
*
|
|
1028
|
+
* Behavior:
|
|
1029
|
+
* - If tokenStorage is provided: Clears both access and refresh tokens from storage
|
|
1030
|
+
* - If tokenStorage is not provided: Clears the manual access token
|
|
1031
|
+
*/
|
|
1032
|
+
async clearTokens() {
|
|
1033
|
+
await this.pos.clearTokens();
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Set the API key for the client
|
|
1037
|
+
*
|
|
1038
|
+
* @param apiKey - The API key to set
|
|
1039
|
+
*/
|
|
1040
|
+
setApiKey(apiKey) {
|
|
1041
|
+
this.pos.setApiKey(apiKey);
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Get the current access token if available
|
|
1045
|
+
*/
|
|
1046
|
+
async getAccessToken() {
|
|
1047
|
+
const header = await this.pos.getAuthorizationHeader();
|
|
1048
|
+
return header.startsWith("Bearer ") ? header.substring(7) : null;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Set default headers for the client
|
|
1052
|
+
*
|
|
1053
|
+
* @param headers - Default headers to set
|
|
1054
|
+
*/
|
|
1055
|
+
setDefaultHeaders(headers) {
|
|
1056
|
+
this.pos.setDefaultHeaders(headers);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Get current default headers
|
|
1060
|
+
*/
|
|
1061
|
+
getDefaultHeaders() {
|
|
1062
|
+
return this.pos.getDefaultHeaders();
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get user information from the current access token
|
|
1066
|
+
*
|
|
1067
|
+
* @returns User information extracted from JWT token, or null if no token or invalid token
|
|
1068
|
+
*/
|
|
1069
|
+
async getUserInfo() {
|
|
1070
|
+
const token = await this.getAccessToken();
|
|
1071
|
+
if (!token) return null;
|
|
1072
|
+
return extractUserInfoFromToken(token);
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Get the current user ID from the access token
|
|
1076
|
+
*
|
|
1077
|
+
* @returns User ID (ulid) or null if no token or invalid token
|
|
1078
|
+
*/
|
|
1079
|
+
async getUserId() {
|
|
1080
|
+
const token = await this.getAccessToken();
|
|
1081
|
+
if (!token) return null;
|
|
1082
|
+
return getUserIdFromToken(token);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Check if the current user is logged in
|
|
1086
|
+
*
|
|
1087
|
+
* @returns True if user is logged in, false otherwise
|
|
1088
|
+
*/
|
|
1089
|
+
async isLoggedIn() {
|
|
1090
|
+
const token = await this.getAccessToken();
|
|
1091
|
+
if (!token) return false;
|
|
1092
|
+
return isUserLoggedIn(token);
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Check if the current user is authenticated with POS access
|
|
1096
|
+
* This verifies the user has valid device and role information
|
|
1097
|
+
*
|
|
1098
|
+
* @returns True if user is authenticated for POS operations
|
|
1099
|
+
*/
|
|
1100
|
+
async isAuthenticated() {
|
|
1101
|
+
const token = await this.getAccessToken();
|
|
1102
|
+
if (!token) return false;
|
|
1103
|
+
return isUserAuthenticated(token);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Get the device ID from the current access token
|
|
1107
|
+
* This is commonly needed for POS API requests
|
|
1108
|
+
*
|
|
1109
|
+
* @returns Device ID or null if no token or invalid token
|
|
1110
|
+
*/
|
|
1111
|
+
async getDeviceId() {
|
|
1112
|
+
const token = await this.getAccessToken();
|
|
1113
|
+
if (!token) return null;
|
|
1114
|
+
return getDeviceIdFromToken(token);
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Get the location ID from the current access token
|
|
1118
|
+
* This is commonly needed for POS API requests
|
|
1119
|
+
*
|
|
1120
|
+
* @returns Location ID or null if no token or invalid token
|
|
1121
|
+
*/
|
|
1122
|
+
async getLocationId() {
|
|
1123
|
+
const token = await this.getAccessToken();
|
|
1124
|
+
if (!token) return null;
|
|
1125
|
+
return getLocationIdFromToken(token);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Get the role information from the current access token
|
|
1129
|
+
*
|
|
1130
|
+
* @returns Role object with id and name, or null if no token or invalid token
|
|
1131
|
+
*/
|
|
1132
|
+
async getRole() {
|
|
1133
|
+
const token = await this.getAccessToken();
|
|
1134
|
+
if (!token) return null;
|
|
1135
|
+
return getRoleFromToken(token);
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Get the tenant ID from the current access token
|
|
1139
|
+
*
|
|
1140
|
+
* @returns Tenant ID or null if no token or invalid token
|
|
1141
|
+
*/
|
|
1142
|
+
async getTenantId() {
|
|
1143
|
+
const token = await this.getAccessToken();
|
|
1144
|
+
if (!token) return null;
|
|
1145
|
+
return getTenantIdFromToken(token);
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
var src_default = PosSDK;
|
|
1149
|
+
|
|
1150
|
+
//#endregion
|
|
1151
|
+
export { BrowserTokenStorage, Environment, MemoryTokenStorage, PosAPIClient, PosClient, PosSDK, ResponseUtils, src_default as default };
|
|
1152
|
+
//# sourceMappingURL=index.js.map
|