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