@shoppexio/storefront 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,1658 @@
1
+ // ../sdk/src/core/errors.ts
2
+ var ShoppexError = class _ShoppexError extends Error {
3
+ constructor(message, code, statusCode) {
4
+ super(message);
5
+ this.name = "ShoppexError";
6
+ this.code = code;
7
+ this.statusCode = statusCode;
8
+ Object.setPrototypeOf(this, _ShoppexError.prototype);
9
+ }
10
+ };
11
+ var NotInitializedError = class _NotInitializedError extends ShoppexError {
12
+ constructor() {
13
+ super(
14
+ "SDK not initialized. Call shoppex.init() first.",
15
+ "NOT_INITIALIZED"
16
+ );
17
+ this.name = "NotInitializedError";
18
+ Object.setPrototypeOf(this, _NotInitializedError.prototype);
19
+ }
20
+ };
21
+ var NetworkError = class _NetworkError extends ShoppexError {
22
+ constructor(message, statusCode) {
23
+ super(message, "NETWORK_ERROR", statusCode);
24
+ this.name = "NetworkError";
25
+ Object.setPrototypeOf(this, _NetworkError.prototype);
26
+ }
27
+ };
28
+ var ValidationError = class _ValidationError extends ShoppexError {
29
+ constructor(message, invalidFields) {
30
+ super(message, "VALIDATION_ERROR");
31
+ this.name = "ValidationError";
32
+ this.invalidFields = invalidFields;
33
+ Object.setPrototypeOf(this, _ValidationError.prototype);
34
+ }
35
+ };
36
+ var CartError = class _CartError extends ShoppexError {
37
+ constructor(message) {
38
+ super(message, "BASKET_ERROR");
39
+ this.name = "CartError";
40
+ Object.setPrototypeOf(this, _CartError.prototype);
41
+ }
42
+ };
43
+
44
+ // ../sdk/src/core/config.ts
45
+ var DEFAULT_API_BASE_URL = "https://api.shoppex.io";
46
+ var currentConfig = null;
47
+ var cachedShopId = null;
48
+ var DEFAULT_CHECKOUT_BASE_URL = "https://checkout.shoppex.io";
49
+ function initConfig(storeSlug, options) {
50
+ cachedShopId = null;
51
+ currentConfig = {
52
+ storeSlug,
53
+ locale: options?.locale,
54
+ currency: options?.currency,
55
+ apiBaseUrl: options?.apiBaseUrl ?? DEFAULT_API_BASE_URL,
56
+ checkoutBaseUrl: options?.checkoutBaseUrl ?? DEFAULT_CHECKOUT_BASE_URL
57
+ };
58
+ return currentConfig;
59
+ }
60
+ function getConfig() {
61
+ if (!currentConfig) {
62
+ throw new NotInitializedError();
63
+ }
64
+ return currentConfig;
65
+ }
66
+ function isInitialized() {
67
+ return currentConfig !== null;
68
+ }
69
+ function setShopId(shopId) {
70
+ cachedShopId = shopId;
71
+ }
72
+ function getShopId() {
73
+ return cachedShopId;
74
+ }
75
+
76
+ // ../sdk/src/core/cache.ts
77
+ var cache = /* @__PURE__ */ new Map();
78
+ var pending = /* @__PURE__ */ new Map();
79
+ var stats = {
80
+ hits: 0,
81
+ misses: 0
82
+ };
83
+ function isExpired(entry) {
84
+ return Date.now() > entry.expiresAt;
85
+ }
86
+ function getCacheStats() {
87
+ return {
88
+ hits: stats.hits,
89
+ misses: stats.misses,
90
+ pendingRequests: pending.size,
91
+ entries: cache.size
92
+ };
93
+ }
94
+ function clearCache() {
95
+ cache.clear();
96
+ pending.clear();
97
+ }
98
+ function invalidateCache(prefixOrKey) {
99
+ for (const key of cache.keys()) {
100
+ if (key === prefixOrKey || key.startsWith(prefixOrKey)) {
101
+ cache.delete(key);
102
+ }
103
+ }
104
+ }
105
+ function setCacheEntry(key, data, ttl) {
106
+ const now = Date.now();
107
+ cache.set(key, {
108
+ data,
109
+ ttl,
110
+ updatedAt: now,
111
+ expiresAt: now + ttl
112
+ });
113
+ }
114
+ function getCacheEntry(key) {
115
+ const entry = cache.get(key);
116
+ if (!entry) return null;
117
+ return entry;
118
+ }
119
+ async function getOrFetch(key, fetcher, options, shouldCache = () => true) {
120
+ const entry = getCacheEntry(key);
121
+ if (entry && !isExpired(entry)) {
122
+ stats.hits += 1;
123
+ return entry.data;
124
+ }
125
+ if (entry && options.staleWhileRevalidate) {
126
+ stats.hits += 1;
127
+ if (!pending.has(key)) {
128
+ const refreshPromise = (async () => {
129
+ try {
130
+ const data = await fetcher();
131
+ if (shouldCache(data)) {
132
+ setCacheEntry(key, data, options.ttl);
133
+ }
134
+ return data;
135
+ } finally {
136
+ pending.delete(key);
137
+ }
138
+ })();
139
+ pending.set(key, refreshPromise);
140
+ }
141
+ return entry.data;
142
+ }
143
+ if (pending.has(key)) {
144
+ return pending.get(key);
145
+ }
146
+ stats.misses += 1;
147
+ const promise = (async () => {
148
+ try {
149
+ const data = await fetcher();
150
+ if (shouldCache(data)) {
151
+ setCacheEntry(key, data, options.ttl);
152
+ }
153
+ return data;
154
+ } finally {
155
+ pending.delete(key);
156
+ }
157
+ })();
158
+ pending.set(key, promise);
159
+ return promise;
160
+ }
161
+
162
+ // ../sdk/src/core/client.ts
163
+ var DEFAULT_TIMEOUT = 1e4;
164
+ var MAX_RETRIES = 2;
165
+ async function sleep(ms) {
166
+ return new Promise((resolve) => setTimeout(resolve, ms));
167
+ }
168
+ async function request(endpoint, options = {}) {
169
+ const config = options.baseUrl ? null : getConfig();
170
+ const {
171
+ method = "GET",
172
+ body,
173
+ timeout = DEFAULT_TIMEOUT,
174
+ retries,
175
+ baseUrl,
176
+ cache: cache2
177
+ } = options;
178
+ const retryCount = retries ?? (method === "GET" ? MAX_RETRIES : 0);
179
+ const apiBaseUrl = baseUrl ?? config?.apiBaseUrl ?? "";
180
+ const url = `${apiBaseUrl}${endpoint}`;
181
+ const headers = {
182
+ "Content-Type": "application/json",
183
+ Accept: "application/json"
184
+ };
185
+ let lastError = null;
186
+ const executeRequest = async () => {
187
+ for (let attempt = 0; attempt <= retryCount; attempt++) {
188
+ try {
189
+ const controller = new AbortController();
190
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
191
+ const response = await fetch(url, {
192
+ method,
193
+ headers,
194
+ body: body ? JSON.stringify(body) : void 0,
195
+ signal: controller.signal
196
+ });
197
+ clearTimeout(timeoutId);
198
+ const payload = await parseResponsePayload(response);
199
+ if (!response.ok) {
200
+ const fallbackHttpMessage = response.statusText ? `HTTP ${response.status}: ${response.statusText}` : `HTTP ${response.status}`;
201
+ const message = (payload.data && typeof payload.data === "object" && "error" in payload.data && typeof payload.data.error === "string" ? payload.data.error : null) ?? (payload.data && typeof payload.data === "object" && "message" in payload.data && typeof payload.data.message === "string" ? payload.data.message : null) ?? payload.rawText ?? fallbackHttpMessage;
202
+ throw new NetworkError(message, response.status);
203
+ }
204
+ if (response.status === 204 && payload.data === null) {
205
+ return {
206
+ success: true
207
+ };
208
+ }
209
+ if (!payload.data || typeof payload.data !== "object" || !("status" in payload.data)) {
210
+ throw new NetworkError("Invalid API response", response.status);
211
+ }
212
+ const data = payload.data;
213
+ return mapApiResponse(data);
214
+ } catch (error) {
215
+ lastError = error instanceof Error ? error : new Error(String(error));
216
+ if (error instanceof DOMException && error.name === "AbortError") {
217
+ lastError = new NetworkError("Request timeout", 408);
218
+ }
219
+ if (attempt < retryCount) {
220
+ await sleep(Math.pow(2, attempt) * 500);
221
+ continue;
222
+ }
223
+ }
224
+ }
225
+ return {
226
+ success: false,
227
+ message: lastError?.message ?? "Unknown error"
228
+ };
229
+ };
230
+ if (method === "GET" && cache2 && cache2.ttl > 0) {
231
+ const cacheKey = cache2.key ?? `GET:${url}`;
232
+ return getOrFetch(
233
+ cacheKey,
234
+ executeRequest,
235
+ { ttl: cache2.ttl, staleWhileRevalidate: cache2.staleWhileRevalidate },
236
+ (value) => value.success
237
+ );
238
+ }
239
+ return executeRequest();
240
+ }
241
+ async function parseResponsePayload(response) {
242
+ const responseWithOptionalMethods = response;
243
+ if (typeof responseWithOptionalMethods.text !== "function") {
244
+ if (typeof responseWithOptionalMethods.json === "function") {
245
+ try {
246
+ return {
247
+ data: await responseWithOptionalMethods.json(),
248
+ rawText: null
249
+ };
250
+ } catch {
251
+ return { data: null, rawText: null };
252
+ }
253
+ }
254
+ return { data: null, rawText: null };
255
+ }
256
+ try {
257
+ const rawText = await responseWithOptionalMethods.text();
258
+ if (!rawText) {
259
+ return { data: null, rawText: null };
260
+ }
261
+ try {
262
+ return {
263
+ data: JSON.parse(rawText),
264
+ rawText: null
265
+ };
266
+ } catch {
267
+ const normalizedText = rawText.trim();
268
+ return {
269
+ data: null,
270
+ rawText: normalizedText.length > 0 ? normalizedText : null
271
+ };
272
+ }
273
+ } catch {
274
+ return { data: null, rawText: null };
275
+ }
276
+ }
277
+ function mapApiResponse(apiResponse) {
278
+ if (apiResponse.status >= 200 && apiResponse.status < 300) {
279
+ return {
280
+ success: true,
281
+ data: apiResponse.data
282
+ };
283
+ }
284
+ return {
285
+ success: false,
286
+ message: apiResponse.error ?? `Request failed with status ${apiResponse.status}`
287
+ };
288
+ }
289
+ async function get(endpoint, options) {
290
+ return request(endpoint, { ...options, method: "GET" });
291
+ }
292
+ async function post(endpoint, body, options) {
293
+ return request(endpoint, { ...options, method: "POST", body });
294
+ }
295
+
296
+ // ../sdk/src/core/endpoint.ts
297
+ var PARAM_PATTERN = /:([A-Za-z0-9_]+)/g;
298
+ function buildEndpoint(template, params) {
299
+ return template.replace(PARAM_PATTERN, (_, key) => {
300
+ const rawValue = params[key];
301
+ if (rawValue === null || rawValue === void 0) {
302
+ throw new Error(`Missing endpoint param: ${key}`);
303
+ }
304
+ const value = String(rawValue).trim();
305
+ if (!value) {
306
+ throw new Error(`Endpoint param "${key}" must not be empty`);
307
+ }
308
+ return encodeURIComponent(value);
309
+ });
310
+ }
311
+
312
+ // ../sdk/src/modules/store.ts
313
+ var STORE_CACHE_TTL = 5 * 60 * 1e3;
314
+ async function getStore() {
315
+ const config = getConfig();
316
+ const response = await get(
317
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug", {
318
+ storeSlug: config.storeSlug
319
+ }),
320
+ {
321
+ cache: {
322
+ key: `store:${config.storeSlug}`,
323
+ ttl: STORE_CACHE_TTL,
324
+ staleWhileRevalidate: true
325
+ }
326
+ }
327
+ );
328
+ if (response.success && response.data) {
329
+ if (response.data.shop?.id) {
330
+ setShopId(response.data.shop.id);
331
+ }
332
+ return {
333
+ success: true,
334
+ data: response.data.shop
335
+ };
336
+ }
337
+ return {
338
+ success: false,
339
+ message: response.message
340
+ };
341
+ }
342
+ async function resolveStoreByDomain(domain, apiBaseUrl) {
343
+ const resolvedDomain = domain ?? (typeof window !== "undefined" ? window.location.hostname : "");
344
+ if (!resolvedDomain) {
345
+ return {
346
+ success: false,
347
+ message: "Domain is required to resolve store"
348
+ };
349
+ }
350
+ const cleanDomain = resolvedDomain.replace(/^https?:\/\//, "").split("/")[0].trim();
351
+ const baseUrl = apiBaseUrl ?? (isInitialized() ? getConfig().apiBaseUrl : DEFAULT_API_BASE_URL);
352
+ const response = await get(
353
+ buildEndpoint("/v1/storefront/shops/domain/:domain", {
354
+ domain: cleanDomain
355
+ }),
356
+ {
357
+ baseUrl,
358
+ cache: {
359
+ key: `store:domain:${cleanDomain}`,
360
+ ttl: STORE_CACHE_TTL,
361
+ staleWhileRevalidate: true
362
+ }
363
+ }
364
+ );
365
+ if (response.success && response.data?.shop) {
366
+ return {
367
+ success: true,
368
+ data: response.data.shop
369
+ };
370
+ }
371
+ return {
372
+ success: false,
373
+ message: response.message ?? "Failed to resolve store"
374
+ };
375
+ }
376
+ async function getStorefront() {
377
+ const config = getConfig();
378
+ const response = await get(
379
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug", {
380
+ storeSlug: config.storeSlug
381
+ }),
382
+ {
383
+ cache: {
384
+ key: `storefront:${config.storeSlug}`,
385
+ ttl: STORE_CACHE_TTL,
386
+ staleWhileRevalidate: true
387
+ }
388
+ }
389
+ );
390
+ if (response.success && response.data) {
391
+ if (response.data.shop?.id) {
392
+ setShopId(response.data.shop.id);
393
+ }
394
+ return {
395
+ success: true,
396
+ data: {
397
+ shop: response.data.shop,
398
+ products: response.data.products ?? [],
399
+ groups: response.data.groups ?? [],
400
+ items: response.data.items ?? [],
401
+ categories: response.data.categories ?? [],
402
+ addons: response.data.addons ?? { items: [] }
403
+ }
404
+ };
405
+ }
406
+ return {
407
+ success: false,
408
+ message: response.message
409
+ };
410
+ }
411
+ async function getStoreLogoUrl() {
412
+ const response = await getStore();
413
+ if (response.success && response.data?.logo) {
414
+ return response.data.logo;
415
+ }
416
+ return null;
417
+ }
418
+ async function getStoreBannerUrl() {
419
+ const response = await getStore();
420
+ if (response.success && response.data?.banner) {
421
+ return response.data.banner;
422
+ }
423
+ return null;
424
+ }
425
+
426
+ // ../sdk/src/modules/products.ts
427
+ var PRODUCTS_CACHE_TTL = 2 * 60 * 1e3;
428
+ async function getProducts() {
429
+ const config = getConfig();
430
+ const response = await get(
431
+ buildEndpoint("/v1/storefront/products/public/:storeSlug", {
432
+ storeSlug: config.storeSlug
433
+ }),
434
+ {
435
+ cache: {
436
+ key: `products:${config.storeSlug}`,
437
+ ttl: PRODUCTS_CACHE_TTL,
438
+ staleWhileRevalidate: true
439
+ }
440
+ }
441
+ );
442
+ if (response.success && response.data) {
443
+ return {
444
+ success: true,
445
+ data: response.data.products
446
+ };
447
+ }
448
+ return {
449
+ success: false,
450
+ message: response.message,
451
+ data: []
452
+ };
453
+ }
454
+ async function getProduct(idOrSlug) {
455
+ const shopId = getShopId();
456
+ const queryParams = shopId ? `?slug_shop_id=${encodeURIComponent(shopId)}` : "";
457
+ const response = await get(
458
+ `${buildEndpoint("/v1/storefront/products/unique/:idOrSlug", { idOrSlug })}${queryParams}`,
459
+ {
460
+ cache: {
461
+ key: `product:${idOrSlug}:${shopId ?? "no-shop"}`,
462
+ ttl: PRODUCTS_CACHE_TTL,
463
+ staleWhileRevalidate: true
464
+ }
465
+ }
466
+ );
467
+ if (response.success && response.data?.product) {
468
+ return {
469
+ success: true,
470
+ data: response.data.product
471
+ };
472
+ }
473
+ return {
474
+ success: false,
475
+ message: response.message
476
+ };
477
+ }
478
+ async function getCategories() {
479
+ const products = await getProducts();
480
+ if (!products.success || !products.data) {
481
+ return {
482
+ success: false,
483
+ message: products.message
484
+ };
485
+ }
486
+ const categories = /* @__PURE__ */ new Set();
487
+ for (const product of products.data) {
488
+ if (product.categories) {
489
+ for (const category of product.categories) {
490
+ if (typeof category === "string") {
491
+ categories.add(category);
492
+ } else if (category && typeof category === "object" && "uniqid" in category) {
493
+ categories.add(category.uniqid);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ return {
499
+ success: true,
500
+ data: Array.from(categories)
501
+ };
502
+ }
503
+
504
+ // ../sdk/src/utils/storage.ts
505
+ var STORAGE_PREFIX = "shoppex_";
506
+ function getKey(key) {
507
+ return `${STORAGE_PREFIX}${key}`;
508
+ }
509
+ function getItem(key) {
510
+ try {
511
+ const item = localStorage.getItem(getKey(key));
512
+ if (!item) return null;
513
+ return JSON.parse(item);
514
+ } catch {
515
+ return null;
516
+ }
517
+ }
518
+ function setItem(key, value) {
519
+ try {
520
+ localStorage.setItem(getKey(key), JSON.stringify(value));
521
+ } catch {
522
+ console.warn("[shoppex] Failed to save to localStorage");
523
+ }
524
+ }
525
+ function removeItem(key) {
526
+ try {
527
+ localStorage.removeItem(getKey(key));
528
+ } catch {
529
+ }
530
+ }
531
+
532
+ // ../sdk/src/modules/cart.ts
533
+ var STORAGE_KEYS = {
534
+ cart: "cart",
535
+ cartBackup: "cart_backup",
536
+ meta: "cart_meta",
537
+ metaBackup: "cart_backup_meta"
538
+ };
539
+ function getStorageKey(type) {
540
+ return `${STORAGE_KEYS[type]}_${getConfig().storeSlug}`;
541
+ }
542
+ function hashString(value) {
543
+ let hash = 2166136261;
544
+ for (let i = 0; i < value.length; i += 1) {
545
+ hash ^= value.charCodeAt(i);
546
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
547
+ }
548
+ return (hash >>> 0).toString(16);
549
+ }
550
+ function computeChecksum(cart) {
551
+ return hashString(JSON.stringify(cart));
552
+ }
553
+ function normalizeQuantity(value) {
554
+ if (!Number.isFinite(value)) {
555
+ throw new CartError("quantity must be a finite number");
556
+ }
557
+ return Math.floor(value);
558
+ }
559
+ function normalizeCartItems(value) {
560
+ if (!Array.isArray(value)) return [];
561
+ const normalized = [];
562
+ for (const entry of value) {
563
+ if (!entry || typeof entry !== "object") continue;
564
+ const record = entry;
565
+ const productId = typeof record.product_id === "string" ? record.product_id.trim() : "";
566
+ const variantId = typeof record.variant_id === "string" ? record.variant_id.trim() : "";
567
+ const quantity = Number(record.quantity);
568
+ if (!productId || !variantId || !Number.isFinite(quantity) || quantity < 1) {
569
+ continue;
570
+ }
571
+ const item = {
572
+ product_id: productId,
573
+ variant_id: variantId,
574
+ quantity: Math.floor(quantity)
575
+ };
576
+ if (typeof record.price_variant_id === "string") {
577
+ item.price_variant_id = record.price_variant_id;
578
+ }
579
+ if (record.price_data && typeof record.price_data === "object") {
580
+ const priceData = record.price_data;
581
+ if (typeof priceData.unit_price === "number" && Number.isFinite(priceData.unit_price)) {
582
+ item.price_data = { unit_price: priceData.unit_price };
583
+ }
584
+ }
585
+ if (Array.isArray(record.addons)) {
586
+ item.addons = record.addons;
587
+ }
588
+ if (record.custom_fields && typeof record.custom_fields === "object" && !Array.isArray(record.custom_fields)) {
589
+ item.custom_fields = record.custom_fields;
590
+ }
591
+ normalized.push(item);
592
+ }
593
+ return normalized;
594
+ }
595
+ function getCartMetadata() {
596
+ return getItem(getStorageKey("meta"));
597
+ }
598
+ function writeCart(cart) {
599
+ setItem(getStorageKey("cart"), cart);
600
+ const now = Date.now();
601
+ const previous = getCartMetadata();
602
+ const nextMeta = {
603
+ created_at: previous?.created_at ?? now,
604
+ last_modified: now,
605
+ version: (previous?.version ?? 0) + 1,
606
+ checksum: computeChecksum(cart)
607
+ };
608
+ setItem(getStorageKey("meta"), nextMeta);
609
+ }
610
+ function setCartWithMetadata(cart, metadata) {
611
+ setItem(getStorageKey("cart"), cart);
612
+ const now = Date.now();
613
+ const base = metadata ?? getCartMetadata();
614
+ const nextMeta = {
615
+ created_at: base?.created_at ?? now,
616
+ last_modified: now,
617
+ version: base?.version ?? 1,
618
+ checksum: computeChecksum(cart)
619
+ };
620
+ setItem(getStorageKey("meta"), nextMeta);
621
+ }
622
+ function getCart() {
623
+ const raw = getItem(getStorageKey("cart"));
624
+ return normalizeCartItems(raw);
625
+ }
626
+ function getCartItemCount() {
627
+ const cart = getCart();
628
+ return cart.reduce((sum, item) => sum + item.quantity, 0);
629
+ }
630
+ function addToCart(productId, variantId, quantity = 1, options) {
631
+ if (!productId || !variantId) {
632
+ throw new CartError("product_id and variant_id are required");
633
+ }
634
+ const normalizedQuantity = normalizeQuantity(quantity);
635
+ if (normalizedQuantity < 1) {
636
+ throw new CartError("quantity must be at least 1");
637
+ }
638
+ const cart = getCart();
639
+ const existingIndex = cart.findIndex(
640
+ (item) => item.product_id === productId && item.variant_id === variantId
641
+ );
642
+ if (existingIndex >= 0) {
643
+ cart[existingIndex].quantity += normalizedQuantity;
644
+ if (options?.addons) {
645
+ cart[existingIndex].addons = options.addons;
646
+ }
647
+ if (options?.custom_fields) {
648
+ cart[existingIndex].custom_fields = options.custom_fields;
649
+ }
650
+ if (options?.price_variant_id) {
651
+ cart[existingIndex].price_variant_id = options.price_variant_id;
652
+ }
653
+ if (options?.price_data) {
654
+ cart[existingIndex].price_data = options.price_data;
655
+ }
656
+ } else {
657
+ cart.push({
658
+ product_id: productId,
659
+ variant_id: variantId,
660
+ quantity: normalizedQuantity,
661
+ addons: options?.addons,
662
+ custom_fields: options?.custom_fields,
663
+ price_variant_id: options?.price_variant_id,
664
+ price_data: options?.price_data
665
+ });
666
+ }
667
+ writeCart(cart);
668
+ }
669
+ function updateCartItem(productId, variantId, updates) {
670
+ const cart = getCart();
671
+ const index = cart.findIndex(
672
+ (item) => item.product_id === productId && item.variant_id === variantId
673
+ );
674
+ if (index < 0) {
675
+ throw new CartError("Item not found in cart");
676
+ }
677
+ if (updates.quantity !== void 0) {
678
+ const normalizedQuantity = normalizeQuantity(updates.quantity);
679
+ if (normalizedQuantity < 1) {
680
+ cart.splice(index, 1);
681
+ writeCart(cart);
682
+ return;
683
+ }
684
+ cart[index].quantity = normalizedQuantity;
685
+ }
686
+ if (updates.addons !== void 0) {
687
+ cart[index].addons = updates.addons;
688
+ }
689
+ if (updates.custom_fields !== void 0) {
690
+ cart[index].custom_fields = updates.custom_fields;
691
+ }
692
+ if (updates.price_variant_id !== void 0) {
693
+ cart[index].price_variant_id = updates.price_variant_id;
694
+ }
695
+ if (updates.price_data !== void 0) {
696
+ cart[index].price_data = updates.price_data;
697
+ }
698
+ writeCart(cart);
699
+ }
700
+ function removeFromCart(productId, variantId) {
701
+ const cart = getCart();
702
+ const filtered = cart.filter(
703
+ (item) => !(item.product_id === productId && item.variant_id === variantId)
704
+ );
705
+ writeCart(filtered);
706
+ }
707
+ function clearCart() {
708
+ removeItem(getStorageKey("cart"));
709
+ removeItem(getStorageKey("meta"));
710
+ }
711
+ function createCartBackup() {
712
+ const cart = getCart();
713
+ setItem(getStorageKey("cartBackup"), cart);
714
+ const metadata = getCartMetadata();
715
+ if (metadata) {
716
+ setItem(getStorageKey("metaBackup"), metadata);
717
+ } else {
718
+ const now = Date.now();
719
+ setItem(getStorageKey("metaBackup"), {
720
+ created_at: now,
721
+ last_modified: now,
722
+ version: 1,
723
+ checksum: computeChecksum(cart)
724
+ });
725
+ }
726
+ }
727
+ function restoreCartFromBackup() {
728
+ const backupRaw = getItem(getStorageKey("cartBackup"));
729
+ const backup = normalizeCartItems(backupRaw);
730
+ const backupMeta = getItem(getStorageKey("metaBackup"));
731
+ if (backup && backup.length > 0) {
732
+ setCartWithMetadata(backup, backupMeta);
733
+ return true;
734
+ }
735
+ return false;
736
+ }
737
+ function mergeBaskets(items) {
738
+ const cart = getCart();
739
+ for (const incoming of items) {
740
+ const productId = typeof incoming.product_id === "string" ? incoming.product_id.trim() : "";
741
+ const variantId = typeof incoming.variant_id === "string" ? incoming.variant_id.trim() : "";
742
+ if (!productId || !variantId) {
743
+ continue;
744
+ }
745
+ let normalizedQuantity;
746
+ try {
747
+ normalizedQuantity = normalizeQuantity(incoming.quantity);
748
+ } catch {
749
+ continue;
750
+ }
751
+ if (normalizedQuantity < 1) {
752
+ continue;
753
+ }
754
+ const index = cart.findIndex(
755
+ (item) => item.product_id === productId && item.variant_id === variantId
756
+ );
757
+ if (index >= 0) {
758
+ cart[index].quantity = Math.max(cart[index].quantity, normalizedQuantity);
759
+ } else {
760
+ cart.push({
761
+ ...incoming,
762
+ product_id: productId,
763
+ variant_id: variantId,
764
+ quantity: normalizedQuantity
765
+ });
766
+ }
767
+ }
768
+ writeCart(cart);
769
+ return cart;
770
+ }
771
+ function moveBasketItem(fromProductId, fromVariantId, toProductId, toVariantId) {
772
+ const cart = getCart();
773
+ const fromIndex = cart.findIndex(
774
+ (item) => item.product_id === fromProductId && item.variant_id === fromVariantId
775
+ );
776
+ if (fromIndex < 0) {
777
+ throw new CartError("Item not found in cart");
778
+ }
779
+ const [fromItem] = cart.splice(fromIndex, 1);
780
+ const toIndex = cart.findIndex(
781
+ (item) => item.product_id === toProductId && item.variant_id === toVariantId
782
+ );
783
+ if (toIndex >= 0) {
784
+ cart[toIndex].quantity += fromItem.quantity;
785
+ } else {
786
+ cart.push({
787
+ ...fromItem,
788
+ product_id: toProductId,
789
+ variant_id: toVariantId
790
+ });
791
+ }
792
+ writeCart(cart);
793
+ }
794
+ function validateCartIntegrity() {
795
+ const cart = getCart();
796
+ const metadata = getCartMetadata();
797
+ const checksum = computeChecksum(cart);
798
+ if (!metadata) {
799
+ setCartWithMetadata(cart, null);
800
+ return true;
801
+ }
802
+ return metadata.checksum === checksum;
803
+ }
804
+ function getCartStats() {
805
+ const cart = getCart();
806
+ const integrityValid = validateCartIntegrity();
807
+ const metadata = getCartMetadata();
808
+ const backup = getItem(getStorageKey("cartBackup")) ?? [];
809
+ const hasCompletePriceSnapshots = cart.length > 0 && cart.every((item) => typeof item.price_data?.unit_price === "number");
810
+ const totalPrice = cart.reduce((sum, item) => {
811
+ const unitPrice = item.price_data?.unit_price ?? 0;
812
+ return sum + unitPrice * item.quantity;
813
+ }, 0);
814
+ return {
815
+ item_count: cart.length,
816
+ total_quantity: cart.reduce((sum, item) => sum + item.quantity, 0),
817
+ last_modified: metadata?.last_modified ?? 0,
818
+ version: metadata?.version ?? 0,
819
+ has_backup: backup.length > 0,
820
+ integrity_valid: integrityValid,
821
+ total_price: totalPrice,
822
+ total_price_is_estimate: cart.length > 0 && !hasCompletePriceSnapshots
823
+ };
824
+ }
825
+
826
+ // ../sdk/src/modules/checkout.ts
827
+ var CHECKOUT_PREFILL_EMAIL_HASH_KEY = "shoppex_prefill_email";
828
+ function normalizeCoupon(coupon) {
829
+ const normalized = coupon?.trim();
830
+ return normalized ? normalized : null;
831
+ }
832
+ function normalizeEmail(email) {
833
+ const normalized = email?.trim();
834
+ return normalized ? normalized : null;
835
+ }
836
+ function normalizeCheckoutFailureMessage(rawMessage) {
837
+ const message = rawMessage?.trim() ?? "";
838
+ if (!message) {
839
+ return "Checkout failed. Please try again.";
840
+ }
841
+ if (isStaleCartProductError(message)) {
842
+ return "Your cart is outdated. Please add the products again.";
843
+ }
844
+ const httpMatch = message.match(/^HTTP\s+(\d{3})(?::\s*(.*))?$/i);
845
+ if (httpMatch) {
846
+ const status = Number(httpMatch[1]);
847
+ const detail = httpMatch[2]?.trim();
848
+ if (detail && detail.length > 0) {
849
+ return `Checkout failed: ${detail}`;
850
+ }
851
+ if (status >= 500) {
852
+ return "Checkout is temporarily unavailable. Please try again.";
853
+ }
854
+ if (status === 400) {
855
+ return "Checkout failed. Please check your details and try again.";
856
+ }
857
+ if (status === 401 || status === 403) {
858
+ return "Checkout is currently unavailable for this request.";
859
+ }
860
+ return "Checkout failed. Please try again.";
861
+ }
862
+ if (/^internal server error$/i.test(message)) {
863
+ return "Checkout is temporarily unavailable. Please try again.";
864
+ }
865
+ return message;
866
+ }
867
+ function isStaleCartProductError(rawMessage) {
868
+ const message = rawMessage?.trim().toLowerCase() ?? "";
869
+ if (!message) {
870
+ return false;
871
+ }
872
+ return message.includes("product not found") || message.includes("product not available") || message.includes("products are no longer available") || message.includes("outdated product");
873
+ }
874
+ function validateCheckoutUrl(checkoutUrl, checkoutBaseUrl, expectedInvoiceId) {
875
+ const expectedBaseUrl = checkoutBaseUrl?.trim();
876
+ if (!expectedBaseUrl) {
877
+ return null;
878
+ }
879
+ try {
880
+ const parsedCheckoutUrl = new URL(checkoutUrl);
881
+ const parsedExpectedBaseUrl = new URL(expectedBaseUrl);
882
+ if (parsedCheckoutUrl.origin !== parsedExpectedBaseUrl.origin) {
883
+ return null;
884
+ }
885
+ const normalizedPath = parsedCheckoutUrl.pathname.replace(/\/+$/, "");
886
+ const normalizedBasePath = parsedExpectedBaseUrl.pathname.replace(/\/+$/, "");
887
+ const expectedInvoicePath = `${normalizedBasePath}/invoice/`.replace(/\/{2,}/g, "/");
888
+ if (!normalizedPath.startsWith(expectedInvoicePath)) {
889
+ return null;
890
+ }
891
+ const invoiceIdSegment = normalizedPath.slice(expectedInvoicePath.length);
892
+ if (!invoiceIdSegment || invoiceIdSegment.includes("/")) {
893
+ return null;
894
+ }
895
+ if (expectedInvoiceId) {
896
+ const normalizedExpectedInvoiceId = expectedInvoiceId.trim();
897
+ const invoiceIdFromUrl = decodeURIComponent(invoiceIdSegment);
898
+ if (!normalizedExpectedInvoiceId || invoiceIdFromUrl !== normalizedExpectedInvoiceId) {
899
+ return null;
900
+ }
901
+ }
902
+ return parsedCheckoutUrl.toString();
903
+ } catch {
904
+ return null;
905
+ }
906
+ }
907
+ function buildCheckoutUrlFromInvoiceId(checkoutBaseUrl, invoiceId) {
908
+ const normalizedBaseUrl = checkoutBaseUrl?.trim();
909
+ if (!normalizedBaseUrl) {
910
+ return null;
911
+ }
912
+ try {
913
+ const baseUrl = new URL(normalizedBaseUrl);
914
+ const basePath = baseUrl.pathname.replace(/\/+$/, "");
915
+ baseUrl.pathname = `${basePath}/invoice/${encodeURIComponent(invoiceId)}`.replace(/\/{2,}/g, "/");
916
+ baseUrl.search = "";
917
+ baseUrl.hash = "";
918
+ return baseUrl.toString();
919
+ } catch {
920
+ return null;
921
+ }
922
+ }
923
+ function appendPrefillEmailToCheckoutUrl(checkoutUrl, email) {
924
+ const normalizedEmail = normalizeEmail(email);
925
+ if (!normalizedEmail) {
926
+ return checkoutUrl;
927
+ }
928
+ try {
929
+ const parsedCheckoutUrl = new URL(checkoutUrl);
930
+ const hashParams = new URLSearchParams(parsedCheckoutUrl.hash.startsWith("#") ? parsedCheckoutUrl.hash.slice(1) : parsedCheckoutUrl.hash);
931
+ hashParams.set(CHECKOUT_PREFILL_EMAIL_HASH_KEY, normalizedEmail);
932
+ parsedCheckoutUrl.hash = hashParams.toString();
933
+ return parsedCheckoutUrl.toString();
934
+ } catch {
935
+ return checkoutUrl;
936
+ }
937
+ }
938
+ function normalizeCheckoutResponse(response, checkoutBaseUrl) {
939
+ const nestedInvoice = response?.invoice;
940
+ const invoiceId = response?.invoiceId?.trim() || response?.invoice_id?.trim() || response?.uniqid?.trim() || nestedInvoice?.invoiceId?.trim() || nestedInvoice?.invoice_id?.trim() || nestedInvoice?.uniqid?.trim();
941
+ let checkoutUrl = response?.checkoutUrl?.trim() || response?.checkout_url?.trim() || response?.url_branded?.trim() || response?.url?.trim() || nestedInvoice?.checkoutUrl?.trim() || nestedInvoice?.checkout_url?.trim() || nestedInvoice?.url_branded?.trim() || nestedInvoice?.url?.trim();
942
+ if (!checkoutUrl && invoiceId && nestedInvoice) {
943
+ checkoutUrl = buildCheckoutUrlFromInvoiceId(checkoutBaseUrl, invoiceId) ?? void 0;
944
+ }
945
+ if (!invoiceId || !checkoutUrl) {
946
+ return null;
947
+ }
948
+ return { invoiceId, checkoutUrl };
949
+ }
950
+ function resolveCheckoutOptions(couponOrOptions, options) {
951
+ if (typeof couponOrOptions === "string") {
952
+ return {
953
+ ...options,
954
+ coupon: couponOrOptions
955
+ };
956
+ }
957
+ return couponOrOptions ?? options ?? {};
958
+ }
959
+ function mapCartItemsForApi(items) {
960
+ const normalizeVariantIdForApi = (value) => {
961
+ const normalized = value?.trim();
962
+ if (!normalized) return null;
963
+ if (normalized.toLowerCase() === "default") return null;
964
+ return normalized;
965
+ };
966
+ return items.map((item) => ({
967
+ product_id: item.product_id,
968
+ variant_id: normalizeVariantIdForApi(item.variant_id),
969
+ quantity: item.quantity,
970
+ addons: item.addons?.map((a) => ({ id: a.id, quantity: a.quantity ?? 1 })),
971
+ custom_fields: item.custom_fields,
972
+ price_variant_id: item.price_variant_id || null
973
+ }));
974
+ }
975
+ async function checkout(couponOrOptions, options) {
976
+ const resolvedOptions = resolveCheckoutOptions(couponOrOptions, options);
977
+ const { autoRedirect = true, email, coupon, affiliateCode } = resolvedOptions;
978
+ const normalizedCoupon = normalizeCoupon(coupon);
979
+ const normalizedEmail = normalizeEmail(email);
980
+ const normalizedAffiliateCode = typeof affiliateCode === "string" && affiliateCode.trim().length > 0 ? affiliateCode.trim() : null;
981
+ const cart = getCart();
982
+ if (cart.length === 0) {
983
+ return {
984
+ success: false,
985
+ message: "Cart is empty"
986
+ };
987
+ }
988
+ createCartBackup();
989
+ const config = getConfig();
990
+ let response = await post("/v1/storefront/invoices/from-cart", {
991
+ shop_slug: config.storeSlug,
992
+ cart: mapCartItemsForApi(cart),
993
+ email: normalizedEmail,
994
+ coupon: normalizedCoupon,
995
+ affiliate_code: normalizedAffiliateCode
996
+ }, { retries: 0 });
997
+ if (!response.success || !response.data) {
998
+ if (isStaleCartProductError(response.message)) {
999
+ clearCart();
1000
+ }
1001
+ return {
1002
+ success: false,
1003
+ message: normalizeCheckoutFailureMessage(response.message)
1004
+ };
1005
+ }
1006
+ const checkoutData = normalizeCheckoutResponse(response.data, config.checkoutBaseUrl);
1007
+ if (!checkoutData) {
1008
+ return {
1009
+ success: false,
1010
+ message: "Failed to create invoice"
1011
+ };
1012
+ }
1013
+ const { invoiceId } = checkoutData;
1014
+ const safeCheckoutUrl = validateCheckoutUrl(
1015
+ checkoutData.checkoutUrl,
1016
+ config.checkoutBaseUrl,
1017
+ invoiceId
1018
+ );
1019
+ if (!safeCheckoutUrl) {
1020
+ return {
1021
+ success: false,
1022
+ message: "Failed to create invoice"
1023
+ };
1024
+ }
1025
+ const checkoutUrlWithPrefill = appendPrefillEmailToCheckoutUrl(safeCheckoutUrl, normalizedEmail);
1026
+ if (autoRedirect) {
1027
+ if (typeof window !== "undefined" && window?.location) {
1028
+ window.location.href = checkoutUrlWithPrefill;
1029
+ clearCart();
1030
+ }
1031
+ }
1032
+ return {
1033
+ success: true,
1034
+ redirectUrl: checkoutUrlWithPrefill,
1035
+ invoiceId
1036
+ };
1037
+ }
1038
+ async function buildCheckoutUrl(couponOrOptions, options) {
1039
+ const resolvedOptions = resolveCheckoutOptions(couponOrOptions, options);
1040
+ const { email, coupon, affiliateCode } = resolvedOptions;
1041
+ const normalizedCoupon = normalizeCoupon(coupon);
1042
+ const normalizedEmail = normalizeEmail(email);
1043
+ const normalizedAffiliateCode = typeof affiliateCode === "string" && affiliateCode.trim().length > 0 ? affiliateCode.trim() : null;
1044
+ const cart = getCart();
1045
+ if (cart.length === 0) {
1046
+ throw new Error("Cart is empty");
1047
+ }
1048
+ const config = getConfig();
1049
+ let response = await post("/v1/storefront/invoices/from-cart", {
1050
+ shop_slug: config.storeSlug,
1051
+ cart: mapCartItemsForApi(cart),
1052
+ email: normalizedEmail,
1053
+ coupon: normalizedCoupon,
1054
+ affiliate_code: normalizedAffiliateCode
1055
+ }, { retries: 0 });
1056
+ if (!response.success || !response.data) {
1057
+ if (isStaleCartProductError(response.message)) {
1058
+ clearCart();
1059
+ }
1060
+ throw new Error(normalizeCheckoutFailureMessage(response.message));
1061
+ }
1062
+ const checkoutData = normalizeCheckoutResponse(response.data, config.checkoutBaseUrl);
1063
+ if (!checkoutData) {
1064
+ throw new Error("Failed to create invoice");
1065
+ }
1066
+ const safeCheckoutUrl = validateCheckoutUrl(
1067
+ checkoutData.checkoutUrl,
1068
+ config.checkoutBaseUrl,
1069
+ checkoutData.invoiceId
1070
+ );
1071
+ if (!safeCheckoutUrl) {
1072
+ throw new Error("Failed to create invoice");
1073
+ }
1074
+ return appendPrefillEmailToCheckoutUrl(safeCheckoutUrl, normalizedEmail);
1075
+ }
1076
+ function buildCheckoutUrlSync() {
1077
+ throw new Error("buildCheckoutUrlSync is deprecated. Use buildCheckoutUrl (async) instead.");
1078
+ }
1079
+
1080
+ // ../sdk/src/modules/affiliates.ts
1081
+ var STORAGE_KEY = "shoppex:affiliate_code:v1";
1082
+ var DEFAULT_TTL_DAYS = 30;
1083
+ function nowMs() {
1084
+ return Date.now();
1085
+ }
1086
+ function ttlMs(days) {
1087
+ return Math.max(1, days) * 24 * 60 * 60 * 1e3;
1088
+ }
1089
+ function safeRead() {
1090
+ if (typeof window === "undefined") return null;
1091
+ try {
1092
+ const raw = window.localStorage.getItem(STORAGE_KEY);
1093
+ if (!raw) return null;
1094
+ const parsed = JSON.parse(raw);
1095
+ if (!parsed || typeof parsed.code !== "string" || typeof parsed.expiresAt !== "number") return null;
1096
+ return parsed;
1097
+ } catch {
1098
+ return null;
1099
+ }
1100
+ }
1101
+ function safeWrite(value) {
1102
+ if (typeof window === "undefined") return;
1103
+ try {
1104
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
1105
+ } catch {
1106
+ }
1107
+ }
1108
+ function clearAffiliateCode() {
1109
+ if (typeof window === "undefined") return;
1110
+ try {
1111
+ window.localStorage.removeItem(STORAGE_KEY);
1112
+ } catch {
1113
+ }
1114
+ }
1115
+ function getAffiliateCode() {
1116
+ const stored = safeRead();
1117
+ if (!stored) return null;
1118
+ if (stored.expiresAt <= nowMs()) {
1119
+ clearAffiliateCode();
1120
+ return null;
1121
+ }
1122
+ const normalized = stored.code.trim();
1123
+ return normalized.length > 0 ? normalized : null;
1124
+ }
1125
+ async function captureAffiliateFromUrl(param = "ref") {
1126
+ if (typeof window === "undefined") return null;
1127
+ let code = null;
1128
+ try {
1129
+ const url = new URL(window.location.href);
1130
+ const raw = url.searchParams.get(param);
1131
+ code = raw ? raw.trim() : null;
1132
+ } catch {
1133
+ code = null;
1134
+ }
1135
+ if (!code) return null;
1136
+ safeWrite({ code, expiresAt: nowMs() + ttlMs(DEFAULT_TTL_DAYS) });
1137
+ if (isInitialized()) {
1138
+ try {
1139
+ const config = getConfig();
1140
+ const res = await post(
1141
+ "/v1/storefront/affiliates/attribution",
1142
+ { shop_slug: config.storeSlug, code },
1143
+ { retries: 0 }
1144
+ );
1145
+ if (!res.success || !res.data?.accepted || !res.data?.affiliate_code) {
1146
+ clearAffiliateCode();
1147
+ return null;
1148
+ }
1149
+ safeWrite({ code: res.data.affiliate_code, expiresAt: nowMs() + ttlMs(DEFAULT_TTL_DAYS) });
1150
+ return res.data.affiliate_code;
1151
+ } catch {
1152
+ return code;
1153
+ }
1154
+ }
1155
+ return code;
1156
+ }
1157
+
1158
+ // ../sdk/src/modules/coupons.ts
1159
+ async function resolveShopId() {
1160
+ const cachedShopId2 = getShopId();
1161
+ if (cachedShopId2) {
1162
+ return cachedShopId2;
1163
+ }
1164
+ const storeResult = await getStore();
1165
+ if (!storeResult.success || !storeResult.data?.id) {
1166
+ return null;
1167
+ }
1168
+ return storeResult.data.id;
1169
+ }
1170
+ async function validateCoupon(code, productId) {
1171
+ const trimmedCode = code.trim();
1172
+ if (!trimmedCode) {
1173
+ return {
1174
+ success: false,
1175
+ message: "Coupon code is required"
1176
+ };
1177
+ }
1178
+ const payload = {
1179
+ code: trimmedCode
1180
+ };
1181
+ if (productId) {
1182
+ payload.product_id = productId;
1183
+ } else {
1184
+ const cart = getCart();
1185
+ if (cart.length === 0) {
1186
+ return {
1187
+ success: false,
1188
+ message: "Cart is empty"
1189
+ };
1190
+ }
1191
+ const shopId = await resolveShopId();
1192
+ if (!shopId) {
1193
+ return {
1194
+ success: false,
1195
+ message: "Failed to resolve store"
1196
+ };
1197
+ }
1198
+ payload.cart = JSON.stringify({
1199
+ shop_id: shopId,
1200
+ products: cart.map((item) => ({
1201
+ uniqid: item.product_id,
1202
+ quantity: item.quantity
1203
+ }))
1204
+ });
1205
+ }
1206
+ const response = await post(
1207
+ "/v1/storefront/coupons/check",
1208
+ payload
1209
+ );
1210
+ return response;
1211
+ }
1212
+
1213
+ // ../sdk/src/modules/reviews.ts
1214
+ async function getShopReviews() {
1215
+ const config = getConfig();
1216
+ const response = await get(
1217
+ buildEndpoint("/v1/storefront/feedback/shop/:storeSlug", {
1218
+ storeSlug: config.storeSlug
1219
+ })
1220
+ );
1221
+ if (response.success && response.data) {
1222
+ return {
1223
+ success: true,
1224
+ data: response.data.feedback
1225
+ };
1226
+ }
1227
+ return {
1228
+ success: false,
1229
+ message: response.message,
1230
+ data: []
1231
+ };
1232
+ }
1233
+
1234
+ // ../sdk/src/modules/search.ts
1235
+ async function searchProducts(query, options) {
1236
+ if (!isInitialized()) {
1237
+ return { success: false, message: "SDK not initialized" };
1238
+ }
1239
+ const trimmed = query.trim().toLowerCase();
1240
+ if (!trimmed) {
1241
+ return { success: true, data: [] };
1242
+ }
1243
+ const products = await getProducts();
1244
+ if (!products.success || !products.data) {
1245
+ return { success: false, message: products.message ?? "Failed to fetch products" };
1246
+ }
1247
+ let results = products.data.filter(
1248
+ (p) => p.title.toLowerCase().includes(trimmed) || (p.description?.toLowerCase().includes(trimmed) ?? false)
1249
+ );
1250
+ if (options?.hideOutOfStock) {
1251
+ results = results.filter((p) => p.stock === void 0 || p.stock === -1 || p.stock > 0);
1252
+ }
1253
+ return { success: true, data: results };
1254
+ }
1255
+
1256
+ // ../sdk/src/modules/invoices.ts
1257
+ function normalizeInvoiceId(invoiceId) {
1258
+ const normalized = invoiceId.trim();
1259
+ if (!normalized) {
1260
+ return null;
1261
+ }
1262
+ return normalized;
1263
+ }
1264
+ async function getInvoice(invoiceId) {
1265
+ const normalizedInvoiceId = normalizeInvoiceId(invoiceId);
1266
+ if (!normalizedInvoiceId) {
1267
+ return {
1268
+ success: false,
1269
+ message: "Invoice ID is required"
1270
+ };
1271
+ }
1272
+ const response = await get(
1273
+ buildEndpoint("/v1/storefront/invoices/unique/:invoiceId", {
1274
+ invoiceId: normalizedInvoiceId
1275
+ })
1276
+ );
1277
+ if (!response.success) {
1278
+ return {
1279
+ success: false,
1280
+ message: response.message
1281
+ };
1282
+ }
1283
+ if (!response.data?.invoice) {
1284
+ return {
1285
+ success: false,
1286
+ message: "Invalid invoice response"
1287
+ };
1288
+ }
1289
+ return {
1290
+ success: true,
1291
+ data: response.data.invoice
1292
+ };
1293
+ }
1294
+ async function getInvoiceStatus(invoiceId) {
1295
+ const normalizedInvoiceId = normalizeInvoiceId(invoiceId);
1296
+ if (!normalizedInvoiceId) {
1297
+ return {
1298
+ success: false,
1299
+ message: "Invoice ID is required"
1300
+ };
1301
+ }
1302
+ const response = await get(
1303
+ buildEndpoint("/v1/storefront/invoices/status/:invoiceId", {
1304
+ invoiceId: normalizedInvoiceId
1305
+ })
1306
+ );
1307
+ if (!response.success) {
1308
+ return {
1309
+ success: false,
1310
+ message: response.message
1311
+ };
1312
+ }
1313
+ if (!response.data?.invoice?.status) {
1314
+ return {
1315
+ success: false,
1316
+ message: "Invalid invoice status response"
1317
+ };
1318
+ }
1319
+ return {
1320
+ success: true,
1321
+ data: { status: response.data.invoice.status }
1322
+ };
1323
+ }
1324
+
1325
+ // ../sdk/src/modules/pages.ts
1326
+ var PAGES_CACHE_TTL = 5 * 60 * 1e3;
1327
+ async function getPages() {
1328
+ const config = getConfig();
1329
+ const response = await get(
1330
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/pages", {
1331
+ storeSlug: config.storeSlug
1332
+ }),
1333
+ {
1334
+ cache: {
1335
+ key: `pages:${config.storeSlug}`,
1336
+ ttl: PAGES_CACHE_TTL,
1337
+ staleWhileRevalidate: true
1338
+ }
1339
+ }
1340
+ );
1341
+ if (response.success && response.data) {
1342
+ return {
1343
+ success: true,
1344
+ data: response.data.pages
1345
+ };
1346
+ }
1347
+ return {
1348
+ success: false,
1349
+ message: response.message
1350
+ };
1351
+ }
1352
+ async function getPage(slug) {
1353
+ const config = getConfig();
1354
+ const response = await get(
1355
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/pages/:slug", {
1356
+ storeSlug: config.storeSlug,
1357
+ slug
1358
+ }),
1359
+ {
1360
+ cache: {
1361
+ key: `page:${config.storeSlug}:${slug}`,
1362
+ ttl: PAGES_CACHE_TTL,
1363
+ staleWhileRevalidate: true
1364
+ }
1365
+ }
1366
+ );
1367
+ if (response.success && response.data) {
1368
+ return {
1369
+ success: true,
1370
+ data: response.data.page
1371
+ };
1372
+ }
1373
+ return {
1374
+ success: false,
1375
+ message: response.message
1376
+ };
1377
+ }
1378
+
1379
+ // ../contracts/src/navigation.ts
1380
+ var NAVIGATION_MENU_SLOT_CONFIG = {
1381
+ header: {
1382
+ title: "Header",
1383
+ aliases: ["Header Menu"]
1384
+ },
1385
+ footer: {
1386
+ title: "Footer",
1387
+ aliases: ["Footer Links", "Legal Links"]
1388
+ }
1389
+ };
1390
+ function getNavigationMenuTitles(slot) {
1391
+ const config = NAVIGATION_MENU_SLOT_CONFIG[slot];
1392
+ return [config.title, ...config.aliases];
1393
+ }
1394
+ var NAVIGATION_MENU_SLOTS = Object.keys(NAVIGATION_MENU_SLOT_CONFIG);
1395
+
1396
+ // ../sdk/src/modules/navigation.ts
1397
+ var NAVIGATION_CACHE_TTL = 5 * 60 * 1e3;
1398
+ async function getMenus() {
1399
+ const config = getConfig();
1400
+ const response = await get(
1401
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/menus", {
1402
+ storeSlug: config.storeSlug
1403
+ }),
1404
+ {
1405
+ cache: {
1406
+ key: `menus:${config.storeSlug}`,
1407
+ ttl: NAVIGATION_CACHE_TTL,
1408
+ staleWhileRevalidate: true
1409
+ }
1410
+ }
1411
+ );
1412
+ if (response.success && response.data) {
1413
+ return {
1414
+ success: true,
1415
+ data: response.data.menus
1416
+ };
1417
+ }
1418
+ return {
1419
+ success: false,
1420
+ message: response.message
1421
+ };
1422
+ }
1423
+ async function getMenuByTitle(title) {
1424
+ const config = getConfig();
1425
+ const response = await get(
1426
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/menus/:title", {
1427
+ storeSlug: config.storeSlug,
1428
+ title
1429
+ }),
1430
+ {
1431
+ cache: {
1432
+ key: `menu:${config.storeSlug}:${title}`,
1433
+ ttl: NAVIGATION_CACHE_TTL,
1434
+ staleWhileRevalidate: true
1435
+ }
1436
+ }
1437
+ );
1438
+ if (response.success && response.data) {
1439
+ return {
1440
+ success: true,
1441
+ data: response.data.menu
1442
+ };
1443
+ }
1444
+ return {
1445
+ success: false,
1446
+ message: response.message
1447
+ };
1448
+ }
1449
+ async function getMenuBySlot(slot) {
1450
+ const config = getConfig();
1451
+ const response = await get(
1452
+ buildEndpoint("/v1/storefront/shops/name/:storeSlug/menus/:title", {
1453
+ storeSlug: config.storeSlug,
1454
+ title: slot
1455
+ }),
1456
+ {
1457
+ cache: {
1458
+ key: `menu-slot:${config.storeSlug}:${slot}`,
1459
+ ttl: NAVIGATION_CACHE_TTL,
1460
+ staleWhileRevalidate: true
1461
+ }
1462
+ }
1463
+ );
1464
+ if (response.success && response.data) {
1465
+ return {
1466
+ success: true,
1467
+ data: response.data.menu
1468
+ };
1469
+ }
1470
+ return {
1471
+ success: false,
1472
+ message: response.message
1473
+ };
1474
+ }
1475
+ function getMenuSlotTitles(slot) {
1476
+ return getNavigationMenuTitles(slot);
1477
+ }
1478
+
1479
+ // ../sdk/src/modules/analytics.ts
1480
+ function createPresenceConnectionId() {
1481
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1482
+ return crypto.randomUUID();
1483
+ }
1484
+ return `spx_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
1485
+ }
1486
+ function getPresenceConnectionId(storeSlug) {
1487
+ if (typeof window === "undefined") return null;
1488
+ const storageKey = `presence_connection_${storeSlug}`;
1489
+ const existing = getItem(storageKey);
1490
+ if (existing && existing.trim()) {
1491
+ return existing;
1492
+ }
1493
+ const nextId = createPresenceConnectionId();
1494
+ setItem(storageKey, nextId);
1495
+ return nextId;
1496
+ }
1497
+ async function trackPageView(cartValue, itemCount) {
1498
+ if (!isInitialized()) return;
1499
+ if (typeof document === "undefined") return;
1500
+ const config = getConfig();
1501
+ const connectionId = getPresenceConnectionId(config.storeSlug);
1502
+ try {
1503
+ const endpoint = buildEndpoint("/v1/storefront/shops/:storeSlug/ping", {
1504
+ storeSlug: config.storeSlug
1505
+ });
1506
+ await fetch(`${config.apiBaseUrl}${endpoint}`, {
1507
+ method: "POST",
1508
+ headers: { "Content-Type": "application/json" },
1509
+ body: JSON.stringify({
1510
+ referer: document.referrer || void 0,
1511
+ cart_value: cartValue ?? void 0,
1512
+ item_count: itemCount ?? void 0,
1513
+ connection_id: connectionId ?? void 0
1514
+ })
1515
+ });
1516
+ } catch {
1517
+ }
1518
+ }
1519
+
1520
+ // ../sdk/src/utils/format.ts
1521
+ function createFormatter(currency, locale) {
1522
+ const config = getConfig();
1523
+ return new Intl.NumberFormat(locale ?? config.locale ?? "en-US", {
1524
+ style: "currency",
1525
+ currency: currency ?? config.currency ?? "USD"
1526
+ });
1527
+ }
1528
+ function formatPrice(amount, currency, locale) {
1529
+ const numericAmount = typeof amount === "string" ? parseFloat(amount) : amount;
1530
+ if (Number.isNaN(numericAmount)) {
1531
+ return createFormatter(currency, locale).format(0);
1532
+ }
1533
+ return createFormatter(currency, locale).format(numericAmount);
1534
+ }
1535
+
1536
+ // ../sdk/src/modules/theme.ts
1537
+ async function fetchPublishedThemeSettings(shopSlug) {
1538
+ const result = await get(
1539
+ buildEndpoint("/v1/storefront/themes/builder/published/:shopSlug", { shopSlug })
1540
+ );
1541
+ return result.success && result.data ? result.data.settings : null;
1542
+ }
1543
+ function resolveDefaults(config) {
1544
+ const resolved = {};
1545
+ for (const [category, fields] of Object.entries(config.settings)) {
1546
+ resolved[category] = {};
1547
+ for (const [key, field] of Object.entries(fields)) {
1548
+ resolved[category][key] = field.default;
1549
+ }
1550
+ }
1551
+ return resolved;
1552
+ }
1553
+ function mergeSettings(defaults, overrides) {
1554
+ const merged = { ...defaults };
1555
+ for (const [category, fields] of Object.entries(overrides)) {
1556
+ if (fields) {
1557
+ merged[category] = { ...merged[category], ...fields };
1558
+ }
1559
+ }
1560
+ return merged;
1561
+ }
1562
+
1563
+ // ../sdk/src/index.ts
1564
+ function init(storeSlugOrOptions, options) {
1565
+ if (typeof storeSlugOrOptions === "string") {
1566
+ initConfig(storeSlugOrOptions, options);
1567
+ } else {
1568
+ initConfig(storeSlugOrOptions.storeId, storeSlugOrOptions);
1569
+ }
1570
+ }
1571
+ var shoppex = {
1572
+ // Initialization
1573
+ init,
1574
+ isInitialized,
1575
+ getConfig,
1576
+ // Store
1577
+ getStore,
1578
+ getStorefront,
1579
+ getStoreLogoUrl,
1580
+ getStoreBannerUrl,
1581
+ resolveStoreByDomain,
1582
+ // Products
1583
+ getProducts,
1584
+ getProduct,
1585
+ getCategories,
1586
+ // Cart
1587
+ getCart,
1588
+ getCartItemCount,
1589
+ addToCart,
1590
+ updateCartItem,
1591
+ removeFromCart,
1592
+ clearCart,
1593
+ createCartBackup,
1594
+ restoreCartFromBackup,
1595
+ mergeBaskets,
1596
+ moveBasketItem,
1597
+ getCartStats,
1598
+ validateCartIntegrity,
1599
+ // Checkout
1600
+ checkout,
1601
+ buildCheckoutUrl,
1602
+ buildCheckoutUrlSync,
1603
+ // Affiliates
1604
+ captureAffiliateFromUrl,
1605
+ getAffiliateCode,
1606
+ clearAffiliateCode,
1607
+ // Coupons
1608
+ validateCoupon,
1609
+ // Reviews
1610
+ getShopReviews,
1611
+ // Search
1612
+ searchProducts,
1613
+ // Invoices
1614
+ getInvoice,
1615
+ getInvoiceStatus,
1616
+ // Pages
1617
+ getPages,
1618
+ getPage,
1619
+ // Navigation
1620
+ getMenus,
1621
+ getMenuBySlot,
1622
+ getMenuByTitle,
1623
+ getMenuSlotTitles,
1624
+ // Analytics
1625
+ trackPageView,
1626
+ // Formatting
1627
+ createFormatter,
1628
+ formatPrice,
1629
+ // Cache
1630
+ clearCache,
1631
+ invalidateCache,
1632
+ getCacheStats,
1633
+ // Theme settings helpers
1634
+ fetchPublishedThemeSettings,
1635
+ resolveDefaults,
1636
+ mergeSettings
1637
+ };
1638
+ var src_default = shoppex;
1639
+ if (typeof window !== "undefined") {
1640
+ window.shoppex = shoppex;
1641
+ }
1642
+ export {
1643
+ CartError,
1644
+ NetworkError,
1645
+ NotInitializedError,
1646
+ ShoppexError,
1647
+ ValidationError,
1648
+ src_default as default,
1649
+ fetchPublishedThemeSettings,
1650
+ getMenuBySlot,
1651
+ getMenuByTitle,
1652
+ getMenuSlotTitles,
1653
+ mergeSettings,
1654
+ resolveDefaults,
1655
+ shoppex,
1656
+ trackPageView
1657
+ };
1658
+ //# sourceMappingURL=index.js.map