@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/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