arky-sdk 0.9.20 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -105
- package/dist/admin.cjs +55 -5
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +5 -3
- package/dist/admin.d.ts +5 -3
- package/dist/admin.js +55 -5
- package/dist/admin.js.map +1 -1
- package/dist/{api-C_2AMzmc.d.cts → index-B0Pd1s-2.d.cts} +4457 -4451
- package/dist/{api-C_2AMzmc.d.ts → index-B0Pd1s-2.d.ts} +4457 -4451
- package/dist/index.cjs +2360 -546
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3829 -3
- package/dist/index.d.ts +3829 -3
- package/dist/index.js +2356 -547
- package/dist/index.js.map +1 -1
- package/dist/{inventory-DdgZ5MI_.d.ts → inventory-C2EZEnV9.d.ts} +1 -1
- package/dist/{inventory-DgPpeRWU.d.cts → inventory-t9OBR0CC.d.cts} +1 -1
- package/dist/storefront.cjs +692 -686
- package/dist/storefront.cjs.map +1 -1
- package/dist/storefront.d.cts +5 -512
- package/dist/storefront.d.ts +5 -512
- package/dist/storefront.js +692 -686
- package/dist/storefront.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/package.json +1 -1
- package/dist/admin-CTtJoarO.d.ts +0 -1543
- package/dist/admin-DB_GYW2b.d.cts +0 -1543
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var nanostores = require('nanostores');
|
|
4
|
+
|
|
3
5
|
// src/types/index.ts
|
|
4
6
|
var PaymentMethodType = /* @__PURE__ */ ((PaymentMethodType2) => {
|
|
5
7
|
PaymentMethodType2["Cash"] = "cash";
|
|
@@ -47,70 +49,50 @@ var COMMON_ACTION_KEYS = [
|
|
|
47
49
|
"share",
|
|
48
50
|
"wishlist.added"
|
|
49
51
|
];
|
|
50
|
-
var createActionApi = (apiConfig) => ({
|
|
52
|
+
var createActionApi = (apiConfig, lifecycle) => ({
|
|
51
53
|
COMMON_ACTION_KEYS,
|
|
52
54
|
async track(params) {
|
|
55
|
+
await lifecycle.ensureVisitorSession();
|
|
53
56
|
try {
|
|
54
|
-
await apiConfig.httpClient.post(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
57
|
+
await apiConfig.httpClient.post("/v1/storefront/actions/track", {
|
|
58
|
+
key: params.key,
|
|
59
|
+
payload: params.payload
|
|
60
|
+
});
|
|
58
61
|
} catch {
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
});
|
|
62
|
-
var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
63
|
-
const base =
|
|
64
|
-
const pendingVerifications = /* @__PURE__ */ new Map();
|
|
65
|
+
var createStorefrontApi = (apiConfig, updateContactSession, lifecycle) => {
|
|
66
|
+
const base = "/v1/storefront";
|
|
65
67
|
function persistIdentification(result) {
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
+
const sessionToken = result.token?.token;
|
|
69
|
+
if (sessionToken) {
|
|
68
70
|
updateContactSession(() => ({
|
|
69
|
-
|
|
70
|
-
contact: result.contact
|
|
71
|
-
store: result.store,
|
|
72
|
-
market: result.market
|
|
71
|
+
sessionToken,
|
|
72
|
+
contact: result.contact
|
|
73
73
|
}));
|
|
74
74
|
} else {
|
|
75
|
-
if (result.verification_challenge) {
|
|
76
|
-
pendingVerifications.set(result.verification_challenge.challenge_id, {
|
|
77
|
-
store: result.store,
|
|
78
|
-
market: result.market
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
75
|
updateContactSession(
|
|
82
|
-
(current) => current ? {
|
|
83
|
-
...current,
|
|
84
|
-
contact: result.contact,
|
|
85
|
-
store: result.store,
|
|
86
|
-
market: result.market
|
|
87
|
-
} : null
|
|
76
|
+
(current) => current ? { ...current, contact: result.contact } : null
|
|
88
77
|
);
|
|
89
78
|
}
|
|
90
79
|
return result;
|
|
91
80
|
}
|
|
92
81
|
async function submitIdentification(path, params, options) {
|
|
93
|
-
const store_id = apiConfig.storeId;
|
|
94
82
|
const result = await apiConfig.httpClient.post(
|
|
95
|
-
`${base
|
|
96
|
-
{
|
|
97
|
-
store_id,
|
|
98
|
-
market: params?.market || apiConfig.market || null,
|
|
99
|
-
email: params?.email
|
|
100
|
-
},
|
|
83
|
+
`${base}/account/${path}`,
|
|
84
|
+
{ email: params?.email },
|
|
101
85
|
options
|
|
102
86
|
);
|
|
103
87
|
return persistIdentification(result);
|
|
104
88
|
}
|
|
105
89
|
return {
|
|
106
90
|
store: {
|
|
107
|
-
|
|
108
|
-
return apiConfig.httpClient.get(base(), options);
|
|
109
|
-
},
|
|
91
|
+
getSetup: lifecycle.getSetup,
|
|
110
92
|
location: {
|
|
111
93
|
getCountries(options) {
|
|
112
94
|
return apiConfig.httpClient.get(
|
|
113
|
-
|
|
95
|
+
"/v1/platform/countries",
|
|
114
96
|
options
|
|
115
97
|
);
|
|
116
98
|
},
|
|
@@ -122,13 +104,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
122
104
|
},
|
|
123
105
|
list(options) {
|
|
124
106
|
return apiConfig.httpClient.get(
|
|
125
|
-
`${base
|
|
107
|
+
`${base}/locations`,
|
|
126
108
|
options
|
|
127
109
|
);
|
|
128
110
|
},
|
|
129
111
|
get(id, options) {
|
|
130
112
|
return apiConfig.httpClient.get(
|
|
131
|
-
`${base
|
|
113
|
+
`${base}/locations/${id}`,
|
|
132
114
|
options
|
|
133
115
|
);
|
|
134
116
|
}
|
|
@@ -136,13 +118,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
136
118
|
market: {
|
|
137
119
|
list(options) {
|
|
138
120
|
return apiConfig.httpClient.get(
|
|
139
|
-
`${base
|
|
121
|
+
`${base}/markets`,
|
|
140
122
|
options
|
|
141
123
|
);
|
|
142
124
|
},
|
|
143
125
|
get(id, options) {
|
|
144
126
|
return apiConfig.httpClient.get(
|
|
145
|
-
`${base
|
|
127
|
+
`${base}/markets/${id}`,
|
|
146
128
|
options
|
|
147
129
|
);
|
|
148
130
|
}
|
|
@@ -151,85 +133,59 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
151
133
|
cms: {
|
|
152
134
|
collection: {
|
|
153
135
|
get(params, options) {
|
|
154
|
-
const
|
|
155
|
-
const identifier = params.id !== void 0 ? params.id : `${store_id}:${params.key}`;
|
|
136
|
+
const identifier = params.id ?? params.key;
|
|
156
137
|
return apiConfig.httpClient.get(
|
|
157
|
-
`${base
|
|
138
|
+
`${base}/collections/${identifier}`,
|
|
158
139
|
options
|
|
159
140
|
);
|
|
160
141
|
}
|
|
161
142
|
},
|
|
162
143
|
entry: {
|
|
163
144
|
get(params, options) {
|
|
164
|
-
const store_id = params.store_id || apiConfig.storeId;
|
|
165
|
-
if (!params.id) {
|
|
166
|
-
throw new Error("GetEntryParams requires id");
|
|
167
|
-
}
|
|
168
145
|
return apiConfig.httpClient.get(
|
|
169
|
-
`${base
|
|
146
|
+
`${base}/entries/${params.id}`,
|
|
170
147
|
options
|
|
171
148
|
);
|
|
172
149
|
},
|
|
173
150
|
find(params, options) {
|
|
174
|
-
const { store_id, ...queryParams } = params;
|
|
175
151
|
return apiConfig.httpClient.get(
|
|
176
|
-
`${base
|
|
177
|
-
{
|
|
178
|
-
...options,
|
|
179
|
-
params: queryParams
|
|
180
|
-
}
|
|
152
|
+
`${base}/entries`,
|
|
153
|
+
{ ...options, params }
|
|
181
154
|
);
|
|
182
155
|
}
|
|
183
156
|
},
|
|
184
157
|
form: {
|
|
185
158
|
get(params, options) {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
if (params.id) {
|
|
189
|
-
identifier = params.id;
|
|
190
|
-
} else if (params.key) {
|
|
191
|
-
identifier = `${store_id}:${params.key}`;
|
|
192
|
-
} else {
|
|
193
|
-
throw new Error("GetFormParams requires id or key");
|
|
194
|
-
}
|
|
159
|
+
const identifier = params.id ?? params.key;
|
|
160
|
+
if (!identifier) throw new Error("GetFormParams requires id or key");
|
|
195
161
|
return apiConfig.httpClient.get(
|
|
196
|
-
`${base
|
|
162
|
+
`${base}/forms/${identifier}`,
|
|
197
163
|
options
|
|
198
164
|
);
|
|
199
165
|
},
|
|
200
|
-
submit(params, options) {
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
if (!form_id)
|
|
204
|
-
throw new Error("SubmitFormParams requires form_id");
|
|
205
|
-
}
|
|
166
|
+
async submit(params, options) {
|
|
167
|
+
await lifecycle.ensureVisitorSession();
|
|
168
|
+
const { form_id, ...payload } = params;
|
|
169
|
+
if (!form_id) throw new Error("SubmitFormParams requires form_id");
|
|
206
170
|
return apiConfig.httpClient.post(
|
|
207
|
-
`${base
|
|
208
|
-
{ ...payload, form_id
|
|
171
|
+
`${base}/forms/${form_id}/submissions`,
|
|
172
|
+
{ ...payload, form_id },
|
|
209
173
|
options
|
|
210
174
|
);
|
|
211
175
|
}
|
|
212
176
|
},
|
|
213
177
|
taxonomy: {
|
|
214
178
|
get(params, options) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
if (params.id) {
|
|
218
|
-
identifier = params.id;
|
|
219
|
-
} else if (params.key) {
|
|
220
|
-
identifier = `${store_id}:${params.key}`;
|
|
221
|
-
} else {
|
|
222
|
-
throw new Error("GetTaxonomyParams requires id or key");
|
|
223
|
-
}
|
|
179
|
+
const identifier = params.id ?? params.key;
|
|
180
|
+
if (!identifier) throw new Error("GetTaxonomyParams requires id or key");
|
|
224
181
|
return apiConfig.httpClient.get(
|
|
225
|
-
`${base
|
|
182
|
+
`${base}/taxonomies/${identifier}`,
|
|
226
183
|
options
|
|
227
184
|
);
|
|
228
185
|
},
|
|
229
186
|
getChildren(params, options) {
|
|
230
|
-
const store_id = params.store_id || apiConfig.storeId;
|
|
231
187
|
return apiConfig.httpClient.get(
|
|
232
|
-
`${base
|
|
188
|
+
`${base}/taxonomies/${params.id}/children`,
|
|
233
189
|
options
|
|
234
190
|
);
|
|
235
191
|
}
|
|
@@ -238,113 +194,106 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
238
194
|
eshop: {
|
|
239
195
|
product: {
|
|
240
196
|
get(params, options) {
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
if (params.id) {
|
|
244
|
-
identifier = params.id;
|
|
245
|
-
} else if (params.slug) {
|
|
246
|
-
identifier = `${store_id}:${apiConfig.locale}:${params.slug}`;
|
|
247
|
-
} else {
|
|
248
|
-
throw new Error("GetProductParams requires id or slug");
|
|
249
|
-
}
|
|
197
|
+
const identifier = params.id ?? params.slug;
|
|
198
|
+
if (!identifier) throw new Error("GetProductParams requires id or slug");
|
|
250
199
|
return apiConfig.httpClient.get(
|
|
251
|
-
`${base
|
|
200
|
+
`${base}/products/${identifier}`,
|
|
252
201
|
options
|
|
253
202
|
);
|
|
254
203
|
},
|
|
255
204
|
find(params, options) {
|
|
256
|
-
const { store_id, ...queryParams } = params;
|
|
257
205
|
return apiConfig.httpClient.get(
|
|
258
|
-
`${base
|
|
259
|
-
{
|
|
260
|
-
...options,
|
|
261
|
-
params: queryParams
|
|
262
|
-
}
|
|
206
|
+
`${base}/products`,
|
|
207
|
+
{ ...options, params }
|
|
263
208
|
);
|
|
264
209
|
}
|
|
265
210
|
},
|
|
266
211
|
cart: {
|
|
267
|
-
current(
|
|
268
|
-
|
|
269
|
-
const { store_id: _store_id, ...payload } = params;
|
|
212
|
+
async current(options) {
|
|
213
|
+
await lifecycle.ensureVisitorSession();
|
|
270
214
|
return apiConfig.httpClient.post(
|
|
271
|
-
`${base
|
|
272
|
-
{
|
|
273
|
-
...payload,
|
|
274
|
-
store_id,
|
|
275
|
-
market: payload.market || apiConfig.market
|
|
276
|
-
},
|
|
215
|
+
`${base}/carts/current`,
|
|
216
|
+
{},
|
|
277
217
|
options
|
|
278
218
|
);
|
|
279
219
|
},
|
|
280
|
-
get(params, options) {
|
|
281
|
-
|
|
220
|
+
async get(params, options) {
|
|
221
|
+
await lifecycle.ensureVisitorSession();
|
|
222
|
+
const queryParams = Object.fromEntries(
|
|
223
|
+
Object.entries(
|
|
224
|
+
options?.params || {}
|
|
225
|
+
).filter(
|
|
226
|
+
([name]) => !["token", "cart_token"].includes(name.toLowerCase())
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
const headers = Object.fromEntries(
|
|
230
|
+
Object.entries(options?.headers || {}).filter(
|
|
231
|
+
([name]) => name.toLowerCase() !== "x-arky-cart-token"
|
|
232
|
+
)
|
|
233
|
+
);
|
|
282
234
|
return apiConfig.httpClient.get(
|
|
283
|
-
`${base
|
|
235
|
+
`${base}/carts/${params.id}`,
|
|
284
236
|
{
|
|
285
237
|
...options,
|
|
286
|
-
|
|
238
|
+
headers: {
|
|
239
|
+
...headers,
|
|
240
|
+
...params.token ? { "X-Arky-Cart-Token": params.token } : {}
|
|
241
|
+
},
|
|
242
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : void 0
|
|
287
243
|
}
|
|
288
244
|
);
|
|
289
245
|
},
|
|
290
|
-
update(params, options) {
|
|
291
|
-
|
|
292
|
-
const
|
|
246
|
+
async update(params, options) {
|
|
247
|
+
await lifecycle.ensureVisitorSession();
|
|
248
|
+
const { items, ...payload } = params;
|
|
293
249
|
return apiConfig.httpClient.put(
|
|
294
|
-
`${base
|
|
250
|
+
`${base}/carts/${params.id}`,
|
|
295
251
|
{
|
|
296
252
|
...payload,
|
|
297
|
-
store_id: target,
|
|
298
253
|
...items ? { items: sanitizePublicCheckoutItems(items) } : {}
|
|
299
254
|
},
|
|
300
255
|
options
|
|
301
256
|
);
|
|
302
257
|
},
|
|
303
|
-
addItem(params, options) {
|
|
304
|
-
|
|
305
|
-
const
|
|
258
|
+
async addItem(params, options) {
|
|
259
|
+
await lifecycle.ensureVisitorSession();
|
|
260
|
+
const { item, ...payload } = params;
|
|
306
261
|
return apiConfig.httpClient.post(
|
|
307
|
-
`${base
|
|
308
|
-
{
|
|
309
|
-
...payload,
|
|
310
|
-
store_id: target,
|
|
311
|
-
item: sanitizePublicCheckoutItems([item])[0]
|
|
312
|
-
},
|
|
262
|
+
`${base}/carts/${params.id}/items`,
|
|
263
|
+
{ ...payload, item: sanitizePublicCheckoutItems([item])[0] },
|
|
313
264
|
options
|
|
314
265
|
);
|
|
315
266
|
},
|
|
316
|
-
removeItem(params, options) {
|
|
317
|
-
|
|
318
|
-
const target = store_id || apiConfig.storeId;
|
|
267
|
+
async removeItem(params, options) {
|
|
268
|
+
await lifecycle.ensureVisitorSession();
|
|
319
269
|
return apiConfig.httpClient.post(
|
|
320
|
-
`${base
|
|
321
|
-
|
|
270
|
+
`${base}/carts/${params.id}/items/remove`,
|
|
271
|
+
params,
|
|
322
272
|
options
|
|
323
273
|
);
|
|
324
274
|
},
|
|
325
|
-
clear(params, options) {
|
|
326
|
-
|
|
275
|
+
async clear(params, options) {
|
|
276
|
+
await lifecycle.ensureVisitorSession();
|
|
327
277
|
return apiConfig.httpClient.post(
|
|
328
|
-
`${base
|
|
329
|
-
{ id: params.id
|
|
278
|
+
`${base}/carts/${params.id}/clear`,
|
|
279
|
+
{ id: params.id },
|
|
330
280
|
options
|
|
331
281
|
);
|
|
332
282
|
},
|
|
333
|
-
quote(params, options) {
|
|
334
|
-
|
|
283
|
+
async quote(params, options) {
|
|
284
|
+
await lifecycle.ensureVisitorSession();
|
|
335
285
|
return apiConfig.httpClient.post(
|
|
336
|
-
`${base
|
|
337
|
-
{ id: params.id
|
|
286
|
+
`${base}/carts/${params.id}/quote`,
|
|
287
|
+
{ id: params.id },
|
|
338
288
|
options
|
|
339
289
|
);
|
|
340
290
|
},
|
|
341
|
-
checkout(params, options) {
|
|
342
|
-
|
|
291
|
+
async checkout(params, options) {
|
|
292
|
+
await lifecycle.ensureVisitorSession();
|
|
343
293
|
return apiConfig.httpClient.post(
|
|
344
|
-
`${base
|
|
294
|
+
`${base}/carts/${params.id}/checkout`,
|
|
345
295
|
{
|
|
346
296
|
id: params.id,
|
|
347
|
-
store_id,
|
|
348
297
|
payment_method_key: params.payment_method_key,
|
|
349
298
|
confirmation_token_id: params.confirmation_token_id,
|
|
350
299
|
return_url: params.return_url
|
|
@@ -354,115 +303,85 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
354
303
|
}
|
|
355
304
|
},
|
|
356
305
|
order: {
|
|
357
|
-
get(params, options) {
|
|
358
|
-
|
|
306
|
+
async get(params, options) {
|
|
307
|
+
await lifecycle.ensureVisitorSession();
|
|
359
308
|
return apiConfig.httpClient.get(
|
|
360
|
-
`${base
|
|
309
|
+
`${base}/orders/${params.id}`,
|
|
361
310
|
options
|
|
362
311
|
);
|
|
363
312
|
},
|
|
364
|
-
find(params, options) {
|
|
365
|
-
|
|
313
|
+
async find(params, options) {
|
|
314
|
+
await lifecycle.ensureVisitorSession();
|
|
366
315
|
return apiConfig.httpClient.get(
|
|
367
|
-
`${base
|
|
368
|
-
{
|
|
369
|
-
...options,
|
|
370
|
-
params: queryParams
|
|
371
|
-
}
|
|
316
|
+
`${base}/orders`,
|
|
317
|
+
{ ...options, params }
|
|
372
318
|
);
|
|
373
319
|
},
|
|
374
|
-
downloadDigitalAccess(params, options) {
|
|
375
|
-
|
|
320
|
+
async downloadDigitalAccess(params, options) {
|
|
321
|
+
await lifecycle.ensureVisitorSession();
|
|
376
322
|
return apiConfig.httpClient.post(
|
|
377
|
-
`${base
|
|
323
|
+
`${base}/orders/${params.order_id}/digital-access/${params.grant_id}/download`,
|
|
378
324
|
{},
|
|
379
325
|
options
|
|
380
326
|
);
|
|
381
327
|
},
|
|
382
|
-
findDigitalAccess(params, options) {
|
|
383
|
-
|
|
328
|
+
async findDigitalAccess(params, options) {
|
|
329
|
+
await lifecycle.ensureVisitorSession();
|
|
330
|
+
const { order_id, ...queryParams } = params;
|
|
384
331
|
return apiConfig.httpClient.get(
|
|
385
|
-
`${base
|
|
332
|
+
`${base}/orders/${order_id}/digital-access`,
|
|
386
333
|
{ ...options, params: queryParams }
|
|
387
334
|
);
|
|
388
335
|
},
|
|
389
|
-
getDigitalAccess(params, options) {
|
|
390
|
-
|
|
336
|
+
async getDigitalAccess(params, options) {
|
|
337
|
+
await lifecycle.ensureVisitorSession();
|
|
391
338
|
return apiConfig.httpClient.get(
|
|
392
|
-
`${base
|
|
339
|
+
`${base}/orders/${params.order_id}/digital-access/${params.grant_id}`,
|
|
393
340
|
options
|
|
394
341
|
);
|
|
395
342
|
}
|
|
396
343
|
},
|
|
397
344
|
service: {
|
|
398
345
|
get(params, options) {
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
if (params.id) {
|
|
402
|
-
identifier = params.id;
|
|
403
|
-
} else if (params.slug) {
|
|
404
|
-
identifier = `${store_id}:${apiConfig.locale}:${params.slug}`;
|
|
405
|
-
} else {
|
|
406
|
-
throw new Error("GetServiceParams requires id or slug");
|
|
407
|
-
}
|
|
346
|
+
const identifier = params.id ?? params.slug;
|
|
347
|
+
if (!identifier) throw new Error("GetServiceParams requires id or slug");
|
|
408
348
|
return apiConfig.httpClient.get(
|
|
409
|
-
`${base
|
|
349
|
+
`${base}/services/${identifier}`,
|
|
410
350
|
options
|
|
411
351
|
);
|
|
412
352
|
},
|
|
413
353
|
find(params, options) {
|
|
414
|
-
const { store_id, ...queryParams } = params;
|
|
415
354
|
return apiConfig.httpClient.get(
|
|
416
|
-
`${base
|
|
417
|
-
{
|
|
418
|
-
...options,
|
|
419
|
-
params: queryParams
|
|
420
|
-
}
|
|
355
|
+
`${base}/services`,
|
|
356
|
+
{ ...options, params }
|
|
421
357
|
);
|
|
422
358
|
},
|
|
423
359
|
findProviders(params, options) {
|
|
424
|
-
const { store_id, ...queryParams } = params;
|
|
425
360
|
return apiConfig.httpClient.get(
|
|
426
|
-
`${base
|
|
427
|
-
{
|
|
428
|
-
...options,
|
|
429
|
-
params: queryParams
|
|
430
|
-
}
|
|
361
|
+
`${base}/service-providers`,
|
|
362
|
+
{ ...options, params }
|
|
431
363
|
);
|
|
432
364
|
},
|
|
433
365
|
getAvailability(params, options) {
|
|
434
|
-
const { store_id, ...queryParams } = params;
|
|
435
|
-
const target_store_id = store_id || apiConfig.storeId;
|
|
436
366
|
return apiConfig.httpClient.get(
|
|
437
|
-
`${base
|
|
438
|
-
{ ...options, params
|
|
367
|
+
`${base}/services/availability`,
|
|
368
|
+
{ ...options, params }
|
|
439
369
|
);
|
|
440
370
|
}
|
|
441
371
|
},
|
|
442
372
|
provider: {
|
|
443
373
|
get(params, options) {
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
if (params.id) {
|
|
447
|
-
identifier = params.id;
|
|
448
|
-
} else if (params.slug) {
|
|
449
|
-
identifier = `${store_id}:${apiConfig.locale}:${params.slug}`;
|
|
450
|
-
} else {
|
|
451
|
-
throw new Error("GetProviderParams requires id or slug");
|
|
452
|
-
}
|
|
374
|
+
const identifier = params.id ?? params.slug;
|
|
375
|
+
if (!identifier) throw new Error("GetProviderParams requires id or slug");
|
|
453
376
|
return apiConfig.httpClient.get(
|
|
454
|
-
`${base
|
|
377
|
+
`${base}/providers/${identifier}`,
|
|
455
378
|
options
|
|
456
379
|
);
|
|
457
380
|
},
|
|
458
381
|
find(params, options) {
|
|
459
|
-
const { store_id, ...queryParams } = params;
|
|
460
382
|
return apiConfig.httpClient.get(
|
|
461
|
-
`${base
|
|
462
|
-
{
|
|
463
|
-
...options,
|
|
464
|
-
params: queryParams
|
|
465
|
-
}
|
|
383
|
+
`${base}/providers`,
|
|
384
|
+
{ ...options, params }
|
|
466
385
|
);
|
|
467
386
|
}
|
|
468
387
|
}
|
|
@@ -476,36 +395,23 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
476
395
|
return submitIdentification("code", params, options);
|
|
477
396
|
},
|
|
478
397
|
async verify(params, options) {
|
|
479
|
-
const store_id = apiConfig.storeId;
|
|
480
398
|
const result = await apiConfig.httpClient.post(
|
|
481
|
-
`${base
|
|
482
|
-
|
|
399
|
+
`${base}/account/verify`,
|
|
400
|
+
params,
|
|
483
401
|
options
|
|
484
402
|
);
|
|
485
|
-
if (result
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
...prev,
|
|
491
|
-
access_token: result.token.token,
|
|
492
|
-
contact: result.contact
|
|
493
|
-
} : {
|
|
494
|
-
access_token: result.token.token,
|
|
495
|
-
contact: result.contact,
|
|
496
|
-
store: identifiedStore,
|
|
497
|
-
market: pending?.market || null
|
|
498
|
-
}
|
|
499
|
-
);
|
|
500
|
-
pendingVerifications.delete(params.challenge_id);
|
|
403
|
+
if (result.token?.token) {
|
|
404
|
+
updateContactSession(() => ({
|
|
405
|
+
sessionToken: result.token.token,
|
|
406
|
+
contact: result.contact
|
|
407
|
+
}));
|
|
501
408
|
}
|
|
502
409
|
return result;
|
|
503
410
|
},
|
|
504
411
|
async logout(options) {
|
|
505
|
-
const store_id = apiConfig.storeId;
|
|
506
412
|
try {
|
|
507
413
|
await apiConfig.httpClient.post(
|
|
508
|
-
`${base
|
|
414
|
+
`${base}/account/logout`,
|
|
509
415
|
{},
|
|
510
416
|
options
|
|
511
417
|
);
|
|
@@ -515,110 +421,74 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
515
421
|
},
|
|
516
422
|
getMe(options) {
|
|
517
423
|
return apiConfig.httpClient.get(
|
|
518
|
-
`${base
|
|
424
|
+
`${base}/account/me`,
|
|
519
425
|
options
|
|
520
426
|
);
|
|
521
427
|
}
|
|
522
428
|
},
|
|
523
429
|
contactList: {
|
|
524
430
|
get(params, options) {
|
|
525
|
-
const store_id = params.store_id || apiConfig.storeId;
|
|
526
431
|
return apiConfig.httpClient.get(
|
|
527
|
-
`${base
|
|
432
|
+
`${base}/contact-lists/${params.id}`,
|
|
528
433
|
options
|
|
529
434
|
);
|
|
530
435
|
},
|
|
531
|
-
find(params, options) {
|
|
532
|
-
const { store_id, ...queryParams } = params || {};
|
|
436
|
+
find(params = {}, options) {
|
|
533
437
|
return apiConfig.httpClient.get(
|
|
534
|
-
`${base
|
|
535
|
-
{
|
|
536
|
-
...options,
|
|
537
|
-
params: queryParams
|
|
538
|
-
}
|
|
438
|
+
`${base}/contact-lists`,
|
|
439
|
+
{ ...options, params }
|
|
539
440
|
);
|
|
540
441
|
},
|
|
541
442
|
plans: {
|
|
542
443
|
find(params, options) {
|
|
543
|
-
const {
|
|
444
|
+
const { contact_list_id, ...queryParams } = params;
|
|
544
445
|
return apiConfig.httpClient.get(
|
|
545
|
-
`${base
|
|
446
|
+
`${base}/contact-lists/${contact_list_id}/plans`,
|
|
546
447
|
{ ...options, params: queryParams }
|
|
547
448
|
);
|
|
548
449
|
}
|
|
549
450
|
},
|
|
550
451
|
memberships: {
|
|
551
|
-
find(params, options) {
|
|
552
|
-
|
|
553
|
-
return apiConfig.httpClient.get(`${base
|
|
452
|
+
async find(params = {}, options) {
|
|
453
|
+
await lifecycle.ensureVisitorSession();
|
|
454
|
+
return apiConfig.httpClient.get(`${base}/contact-lists/memberships`, {
|
|
554
455
|
...options,
|
|
555
|
-
params
|
|
456
|
+
params
|
|
556
457
|
});
|
|
557
458
|
}
|
|
558
459
|
},
|
|
559
|
-
subscribe(params, options) {
|
|
560
|
-
|
|
460
|
+
async subscribe(params, options) {
|
|
461
|
+
await lifecycle.ensureVisitorSession();
|
|
462
|
+
const { id, ...payload } = params;
|
|
561
463
|
return apiConfig.httpClient.post(
|
|
562
|
-
`${base
|
|
464
|
+
`${base}/contact-lists/${id}/subscribe`,
|
|
563
465
|
payload,
|
|
564
466
|
options
|
|
565
467
|
);
|
|
566
468
|
},
|
|
567
|
-
checkAccess(params, options) {
|
|
568
|
-
|
|
469
|
+
async checkAccess(params, options) {
|
|
470
|
+
await lifecycle.ensureVisitorSession();
|
|
569
471
|
return apiConfig.httpClient.get(
|
|
570
|
-
`${base
|
|
571
|
-
options
|
|
572
|
-
);
|
|
573
|
-
},
|
|
574
|
-
checkContentAccess(params, options) {
|
|
575
|
-
const { store_id, ...payload } = params;
|
|
576
|
-
return apiConfig.httpClient.post(
|
|
577
|
-
`${base(store_id)}/contact-lists/access`,
|
|
578
|
-
payload,
|
|
579
|
-
options
|
|
580
|
-
);
|
|
581
|
-
},
|
|
582
|
-
manage(token, options) {
|
|
583
|
-
return apiConfig.httpClient.post(
|
|
584
|
-
`${base()}/contact-lists/manage`,
|
|
585
|
-
{ token },
|
|
472
|
+
`${base}/contact-lists/${params.id}/access`,
|
|
586
473
|
options
|
|
587
474
|
);
|
|
588
475
|
},
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
for (const name of Object.keys(headers)) {
|
|
592
|
-
if (name.toLowerCase() === "content-type") delete headers[name];
|
|
593
|
-
}
|
|
594
|
-
return apiConfig.httpClient.post(
|
|
595
|
-
`${base()}/contact-lists/unsubscribe`,
|
|
596
|
-
new URLSearchParams({ "List-Unsubscribe": "One-Click" }),
|
|
597
|
-
{
|
|
598
|
-
...options,
|
|
599
|
-
params: { ...options?.params || {}, token },
|
|
600
|
-
headers: {
|
|
601
|
-
...headers,
|
|
602
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
);
|
|
606
|
-
},
|
|
607
|
-
confirm(token, options) {
|
|
476
|
+
async checkContentAccess(params, options) {
|
|
477
|
+
await lifecycle.ensureVisitorSession();
|
|
608
478
|
return apiConfig.httpClient.post(
|
|
609
|
-
`${base
|
|
610
|
-
|
|
479
|
+
`${base}/contact-lists/access`,
|
|
480
|
+
params,
|
|
611
481
|
options
|
|
612
482
|
);
|
|
613
483
|
}
|
|
614
484
|
}
|
|
615
485
|
},
|
|
616
|
-
action: createActionApi(apiConfig),
|
|
486
|
+
action: createActionApi(apiConfig, lifecycle),
|
|
617
487
|
experiments: {
|
|
618
|
-
use(params, options) {
|
|
619
|
-
|
|
488
|
+
async use(params, options) {
|
|
489
|
+
await lifecycle.ensureVisitorSession();
|
|
620
490
|
return apiConfig.httpClient.post(
|
|
621
|
-
`${base
|
|
491
|
+
`${base}/experiments/use`,
|
|
622
492
|
{ key: params.key },
|
|
623
493
|
options
|
|
624
494
|
);
|
|
@@ -778,6 +648,15 @@ function requestError(name, message, details = {}) {
|
|
|
778
648
|
function isRecord(value) {
|
|
779
649
|
return typeof value === "object" && value !== null;
|
|
780
650
|
}
|
|
651
|
+
function omitStorefrontRouting(value) {
|
|
652
|
+
if (!isRecord(value) || Array.isArray(value)) return value;
|
|
653
|
+
const prototype = Object.getPrototypeOf(value);
|
|
654
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
655
|
+
const result = { ...value };
|
|
656
|
+
delete result.store_id;
|
|
657
|
+
delete result.market;
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
781
660
|
function isTokenSet(value) {
|
|
782
661
|
return isRecord(value) && typeof value.access_token === "string" && (value.refresh_token === void 0 || typeof value.refresh_token === "string") && (value.access_expires_at === void 0 || typeof value.access_expires_at === "number");
|
|
783
662
|
}
|
|
@@ -853,10 +732,25 @@ function createHttpClient(cfg) {
|
|
|
853
732
|
if (!retried && options?.transformRequest) {
|
|
854
733
|
body = options.transformRequest(body);
|
|
855
734
|
}
|
|
735
|
+
if (cfg.storefrontMode) {
|
|
736
|
+
body = omitStorefrontRouting(body);
|
|
737
|
+
}
|
|
738
|
+
const forcedHeaders = typeof cfg.forcedHeaders === "function" ? cfg.forcedHeaders() : cfg.forcedHeaders || {};
|
|
739
|
+
const callerHeaders = { ...options?.headers || {} };
|
|
740
|
+
const protectedHeaderNames = new Set(
|
|
741
|
+
Object.keys(forcedHeaders).map((name) => name.toLowerCase())
|
|
742
|
+
);
|
|
743
|
+
if (cfg.storefrontMode) protectedHeaderNames.add("authorization");
|
|
744
|
+
for (const name of Object.keys(callerHeaders)) {
|
|
745
|
+
if (protectedHeaderNames.has(name.toLowerCase())) {
|
|
746
|
+
delete callerHeaders[name];
|
|
747
|
+
}
|
|
748
|
+
}
|
|
856
749
|
const headers = {
|
|
857
750
|
Accept: "application/json",
|
|
858
751
|
"Content-Type": "application/json",
|
|
859
|
-
...
|
|
752
|
+
...callerHeaders,
|
|
753
|
+
...forcedHeaders
|
|
860
754
|
};
|
|
861
755
|
let tokens = authStorage.getTokens();
|
|
862
756
|
const nowSec = Date.now() / 1e3;
|
|
@@ -867,7 +761,8 @@ function createHttpClient(cfg) {
|
|
|
867
761
|
if (tokens?.access_token) {
|
|
868
762
|
headers["Authorization"] = `Bearer ${tokens.access_token}`;
|
|
869
763
|
}
|
|
870
|
-
const
|
|
764
|
+
const requestParams = cfg.storefrontMode ? omitStorefrontRouting(options?.params) : options?.params;
|
|
765
|
+
const finalPath = requestParams ? path + buildQueryString(requestParams) : path;
|
|
871
766
|
const fetchOptions = {
|
|
872
767
|
method,
|
|
873
768
|
headers,
|
|
@@ -898,8 +793,19 @@ function createHttpClient(cfg) {
|
|
|
898
793
|
throw err;
|
|
899
794
|
}
|
|
900
795
|
if (res.status === 401 && !retried) {
|
|
901
|
-
|
|
902
|
-
|
|
796
|
+
if (cfg.onUnauthorized) {
|
|
797
|
+
const recovered = await cfg.onUnauthorized({
|
|
798
|
+
hadAuthorization: Boolean(tokens?.access_token),
|
|
799
|
+
authorizationToken: tokens?.access_token || null,
|
|
800
|
+
path
|
|
801
|
+
});
|
|
802
|
+
if (recovered) {
|
|
803
|
+
return request(method, path, body, options, true);
|
|
804
|
+
}
|
|
805
|
+
} else {
|
|
806
|
+
await ensureFreshToken();
|
|
807
|
+
return request(method, path, body, options, true);
|
|
808
|
+
}
|
|
903
809
|
}
|
|
904
810
|
try {
|
|
905
811
|
const contentLength = res.headers.get("content-length");
|
|
@@ -1115,6 +1021,14 @@ var createStoreApi = (apiConfig, _updateSession) => {
|
|
|
1115
1021
|
params
|
|
1116
1022
|
});
|
|
1117
1023
|
},
|
|
1024
|
+
async regeneratePublishableKey(params = {}, options) {
|
|
1025
|
+
const store_id = params.store_id || apiConfig.storeId;
|
|
1026
|
+
return apiConfig.httpClient.post(
|
|
1027
|
+
`/v1/stores/${store_id}/publishable-key/regenerate`,
|
|
1028
|
+
{},
|
|
1029
|
+
options
|
|
1030
|
+
);
|
|
1031
|
+
},
|
|
1118
1032
|
async getSubscriptionPlans(_params, options) {
|
|
1119
1033
|
return apiConfig.httpClient.get("/v1/stores/plans", options);
|
|
1120
1034
|
},
|
|
@@ -2013,7 +1927,10 @@ var createMarketApi = (apiConfig) => {
|
|
|
2013
1927
|
async delete(params, options) {
|
|
2014
1928
|
return apiConfig.httpClient.delete(
|
|
2015
1929
|
`/v1/stores/${apiConfig.storeId}/markets/${params.id}`,
|
|
2016
|
-
|
|
1930
|
+
{
|
|
1931
|
+
...options,
|
|
1932
|
+
params: params.replacement_default_market_id ? { replacement_default_market_id: params.replacement_default_market_id } : options?.params
|
|
1933
|
+
}
|
|
2017
1934
|
);
|
|
2018
1935
|
}
|
|
2019
1936
|
};
|
|
@@ -2639,32 +2556,38 @@ function storefrontSupportOptions(supportToken, options) {
|
|
|
2639
2556
|
}
|
|
2640
2557
|
};
|
|
2641
2558
|
}
|
|
2642
|
-
function createStorefrontSupportApi(config) {
|
|
2559
|
+
function createStorefrontSupportApi(config, ensureVisitorSession) {
|
|
2643
2560
|
const { httpClient } = config;
|
|
2644
2561
|
return {
|
|
2645
2562
|
async startConversation(params = {}, opts) {
|
|
2646
|
-
|
|
2563
|
+
await ensureVisitorSession();
|
|
2647
2564
|
return httpClient.post(
|
|
2648
|
-
|
|
2649
|
-
|
|
2565
|
+
"/v1/storefront/support/conversations",
|
|
2566
|
+
params,
|
|
2650
2567
|
opts
|
|
2651
2568
|
);
|
|
2652
2569
|
},
|
|
2653
2570
|
async sendMessage(params, opts) {
|
|
2571
|
+
await ensureVisitorSession();
|
|
2654
2572
|
const { support_token, ...request } = params;
|
|
2655
|
-
const storeId = config.storeId;
|
|
2656
2573
|
return httpClient.post(
|
|
2657
|
-
`/v1/storefront
|
|
2658
|
-
|
|
2574
|
+
`/v1/storefront/support/conversations/${request.conversation_id}/messages`,
|
|
2575
|
+
request,
|
|
2659
2576
|
storefrontSupportOptions(support_token, opts)
|
|
2660
2577
|
);
|
|
2661
2578
|
},
|
|
2662
2579
|
async getConversation(params, opts) {
|
|
2580
|
+
await ensureVisitorSession();
|
|
2663
2581
|
const { support_token, ...request } = params;
|
|
2664
|
-
const
|
|
2665
|
-
|
|
2582
|
+
const query = new URLSearchParams();
|
|
2583
|
+
if (request.message_limit) query.set("message_limit", String(request.message_limit));
|
|
2584
|
+
if (typeof request.after_created_at === "number") {
|
|
2585
|
+
query.set("after_created_at", String(request.after_created_at));
|
|
2586
|
+
}
|
|
2587
|
+
if (request.after_id) query.set("after_id", request.after_id);
|
|
2588
|
+
const suffix = query.size ? `?${query}` : "";
|
|
2666
2589
|
return httpClient.get(
|
|
2667
|
-
`/v1/storefront
|
|
2590
|
+
`/v1/storefront/support/conversations/${request.conversation_id}${suffix}`,
|
|
2668
2591
|
storefrontSupportOptions(support_token, opts)
|
|
2669
2592
|
);
|
|
2670
2593
|
}
|
|
@@ -4115,138 +4038,1929 @@ function getFirstAvailableFCId(variant, quantity = 1) {
|
|
|
4115
4038
|
return inv?.location_id;
|
|
4116
4039
|
}
|
|
4117
4040
|
|
|
4118
|
-
// src/
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
"react",
|
|
4123
|
-
"vue",
|
|
4124
|
-
"svelte",
|
|
4125
|
-
"vanilla"
|
|
4126
|
-
];
|
|
4127
|
-
function createUtilitySurface(apiConfig) {
|
|
4128
|
-
return {
|
|
4129
|
-
getImageUrl: (imageBlock, isBlock2 = true) => getImageUrl(imageBlock, isBlock2),
|
|
4130
|
-
getBlockValue,
|
|
4131
|
-
getBlockTextValue,
|
|
4132
|
-
getBlockContentValue,
|
|
4133
|
-
getBlockValues,
|
|
4134
|
-
getBlockLabel,
|
|
4135
|
-
getBlockObjectValues,
|
|
4136
|
-
getBlockFromArray,
|
|
4137
|
-
formatBlockValue,
|
|
4138
|
-
prepareBlocksForSubmission,
|
|
4139
|
-
extractBlockValues,
|
|
4140
|
-
formatPrice: (prices) => formatPrice(prices, apiConfig.market),
|
|
4141
|
-
getPriceAmount: (prices) => getPriceAmount(prices, apiConfig.market),
|
|
4142
|
-
formatPayment,
|
|
4143
|
-
formatMinor,
|
|
4144
|
-
getCurrencySymbol,
|
|
4145
|
-
getCurrencyName,
|
|
4146
|
-
validatePhoneNumber,
|
|
4147
|
-
tzGroups,
|
|
4148
|
-
findTimeZone,
|
|
4149
|
-
slugify,
|
|
4150
|
-
humanize,
|
|
4151
|
-
categorify,
|
|
4152
|
-
formatDate,
|
|
4153
|
-
getSvgContentForAstro,
|
|
4154
|
-
fetchSvgContent,
|
|
4155
|
-
injectSvgIntoElement,
|
|
4156
|
-
isValidKey,
|
|
4157
|
-
validateKey,
|
|
4158
|
-
toKey,
|
|
4159
|
-
nameToKey,
|
|
4160
|
-
getAvailableStock,
|
|
4161
|
-
getReservedStock,
|
|
4162
|
-
hasStock,
|
|
4163
|
-
getInventoryAt,
|
|
4164
|
-
getFirstAvailableFCId
|
|
4165
|
-
};
|
|
4166
|
-
}
|
|
4167
|
-
var ADMIN_STORAGE_KEY = "arky_admin_session";
|
|
4168
|
-
function readAdminSession() {
|
|
4169
|
-
if (typeof window === "undefined") return null;
|
|
4170
|
-
try {
|
|
4171
|
-
const raw = localStorage.getItem(ADMIN_STORAGE_KEY);
|
|
4172
|
-
return raw ? JSON.parse(raw) : null;
|
|
4173
|
-
} catch {
|
|
4174
|
-
return null;
|
|
4175
|
-
}
|
|
4041
|
+
// src/payments/stripe.ts
|
|
4042
|
+
async function defaultStripeLoader(publishableKey, options) {
|
|
4043
|
+
const { loadStripe } = await import('@stripe/stripe-js');
|
|
4044
|
+
return loadStripe(publishableKey, options);
|
|
4176
4045
|
}
|
|
4177
|
-
function
|
|
4178
|
-
|
|
4179
|
-
if (s) {
|
|
4180
|
-
localStorage.setItem(ADMIN_STORAGE_KEY, JSON.stringify(s));
|
|
4181
|
-
} else {
|
|
4182
|
-
localStorage.removeItem(ADMIN_STORAGE_KEY);
|
|
4183
|
-
}
|
|
4046
|
+
function normalizeCurrency(currency) {
|
|
4047
|
+
return currency.trim().toLowerCase();
|
|
4184
4048
|
}
|
|
4185
|
-
function
|
|
4186
|
-
const
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4049
|
+
async function createStripeConfirmationTokenController(config, stripeLoader = defaultStripeLoader) {
|
|
4050
|
+
const stripe = await stripeLoader(
|
|
4051
|
+
config.publishableKey,
|
|
4052
|
+
config.connectedAccountId ? { stripeAccount: config.connectedAccountId } : void 0
|
|
4053
|
+
);
|
|
4054
|
+
if (!stripe) throw new Error("Stripe failed to initialize");
|
|
4055
|
+
let elements = createElements(stripe, config);
|
|
4056
|
+
let paymentElement = elements.create("payment", {
|
|
4057
|
+
layout: "tabs"
|
|
4058
|
+
});
|
|
4059
|
+
return {
|
|
4060
|
+
mount(target) {
|
|
4061
|
+
if (!paymentElement) {
|
|
4062
|
+
paymentElement = elements.create("payment", { layout: "tabs" });
|
|
4063
|
+
}
|
|
4064
|
+
paymentElement.mount(target);
|
|
4065
|
+
},
|
|
4066
|
+
update(input) {
|
|
4067
|
+
elements.update({
|
|
4068
|
+
...input.amount !== void 0 ? { amount: input.amount } : {},
|
|
4069
|
+
...input.currency ? { currency: normalizeCurrency(input.currency) } : {}
|
|
4195
4070
|
});
|
|
4196
|
-
}
|
|
4197
|
-
}
|
|
4198
|
-
const updateSession = (updater) => {
|
|
4199
|
-
if (config.apiToken) return;
|
|
4200
|
-
const prev = readAdminSession();
|
|
4201
|
-
const next = updater(prev);
|
|
4202
|
-
writeAdminSession(next);
|
|
4203
|
-
emit();
|
|
4204
|
-
};
|
|
4205
|
-
const authStorage = config.apiToken ? {
|
|
4206
|
-
getTokens: () => ({ access_token: config.apiToken }),
|
|
4207
|
-
onTokensRefreshed: () => {
|
|
4208
4071
|
},
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4072
|
+
async createConfirmationToken(options = {}) {
|
|
4073
|
+
const submitResult = await elements.submit();
|
|
4074
|
+
if (submitResult.error) {
|
|
4075
|
+
throw new Error(submitResult.error.message || "Payment details are incomplete");
|
|
4076
|
+
}
|
|
4077
|
+
const tokenInput = {
|
|
4078
|
+
elements,
|
|
4079
|
+
params: {
|
|
4080
|
+
...options.return_url ? { return_url: options.return_url } : {},
|
|
4081
|
+
...options.billing_details ? { payment_method_data: { billing_details: options.billing_details } } : {}
|
|
4082
|
+
}
|
|
4083
|
+
};
|
|
4084
|
+
const result = await stripe.createConfirmationToken(tokenInput);
|
|
4085
|
+
if (result.error) {
|
|
4086
|
+
throw new Error(result.error.message || "Payment confirmation token failed");
|
|
4087
|
+
}
|
|
4088
|
+
if (!result.confirmationToken?.id) {
|
|
4089
|
+
throw new Error("Stripe did not return a confirmation token");
|
|
4090
|
+
}
|
|
4215
4091
|
return {
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
access_expires_at: s.access_expires_at
|
|
4092
|
+
confirmation_token_id: result.confirmationToken.id,
|
|
4093
|
+
return_url: options.return_url
|
|
4219
4094
|
};
|
|
4220
4095
|
},
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
refresh_token: tokens.refresh_token ?? prev.refresh_token,
|
|
4227
|
-
access_expires_at: tokens.access_expires_at ?? prev.access_expires_at
|
|
4228
|
-
} : null
|
|
4229
|
-
);
|
|
4096
|
+
async handleNextAction(clientSecret) {
|
|
4097
|
+
const result = await stripe.handleNextAction({ clientSecret });
|
|
4098
|
+
if (result.error) {
|
|
4099
|
+
throw new Error(result.error.message || "Payment authentication failed");
|
|
4100
|
+
}
|
|
4230
4101
|
},
|
|
4231
|
-
|
|
4232
|
-
|
|
4102
|
+
destroy() {
|
|
4103
|
+
paymentElement?.destroy();
|
|
4104
|
+
paymentElement = null;
|
|
4233
4105
|
}
|
|
4234
4106
|
};
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4107
|
+
}
|
|
4108
|
+
function createElements(stripe, config) {
|
|
4109
|
+
return stripe.elements({
|
|
4110
|
+
mode: "payment",
|
|
4111
|
+
amount: config.amount,
|
|
4112
|
+
currency: normalizeCurrency(config.currency),
|
|
4113
|
+
paymentMethodCreation: "manual",
|
|
4114
|
+
...config.appearance ? { appearance: config.appearance } : {},
|
|
4115
|
+
...config.setupFutureUsage ? { setupFutureUsage: config.setupFutureUsage } : {}
|
|
4242
4116
|
});
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
// src/storefrontStore/utils.ts
|
|
4120
|
+
function readErrorMessage(error, fallback) {
|
|
4121
|
+
if (error instanceof Error && error.message) return error.message;
|
|
4122
|
+
if (typeof error === "string" && error.length > 0) return error;
|
|
4123
|
+
return fallback;
|
|
4124
|
+
}
|
|
4125
|
+
function createId(prefix) {
|
|
4126
|
+
const cryptoValue = globalThis.crypto;
|
|
4127
|
+
if (cryptoValue && "randomUUID" in cryptoValue) return cryptoValue.randomUUID();
|
|
4128
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
4129
|
+
}
|
|
4130
|
+
function firstLocalized(value, locale) {
|
|
4131
|
+
if (typeof value === "string") return value;
|
|
4132
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return "";
|
|
4133
|
+
const record = value;
|
|
4134
|
+
const localeValue = record[locale];
|
|
4135
|
+
if (typeof localeValue === "string") return localeValue;
|
|
4136
|
+
const englishValue = record.en;
|
|
4137
|
+
if (typeof englishValue === "string") return englishValue;
|
|
4138
|
+
for (const entry of Object.values(record)) {
|
|
4139
|
+
if (typeof entry === "string") return entry;
|
|
4140
|
+
}
|
|
4141
|
+
return "";
|
|
4142
|
+
}
|
|
4143
|
+
function findBlock2(blocks, keys) {
|
|
4144
|
+
return (blocks || []).find((block) => keys.includes(block.key)) || null;
|
|
4145
|
+
}
|
|
4146
|
+
function blockText(blocks, keys, locale) {
|
|
4147
|
+
const block = findBlock2(blocks, keys);
|
|
4148
|
+
if (!block) return "";
|
|
4149
|
+
return firstLocalized(block.value, locale);
|
|
4150
|
+
}
|
|
4151
|
+
function productName(product, locale) {
|
|
4152
|
+
return blockText(product.blocks, ["name", "title"], locale) || product.key || product.id;
|
|
4153
|
+
}
|
|
4154
|
+
function serviceName(service, locale) {
|
|
4155
|
+
return blockText(service.blocks, ["name", "title"], locale) || service.key || service.id;
|
|
4156
|
+
}
|
|
4157
|
+
function providerName(provider, locale) {
|
|
4158
|
+
return blockText(provider.blocks, ["name", "title"], locale) || provider.key || provider.id;
|
|
4159
|
+
}
|
|
4160
|
+
function entitySlug(entity, locale) {
|
|
4161
|
+
return entity.slug?.[locale] || entity.slug?.en || Object.values(entity.slug || {})[0] || entity.id;
|
|
4162
|
+
}
|
|
4163
|
+
function priceForMarket(prices, market, marketCurrency) {
|
|
4164
|
+
const marketKey = market.trim();
|
|
4165
|
+
if (!marketKey) throw new Error("A market is required to select a product price");
|
|
4166
|
+
const currency = marketCurrency?.trim().toUpperCase();
|
|
4167
|
+
if (!currency) throw new Error(`Market ${marketKey} does not have an authoritative currency`);
|
|
4168
|
+
const marketPrices = prices.filter((candidate) => candidate.market === marketKey);
|
|
4169
|
+
if (marketPrices.length === 0) {
|
|
4170
|
+
throw new Error(`Product is not priced for market ${marketKey}`);
|
|
4171
|
+
}
|
|
4172
|
+
if (marketPrices.some(
|
|
4173
|
+
(candidate) => !Number.isSafeInteger(candidate.amount) || candidate.amount < 0 || candidate.currency.trim().toUpperCase() !== currency
|
|
4174
|
+
)) {
|
|
4175
|
+
throw new Error(`Product has an invalid price for market ${marketKey}`);
|
|
4176
|
+
}
|
|
4177
|
+
const authorizedPrices = marketPrices.filter((candidate) => candidate.contact_list_id);
|
|
4178
|
+
if (authorizedPrices.length > 0) {
|
|
4179
|
+
return authorizedPrices.reduce(
|
|
4180
|
+
(lowest, candidate) => candidate.amount < lowest.amount ? candidate : lowest
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
const basePrices = marketPrices.filter((candidate) => !candidate.contact_list_id);
|
|
4184
|
+
if (basePrices.length !== 1) {
|
|
4185
|
+
throw new Error(`Product does not have one base price for market ${marketKey}`);
|
|
4186
|
+
}
|
|
4187
|
+
const price = basePrices[0];
|
|
4188
|
+
return price;
|
|
4189
|
+
}
|
|
4190
|
+
function availableStock(client, variant) {
|
|
4191
|
+
const fromUtility = client.utils.getAvailableStock(variant);
|
|
4192
|
+
if (Number.isFinite(fromUtility)) return fromUtility;
|
|
4193
|
+
const stock = (variant.inventory || []).reduce((total, row) => total + (row.available || 0), 0);
|
|
4194
|
+
return stock > 0 ? stock : void 0;
|
|
4195
|
+
}
|
|
4196
|
+
function locationToAddress(location) {
|
|
4197
|
+
return {
|
|
4198
|
+
country: location.country || "",
|
|
4199
|
+
state: location.state || "",
|
|
4200
|
+
city: location.city || "",
|
|
4201
|
+
postal_code: location.postal_code || "",
|
|
4202
|
+
name: "",
|
|
4203
|
+
street1: "",
|
|
4204
|
+
street2: null
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
4207
|
+
function createFormEntry(formId, fields) {
|
|
4208
|
+
if (!formId.trim()) throw new Error("formId is required");
|
|
4209
|
+
return { form_id: formId, fields };
|
|
4210
|
+
}
|
|
4211
|
+
function toProductCheckoutItems(items) {
|
|
4212
|
+
return items.map((item) => ({
|
|
4213
|
+
type: "product",
|
|
4214
|
+
id: item.id,
|
|
4215
|
+
product_id: item.product_id,
|
|
4216
|
+
variant_id: item.variant_id,
|
|
4217
|
+
quantity: item.quantity
|
|
4218
|
+
}));
|
|
4219
|
+
}
|
|
4220
|
+
function toServiceCheckoutItems(items) {
|
|
4221
|
+
return items.map((item) => ({
|
|
4222
|
+
type: "service",
|
|
4223
|
+
id: item.id,
|
|
4224
|
+
service_id: item.service_id,
|
|
4225
|
+
provider_id: item.provider_id,
|
|
4226
|
+
slots: [...item.slots].sort((a, b) => a.from - b.from),
|
|
4227
|
+
forms: item.forms
|
|
4228
|
+
}));
|
|
4229
|
+
}
|
|
4230
|
+
function formValueError(field, message) {
|
|
4231
|
+
return new Error(`Invalid value for form field '${field.key}': ${message}`);
|
|
4232
|
+
}
|
|
4233
|
+
function isValidGeoLocation(value) {
|
|
4234
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4235
|
+
const location = value;
|
|
4236
|
+
if (location.label !== void 0 && location.label !== null && typeof location.label !== "string") {
|
|
4237
|
+
return false;
|
|
4238
|
+
}
|
|
4239
|
+
const coordinates = location.coordinates;
|
|
4240
|
+
if (!coordinates || typeof coordinates !== "object") return false;
|
|
4241
|
+
return Number.isFinite(coordinates.lat) && Number.isFinite(coordinates.lon) && coordinates.lat >= -90 && coordinates.lat <= 90 && coordinates.lon >= -180 && coordinates.lon <= 180;
|
|
4242
|
+
}
|
|
4243
|
+
function buildFormField(field, value) {
|
|
4244
|
+
const common = { id: field.id, key: field.key };
|
|
4245
|
+
switch (field.type) {
|
|
4246
|
+
case "text":
|
|
4247
|
+
if (typeof value !== "string") throw formValueError(field, "expected text");
|
|
4248
|
+
if (field.required && value.trim().length === 0) throw formValueError(field, "required text is blank");
|
|
4249
|
+
return { ...common, type: "text", value };
|
|
4250
|
+
case "number":
|
|
4251
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
4252
|
+
throw formValueError(field, "expected a finite number");
|
|
4253
|
+
}
|
|
4254
|
+
if (field.min !== null && field.min !== void 0 && value < field.min) {
|
|
4255
|
+
throw formValueError(field, `must be at least ${field.min}`);
|
|
4256
|
+
}
|
|
4257
|
+
if (field.max !== null && field.max !== void 0 && value > field.max) {
|
|
4258
|
+
throw formValueError(field, `must be at most ${field.max}`);
|
|
4259
|
+
}
|
|
4260
|
+
return { ...common, type: "number", value };
|
|
4261
|
+
case "boolean":
|
|
4262
|
+
if (typeof value !== "boolean") throw formValueError(field, "expected a boolean");
|
|
4263
|
+
return { ...common, type: "boolean", value };
|
|
4264
|
+
case "date":
|
|
4265
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
4266
|
+
throw formValueError(field, "expected an integer timestamp");
|
|
4267
|
+
}
|
|
4268
|
+
return { ...common, type: "date", value };
|
|
4269
|
+
case "geo_location":
|
|
4270
|
+
if (!isValidGeoLocation(value)) throw formValueError(field, "expected valid coordinates");
|
|
4271
|
+
return { ...common, type: "geo_location", value };
|
|
4272
|
+
case "select": {
|
|
4273
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
4274
|
+
throw formValueError(field, "expected a list of options");
|
|
4275
|
+
}
|
|
4276
|
+
const selected = value;
|
|
4277
|
+
if (field.required && selected.length === 0) throw formValueError(field, "at least one option is required");
|
|
4278
|
+
if (new Set(selected).size !== selected.length || selected.some((item) => !field.options.includes(item))) {
|
|
4279
|
+
throw formValueError(field, "contains an unknown or duplicate option");
|
|
4280
|
+
}
|
|
4281
|
+
return { ...common, type: "select", value: selected };
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
function isEmptyOptionalValue(field, value) {
|
|
4286
|
+
if (field.required) return false;
|
|
4287
|
+
if (field.type === "text") return value === "";
|
|
4288
|
+
if (field.type === "select") return Array.isArray(value) && value.length === 0;
|
|
4289
|
+
if (field.type === "geo_location") {
|
|
4290
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0);
|
|
4291
|
+
}
|
|
4292
|
+
return false;
|
|
4293
|
+
}
|
|
4294
|
+
function buildFormFields(schema, values) {
|
|
4295
|
+
const knownKeys = new Set(schema.map((field) => field.key));
|
|
4296
|
+
const unknownKey = Object.keys(values).find((key) => !knownKeys.has(key));
|
|
4297
|
+
if (unknownKey) throw new Error(`Form field '${unknownKey}' is not defined by the form schema`);
|
|
4298
|
+
const fields = [];
|
|
4299
|
+
for (const field of schema) {
|
|
4300
|
+
const value = values[field.key];
|
|
4301
|
+
if (value === void 0) {
|
|
4302
|
+
if (field.required) throw formValueError(field, "required value is missing");
|
|
4303
|
+
continue;
|
|
4304
|
+
}
|
|
4305
|
+
if (isEmptyOptionalValue(field, value)) continue;
|
|
4306
|
+
fields.push(buildFormField(field, value));
|
|
4307
|
+
}
|
|
4308
|
+
return fields;
|
|
4309
|
+
}
|
|
4310
|
+
function createFormEntryFromValues(form, values) {
|
|
4311
|
+
return createFormEntry(form.id, buildFormFields(form.schema, values));
|
|
4312
|
+
}
|
|
4313
|
+
function getFormBlockType(field) {
|
|
4314
|
+
if (field.key === "email") return "email";
|
|
4315
|
+
if (field.key === "phone") return "phone";
|
|
4316
|
+
if (field.type === "geo_location") return "address";
|
|
4317
|
+
return field.type;
|
|
4318
|
+
}
|
|
4319
|
+
function getFormBlockValue(field) {
|
|
4320
|
+
if (field.type === "boolean") return false;
|
|
4321
|
+
if (field.type === "select") return [];
|
|
4322
|
+
if (field.type === "geo_location") return {};
|
|
4323
|
+
if (field.type === "number" || field.type === "date") return void 0;
|
|
4324
|
+
return "";
|
|
4325
|
+
}
|
|
4326
|
+
function formSchemaToBlock(field) {
|
|
4327
|
+
const min = field.type === "number" ? field.min : void 0;
|
|
4328
|
+
const max = field.type === "number" ? field.max : void 0;
|
|
4329
|
+
const options = field.type === "select" ? field.options : void 0;
|
|
4330
|
+
return {
|
|
4331
|
+
id: field.id,
|
|
4332
|
+
key: field.key,
|
|
4333
|
+
type: getFormBlockType(field),
|
|
4334
|
+
properties: {
|
|
4335
|
+
isRequired: field.required,
|
|
4336
|
+
minValues: field.required ? 1 : 0,
|
|
4337
|
+
min,
|
|
4338
|
+
max,
|
|
4339
|
+
options,
|
|
4340
|
+
pattern: field.key === "email" ? "^.+@.+\\..+$" : field.key === "phone" ? "^.{6,20}$" : void 0
|
|
4341
|
+
},
|
|
4342
|
+
value: getFormBlockValue(field)
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
function formatServiceTime(ts, tz) {
|
|
4346
|
+
return new Date(ts * 1e3).toLocaleTimeString([], {
|
|
4347
|
+
hour: "2-digit",
|
|
4348
|
+
minute: "2-digit",
|
|
4349
|
+
timeZone: tz
|
|
4350
|
+
});
|
|
4351
|
+
}
|
|
4352
|
+
function formatServiceSlotTime(from, to, tz) {
|
|
4353
|
+
return `${formatServiceTime(from, tz)} - ${formatServiceTime(to, tz)}`;
|
|
4354
|
+
}
|
|
4355
|
+
function getSlotsForDate(availability, dateStr, providerId) {
|
|
4356
|
+
if (!availability) return [];
|
|
4357
|
+
const slots = [];
|
|
4358
|
+
for (const provider of availability.providers) {
|
|
4359
|
+
if (providerId && provider.provider_id !== providerId) continue;
|
|
4360
|
+
const day = provider.days.find((candidate) => candidate.date === dateStr);
|
|
4361
|
+
if (!day) continue;
|
|
4362
|
+
for (const slot of day.slots) {
|
|
4363
|
+
if (slot.spots > 0) slots.push({ from: slot.from, to: slot.to, providerId: provider.provider_id });
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
return slots.sort((a, b) => a.from - b.from);
|
|
4367
|
+
}
|
|
4368
|
+
function hasAvailableSlotsForDate(availability, dateStr, providerId) {
|
|
4369
|
+
if (!availability) return false;
|
|
4370
|
+
return availability.providers.some((provider) => {
|
|
4371
|
+
if (providerId && provider.provider_id !== providerId) return false;
|
|
4372
|
+
const day = provider.days.find((candidate) => candidate.date === dateStr);
|
|
4373
|
+
return !!day?.slots.some((slot) => slot.spots > 0);
|
|
4374
|
+
});
|
|
4375
|
+
}
|
|
4376
|
+
var SERVICE_WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
4377
|
+
function createServiceInitialState() {
|
|
4378
|
+
return {
|
|
4379
|
+
service: null,
|
|
4380
|
+
availability: null,
|
|
4381
|
+
providers: [],
|
|
4382
|
+
serviceProviders: [],
|
|
4383
|
+
selectedProviderId: null,
|
|
4384
|
+
currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
|
|
4385
|
+
calendar: [],
|
|
4386
|
+
selectedDate: null,
|
|
4387
|
+
slots: [],
|
|
4388
|
+
selectedSlot: null,
|
|
4389
|
+
timezone: typeof window !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "UTC",
|
|
4390
|
+
tzGroups: {},
|
|
4391
|
+
loading: false,
|
|
4392
|
+
weekdays: SERVICE_WEEKDAYS,
|
|
4393
|
+
quote: null,
|
|
4394
|
+
fetchingQuote: false,
|
|
4395
|
+
quoteError: null,
|
|
4396
|
+
currency: null,
|
|
4397
|
+
dateTimeConfirmed: false,
|
|
4398
|
+
availablePaymentMethods: [],
|
|
4399
|
+
cartId: null,
|
|
4400
|
+
promoCode: null
|
|
4401
|
+
};
|
|
4402
|
+
}
|
|
4403
|
+
function normalizeTimezoneGroups(groups) {
|
|
4404
|
+
const normalized = {};
|
|
4405
|
+
for (const group of groups) {
|
|
4406
|
+
normalized[group.label] = group.zones.map((zone) => ({
|
|
4407
|
+
zone: zone.value,
|
|
4408
|
+
name: zone.label
|
|
4409
|
+
}));
|
|
4410
|
+
}
|
|
4411
|
+
return normalized;
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
// src/storefrontStore/initialize.ts
|
|
4415
|
+
function firstFiniteNumber(...values) {
|
|
4416
|
+
return values.find((value) => typeof value === "number" && Number.isFinite(value));
|
|
4417
|
+
}
|
|
4418
|
+
function initializeStoreCore(publishableKey, config, scopedClient) {
|
|
4419
|
+
const client = scopedClient || createStorefront(publishableKey, config);
|
|
4420
|
+
const session = nanostores.atom(client.session);
|
|
4421
|
+
const setup = nanostores.atom(null);
|
|
4422
|
+
const locale = nanostores.atom(config.locale || client.getLocale());
|
|
4423
|
+
const market_key = nanostores.atom(config.market || client.getMarket());
|
|
4424
|
+
const market = nanostores.computed([setup, market_key], (setupValue, marketKey) => {
|
|
4425
|
+
const resolvedKey = marketKey || setupValue?.markets.default;
|
|
4426
|
+
return setupValue?.markets.available.find((candidate) => candidate.key === resolvedKey) || null;
|
|
4427
|
+
});
|
|
4428
|
+
const currency = nanostores.computed(market, (value) => value?.currency || null);
|
|
4429
|
+
const allowed_payment_methods = nanostores.computed(market, (value) => value?.payment_methods || []);
|
|
4430
|
+
const payment_config = nanostores.computed([setup, market], (setupValue, marketValue) => {
|
|
4431
|
+
const methods = marketValue?.payment_methods || [];
|
|
4432
|
+
const hasCreditCard = methods.some((method) => method.type === "credit_card");
|
|
4433
|
+
return {
|
|
4434
|
+
provider: setupValue?.payment || null,
|
|
4435
|
+
enabled: hasCreditCard && Boolean(setupValue?.readiness.payment)
|
|
4436
|
+
};
|
|
4437
|
+
});
|
|
4438
|
+
const cart = nanostores.atom(null);
|
|
4439
|
+
const product_items = nanostores.atom([]);
|
|
4440
|
+
const service_items = nanostores.atom([]);
|
|
4441
|
+
const quote = nanostores.atom(null);
|
|
4442
|
+
const promo_code = nanostores.atom(null);
|
|
4443
|
+
const last_order = nanostores.atom(null);
|
|
4444
|
+
const payment_controller = nanostores.atom(null);
|
|
4445
|
+
const payment_ready = nanostores.computed(payment_controller, (value) => value !== null);
|
|
4446
|
+
const cart_status = nanostores.map({
|
|
4447
|
+
loading: false,
|
|
4448
|
+
syncing: false,
|
|
4449
|
+
fetching_quote: false,
|
|
4450
|
+
processing_checkout: false,
|
|
4451
|
+
error: null,
|
|
4452
|
+
quote_error: null,
|
|
4453
|
+
selected_shipping_method_id: null,
|
|
4454
|
+
user_token: null
|
|
4455
|
+
});
|
|
4456
|
+
function rawProductItemCount(value) {
|
|
4457
|
+
return (value?.items || []).reduce((total, item) => {
|
|
4458
|
+
if (item.type !== "product") return total;
|
|
4459
|
+
return total + (item.quantity || 0);
|
|
4460
|
+
}, 0);
|
|
4461
|
+
}
|
|
4462
|
+
function rawServiceItemCount(value) {
|
|
4463
|
+
return (value?.items || []).reduce((total, item) => {
|
|
4464
|
+
if (item.type !== "service") return total;
|
|
4465
|
+
return total + Math.max(1, item.slots?.length || 0);
|
|
4466
|
+
}, 0);
|
|
4467
|
+
}
|
|
4468
|
+
const product_item_count = nanostores.computed(
|
|
4469
|
+
[cart, product_items],
|
|
4470
|
+
(cartValue, items) => Math.max(
|
|
4471
|
+
rawProductItemCount(cartValue),
|
|
4472
|
+
items.reduce((total, item) => total + (item.quantity || 0), 0)
|
|
4473
|
+
)
|
|
4474
|
+
);
|
|
4475
|
+
const service_item_count = nanostores.computed(
|
|
4476
|
+
[cart, service_items],
|
|
4477
|
+
(cartValue, items) => Math.max(
|
|
4478
|
+
rawServiceItemCount(cartValue),
|
|
4479
|
+
items.reduce((total, item) => total + Math.max(1, item.slots.length), 0)
|
|
4480
|
+
)
|
|
4481
|
+
);
|
|
4482
|
+
const item_count = nanostores.computed(
|
|
4483
|
+
[cart, product_item_count, service_item_count],
|
|
4484
|
+
(cartValue, products, services) => Math.max(cartValue?.item_count || 0, products + services)
|
|
4485
|
+
);
|
|
4486
|
+
const snapshot = nanostores.computed([cart, product_items, service_items, item_count], (cartValue, products, services, count) => ({
|
|
4487
|
+
cart: cartValue,
|
|
4488
|
+
product_items: products,
|
|
4489
|
+
service_items: services,
|
|
4490
|
+
item_count: count
|
|
4491
|
+
}));
|
|
4492
|
+
let cartWriteRevision = 0;
|
|
4493
|
+
let sessionRequest = null;
|
|
4494
|
+
let cartRequest = null;
|
|
4495
|
+
function nextCartWriteRevision() {
|
|
4496
|
+
cartWriteRevision += 1;
|
|
4497
|
+
return cartWriteRevision;
|
|
4498
|
+
}
|
|
4499
|
+
const cms_state = nanostores.map({
|
|
4500
|
+
entries: {},
|
|
4501
|
+
forms: {},
|
|
4502
|
+
loading: false,
|
|
4503
|
+
error: null
|
|
4504
|
+
});
|
|
4505
|
+
const eshop_state = nanostores.map({
|
|
4506
|
+
products: [],
|
|
4507
|
+
services: [],
|
|
4508
|
+
providers: [],
|
|
4509
|
+
product_cursor: null,
|
|
4510
|
+
service_cursor: null,
|
|
4511
|
+
provider_cursor: null,
|
|
4512
|
+
availability: null,
|
|
4513
|
+
loading_products: false,
|
|
4514
|
+
loading_services: false,
|
|
4515
|
+
loading_providers: false,
|
|
4516
|
+
loading_availability: false,
|
|
4517
|
+
error: null
|
|
4518
|
+
});
|
|
4519
|
+
const service_state = nanostores.map(createServiceInitialState());
|
|
4520
|
+
const service_form_definitions = /* @__PURE__ */ new Map();
|
|
4521
|
+
const service_form_state = nanostores.map({
|
|
4522
|
+
provider_id: null,
|
|
4523
|
+
groups: [],
|
|
4524
|
+
loading: false,
|
|
4525
|
+
error: null
|
|
4526
|
+
});
|
|
4527
|
+
const service_form_groups = nanostores.computed(service_form_state, (state) => state.groups);
|
|
4528
|
+
const service_form_blocks = nanostores.computed(service_form_groups, (groups) => groups.flatMap((group) => group.blocks));
|
|
4529
|
+
client.onAuthStateChanged((value) => session.set(value));
|
|
4530
|
+
currency.subscribe((value) => service_state.setKey("currency", value));
|
|
4531
|
+
market.subscribe((value) => {
|
|
4532
|
+
const methods = value?.payment_methods || [];
|
|
4533
|
+
if (methods.length && service_state.get().availablePaymentMethods.length === 0) {
|
|
4534
|
+
service_state.setKey("availablePaymentMethods", methods);
|
|
4535
|
+
}
|
|
4536
|
+
});
|
|
4537
|
+
function currentMarketKey() {
|
|
4538
|
+
return market_key.get() || client.getMarket() || setup.get()?.markets.default || "";
|
|
4539
|
+
}
|
|
4540
|
+
function currentLocale() {
|
|
4541
|
+
return locale.get() || client.getLocale() || setup.get()?.languages.default || "en";
|
|
4542
|
+
}
|
|
4543
|
+
function currentStripePublishableKey() {
|
|
4544
|
+
const provider = payment_config.get()?.provider;
|
|
4545
|
+
return provider?.publishable_key || null;
|
|
4546
|
+
}
|
|
4547
|
+
function currentStripeConnectedAccountId() {
|
|
4548
|
+
const provider = payment_config.get()?.provider;
|
|
4549
|
+
return provider?.connected_account_id || null;
|
|
4550
|
+
}
|
|
4551
|
+
function currentPaymentAmount() {
|
|
4552
|
+
return Math.max(
|
|
4553
|
+
0,
|
|
4554
|
+
firstFiniteNumber(quote.get()?.charge_amount, cart.get()?.quote_snapshot?.charge_amount, cart.get()?.quote_snapshot?.total) ?? 0
|
|
4555
|
+
);
|
|
4556
|
+
}
|
|
4557
|
+
function currentPaymentCurrency() {
|
|
4558
|
+
return quote.get()?.money?.currency?.trim() || cart.get()?.quote_snapshot?.money?.currency?.trim() || null;
|
|
4559
|
+
}
|
|
4560
|
+
function setPaymentController(controller) {
|
|
4561
|
+
const current = payment_controller.get();
|
|
4562
|
+
if (current && current !== controller) {
|
|
4563
|
+
current.destroy();
|
|
4564
|
+
}
|
|
4565
|
+
payment_controller.set(controller);
|
|
4566
|
+
return controller;
|
|
4567
|
+
}
|
|
4568
|
+
function destroyPaymentController() {
|
|
4569
|
+
setPaymentController(null);
|
|
4570
|
+
}
|
|
4571
|
+
async function loadSetup() {
|
|
4572
|
+
const current = setup.get();
|
|
4573
|
+
if (current) return current;
|
|
4574
|
+
const result = await client.getSetup();
|
|
4575
|
+
setup.set(result);
|
|
4576
|
+
if (!market_key.get() && result.markets.default) {
|
|
4577
|
+
market_key.set(result.markets.default);
|
|
4578
|
+
}
|
|
4579
|
+
if (!locale.get() && result.languages.default) {
|
|
4580
|
+
locale.set(result.languages.default);
|
|
4581
|
+
}
|
|
4582
|
+
return result;
|
|
4583
|
+
}
|
|
4584
|
+
async function mountPayment(target, options = {}) {
|
|
4585
|
+
await loadSetup();
|
|
4586
|
+
const publishableKey2 = currentStripePublishableKey();
|
|
4587
|
+
if (!publishableKey2) {
|
|
4588
|
+
throw new Error("Stripe card payment is not configured for this Store");
|
|
4589
|
+
}
|
|
4590
|
+
const hasExplicitAmount = options.amount !== void 0;
|
|
4591
|
+
const hasExplicitCurrency = Boolean(options.currency?.trim());
|
|
4592
|
+
if (hasExplicitAmount !== hasExplicitCurrency) {
|
|
4593
|
+
throw new Error("Stripe amount and currency must be supplied together");
|
|
4594
|
+
}
|
|
4595
|
+
const amount = hasExplicitAmount ? options.amount : currentPaymentAmount();
|
|
4596
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
4597
|
+
throw new Error("A positive minor-unit payment amount is required to mount card payment");
|
|
4598
|
+
}
|
|
4599
|
+
const paymentCurrency = hasExplicitCurrency ? options.currency.trim() : currentPaymentCurrency();
|
|
4600
|
+
if (!paymentCurrency || !/^[a-z]{3}$/i.test(paymentCurrency)) {
|
|
4601
|
+
throw new Error("An explicit three-letter payment currency is required to mount card payment");
|
|
4602
|
+
}
|
|
4603
|
+
const controller = await createStripeConfirmationTokenController({
|
|
4604
|
+
publishableKey: publishableKey2,
|
|
4605
|
+
connectedAccountId: currentStripeConnectedAccountId() || void 0,
|
|
4606
|
+
amount,
|
|
4607
|
+
currency: paymentCurrency,
|
|
4608
|
+
...options.appearance ? { appearance: options.appearance } : {},
|
|
4609
|
+
...options.setupFutureUsage ? { setupFutureUsage: options.setupFutureUsage } : {}
|
|
4610
|
+
});
|
|
4611
|
+
controller.mount(target);
|
|
4612
|
+
setPaymentController(controller);
|
|
4613
|
+
return controller;
|
|
4614
|
+
}
|
|
4615
|
+
function updatePaymentController(input) {
|
|
4616
|
+
payment_controller.get()?.update(input);
|
|
4617
|
+
}
|
|
4618
|
+
async function ensureSession() {
|
|
4619
|
+
const current = session.get();
|
|
4620
|
+
if (client.isAuthenticated) return current;
|
|
4621
|
+
if (!sessionRequest) {
|
|
4622
|
+
sessionRequest = identify().finally(() => {
|
|
4623
|
+
sessionRequest = null;
|
|
4624
|
+
});
|
|
4625
|
+
}
|
|
4626
|
+
return sessionRequest;
|
|
4627
|
+
}
|
|
4628
|
+
async function identify(params = {}) {
|
|
4629
|
+
if (params.market) setMarket(params.market);
|
|
4630
|
+
const result = await client.identify({
|
|
4631
|
+
...params,
|
|
4632
|
+
market: params.market || currentMarketKey()
|
|
4633
|
+
});
|
|
4634
|
+
session.set({
|
|
4635
|
+
contact: result.contact
|
|
4636
|
+
});
|
|
4637
|
+
return result;
|
|
4638
|
+
}
|
|
4639
|
+
async function identifyContactEmailIfMissing(email) {
|
|
4640
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
4641
|
+
if (!normalizedEmail) throw new Error("Contact email is required");
|
|
4642
|
+
const current = session.get();
|
|
4643
|
+
if (current?.contact.email === normalizedEmail) return current;
|
|
4644
|
+
return identify({ email: normalizedEmail });
|
|
4645
|
+
}
|
|
4646
|
+
function setMarket(key) {
|
|
4647
|
+
const next = key.trim();
|
|
4648
|
+
const current = currentMarketKey();
|
|
4649
|
+
if (next && current && next !== current && (cart.get()?.item_count || item_count.get()) > 0) {
|
|
4650
|
+
throw Object.assign(
|
|
4651
|
+
new Error("Market cannot change while the cart contains items"),
|
|
4652
|
+
{ code: "CART_MARKET_LOCKED" }
|
|
4653
|
+
);
|
|
4654
|
+
}
|
|
4655
|
+
market_key.set(next);
|
|
4656
|
+
client.setMarket(next);
|
|
4657
|
+
}
|
|
4658
|
+
function setLocale(value) {
|
|
4659
|
+
locale.set(value);
|
|
4660
|
+
client.setLocale(value);
|
|
4661
|
+
}
|
|
4662
|
+
function setContext(context) {
|
|
4663
|
+
if (context.market !== void 0) setMarket(context.market);
|
|
4664
|
+
if (context.locale !== void 0) setLocale(context.locale);
|
|
4665
|
+
}
|
|
4666
|
+
async function ensureCart() {
|
|
4667
|
+
if (cartRequest) return cartRequest;
|
|
4668
|
+
cart_status.setKey("loading", true);
|
|
4669
|
+
cart_status.setKey("error", null);
|
|
4670
|
+
const refreshRevision = cartWriteRevision;
|
|
4671
|
+
cartRequest = (async () => {
|
|
4672
|
+
await ensureSession();
|
|
4673
|
+
const response = await client.eshop.cart.current();
|
|
4674
|
+
await applyCartResponse(response, { ifRevision: refreshRevision });
|
|
4675
|
+
return response;
|
|
4676
|
+
})();
|
|
4677
|
+
try {
|
|
4678
|
+
return await cartRequest;
|
|
4679
|
+
} catch (error) {
|
|
4680
|
+
cart_status.setKey("error", readErrorMessage(error, "Failed to load cart."));
|
|
4681
|
+
throw error;
|
|
4682
|
+
} finally {
|
|
4683
|
+
cartRequest = null;
|
|
4684
|
+
cart_status.setKey("loading", false);
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
async function buildProductCartItem(item, source, productHint) {
|
|
4688
|
+
try {
|
|
4689
|
+
const product = productHint?.id === item.product_id ? productHint : await client.eshop.product.get({ id: item.product_id });
|
|
4690
|
+
const variant = product.variants.find(
|
|
4691
|
+
(candidate) => candidate.id === item.variant_id
|
|
4692
|
+
);
|
|
4693
|
+
if (!variant) {
|
|
4694
|
+
cart_status.setKey(
|
|
4695
|
+
"error",
|
|
4696
|
+
`Cart product ${item.product_id} references unavailable variant ${item.variant_id}.`
|
|
4697
|
+
);
|
|
4698
|
+
return null;
|
|
4699
|
+
}
|
|
4700
|
+
return {
|
|
4701
|
+
id: item.id || createId("product"),
|
|
4702
|
+
product_id: product.id,
|
|
4703
|
+
variant_id: variant.id,
|
|
4704
|
+
product_name: productName(product, currentLocale()),
|
|
4705
|
+
product_slug: entitySlug(product, currentLocale()),
|
|
4706
|
+
variant_attributes: variant.attributes,
|
|
4707
|
+
requires_shipping: variant.requires_shipping !== false,
|
|
4708
|
+
price: priceForMarket(variant.prices, currentMarketKey(), market.get()?.currency),
|
|
4709
|
+
quantity: item.quantity,
|
|
4710
|
+
added_at: source.created_at ? source.created_at * 1e3 : Date.now(),
|
|
4711
|
+
max_stock: availableStock(client, variant)
|
|
4712
|
+
};
|
|
4713
|
+
} catch (error) {
|
|
4714
|
+
cart_status.setKey(
|
|
4715
|
+
"error",
|
|
4716
|
+
readErrorMessage(
|
|
4717
|
+
error,
|
|
4718
|
+
`Failed to load cart product ${item.product_id}.`
|
|
4719
|
+
)
|
|
4720
|
+
);
|
|
4721
|
+
return null;
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
async function buildServiceCartItems(items) {
|
|
4725
|
+
const rows = [];
|
|
4726
|
+
for (const item of items) {
|
|
4727
|
+
let service = null;
|
|
4728
|
+
let provider = null;
|
|
4729
|
+
try {
|
|
4730
|
+
service = await client.eshop.service.get({ id: item.service_id });
|
|
4731
|
+
} catch {
|
|
4732
|
+
}
|
|
4733
|
+
try {
|
|
4734
|
+
provider = await client.eshop.provider.get({ id: item.provider_id });
|
|
4735
|
+
} catch {
|
|
4736
|
+
}
|
|
4737
|
+
rows.push({
|
|
4738
|
+
id: item.id || createId("service"),
|
|
4739
|
+
service_id: item.service_id,
|
|
4740
|
+
provider_id: item.provider_id,
|
|
4741
|
+
slots: item.slots,
|
|
4742
|
+
forms: item.forms || [],
|
|
4743
|
+
service_name: service ? serviceName(service, currentLocale()) : item.service_id,
|
|
4744
|
+
provider_name: provider ? providerName(provider, currentLocale()) : item.provider_id
|
|
4745
|
+
});
|
|
4746
|
+
}
|
|
4747
|
+
return rows;
|
|
4748
|
+
}
|
|
4749
|
+
async function applyCartResponse(response, options = {}) {
|
|
4750
|
+
if (options.ifRevision !== void 0 && options.ifRevision !== cartWriteRevision) {
|
|
4751
|
+
return cart.get() || response;
|
|
4752
|
+
}
|
|
4753
|
+
cart.set(response);
|
|
4754
|
+
cart_status.setKey("user_token", response.token || null);
|
|
4755
|
+
cart_status.setKey("selected_shipping_method_id", response.shipping_method_id || null);
|
|
4756
|
+
promo_code.set(response.promo_code || null);
|
|
4757
|
+
quote.set(response.quote_snapshot || null);
|
|
4758
|
+
const items = response.items || [];
|
|
4759
|
+
if (items.length > 0) await loadSetup();
|
|
4760
|
+
const products = await Promise.all(
|
|
4761
|
+
items.filter((item) => item.type === "product").map((item) => buildProductCartItem(item, response, options.productHint))
|
|
4762
|
+
);
|
|
4763
|
+
const services = await buildServiceCartItems(items.filter((item) => item.type === "service"));
|
|
4764
|
+
product_items.set(products.filter((item) => item !== null));
|
|
4765
|
+
service_items.set(services);
|
|
4766
|
+
return response;
|
|
4767
|
+
}
|
|
4768
|
+
function checkoutItems(input = {}) {
|
|
4769
|
+
return [
|
|
4770
|
+
...toProductCheckoutItems(input.product_items || product_items.get()),
|
|
4771
|
+
...toServiceCheckoutItems(input.service_items || service_items.get())
|
|
4772
|
+
];
|
|
4773
|
+
}
|
|
4774
|
+
async function syncCart(input = {}, writeRevision = nextCartWriteRevision()) {
|
|
4775
|
+
cart_status.setKey("syncing", true);
|
|
4776
|
+
cart_status.setKey("error", null);
|
|
4777
|
+
try {
|
|
4778
|
+
const current = cart.get() || await ensureCart();
|
|
4779
|
+
const response = await client.eshop.cart.update({
|
|
4780
|
+
id: current.id,
|
|
4781
|
+
items: checkoutItems(input),
|
|
4782
|
+
shipping_address: input.shipping_address,
|
|
4783
|
+
billing_address: input.billing_address,
|
|
4784
|
+
forms: input.forms,
|
|
4785
|
+
promo_code: input.promo_code === null ? "" : input.promo_code === void 0 ? promo_code.get() || void 0 : input.promo_code,
|
|
4786
|
+
payment_method_key: input.payment_method_key === null ? "" : input.payment_method_key,
|
|
4787
|
+
shipping_method_id: input.shipping_method_id === null ? "" : input.shipping_method_id === void 0 ? cart_status.get().selected_shipping_method_id || void 0 : input.shipping_method_id
|
|
4788
|
+
});
|
|
4789
|
+
await applyCartResponse(response, { ifRevision: writeRevision });
|
|
4790
|
+
return response;
|
|
4791
|
+
} catch (error) {
|
|
4792
|
+
cart_status.setKey("error", readErrorMessage(error, "Failed to sync cart."));
|
|
4793
|
+
throw error;
|
|
4794
|
+
} finally {
|
|
4795
|
+
cart_status.setKey("syncing", false);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
async function addProduct(product, variant, quantity = 1) {
|
|
4799
|
+
cart_status.setKey("error", null);
|
|
4800
|
+
const writeRevision = nextCartWriteRevision();
|
|
4801
|
+
try {
|
|
4802
|
+
const current = cart.get() || await ensureCart();
|
|
4803
|
+
const response = await client.eshop.cart.addItem({
|
|
4804
|
+
id: current.id,
|
|
4805
|
+
item: {
|
|
4806
|
+
type: "product",
|
|
4807
|
+
product_id: product.id,
|
|
4808
|
+
variant_id: variant.id,
|
|
4809
|
+
quantity
|
|
4810
|
+
}
|
|
4811
|
+
});
|
|
4812
|
+
await applyCartResponse(response, { ifRevision: writeRevision, productHint: product });
|
|
4813
|
+
return response;
|
|
4814
|
+
} catch (error) {
|
|
4815
|
+
cart_status.setKey("error", readErrorMessage(error, "Failed to add product to cart."));
|
|
4816
|
+
throw error;
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
async function setProductQuantity(itemId, quantity) {
|
|
4820
|
+
const writeRevision = nextCartWriteRevision();
|
|
4821
|
+
const next = product_items.get().map((item) => {
|
|
4822
|
+
if (item.id !== itemId) return item;
|
|
4823
|
+
const bounded = item.max_stock ? Math.min(Math.max(1, quantity), item.max_stock) : Math.max(1, quantity);
|
|
4824
|
+
return { ...item, quantity: bounded };
|
|
4825
|
+
});
|
|
4826
|
+
product_items.set(next);
|
|
4827
|
+
return syncCart({ product_items: next }, writeRevision);
|
|
4828
|
+
}
|
|
4829
|
+
async function removeProduct(itemId) {
|
|
4830
|
+
const writeRevision = nextCartWriteRevision();
|
|
4831
|
+
const item = product_items.get().find((candidate) => candidate.id === itemId);
|
|
4832
|
+
product_items.set(product_items.get().filter((candidate) => candidate.id !== itemId));
|
|
4833
|
+
const current = cart.get();
|
|
4834
|
+
if (!current || !item) return null;
|
|
4835
|
+
const response = await client.eshop.cart.removeItem({
|
|
4836
|
+
id: current.id,
|
|
4837
|
+
item_id: item.id
|
|
4838
|
+
});
|
|
4839
|
+
await applyCartResponse(response, { ifRevision: writeRevision });
|
|
4840
|
+
return response;
|
|
4841
|
+
}
|
|
4842
|
+
async function addServiceItem(item) {
|
|
4843
|
+
const writeRevision = nextCartWriteRevision();
|
|
4844
|
+
const next = [...service_items.get(), item];
|
|
4845
|
+
service_items.set(next);
|
|
4846
|
+
return syncCart({ service_items: next }, writeRevision);
|
|
4847
|
+
}
|
|
4848
|
+
async function removeServiceItem(itemId) {
|
|
4849
|
+
const writeRevision = nextCartWriteRevision();
|
|
4850
|
+
const next = service_items.get().filter((item) => item.id !== itemId);
|
|
4851
|
+
service_items.set(next);
|
|
4852
|
+
return syncCart({ service_items: next }, writeRevision);
|
|
4853
|
+
}
|
|
4854
|
+
async function clearCart() {
|
|
4855
|
+
const writeRevision = nextCartWriteRevision();
|
|
4856
|
+
const current = cart.get();
|
|
4857
|
+
clearLocalCart();
|
|
4858
|
+
if (!current) return null;
|
|
4859
|
+
const response = await client.eshop.cart.clear({ id: current.id });
|
|
4860
|
+
await applyCartResponse(response, { ifRevision: writeRevision });
|
|
4861
|
+
return response;
|
|
4862
|
+
}
|
|
4863
|
+
function clearLocalCart() {
|
|
4864
|
+
product_items.set([]);
|
|
4865
|
+
service_items.set([]);
|
|
4866
|
+
cart.set(null);
|
|
4867
|
+
quote.set(null);
|
|
4868
|
+
promo_code.set(null);
|
|
4869
|
+
cart_status.setKey("selected_shipping_method_id", null);
|
|
4870
|
+
}
|
|
4871
|
+
async function fetchQuote(input = {}) {
|
|
4872
|
+
if (checkoutItems(input).length === 0) {
|
|
4873
|
+
quote.set(null);
|
|
4874
|
+
return null;
|
|
4875
|
+
}
|
|
4876
|
+
cart_status.setKey("fetching_quote", true);
|
|
4877
|
+
cart_status.setKey("quote_error", null);
|
|
4878
|
+
try {
|
|
4879
|
+
const current = await syncCart(input);
|
|
4880
|
+
const response = await client.eshop.cart.quote({ id: current.id });
|
|
4881
|
+
quote.set(response);
|
|
4882
|
+
return response;
|
|
4883
|
+
} catch (error) {
|
|
4884
|
+
quote.set(null);
|
|
4885
|
+
cart_status.setKey("quote_error", readErrorMessage(error, "Failed to fetch quote."));
|
|
4886
|
+
throw error;
|
|
4887
|
+
} finally {
|
|
4888
|
+
cart_status.setKey("fetching_quote", false);
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
async function checkout(input = {}) {
|
|
4892
|
+
if (checkoutItems(input).length === 0) throw new Error("Cart is empty");
|
|
4893
|
+
cart_status.setKey("processing_checkout", true);
|
|
4894
|
+
cart_status.setKey("error", null);
|
|
4895
|
+
try {
|
|
4896
|
+
const current = await syncCart(input);
|
|
4897
|
+
const quoteValue = quote.get();
|
|
4898
|
+
const paymentMethodKey = input.payment_method_key || current.payment_method_key || quoteValue?.money?.payment_method_key || void 0;
|
|
4899
|
+
let chargeAmount = firstFiniteNumber(quoteValue?.charge_amount, current.quote_snapshot?.charge_amount);
|
|
4900
|
+
if (paymentMethodKey === "credit_card" && chargeAmount === void 0) {
|
|
4901
|
+
const latestQuote = await client.eshop.cart.quote({ id: current.id });
|
|
4902
|
+
quote.set(latestQuote);
|
|
4903
|
+
chargeAmount = firstFiniteNumber(latestQuote.charge_amount, latestQuote.total);
|
|
4904
|
+
}
|
|
4905
|
+
if (paymentMethodKey === "credit_card" && (typeof chargeAmount !== "number" || !Number.isSafeInteger(chargeAmount) || chargeAmount < 0)) {
|
|
4906
|
+
throw new Error("Card checkout requires a non-negative integer charge amount in minor units");
|
|
4907
|
+
}
|
|
4908
|
+
const needsConfirmationToken = paymentMethodKey === "credit_card" && typeof chargeAmount === "number" && chargeAmount > 0;
|
|
4909
|
+
let confirmationTokenId;
|
|
4910
|
+
let returnUrl = input.return_url;
|
|
4911
|
+
const paymentController = input.payment ?? payment_controller.get();
|
|
4912
|
+
if (needsConfirmationToken) {
|
|
4913
|
+
if (!paymentController) throw new Error("Payment controller is required for card checkout");
|
|
4914
|
+
returnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : void 0);
|
|
4915
|
+
const token = await paymentController.createConfirmationToken({
|
|
4916
|
+
return_url: returnUrl,
|
|
4917
|
+
billing_details: input.billing_details
|
|
4918
|
+
});
|
|
4919
|
+
confirmationTokenId = token.confirmation_token_id;
|
|
4920
|
+
returnUrl = token.return_url || returnUrl;
|
|
4921
|
+
}
|
|
4922
|
+
const response = await client.eshop.cart.checkout({
|
|
4923
|
+
id: current.id,
|
|
4924
|
+
payment_method_key: paymentMethodKey,
|
|
4925
|
+
confirmation_token_id: confirmationTokenId,
|
|
4926
|
+
return_url: returnUrl
|
|
4927
|
+
});
|
|
4928
|
+
if (response.payment_action.type === "handle_next_action") {
|
|
4929
|
+
if (!paymentController) throw new Error("Payment controller is required for card authentication");
|
|
4930
|
+
await paymentController.handleNextAction(response.payment_action.client_secret);
|
|
4931
|
+
}
|
|
4932
|
+
const stored = {
|
|
4933
|
+
order_id: response.order_id,
|
|
4934
|
+
number: response.number,
|
|
4935
|
+
payment_action: response.payment_action,
|
|
4936
|
+
payment: response.payment,
|
|
4937
|
+
product_items: input.product_items || product_items.get(),
|
|
4938
|
+
service_items: input.service_items || service_items.get(),
|
|
4939
|
+
shipping_address: input.shipping_address || null,
|
|
4940
|
+
billing_address: input.billing_address || null,
|
|
4941
|
+
total: response.payment.amount,
|
|
4942
|
+
currency: response.payment.currency,
|
|
4943
|
+
payment_method_key: paymentMethodKey || null,
|
|
4944
|
+
created_at: Date.now()
|
|
4945
|
+
};
|
|
4946
|
+
last_order.set(stored);
|
|
4947
|
+
if (input.clear_after_checkout !== false) {
|
|
4948
|
+
clearLocalCart();
|
|
4949
|
+
}
|
|
4950
|
+
return response;
|
|
4951
|
+
} catch (error) {
|
|
4952
|
+
cart_status.setKey("error", readErrorMessage(error, "Checkout failed."));
|
|
4953
|
+
throw error;
|
|
4954
|
+
} finally {
|
|
4955
|
+
cart_status.setKey("processing_checkout", false);
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
function serviceCalendar() {
|
|
4959
|
+
const state = service_state.get();
|
|
4960
|
+
const { currentMonth, selectedDate, availability, selectedProviderId } = state;
|
|
4961
|
+
const year = currentMonth.getFullYear();
|
|
4962
|
+
const monthIndex = currentMonth.getMonth();
|
|
4963
|
+
const first = new Date(year, monthIndex, 1);
|
|
4964
|
+
const last = new Date(year, monthIndex + 1, 0);
|
|
4965
|
+
const today = /* @__PURE__ */ new Date();
|
|
4966
|
+
today.setHours(0, 0, 0, 0);
|
|
4967
|
+
const cells = [];
|
|
4968
|
+
const pad = (first.getDay() + 6) % 7;
|
|
4969
|
+
for (let i = 0; i < pad; i++) {
|
|
4970
|
+
cells.push({
|
|
4971
|
+
date: /* @__PURE__ */ new Date(0),
|
|
4972
|
+
iso: "",
|
|
4973
|
+
available: false,
|
|
4974
|
+
isSelected: false,
|
|
4975
|
+
isInRange: false,
|
|
4976
|
+
isToday: false,
|
|
4977
|
+
blank: true
|
|
4978
|
+
});
|
|
4979
|
+
}
|
|
4980
|
+
for (let day = 1; day <= last.getDate(); day++) {
|
|
4981
|
+
const date = new Date(year, monthIndex, day);
|
|
4982
|
+
const iso = `${year}-${String(monthIndex + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
4983
|
+
cells.push({
|
|
4984
|
+
date,
|
|
4985
|
+
iso,
|
|
4986
|
+
available: hasAvailableSlotsForDate(availability, iso, selectedProviderId),
|
|
4987
|
+
isSelected: iso === selectedDate,
|
|
4988
|
+
isInRange: false,
|
|
4989
|
+
isToday: date.getTime() === today.getTime(),
|
|
4990
|
+
blank: false
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4993
|
+
const suffix = (7 - cells.length % 7) % 7;
|
|
4994
|
+
for (let i = 0; i < suffix; i++) {
|
|
4995
|
+
cells.push({
|
|
4996
|
+
date: /* @__PURE__ */ new Date(0),
|
|
4997
|
+
iso: "",
|
|
4998
|
+
available: false,
|
|
4999
|
+
isSelected: false,
|
|
5000
|
+
isInRange: false,
|
|
5001
|
+
isToday: false,
|
|
5002
|
+
blank: true
|
|
5003
|
+
});
|
|
5004
|
+
}
|
|
5005
|
+
return cells;
|
|
5006
|
+
}
|
|
5007
|
+
function computeServiceSlots(dateStr) {
|
|
5008
|
+
const state = service_state.get();
|
|
5009
|
+
const { availability, selectedProviderId, timezone, service } = state;
|
|
5010
|
+
return getSlotsForDate(availability, dateStr, selectedProviderId).map((slot, index) => ({
|
|
5011
|
+
id: `${service?.id || "service"}-${slot.from}-${index}`,
|
|
5012
|
+
serviceId: service?.id || "",
|
|
5013
|
+
providerId: slot.providerId,
|
|
5014
|
+
from: slot.from,
|
|
5015
|
+
to: slot.to,
|
|
5016
|
+
timeText: formatServiceSlotTime(slot.from, slot.to, timezone),
|
|
5017
|
+
dateText: new Date(slot.from * 1e3).toLocaleDateString([], {
|
|
5018
|
+
weekday: "short",
|
|
5019
|
+
month: "short",
|
|
5020
|
+
day: "numeric",
|
|
5021
|
+
timeZone: timezone
|
|
5022
|
+
})
|
|
5023
|
+
}));
|
|
5024
|
+
}
|
|
5025
|
+
function toServiceCartItem(slots, forms = []) {
|
|
5026
|
+
const orderedSlots = [...slots].sort((left, right) => left.from - right.from || left.to - right.to);
|
|
5027
|
+
const [first] = orderedSlots;
|
|
5028
|
+
if (!first) throw new Error("At least one service slot is required");
|
|
5029
|
+
if (orderedSlots.some((slot) => slot.serviceId !== first.serviceId || slot.providerId !== first.providerId)) {
|
|
5030
|
+
throw new Error("A booking can only contain slots for one service and provider");
|
|
5031
|
+
}
|
|
5032
|
+
if (orderedSlots.some((slot) => !Number.isSafeInteger(slot.from) || !Number.isSafeInteger(slot.to) || slot.from >= slot.to)) {
|
|
5033
|
+
throw new Error("A booking contains an invalid service slot");
|
|
5034
|
+
}
|
|
5035
|
+
for (let index = 1; index < orderedSlots.length; index += 1) {
|
|
5036
|
+
if (orderedSlots[index - 1].to !== orderedSlots[index].from) {
|
|
5037
|
+
throw new Error("A multi-slot booking must contain adjacent slots");
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
return {
|
|
5041
|
+
id: createId("booking"),
|
|
5042
|
+
service_id: first.serviceId,
|
|
5043
|
+
provider_id: first.providerId,
|
|
5044
|
+
slots: orderedSlots.map(({ from, to }) => ({ from, to })),
|
|
5045
|
+
forms,
|
|
5046
|
+
service_name: first.serviceName,
|
|
5047
|
+
date_text: first.dateText,
|
|
5048
|
+
time_text: first.timeText,
|
|
5049
|
+
is_multi_day: orderedSlots.length > 1
|
|
5050
|
+
};
|
|
5051
|
+
}
|
|
5052
|
+
async function syncServiceCart(items) {
|
|
5053
|
+
try {
|
|
5054
|
+
return await syncCart({
|
|
5055
|
+
product_items: product_items.get(),
|
|
5056
|
+
service_items: items
|
|
5057
|
+
});
|
|
5058
|
+
} catch (error) {
|
|
5059
|
+
service_state.setKey("quoteError", readErrorMessage(error, "Failed to sync service cart."));
|
|
5060
|
+
throw error;
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
function serviceCurrentStepName() {
|
|
5064
|
+
const state = service_state.get();
|
|
5065
|
+
if (!state.service) return "";
|
|
5066
|
+
if (!state.selectedSlot || !state.dateTimeConfirmed) return "datetime";
|
|
5067
|
+
return "review";
|
|
5068
|
+
}
|
|
5069
|
+
const service_current_step_name = nanostores.computed(service_state, serviceCurrentStepName);
|
|
5070
|
+
const service_can_proceed = nanostores.computed(service_state, (state) => {
|
|
5071
|
+
const step = serviceCurrentStepName();
|
|
5072
|
+
if (step === "datetime") {
|
|
5073
|
+
return !!(state.selectedDate && state.selectedSlot);
|
|
5074
|
+
}
|
|
5075
|
+
if (step === "review") return true;
|
|
5076
|
+
return false;
|
|
5077
|
+
});
|
|
5078
|
+
const service_month_year = nanostores.computed(
|
|
5079
|
+
service_state,
|
|
5080
|
+
(state) => state.currentMonth.toLocaleString(void 0, {
|
|
5081
|
+
month: "long",
|
|
5082
|
+
year: "numeric"
|
|
5083
|
+
})
|
|
5084
|
+
);
|
|
5085
|
+
const service_chain_start = nanostores.computed(service_items, (items) => {
|
|
5086
|
+
const slots = items.flatMap((item) => item.slots);
|
|
5087
|
+
if (!slots.length) return null;
|
|
5088
|
+
return Math.max(...slots.map((slot) => slot.to));
|
|
5089
|
+
});
|
|
5090
|
+
const service_total_steps = nanostores.computed(service_state, (state) => state.service ? 2 : 0);
|
|
5091
|
+
const service_steps = nanostores.computed(service_state, () => ({
|
|
5092
|
+
1: { name: "datetime" },
|
|
5093
|
+
2: { name: "review" }
|
|
5094
|
+
}));
|
|
5095
|
+
const service_current_step = nanostores.computed([service_current_step_name, service_steps], (name, steps) => {
|
|
5096
|
+
for (const [idx, step] of Object.entries(steps)) {
|
|
5097
|
+
if (step.name === name) return Number(idx);
|
|
5098
|
+
}
|
|
5099
|
+
return 1;
|
|
5100
|
+
});
|
|
5101
|
+
function formatServiceDateDisplay(value) {
|
|
5102
|
+
if (!value) return "";
|
|
5103
|
+
return new Date(value).toLocaleDateString(void 0, {
|
|
5104
|
+
month: "short",
|
|
5105
|
+
day: "numeric"
|
|
5106
|
+
});
|
|
5107
|
+
}
|
|
5108
|
+
function configuredServiceFormIds(relationship) {
|
|
5109
|
+
const formIds = relationship.forms.map((entry) => entry.form_id.trim());
|
|
5110
|
+
if (formIds.some((formId) => !formId) || new Set(formIds).size !== formIds.length) {
|
|
5111
|
+
throw new Error(`Service provider ${relationship.provider_id} has blank or duplicate configured form IDs`);
|
|
5112
|
+
}
|
|
5113
|
+
return formIds;
|
|
5114
|
+
}
|
|
5115
|
+
function resolveServiceProvider(state, providerId) {
|
|
5116
|
+
const serviceId = state.service?.id;
|
|
5117
|
+
if (!serviceId) return null;
|
|
5118
|
+
const relationships = state.serviceProviders.filter((relationship) => relationship.service_id === serviceId);
|
|
5119
|
+
const targetProviderId = providerId ?? state.selectedSlot?.providerId ?? state.selectedProviderId;
|
|
5120
|
+
if (targetProviderId) {
|
|
5121
|
+
return relationships.find((relationship) => relationship.provider_id === targetProviderId) || null;
|
|
5122
|
+
}
|
|
5123
|
+
return relationships.length === 1 ? relationships[0] : null;
|
|
5124
|
+
}
|
|
5125
|
+
function clearServiceFormState(error = null, loading = false) {
|
|
5126
|
+
service_form_state.set({
|
|
5127
|
+
provider_id: null,
|
|
5128
|
+
groups: [],
|
|
5129
|
+
loading,
|
|
5130
|
+
error
|
|
5131
|
+
});
|
|
5132
|
+
}
|
|
5133
|
+
function activateServiceProviderForms(providerId, reset = false) {
|
|
5134
|
+
const relationship = resolveServiceProvider(service_state.get(), providerId);
|
|
5135
|
+
if (!relationship) {
|
|
5136
|
+
clearServiceFormState();
|
|
5137
|
+
return null;
|
|
5138
|
+
}
|
|
5139
|
+
const formIds = configuredServiceFormIds(relationship);
|
|
5140
|
+
const current = service_form_state.get();
|
|
5141
|
+
const currentFormIds = current.groups.map((group) => group.form.id);
|
|
5142
|
+
if (!reset && current.provider_id === relationship.provider_id && currentFormIds.length === formIds.length && currentFormIds.every((formId, index) => formId === formIds[index])) {
|
|
5143
|
+
if (current.error) service_form_state.setKey("error", null);
|
|
5144
|
+
return relationship;
|
|
5145
|
+
}
|
|
5146
|
+
const groups = formIds.map((formId) => {
|
|
5147
|
+
const form = service_form_definitions.get(formId);
|
|
5148
|
+
if (!form) throw new Error(`Configured booking form '${formId}' was not loaded`);
|
|
5149
|
+
return {
|
|
5150
|
+
form,
|
|
5151
|
+
blocks: form.schema.map(formSchemaToBlock)
|
|
5152
|
+
};
|
|
5153
|
+
});
|
|
5154
|
+
service_form_state.set({
|
|
5155
|
+
provider_id: relationship.provider_id,
|
|
5156
|
+
groups,
|
|
5157
|
+
loading: false,
|
|
5158
|
+
error: null
|
|
5159
|
+
});
|
|
5160
|
+
return relationship;
|
|
5161
|
+
}
|
|
5162
|
+
async function loadServiceFormDefinitions(relationships) {
|
|
5163
|
+
const formIds = [
|
|
5164
|
+
...new Set(relationships.flatMap((relationship) => configuredServiceFormIds(relationship)))
|
|
5165
|
+
];
|
|
5166
|
+
if (formIds.length === 0) return;
|
|
5167
|
+
const forms = await Promise.all(formIds.map((formId) => loadForm({ id: formId })));
|
|
5168
|
+
for (let index = 0; index < forms.length; index += 1) {
|
|
5169
|
+
const form = forms[index];
|
|
5170
|
+
const formId = formIds[index];
|
|
5171
|
+
if (form.id !== formId) {
|
|
5172
|
+
throw new Error(`Configured booking form '${formId}' resolved to '${form.id}'`);
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
5175
|
+
for (const form of forms) service_form_definitions.set(form.id, form);
|
|
5176
|
+
}
|
|
5177
|
+
function configuredServiceFormEntries(relationship) {
|
|
5178
|
+
const formIds = configuredServiceFormIds(relationship);
|
|
5179
|
+
if (formIds.length === 0) return [];
|
|
5180
|
+
activateServiceProviderForms(relationship.provider_id);
|
|
5181
|
+
const state = service_form_state.get();
|
|
5182
|
+
if (state.provider_id !== relationship.provider_id || state.groups.length !== formIds.length || state.groups.some((group, index) => group.form.id !== formIds[index])) {
|
|
5183
|
+
throw new Error(`Booking forms are not ready for provider ${relationship.provider_id}`);
|
|
5184
|
+
}
|
|
5185
|
+
return state.groups.map(
|
|
5186
|
+
(group) => createFormEntryFromValues(
|
|
5187
|
+
group.form,
|
|
5188
|
+
Object.fromEntries(group.blocks.map((block) => [block.key, block.value]))
|
|
5189
|
+
)
|
|
5190
|
+
);
|
|
5191
|
+
}
|
|
5192
|
+
const service_controller = {
|
|
5193
|
+
async initialize() {
|
|
5194
|
+
service_state.setKey("tzGroups", normalizeTimezoneGroups(client.utils.tzGroups));
|
|
5195
|
+
await ensureCart();
|
|
5196
|
+
const methods = market.get()?.payment_methods || [];
|
|
5197
|
+
if (methods.length) service_state.setKey("availablePaymentMethods", methods);
|
|
5198
|
+
},
|
|
5199
|
+
setTimezone(tz) {
|
|
5200
|
+
service_state.setKey("timezone", tz);
|
|
5201
|
+
service_state.setKey("calendar", serviceCalendar());
|
|
5202
|
+
const state = service_state.get();
|
|
5203
|
+
if (state.selectedDate) {
|
|
5204
|
+
service_state.setKey("slots", computeServiceSlots(state.selectedDate));
|
|
5205
|
+
service_state.setKey("selectedSlot", null);
|
|
5206
|
+
service_state.setKey("quote", null);
|
|
5207
|
+
service_state.setKey("quoteError", null);
|
|
5208
|
+
activateServiceProviderForms();
|
|
5209
|
+
}
|
|
5210
|
+
},
|
|
5211
|
+
async select(service) {
|
|
5212
|
+
service_form_definitions.clear();
|
|
5213
|
+
clearServiceFormState(null, true);
|
|
5214
|
+
service_state.set({
|
|
5215
|
+
...service_state.get(),
|
|
5216
|
+
service: null,
|
|
5217
|
+
serviceProviders: [],
|
|
5218
|
+
providers: [],
|
|
5219
|
+
selectedProviderId: null,
|
|
5220
|
+
availability: null,
|
|
5221
|
+
selectedDate: null,
|
|
5222
|
+
slots: [],
|
|
5223
|
+
selectedSlot: null,
|
|
5224
|
+
dateTimeConfirmed: false,
|
|
5225
|
+
quote: null,
|
|
5226
|
+
quoteError: null,
|
|
5227
|
+
loading: true
|
|
5228
|
+
});
|
|
5229
|
+
try {
|
|
5230
|
+
const [fullService, serviceProviders] = await Promise.all([
|
|
5231
|
+
client.eshop.service.get({ id: service.id }),
|
|
5232
|
+
client.eshop.service.findProviders({
|
|
5233
|
+
service_id: service.id
|
|
5234
|
+
})
|
|
5235
|
+
]);
|
|
5236
|
+
const providerIds = [...new Set(serviceProviders.map((relationship) => relationship.provider_id))];
|
|
5237
|
+
const [providerResults] = await Promise.all([
|
|
5238
|
+
Promise.all(providerIds.map((id) => client.eshop.provider.get({ id }).catch(() => null))),
|
|
5239
|
+
loadServiceFormDefinitions(serviceProviders)
|
|
5240
|
+
]);
|
|
5241
|
+
service_state.set({
|
|
5242
|
+
...service_state.get(),
|
|
5243
|
+
service: fullService,
|
|
5244
|
+
serviceProviders,
|
|
5245
|
+
providers: providerResults.filter(
|
|
5246
|
+
(provider) => provider !== null
|
|
5247
|
+
),
|
|
5248
|
+
selectedProviderId: null,
|
|
5249
|
+
availability: null,
|
|
5250
|
+
selectedDate: null,
|
|
5251
|
+
slots: [],
|
|
5252
|
+
selectedSlot: null,
|
|
5253
|
+
currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
|
|
5254
|
+
loading: false,
|
|
5255
|
+
dateTimeConfirmed: false,
|
|
5256
|
+
quote: null,
|
|
5257
|
+
quoteError: null
|
|
5258
|
+
});
|
|
5259
|
+
activateServiceProviderForms();
|
|
5260
|
+
await service_controller.loadMonth();
|
|
5261
|
+
} catch (error) {
|
|
5262
|
+
service_form_definitions.clear();
|
|
5263
|
+
clearServiceFormState(readErrorMessage(error, "Failed to load booking forms."));
|
|
5264
|
+
service_state.setKey("loading", false);
|
|
5265
|
+
throw error;
|
|
5266
|
+
}
|
|
5267
|
+
},
|
|
5268
|
+
async loadMonth() {
|
|
5269
|
+
const state = service_state.get();
|
|
5270
|
+
if (!state.service) return;
|
|
5271
|
+
service_state.setKey("loading", true);
|
|
5272
|
+
try {
|
|
5273
|
+
const chainedStart = service_chain_start.get();
|
|
5274
|
+
let from;
|
|
5275
|
+
let to;
|
|
5276
|
+
if (chainedStart) {
|
|
5277
|
+
from = chainedStart;
|
|
5278
|
+
to = chainedStart;
|
|
5279
|
+
} else {
|
|
5280
|
+
const month = state.currentMonth;
|
|
5281
|
+
from = Math.floor(Date.UTC(month.getFullYear(), month.getMonth(), 1) / 1e3);
|
|
5282
|
+
to = Math.floor(Date.UTC(month.getFullYear(), month.getMonth() + 1, 1) / 1e3);
|
|
5283
|
+
}
|
|
5284
|
+
const availability = await loadAvailability({
|
|
5285
|
+
service_id: state.service.id,
|
|
5286
|
+
from,
|
|
5287
|
+
to
|
|
5288
|
+
});
|
|
5289
|
+
service_state.setKey("availability", availability);
|
|
5290
|
+
service_state.setKey("calendar", serviceCalendar());
|
|
5291
|
+
} finally {
|
|
5292
|
+
service_state.setKey("loading", false);
|
|
5293
|
+
}
|
|
5294
|
+
},
|
|
5295
|
+
prevMonth() {
|
|
5296
|
+
const { currentMonth } = service_state.get();
|
|
5297
|
+
service_state.setKey("currentMonth", new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1));
|
|
5298
|
+
void service_controller.loadMonth();
|
|
5299
|
+
},
|
|
5300
|
+
nextMonth() {
|
|
5301
|
+
const { currentMonth } = service_state.get();
|
|
5302
|
+
service_state.setKey("currentMonth", new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1));
|
|
5303
|
+
void service_controller.loadMonth();
|
|
5304
|
+
},
|
|
5305
|
+
selectProvider(providerId) {
|
|
5306
|
+
const state = service_state.get();
|
|
5307
|
+
if (providerId && !resolveServiceProvider(state, providerId)) {
|
|
5308
|
+
throw new Error(`Provider ${providerId} is not configured for the selected service`);
|
|
5309
|
+
}
|
|
5310
|
+
service_state.set({
|
|
5311
|
+
...state,
|
|
5312
|
+
selectedProviderId: providerId,
|
|
5313
|
+
selectedDate: null,
|
|
5314
|
+
slots: [],
|
|
5315
|
+
selectedSlot: null,
|
|
5316
|
+
dateTimeConfirmed: false,
|
|
5317
|
+
quote: null,
|
|
5318
|
+
quoteError: null
|
|
5319
|
+
});
|
|
5320
|
+
activateServiceProviderForms(providerId);
|
|
5321
|
+
void service_controller.loadMonth();
|
|
5322
|
+
},
|
|
5323
|
+
selectDate(cell) {
|
|
5324
|
+
if (cell.blank || !cell.available) return;
|
|
5325
|
+
const state = service_state.get();
|
|
5326
|
+
service_state.set({
|
|
5327
|
+
...state,
|
|
5328
|
+
selectedDate: cell.iso,
|
|
5329
|
+
slots: computeServiceSlots(cell.iso),
|
|
5330
|
+
selectedSlot: null,
|
|
5331
|
+
dateTimeConfirmed: false,
|
|
5332
|
+
quote: null,
|
|
5333
|
+
quoteError: null
|
|
5334
|
+
});
|
|
5335
|
+
activateServiceProviderForms();
|
|
5336
|
+
service_state.setKey("calendar", serviceCalendar());
|
|
5337
|
+
},
|
|
5338
|
+
selectTimeSlot(slot) {
|
|
5339
|
+
const state = service_state.get();
|
|
5340
|
+
if (slot) {
|
|
5341
|
+
if (!state.service || slot.serviceId !== state.service.id) {
|
|
5342
|
+
throw new Error("The selected slot does not belong to the selected service");
|
|
5343
|
+
}
|
|
5344
|
+
if (state.selectedProviderId && slot.providerId !== state.selectedProviderId) {
|
|
5345
|
+
throw new Error("The selected slot does not belong to the selected provider");
|
|
5346
|
+
}
|
|
5347
|
+
if (!resolveServiceProvider(state, slot.providerId)) {
|
|
5348
|
+
throw new Error(`Provider ${slot.providerId} is not configured for the selected service`);
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
service_state.set({
|
|
5352
|
+
...state,
|
|
5353
|
+
selectedSlot: slot,
|
|
5354
|
+
dateTimeConfirmed: false,
|
|
5355
|
+
quote: null,
|
|
5356
|
+
quoteError: null
|
|
5357
|
+
});
|
|
5358
|
+
activateServiceProviderForms(slot?.providerId);
|
|
5359
|
+
},
|
|
5360
|
+
resetDateSelection() {
|
|
5361
|
+
service_state.set({
|
|
5362
|
+
...service_state.get(),
|
|
5363
|
+
selectedDate: null,
|
|
5364
|
+
slots: [],
|
|
5365
|
+
selectedSlot: null,
|
|
5366
|
+
dateTimeConfirmed: false,
|
|
5367
|
+
quote: null,
|
|
5368
|
+
quoteError: null
|
|
5369
|
+
});
|
|
5370
|
+
activateServiceProviderForms();
|
|
5371
|
+
},
|
|
5372
|
+
updateCalendar() {
|
|
5373
|
+
service_state.setKey("calendar", serviceCalendar());
|
|
5374
|
+
},
|
|
5375
|
+
findFirstAvailable() {
|
|
5376
|
+
for (const day of service_state.get().calendar) {
|
|
5377
|
+
if (!day.blank && day.available) {
|
|
5378
|
+
service_controller.selectDate(day);
|
|
5379
|
+
return;
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
},
|
|
5383
|
+
async addToCart(explicitSlots) {
|
|
5384
|
+
const state = service_state.get();
|
|
5385
|
+
const slots = explicitSlots || (state.selectedSlot ? [state.selectedSlot] : []);
|
|
5386
|
+
if (slots.length === 0) return;
|
|
5387
|
+
const first = slots[0];
|
|
5388
|
+
if (!state.service || first.serviceId !== state.service.id) {
|
|
5389
|
+
throw new Error("The booking slots do not belong to the selected service");
|
|
5390
|
+
}
|
|
5391
|
+
const relationship = resolveServiceProvider(state, first.providerId);
|
|
5392
|
+
if (!relationship) {
|
|
5393
|
+
throw new Error(`Provider ${first.providerId} is not configured for the selected service`);
|
|
5394
|
+
}
|
|
5395
|
+
let forms;
|
|
5396
|
+
try {
|
|
5397
|
+
forms = configuredServiceFormEntries(relationship);
|
|
5398
|
+
} catch (error) {
|
|
5399
|
+
service_form_state.setKey("error", readErrorMessage(error, "Booking forms are invalid."));
|
|
5400
|
+
throw error;
|
|
5401
|
+
}
|
|
5402
|
+
const displayName = serviceName(state.service, currentLocale());
|
|
5403
|
+
const enriched = slots.map((slot) => ({
|
|
5404
|
+
...slot,
|
|
5405
|
+
serviceName: displayName,
|
|
5406
|
+
date: slot.dateText
|
|
5407
|
+
}));
|
|
5408
|
+
const nextItems = [...service_items.get(), toServiceCartItem(enriched, forms)];
|
|
5409
|
+
await syncServiceCart(nextItems);
|
|
5410
|
+
service_state.set({
|
|
5411
|
+
...service_state.get(),
|
|
5412
|
+
selectedDate: null,
|
|
5413
|
+
slots: [],
|
|
5414
|
+
selectedSlot: null,
|
|
5415
|
+
dateTimeConfirmed: false,
|
|
5416
|
+
quote: null,
|
|
5417
|
+
quoteError: null
|
|
5418
|
+
});
|
|
5419
|
+
activateServiceProviderForms(service_state.get().selectedProviderId, true);
|
|
5420
|
+
service_state.setKey("calendar", serviceCalendar());
|
|
5421
|
+
},
|
|
5422
|
+
async removeFromCart(bookingId) {
|
|
5423
|
+
await syncServiceCart(service_items.get().filter((item) => item.id !== bookingId));
|
|
5424
|
+
},
|
|
5425
|
+
async clearCart() {
|
|
5426
|
+
await syncServiceCart([]);
|
|
5427
|
+
},
|
|
5428
|
+
async checkout(paymentMethodId, forms = []) {
|
|
5429
|
+
const state = service_state.get();
|
|
5430
|
+
const items = service_items.get();
|
|
5431
|
+
if (!items.length) throw new Error("Cart is empty");
|
|
5432
|
+
service_state.setKey("loading", true);
|
|
5433
|
+
try {
|
|
5434
|
+
const result = await checkout({
|
|
5435
|
+
service_items: items,
|
|
5436
|
+
payment_method_key: paymentMethodId,
|
|
5437
|
+
promo_code: state.promoCode || void 0,
|
|
5438
|
+
forms
|
|
5439
|
+
});
|
|
5440
|
+
service_state.setKey("cartId", cart.get()?.id || null);
|
|
5441
|
+
return result;
|
|
5442
|
+
} finally {
|
|
5443
|
+
service_state.setKey("loading", false);
|
|
5444
|
+
}
|
|
5445
|
+
},
|
|
5446
|
+
async fetchQuote(paymentMethodId, promoCode) {
|
|
5447
|
+
service_state.get();
|
|
5448
|
+
const items = service_items.get();
|
|
5449
|
+
if (!items.length) return null;
|
|
5450
|
+
service_state.setKey("fetchingQuote", true);
|
|
5451
|
+
service_state.setKey("quoteError", null);
|
|
5452
|
+
try {
|
|
5453
|
+
service_state.setKey("promoCode", promoCode || null);
|
|
5454
|
+
const response = await fetchQuote({
|
|
5455
|
+
service_items: items,
|
|
5456
|
+
payment_method_key: paymentMethodId,
|
|
5457
|
+
promo_code: promoCode || void 0
|
|
5458
|
+
});
|
|
5459
|
+
service_state.setKey("cartId", cart.get()?.id || null);
|
|
5460
|
+
service_state.setKey("quote", response);
|
|
5461
|
+
const methods = response?.payment_methods || market.get()?.payment_methods || [];
|
|
5462
|
+
if (methods.length) service_state.setKey("availablePaymentMethods", methods);
|
|
5463
|
+
return response;
|
|
5464
|
+
} catch (error) {
|
|
5465
|
+
service_state.setKey("quoteError", readErrorMessage(error, "Failed to fetch quote."));
|
|
5466
|
+
return null;
|
|
5467
|
+
} finally {
|
|
5468
|
+
service_state.setKey("fetchingQuote", false);
|
|
5469
|
+
}
|
|
5470
|
+
},
|
|
5471
|
+
getProvidersList() {
|
|
5472
|
+
return service_state.get().providers;
|
|
5473
|
+
},
|
|
5474
|
+
prevStep() {
|
|
5475
|
+
const current = serviceCurrentStepName();
|
|
5476
|
+
if (current === "review") {
|
|
5477
|
+
service_state.setKey("dateTimeConfirmed", false);
|
|
5478
|
+
return;
|
|
5479
|
+
}
|
|
5480
|
+
if (current === "datetime") {
|
|
5481
|
+
service_state.setKey("selectedSlot", null);
|
|
5482
|
+
service_state.setKey("dateTimeConfirmed", false);
|
|
5483
|
+
service_state.setKey("quote", null);
|
|
5484
|
+
service_state.setKey("quoteError", null);
|
|
5485
|
+
activateServiceProviderForms();
|
|
5486
|
+
}
|
|
5487
|
+
},
|
|
5488
|
+
nextStep() {
|
|
5489
|
+
if (serviceCurrentStepName() === "datetime" && service_can_proceed.get()) {
|
|
5490
|
+
service_state.setKey("dateTimeConfirmed", true);
|
|
5491
|
+
}
|
|
5492
|
+
},
|
|
5493
|
+
getServicePrice() {
|
|
5494
|
+
const state = service_state.get();
|
|
5495
|
+
const relationship = resolveServiceProvider(state);
|
|
5496
|
+
if (!relationship) return "";
|
|
5497
|
+
try {
|
|
5498
|
+
const price = priceForMarket(relationship.prices, currentMarketKey(), market.get()?.currency);
|
|
5499
|
+
return client.utils.formatPrice([price]);
|
|
5500
|
+
} catch {
|
|
5501
|
+
return "";
|
|
5502
|
+
}
|
|
5503
|
+
},
|
|
5504
|
+
formatDateDisplay: formatServiceDateDisplay,
|
|
5505
|
+
serviceItemsFromSlots(slots, forms = []) {
|
|
5506
|
+
return slots.length ? [toServiceCartItem(slots, forms)] : [];
|
|
5507
|
+
}
|
|
5508
|
+
};
|
|
5509
|
+
async function loadEntry(params, options) {
|
|
5510
|
+
cms_state.setKey("loading", true);
|
|
5511
|
+
cms_state.setKey("error", null);
|
|
5512
|
+
try {
|
|
5513
|
+
const { locale: nextLocale, market: nextMarket, ...entryParams } = params;
|
|
5514
|
+
setContext({ locale: nextLocale, market: nextMarket });
|
|
5515
|
+
if (entryParams.id) {
|
|
5516
|
+
const entry2 = await client.cms.entry.get(entryParams, options);
|
|
5517
|
+
const cacheKey = entryParams.key || entryParams.id || entry2.id;
|
|
5518
|
+
cms_state.setKey("entries", {
|
|
5519
|
+
...cms_state.get().entries,
|
|
5520
|
+
[cacheKey]: entry2
|
|
5521
|
+
});
|
|
5522
|
+
return entry2;
|
|
5523
|
+
}
|
|
5524
|
+
if (!entryParams.collection_id || !entryParams.key) {
|
|
5525
|
+
throw new Error("ArkyCmsEntryParams requires id, or collection_id and key");
|
|
5526
|
+
}
|
|
5527
|
+
const result = await client.cms.entry.find(
|
|
5528
|
+
{
|
|
5529
|
+
...entryParams,
|
|
5530
|
+
collection_id: entryParams.collection_id,
|
|
5531
|
+
key: entryParams.key,
|
|
5532
|
+
limit: 1
|
|
5533
|
+
},
|
|
5534
|
+
options
|
|
5535
|
+
);
|
|
5536
|
+
const entry = result.items?.[0];
|
|
5537
|
+
if (!entry) {
|
|
5538
|
+
throw new Error("CMS entry not found");
|
|
5539
|
+
}
|
|
5540
|
+
cms_state.setKey("entries", {
|
|
5541
|
+
...cms_state.get().entries,
|
|
5542
|
+
[entryParams.key]: entry
|
|
5543
|
+
});
|
|
5544
|
+
return entry;
|
|
5545
|
+
} catch (error) {
|
|
5546
|
+
cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS entry."));
|
|
5547
|
+
throw error;
|
|
5548
|
+
} finally {
|
|
5549
|
+
cms_state.setKey("loading", false);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
function formCacheKey(params) {
|
|
5553
|
+
const identifier = params.id ? `id:${params.id}` : params.key ? `key:${params.key}` : "missing";
|
|
5554
|
+
return identifier;
|
|
5555
|
+
}
|
|
5556
|
+
async function loadForm(params, options) {
|
|
5557
|
+
cms_state.setKey("loading", true);
|
|
5558
|
+
cms_state.setKey("error", null);
|
|
5559
|
+
try {
|
|
5560
|
+
const form = await client.cms.form.get(params, options);
|
|
5561
|
+
const forms = { ...cms_state.get().forms };
|
|
5562
|
+
forms[formCacheKey({ id: form.id })] = form;
|
|
5563
|
+
forms[formCacheKey({ key: form.key })] = form;
|
|
5564
|
+
cms_state.setKey("forms", forms);
|
|
5565
|
+
return form;
|
|
5566
|
+
} catch (error) {
|
|
5567
|
+
cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS form."));
|
|
5568
|
+
throw error;
|
|
5569
|
+
} finally {
|
|
5570
|
+
cms_state.setKey("loading", false);
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
async function submitForm(params, options) {
|
|
5574
|
+
await ensureSession();
|
|
5575
|
+
return client.cms.form.submit(params, options);
|
|
5576
|
+
}
|
|
5577
|
+
async function submitFormByKey(params, options) {
|
|
5578
|
+
const form = await loadForm({ key: params.key }, options);
|
|
5579
|
+
const entry = createFormEntryFromValues(form, params.values);
|
|
5580
|
+
return submitForm({ form_id: form.id, fields: entry.fields }, options);
|
|
5581
|
+
}
|
|
5582
|
+
async function loadProducts(params = {}, options) {
|
|
5583
|
+
eshop_state.setKey("loading_products", true);
|
|
5584
|
+
eshop_state.setKey("error", null);
|
|
5585
|
+
try {
|
|
5586
|
+
const response = await client.eshop.product.find(params, options);
|
|
5587
|
+
eshop_state.setKey("products", response.items || []);
|
|
5588
|
+
eshop_state.setKey("product_cursor", response.cursor || null);
|
|
5589
|
+
return response;
|
|
5590
|
+
} catch (error) {
|
|
5591
|
+
eshop_state.setKey("error", readErrorMessage(error, "Failed to load products."));
|
|
5592
|
+
throw error;
|
|
5593
|
+
} finally {
|
|
5594
|
+
eshop_state.setKey("loading_products", false);
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
async function loadServices(params = {}, options) {
|
|
5598
|
+
eshop_state.setKey("loading_services", true);
|
|
5599
|
+
eshop_state.setKey("error", null);
|
|
5600
|
+
try {
|
|
5601
|
+
const response = await client.eshop.service.find(params, options);
|
|
5602
|
+
eshop_state.setKey("services", response.items || []);
|
|
5603
|
+
eshop_state.setKey("service_cursor", response.cursor || null);
|
|
5604
|
+
return response;
|
|
5605
|
+
} catch (error) {
|
|
5606
|
+
eshop_state.setKey("error", readErrorMessage(error, "Failed to load services."));
|
|
5607
|
+
throw error;
|
|
5608
|
+
} finally {
|
|
5609
|
+
eshop_state.setKey("loading_services", false);
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
async function loadProviders(params = {}, options) {
|
|
5613
|
+
eshop_state.setKey("loading_providers", true);
|
|
5614
|
+
eshop_state.setKey("error", null);
|
|
5615
|
+
try {
|
|
5616
|
+
const response = await client.eshop.provider.find(params, options);
|
|
5617
|
+
eshop_state.setKey("providers", response.items || []);
|
|
5618
|
+
eshop_state.setKey("provider_cursor", response.cursor || null);
|
|
5619
|
+
return response;
|
|
5620
|
+
} catch (error) {
|
|
5621
|
+
eshop_state.setKey("error", readErrorMessage(error, "Failed to load providers."));
|
|
5622
|
+
throw error;
|
|
5623
|
+
} finally {
|
|
5624
|
+
eshop_state.setKey("loading_providers", false);
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
async function loadAvailability(params, options) {
|
|
5628
|
+
eshop_state.setKey("loading_availability", true);
|
|
5629
|
+
eshop_state.setKey("error", null);
|
|
5630
|
+
try {
|
|
5631
|
+
const response = await client.eshop.service.getAvailability(params, options);
|
|
5632
|
+
eshop_state.setKey("availability", response);
|
|
5633
|
+
return response;
|
|
5634
|
+
} catch (error) {
|
|
5635
|
+
eshop_state.setKey("error", readErrorMessage(error, "Failed to load availability."));
|
|
5636
|
+
throw error;
|
|
5637
|
+
} finally {
|
|
5638
|
+
eshop_state.setKey("loading_availability", false);
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
async function useExperiment(params) {
|
|
5642
|
+
await ensureSession();
|
|
5643
|
+
const input = typeof params === "string" ? { key: params } : params;
|
|
5644
|
+
return client.experiments.use(input);
|
|
5645
|
+
}
|
|
5646
|
+
async function trackAction(params) {
|
|
5647
|
+
await ensureSession();
|
|
5648
|
+
return client.action.track(params);
|
|
5649
|
+
}
|
|
5650
|
+
const cart_store = {
|
|
5651
|
+
cart,
|
|
5652
|
+
product_items,
|
|
5653
|
+
service_items,
|
|
5654
|
+
quote_result: quote,
|
|
5655
|
+
promo_code,
|
|
5656
|
+
last_order,
|
|
5657
|
+
status: cart_status,
|
|
5658
|
+
product_item_count,
|
|
5659
|
+
service_item_count,
|
|
5660
|
+
item_count,
|
|
5661
|
+
snapshot,
|
|
5662
|
+
load: ensureCart,
|
|
5663
|
+
refresh: syncCart,
|
|
5664
|
+
addProduct,
|
|
5665
|
+
setProductQuantity,
|
|
5666
|
+
removeProduct,
|
|
5667
|
+
addServiceItem,
|
|
5668
|
+
removeServiceItem,
|
|
5669
|
+
clear: clearCart,
|
|
5670
|
+
clearLocal: clearLocalCart,
|
|
5671
|
+
quote: fetchQuote,
|
|
5672
|
+
checkout,
|
|
5673
|
+
payment: {
|
|
5674
|
+
controller: payment_controller,
|
|
5675
|
+
ready: payment_ready,
|
|
5676
|
+
setController: setPaymentController,
|
|
5677
|
+
getController: () => payment_controller.get(),
|
|
5678
|
+
mount: mountPayment,
|
|
5679
|
+
update: updatePaymentController,
|
|
5680
|
+
destroy: destroyPaymentController
|
|
5681
|
+
},
|
|
5682
|
+
applyPromoCode(code, input = {}) {
|
|
5683
|
+
return fetchQuote({ ...input, promo_code: code });
|
|
5684
|
+
},
|
|
5685
|
+
removePromoCode(input = {}) {
|
|
5686
|
+
return fetchQuote({ ...input, promo_code: null });
|
|
5687
|
+
},
|
|
5688
|
+
selectShippingMethod(id) {
|
|
5689
|
+
cart_status.setKey("selected_shipping_method_id", id);
|
|
5690
|
+
},
|
|
5691
|
+
locationToAddress,
|
|
5692
|
+
createFormEntry,
|
|
5693
|
+
buildItems: checkoutItems,
|
|
5694
|
+
buildProductItems: toProductCheckoutItems,
|
|
5695
|
+
buildServiceItems: toServiceCheckoutItems
|
|
5696
|
+
};
|
|
5697
|
+
const product_store = {
|
|
5698
|
+
get: (params, options) => client.eshop.product.get(params, options),
|
|
5699
|
+
list: loadProducts
|
|
5700
|
+
};
|
|
5701
|
+
const service_store = {
|
|
5702
|
+
get: (params, options) => client.eshop.service.get(params, options),
|
|
5703
|
+
list: loadServices,
|
|
5704
|
+
listProviders: (params, options) => client.eshop.service.findProviders(params, options),
|
|
5705
|
+
getAvailability: loadAvailability,
|
|
5706
|
+
state: service_state,
|
|
5707
|
+
form_state: service_form_state,
|
|
5708
|
+
form_groups: service_form_groups,
|
|
5709
|
+
form_blocks: service_form_blocks,
|
|
5710
|
+
current_step_name: service_current_step_name,
|
|
5711
|
+
can_proceed: service_can_proceed,
|
|
5712
|
+
month_year: service_month_year,
|
|
5713
|
+
chain_start: service_chain_start,
|
|
5714
|
+
total_steps: service_total_steps,
|
|
5715
|
+
steps: service_steps,
|
|
5716
|
+
current_step: service_current_step,
|
|
5717
|
+
initialize: service_controller.initialize,
|
|
5718
|
+
select: service_controller.select,
|
|
5719
|
+
setTimezone: service_controller.setTimezone,
|
|
5720
|
+
loadMonth: service_controller.loadMonth,
|
|
5721
|
+
prevMonth: service_controller.prevMonth,
|
|
5722
|
+
nextMonth: service_controller.nextMonth,
|
|
5723
|
+
selectProvider: service_controller.selectProvider,
|
|
5724
|
+
selectDate: service_controller.selectDate,
|
|
5725
|
+
selectTimeSlot: service_controller.selectTimeSlot,
|
|
5726
|
+
resetDateSelection: service_controller.resetDateSelection,
|
|
5727
|
+
updateCalendar: service_controller.updateCalendar,
|
|
5728
|
+
findFirstAvailable: service_controller.findFirstAvailable,
|
|
5729
|
+
addToCart: service_controller.addToCart,
|
|
5730
|
+
removeFromCart: service_controller.removeFromCart,
|
|
5731
|
+
clearCart: service_controller.clearCart,
|
|
5732
|
+
getProvidersList: service_controller.getProvidersList,
|
|
5733
|
+
prevStep: service_controller.prevStep,
|
|
5734
|
+
nextStep: service_controller.nextStep,
|
|
5735
|
+
getServicePrice: service_controller.getServicePrice,
|
|
5736
|
+
formatDateDisplay: service_controller.formatDateDisplay,
|
|
5737
|
+
serviceItemsFromSlots: service_controller.serviceItemsFromSlots
|
|
5738
|
+
};
|
|
5739
|
+
return {
|
|
5740
|
+
client,
|
|
5741
|
+
session,
|
|
5742
|
+
setup,
|
|
5743
|
+
market,
|
|
5744
|
+
market_key,
|
|
5745
|
+
locale,
|
|
5746
|
+
currency,
|
|
5747
|
+
allowed_payment_methods,
|
|
5748
|
+
payment_config,
|
|
5749
|
+
identify,
|
|
5750
|
+
identifyContactEmailIfMissing,
|
|
5751
|
+
verify: client.verify,
|
|
5752
|
+
me: client.me,
|
|
5753
|
+
logout: client.logout,
|
|
5754
|
+
onAuthStateChanged: client.onAuthStateChanged,
|
|
5755
|
+
get isAuthenticated() {
|
|
5756
|
+
return client.isAuthenticated;
|
|
5757
|
+
},
|
|
5758
|
+
setMarket,
|
|
5759
|
+
setLocale,
|
|
5760
|
+
setContext,
|
|
5761
|
+
getMarket: currentMarketKey,
|
|
5762
|
+
getLocale: currentLocale,
|
|
5763
|
+
cms: {
|
|
5764
|
+
state: cms_state,
|
|
5765
|
+
collection: {
|
|
5766
|
+
get: (params, options) => client.cms.collection.get(params, options)
|
|
5767
|
+
},
|
|
5768
|
+
entry: {
|
|
5769
|
+
get: loadEntry,
|
|
5770
|
+
find: (params, options) => client.cms.entry.find(params, options)
|
|
5771
|
+
},
|
|
5772
|
+
form: {
|
|
5773
|
+
get: loadForm,
|
|
5774
|
+
submit: submitForm,
|
|
5775
|
+
submitByKey: submitFormByKey
|
|
5776
|
+
},
|
|
5777
|
+
taxonomy: client.cms.taxonomy
|
|
5778
|
+
},
|
|
5779
|
+
eshop: {
|
|
5780
|
+
state: eshop_state,
|
|
5781
|
+
product: product_store,
|
|
5782
|
+
service: service_store,
|
|
5783
|
+
provider: {
|
|
5784
|
+
get: (params, options) => client.eshop.provider.get(params, options),
|
|
5785
|
+
list: loadProviders
|
|
5786
|
+
},
|
|
5787
|
+
order: client.eshop.order,
|
|
5788
|
+
cart: cart_store
|
|
5789
|
+
},
|
|
5790
|
+
crm: client.crm,
|
|
5791
|
+
action: {
|
|
5792
|
+
track(params) {
|
|
5793
|
+
return trackAction(params);
|
|
5794
|
+
},
|
|
5795
|
+
pageView(payload = {}) {
|
|
5796
|
+
return trackAction({ key: "page.view", payload });
|
|
5797
|
+
},
|
|
5798
|
+
state: nanostores.atom(null)
|
|
5799
|
+
},
|
|
5800
|
+
experiments: {
|
|
5801
|
+
use: useExperiment
|
|
5802
|
+
},
|
|
5803
|
+
support: client.support,
|
|
5804
|
+
store: {
|
|
5805
|
+
...client.store,
|
|
5806
|
+
setup,
|
|
5807
|
+
load: loadSetup
|
|
5808
|
+
},
|
|
5809
|
+
utils: client.utils
|
|
5810
|
+
};
|
|
5811
|
+
}
|
|
5812
|
+
function initializeStore(publishableKey, config, scopedClient) {
|
|
5813
|
+
const store = initializeStoreCore(publishableKey, config, scopedClient);
|
|
5814
|
+
return Object.assign(store, {
|
|
5815
|
+
withContext(context) {
|
|
5816
|
+
return initializeStore(
|
|
5817
|
+
publishableKey,
|
|
5818
|
+
{
|
|
5819
|
+
...config,
|
|
5820
|
+
locale: context.locale ?? store.getLocale(),
|
|
5821
|
+
market: context.market ?? store.getMarket()
|
|
5822
|
+
},
|
|
5823
|
+
store.client.withContext(context)
|
|
5824
|
+
);
|
|
5825
|
+
}
|
|
5826
|
+
});
|
|
5827
|
+
}
|
|
5828
|
+
function initialize(publishableKey, options = {}) {
|
|
5829
|
+
return initializeStore(publishableKey, options);
|
|
5830
|
+
}
|
|
5831
|
+
|
|
5832
|
+
// src/index.ts
|
|
5833
|
+
var SDK_VERSION = "0.11.0";
|
|
5834
|
+
var SUPPORTED_FRAMEWORKS = [
|
|
5835
|
+
"astro",
|
|
5836
|
+
"react",
|
|
5837
|
+
"vue",
|
|
5838
|
+
"svelte",
|
|
5839
|
+
"vanilla"
|
|
5840
|
+
];
|
|
5841
|
+
function createUtilitySurface(apiConfig) {
|
|
5842
|
+
return {
|
|
5843
|
+
getImageUrl: (imageBlock, isBlock2 = true) => getImageUrl(imageBlock, isBlock2),
|
|
5844
|
+
getBlockValue,
|
|
5845
|
+
getBlockTextValue,
|
|
5846
|
+
getBlockContentValue,
|
|
5847
|
+
getBlockValues,
|
|
5848
|
+
getBlockLabel,
|
|
5849
|
+
getBlockObjectValues,
|
|
5850
|
+
getBlockFromArray,
|
|
5851
|
+
formatBlockValue,
|
|
5852
|
+
prepareBlocksForSubmission,
|
|
5853
|
+
extractBlockValues,
|
|
5854
|
+
formatPrice: (prices) => formatPrice(prices, apiConfig.market),
|
|
5855
|
+
getPriceAmount: (prices) => getPriceAmount(prices, apiConfig.market),
|
|
5856
|
+
formatPayment,
|
|
5857
|
+
formatMinor,
|
|
5858
|
+
getCurrencySymbol,
|
|
5859
|
+
getCurrencyName,
|
|
5860
|
+
validatePhoneNumber,
|
|
5861
|
+
tzGroups,
|
|
5862
|
+
findTimeZone,
|
|
5863
|
+
slugify,
|
|
5864
|
+
humanize,
|
|
5865
|
+
categorify,
|
|
5866
|
+
formatDate,
|
|
5867
|
+
getSvgContentForAstro,
|
|
5868
|
+
fetchSvgContent,
|
|
5869
|
+
injectSvgIntoElement,
|
|
5870
|
+
isValidKey,
|
|
5871
|
+
validateKey,
|
|
5872
|
+
toKey,
|
|
5873
|
+
nameToKey,
|
|
5874
|
+
getAvailableStock,
|
|
5875
|
+
getReservedStock,
|
|
5876
|
+
hasStock,
|
|
5877
|
+
getInventoryAt,
|
|
5878
|
+
getFirstAvailableFCId
|
|
5879
|
+
};
|
|
5880
|
+
}
|
|
5881
|
+
var ADMIN_STORAGE_KEY = "arky_admin_session";
|
|
5882
|
+
function readAdminSession() {
|
|
5883
|
+
if (typeof window === "undefined") return null;
|
|
5884
|
+
try {
|
|
5885
|
+
const raw = localStorage.getItem(ADMIN_STORAGE_KEY);
|
|
5886
|
+
return raw ? JSON.parse(raw) : null;
|
|
5887
|
+
} catch {
|
|
5888
|
+
return null;
|
|
5889
|
+
}
|
|
5890
|
+
}
|
|
5891
|
+
function writeAdminSession(s) {
|
|
5892
|
+
if (typeof window === "undefined") return;
|
|
5893
|
+
if (s) {
|
|
5894
|
+
localStorage.setItem(ADMIN_STORAGE_KEY, JSON.stringify(s));
|
|
5895
|
+
} else {
|
|
5896
|
+
localStorage.removeItem(ADMIN_STORAGE_KEY);
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
function createAdmin(config) {
|
|
5900
|
+
const locale = config.locale || "en";
|
|
5901
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5902
|
+
function toPublic(s) {
|
|
5903
|
+
return s ? { email: s.email } : null;
|
|
5904
|
+
}
|
|
5905
|
+
function emit() {
|
|
5906
|
+
const pub = toPublic(readAdminSession());
|
|
5907
|
+
for (const l of listeners) {
|
|
5908
|
+
Promise.resolve().then(() => l(pub)).catch(() => {
|
|
5909
|
+
});
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
const updateSession = (updater) => {
|
|
5913
|
+
if (config.apiToken) return;
|
|
5914
|
+
const prev = readAdminSession();
|
|
5915
|
+
const next = updater(prev);
|
|
5916
|
+
writeAdminSession(next);
|
|
5917
|
+
emit();
|
|
5918
|
+
};
|
|
5919
|
+
const authStorage = config.apiToken ? {
|
|
5920
|
+
getTokens: () => ({ access_token: config.apiToken }),
|
|
5921
|
+
onTokensRefreshed: () => {
|
|
5922
|
+
},
|
|
5923
|
+
onForcedLogout: () => {
|
|
5924
|
+
}
|
|
5925
|
+
} : {
|
|
5926
|
+
getTokens() {
|
|
5927
|
+
const s = readAdminSession();
|
|
5928
|
+
if (!s) return null;
|
|
5929
|
+
return {
|
|
5930
|
+
access_token: s.access_token,
|
|
5931
|
+
refresh_token: s.refresh_token,
|
|
5932
|
+
access_expires_at: s.access_expires_at
|
|
5933
|
+
};
|
|
5934
|
+
},
|
|
5935
|
+
onTokensRefreshed(tokens) {
|
|
5936
|
+
updateSession(
|
|
5937
|
+
(prev) => prev ? {
|
|
5938
|
+
...prev,
|
|
5939
|
+
access_token: tokens.access_token,
|
|
5940
|
+
refresh_token: tokens.refresh_token ?? prev.refresh_token,
|
|
5941
|
+
access_expires_at: tokens.access_expires_at ?? prev.access_expires_at
|
|
5942
|
+
} : null
|
|
5943
|
+
);
|
|
5944
|
+
},
|
|
5945
|
+
onForcedLogout() {
|
|
5946
|
+
updateSession(() => null);
|
|
5947
|
+
}
|
|
5948
|
+
};
|
|
5949
|
+
const httpClient = createHttpClient({
|
|
5950
|
+
baseUrl: config.baseUrl,
|
|
5951
|
+
storeId: config.storeId,
|
|
5952
|
+
refreshPath: config.refreshPath,
|
|
5953
|
+
navigate: config.navigate,
|
|
5954
|
+
loginFallbackPath: config.loginFallbackPath,
|
|
5955
|
+
authStorage
|
|
5956
|
+
});
|
|
5957
|
+
const apiConfig = {
|
|
5958
|
+
httpClient,
|
|
5959
|
+
storeId: config.storeId,
|
|
5960
|
+
baseUrl: config.baseUrl,
|
|
5961
|
+
market: config.market,
|
|
5962
|
+
locale,
|
|
5963
|
+
authStorage
|
|
4250
5964
|
};
|
|
4251
5965
|
const accountApi = createAccountApi(apiConfig);
|
|
4252
5966
|
const authApi = createAuthApi(apiConfig, updateSession);
|
|
@@ -4314,6 +6028,7 @@ function createAdmin(config) {
|
|
|
4314
6028
|
update: storeApi.updateStore,
|
|
4315
6029
|
get: storeApi.getStore,
|
|
4316
6030
|
find: storeApi.getStores,
|
|
6031
|
+
regeneratePublishableKey: storeApi.regeneratePublishableKey,
|
|
4317
6032
|
subscription: {
|
|
4318
6033
|
get: storeApi.getSubscription,
|
|
4319
6034
|
getPlans: storeApi.getSubscriptionPlans,
|
|
@@ -4632,6 +6347,8 @@ function createAdmin(config) {
|
|
|
4632
6347
|
};
|
|
4633
6348
|
return sdk;
|
|
4634
6349
|
}
|
|
6350
|
+
var DEFAULT_STOREFRONT_API_URL = "https://api.arky.io";
|
|
6351
|
+
var storefrontScopeSequence = 0;
|
|
4635
6352
|
function defaultStorefrontSessionStorage() {
|
|
4636
6353
|
if (typeof window === "undefined") return null;
|
|
4637
6354
|
try {
|
|
@@ -4640,132 +6357,177 @@ function defaultStorefrontSessionStorage() {
|
|
|
4640
6357
|
return null;
|
|
4641
6358
|
}
|
|
4642
6359
|
}
|
|
4643
|
-
function
|
|
4644
|
-
const
|
|
4645
|
-
|
|
6360
|
+
function normalizeStorefrontApiUrl(value) {
|
|
6361
|
+
const input = value?.trim() || DEFAULT_STOREFRONT_API_URL;
|
|
6362
|
+
let parsed;
|
|
6363
|
+
try {
|
|
6364
|
+
parsed = new URL(input);
|
|
6365
|
+
} catch {
|
|
6366
|
+
throw new Error("Storefront apiUrl must be a valid HTTP(S) URL");
|
|
6367
|
+
}
|
|
6368
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
6369
|
+
throw new Error("Storefront apiUrl must use HTTP or HTTPS");
|
|
6370
|
+
}
|
|
6371
|
+
return input.replace(/\/+$/, "");
|
|
4646
6372
|
}
|
|
4647
|
-
function
|
|
4648
|
-
|
|
4649
|
-
|
|
6373
|
+
function validatePublishableKey(publishableKey) {
|
|
6374
|
+
if (typeof publishableKey !== "string") {
|
|
6375
|
+
throw new Error(
|
|
6376
|
+
"A valid Arky publishable key is required (arky_pk_ followed by 43 URL-safe characters)"
|
|
6377
|
+
);
|
|
6378
|
+
}
|
|
6379
|
+
const key = publishableKey.trim();
|
|
6380
|
+
if (!/^arky_pk_[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/.test(key)) {
|
|
6381
|
+
throw new Error(
|
|
6382
|
+
"A valid Arky publishable key is required (arky_pk_ followed by 43 URL-safe characters)"
|
|
6383
|
+
);
|
|
6384
|
+
}
|
|
6385
|
+
return key;
|
|
6386
|
+
}
|
|
6387
|
+
function publishableKeyFingerprint(publishableKey) {
|
|
6388
|
+
let hashA = 2166136261;
|
|
6389
|
+
let hashB = 2654435769;
|
|
6390
|
+
for (let index = 0; index < publishableKey.length; index += 1) {
|
|
6391
|
+
const code = publishableKey.charCodeAt(index);
|
|
6392
|
+
hashA = Math.imul(hashA ^ code, 16777619);
|
|
6393
|
+
hashB = Math.imul(hashB ^ code, 2246822507);
|
|
6394
|
+
}
|
|
6395
|
+
return `${(hashA >>> 0).toString(36)}${(hashB >>> 0).toString(36)}`;
|
|
6396
|
+
}
|
|
6397
|
+
function storefrontSessionStorageKey(apiUrl, publishableKey) {
|
|
6398
|
+
return `arky_visitor_session:${encodeURIComponent(apiUrl.toLowerCase())}:${publishableKeyFingerprint(publishableKey)}`;
|
|
6399
|
+
}
|
|
6400
|
+
function isVisitorSessionToken(value) {
|
|
6401
|
+
return Boolean(value && /^arky_vst_[0-9a-f]{64}$/.test(value));
|
|
6402
|
+
}
|
|
6403
|
+
function createStorefrontClientCore(publishableKeyInput, options = {}, isolatedSession = false) {
|
|
6404
|
+
const publishableKey = validatePublishableKey(publishableKeyInput);
|
|
6405
|
+
const apiUrl = normalizeStorefrontApiUrl(options.apiUrl);
|
|
6406
|
+
let locale = options.locale?.trim() || "";
|
|
6407
|
+
let market = options.market?.trim() || "";
|
|
4650
6408
|
const listeners = /* @__PURE__ */ new Set();
|
|
4651
|
-
let
|
|
6409
|
+
let identifyPromise = null;
|
|
4652
6410
|
let identityTail = Promise.resolve();
|
|
4653
|
-
|
|
6411
|
+
let setupPromise = null;
|
|
6412
|
+
let setupValue = null;
|
|
6413
|
+
const explicitSessionStorage = options.sessionStorage;
|
|
6414
|
+
const sessionStorage = isolatedSession ? explicitSessionStorage || null : explicitSessionStorage || defaultStorefrontSessionStorage();
|
|
6415
|
+
const canCreateVisitorSession = typeof window !== "undefined" || Boolean(explicitSessionStorage);
|
|
6416
|
+
const storageKey = `${storefrontSessionStorageKey(apiUrl, publishableKey)}${isolatedSession ? `:scope:${++storefrontScopeSequence}` : ""}`;
|
|
4654
6417
|
let memorySession = null;
|
|
4655
|
-
|
|
4656
|
-
|
|
6418
|
+
let memorySessionToken = null;
|
|
6419
|
+
function readSessionToken() {
|
|
6420
|
+
if (!sessionStorage) return memorySessionToken;
|
|
4657
6421
|
try {
|
|
4658
|
-
const
|
|
4659
|
-
|
|
4660
|
-
);
|
|
4661
|
-
return raw ? JSON.parse(raw) : null;
|
|
6422
|
+
const stored = sessionStorage.getItem(storageKey);
|
|
6423
|
+
return isVisitorSessionToken(stored) ? stored : memorySessionToken;
|
|
4662
6424
|
} catch {
|
|
4663
|
-
return
|
|
6425
|
+
return memorySessionToken;
|
|
4664
6426
|
}
|
|
4665
6427
|
}
|
|
4666
6428
|
function writeContactSession(session) {
|
|
4667
6429
|
memorySession = session;
|
|
6430
|
+
memorySessionToken = session?.sessionToken || null;
|
|
4668
6431
|
if (!sessionStorage) return;
|
|
4669
|
-
const key = storefrontSessionStorageKey(config.baseUrl, config.storeId);
|
|
4670
6432
|
try {
|
|
4671
6433
|
if (session) {
|
|
4672
|
-
sessionStorage.setItem(
|
|
6434
|
+
sessionStorage.setItem(storageKey, session.sessionToken);
|
|
4673
6435
|
} else {
|
|
4674
|
-
sessionStorage.removeItem(
|
|
6436
|
+
sessionStorage.removeItem(storageKey);
|
|
4675
6437
|
}
|
|
4676
6438
|
} catch {
|
|
4677
6439
|
}
|
|
4678
6440
|
}
|
|
4679
6441
|
function toPublic(s) {
|
|
4680
|
-
return s ? { contact: s.contact
|
|
6442
|
+
return s ? { contact: s.contact } : null;
|
|
4681
6443
|
}
|
|
4682
6444
|
function emit() {
|
|
4683
|
-
const pub = toPublic(
|
|
6445
|
+
const pub = toPublic(memorySession);
|
|
4684
6446
|
for (const l of listeners) {
|
|
4685
6447
|
Promise.resolve().then(() => l(pub)).catch(() => {
|
|
4686
6448
|
});
|
|
4687
6449
|
}
|
|
4688
6450
|
}
|
|
4689
6451
|
const updateSession = (updater) => {
|
|
4690
|
-
|
|
4691
|
-
const prev = readContactSession();
|
|
4692
|
-
const next = updater(prev);
|
|
6452
|
+
const next = updater(memorySession);
|
|
4693
6453
|
writeContactSession(next);
|
|
4694
6454
|
emit();
|
|
4695
6455
|
};
|
|
4696
|
-
const authStorage =
|
|
4697
|
-
getTokens: () => ({ access_token: config.apiToken }),
|
|
4698
|
-
onTokensRefreshed: () => {
|
|
4699
|
-
},
|
|
4700
|
-
onForcedLogout: () => {
|
|
4701
|
-
}
|
|
4702
|
-
} : {
|
|
6456
|
+
const authStorage = {
|
|
4703
6457
|
getTokens() {
|
|
4704
|
-
const
|
|
4705
|
-
return
|
|
6458
|
+
const sessionToken = readSessionToken();
|
|
6459
|
+
return sessionToken ? { access_token: sessionToken } : null;
|
|
4706
6460
|
},
|
|
4707
6461
|
onTokensRefreshed() {
|
|
4708
6462
|
},
|
|
4709
6463
|
onForcedLogout() {
|
|
4710
|
-
|
|
6464
|
+
identifyPromise = null;
|
|
4711
6465
|
updateSession(() => null);
|
|
4712
6466
|
}
|
|
4713
6467
|
};
|
|
6468
|
+
let recoverUnauthorized = async () => false;
|
|
6469
|
+
let visitorRecoveryPromise = null;
|
|
4714
6470
|
const httpClient = createHttpClient({
|
|
4715
|
-
baseUrl:
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
6471
|
+
baseUrl: apiUrl,
|
|
6472
|
+
authStorage,
|
|
6473
|
+
storefrontMode: true,
|
|
6474
|
+
forcedHeaders: () => ({
|
|
6475
|
+
"X-Arky-Publishable-Key": publishableKey,
|
|
6476
|
+
...locale ? { "X-Arky-Locale": locale } : {},
|
|
6477
|
+
...market ? { "X-Arky-Market": market } : {}
|
|
6478
|
+
}),
|
|
6479
|
+
onUnauthorized: ({ authorizationToken, path }) => recoverUnauthorized(authorizationToken, path)
|
|
4721
6480
|
});
|
|
4722
6481
|
const apiConfig = {
|
|
4723
6482
|
httpClient,
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
market
|
|
6483
|
+
apiUrl,
|
|
6484
|
+
publishableKey,
|
|
6485
|
+
market,
|
|
4727
6486
|
locale,
|
|
4728
6487
|
authStorage
|
|
4729
6488
|
};
|
|
4730
|
-
|
|
6489
|
+
function requireVisitorSessionCapability() {
|
|
6490
|
+
if (!canCreateVisitorSession) {
|
|
6491
|
+
throw new Error(
|
|
6492
|
+
"Stateful storefront operations during SSR require an explicit request-local sessionStorage adapter"
|
|
6493
|
+
);
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
async function getSetup(requestOptions) {
|
|
6497
|
+
if (setupValue) return setupValue;
|
|
6498
|
+
if (setupPromise) return setupPromise;
|
|
6499
|
+
setupPromise = httpClient.get(
|
|
6500
|
+
"/v1/storefront",
|
|
6501
|
+
requestOptions
|
|
6502
|
+
).then((setup) => {
|
|
6503
|
+
setupValue = setup;
|
|
6504
|
+
return setup;
|
|
6505
|
+
}).finally(() => {
|
|
6506
|
+
setupPromise = null;
|
|
6507
|
+
});
|
|
6508
|
+
return setupPromise;
|
|
6509
|
+
}
|
|
6510
|
+
async function ensureVisitorSession() {
|
|
6511
|
+
if (readSessionToken()) return;
|
|
6512
|
+
requireVisitorSessionCapability();
|
|
6513
|
+
await identify();
|
|
6514
|
+
}
|
|
6515
|
+
const storefrontApi = createStorefrontApi(apiConfig, updateSession, {
|
|
6516
|
+
ensureVisitorSession,
|
|
6517
|
+
getSetup
|
|
6518
|
+
});
|
|
4731
6519
|
const contactApi = storefrontApi.crm.contact;
|
|
4732
6520
|
function identify(params) {
|
|
4733
|
-
|
|
4734
|
-
|
|
6521
|
+
requireVisitorSessionCapability();
|
|
6522
|
+
if (params?.market !== void 0) setMarket(params.market);
|
|
4735
6523
|
const isBareCall = !params?.email && !params?.verify;
|
|
4736
|
-
if (isBareCall &&
|
|
6524
|
+
if (isBareCall && identifyPromise) return identifyPromise;
|
|
4737
6525
|
const run = async () => {
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
market,
|
|
4744
|
-
email: params?.email
|
|
4745
|
-
}));
|
|
4746
|
-
return {
|
|
4747
|
-
contact: result.contact,
|
|
4748
|
-
store: result.store,
|
|
4749
|
-
market: result.market,
|
|
4750
|
-
verification_challenge: result.verification_challenge
|
|
4751
|
-
};
|
|
4752
|
-
} catch (err) {
|
|
4753
|
-
const e = err;
|
|
4754
|
-
const status = e?.statusCode || e?.status || e?.response?.status;
|
|
4755
|
-
if (isBareCall && status === 401) {
|
|
4756
|
-
updateSession(() => null);
|
|
4757
|
-
const result = await contactApi.identify({
|
|
4758
|
-
market
|
|
4759
|
-
});
|
|
4760
|
-
return {
|
|
4761
|
-
contact: result.contact,
|
|
4762
|
-
store: result.store,
|
|
4763
|
-
market: result.market,
|
|
4764
|
-
verification_challenge: result.verification_challenge
|
|
4765
|
-
};
|
|
4766
|
-
}
|
|
4767
|
-
throw err;
|
|
4768
|
-
}
|
|
6526
|
+
const result = await (params?.verify ? contactApi.requestCode({ email: params.email }) : contactApi.identify({ email: params?.email }));
|
|
6527
|
+
return {
|
|
6528
|
+
contact: result.contact,
|
|
6529
|
+
verification_challenge: result.verification_challenge
|
|
6530
|
+
};
|
|
4769
6531
|
};
|
|
4770
6532
|
const promise = identityTail.then(run);
|
|
4771
6533
|
identityTail = promise.then(
|
|
@@ -4773,49 +6535,88 @@ function createStorefrontClient(config) {
|
|
|
4773
6535
|
() => void 0
|
|
4774
6536
|
);
|
|
4775
6537
|
if (isBareCall) {
|
|
4776
|
-
|
|
6538
|
+
identifyPromise = promise;
|
|
4777
6539
|
void promise.then(
|
|
4778
6540
|
() => {
|
|
4779
|
-
if (
|
|
6541
|
+
if (identifyPromise === promise) identifyPromise = null;
|
|
4780
6542
|
},
|
|
4781
6543
|
() => {
|
|
4782
|
-
if (
|
|
6544
|
+
if (identifyPromise === promise) identifyPromise = null;
|
|
4783
6545
|
}
|
|
4784
6546
|
);
|
|
4785
6547
|
}
|
|
4786
6548
|
return promise;
|
|
4787
6549
|
}
|
|
4788
6550
|
async function verify(params) {
|
|
6551
|
+
requireVisitorSessionCapability();
|
|
4789
6552
|
const result = await contactApi.verify(params);
|
|
4790
|
-
|
|
4791
|
-
return result;
|
|
6553
|
+
identifyPromise = null;
|
|
6554
|
+
return { contact: result.contact };
|
|
6555
|
+
}
|
|
6556
|
+
async function me() {
|
|
6557
|
+
await ensureVisitorSession();
|
|
6558
|
+
return contactApi.getMe();
|
|
4792
6559
|
}
|
|
4793
6560
|
async function logout() {
|
|
4794
|
-
|
|
4795
|
-
|
|
6561
|
+
identifyPromise = null;
|
|
6562
|
+
if (!readSessionToken()) {
|
|
6563
|
+
updateSession(() => null);
|
|
6564
|
+
return;
|
|
6565
|
+
}
|
|
4796
6566
|
try {
|
|
4797
6567
|
await contactApi.logout();
|
|
4798
6568
|
} catch {
|
|
4799
6569
|
updateSession(() => null);
|
|
4800
6570
|
}
|
|
4801
6571
|
}
|
|
6572
|
+
function setMarket(value) {
|
|
6573
|
+
market = value.trim();
|
|
6574
|
+
apiConfig.market = market;
|
|
6575
|
+
identifyPromise = null;
|
|
6576
|
+
}
|
|
6577
|
+
function setLocale(value) {
|
|
6578
|
+
locale = value.trim();
|
|
6579
|
+
apiConfig.locale = locale;
|
|
6580
|
+
}
|
|
6581
|
+
function setContext(context) {
|
|
6582
|
+
if (context.locale !== void 0) setLocale(context.locale);
|
|
6583
|
+
if (context.market !== void 0) setMarket(context.market);
|
|
6584
|
+
}
|
|
6585
|
+
recoverUnauthorized = async (authorizationToken, path) => {
|
|
6586
|
+
if (!authorizationToken) return false;
|
|
6587
|
+
const currentToken = readSessionToken();
|
|
6588
|
+
if (currentToken !== authorizationToken) {
|
|
6589
|
+
if (currentToken) return true;
|
|
6590
|
+
if (!visitorRecoveryPromise) return false;
|
|
6591
|
+
await visitorRecoveryPromise;
|
|
6592
|
+
return Boolean(readSessionToken());
|
|
6593
|
+
}
|
|
6594
|
+
updateSession(() => null);
|
|
6595
|
+
if (/\/account\/(identify|code)$/.test(path)) return true;
|
|
6596
|
+
const recovery = ensureVisitorSession();
|
|
6597
|
+
const trackedRecovery = recovery.finally(() => {
|
|
6598
|
+
if (visitorRecoveryPromise === trackedRecovery) {
|
|
6599
|
+
visitorRecoveryPromise = null;
|
|
6600
|
+
}
|
|
6601
|
+
});
|
|
6602
|
+
visitorRecoveryPromise = trackedRecovery;
|
|
6603
|
+
await trackedRecovery;
|
|
6604
|
+
return true;
|
|
6605
|
+
};
|
|
4802
6606
|
return {
|
|
4803
6607
|
identify,
|
|
4804
6608
|
verify,
|
|
4805
6609
|
logout,
|
|
4806
|
-
me
|
|
6610
|
+
me,
|
|
4807
6611
|
get session() {
|
|
4808
|
-
|
|
4809
|
-
return toPublic(readContactSession());
|
|
6612
|
+
return toPublic(memorySession);
|
|
4810
6613
|
},
|
|
4811
6614
|
get isAuthenticated() {
|
|
4812
|
-
|
|
4813
|
-
const s = readContactSession();
|
|
4814
|
-
return s !== null && !!s.access_token;
|
|
6615
|
+
return Boolean(readSessionToken());
|
|
4815
6616
|
},
|
|
4816
6617
|
onAuthStateChanged(listener) {
|
|
4817
6618
|
listeners.add(listener);
|
|
4818
|
-
const current = toPublic(
|
|
6619
|
+
const current = toPublic(memorySession);
|
|
4819
6620
|
if (current) {
|
|
4820
6621
|
Promise.resolve().then(() => listener(current)).catch(() => {
|
|
4821
6622
|
});
|
|
@@ -4827,50 +6628,63 @@ function createStorefrontClient(config) {
|
|
|
4827
6628
|
store: storefrontApi.store,
|
|
4828
6629
|
cms: storefrontApi.cms,
|
|
4829
6630
|
eshop: storefrontApi.eshop,
|
|
4830
|
-
crm:
|
|
6631
|
+
crm: {
|
|
6632
|
+
...storefrontApi.crm,
|
|
6633
|
+
contact: {
|
|
6634
|
+
identify: (params) => identify(params),
|
|
6635
|
+
requestCode: (params) => identify({ ...params, verify: true }),
|
|
6636
|
+
verify,
|
|
6637
|
+
logout,
|
|
6638
|
+
getMe: me
|
|
6639
|
+
}
|
|
6640
|
+
},
|
|
4831
6641
|
action: storefrontApi.action,
|
|
4832
6642
|
experiments: storefrontApi.experiments,
|
|
4833
|
-
support: createStorefrontSupportApi(apiConfig),
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
setLocale: (l) => {
|
|
4841
|
-
apiConfig.locale = l;
|
|
4842
|
-
},
|
|
4843
|
-
getLocale: () => apiConfig.locale,
|
|
6643
|
+
support: createStorefrontSupportApi(apiConfig, ensureVisitorSession),
|
|
6644
|
+
getSetup,
|
|
6645
|
+
setContext,
|
|
6646
|
+
setMarket,
|
|
6647
|
+
getMarket: () => market,
|
|
6648
|
+
setLocale,
|
|
6649
|
+
getLocale: () => locale,
|
|
4844
6650
|
utils: createUtilitySurface(apiConfig)
|
|
4845
6651
|
};
|
|
4846
6652
|
}
|
|
4847
|
-
function
|
|
4848
|
-
const
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
6653
|
+
function createStorefrontClient(publishableKey, options = {}, isolatedSession = false) {
|
|
6654
|
+
const client = createStorefrontClientCore(
|
|
6655
|
+
publishableKey,
|
|
6656
|
+
options,
|
|
6657
|
+
isolatedSession
|
|
6658
|
+
);
|
|
6659
|
+
return Object.assign(client, {
|
|
6660
|
+
withContext(context) {
|
|
6661
|
+
return createStorefrontClient(
|
|
6662
|
+
publishableKey,
|
|
6663
|
+
{
|
|
6664
|
+
...options,
|
|
6665
|
+
locale: context.locale ?? client.getLocale(),
|
|
6666
|
+
market: context.market ?? client.getMarket()
|
|
6667
|
+
},
|
|
6668
|
+
true
|
|
6669
|
+
);
|
|
6670
|
+
}
|
|
4864
6671
|
});
|
|
4865
|
-
|
|
6672
|
+
}
|
|
6673
|
+
function createStorefront(publishableKey, options = {}) {
|
|
6674
|
+
return createStorefrontClient(publishableKey, options);
|
|
4866
6675
|
}
|
|
4867
6676
|
|
|
4868
6677
|
exports.COMMON_ACTION_KEYS = COMMON_ACTION_KEYS;
|
|
6678
|
+
exports.DEFAULT_STOREFRONT_API_URL = DEFAULT_STOREFRONT_API_URL;
|
|
4869
6679
|
exports.PaymentMethodType = PaymentMethodType;
|
|
4870
6680
|
exports.SDK_VERSION = SDK_VERSION;
|
|
4871
6681
|
exports.SUPPORTED_FRAMEWORKS = SUPPORTED_FRAMEWORKS;
|
|
6682
|
+
exports.buildFormFields = buildFormFields;
|
|
4872
6683
|
exports.createAdmin = createAdmin;
|
|
4873
6684
|
exports.createCartController = createCartController;
|
|
6685
|
+
exports.createFormEntry = createFormEntry;
|
|
6686
|
+
exports.createFormEntryFromValues = createFormEntryFromValues;
|
|
4874
6687
|
exports.createStorefront = createStorefront;
|
|
6688
|
+
exports.initialize = initialize;
|
|
4875
6689
|
//# sourceMappingURL=index.cjs.map
|
|
4876
6690
|
//# sourceMappingURL=index.cjs.map
|