@misiki/gocommerce-connector 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.cjs ADDED
@@ -0,0 +1,2477 @@
1
+ 'use strict';
2
+
3
+ // src/services/base.ts
4
+ var API_PROXY_PREFIX = "/proxy/gocommerce";
5
+ var GoCommerceError = class extends Error {
6
+ code;
7
+ details;
8
+ status;
9
+ constructor(message, code = "internal_error", status = 0, details) {
10
+ super(message);
11
+ this.name = "GoCommerceError";
12
+ this.code = code;
13
+ this.status = status;
14
+ this.details = details;
15
+ }
16
+ };
17
+ var BaseService = class _BaseService {
18
+ _fetch;
19
+ static _credentials = {};
20
+ static _languageCode;
21
+ constructor(fetchFn) {
22
+ this._fetch = fetchFn || ((...args) => globalThis.fetch(...args));
23
+ }
24
+ setFetch(fetchFn) {
25
+ this._fetch = fetchFn;
26
+ return this;
27
+ }
28
+ getFetch() {
29
+ return this._fetch;
30
+ }
31
+ /**
32
+ * The fetch a GoCommerce call actually goes out on.
33
+ *
34
+ * In the browser it is whatever was handed in, against this storefront's own origin. On the
35
+ * server it is deliberately the platform's, not SvelteKit's: a universal `+page.ts` load is
36
+ * given a `fetch` that simulates a browser's CORS check, and GoCommerce sends no
37
+ * `Access-Control-Allow-Origin`, so the same product page that renders from a `+page.server.ts`
38
+ * would throw from a `+page.ts`. Nothing is lost by stepping around it — SvelteKit's fetch adds
39
+ * cookie forwarding and internal routing, and both are about this storefront's own origin, not
40
+ * a separate API host.
41
+ */
42
+ transport() {
43
+ return _BaseService.isBrowser() ? this._fetch : (...args) => globalThis.fetch(...args);
44
+ }
45
+ /**
46
+ * The hook the storefront's `src/lib/core/connectors/init.ts` calls with `PUBLIC_GOCOMMERCE_*`. Merges, so a
47
+ * later call with a subset never erases the base url an earlier one established.
48
+ */
49
+ static setCredentials(creds) {
50
+ _BaseService._credentials = { ..._BaseService._credentials, ...creds };
51
+ }
52
+ static getCredentials() {
53
+ return _BaseService._credentials;
54
+ }
55
+ static setBaseUrl(url) {
56
+ if (url) _BaseService._credentials.apiUrl = url;
57
+ }
58
+ /** Trailing slashes stripped so `${base}/api/products` never doubles up. */
59
+ static getBaseUrl() {
60
+ return (_BaseService._credentials.apiUrl || "").replace(/\/+$/, "");
61
+ }
62
+ static setLanguageCode(code) {
63
+ _BaseService._languageCode = code;
64
+ }
65
+ static getLanguageCode() {
66
+ return _BaseService._languageCode;
67
+ }
68
+ setBaseUrl(url) {
69
+ _BaseService.setBaseUrl(url);
70
+ return this;
71
+ }
72
+ getBaseUrl() {
73
+ return _BaseService.getBaseUrl();
74
+ }
75
+ /**
76
+ * Where a request for `path` actually goes.
77
+ *
78
+ * GoCommerce sends no CORS headers and answers a preflight with 405, so a browser cannot reach
79
+ * it cross-origin at all — and neither can a universal `+page.ts` load, because SvelteKit
80
+ * simulates the same CORS check server-side for consistency. So anything the browser issues goes
81
+ * through this storefront's own origin (`src/routes/proxy/gocommerce/[...path]`), which is
82
+ * same-origin and needs no headers from the API.
83
+ *
84
+ * On the server there is no such rule, and the extra hop through the storefront's own handler
85
+ * would be a request the process makes to itself, so it calls the API directly.
86
+ */
87
+ static apiUrl(path) {
88
+ if (/^https?:\/\//i.test(path)) return path;
89
+ const suffix = path.startsWith("/") ? path : `/${path}`;
90
+ if (_BaseService.isBrowser()) return `${API_PROXY_PREFIX}${suffix}`;
91
+ const base = _BaseService.getBaseUrl();
92
+ if (!base) {
93
+ throw new GoCommerceError("PUBLIC_GOCOMMERCE_API_URL is not configured \u2014 add it to .env (see docs/GOCOMMERCE.md).", "not_ready");
94
+ }
95
+ return `${base}${suffix}`;
96
+ }
97
+ /**
98
+ * Where an `<img>` points.
99
+ *
100
+ * Media urls come back site-relative for files this store holds (`/media/42.jpg`). Unlike an API
101
+ * call, the consumer is always the browser — a server-rendered `<img src>` is still fetched by
102
+ * it — so this always resolves through the proxy, and the picture loads wherever the API happens
103
+ * to be reachable from. A url that is already absolute belongs to someone else's host and is
104
+ * left exactly as it is.
105
+ */
106
+ static mediaUrl(path) {
107
+ if (/^https?:\/\//i.test(path)) return path;
108
+ return `${API_PROXY_PREFIX}${path.startsWith("/") ? path : `/${path}`}`;
109
+ }
110
+ static isBrowser() {
111
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
112
+ }
113
+ /**
114
+ * One request against the GoCommerce API.
115
+ *
116
+ * Unwraps `{ data, meta }` and turns `{ error: { code, message } }` into a thrown
117
+ * GoCommerceError carrying the server's own wording — a 409 at checkout says which line went
118
+ * stale, and that sentence is what the shopper needs to see.
119
+ */
120
+ async api(method, path, body, headers = {}) {
121
+ const url = _BaseService.apiUrl(path);
122
+ const lang = _BaseService.getLanguageCode();
123
+ let res;
124
+ try {
125
+ res = await this.transport()(url, {
126
+ method,
127
+ headers: {
128
+ Accept: "application/json",
129
+ ...body === void 0 ? {} : { "Content-Type": "application/json" },
130
+ ...lang ? { "Accept-Language": lang } : {},
131
+ ...headers
132
+ },
133
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
134
+ });
135
+ } catch (cause) {
136
+ const reason = cause?.cause?.code ?? cause?.message ?? "unknown error";
137
+ throw new GoCommerceError(
138
+ `Could not reach the GoCommerce server at ${_BaseService.getBaseUrl() || url} \u2014 ${reason}. Is it running?`,
139
+ "not_ready",
140
+ 0,
141
+ cause
142
+ );
143
+ }
144
+ const text = await res.text();
145
+ let parsed = void 0;
146
+ if (text) {
147
+ try {
148
+ parsed = JSON.parse(text);
149
+ } catch {
150
+ throw new GoCommerceError(`GoCommerce returned a non-JSON response for ${method} ${path} (${res.status}).`, "internal_error", res.status);
151
+ }
152
+ }
153
+ const envelope = parsed ?? {};
154
+ if (!res.ok || envelope.error) {
155
+ const error = envelope.error;
156
+ throw new GoCommerceError(
157
+ error?.message || envelope.message || `GoCommerce request failed: ${method} ${path} (${res.status}).`,
158
+ error?.code || "internal_error",
159
+ res.status,
160
+ error?.details
161
+ );
162
+ }
163
+ return { data: envelope.data, meta: envelope.meta };
164
+ }
165
+ /**
166
+ * Litekart-era raw-url reads. Deliberately not routed to GoCommerce: a bare `/api/...` here means
167
+ * a path from the storefront's own REST API, which rest-guard.ts answers from local data or
168
+ * reports. Absolute urls pass straight through.
169
+ */
170
+ async get(url) {
171
+ const res = await this._fetch(url, { headers: { Accept: "application/json" } });
172
+ return await res.json();
173
+ }
174
+ async post(url, data) {
175
+ const res = await this._fetch(url, {
176
+ method: "POST",
177
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
178
+ body: JSON.stringify(data)
179
+ });
180
+ return await res.json();
181
+ }
182
+ async put(url, data) {
183
+ const res = await this._fetch(url, {
184
+ method: "PUT",
185
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
186
+ body: JSON.stringify(data)
187
+ });
188
+ return await res.json();
189
+ }
190
+ async patch(url, data) {
191
+ const res = await this._fetch(url, {
192
+ method: "PATCH",
193
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
194
+ body: JSON.stringify(data)
195
+ });
196
+ return await res.json();
197
+ }
198
+ async delete(url) {
199
+ const res = await this._fetch(url, { method: "DELETE", headers: { Accept: "application/json" } });
200
+ return await res.json();
201
+ }
202
+ };
203
+
204
+ // src/services/static-store.ts
205
+ var provider;
206
+ var setStaticStore = (fn) => {
207
+ provider = fn;
208
+ };
209
+ var hasStaticStore = () => Boolean(provider);
210
+ var IMPOSSIBLE_FEATURES = {
211
+ accounts: { active: false },
212
+ isDiscountCoupons: { active: false },
213
+ isWishlist: { active: false },
214
+ isProductReviewsAndRatings: { active: false },
215
+ isMultiVendor: { active: false }
216
+ };
217
+ var readStaticStore = async () => {
218
+ if (!provider) return void 0;
219
+ const store = await provider();
220
+ if (!store) return store;
221
+ return {
222
+ ...store,
223
+ plugins: { ...store.plugins ?? {}, ...IMPOSSIBLE_FEATURES }
224
+ };
225
+ };
226
+ var readStaticStoreDetails = readStaticStore;
227
+
228
+ // src/services/money.ts
229
+ var DEFAULT_FRACTION_DIGITS = 2;
230
+ var fractionDigitsCache = /* @__PURE__ */ new Map();
231
+ var currencyFractionDigits = (currencyCode) => {
232
+ if (!currencyCode) return DEFAULT_FRACTION_DIGITS;
233
+ const code = currencyCode.toUpperCase();
234
+ const cached = fractionDigitsCache.get(code);
235
+ if (cached !== void 0) return cached;
236
+ let digits = DEFAULT_FRACTION_DIGITS;
237
+ try {
238
+ const resolved = new Intl.NumberFormat("en", { style: "currency", currency: code }).resolvedOptions().maximumFractionDigits;
239
+ if (typeof resolved === "number") digits = resolved;
240
+ } catch {
241
+ }
242
+ fractionDigitsCache.set(code, digits);
243
+ return digits;
244
+ };
245
+ var fromMoney = (money) => {
246
+ if (!money || typeof money.amount_minor !== "number" || !Number.isFinite(money.amount_minor)) return 0;
247
+ const digits = currencyFractionDigits(money.currency);
248
+ return digits === 0 ? money.amount_minor : money.amount_minor / 10 ** digits;
249
+ };
250
+
251
+ // src/services/mappers.ts
252
+ var PAGE_SIZE = 20;
253
+ var MAX_LIMIT = 200;
254
+ var UNTRACKED_STOCK = 999;
255
+ var paginated = (data, count, page = 1, pageSize = PAGE_SIZE) => ({
256
+ data,
257
+ count,
258
+ pageSize,
259
+ noOfPage: Math.ceil(count / pageSize) || (count ? 1 : 0),
260
+ page
261
+ });
262
+ var emptyPage = (page = 1, pageSize = PAGE_SIZE) => ({
263
+ data: [],
264
+ count: 0,
265
+ pageSize,
266
+ noOfPage: 0,
267
+ page
268
+ });
269
+ var fromListMeta = (data, meta, fallbackPage = 1) => {
270
+ const count = meta?.total ?? data.length;
271
+ const pageSize = meta?.limit || data.length || PAGE_SIZE;
272
+ return {
273
+ data,
274
+ count,
275
+ pageSize,
276
+ noOfPage: meta?.total_pages ?? (Math.ceil(count / pageSize) || (count ? 1 : 0)),
277
+ page: meta?.page ?? fallbackPage
278
+ };
279
+ };
280
+ var absoluteMedia = (url) => url ? BaseService.mediaUrl(url) : null;
281
+ var activeVariants = (product) => (product?.variants || []).filter((v) => v?.active !== false);
282
+ var primaryVariant = (product) => activeVariants(product)[0] || (product?.variants || [])[0];
283
+ var variantStock = (variant) => {
284
+ if (!variant) return 0;
285
+ if (variant.track_inventory === false) return UNTRACKED_STOCK;
286
+ const available = Math.max(0, variant.available ?? 0);
287
+ return available === 0 && variant.continue_selling ? UNTRACKED_STOCK : available;
288
+ };
289
+ var productImages = (product) => {
290
+ const seen = /* @__PURE__ */ new Set();
291
+ const featured = absoluteMedia(product?.image_url);
292
+ if (featured) seen.add(featured);
293
+ for (const variant of product?.variants || []) {
294
+ const url = absoluteMedia(variant?.image?.url);
295
+ if (url) seen.add(url);
296
+ }
297
+ return [...seen];
298
+ };
299
+ var flattenMetadata = (metadata) => {
300
+ if (!metadata || typeof metadata !== "object") return null;
301
+ const out = {};
302
+ const printable = (value) => {
303
+ if (value === null || value === void 0 || value === "") return void 0;
304
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
305
+ if (Array.isArray(value)) {
306
+ const items = value.map(printable).filter(Boolean);
307
+ return items.length ? items.join(", ") : void 0;
308
+ }
309
+ return void 0;
310
+ };
311
+ const walk = (node, namespace = "") => {
312
+ for (const [key, value] of Object.entries(node)) {
313
+ const text = printable(value);
314
+ if (text !== void 0) {
315
+ out[key in out && namespace ? `${namespace} ${key}` : key] = text;
316
+ continue;
317
+ }
318
+ if (value && typeof value === "object" && !Array.isArray(value)) walk(value, key);
319
+ }
320
+ };
321
+ walk(metadata);
322
+ return Object.keys(out).length ? out : null;
323
+ };
324
+ var optionValueId = (product, optionIndex, value) => product?.options?.[optionIndex]?.values?.find((v) => v.value === value)?.id ?? `${optionIndex}-${value}`;
325
+ var mapVariant = (product, variant) => {
326
+ const price = fromMoney(variant.price);
327
+ const compareAt = fromMoney(variant.compare_at_price);
328
+ const image = absoluteMedia(variant.image?.url) || absoluteMedia(product.image_url);
329
+ return {
330
+ id: variant.id,
331
+ // The PDP prints this as the heading whenever it is not 'default'. GoCommerce labels a variant
332
+ // by its options ("M / Black") and leaves that empty for a product that has none — where the
333
+ // product's own title is the right heading, and the SKU would be a code shown to a shopper.
334
+ title: variant.label || (variant.options || []).join(" / ") || product.title || "default",
335
+ productId: product.id,
336
+ sku: variant.sku || null,
337
+ barcode: variant.barcode || null,
338
+ batchNo: null,
339
+ stock: variantStock(variant),
340
+ allowBackorder: Boolean(variant.continue_selling),
341
+ manageInventory: variant.track_inventory !== false,
342
+ hsCode: variant.hs_code || null,
343
+ originCountry: variant.origin_country || null,
344
+ midCode: null,
345
+ material: null,
346
+ weight: variant.weight ?? null,
347
+ length: null,
348
+ height: null,
349
+ width: null,
350
+ price,
351
+ costPerItem: 0,
352
+ mfgDate: null,
353
+ expiryDate: null,
354
+ returnAllowed: false,
355
+ replaceAllowed: false,
356
+ // No compare-at price recorded means nothing is on sale; mrp equals price so the components
357
+ // print one number instead of a struck-through duplicate.
358
+ mrp: compareAt > price ? compareAt : price,
359
+ img: image,
360
+ description: null,
361
+ storeId: null,
362
+ len: null,
363
+ rank: variant.position ?? 0,
364
+ shippingWeight: null,
365
+ shippingHeight: null,
366
+ shippingLen: null,
367
+ shippingWidth: null,
368
+ shippingCost: null,
369
+ metadata: variant.metadata ?? null,
370
+ variantRank: variant.position ?? 0,
371
+ // A variant's `options` are the VALUES, positionally aligned with the product's option list.
372
+ options: (variant.options || []).map((value, index) => ({
373
+ id: optionValueId(product, index, value),
374
+ optionId: product.options?.[index]?.id ?? index,
375
+ value,
376
+ variantId: variant.id
377
+ }))
378
+ };
379
+ };
380
+ var mapProduct = (product) => {
381
+ if (!product) return null;
382
+ const variants = activeVariants(product);
383
+ const priced = variants.length ? variants : product.variants || [];
384
+ const prices = priced.map((v) => fromMoney(v.price)).filter((n) => Number.isFinite(n));
385
+ const price = prices.length ? Math.min(...prices) : 0;
386
+ const compareAtPrices = priced.map((v) => fromMoney(v.compare_at_price)).filter((n) => n > 0);
387
+ const mrp = compareAtPrices.length ? Math.max(...compareAtPrices) : price;
388
+ const images = productImages(product);
389
+ const featured = images[0] || null;
390
+ const first = primaryVariant(product);
391
+ return {
392
+ id: product.id,
393
+ title: product.title,
394
+ slug: product.slug,
395
+ description: product.description || "",
396
+ price,
397
+ mrp,
398
+ featuredImage: featured,
399
+ active: product.status === "active",
400
+ status: product.status === "active" ? "published" : product.status || "draft",
401
+ type: "physical",
402
+ vendorId: product.vendor || "",
403
+ categoryId: product.category?.id ?? null,
404
+ currency: product.currency || null,
405
+ instructions: null,
406
+ hsnCode: first?.hs_code || null,
407
+ // The components read this as a comma-joined list, the way Litekart serves it.
408
+ images: images.join(","),
409
+ thumbnail: featured,
410
+ keywords: (product.tags || []).join(",") || null,
411
+ link: null,
412
+ metaTitle: product.seo_title || null,
413
+ metaDescription: product.seo_description || null,
414
+ subtitle: product.product_type || null,
415
+ popularity: 0,
416
+ rank: 0,
417
+ expiryDate: null,
418
+ weight: first?.weight ?? null,
419
+ mfgDate: null,
420
+ costPerItem: 0,
421
+ sku: first?.sku || null,
422
+ stock: priced.reduce((sum, v) => sum + variantStock(v), 0),
423
+ allowBackorder: Boolean(first?.continue_selling),
424
+ manageInventory: first?.track_inventory !== false,
425
+ shippingWeight: null,
426
+ shippingHeight: null,
427
+ shippingLen: null,
428
+ shippingWidth: null,
429
+ height: null,
430
+ width: null,
431
+ len: null,
432
+ barcode: first?.barcode || null,
433
+ shippingCost: null,
434
+ returnAllowed: false,
435
+ replaceAllowed: false,
436
+ originCountry: first?.origin_country || null,
437
+ weightUnit: first?.weight_unit || "g",
438
+ dimensionUnit: "cm",
439
+ metadata: flattenMetadata(product.metadata),
440
+ collectionId: product.collections?.[0]?.id ?? null,
441
+ tags: product.tags || [],
442
+ vendor: product.vendor || null,
443
+ categories: product.category ? [{ category: { id: product.category.id, name: product.category.title, slug: product.category.slug } }] : [],
444
+ createdAt: product.created_at || null,
445
+ updatedAt: product.updated_at || null,
446
+ options: (product.options || []).map((option) => ({
447
+ id: option.id,
448
+ title: option.name,
449
+ type: "radio",
450
+ values: (option.values || []).map((v) => ({ id: v.id, value: v.value }))
451
+ })),
452
+ variants: priced.map((variant) => mapVariant(product, variant))
453
+ };
454
+ };
455
+ var mapCategory = (category) => {
456
+ if (!category) return null;
457
+ return {
458
+ id: category.id,
459
+ isActive: true,
460
+ isInternal: false,
461
+ isMegamenu: false,
462
+ thumbnail: null,
463
+ path: category.full_name || null,
464
+ level: category.depth ?? 0,
465
+ description: null,
466
+ isFeatured: false,
467
+ keywords: null,
468
+ rank: category.position ?? 0,
469
+ link: null,
470
+ metaDescription: null,
471
+ metaKeywords: null,
472
+ metaTitle: null,
473
+ name: category.title,
474
+ // The storefront's filter composable rebuilds the tree from this field.
475
+ parentCategoryId: category.parent_id ?? null,
476
+ store: null,
477
+ slug: category.slug,
478
+ userId: "",
479
+ activeProducts: 0,
480
+ inactiveProducts: 0,
481
+ createdAt: category.created_at || null,
482
+ updatedAt: category.updated_at || null
483
+ };
484
+ };
485
+ var mapCollection = (collection) => {
486
+ if (!collection) return null;
487
+ const products = collection.products || [];
488
+ return {
489
+ id: collection.id,
490
+ name: collection.title,
491
+ slug: collection.slug,
492
+ description: collection.description || null,
493
+ isActive: true,
494
+ isFeatured: false,
495
+ userId: "",
496
+ thumbnail: productImages(products[0])[0] || null,
497
+ metaTitle: null,
498
+ metaDescription: null,
499
+ rank: collection.position ?? 0,
500
+ collectionvalues: products.map((product) => {
501
+ const mapped = mapProduct(product);
502
+ return {
503
+ products: {
504
+ id: product.id,
505
+ name: product.title,
506
+ title: product.title,
507
+ slug: product.slug,
508
+ thumbnail: mapped?.thumbnail || null,
509
+ price: mapped?.price ?? 0,
510
+ mrp: mapped?.mrp ?? 0
511
+ }
512
+ };
513
+ }),
514
+ productCount: products.length,
515
+ createdAt: collection.created_at || null,
516
+ updatedAt: collection.updated_at || null
517
+ };
518
+ };
519
+ var lineProduct = (index, productId) => productId == null ? void 0 : index?.get(productId);
520
+ var mapCartLine = (line, index) => {
521
+ const product = lineProduct(index, line.product_id);
522
+ const mapped = product ? mapProduct(product) : null;
523
+ const variant = product?.variants?.find((v) => v.id === line.variant_id);
524
+ const price = fromMoney(line.unit_price);
525
+ const compareAt = fromMoney(variant?.compare_at_price);
526
+ const thumbnail = absoluteMedia(variant?.image?.url) || mapped?.thumbnail || null;
527
+ return {
528
+ id: line.id,
529
+ productId: line.product_id,
530
+ variantId: line.variant_id,
531
+ qty: line.quantity,
532
+ subtotal: fromMoney(line.total),
533
+ discount: 0,
534
+ tax: 0,
535
+ shippingCharges: 0,
536
+ total: fromMoney(line.total),
537
+ price,
538
+ mrp: compareAt > price ? compareAt : price,
539
+ title: line.title || mapped?.title || "",
540
+ slug: mapped?.slug || "",
541
+ sku: line.sku || "",
542
+ description: null,
543
+ thumbnail,
544
+ metadata: null,
545
+ vendorId: null,
546
+ weight: null,
547
+ dimensionUnit: "cm",
548
+ height: null,
549
+ width: null,
550
+ len: null,
551
+ shippingWeight: null,
552
+ shippingHeight: null,
553
+ shippingLen: null,
554
+ shippingWidth: null,
555
+ isSelectedForCheckout: true,
556
+ // Surfaced so the cart can warn, instead of letting checkout refuse the whole order later.
557
+ priceChanged: Boolean(line.price_changed),
558
+ inStock: line.in_stock !== false,
559
+ createdAt: null,
560
+ product: {
561
+ id: line.product_id,
562
+ title: line.title || mapped?.title || "",
563
+ thumbnail,
564
+ slug: mapped?.slug || "",
565
+ sku: line.sku || "",
566
+ categories: mapped?.categories || []
567
+ },
568
+ variant: {
569
+ id: line.variant_id,
570
+ price,
571
+ mrp: compareAt > price ? compareAt : price,
572
+ weight: null,
573
+ height: null,
574
+ width: null,
575
+ len: null,
576
+ shippingWeight: null,
577
+ shippingHeight: null,
578
+ shippingLen: null,
579
+ shippingWidth: null,
580
+ sku: line.sku || "",
581
+ title: line.variant_label || variant?.label || "default",
582
+ options: variant && product ? mapVariant(product, variant).options : []
583
+ }
584
+ };
585
+ };
586
+ var mapCart = (cart, details = {}, index, store) => {
587
+ if (!cart) return {};
588
+ const currency = cart.currency || store?.currency?.code || "USD";
589
+ const lines = cart.line_items || [];
590
+ const subtotal = fromMoney(cart.subtotal);
591
+ const shippingAddress = details.shippingAddress || null;
592
+ const billingAddress = details.billingAddress || shippingAddress;
593
+ return {
594
+ id: cart.id,
595
+ email: cart.email || details.email || null,
596
+ phone: details.phone || shippingAddress?.phone || null,
597
+ name: details.name || null,
598
+ // The payment step gates on an id as well as the address itself. There is no address record
599
+ // in GoCommerce to have an id of its own, so the cart it belongs to names it.
600
+ billingAddressId: billingAddress ? `${cart.id}-billing` : null,
601
+ shippingAddressId: shippingAddress ? `${cart.id}-shipping` : null,
602
+ regionId: null,
603
+ userId: null,
604
+ couponCode: null,
605
+ discountAmount: 0,
606
+ couponAppliedDate: null,
607
+ needAddress: !shippingAddress,
608
+ isCodAvailable: true,
609
+ paymentId: null,
610
+ type: null,
611
+ completedAt: cart.status === "converted" ? cart.updated_at || null : null,
612
+ paymentAuthorizedAt: null,
613
+ idempotencyKey: null,
614
+ salesChannelId: null,
615
+ qty: cart.item_count ?? lines.reduce((sum, l) => sum + (l.quantity || 0), 0),
616
+ // GoCommerce prices shipping at checkout, not on the cart, so there is nothing honest to put
617
+ // here yet — and a fabricated number is the one the shopper would check the total against.
618
+ shippingCharges: 0,
619
+ paymentMethod: null,
620
+ shippingMethod: null,
621
+ subtotal,
622
+ codCharges: 0,
623
+ tax: 0,
624
+ total: subtotal,
625
+ savingAmount: 0,
626
+ userAuthToken: null,
627
+ currencyCode: currency,
628
+ currencySymbol: store?.currency?.symbol || "",
629
+ shippingRateId: null,
630
+ currencyDecimalDigits: currencyFractionDigits(currency),
631
+ storeId: store?.id ?? null,
632
+ status: cart.status || "open",
633
+ createdAt: cart.created_at || null,
634
+ updatedAt: cart.updated_at || null,
635
+ deletedAt: null,
636
+ lineItems: lines.map((line) => mapCartLine(line, index)),
637
+ shippingAddress,
638
+ billingAddress
639
+ };
640
+ };
641
+ var splitName = (name) => {
642
+ const parts = (name || "").trim().split(/\s+/).filter(Boolean);
643
+ return { firstName: parts[0] || "", lastName: parts.slice(1).join(" ") };
644
+ };
645
+ var toStorefrontAddress = (address, email, phone) => {
646
+ if (!address) return null;
647
+ const { firstName, lastName } = splitName(address.name);
648
+ return {
649
+ id: null,
650
+ firstName,
651
+ lastName,
652
+ email: email || null,
653
+ phone: address.phone || phone || "",
654
+ address_1: address.line1 || "",
655
+ address_2: address.line2 || "",
656
+ city: address.city || "",
657
+ state: address.state || "",
658
+ zip: address.postal_code || "",
659
+ country: address.country || "",
660
+ countryCode: address.country || "",
661
+ isPrimary: false,
662
+ isResidential: true,
663
+ deliveryInstructions: null
664
+ };
665
+ };
666
+ var toGoCommerceAddress = (address, fallbackPhone) => {
667
+ if (!address) return null;
668
+ const name = [address.firstName, address.lastName].filter(Boolean).join(" ").trim() || address.name || "";
669
+ const line1 = address.address_1 || address.line1 || "";
670
+ const city = address.city || "";
671
+ const postal = address.zip || address.postal_code || "";
672
+ const country = address.countryCode || address.country_code || address.country || "";
673
+ if (!line1 || !city || !postal || !country) return null;
674
+ return {
675
+ name,
676
+ phone: address.phone || fallbackPhone || "",
677
+ line1,
678
+ line2: address.address_2 || address.line2 || "",
679
+ city,
680
+ state: address.state || "",
681
+ postal_code: postal,
682
+ // GoCommerce wants ISO 3166-1 alpha-2; the form may hold a full country name alongside it.
683
+ country: String(country).length === 2 ? String(country).toUpperCase() : String(country)
684
+ };
685
+ };
686
+ var mapOrder = (order, index) => {
687
+ if (!order) return null;
688
+ const shippingAddress = toStorefrontAddress(order.address, order.email, order.phone);
689
+ return {
690
+ id: order.id,
691
+ orderNo: order.number,
692
+ order_no: order.number,
693
+ // The orders list links a detail page by parentOrderNo; the number is the handle here.
694
+ parentOrderNo: order.number,
695
+ status: order.status || "pending",
696
+ paymentStatus: order.payment_status || "pending",
697
+ paymentMethod: order.payment_provider || null,
698
+ createdAt: order.created_at || null,
699
+ updatedAt: order.updated_at || null,
700
+ userEmail: order.email || "",
701
+ phone: order.phone || "",
702
+ subtotal: fromMoney(order.subtotal),
703
+ shippingCharges: fromMoney(order.shipping),
704
+ discount: fromMoney(order.discount),
705
+ total: fromMoney(order.total),
706
+ currencyCode: order.currency,
707
+ currencyDecimalDigits: currencyFractionDigits(order.currency),
708
+ shippingRate: null,
709
+ fulfillments: (order.fulfillments || []).map((f) => ({
710
+ id: f.id,
711
+ status: f.status || "",
712
+ provider: f.provider || "",
713
+ trackingNumber: f.tracking || "",
714
+ labelUrl: f.label_url || "",
715
+ createdAt: f.created_at || null
716
+ })),
717
+ shippingAddress,
718
+ billingAddress: shippingAddress,
719
+ lineItems: (order.line_items || []).map((line) => {
720
+ const product = lineProduct(index, line.product_id);
721
+ const mapped = product ? mapProduct(product) : null;
722
+ const variant = product?.variants?.find((v) => v.id === line.variant_id);
723
+ return {
724
+ id: line.id,
725
+ productId: line.product_id,
726
+ variantId: line.variant_id,
727
+ title: line.title || mapped?.title || "",
728
+ variantTitle: line.variant_label || line.sku || "",
729
+ slug: mapped?.slug || "",
730
+ sku: line.sku || "",
731
+ thumbnail: absoluteMedia(variant?.image?.url) || mapped?.thumbnail || null,
732
+ qty: line.quantity,
733
+ price: fromMoney(line.unit_price),
734
+ // The orders list prints `total`; the confirmation page prints `subtotal`.
735
+ subtotal: fromMoney(line.total),
736
+ total: fromMoney(line.total)
737
+ };
738
+ })
739
+ };
740
+ };
741
+
742
+ // src/services/checkout-details.ts
743
+ var DETAILS_PREFIX = "gocommerce_checkout_";
744
+ var ORDER_TOKENS_KEY = "gocommerce_order_tokens";
745
+ var storage = () => {
746
+ try {
747
+ return typeof window !== "undefined" && window.localStorage ? window.localStorage : void 0;
748
+ } catch {
749
+ return void 0;
750
+ }
751
+ };
752
+ var readJson = (key) => {
753
+ const store = storage();
754
+ if (!store) return void 0;
755
+ try {
756
+ const raw = store.getItem(key);
757
+ return raw ? JSON.parse(raw) : void 0;
758
+ } catch {
759
+ return void 0;
760
+ }
761
+ };
762
+ var writeJson = (key, value) => {
763
+ const store = storage();
764
+ if (!store) return;
765
+ try {
766
+ store.setItem(key, JSON.stringify(value));
767
+ } catch {
768
+ }
769
+ };
770
+ var readCheckoutDetails = (cartId) => {
771
+ if (!cartId) return {};
772
+ return readJson(`${DETAILS_PREFIX}${cartId}`) ?? {};
773
+ };
774
+ var writeCheckoutDetails = (cartId, patch) => {
775
+ if (!cartId) return patch;
776
+ const merged = { ...readCheckoutDetails(cartId), ...patch };
777
+ writeJson(`${DETAILS_PREFIX}${cartId}`, merged);
778
+ return merged;
779
+ };
780
+ var clearCheckoutDetails = (cartId) => {
781
+ const store = storage();
782
+ if (!store || !cartId) return;
783
+ try {
784
+ store.removeItem(`${DETAILS_PREFIX}${cartId}`);
785
+ } catch {
786
+ }
787
+ };
788
+ var rememberOrderToken = (orderNumber, token) => {
789
+ if (!orderNumber || !token) return;
790
+ const tokens = readJson(ORDER_TOKENS_KEY) ?? {};
791
+ tokens[orderNumber] = token;
792
+ writeJson(ORDER_TOKENS_KEY, tokens);
793
+ };
794
+ var readOrderToken = (orderNumber) => {
795
+ if (!orderNumber) return void 0;
796
+ return (readJson(ORDER_TOKENS_KEY) ?? {})[orderNumber];
797
+ };
798
+ var readOrderNumbers = () => Object.keys(readJson(ORDER_TOKENS_KEY) ?? {});
799
+
800
+ // src/services/address-book.ts
801
+ var ADDRESSES_KEY = "gocommerce_addresses";
802
+ var storage2 = () => {
803
+ try {
804
+ return typeof window !== "undefined" && window.localStorage ? window.localStorage : void 0;
805
+ } catch {
806
+ return void 0;
807
+ }
808
+ };
809
+ var readAll = () => {
810
+ const store = storage2();
811
+ if (!store) return [];
812
+ try {
813
+ const raw = store.getItem(ADDRESSES_KEY);
814
+ const parsed = raw ? JSON.parse(raw) : [];
815
+ return Array.isArray(parsed) ? parsed : [];
816
+ } catch {
817
+ return [];
818
+ }
819
+ };
820
+ var writeAll = (addresses) => {
821
+ const store = storage2();
822
+ if (!store) return;
823
+ try {
824
+ store.setItem(ADDRESSES_KEY, JSON.stringify(addresses));
825
+ } catch {
826
+ }
827
+ };
828
+ var isSavedId = (id) => typeof id === "string" && id.length > 0 && id !== "new";
829
+ var newId = () => `addr_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;
830
+ var listAddresses = () => readAll();
831
+ var findAddress = (id) => readAll().find((a) => a.id === id);
832
+ var putAddress = (address) => {
833
+ const addresses = readAll();
834
+ const id = isSavedId(address.id) ? address.id : newId();
835
+ const saved = { ...address, id };
836
+ const index = addresses.findIndex((a) => a.id === id);
837
+ if (index >= 0) addresses[index] = saved;
838
+ else addresses.push(saved);
839
+ writeAll(addresses);
840
+ return saved;
841
+ };
842
+ var patchAddress = (id, patch) => {
843
+ const existing = findAddress(id);
844
+ if (!existing) return void 0;
845
+ return putAddress({ ...existing, ...patch, id });
846
+ };
847
+ var removeAddress = (id) => {
848
+ writeAll(readAll().filter((a) => a.id !== id));
849
+ };
850
+ var SESSION_COOKIES = ["connect.sid", "me"];
851
+ var clearPhantomSession = () => {
852
+ if (typeof document === "undefined") return;
853
+ for (const name of SESSION_COOKIES) {
854
+ document.cookie = `${name}=; Max-Age=0; path=/`;
855
+ document.cookie = `${name}=; Max-Age=0`;
856
+ }
857
+ };
858
+ var hasPhantomSession = () => typeof document !== "undefined" && SESSION_COOKIES.some((name) => new RegExp(`(^|;\\s*)${name.replace(".", "\\.")}=`).test(document.cookie));
859
+
860
+ // src/services/catalog.ts
861
+ var productCache = /* @__PURE__ */ new Map();
862
+ var categorySlugById = /* @__PURE__ */ new Map();
863
+ var remember = (products) => {
864
+ for (const product of products) {
865
+ if (product?.id != null) productCache.set(product.id, product);
866
+ if (product?.category?.id != null && product.category.slug) categorySlugById.set(product.category.id, product.category.slug);
867
+ }
868
+ return products;
869
+ };
870
+ var rememberCategories = (categories) => {
871
+ for (const category of categories) {
872
+ if (category?.id != null && category.slug) categorySlugById.set(category.id, category.slug);
873
+ if (category?.children?.length) rememberCategories(category.children);
874
+ }
875
+ return categories;
876
+ };
877
+ var query = (params) => {
878
+ const search = new URLSearchParams();
879
+ for (const [key, value] of Object.entries(params)) {
880
+ if (value === void 0 || value === "") continue;
881
+ search.set(key, String(value));
882
+ }
883
+ const qs = search.toString();
884
+ return qs ? `?${qs}` : "";
885
+ };
886
+ var ProductService = class _ProductService extends BaseService {
887
+ static instance;
888
+ static getInstance() {
889
+ if (!_ProductService.instance) _ProductService.instance = new _ProductService();
890
+ return _ProductService.instance;
891
+ }
892
+ /** One window of the catalogue, raw. Everything else in this file is built on it. */
893
+ async fetchProducts({ q, page = 1, limit = PAGE_SIZE } = {}) {
894
+ const res = await this.api("GET", `/api/products${query({ q, page, limit })}`);
895
+ return { products: remember(res.data || []), meta: res.meta };
896
+ }
897
+ /** A product by its numeric id — how a cart line names what is in it. */
898
+ async fetchProductById(id) {
899
+ const cached = productCache.get(id);
900
+ if (cached) return cached;
901
+ try {
902
+ const res = await this.api("GET", `/api/products/${id}`);
903
+ if (res.data) remember([res.data]);
904
+ return res.data ?? null;
905
+ } catch {
906
+ return null;
907
+ }
908
+ }
909
+ async fetchProductBySlug(slug) {
910
+ const res = await this.api("GET", `/api/products/slug/${encodeURIComponent(slug)}`);
911
+ if (res.data) remember([res.data]);
912
+ return res.data ?? null;
913
+ }
914
+ async list({ page = 1, search = "", sort = "" } = {}) {
915
+ if (sortNeedsWholeCatalogue(sort)) {
916
+ const { products: products2, meta: meta2 } = await this.fetchProducts({ q: search, page: 1, limit: MAX_LIMIT });
917
+ const sorted = sortProducts(products2, sort);
918
+ const start = (page - 1) * PAGE_SIZE;
919
+ return paginated(sorted.slice(start, start + PAGE_SIZE).map(mapProduct), meta2?.total ?? sorted.length, page);
920
+ }
921
+ const { products, meta } = await this.fetchProducts({ q: search, page, limit: PAGE_SIZE });
922
+ return fromListMeta(products.map(mapProduct), meta, page);
923
+ }
924
+ // GoCommerce records no "featured" or "trending" flag, and inventing one would rank the catalogue
925
+ // by nothing. Newest first is the honest reading of both, and it is what the homepage asks for.
926
+ async listFeaturedProducts({ page = 1, sort = "-createdAt" } = {}) {
927
+ return this.list({ page, sort });
928
+ }
929
+ async listTrendingProducts({ page = 1, search = "", sort = "-createdAt" } = {}) {
930
+ return this.list({ page, search, sort });
931
+ }
932
+ /** The "you may also like" rail: the rest of the product's own category. */
933
+ async listRelatedProducts({ page = 1, categoryId = "" } = {}) {
934
+ const slug = await resolveCategorySlug(categoryId);
935
+ if (!slug) return emptyPage(page);
936
+ const { products, meta } = await categoryService.fetchCategoryPage(slug, { limit: MAX_LIMIT });
937
+ const start = (page - 1) * PAGE_SIZE;
938
+ return paginated(products.slice(start, start + PAGE_SIZE).map(mapProduct), meta?.total ?? products.length, page);
939
+ }
940
+ async getOne(slug) {
941
+ return mapProduct(await this.fetchProductBySlug(slug));
942
+ }
943
+ /** A product's variants, for a PDP that wants them without re-reading the product. */
944
+ async listVariants(productId) {
945
+ const res = await this.api("GET", `/api/variants${query({ product_id: productId, limit: MAX_LIMIT })}`);
946
+ return fromListMeta(res.data || [], res.meta);
947
+ }
948
+ // GoCommerce ships no review API, and a silent success would tell the shopper their review was
949
+ // saved when nothing left the browser.
950
+ async addReview(_params) {
951
+ throw new Error("Product reviews are not available on this store.");
952
+ }
953
+ async fetchReels() {
954
+ return { data: [], count: 0 };
955
+ }
956
+ };
957
+ var productService = ProductService.getInstance();
958
+ var CategoryService = class _CategoryService extends BaseService {
959
+ static instance;
960
+ static getInstance() {
961
+ if (!_CategoryService.instance) _CategoryService.instance = new _CategoryService();
962
+ return _CategoryService.instance;
963
+ }
964
+ /**
965
+ * `use-category-filters` in @misiki/kitcommerce-core fetches the tree by raw Litekart REST path
966
+ * rather than a typed method. On a GoCommerce store that url resolves against the storefront's
967
+ * own origin, so route that one path to the real tree; `fetchAllCategories` already returns the
968
+ * `{ data }` shape it reads.
969
+ */
970
+ async get(url) {
971
+ if (typeof url === "string" && url.startsWith("/api/categories/all")) {
972
+ return await this.fetchAllCategories();
973
+ }
974
+ return super.get(url);
975
+ }
976
+ /**
977
+ * The taxonomy, flattened.
978
+ *
979
+ * `GET /api/categories` answers with one fixed window and ignores `limit`/`page`, so this is
980
+ * every category the storefront can see — roots plus what fits beneath them. A store that has
981
+ * imported a full retail taxonomy has far more nodes than that; the ones a shopper reaches are
982
+ * still reachable, because a listing page looks its category up by slug directly.
983
+ */
984
+ async fetchAllCategories() {
985
+ const res = await this.api("GET", `/api/categories${query({ flat: "true" })}`);
986
+ const categories = rememberCategories(res.data || []);
987
+ return paginated(categories.map(mapCategory), categories.length, 1, categories.length || PAGE_SIZE);
988
+ }
989
+ /** A category and the active products filed under it or anything beneath it. */
990
+ async fetchCategoryPage(slug, { limit = MAX_LIMIT, offset = 0 } = {}) {
991
+ const res = await this.api("GET", `/api/categories/${encodeURIComponent(slug)}${query({ limit, offset })}`);
992
+ const category = res.data;
993
+ if (category) rememberCategories([category]);
994
+ const products = remember(category?.products || []);
995
+ return { category, products, meta: res.meta };
996
+ }
997
+ async fetchCategory(slugOrId) {
998
+ const { category } = await this.fetchCategoryPage(String(slugOrId), { limit: 1 });
999
+ return mapCategory(category);
1000
+ }
1001
+ async fetchFooterCategories({ page = 1, q = "" } = {}) {
1002
+ const all = await this.fetchAllCategories();
1003
+ const filtered = q ? all.data.filter((c) => c?.name?.toLowerCase().includes(q.toLowerCase())) : all.data;
1004
+ const start = (page - 1) * PAGE_SIZE;
1005
+ return paginated(filtered.slice(start, start + PAGE_SIZE), filtered.length, page);
1006
+ }
1007
+ /** The homepage grid: the roots of the taxonomy, which is what a shopper navigates from. */
1008
+ async fetchFeaturedCategories({ limit = 18 } = {}) {
1009
+ const all = await this.fetchAllCategories();
1010
+ const roots = all.data.filter((c) => c && c.parentCategoryId == null);
1011
+ const picked = (roots.length ? roots : all.data).slice(0, limit);
1012
+ return paginated(picked, picked.length, 1, limit);
1013
+ }
1014
+ async fetchAllProductsOfCategories(slugOrId) {
1015
+ const slug = await resolveCategorySlug(slugOrId) || String(slugOrId);
1016
+ const { products, meta } = await this.fetchCategoryPage(slug);
1017
+ return fromListMeta(products.map(mapProduct), meta);
1018
+ }
1019
+ async getMegamenu() {
1020
+ return this.fetchFeaturedCategories({ limit: 50 });
1021
+ }
1022
+ };
1023
+ var categoryService = CategoryService.getInstance();
1024
+ var resolveCategorySlug = async (categoryId) => {
1025
+ if (categoryId === void 0 || categoryId === null || categoryId === "") return void 0;
1026
+ const asNumber = Number(categoryId);
1027
+ if (!Number.isFinite(asNumber)) return String(categoryId);
1028
+ const known = categorySlugById.get(asNumber);
1029
+ if (known) return known;
1030
+ await categoryService.fetchAllCategories();
1031
+ return categorySlugById.get(asNumber);
1032
+ };
1033
+ var CollectionService = class _CollectionService extends BaseService {
1034
+ static instance;
1035
+ static getInstance() {
1036
+ if (!_CollectionService.instance) _CollectionService.instance = new _CollectionService();
1037
+ return _CollectionService.instance;
1038
+ }
1039
+ async list({ page = 1, q = "" } = {}) {
1040
+ const res = await this.api("GET", `/api/collections${query({ page, limit: PAGE_SIZE })}`);
1041
+ const items = res.data || [];
1042
+ const filtered = q ? items.filter((c) => c.title?.toLowerCase().includes(q.toLowerCase())) : items;
1043
+ return fromListMeta(
1044
+ filtered.map((c) => mapCollection(c)),
1045
+ res.meta,
1046
+ page
1047
+ );
1048
+ }
1049
+ /**
1050
+ * A collection with its products. GoCommerce is keyed on slug; the storefront sometimes has only
1051
+ * an id, and there is no id lookup, so that case reads the list and matches there.
1052
+ */
1053
+ async getOne(idOrSlug) {
1054
+ const looksLikeId = /^[0-9]+$/.test(String(idOrSlug));
1055
+ let slug = String(idOrSlug);
1056
+ if (looksLikeId) {
1057
+ const res2 = await this.api("GET", `/api/collections${query({ limit: MAX_LIMIT })}`);
1058
+ const match = (res2.data || []).find((c) => String(c.id) === String(idOrSlug));
1059
+ if (!match) throw new Error(`Collection ${idOrSlug} not found`);
1060
+ slug = match.slug;
1061
+ }
1062
+ const res = await this.api("GET", `/api/collections/${encodeURIComponent(slug)}${query({ limit: MAX_LIMIT })}`);
1063
+ if (!res.data) throw new Error(`Collection ${idOrSlug} not found`);
1064
+ remember(res.data.products || []);
1065
+ return mapCollection(res.data);
1066
+ }
1067
+ // GoCommerce records no ratings, so there is no "all ratings" collection to serve.
1068
+ async getAllRatings() {
1069
+ return {
1070
+ id: null,
1071
+ name: "All Ratings",
1072
+ slug: "all-ratings",
1073
+ description: null,
1074
+ isActive: true,
1075
+ isFeatured: false,
1076
+ userId: "",
1077
+ productCount: 0,
1078
+ thumbnail: null,
1079
+ metaTitle: null,
1080
+ metaDescription: null,
1081
+ collectionvalues: [],
1082
+ createdAt: null,
1083
+ updatedAt: null
1084
+ };
1085
+ }
1086
+ };
1087
+ var collectionService = CollectionService.getInstance();
1088
+ var sortNeedsWholeCatalogue = (sort) => {
1089
+ if (!sort) return false;
1090
+ const key = sort.replace(/^-/, "").split(":")[0];
1091
+ return key === "price" || key === "updatedAt" || key === "createdAt";
1092
+ };
1093
+ var sortProducts = (products, sort) => {
1094
+ if (!sortNeedsWholeCatalogue(sort)) return products;
1095
+ const leadingMinus = sort.startsWith("-");
1096
+ const [rawKey, rawDirection] = sort.replace(/^-/, "").split(":");
1097
+ const descending = leadingMinus || rawDirection === "desc";
1098
+ const value = (product) => {
1099
+ if (rawKey === "price") {
1100
+ const prices = (product.variants || []).filter((v) => v.active !== false).map((v) => v.price?.amount_minor ?? 0);
1101
+ return prices.length ? Math.min(...prices) : 0;
1102
+ }
1103
+ const stamp = rawKey === "createdAt" ? product.created_at : product.updated_at;
1104
+ return stamp ? Date.parse(stamp) : 0;
1105
+ };
1106
+ return [...products].sort((a, b) => descending ? value(b) - value(a) : value(a) - value(b));
1107
+ };
1108
+ var productPrice = (product) => {
1109
+ const prices = (product.variants || []).filter((v) => v.active !== false).map((v) => v.price?.amount_minor ?? 0);
1110
+ return prices.length ? Math.min(...prices) : 0;
1111
+ };
1112
+ var countBy = (items, key) => {
1113
+ const counts = /* @__PURE__ */ new Map();
1114
+ for (const item of items) {
1115
+ const entry = key(item);
1116
+ if (!entry?.name) continue;
1117
+ const id = entry.slug || entry.name;
1118
+ const existing = counts.get(id);
1119
+ if (existing) existing.count += 1;
1120
+ else counts.set(id, { ...entry, count: 1 });
1121
+ }
1122
+ return [...counts.values()].sort((a, b) => b.count - a.count);
1123
+ };
1124
+ var SearchService = class _SearchService extends BaseService {
1125
+ static instance;
1126
+ static getInstance() {
1127
+ if (!_SearchService.instance) _SearchService.instance = new _SearchService();
1128
+ return _SearchService.instance;
1129
+ }
1130
+ emptyResult() {
1131
+ return {
1132
+ data: [],
1133
+ count: 0,
1134
+ totalPages: 0,
1135
+ categoryHierarchy: [],
1136
+ facets: { priceStat: { min: void 0, max: void 0 }, categories: [], tags: [], allFilters: {} }
1137
+ };
1138
+ }
1139
+ /**
1140
+ * The listing page: `/products`, `/[slug]` for a category, and every filter the sidebar sets.
1141
+ *
1142
+ * Term and category are pushed to the API. Tags, the price range and the sort control are
1143
+ * applied here, because GoCommerce has no parameter for them — so when any of those is active
1144
+ * the request asks for the whole window rather than one page, and the result is paged locally.
1145
+ */
1146
+ async searchWithUrl(url, slug) {
1147
+ try {
1148
+ const params = new URLSearchParams(url.search);
1149
+ const term = params.get("search") || params.get("q") || "";
1150
+ const page = Math.max(1, Number(params.get("page") || 1) || 1);
1151
+ const sort = params.get("sort") || "";
1152
+ const tags = (params.get("tags") || "").split(",").map((t) => decodeURIComponent(t).trim().toLowerCase()).filter(Boolean);
1153
+ const priceFrom = params.get("priceFrom") ? Number(params.get("priceFrom")) : void 0;
1154
+ const priceTo = params.get("priceTo") ? Number(params.get("priceTo")) : void 0;
1155
+ const categorySlug = slug && slug !== "products" ? slug : params.get("categories") || void 0;
1156
+ const localFilters = tags.length > 0 || priceFrom !== void 0 || priceTo !== void 0;
1157
+ const needsWholeWindow = localFilters || sortNeedsWholeCatalogue(sort) || Boolean(categorySlug);
1158
+ let products = [];
1159
+ let meta;
1160
+ let category;
1161
+ if (categorySlug) {
1162
+ const res = await categoryService.fetchCategoryPage(categorySlug, { limit: MAX_LIMIT });
1163
+ products = res.products;
1164
+ meta = res.meta;
1165
+ category = res.category;
1166
+ if (term) {
1167
+ const needle = term.toLowerCase();
1168
+ products = products.filter((p) => `${p.title} ${p.description || ""}`.toLowerCase().includes(needle));
1169
+ }
1170
+ } else {
1171
+ const res = await productService.fetchProducts({
1172
+ q: term,
1173
+ page: needsWholeWindow ? 1 : page,
1174
+ limit: needsWholeWindow ? MAX_LIMIT : PAGE_SIZE
1175
+ });
1176
+ products = res.products;
1177
+ meta = res.meta;
1178
+ }
1179
+ const facetSource = products;
1180
+ const priceMinor = facetSource.map(productPrice).filter((n) => Number.isFinite(n));
1181
+ const currency = facetSource[0]?.variants?.[0]?.price?.currency || facetSource[0]?.currency;
1182
+ const digits = currency ? 10 ** currencyFractionDigits(currency) : 100;
1183
+ let filtered = products;
1184
+ if (tags.length) {
1185
+ filtered = filtered.filter((p) => (p.tags || []).some((tag) => tags.includes(tag.toLowerCase())));
1186
+ }
1187
+ if (priceFrom !== void 0) filtered = filtered.filter((p) => productPrice(p) / digits >= priceFrom);
1188
+ if (priceTo !== void 0) filtered = filtered.filter((p) => productPrice(p) / digits <= priceTo);
1189
+ filtered = sortProducts(filtered, sort);
1190
+ const paged = needsWholeWindow ? filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE) : filtered;
1191
+ const count = needsWholeWindow ? filtered.length : meta?.total ?? filtered.length;
1192
+ return {
1193
+ data: paged.map(mapProduct),
1194
+ count,
1195
+ totalPages: Math.ceil(count / PAGE_SIZE) || (count ? 1 : 0),
1196
+ // Only the category itself: its ancestors are named in `full_name` but their slugs are
1197
+ // not served to a storefront, and a breadcrumb link with no destination is worse than
1198
+ // no crumb.
1199
+ categoryHierarchy: category ? [{ name: category.title, slug: category.slug }] : [],
1200
+ facets: {
1201
+ priceStat: priceMinor.length ? { min: Math.min(...priceMinor) / digits, max: Math.max(...priceMinor) / digits } : { min: void 0, max: void 0 },
1202
+ categories: countBy(facetSource, (p) => p.category ? { name: p.category.title, slug: p.category.slug } : void 0),
1203
+ tags: countBy(
1204
+ facetSource.flatMap((p) => (p.tags || []).map((tag) => ({ tag }))),
1205
+ ({ tag }) => ({ name: tag, slug: tag })
1206
+ ),
1207
+ allFilters: {}
1208
+ }
1209
+ };
1210
+ } catch (error) {
1211
+ if (error?.code !== "not_found") console.error("[gocommerce] search failed", error);
1212
+ return this.emptyResult();
1213
+ }
1214
+ }
1215
+ async searchWithQuery(queryString) {
1216
+ const url = new URL("https://storefront.local/products");
1217
+ if (queryString) url.searchParams.set("search", queryString);
1218
+ return this.searchWithUrl(url);
1219
+ }
1220
+ };
1221
+ var searchService = SearchService.getInstance();
1222
+ var MeilisearchService = class _MeilisearchService extends BaseService {
1223
+ static instance;
1224
+ static getInstance() {
1225
+ if (!_MeilisearchService.instance) _MeilisearchService.instance = new _MeilisearchService();
1226
+ return _MeilisearchService.instance;
1227
+ }
1228
+ async search(params = {}) {
1229
+ const page = Math.max(1, Number(params.page || 1) || 1);
1230
+ const { products, meta } = await productService.fetchProducts({
1231
+ q: params.q || "",
1232
+ page,
1233
+ limit: params.limit || PAGE_SIZE
1234
+ });
1235
+ const hits = products.map(mapProduct);
1236
+ return {
1237
+ hits,
1238
+ data: hits,
1239
+ estimatedTotalHits: meta?.total ?? hits.length,
1240
+ count: meta?.total ?? hits.length,
1241
+ page,
1242
+ totalPages: meta?.total_pages ?? 1,
1243
+ hitsPerPage: meta?.limit ?? PAGE_SIZE,
1244
+ facetDistribution: {},
1245
+ processingTimeMs: 0,
1246
+ query: params.q || ""
1247
+ };
1248
+ }
1249
+ async searchAutoComplete(params = {}) {
1250
+ const { products } = await productService.fetchProducts({ q: params.q || "", page: 1, limit: params.limit || 8 });
1251
+ const hits = products.map((product) => ({
1252
+ id: product.id,
1253
+ name: product.title,
1254
+ title: product.title,
1255
+ slug: product.slug,
1256
+ thumbnail: mapProduct(product)?.thumbnail || null
1257
+ }));
1258
+ return { hits, data: hits, count: hits.length, query: params.q || "" };
1259
+ }
1260
+ };
1261
+ var meilisearchService = MeilisearchService.getInstance();
1262
+ var AutocompleteService = class _AutocompleteService extends BaseService {
1263
+ static instance;
1264
+ static getInstance() {
1265
+ if (!_AutocompleteService.instance) _AutocompleteService.instance = new _AutocompleteService();
1266
+ return _AutocompleteService.instance;
1267
+ }
1268
+ async list({ page = 1, q = "" } = {}) {
1269
+ const { products, meta } = await productService.fetchProducts({ q, page, limit: 10 });
1270
+ return fromListMeta(
1271
+ products.map((product) => ({ id: product.id, name: product.title, slug: product.slug })),
1272
+ meta,
1273
+ page
1274
+ );
1275
+ }
1276
+ };
1277
+ var autocompleteService = AutocompleteService.getInstance();
1278
+ var resolveProductIndex = async (ids, fetchFn) => {
1279
+ const wanted = [...new Set(ids.filter((id) => typeof id === "number"))];
1280
+ const service = fetchFn ? new ProductService(fetchFn) : productService;
1281
+ await Promise.all(wanted.filter((id) => !productCache.has(id)).map((id) => service.fetchProductById(id)));
1282
+ const index = /* @__PURE__ */ new Map();
1283
+ for (const id of wanted) {
1284
+ const product = productCache.get(id);
1285
+ if (product) index.set(id, product);
1286
+ }
1287
+ return index;
1288
+ };
1289
+
1290
+ // src/services/cart.ts
1291
+ var REMOVE_LINE = -9999999;
1292
+ var CART_ID_KEY = "cart_id";
1293
+ var readStoredCartId = () => {
1294
+ try {
1295
+ return typeof window !== "undefined" ? window.localStorage?.getItem(CART_ID_KEY) || null : null;
1296
+ } catch {
1297
+ return null;
1298
+ }
1299
+ };
1300
+ var storeCartId = (cartId) => {
1301
+ if (!cartId) return;
1302
+ try {
1303
+ window?.localStorage?.setItem(CART_ID_KEY, cartId);
1304
+ } catch {
1305
+ }
1306
+ };
1307
+ var forgetCartId = () => {
1308
+ try {
1309
+ window?.localStorage?.removeItem(CART_ID_KEY);
1310
+ } catch {
1311
+ }
1312
+ };
1313
+ var usableId = (cartId) => cartId && cartId !== "undefined" && cartId !== "null" ? cartId : null;
1314
+ var CartService = class _CartService extends BaseService {
1315
+ static instance;
1316
+ static getInstance() {
1317
+ if (!_CartService.instance) _CartService.instance = new _CartService();
1318
+ return _CartService.instance;
1319
+ }
1320
+ /** Turns a GoCommerce cart into the shape the storefront's cart, drawer and checkout read. */
1321
+ async present(cart) {
1322
+ if (!cart) return {};
1323
+ storeCartId(cart.id);
1324
+ const [index, store] = await Promise.all([
1325
+ // Cart lines name a product by id only, so slug and image are filled in from the catalogue.
1326
+ resolveProductIndex(
1327
+ (cart.line_items || []).map((line) => line.product_id),
1328
+ this.getFetch()
1329
+ ),
1330
+ readStaticStore()
1331
+ ]);
1332
+ return mapCart(cart, readCheckoutDetails(cart.id), index, store);
1333
+ }
1334
+ /** Opens a cart. The email is optional here and is the only field `POST /api/carts` accepts. */
1335
+ async openCart(email) {
1336
+ const res = await this.api("POST", "/api/carts", email ? { email } : {});
1337
+ storeCartId(res.data?.id);
1338
+ return res.data;
1339
+ }
1340
+ async readCart(cartId) {
1341
+ let cart = null;
1342
+ try {
1343
+ const res = await this.api("GET", `/api/carts/${encodeURIComponent(cartId)}`);
1344
+ cart = res.data ?? null;
1345
+ } catch {
1346
+ return null;
1347
+ }
1348
+ if (cart && cart.status && cart.status !== "open") {
1349
+ forgetCartId();
1350
+ return null;
1351
+ }
1352
+ return cart;
1353
+ }
1354
+ /**
1355
+ * Is the cart behind this token gone for good — expired, unknown, or already checked out?
1356
+ *
1357
+ * Asked by re-reading the cart rather than by matching the failure's wording, because the API
1358
+ * reports several unrelated things the same way: a dead cart and an unknown payment code are both
1359
+ * `404 not_found`. The status is the fact; the message is prose.
1360
+ */
1361
+ async cartIsSpent(cartId) {
1362
+ try {
1363
+ const res = await this.api("GET", `/api/carts/${encodeURIComponent(cartId)}`);
1364
+ return !res.data || Boolean(res.data.status) && res.data.status !== "open";
1365
+ } catch {
1366
+ return true;
1367
+ }
1368
+ }
1369
+ /** One line-item write against a cart that exists. Returns the cart the write left behind. */
1370
+ async writeLine(cartId, { variantId, qty, lineId, removing }) {
1371
+ const cart = encodeURIComponent(cartId);
1372
+ if (lineId) {
1373
+ const line = encodeURIComponent(String(lineId));
1374
+ const res2 = removing ? await this.api("DELETE", `/api/carts/${cart}/line-items/${line}`) : await this.api("PATCH", `/api/carts/${cart}/line-items/${line}`, { quantity: qty });
1375
+ return res2.data ?? null;
1376
+ }
1377
+ if (removing) return this.readCart(cartId);
1378
+ const res = await this.api("POST", `/api/carts/${cart}/line-items`, {
1379
+ variant_id: Number(variantId),
1380
+ quantity: qty
1381
+ });
1382
+ return res.data ?? null;
1383
+ }
1384
+ async getCartByCartId(cartId) {
1385
+ const id = usableId(cartId) || readStoredCartId();
1386
+ if (!id) return {};
1387
+ return this.present(await this.readCart(id));
1388
+ }
1389
+ async fetchCartData() {
1390
+ const id = readStoredCartId();
1391
+ if (!id) return {};
1392
+ return this.present(await this.readCart(id));
1393
+ }
1394
+ async refereshCart() {
1395
+ return this.fetchCartData();
1396
+ }
1397
+ /**
1398
+ * Add, change or remove a line.
1399
+ *
1400
+ * `qty` is the line's new quantity, not a delta — which is how the shared cart store calls it
1401
+ * (its stepper sends `qty ± 1`), and what `PATCH .../line-items/{lineId}` takes. A quantity of
1402
+ * zero removes the line, and so does the store's `REMOVE_LINE` sentinel.
1403
+ *
1404
+ * A cart token in this browser can be dead — expired, wiped server-side, or already checked out —
1405
+ * and the API then answers "cart not found", which is what the shopper was shown when they
1406
+ * clicked Add to bag. That is a token this storefront is holding, not anything they did, so a
1407
+ * dead one is dropped and the item is added to a fresh cart instead of reporting it.
1408
+ */
1409
+ async addToCart({
1410
+ variantId,
1411
+ qty,
1412
+ cartId,
1413
+ lineId
1414
+ }) {
1415
+ const removing = qty === REMOVE_LINE || qty <= 0;
1416
+ if (!removing && !variantId) throw new Error("A variant is required to add an item to the cart.");
1417
+ let id = usableId(cartId) || readStoredCartId();
1418
+ if (id) {
1419
+ let cart = null;
1420
+ let spent = false;
1421
+ try {
1422
+ cart = await this.writeLine(id, { variantId, qty, lineId, removing });
1423
+ } catch (error) {
1424
+ if (!await this.cartIsSpent(id)) throw error;
1425
+ spent = true;
1426
+ }
1427
+ if (!spent) return this.present(cart);
1428
+ forgetCartId();
1429
+ id = null;
1430
+ }
1431
+ if (removing) return {};
1432
+ const opened = await this.openCart(readCheckoutDetails(null).email);
1433
+ if (!opened?.id) throw new Error("Could not open a cart on this store.");
1434
+ return this.present(await this.writeLine(opened.id, { variantId, qty, lineId: null, removing: false }));
1435
+ }
1436
+ async updateCart({
1437
+ qty,
1438
+ cartId,
1439
+ lineId = null,
1440
+ productId,
1441
+ variantId
1442
+ }) {
1443
+ return this.addToCart({ productId, variantId, qty, cartId, lineId });
1444
+ }
1445
+ async removeCart({ cartId, lineId = null }) {
1446
+ return this.addToCart({ qty: REMOVE_LINE, cartId, lineId });
1447
+ }
1448
+ /**
1449
+ * The address step's write.
1450
+ *
1451
+ * GoCommerce has no endpoint that changes a cart after it is opened, and no address on it: these
1452
+ * fields are arguments to checkout. They are kept against this cart id in the browser and sent
1453
+ * when the order is placed — see checkout-details.ts. The email additionally seeds a cart that
1454
+ * has not been opened yet, which is the one moment GoCommerce does accept it.
1455
+ */
1456
+ async updateCart2({
1457
+ cartId,
1458
+ email,
1459
+ phone,
1460
+ billingAddress,
1461
+ shippingAddress,
1462
+ isBillingAddressSameAsShipping
1463
+ }) {
1464
+ let id = usableId(cartId) || readStoredCartId();
1465
+ let cart = id ? await this.readCart(id) : null;
1466
+ if (!cart) {
1467
+ cart = await this.openCart(email);
1468
+ id = cart?.id ?? null;
1469
+ }
1470
+ if (!id) throw new Error("Could not open a cart on this store.");
1471
+ const name = [shippingAddress?.firstName, shippingAddress?.lastName].filter(Boolean).join(" ").trim() || [billingAddress?.firstName, billingAddress?.lastName].filter(Boolean).join(" ").trim() || void 0;
1472
+ writeCheckoutDetails(id, {
1473
+ ...email ? { email } : {},
1474
+ ...phone || shippingAddress?.phone ? { phone: phone || shippingAddress?.phone } : {},
1475
+ ...name ? { name } : {},
1476
+ ...shippingAddress ? { shippingAddress } : {},
1477
+ ...billingAddress && !isBillingAddressSameAsShipping ? { billingAddress } : shippingAddress ? { billingAddress: shippingAddress } : {}
1478
+ });
1479
+ return this.present(cart);
1480
+ }
1481
+ /** Nothing to reserve ahead of payment: `POST /api/checkout/{code}` is one transaction. */
1482
+ async completeCart(cartId) {
1483
+ return this.getCartByCartId(cartId);
1484
+ }
1485
+ /** Remembered so the payment step can re-select it; GoCommerce takes the code at checkout. */
1486
+ async updateCartPaymentMethod({ cartId, paymentMethod }) {
1487
+ const id = usableId(cartId) || readStoredCartId();
1488
+ if (!id) return {};
1489
+ const cart = await this.readCart(id);
1490
+ const presented = await this.present(cart);
1491
+ if (presented && paymentMethod) presented.paymentMethod = paymentMethod;
1492
+ return presented;
1493
+ }
1494
+ // GoCommerce has no discount engine, so there is no code to accept. Saying so beats a silent
1495
+ // no-op that leaves the shopper waiting for a discount that is never coming.
1496
+ async applyCoupon(_params) {
1497
+ throw new Error("Discount codes are not available on this store.");
1498
+ }
1499
+ async removeCoupon() {
1500
+ return this.fetchCartData();
1501
+ }
1502
+ /** Shipping is priced by the store at checkout; there are no rates for a shopper to choose. */
1503
+ async updateShippingRate({ cartId }) {
1504
+ return this.getCartByCartId(cartId || "");
1505
+ }
1506
+ };
1507
+ var cartService = CartService.getInstance();
1508
+
1509
+ // src/services/checkout.ts
1510
+ var CART_ID_KEY2 = "cart_id";
1511
+ var readStoredCartId2 = () => {
1512
+ try {
1513
+ return typeof window !== "undefined" ? window.localStorage?.getItem(CART_ID_KEY2) || null : null;
1514
+ } catch {
1515
+ return null;
1516
+ }
1517
+ };
1518
+ var METHOD_NAMES = {
1519
+ cod: "Cash on delivery",
1520
+ stripe: "Card",
1521
+ razorpay: "Razorpay",
1522
+ cashfree: "Cashfree",
1523
+ paypal: "PayPal",
1524
+ phonepe: "PhonePe",
1525
+ manual: "Manual payment"
1526
+ };
1527
+ var installedCodes = [];
1528
+ var goCommerceCode = (storefrontCode) => {
1529
+ const match = installedCodes.find((code) => code.toLowerCase() === storefrontCode.toLowerCase());
1530
+ return match || storefrontCode.toLowerCase();
1531
+ };
1532
+ var titleCase = (code) => code.charAt(0).toUpperCase() + code.slice(1);
1533
+ var PaymentMethodService = class _PaymentMethodService extends BaseService {
1534
+ static instance;
1535
+ static getInstance() {
1536
+ if (!_PaymentMethodService.instance) _PaymentMethodService.instance = new _PaymentMethodService();
1537
+ return _PaymentMethodService.instance;
1538
+ }
1539
+ /**
1540
+ * What this store accepts, from `GET /api/checkout`.
1541
+ *
1542
+ * The storefront branches on an uppercase code (`COD`, `STRIPE`, …) while GoCommerce installs
1543
+ * them lowercase, so the code is uppercased for the UI and the original kept for the API call.
1544
+ */
1545
+ async list(_params = {}) {
1546
+ const res = await this.api("GET", "/api/checkout");
1547
+ const codes = res.data?.payment_methods || [];
1548
+ installedCodes = codes;
1549
+ const data = codes.map((code, index) => ({
1550
+ id: code,
1551
+ code: code.toUpperCase(),
1552
+ name: METHOD_NAMES[code.toLowerCase()] || titleCase(code),
1553
+ description: "",
1554
+ active: true,
1555
+ isTest: false,
1556
+ img: null,
1557
+ rank: index,
1558
+ currency: res.data?.currency || null
1559
+ }));
1560
+ return { data, count: data.length, pageSize: data.length, noOfPage: data.length ? 1 : 0, page: 1 };
1561
+ }
1562
+ };
1563
+ var paymentMethodService = PaymentMethodService.getInstance();
1564
+ var CheckoutService = class _CheckoutService extends BaseService {
1565
+ static instance;
1566
+ static getInstance() {
1567
+ if (!_CheckoutService.instance) _CheckoutService.instance = new _CheckoutService();
1568
+ return _CheckoutService.instance;
1569
+ }
1570
+ /**
1571
+ * Places the order.
1572
+ *
1573
+ * Everything the API requires and the cart cannot hold — email, name, phone, delivery address —
1574
+ * comes from what the address step recorded against this cart. Missing pieces are named in the
1575
+ * error rather than sent as blanks, because GoCommerce's own validation message ("address.line1
1576
+ * is required") does not tell a shopper which screen to go back to.
1577
+ */
1578
+ async place(storefrontCode, { cartId, origin, returnUrl }) {
1579
+ const id = cartId && cartId !== "undefined" ? cartId : readStoredCartId2();
1580
+ if (!id) throw new Error("There is no cart to check out.");
1581
+ const details = readCheckoutDetails(id);
1582
+ const address = toGoCommerceAddress(details.shippingAddress, details.phone);
1583
+ if (!details.email) throw new Error("An email address is required to place this order \u2014 add one at the address step.");
1584
+ if (!address) throw new Error("A complete delivery address is required to place this order \u2014 add one at the address step.");
1585
+ const code = goCommerceCode(storefrontCode);
1586
+ let res;
1587
+ try {
1588
+ res = await this.api(
1589
+ "POST",
1590
+ `/api/checkout/${encodeURIComponent(code)}`,
1591
+ {
1592
+ cart_id: id,
1593
+ email: details.email,
1594
+ ...details.name ? { name: details.name } : {},
1595
+ ...details.phone ? { phone: details.phone } : {},
1596
+ address,
1597
+ ...returnUrl || origin ? { return_url: returnUrl || `${origin}/checkout/process` } : {}
1598
+ },
1599
+ // A double-submitted Place Order must not become two orders and two inventory holds.
1600
+ { "Idempotency-Key": `${id}:${code}` }
1601
+ );
1602
+ } catch (error) {
1603
+ if (await cartService.cartIsSpent(id)) {
1604
+ throw new Error("This cart is no longer available \u2014 it has expired or was already checked out. Add the items to a new bag and try again.");
1605
+ }
1606
+ throw error;
1607
+ }
1608
+ const order = res.data?.order;
1609
+ const payment = res.data?.payment;
1610
+ rememberOrderToken(order?.number, order?.access_token);
1611
+ clearCheckoutDetails(id);
1612
+ const index = await resolveProductIndex(
1613
+ (order?.line_items || []).map((line) => line.product_id),
1614
+ this.getFetch()
1615
+ );
1616
+ return {
1617
+ ...mapOrder(order, index) ?? {},
1618
+ // The order number, under every name the storefront's payment step reads it by.
1619
+ order_no: order?.number,
1620
+ orderNo: order?.number,
1621
+ code: order?.number,
1622
+ payment: payment ?? null,
1623
+ paymentKind: payment?.kind ?? "none",
1624
+ // A gateway's own fields (a Razorpay order id, a Stripe client secret, a redirect url)
1625
+ // ride on `client_data`; spreading them is what lets the storefront's per-gateway
1626
+ // branches find what they look for without this connector naming each gateway.
1627
+ ...payment?.client_data ?? {}
1628
+ };
1629
+ }
1630
+ async checkoutCOD({ cartId, origin }) {
1631
+ return this.place("cod", { cartId, origin });
1632
+ }
1633
+ async checkoutPOS({ cartId, origin }) {
1634
+ return this.place("pos", { cartId, origin });
1635
+ }
1636
+ async checkoutRazorpay({ cartId, origin }) {
1637
+ return this.place("razorpay", { cartId, origin });
1638
+ }
1639
+ async checkoutStripe({ cartId, origin }) {
1640
+ return this.place("stripe", { cartId, origin });
1641
+ }
1642
+ async checkoutCashfree({ cartId, origin }) {
1643
+ return this.place("cashfree", { cartId, origin });
1644
+ }
1645
+ async checkoutPaypal({ cartId, origin, return_url }) {
1646
+ return this.place("paypal", { cartId, origin, returnUrl: return_url });
1647
+ }
1648
+ async checkoutPhonepe({ cartId, origin }) {
1649
+ return this.place("phonepe", { cartId, origin });
1650
+ }
1651
+ async createAffirmPayOrder({
1652
+ cartId,
1653
+ origin
1654
+ }) {
1655
+ return this.place("affirm", { cartId, origin });
1656
+ }
1657
+ // GoCommerce reconciles a payment through `POST /api/checkout/{code}/webhook`, which the gateway
1658
+ // calls server to server. There is nothing for the browser to capture afterwards, so these keep
1659
+ // the storefront's redirect moving with the order number it already has rather than pretending
1660
+ // to perform a capture that this API does not offer.
1661
+ settled(orderNo) {
1662
+ return { order_no: orderNo || "", orderNo: orderNo || "", captured: false };
1663
+ }
1664
+ async captureRazorpayPayment({ razorpay_order_id }) {
1665
+ return this.settled(razorpay_order_id);
1666
+ }
1667
+ async capturePhonepePayment({ phonepe_order_id }) {
1668
+ return this.settled(phonepe_order_id);
1669
+ }
1670
+ async captureCashfreePayment({ order_no }) {
1671
+ return this.settled(order_no);
1672
+ }
1673
+ async checkoutStripeCapture({ order_no }) {
1674
+ return this.settled(order_no);
1675
+ }
1676
+ async confirmAffirmOrder({ orderId }) {
1677
+ return this.settled(orderId);
1678
+ }
1679
+ async cancelAffirmOrder({ orderId }) {
1680
+ throw new Error(`Cancelling order ${orderId || ""} is not available on this store.`);
1681
+ }
1682
+ /**
1683
+ * GoCommerce prices shipping when it creates the order — there is no rate list for a shopper to
1684
+ * choose from. An empty list is the accurate answer, and the payment step renders no rate picker
1685
+ * for it rather than an empty one.
1686
+ */
1687
+ async getShippingRates(_params) {
1688
+ return { message: "This store does not offer selectable shipping rates", success: true, error: null, data: [] };
1689
+ }
1690
+ };
1691
+ var checkoutService = CheckoutService.getInstance();
1692
+
1693
+ // src/services/orders.ts
1694
+ var usable = (value) => value && value !== "undefined" && value !== "null" ? value : null;
1695
+ var OrderService = class _OrderService extends BaseService {
1696
+ static instance;
1697
+ static getInstance() {
1698
+ if (!_OrderService.instance) _OrderService.instance = new _OrderService();
1699
+ return _OrderService.instance;
1700
+ }
1701
+ async readOrder(orderNumber, token) {
1702
+ try {
1703
+ const res = await this.api("GET", `/api/orders/${encodeURIComponent(orderNumber)}?token=${encodeURIComponent(token)}`);
1704
+ return res.data ?? null;
1705
+ } catch {
1706
+ return null;
1707
+ }
1708
+ }
1709
+ async present(order) {
1710
+ if (!order) return null;
1711
+ const index = await resolveProductIndex(
1712
+ (order.line_items || []).map((line) => line.product_id),
1713
+ this.getFetch()
1714
+ );
1715
+ return mapOrder(order, index);
1716
+ }
1717
+ /**
1718
+ * The confirmation view, rebuilt from the cart that became the order.
1719
+ *
1720
+ * Everything a receipt needs is on the converted cart except the address, which was never stored
1721
+ * on it — so that block is simply absent rather than invented.
1722
+ */
1723
+ async orderFromCart(cartId, orderNumber) {
1724
+ let cart = null;
1725
+ try {
1726
+ const res = await this.api("GET", `/api/carts/${encodeURIComponent(cartId)}`);
1727
+ cart = res.data ?? null;
1728
+ } catch {
1729
+ return null;
1730
+ }
1731
+ if (!cart) return null;
1732
+ const lines = cart.line_items || [];
1733
+ const index = await resolveProductIndex(
1734
+ lines.map((line) => line.product_id),
1735
+ this.getFetch()
1736
+ );
1737
+ const subtotal = fromMoney(cart.subtotal);
1738
+ return {
1739
+ id: null,
1740
+ orderNo: orderNumber,
1741
+ order_no: orderNumber,
1742
+ parentOrderNo: orderNumber,
1743
+ status: cart.status === "converted" ? "confirmed" : "pending",
1744
+ paymentStatus: "pending",
1745
+ paymentMethod: null,
1746
+ createdAt: cart.updated_at || cart.created_at || null,
1747
+ updatedAt: cart.updated_at || null,
1748
+ userEmail: cart.email || "",
1749
+ phone: "",
1750
+ subtotal,
1751
+ shippingCharges: 0,
1752
+ discount: 0,
1753
+ total: subtotal,
1754
+ currencyCode: cart.currency,
1755
+ shippingRate: null,
1756
+ fulfillments: [],
1757
+ shippingAddress: null,
1758
+ billingAddress: null,
1759
+ lineItems: lines.map((line) => {
1760
+ const product = line.product_id == null ? void 0 : index.get(line.product_id);
1761
+ const mapped = product ? mapProduct(product) : null;
1762
+ const variant = product?.variants?.find((v) => v.id === line.variant_id);
1763
+ return {
1764
+ id: line.id,
1765
+ productId: line.product_id,
1766
+ variantId: line.variant_id,
1767
+ title: line.title || mapped?.title || "",
1768
+ variantTitle: line.variant_label || line.sku || "",
1769
+ slug: mapped?.slug || "",
1770
+ sku: line.sku || "",
1771
+ thumbnail: absoluteMedia(variant?.image?.url) || mapped?.thumbnail || null,
1772
+ qty: line.quantity,
1773
+ price: fromMoney(line.unit_price),
1774
+ subtotal: fromMoney(line.total),
1775
+ total: fromMoney(line.total)
1776
+ };
1777
+ })
1778
+ };
1779
+ }
1780
+ /**
1781
+ * The shopper's orders. Without an account there is no server-side list to ask for, so this is
1782
+ * every order placed from this browser, newest first — which is exactly what it can know.
1783
+ */
1784
+ async list({ page = 1 } = {}) {
1785
+ const numbers = readOrderNumbers().reverse();
1786
+ if (!numbers.length) return emptyPage(page);
1787
+ const start = (page - 1) * PAGE_SIZE;
1788
+ const window2 = numbers.slice(start, start + PAGE_SIZE);
1789
+ const orders = await Promise.all(
1790
+ window2.map(async (number) => {
1791
+ const token = readOrderToken(number);
1792
+ return token ? this.present(await this.readOrder(number, token)) : null;
1793
+ })
1794
+ );
1795
+ return paginated(orders.filter(Boolean), numbers.length, page);
1796
+ }
1797
+ /** What the confirmation page loads with, server-side, straight after checkout. */
1798
+ async listOrdersByParent({ orderNo, cartId }) {
1799
+ const number = usable(orderNo);
1800
+ if (!number) return emptyPage();
1801
+ const token = readOrderToken(number);
1802
+ if (token) {
1803
+ const order = await this.present(await this.readOrder(number, token));
1804
+ if (order) return paginated([order], 1, 1, 1);
1805
+ }
1806
+ const cart = usable(cartId);
1807
+ if (cart) {
1808
+ const reconstructed = await this.orderFromCart(cart, number);
1809
+ if (reconstructed) return paginated([reconstructed], 1, 1, 1);
1810
+ }
1811
+ return emptyPage();
1812
+ }
1813
+ async getOrder(orderNo) {
1814
+ const number = usable(orderNo);
1815
+ if (!number) return {};
1816
+ const token = readOrderToken(number);
1817
+ if (!token) return {};
1818
+ return await this.present(await this.readOrder(number, token)) ?? {};
1819
+ }
1820
+ async fetchOrder(id) {
1821
+ return this.getOrder(id);
1822
+ }
1823
+ async fetchTrackOrder(id) {
1824
+ return this.listOrdersByParent({ orderNo: id, cartId: null });
1825
+ }
1826
+ async paySuccessPageHit(orderId) {
1827
+ return this.getOrder(orderId);
1828
+ }
1829
+ // GoCommerce has no email+OTP lookup: an order is reached by its number and access token.
1830
+ async getOrderByEmailAndOTP(_params) {
1831
+ return emptyPage();
1832
+ }
1833
+ // No server-side order feed exists to reorder from; `list()` is the shopper's own history.
1834
+ async buyAgain() {
1835
+ return emptyPage();
1836
+ }
1837
+ async listPublic() {
1838
+ return emptyPage();
1839
+ }
1840
+ async submitReview(_params) {
1841
+ throw new Error("Product reviews are not available on this store.");
1842
+ }
1843
+ // Checkout runs through CheckoutService here; these Litekart-era entry points have no equivalent
1844
+ // and must not fall back to an API a GoCommerce store does not run.
1845
+ async codCheckout(_params) {
1846
+ throw new Error("Use checkoutService.checkoutCOD() on GoCommerce.");
1847
+ }
1848
+ async razorpayCheckout(_params) {
1849
+ throw new Error("Use checkoutService.checkoutRazorpay() on GoCommerce.");
1850
+ }
1851
+ async stripeCheckout(_params) {
1852
+ throw new Error("Use checkoutService.checkoutStripe() on GoCommerce.");
1853
+ }
1854
+ async cashfreeCheckout(_params) {
1855
+ throw new Error("Use checkoutService.checkoutCashfree() on GoCommerce.");
1856
+ }
1857
+ async razorCapture(_params) {
1858
+ throw new Error("Use checkoutService.captureRazorpayPayment() on GoCommerce.");
1859
+ }
1860
+ };
1861
+ var orderService = OrderService.getInstance();
1862
+
1863
+ // src/services/store.ts
1864
+ var listOf = (items) => paginated(items, items.length, 1, items.length || PAGE_SIZE);
1865
+ var StoreService = class _StoreService extends BaseService {
1866
+ static instance;
1867
+ static getInstance() {
1868
+ if (!_StoreService.instance) _StoreService.instance = new _StoreService();
1869
+ return _StoreService.instance;
1870
+ }
1871
+ async getStoreByIdOrDomain(_params = {}) {
1872
+ const store = await readStaticStoreDetails();
1873
+ if (!store) {
1874
+ throw new Error("No store configuration is registered \u2014 check kitcommerce.config.ts (see docs/GOCOMMERCE.md).");
1875
+ }
1876
+ return store;
1877
+ }
1878
+ /** The settings store asks for the record by id; there is only ever the configured one. */
1879
+ async getStore(_storeId) {
1880
+ return this.getStoreByIdOrDomain({});
1881
+ }
1882
+ };
1883
+ var storeService = StoreService.getInstance();
1884
+ var MenuService = class _MenuService extends BaseService {
1885
+ static instance;
1886
+ static getInstance() {
1887
+ if (!_MenuService.instance) _MenuService.instance = new _MenuService();
1888
+ return _MenuService.instance;
1889
+ }
1890
+ /** The nav reads `response.data` and filters it on `menuId`, the way the Litekart API served it. */
1891
+ async list() {
1892
+ const store = await readStaticStore();
1893
+ return { data: store?.menu ?? [] };
1894
+ }
1895
+ };
1896
+ var menuService = MenuService.getInstance();
1897
+ var StateService = class _StateService extends BaseService {
1898
+ static instance;
1899
+ static getInstance() {
1900
+ if (!_StateService.instance) _StateService.instance = new _StateService();
1901
+ return _StateService.instance;
1902
+ }
1903
+ // No state list ships with the store record, and GoCommerce takes the state as free text.
1904
+ async list() {
1905
+ return listOf([]);
1906
+ }
1907
+ };
1908
+ var stateService = StateService.getInstance();
1909
+ var CurrencyService = class _CurrencyService extends BaseService {
1910
+ static instance;
1911
+ static getInstance() {
1912
+ if (!_CurrencyService.instance) _CurrencyService.instance = new _CurrencyService();
1913
+ return _CurrencyService.instance;
1914
+ }
1915
+ /** One currency: GoCommerce prices a store in the currency its catalogue is recorded in. */
1916
+ async listCurrencies() {
1917
+ const store = await readStaticStore();
1918
+ return listOf(store?.currency ? [store.currency] : []);
1919
+ }
1920
+ };
1921
+ var currencyService = CurrencyService.getInstance();
1922
+ var RegionService = class _RegionService extends BaseService {
1923
+ static instance;
1924
+ static getInstance() {
1925
+ if (!_RegionService.instance) _RegionService.instance = new _RegionService();
1926
+ return _RegionService.instance;
1927
+ }
1928
+ // GoCommerce has no regions; one store, one currency, one price list.
1929
+ async getRegionByRegionId(_id) {
1930
+ return {};
1931
+ }
1932
+ };
1933
+ var regionService = RegionService.getInstance();
1934
+ var PluginService = class _PluginService extends BaseService {
1935
+ static instance;
1936
+ static getInstance() {
1937
+ if (!_PluginService.instance) _PluginService.instance = new _PluginService();
1938
+ return _PluginService.instance;
1939
+ }
1940
+ async list(_params = {}) {
1941
+ const store = await readStaticStore();
1942
+ return listOf(store?.plugins ?? []);
1943
+ }
1944
+ };
1945
+ var pluginService = PluginService.getInstance();
1946
+ var pluginsService = pluginService;
1947
+ var SettingService = class _SettingService extends BaseService {
1948
+ static instance;
1949
+ static getInstance() {
1950
+ if (!_SettingService.instance) _SettingService.instance = new _SettingService();
1951
+ return _SettingService.instance;
1952
+ }
1953
+ async fetchSetting() {
1954
+ const store = await readStaticStore();
1955
+ return store ? [store] : [];
1956
+ }
1957
+ // Settings are the storefront's own configuration; a storefront must not write them back.
1958
+ async saveSettings(_setting) {
1959
+ throw new Error("Store settings are configured in kitcommerce.config.ts, not from the storefront.");
1960
+ }
1961
+ async updateSettings(_id, _setting) {
1962
+ throw new Error("Store settings are configured in kitcommerce.config.ts, not from the storefront.");
1963
+ }
1964
+ };
1965
+ var settingService = SettingService.getInstance();
1966
+ var settingsService = settingService;
1967
+ var InitService = class _InitService extends BaseService {
1968
+ static instance;
1969
+ static getInstance() {
1970
+ if (!_InitService.instance) _InitService.instance = new _InitService();
1971
+ return _InitService.instance;
1972
+ }
1973
+ async fetchInit() {
1974
+ const store = await readStaticStore();
1975
+ return store ? [store] : [];
1976
+ }
1977
+ };
1978
+ var initService = InitService.getInstance();
1979
+ var HomeService = class _HomeService extends BaseService {
1980
+ static instance;
1981
+ static getInstance() {
1982
+ if (!_HomeService.instance) _HomeService.instance = new _HomeService();
1983
+ return _HomeService.instance;
1984
+ }
1985
+ // The homepage is composed from the storefront's own blocks; GoCommerce stores no page layouts.
1986
+ async getHome() {
1987
+ return [];
1988
+ }
1989
+ };
1990
+ var homeService = HomeService.getInstance();
1991
+ var PopularSearchService = class _PopularSearchService extends BaseService {
1992
+ static instance;
1993
+ static getInstance() {
1994
+ if (!_PopularSearchService.instance) _PopularSearchService.instance = new _PopularSearchService();
1995
+ return _PopularSearchService.instance;
1996
+ }
1997
+ // GoCommerce records no search analytics, so there are no popular searches to report.
1998
+ async listPopularSearch(_params = {}) {
1999
+ return listOf([]);
2000
+ }
2001
+ };
2002
+ var popularSearchService = PopularSearchService.getInstance();
2003
+ var PopularityService = class _PopularityService extends BaseService {
2004
+ static instance;
2005
+ static getInstance() {
2006
+ if (!_PopularityService.instance) _PopularityService.instance = new _PopularityService();
2007
+ return _PopularityService.instance;
2008
+ }
2009
+ /** Nothing to record against: a view counter is not part of this API. Accepted and dropped. */
2010
+ async updatePopularity(_params = {}) {
2011
+ return { success: true };
2012
+ }
2013
+ };
2014
+ var popularityService = PopularityService.getInstance();
2015
+
2016
+ // src/services/unsupported.ts
2017
+ var unavailable = (what) => new Error(`${what} is not available on this store.`);
2018
+ var singleton = (Ctor) => {
2019
+ let instance;
2020
+ return () => instance ??= new Ctor();
2021
+ };
2022
+ var NO_ACCOUNTS = "Accounts are not available on this store \u2014 checkout does not need one";
2023
+ var UserService = class _UserService extends BaseService {
2024
+ static getInstance = singleton(_UserService);
2025
+ /** No session to report. `null` is what the header and guards check for. */
2026
+ async getMe() {
2027
+ return null;
2028
+ }
2029
+ async getUser(_id) {
2030
+ return null;
2031
+ }
2032
+ async login(_params) {
2033
+ throw new Error(`${NO_ACCOUNTS}.`);
2034
+ }
2035
+ async signup(_params) {
2036
+ throw new Error(`${NO_ACCOUNTS}.`);
2037
+ }
2038
+ async joinAsVendor(_params) {
2039
+ throw unavailable("Vendor signup");
2040
+ }
2041
+ async forgotPassword(_params) {
2042
+ throw new Error(`${NO_ACCOUNTS}.`);
2043
+ }
2044
+ async resetPassword(_params) {
2045
+ throw new Error(`${NO_ACCOUNTS}.`);
2046
+ }
2047
+ async changePassword(_params) {
2048
+ throw new Error(`${NO_ACCOUNTS}.`);
2049
+ }
2050
+ async getOtp(_params) {
2051
+ throw new Error(`${NO_ACCOUNTS}.`);
2052
+ }
2053
+ async verifyOtp(_params) {
2054
+ throw new Error(`${NO_ACCOUNTS}.`);
2055
+ }
2056
+ async updateProfile(_params) {
2057
+ throw new Error(`${NO_ACCOUNTS}.`);
2058
+ }
2059
+ async checkEmail(_email) {
2060
+ return { exists: false };
2061
+ }
2062
+ async deleteUser(_id) {
2063
+ throw new Error(`${NO_ACCOUNTS}.`);
2064
+ }
2065
+ /**
2066
+ * Clears the session this storefront thinks it has.
2067
+ *
2068
+ * There is no session on GoCommerce to end, so it would be easy to make this a no-op — and that
2069
+ * is a trap. The shared user store decides who is signed in by reading the `connect.sid` and `me`
2070
+ * cookies straight from `document.cookie`, not by asking a service. A pair left behind by running
2071
+ * this storefront against a backend that does have accounts therefore makes GoCommerce look
2072
+ * signed-in, which sends the checkout down the saved-address path instead of the guest one. The
2073
+ * checkout page already knows to call `logout()` and retry when that happens — so this has to
2074
+ * actually clear them, or that recovery does nothing.
2075
+ */
2076
+ async logout() {
2077
+ clearPhantomSession();
2078
+ return { success: true };
2079
+ }
2080
+ };
2081
+ var userService = UserService.getInstance();
2082
+ var AuthService = class _AuthService extends UserService {
2083
+ static getInstance = singleton(_AuthService);
2084
+ async verifyEmail(_email, _token) {
2085
+ throw new Error(`${NO_ACCOUNTS}, so there is no address to verify.`);
2086
+ }
2087
+ async joinAsAdmin(_params) {
2088
+ throw unavailable("Admin signup");
2089
+ }
2090
+ };
2091
+ var authService = AuthService.getInstance();
2092
+ var ProfileService = class _ProfileService extends BaseService {
2093
+ static getInstance = singleton(_ProfileService);
2094
+ async getOne() {
2095
+ return null;
2096
+ }
2097
+ async save(_profile) {
2098
+ throw new Error(`${NO_ACCOUNTS}.`);
2099
+ }
2100
+ };
2101
+ var profileService = ProfileService.getInstance();
2102
+ var AddressService = class _AddressService extends BaseService {
2103
+ static getInstance = singleton(_AddressService);
2104
+ async list(params = {}) {
2105
+ const addresses = listAddresses();
2106
+ const page = Math.max(1, Number(params.page || 1) || 1);
2107
+ const start = (page - 1) * PAGE_SIZE;
2108
+ return paginated(addresses.slice(start, start + PAGE_SIZE), addresses.length, page);
2109
+ }
2110
+ async fetchAddress(id) {
2111
+ const address = findAddress(id);
2112
+ if (!address) throw new Error("That address is no longer saved on this device.");
2113
+ return address;
2114
+ }
2115
+ async saveAddress(address) {
2116
+ return putAddress(address);
2117
+ }
2118
+ async editAddress(id, address) {
2119
+ const saved = patchAddress(id, address);
2120
+ if (!saved) throw new Error("That address is no longer saved on this device.");
2121
+ return saved;
2122
+ }
2123
+ async deleteAddress(id) {
2124
+ removeAddress(id);
2125
+ }
2126
+ };
2127
+ var addressService = AddressService.getInstance();
2128
+ var WishlistService = class _WishlistService extends BaseService {
2129
+ static getInstance = singleton(_WishlistService);
2130
+ async fetchWishlist(_params = {}) {
2131
+ return emptyPage();
2132
+ }
2133
+ async checkWishlist(_params = {}) {
2134
+ return { data: false };
2135
+ }
2136
+ /** The product grid asks about many ids at once; nothing is on a list that cannot exist. */
2137
+ async checkWishlistInBulk(_ids) {
2138
+ return { data: [] };
2139
+ }
2140
+ async toggleWishlist(_params) {
2141
+ throw unavailable("Wishlists");
2142
+ }
2143
+ };
2144
+ var wishlistService = WishlistService.getInstance();
2145
+ var CouponService = class _CouponService extends BaseService {
2146
+ static getInstance = singleton(_CouponService);
2147
+ // No discount engine, so the payment step renders no coupon box rather than an empty one.
2148
+ async listCoupons(_params = {}) {
2149
+ return emptyPage();
2150
+ }
2151
+ async searchCoupons(_params = {}) {
2152
+ return emptyPage();
2153
+ }
2154
+ async getCoupon(_id) {
2155
+ throw unavailable("Discount codes");
2156
+ }
2157
+ async createCoupon(_coupon) {
2158
+ throw unavailable("Discount codes");
2159
+ }
2160
+ async patchCoupon(_id, _coupon) {
2161
+ throw unavailable("Discount codes");
2162
+ }
2163
+ async deleteCoupon(_id) {
2164
+ throw unavailable("Discount codes");
2165
+ }
2166
+ };
2167
+ var couponService = CouponService.getInstance();
2168
+ var DealService = class _DealService extends BaseService {
2169
+ static getInstance = singleton(_DealService);
2170
+ async fetchDeals() {
2171
+ return emptyPage();
2172
+ }
2173
+ };
2174
+ var dealService = DealService.getInstance();
2175
+ var ReviewService = class _ReviewService extends BaseService {
2176
+ static getInstance = singleton(_ReviewService);
2177
+ async fetchReviews(_params = {}) {
2178
+ return emptyPage();
2179
+ }
2180
+ async allReviews(_params = {}) {
2181
+ return emptyPage();
2182
+ }
2183
+ async fetchProducrReviews(_productId) {
2184
+ return emptyPage();
2185
+ }
2186
+ async saveReview(_review) {
2187
+ throw unavailable("Product reviews");
2188
+ }
2189
+ };
2190
+ var reviewService = ReviewService.getInstance();
2191
+ var PageService = class _PageService extends BaseService {
2192
+ static getInstance = singleton(_PageService);
2193
+ async list(_params = {}) {
2194
+ return emptyPage();
2195
+ }
2196
+ async listLatestPages(_params = {}) {
2197
+ return emptyPage();
2198
+ }
2199
+ /**
2200
+ * Every caller asks for a fixed, optional slug — `products`, `about-us`, `privacy-policy` — to
2201
+ * dress a page the storefront already renders. So an absent page is the normal state, not a dead
2202
+ * link: answer empty and let the route show its own content, rather than log a failure on every
2203
+ * request for something that was never required.
2204
+ */
2205
+ async getOne(_id) {
2206
+ return {};
2207
+ }
2208
+ };
2209
+ var pageService = PageService.getInstance();
2210
+ var BlogService = class _BlogService extends BaseService {
2211
+ static getInstance = singleton(_BlogService);
2212
+ async list(_params = {}) {
2213
+ return emptyPage();
2214
+ }
2215
+ async getOne(id) {
2216
+ throw new Error(`The post "${id}" does not exist on this store.`);
2217
+ }
2218
+ };
2219
+ var blogService = BlogService.getInstance();
2220
+ var BannerService = class _BannerService extends BaseService {
2221
+ static getInstance = singleton(_BannerService);
2222
+ async list() {
2223
+ return emptyPage();
2224
+ }
2225
+ async fetchBannersGroup() {
2226
+ return emptyPage();
2227
+ }
2228
+ };
2229
+ var bannerService = BannerService.getInstance();
2230
+ var GalleryService = class _GalleryService extends BaseService {
2231
+ static getInstance = singleton(_GalleryService);
2232
+ async fetchGallery() {
2233
+ return [];
2234
+ }
2235
+ };
2236
+ var galleryService = GalleryService.getInstance();
2237
+ var ReelsService = class _ReelsService extends BaseService {
2238
+ static getInstance = singleton(_ReelsService);
2239
+ async list() {
2240
+ return emptyPage();
2241
+ }
2242
+ };
2243
+ var reelsService = ReelsService.getInstance();
2244
+ var FaqService = class _FaqService extends BaseService {
2245
+ static getInstance = singleton(_FaqService);
2246
+ async listFaqs(_params = {}) {
2247
+ return emptyPage();
2248
+ }
2249
+ };
2250
+ var faqService = FaqService.getInstance();
2251
+ var FeedbackService = class _FeedbackService extends BaseService {
2252
+ static getInstance = singleton(_FeedbackService);
2253
+ async listFeedbacks(_params = {}) {
2254
+ return emptyPage();
2255
+ }
2256
+ };
2257
+ var feedbackService = FeedbackService.getInstance();
2258
+ var ContactService = class _ContactService extends BaseService {
2259
+ static getInstance = singleton(_ContactService);
2260
+ async submitContactUsForm(_params) {
2261
+ throw unavailable("The contact form");
2262
+ }
2263
+ };
2264
+ var contactService = ContactService.getInstance();
2265
+ var EnquiryService = class _EnquiryService extends BaseService {
2266
+ static getInstance = singleton(_EnquiryService);
2267
+ async create(_params) {
2268
+ throw unavailable("Product enquiries");
2269
+ }
2270
+ };
2271
+ var enquiryService = EnquiryService.getInstance();
2272
+ var ChatService = class _ChatService extends BaseService {
2273
+ static getInstance = singleton(_ChatService);
2274
+ async list() {
2275
+ return [];
2276
+ }
2277
+ async chats() {
2278
+ return [];
2279
+ }
2280
+ async save(_message) {
2281
+ throw unavailable("Messaging");
2282
+ }
2283
+ };
2284
+ var chatService = ChatService.getInstance();
2285
+ var UploadService = class _UploadService extends BaseService {
2286
+ static getInstance = singleton(_UploadService);
2287
+ // Media in GoCommerce is an operator's, uploaded through the admin API. A storefront holds no
2288
+ // admin credential and must not act as though it does.
2289
+ async uploadToS3(_params) {
2290
+ throw unavailable("File uploads");
2291
+ }
2292
+ async uploadMultipleToS3(_params) {
2293
+ throw unavailable("File uploads");
2294
+ }
2295
+ async deleteFromS3(_params) {
2296
+ throw unavailable("File uploads");
2297
+ }
2298
+ };
2299
+ var uploadService = UploadService.getInstance();
2300
+ var VendorService = class _VendorService extends BaseService {
2301
+ static getInstance = singleton(_VendorService);
2302
+ async list(_params = {}) {
2303
+ return emptyPage();
2304
+ }
2305
+ async getVendor(_id) {
2306
+ throw unavailable("Vendor pages");
2307
+ }
2308
+ async fetchMyVendorDetails() {
2309
+ throw unavailable("Vendor accounts");
2310
+ }
2311
+ async fetchDashboard() {
2312
+ throw unavailable("Vendor accounts");
2313
+ }
2314
+ async fetchProductsOfVendor(_vendorId) {
2315
+ return [];
2316
+ }
2317
+ async getAllVendorRatings(_vendorId) {
2318
+ return [];
2319
+ }
2320
+ async save(_data) {
2321
+ throw unavailable("Vendor accounts");
2322
+ }
2323
+ async update(_data) {
2324
+ throw unavailable("Vendor accounts");
2325
+ }
2326
+ };
2327
+ var vendorService = VendorService.getInstance();
2328
+ var VarniCustomDesignService = class _VarniCustomDesignService extends BaseService {
2329
+ static getInstance = singleton(_VarniCustomDesignService);
2330
+ async submitForm(_formDetails) {
2331
+ throw unavailable("Custom design requests");
2332
+ }
2333
+ };
2334
+ var varniCustomDesignService = VarniCustomDesignService.getInstance();
2335
+ var VarniCustomProductService = class _VarniCustomProductService extends BaseService {
2336
+ static getInstance = singleton(_VarniCustomProductService);
2337
+ async checkForCombinationWith(_args) {
2338
+ return { data: [], count: 0, pageSize: PAGE_SIZE, noOfPage: 0, page: 1 };
2339
+ }
2340
+ async createCustomOrderChat(_productId) {
2341
+ throw unavailable("Custom product chat");
2342
+ }
2343
+ async sendMessageToCustomOrderChat(_chatId, _vendor, _message) {
2344
+ throw unavailable("Custom product chat");
2345
+ }
2346
+ };
2347
+ var varniCustomProductService = VarniCustomProductService.getInstance();
2348
+
2349
+ exports.API_PROXY_PREFIX = API_PROXY_PREFIX;
2350
+ exports.AddressService = AddressService;
2351
+ exports.AuthService = AuthService;
2352
+ exports.AutocompleteService = AutocompleteService;
2353
+ exports.BannerService = BannerService;
2354
+ exports.BaseService = BaseService;
2355
+ exports.BlogService = BlogService;
2356
+ exports.CartService = CartService;
2357
+ exports.CategoryService = CategoryService;
2358
+ exports.ChatService = ChatService;
2359
+ exports.CheckoutService = CheckoutService;
2360
+ exports.CollectionService = CollectionService;
2361
+ exports.ContactService = ContactService;
2362
+ exports.CouponService = CouponService;
2363
+ exports.CurrencyService = CurrencyService;
2364
+ exports.DealService = DealService;
2365
+ exports.EnquiryService = EnquiryService;
2366
+ exports.FaqService = FaqService;
2367
+ exports.FeedbackService = FeedbackService;
2368
+ exports.GalleryService = GalleryService;
2369
+ exports.GoCommerceError = GoCommerceError;
2370
+ exports.HomeService = HomeService;
2371
+ exports.InitService = InitService;
2372
+ exports.MAX_LIMIT = MAX_LIMIT;
2373
+ exports.MeilisearchService = MeilisearchService;
2374
+ exports.MenuService = MenuService;
2375
+ exports.OrderService = OrderService;
2376
+ exports.PAGE_SIZE = PAGE_SIZE;
2377
+ exports.PageService = PageService;
2378
+ exports.PaymentMethodService = PaymentMethodService;
2379
+ exports.PluginService = PluginService;
2380
+ exports.PluginsService = PluginService;
2381
+ exports.PopularSearchService = PopularSearchService;
2382
+ exports.PopularityService = PopularityService;
2383
+ exports.ProductService = ProductService;
2384
+ exports.ProfileService = ProfileService;
2385
+ exports.ReelsService = ReelsService;
2386
+ exports.RegionService = RegionService;
2387
+ exports.ReviewService = ReviewService;
2388
+ exports.SearchService = SearchService;
2389
+ exports.SettingService = SettingService;
2390
+ exports.SettingsService = SettingService;
2391
+ exports.StateService = StateService;
2392
+ exports.StoreService = StoreService;
2393
+ exports.UploadService = UploadService;
2394
+ exports.UserService = UserService;
2395
+ exports.VarniCustomDesignService = VarniCustomDesignService;
2396
+ exports.VarniCustomProductService = VarniCustomProductService;
2397
+ exports.VendorService = VendorService;
2398
+ exports.WishlistService = WishlistService;
2399
+ exports.absoluteMedia = absoluteMedia;
2400
+ exports.addressService = addressService;
2401
+ exports.authService = authService;
2402
+ exports.autocompleteService = autocompleteService;
2403
+ exports.bannerService = bannerService;
2404
+ exports.blogService = blogService;
2405
+ exports.cartService = cartService;
2406
+ exports.categoryService = categoryService;
2407
+ exports.chatService = chatService;
2408
+ exports.checkoutService = checkoutService;
2409
+ exports.clearCheckoutDetails = clearCheckoutDetails;
2410
+ exports.clearPhantomSession = clearPhantomSession;
2411
+ exports.collectionService = collectionService;
2412
+ exports.contactService = contactService;
2413
+ exports.couponService = couponService;
2414
+ exports.currencyFractionDigits = currencyFractionDigits;
2415
+ exports.currencyService = currencyService;
2416
+ exports.dealService = dealService;
2417
+ exports.emptyPage = emptyPage;
2418
+ exports.enquiryService = enquiryService;
2419
+ exports.faqService = faqService;
2420
+ exports.feedbackService = feedbackService;
2421
+ exports.findAddress = findAddress;
2422
+ exports.flattenMetadata = flattenMetadata;
2423
+ exports.fromMoney = fromMoney;
2424
+ exports.galleryService = galleryService;
2425
+ exports.hasPhantomSession = hasPhantomSession;
2426
+ exports.hasStaticStore = hasStaticStore;
2427
+ exports.homeService = homeService;
2428
+ exports.initService = initService;
2429
+ exports.listAddresses = listAddresses;
2430
+ exports.mapCart = mapCart;
2431
+ exports.mapCategory = mapCategory;
2432
+ exports.mapCollection = mapCollection;
2433
+ exports.mapOrder = mapOrder;
2434
+ exports.mapProduct = mapProduct;
2435
+ exports.meilisearchService = meilisearchService;
2436
+ exports.menuService = menuService;
2437
+ exports.orderService = orderService;
2438
+ exports.pageService = pageService;
2439
+ exports.paginated = paginated;
2440
+ exports.patchAddress = patchAddress;
2441
+ exports.paymentMethodService = paymentMethodService;
2442
+ exports.pluginService = pluginService;
2443
+ exports.pluginsService = pluginsService;
2444
+ exports.popularSearchService = popularSearchService;
2445
+ exports.popularityService = popularityService;
2446
+ exports.productImages = productImages;
2447
+ exports.productService = productService;
2448
+ exports.profileService = profileService;
2449
+ exports.putAddress = putAddress;
2450
+ exports.readCheckoutDetails = readCheckoutDetails;
2451
+ exports.readOrderNumbers = readOrderNumbers;
2452
+ exports.readOrderToken = readOrderToken;
2453
+ exports.readStaticStore = readStaticStore;
2454
+ exports.readStaticStoreDetails = readStaticStoreDetails;
2455
+ exports.reelsService = reelsService;
2456
+ exports.regionService = regionService;
2457
+ exports.rememberOrderToken = rememberOrderToken;
2458
+ exports.removeAddress = removeAddress;
2459
+ exports.resolveProductIndex = resolveProductIndex;
2460
+ exports.reviewService = reviewService;
2461
+ exports.searchService = searchService;
2462
+ exports.setStaticStore = setStaticStore;
2463
+ exports.settingService = settingService;
2464
+ exports.settingsService = settingsService;
2465
+ exports.stateService = stateService;
2466
+ exports.storeService = storeService;
2467
+ exports.toGoCommerceAddress = toGoCommerceAddress;
2468
+ exports.toStorefrontAddress = toStorefrontAddress;
2469
+ exports.uploadService = uploadService;
2470
+ exports.userService = userService;
2471
+ exports.varniCustomDesignService = varniCustomDesignService;
2472
+ exports.varniCustomProductService = varniCustomProductService;
2473
+ exports.vendorService = vendorService;
2474
+ exports.wishlistService = wishlistService;
2475
+ exports.writeCheckoutDetails = writeCheckoutDetails;
2476
+ //# sourceMappingURL=index.cjs.map
2477
+ //# sourceMappingURL=index.cjs.map