arky-sdk 0.9.13 → 0.9.17

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.
Files changed (40) hide show
  1. package/README.md +16 -12
  2. package/dist/admin-6xpHuyKc.d.cts +1544 -0
  3. package/dist/admin-DVFAgnHm.d.ts +1544 -0
  4. package/dist/admin.cjs +982 -425
  5. package/dist/admin.cjs.map +1 -1
  6. package/dist/admin.d.cts +3 -3
  7. package/dist/admin.d.ts +3 -3
  8. package/dist/admin.js +982 -425
  9. package/dist/admin.js.map +1 -1
  10. package/dist/{api-DvsFdOaF.d.cts → api-DJrUdQ1C.d.cts} +1425 -657
  11. package/dist/{api-DvsFdOaF.d.ts → api-DJrUdQ1C.d.ts} +1425 -657
  12. package/dist/index.cjs +1317 -539
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +3 -3
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +1317 -539
  17. package/dist/index.js.map +1 -1
  18. package/dist/{index-CQd9b_7n.d.ts → inventory-DOwNF3D-.d.cts} +6 -4
  19. package/dist/{index-BC06yiuv.d.cts → inventory-GpWTZ2oe.d.ts} +6 -4
  20. package/dist/storefront.cjs +1210 -658
  21. package/dist/storefront.cjs.map +1 -1
  22. package/dist/storefront.d.cts +88 -285
  23. package/dist/storefront.d.ts +88 -285
  24. package/dist/storefront.js +1207 -659
  25. package/dist/storefront.js.map +1 -1
  26. package/dist/types.cjs.map +1 -1
  27. package/dist/types.d.cts +1 -1
  28. package/dist/types.d.ts +1 -1
  29. package/dist/types.js.map +1 -1
  30. package/dist/utils.cjs +198 -16
  31. package/dist/utils.cjs.map +1 -1
  32. package/dist/utils.d.cts +18 -2
  33. package/dist/utils.d.ts +18 -2
  34. package/dist/utils.js +191 -17
  35. package/dist/utils.js.map +1 -1
  36. package/package.json +4 -5
  37. package/dist/admin-Q9MBFwCb.d.cts +0 -1496
  38. package/dist/admin-ZLXD4_en.d.ts +0 -1496
  39. package/scripts/contract-admin.mjs +0 -120
  40. package/scripts/contract-storefront.mjs +0 -296
@@ -3,21 +3,25 @@
3
3
  var nanostores = require('nanostores');
4
4
 
5
5
  // src/utils/orderItems.ts
6
- function normalizeOrderCheckoutItems(items) {
6
+ function sanitizePublicCheckoutItems(items) {
7
7
  return items.map((item) => {
8
- if ("type" in item) {
9
- return item;
10
- }
11
- if ("product_id" in item) {
12
- return { type: "product", ...item };
8
+ if (item.type === "product") {
9
+ return {
10
+ type: "product",
11
+ ...item.id ? { id: item.id } : {},
12
+ product_id: item.product_id,
13
+ variant_id: item.variant_id,
14
+ quantity: item.quantity
15
+ };
13
16
  }
14
- return { type: "service", ...item };
15
- });
16
- }
17
- function normalizePublicCheckoutItems(items) {
18
- return normalizeOrderCheckoutItems(items).map((item) => {
19
- const { price: _price, ...publicItem } = item;
20
- return publicItem;
17
+ return {
18
+ type: "service",
19
+ ...item.id ? { id: item.id } : {},
20
+ service_id: item.service_id,
21
+ provider_id: item.provider_id,
22
+ slots: item.slots,
23
+ ...item.forms ? { forms: item.forms } : {}
24
+ };
21
25
  });
22
26
  }
23
27
 
@@ -42,10 +46,9 @@ var createActionApi = (apiConfig) => ({
42
46
  COMMON_ACTION_KEYS,
43
47
  async track(params) {
44
48
  try {
45
- const key = "key" in params && params.key ? params.key : params.type;
46
49
  await apiConfig.httpClient.post(
47
50
  `/v1/storefront/${apiConfig.storeId}/actions/track`,
48
- { key, payload: params.payload }
51
+ { key: params.key, payload: params.payload }
49
52
  );
50
53
  } catch {
51
54
  }
@@ -53,6 +56,47 @@ var createActionApi = (apiConfig) => ({
53
56
  });
54
57
  var createStorefrontApi = (apiConfig, updateContactSession) => {
55
58
  const base = (storeId = apiConfig.storeId) => `/v1/storefront/${storeId}`;
59
+ const pendingVerifications = /* @__PURE__ */ new Map();
60
+ function persistIdentification(result) {
61
+ const issued = result.token;
62
+ if (issued?.token) {
63
+ updateContactSession(() => ({
64
+ access_token: issued.token,
65
+ contact: result.contact,
66
+ store: result.store,
67
+ market: result.market
68
+ }));
69
+ } else {
70
+ if (result.verification_challenge) {
71
+ pendingVerifications.set(result.verification_challenge.challenge_id, {
72
+ store: result.store,
73
+ market: result.market
74
+ });
75
+ }
76
+ updateContactSession(
77
+ (current) => current ? {
78
+ ...current,
79
+ contact: result.contact,
80
+ store: result.store,
81
+ market: result.market
82
+ } : null
83
+ );
84
+ }
85
+ return result;
86
+ }
87
+ async function submitIdentification(path, params, options) {
88
+ const store_id = apiConfig.storeId;
89
+ const result = await apiConfig.httpClient.post(
90
+ `${base(store_id)}/account/${path}`,
91
+ {
92
+ store_id,
93
+ market: params?.market || apiConfig.market || null,
94
+ email: params?.email
95
+ },
96
+ options
97
+ );
98
+ return persistIdentification(result);
99
+ }
56
100
  return {
57
101
  store: {
58
102
  getStore(options) {
@@ -60,7 +104,10 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
60
104
  },
61
105
  location: {
62
106
  getCountries(options) {
63
- return apiConfig.httpClient.get(`/v1/platform/countries`, options);
107
+ return apiConfig.httpClient.get(
108
+ `/v1/platform/countries`,
109
+ options
110
+ );
64
111
  },
65
112
  getCountry(countryCode, options) {
66
113
  return apiConfig.httpClient.get(
@@ -69,18 +116,30 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
69
116
  );
70
117
  },
71
118
  list(options) {
72
- return apiConfig.httpClient.get(`${base()}/locations`, options);
119
+ return apiConfig.httpClient.get(
120
+ `${base()}/locations`,
121
+ options
122
+ );
73
123
  },
74
124
  get(id, options) {
75
- return apiConfig.httpClient.get(`${base()}/locations/${id}`, options);
125
+ return apiConfig.httpClient.get(
126
+ `${base()}/locations/${id}`,
127
+ options
128
+ );
76
129
  }
77
130
  },
78
131
  market: {
79
132
  list(options) {
80
- return apiConfig.httpClient.get(`${base()}/markets`, options);
133
+ return apiConfig.httpClient.get(
134
+ `${base()}/markets`,
135
+ options
136
+ );
81
137
  },
82
138
  get(id, options) {
83
- return apiConfig.httpClient.get(`${base()}/markets/${id}`, options);
139
+ return apiConfig.httpClient.get(
140
+ `${base()}/markets/${id}`,
141
+ options
142
+ );
84
143
  }
85
144
  }
86
145
  },
@@ -88,11 +147,9 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
88
147
  collection: {
89
148
  get(params, options) {
90
149
  const store_id = params.store_id || apiConfig.storeId;
91
- if (!params.id) {
92
- throw new Error("GetCollectionParams requires id");
93
- }
150
+ const identifier = params.id !== void 0 ? params.id : `${store_id}:${params.key}`;
94
151
  return apiConfig.httpClient.get(
95
- `${base(store_id)}/collections/${params.id}`,
152
+ `${base(store_id)}/collections/${identifier}`,
96
153
  options
97
154
  );
98
155
  }
@@ -110,10 +167,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
110
167
  },
111
168
  find(params, options) {
112
169
  const { store_id, ...queryParams } = params;
113
- return apiConfig.httpClient.get(`${base(store_id)}/entries`, {
114
- ...options,
115
- params: queryParams
116
- });
170
+ return apiConfig.httpClient.get(
171
+ `${base(store_id)}/entries`,
172
+ {
173
+ ...options,
174
+ params: queryParams
175
+ }
176
+ );
117
177
  }
118
178
  },
119
179
  form: {
@@ -189,10 +249,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
189
249
  },
190
250
  find(params, options) {
191
251
  const { store_id, ...queryParams } = params;
192
- return apiConfig.httpClient.get(`${base(store_id)}/products`, {
193
- ...options,
194
- params: queryParams
195
- });
252
+ return apiConfig.httpClient.get(
253
+ `${base(store_id)}/products`,
254
+ {
255
+ ...options,
256
+ params: queryParams
257
+ }
258
+ );
196
259
  }
197
260
  },
198
261
  cart: {
@@ -201,16 +264,23 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
201
264
  const { store_id: _store_id, ...payload } = params;
202
265
  return apiConfig.httpClient.post(
203
266
  `${base(store_id)}/carts/current`,
204
- { ...payload, store_id, market: payload.market || apiConfig.market },
267
+ {
268
+ ...payload,
269
+ store_id,
270
+ market: payload.market || apiConfig.market
271
+ },
205
272
  options
206
273
  );
207
274
  },
208
275
  get(params, options) {
209
276
  const store_id = params.store_id || apiConfig.storeId;
210
- return apiConfig.httpClient.get(`${base(store_id)}/carts/${params.id}`, {
211
- ...options,
212
- params: params.token ? { token: params.token } : options?.params
213
- });
277
+ return apiConfig.httpClient.get(
278
+ `${base(store_id)}/carts/${params.id}`,
279
+ {
280
+ ...options,
281
+ params: params.token ? { token: params.token } : options?.params
282
+ }
283
+ );
214
284
  },
215
285
  update(params, options) {
216
286
  const { store_id, items, ...payload } = params;
@@ -220,7 +290,7 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
220
290
  {
221
291
  ...payload,
222
292
  store_id: target,
223
- ...items ? { items: normalizePublicCheckoutItems(items) } : {}
293
+ ...items ? { items: sanitizePublicCheckoutItems(items) } : {}
224
294
  },
225
295
  options
226
296
  );
@@ -233,7 +303,7 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
233
303
  {
234
304
  ...payload,
235
305
  store_id: target,
236
- item: normalizePublicCheckoutItems([item])[0]
306
+ item: sanitizePublicCheckoutItems([item])[0]
237
307
  },
238
308
  options
239
309
  );
@@ -288,18 +358,35 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
288
358
  },
289
359
  find(params, options) {
290
360
  const { store_id, ...queryParams } = params;
291
- return apiConfig.httpClient.get(`${base(store_id)}/orders`, {
292
- ...options,
293
- params: queryParams
294
- });
361
+ return apiConfig.httpClient.get(
362
+ `${base(store_id)}/orders`,
363
+ {
364
+ ...options,
365
+ params: queryParams
366
+ }
367
+ );
295
368
  },
296
369
  downloadDigitalAccess(params, options) {
297
370
  const store_id = params.store_id || apiConfig.storeId;
298
371
  return apiConfig.httpClient.post(
299
- `${base(store_id)}/orders/${params.id}/digital-access/${params.grant_id}/download`,
372
+ `${base(store_id)}/orders/${params.order_id}/digital-access/${params.grant_id}/download`,
300
373
  {},
301
374
  options
302
375
  );
376
+ },
377
+ findDigitalAccess(params, options) {
378
+ const { order_id, store_id, ...queryParams } = params;
379
+ return apiConfig.httpClient.get(
380
+ `${base(store_id)}/orders/${order_id}/digital-access`,
381
+ { ...options, params: queryParams }
382
+ );
383
+ },
384
+ getDigitalAccess(params, options) {
385
+ const store_id = params.store_id || apiConfig.storeId;
386
+ return apiConfig.httpClient.get(
387
+ `${base(store_id)}/orders/${params.order_id}/digital-access/${params.grant_id}`,
388
+ options
389
+ );
303
390
  }
304
391
  },
305
392
  service: {
@@ -320,17 +407,23 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
320
407
  },
321
408
  find(params, options) {
322
409
  const { store_id, ...queryParams } = params;
323
- return apiConfig.httpClient.get(`${base(store_id)}/services`, {
324
- ...options,
325
- params: queryParams
326
- });
410
+ return apiConfig.httpClient.get(
411
+ `${base(store_id)}/services`,
412
+ {
413
+ ...options,
414
+ params: queryParams
415
+ }
416
+ );
327
417
  },
328
418
  findProviders(params, options) {
329
419
  const { store_id, ...queryParams } = params;
330
- return apiConfig.httpClient.get(`${base(store_id)}/service-providers`, {
331
- ...options,
332
- params: queryParams
333
- });
420
+ return apiConfig.httpClient.get(
421
+ `${base(store_id)}/service-providers`,
422
+ {
423
+ ...options,
424
+ params: queryParams
425
+ }
426
+ );
334
427
  },
335
428
  getAvailability(params, options) {
336
429
  const { store_id, ...queryParams } = params;
@@ -359,48 +452,47 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
359
452
  },
360
453
  find(params, options) {
361
454
  const { store_id, ...queryParams } = params;
362
- return apiConfig.httpClient.get(`${base(store_id)}/providers`, {
363
- ...options,
364
- params: queryParams
365
- });
455
+ return apiConfig.httpClient.get(
456
+ `${base(store_id)}/providers`,
457
+ {
458
+ ...options,
459
+ params: queryParams
460
+ }
461
+ );
366
462
  }
367
463
  }
368
464
  },
369
465
  crm: {
370
466
  contact: {
371
- async identify(params, options) {
372
- const store_id = apiConfig.storeId;
373
- const result = await apiConfig.httpClient.post(
374
- `${base(store_id)}/account/identify`,
375
- {
376
- store_id,
377
- market: params?.market || apiConfig.market || null,
378
- email: params?.email,
379
- verify: params?.verify ?? false
380
- },
381
- options
382
- );
383
- if (result?.token?.token) {
384
- updateContactSession(() => ({
385
- access_token: result.token.token,
386
- contact: result.contact,
387
- store: result.store,
388
- market: result.market
389
- }));
390
- }
391
- return result;
467
+ identify(params, options) {
468
+ return submitIdentification("identify", params, options);
469
+ },
470
+ requestCode(params, options) {
471
+ return submitIdentification("code", params, options);
392
472
  },
393
473
  async verify(params, options) {
394
474
  const store_id = apiConfig.storeId;
395
475
  const result = await apiConfig.httpClient.post(
396
476
  `${base(store_id)}/account/verify`,
397
- { store_id, code: params.code },
477
+ { store_id, challenge_id: params.challenge_id, code: params.code },
398
478
  options
399
479
  );
400
- if (result?.token) {
480
+ if (result?.token?.token) {
481
+ const pending = pendingVerifications.get(params.challenge_id);
482
+ const identifiedStore = pending?.store || await apiConfig.httpClient.get(base(store_id), options);
401
483
  updateContactSession(
402
- (prev) => prev ? { ...prev, access_token: result.token } : null
484
+ (prev) => prev ? {
485
+ ...prev,
486
+ access_token: result.token.token,
487
+ contact: result.contact
488
+ } : {
489
+ access_token: result.token.token,
490
+ contact: result.contact,
491
+ store: identifiedStore,
492
+ market: pending?.market || null
493
+ }
403
494
  );
495
+ pendingVerifications.delete(params.challenge_id);
404
496
  }
405
497
  return result;
406
498
  },
@@ -417,7 +509,10 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
417
509
  }
418
510
  },
419
511
  getMe(options) {
420
- return apiConfig.httpClient.get(`${base()}/account/me`, options);
512
+ return apiConfig.httpClient.get(
513
+ `${base()}/account/me`,
514
+ options
515
+ );
421
516
  }
422
517
  },
423
518
  contactList: {
@@ -430,10 +525,31 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
430
525
  },
431
526
  find(params, options) {
432
527
  const { store_id, ...queryParams } = params || {};
433
- return apiConfig.httpClient.get(`${base(store_id)}/contact-lists`, {
434
- ...options,
435
- params: queryParams
436
- });
528
+ return apiConfig.httpClient.get(
529
+ `${base(store_id)}/contact-lists`,
530
+ {
531
+ ...options,
532
+ params: queryParams
533
+ }
534
+ );
535
+ },
536
+ plans: {
537
+ find(params, options) {
538
+ const { store_id, contact_list_id, ...queryParams } = params;
539
+ return apiConfig.httpClient.get(
540
+ `${base(store_id)}/contact-lists/${contact_list_id}/plans`,
541
+ { ...options, params: queryParams }
542
+ );
543
+ }
544
+ },
545
+ memberships: {
546
+ find(params, options) {
547
+ const { store_id, ...queryParams } = params || {};
548
+ return apiConfig.httpClient.get(`${base(store_id)}/contact-lists/memberships`, {
549
+ ...options,
550
+ params: queryParams
551
+ });
552
+ }
437
553
  },
438
554
  subscribe(params, options) {
439
555
  const { store_id, id, ...payload } = params;
@@ -458,13 +574,31 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
458
574
  options
459
575
  );
460
576
  },
461
- unsubscribe(token, options) {
577
+ manage(token, options) {
462
578
  return apiConfig.httpClient.post(
463
- `${base()}/contact-lists/unsubscribe`,
579
+ `${base()}/contact-lists/manage`,
464
580
  { token },
465
581
  options
466
582
  );
467
583
  },
584
+ unsubscribe(token, options) {
585
+ const headers = { ...options?.headers };
586
+ for (const name of Object.keys(headers)) {
587
+ if (name.toLowerCase() === "content-type") delete headers[name];
588
+ }
589
+ return apiConfig.httpClient.post(
590
+ `${base()}/contact-lists/unsubscribe`,
591
+ new URLSearchParams({ "List-Unsubscribe": "One-Click" }),
592
+ {
593
+ ...options,
594
+ params: { ...options?.params || {}, token },
595
+ headers: {
596
+ ...headers,
597
+ "Content-Type": "application/x-www-form-urlencoded"
598
+ }
599
+ }
600
+ );
601
+ },
468
602
  confirm(token, options) {
469
603
  return apiConfig.httpClient.post(
470
604
  `${base()}/contact-lists/confirm`,
@@ -629,27 +763,47 @@ var convertServerErrorToRequestError = (serverError, renameRules) => {
629
763
 
630
764
  // src/utils/queryParams.ts
631
765
  function buildQueryString(params) {
632
- const queryParts = [];
633
- Object.entries(params).forEach(([key, value]) => {
634
- if (value === null || value === void 0) {
635
- return;
636
- }
637
- if (Array.isArray(value)) {
638
- const jsonString = JSON.stringify(value);
639
- queryParts.push(`${key}=${encodeURIComponent(jsonString)}`);
640
- } else if (typeof value === "string") {
641
- queryParts.push(`${key}=${encodeURIComponent(value)}`);
642
- } else if (typeof value === "number" || typeof value === "boolean") {
643
- queryParts.push(`${key}=${value}`);
644
- } else if (typeof value === "object") {
645
- const jsonString = JSON.stringify(value);
646
- queryParts.push(`${key}=${encodeURIComponent(jsonString)}`);
766
+ const queryParts = Object.entries(params).flatMap(
767
+ ([key, value]) => {
768
+ if (value === null || value === void 0) return [];
769
+ if (typeof value === "string") {
770
+ return [`${key}=${encodeURIComponent(value)}`];
771
+ }
772
+ if (typeof value === "number" || typeof value === "boolean") {
773
+ return [`${key}=${value}`];
774
+ }
775
+ if (Array.isArray(value) || typeof value === "object") {
776
+ return [`${key}=${encodeURIComponent(JSON.stringify(value))}`];
777
+ }
778
+ return [];
647
779
  }
648
- });
780
+ );
649
781
  return queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
650
782
  }
651
783
 
652
784
  // src/services/createHttpClient.ts
785
+ function requestError(name, message, details = {}) {
786
+ return Object.assign(new Error(message), { name }, details);
787
+ }
788
+ function isRecord(value) {
789
+ return typeof value === "object" && value !== null;
790
+ }
791
+ function isTokenSet(value) {
792
+ 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");
793
+ }
794
+ function isValidationError(value) {
795
+ return isRecord(value) && typeof value.field === "string" && typeof value.error === "string";
796
+ }
797
+ function toServerError(value, statusCode) {
798
+ const payload = isRecord(value) ? value : {};
799
+ const validationErrors = Array.isArray(payload.validationErrors) ? payload.validationErrors.filter(isValidationError) : [];
800
+ return {
801
+ message: typeof payload.message === "string" ? payload.message : "Request failed",
802
+ error: typeof payload.error === "string" ? payload.error : "REQUEST_FAILED",
803
+ statusCode: typeof payload.statusCode === "number" ? payload.statusCode : statusCode,
804
+ validationErrors
805
+ };
806
+ }
653
807
  function createHttpClient(cfg) {
654
808
  const { authStorage } = cfg;
655
809
  let refreshPromise = null;
@@ -666,32 +820,41 @@ function createHttpClient(cfg) {
666
820
  const refresh_token = tokens?.refresh_token;
667
821
  if (!refresh_token) {
668
822
  authStorage.onForcedLogout();
669
- const err = new Error("No refresh token available");
670
- err.name = "ApiError";
671
- err.statusCode = 401;
672
- throw err;
823
+ throw requestError("ApiError", "No refresh token available", {
824
+ statusCode: 401
825
+ });
673
826
  }
674
827
  const refRes = await fetch(getRefreshEndpoint(), {
675
828
  method: "POST",
676
- headers: { Accept: "application/json", "Content-Type": "application/json" },
829
+ headers: {
830
+ Accept: "application/json",
831
+ "Content-Type": "application/json"
832
+ },
677
833
  body: JSON.stringify({ refresh_token })
678
834
  });
679
835
  if (!refRes.ok) {
680
836
  authStorage.onForcedLogout();
681
- const err = new Error("Token refresh failed");
682
- err.name = "ApiError";
683
- err.statusCode = 401;
684
- throw err;
837
+ throw requestError("ApiError", "Token refresh failed", {
838
+ statusCode: 401
839
+ });
685
840
  }
686
841
  const data = await refRes.json();
842
+ if (!isTokenSet(data)) {
843
+ authStorage.onForcedLogout();
844
+ throw requestError(
845
+ "ParseError",
846
+ "Token refresh returned an invalid response",
847
+ { statusCode: refRes.status }
848
+ );
849
+ }
687
850
  authStorage.onTokensRefreshed(data);
688
851
  })().finally(() => {
689
852
  refreshPromise = null;
690
853
  });
691
854
  return refreshPromise;
692
855
  }
693
- async function request(method, path, body, options) {
694
- if (options?.transformRequest) {
856
+ async function request(method, path, body, options, retried = false) {
857
+ if (!retried && options?.transformRequest) {
695
858
  body = options.transformRequest(body);
696
859
  }
697
860
  const headers = {
@@ -709,23 +872,27 @@ function createHttpClient(cfg) {
709
872
  headers["Authorization"] = `Bearer ${tokens.access_token}`;
710
873
  }
711
874
  const finalPath = options?.params ? path + buildQueryString(options.params) : path;
712
- const fetchOpts = { method, headers, signal: options?.signal };
875
+ const fetchOptions = {
876
+ method,
877
+ headers,
878
+ signal: options?.signal
879
+ };
713
880
  if (!["GET", "DELETE"].includes(method) && body !== void 0) {
714
- fetchOpts.body = JSON.stringify(body);
881
+ fetchOptions.body = body instanceof URLSearchParams ? body.toString() : JSON.stringify(body);
715
882
  }
716
883
  const fullUrl = `${cfg.baseUrl}${finalPath}`;
717
884
  let res;
718
885
  let data;
719
886
  const startedAt = Date.now();
720
887
  try {
721
- res = await fetch(fullUrl, fetchOpts);
888
+ res = await fetch(fullUrl, fetchOptions);
722
889
  } catch (error) {
723
890
  const aborted = options?.signal?.aborted || error instanceof Error && error.name === "AbortError";
724
- const err = new Error(error instanceof Error ? error.message : "Network request failed");
725
- err.name = aborted ? "AbortError" : "NetworkError";
726
- err.method = method;
727
- err.url = fullUrl;
728
- err.aborted = aborted;
891
+ const err = requestError(
892
+ aborted ? "AbortError" : "NetworkError",
893
+ error instanceof Error ? error.message : "Network request failed",
894
+ { method, url: fullUrl, aborted }
895
+ );
729
896
  if (options?.onError && method !== "GET") {
730
897
  Promise.resolve(
731
898
  options.onError({ error: err, method, url: fullUrl, aborted })
@@ -734,18 +901,9 @@ function createHttpClient(cfg) {
734
901
  }
735
902
  throw err;
736
903
  }
737
- if (res.status === 401 && !options?.["_retried"]) {
738
- try {
739
- await ensureFreshToken();
740
- const refreshed = authStorage.getTokens();
741
- if (refreshed?.access_token) {
742
- headers["Authorization"] = `Bearer ${refreshed.access_token}`;
743
- fetchOpts.headers = headers;
744
- }
745
- return request(method, path, body, { ...options, _retried: true });
746
- } catch (refreshError) {
747
- throw refreshError;
748
- }
904
+ if (res.status === 401 && !retried) {
905
+ await ensureFreshToken();
906
+ return request(method, path, body, options, true);
749
907
  }
750
908
  try {
751
909
  const contentLength = res.headers.get("content-length");
@@ -756,30 +914,35 @@ function createHttpClient(cfg) {
756
914
  data = await res.json();
757
915
  }
758
916
  } catch (error) {
759
- const err = new Error("Failed to parse response");
760
- err.name = "ParseError";
761
- err.method = method;
762
- err.url = fullUrl;
763
- err.status = res.status;
917
+ const err = requestError("ParseError", "Failed to parse response", {
918
+ method,
919
+ url: fullUrl,
920
+ statusCode: res.status
921
+ });
764
922
  if (options?.onError && method !== "GET") {
765
923
  Promise.resolve(
766
- options.onError({ error: err, method, url: fullUrl, status: res.status })
924
+ options.onError({
925
+ error: err,
926
+ method,
927
+ url: fullUrl,
928
+ status: res.status
929
+ })
767
930
  ).catch(() => {
768
931
  });
769
932
  }
770
933
  throw err;
771
934
  }
772
935
  if (!res.ok) {
773
- const serverErr = data;
936
+ const serverErr = toServerError(data, res.status);
774
937
  const reqErr = convertServerErrorToRequestError(serverErr);
775
- const err = new Error(serverErr.message || "Request failed");
776
- err.name = "ApiError";
777
- err.statusCode = serverErr.statusCode || res.status;
778
- err.validationErrors = reqErr.validationErrors;
779
- err.method = method;
780
- err.url = fullUrl;
781
938
  const requestId = res.headers.get("x-request-id") || res.headers.get("request-id");
782
- if (requestId) err.requestId = requestId;
939
+ const err = requestError("ApiError", serverErr.message, {
940
+ statusCode: serverErr.statusCode,
941
+ validationErrors: reqErr.validationErrors,
942
+ method,
943
+ url: fullUrl,
944
+ requestId: requestId || void 0
945
+ });
783
946
  if (options?.onError && method !== "GET") {
784
947
  Promise.resolve(
785
948
  options.onError({
@@ -829,183 +992,279 @@ function supportConversationQuery(params) {
829
992
  if (params.after_id) qs.set("after_id", params.after_id);
830
993
  return qs.toString();
831
994
  }
995
+ function storefrontSupportOptions(supportToken, options) {
996
+ if (!/^[0-9a-f]{64}$/.test(supportToken)) {
997
+ throw new Error("support_token must be a 64-character lowercase hexadecimal token");
998
+ }
999
+ const headers = { ...options?.headers };
1000
+ for (const name of Object.keys(headers)) {
1001
+ if (name.toLowerCase() === "x-arky-support-token") delete headers[name];
1002
+ }
1003
+ return {
1004
+ ...options,
1005
+ headers: {
1006
+ ...headers,
1007
+ "X-Arky-Support-Token": supportToken
1008
+ }
1009
+ };
1010
+ }
832
1011
  function createStorefrontSupportApi(config) {
833
- const { httpClient, storeId } = config;
1012
+ const { httpClient } = config;
834
1013
  return {
835
1014
  async startConversation(params = {}, opts) {
1015
+ const storeId = config.storeId;
836
1016
  return httpClient.post(
837
1017
  `/v1/storefront/${storeId}/support/conversations`,
838
- { store_id: storeId, ...params },
1018
+ { ...params, store_id: storeId },
839
1019
  opts
840
1020
  );
841
1021
  },
842
1022
  async sendMessage(params, opts) {
1023
+ const { support_token, ...request } = params;
1024
+ const storeId = config.storeId;
843
1025
  return httpClient.post(
844
- `/v1/storefront/${storeId}/support/conversations/${params.conversation_id}/messages`,
845
- { store_id: storeId, ...params },
846
- opts
1026
+ `/v1/storefront/${storeId}/support/conversations/${request.conversation_id}/messages`,
1027
+ { ...request, store_id: storeId },
1028
+ storefrontSupportOptions(support_token, opts)
847
1029
  );
848
1030
  },
849
1031
  async getConversation(params, opts) {
850
- const qs = supportConversationQuery({ store_id: storeId, ...params });
1032
+ const { support_token, ...request } = params;
1033
+ const storeId = config.storeId;
1034
+ const qs = supportConversationQuery({ ...request, store_id: storeId });
851
1035
  return httpClient.get(
852
- `/v1/storefront/${storeId}/support/conversations/${params.conversation_id}?${qs}`,
853
- opts
1036
+ `/v1/storefront/${storeId}/support/conversations/${request.conversation_id}?${qs}`,
1037
+ storefrontSupportOptions(support_token, opts)
854
1038
  );
855
1039
  }
856
1040
  };
857
1041
  }
858
1042
 
859
1043
  // src/utils/blocks.ts
1044
+ function isRecord2(value) {
1045
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1046
+ }
1047
+ function isBlock(value) {
1048
+ return isRecord2(value) && typeof value.key === "string" && typeof value.type === "string";
1049
+ }
1050
+ function recordFromBlocks(values, locale) {
1051
+ const result = {};
1052
+ for (const value of values) {
1053
+ if (!isBlock(value)) continue;
1054
+ result[value.key] = unwrapBlock(value, locale);
1055
+ }
1056
+ return result;
1057
+ }
1058
+ function localizedValue(value, locale) {
1059
+ if (typeof value === "string") return value;
1060
+ if (!isRecord2(value)) return "";
1061
+ const selected = value[locale] ?? value.en;
1062
+ return typeof selected === "string" ? selected : "";
1063
+ }
1064
+ function unwrapBlock(value, locale) {
1065
+ if (!isBlock(value)) return value;
1066
+ if (value.type === "localized_text" || value.type === "markdown") {
1067
+ return localizedValue(value.value, locale);
1068
+ }
1069
+ if (value.type === "array") {
1070
+ if (!Array.isArray(value.value)) return [];
1071
+ return value.value.map((item) => {
1072
+ if (isBlock(item)) return unwrapBlock(item, locale);
1073
+ if (isRecord2(item) && Array.isArray(item.value)) {
1074
+ return recordFromBlocks(item.value, locale);
1075
+ }
1076
+ if (!isRecord2(item)) return item;
1077
+ return Object.fromEntries(
1078
+ Object.entries(item).map(([key, nested]) => [key, unwrapBlock(nested, locale)])
1079
+ );
1080
+ });
1081
+ }
1082
+ if (value.type === "object") {
1083
+ if (Array.isArray(value.value)) return recordFromBlocks(value.value, locale);
1084
+ if (!isRecord2(value.value)) return {};
1085
+ return Object.fromEntries(
1086
+ Object.entries(value.value).map(([key, nested]) => [key, unwrapBlock(nested, locale)])
1087
+ );
1088
+ }
1089
+ return value.value;
1090
+ }
1091
+ function blockContentArray(values, locale) {
1092
+ const blocks = values.filter(isBlock);
1093
+ if (blocks.length === 0) return [];
1094
+ if (blocks.every((block) => block.key === blocks[0].key)) {
1095
+ return blocks.map((block) => blockContentValue(block, locale));
1096
+ }
1097
+ return Object.fromEntries(
1098
+ blocks.map((block) => [block.key, blockContentValue(block, locale)])
1099
+ );
1100
+ }
1101
+ function blockContentValue(block, locale) {
1102
+ if (block.type === "localized_text" || block.type === "markdown") {
1103
+ return localizedValue(block.value, locale);
1104
+ }
1105
+ if (block.type === "media") return block.value ?? null;
1106
+ if (block.type === "array") {
1107
+ return Array.isArray(block.value) ? blockContentArray(block.value, locale) : [];
1108
+ }
1109
+ if (block.type === "object") {
1110
+ if (Array.isArray(block.value)) return blockContentArray(block.value, locale);
1111
+ if (!isRecord2(block.value)) return {};
1112
+ return Object.fromEntries(
1113
+ Object.entries(block.value).map(([key, value]) => [
1114
+ key,
1115
+ isBlock(value) ? blockContentValue(value, locale) : value
1116
+ ])
1117
+ );
1118
+ }
1119
+ return block.value;
1120
+ }
1121
+ function findBlock(entry, key) {
1122
+ return entry?.blocks?.find((block) => block.key === key);
1123
+ }
860
1124
  function getBlockLabel(block) {
861
- if (!block) return "";
862
- return block.key?.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()) ?? "";
1125
+ return block?.key.replace(/_/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()) ?? "";
863
1126
  }
864
1127
  function formatBlockValue(block) {
865
- if (!block || block.value === null || block.value === void 0) return "";
866
- const value = block.value;
867
- switch (block.type) {
868
- case "boolean":
869
- return value ? "Yes" : "No";
870
- case "number":
871
- if (block.properties?.variant === "DATE" || block.properties?.variant === "DATE_TIME") {
872
- return new Date(value).toLocaleDateString();
873
- }
874
- return String(value);
875
- case "media":
876
- if (value && typeof value === "object") {
877
- return value.mime_type ? value.name || value.id : value.title || value.name || value.id;
878
- }
879
- return String(value);
880
- default:
881
- return String(value);
1128
+ if (block?.value === null || block?.value === void 0) return "";
1129
+ if (block.type === "boolean") return block.value ? "Yes" : "No";
1130
+ if (block.type === "number") {
1131
+ const properties = isRecord2(block.properties) ? block.properties : {};
1132
+ if (properties.variant === "DATE" || properties.variant === "DATE_TIME") {
1133
+ return new Date(Number(block.value)).toLocaleDateString();
1134
+ }
882
1135
  }
1136
+ if (block.type === "media" && isRecord2(block.value)) {
1137
+ const label = block.value.name ?? block.value.title ?? block.value.id;
1138
+ return typeof label === "string" ? label : "";
1139
+ }
1140
+ return String(block.value);
883
1141
  }
884
- function prepareBlocksForSubmission(formData, blockTypes) {
885
- return Object.keys(formData).filter((key) => formData[key] !== null && formData[key] !== void 0).map((key) => ({
1142
+ function prepareBlocksForSubmission(formData, blockTypes = {}) {
1143
+ return Object.entries(formData).filter(([, value]) => value !== null && value !== void 0).map(([key, value]) => ({
886
1144
  key,
887
- value: (blockTypes?.[key] || "text") === "array" && !Array.isArray(formData[key]) ? [formData[key]] : formData[key]
1145
+ value: blockTypes[key] === "array" && !Array.isArray(value) ? [value] : value
888
1146
  }));
889
1147
  }
890
1148
  function extractBlockValues(blocks) {
891
- const values = {};
892
- blocks.forEach((block) => {
893
- values[block.key] = block.value ?? null;
894
- });
895
- return values;
1149
+ return Object.fromEntries(blocks.map((block) => [block.key, block.value ?? null]));
896
1150
  }
897
- var getBlockValue = (entry, blockKey) => {
898
- const block = entry?.blocks?.find((f) => f.key === blockKey);
899
- return block?.value ?? null;
900
- };
901
- var getBlockTextValue = (block, locale = "en") => {
1151
+ function getBlockValue(entry, key) {
1152
+ return findBlock(entry, key)?.value ?? null;
1153
+ }
1154
+ function getBlockTextValue(block, locale = "en") {
902
1155
  if (!block || block.value === null || block.value === void 0) return "";
903
- const blockType = block.type;
904
- if (blockType === "localized_text" || blockType === "markdown") {
905
- if (typeof block.value === "object" && block.value !== null) {
906
- return block.value[locale] ?? block.value["en"] ?? "";
907
- }
908
- }
909
- if (typeof block.value === "string") return block.value;
910
- return String(block.value ?? "");
911
- };
912
- var getBlockValues = (entry, blockKey) => {
913
- const block = entry?.blocks?.find((f) => f.key === blockKey);
914
- if (!block) return [];
915
- if (block.type === "array" && Array.isArray(block.value)) {
916
- return block.value;
917
- }
918
- return [];
919
- };
920
- function unwrapBlock(block, locale) {
921
- if (!block?.type || block.value === void 0) return block;
922
- const blockType = block.type;
923
- if (blockType === "array") {
924
- return block.value.map((obj) => {
925
- const parsed = {};
926
- for (const [k, v] of Object.entries(obj)) {
927
- parsed[k] = unwrapBlock(v, locale);
928
- }
929
- return parsed;
930
- });
931
- }
932
- if (blockType === "object") {
933
- const parsed = {};
934
- for (const [k, v] of Object.entries(block.value || {})) {
935
- parsed[k] = unwrapBlock(v, locale);
936
- }
937
- return parsed;
1156
+ if (block.type === "localized_text" || block.type === "markdown") {
1157
+ return localizedValue(block.value, locale);
938
1158
  }
939
- if (blockType === "localized_text" || blockType === "markdown") {
940
- return block.value?.[locale];
941
- }
942
- return block.value;
1159
+ return typeof block.value === "string" ? block.value : String(block.value);
1160
+ }
1161
+ function getBlockContentValue(entry, key, locale = "en") {
1162
+ const block = findBlock(entry, key);
1163
+ return block ? blockContentValue(block, locale) : null;
943
1164
  }
944
- var getBlockObjectValues = (entry, blockKey, locale = "en") => {
945
- const block = entry?.blocks?.find((f) => f.key === blockKey);
946
- if (!block || block.type !== "array" || !Array.isArray(block.value)) return [];
947
- return block.value.map((obj) => {
948
- if (!obj?.value || !Array.isArray(obj.value)) return {};
949
- return obj.value.reduce((acc, current) => {
950
- acc[current.key] = unwrapBlock(current, locale);
951
- return acc;
952
- }, {});
1165
+ function getBlockValues(entry, key) {
1166
+ const block = findBlock(entry, key);
1167
+ return block?.type === "array" && Array.isArray(block.value) ? block.value : [];
1168
+ }
1169
+ function getBlockObjectValues(entry, key, locale = "en") {
1170
+ return getBlockValues(entry, key).map((value) => {
1171
+ if (isRecord2(value) && Array.isArray(value.value)) {
1172
+ return recordFromBlocks(value.value, locale);
1173
+ }
1174
+ return isRecord2(value) ? Object.fromEntries(
1175
+ Object.entries(value).map(([field, nested]) => [field, unwrapBlock(nested, locale)])
1176
+ ) : {};
953
1177
  });
954
- };
955
- var getBlockFromArray = (entry, blockKey, locale = "en") => {
956
- const block = entry?.blocks?.find((f) => f.key === blockKey);
1178
+ }
1179
+ function getBlockFromArray(entry, key, locale = "en") {
1180
+ const block = findBlock(entry, key);
957
1181
  if (!block) return {};
958
- if (block.type === "array" && Array.isArray(block.value)) {
959
- return block.value.reduce((acc, current) => {
960
- acc[current.key] = unwrapBlock(current, locale);
961
- return acc;
962
- }, {});
963
- }
964
- if (block.type === "object" && block.value && typeof block.value === "object") {
965
- const result = {};
966
- for (const [k, v] of Object.entries(block.value)) {
967
- result[k] = unwrapBlock(v, locale);
968
- }
969
- return result;
970
- }
971
- return { [block.key]: unwrapBlock(block, locale) };
972
- };
973
- var getImageUrl = (imageBlock, isBlock = true) => {
974
- if (!imageBlock) return null;
975
- if (imageBlock.type === "media") {
976
- const mediaValue = imageBlock.value;
977
- return mediaValue?.resolutions?.original?.url || mediaValue?.url || null;
978
- }
979
- if (isBlock) {
980
- if (typeof imageBlock === "string") return imageBlock;
981
- if (imageBlock.url) return imageBlock.url;
982
- }
983
- return imageBlock.resolutions?.original?.url || null;
984
- };
1182
+ const value = unwrapBlock(block, locale);
1183
+ if (isRecord2(value)) return value;
1184
+ if (Array.isArray(block.value)) return recordFromBlocks(block.value, locale);
1185
+ return { [block.key]: value };
1186
+ }
1187
+ function nestedUrl(value) {
1188
+ const resolutions = isRecord2(value.resolutions) ? value.resolutions : null;
1189
+ const original = resolutions && isRecord2(resolutions.original) ? resolutions.original : null;
1190
+ if (typeof original?.url === "string") return original.url;
1191
+ return typeof value.url === "string" ? value.url : null;
1192
+ }
1193
+ function getImageUrl(value, isBlock2 = true) {
1194
+ if (typeof value === "string") return value;
1195
+ if (!isRecord2(value)) return null;
1196
+ if (value.type === "media" && isRecord2(value.value)) return nestedUrl(value.value);
1197
+ if (isBlock2 && typeof value.url === "string") return value.url;
1198
+ return nestedUrl(value);
1199
+ }
985
1200
 
986
1201
  // src/utils/price.ts
1202
+ var SUPPORTED_STORE_CURRENCIES = Object.freeze([
1203
+ "USD",
1204
+ "EUR",
1205
+ "GBP",
1206
+ "JPY",
1207
+ "CNY",
1208
+ "CHF",
1209
+ "AUD",
1210
+ "CAD",
1211
+ "HKD",
1212
+ "SGD",
1213
+ "NZD",
1214
+ "KRW",
1215
+ "SEK",
1216
+ "NOK",
1217
+ "DKK",
1218
+ "INR",
1219
+ "MXN",
1220
+ "BRL",
1221
+ "ZAR",
1222
+ "RUB",
1223
+ "TRY",
1224
+ "PLN",
1225
+ "THB",
1226
+ "IDR",
1227
+ "MYR",
1228
+ "PHP",
1229
+ "CZK",
1230
+ "ILS",
1231
+ "AED",
1232
+ "SAR",
1233
+ "HUF",
1234
+ "RON",
1235
+ "BGN",
1236
+ "HRK",
1237
+ "BAM",
1238
+ "RSD",
1239
+ "MKD",
1240
+ "ALL"
1241
+ ]);
1242
+ var SUPPORTED_STORE_CURRENCY_SET = Object.freeze(
1243
+ new Set(SUPPORTED_STORE_CURRENCIES)
1244
+ );
1245
+ var ZERO_MINOR_UNIT_STORE_CURRENCIES = Object.freeze(
1246
+ /* @__PURE__ */ new Set(["JPY", "KRW"])
1247
+ );
987
1248
  function formatCurrency(amount, currencyCode, locale = "en") {
988
- if (!currencyCode) return "";
1249
+ const normalized = currencyCode.trim().toUpperCase();
1250
+ if (!normalized) return "";
1251
+ const minorUnits = getCurrencyMinorUnits(normalized);
989
1252
  return new Intl.NumberFormat(locale, {
990
1253
  style: "currency",
991
- currency: currencyCode.toUpperCase()
1254
+ currency: normalized,
1255
+ minimumFractionDigits: minorUnits,
1256
+ maximumFractionDigits: minorUnits
992
1257
  }).format(amount);
993
1258
  }
994
- function getMinorUnits(currency) {
995
- try {
996
- const formatter = new Intl.NumberFormat("en", {
997
- style: "currency",
998
- currency: currency.toUpperCase()
999
- });
1000
- const parts = formatter.formatToParts(1.11);
1001
- const fractionPart = parts.find((p) => p.type === "fraction");
1002
- return fractionPart?.value.length ?? 2;
1003
- } catch {
1004
- return 2;
1259
+ function getCurrencyMinorUnits(currency) {
1260
+ const normalized = currency.trim().toUpperCase();
1261
+ if (!SUPPORTED_STORE_CURRENCY_SET.has(normalized)) {
1262
+ throw new RangeError(`Unsupported currency '${currency}'`);
1005
1263
  }
1264
+ return ZERO_MINOR_UNIT_STORE_CURRENCIES.has(normalized) ? 0 : 2;
1006
1265
  }
1007
1266
  function convertToMajor(minorAmount, currency) {
1008
- const units = getMinorUnits(currency);
1267
+ const units = getCurrencyMinorUnits(currency);
1009
1268
  return minorAmount / Math.pow(10, units);
1010
1269
  }
1011
1270
  function getCurrencySymbol(currency) {
@@ -1027,22 +1286,24 @@ function getCurrencyName(currency) {
1027
1286
  }
1028
1287
  }
1029
1288
  function formatMinor(amountMinor, currency) {
1030
- if (!currency) return "";
1289
+ if (!Number.isSafeInteger(amountMinor)) {
1290
+ throw new RangeError("Minor-unit amount must be a safe integer");
1291
+ }
1031
1292
  return formatCurrency(convertToMajor(amountMinor, currency), currency);
1032
1293
  }
1033
1294
  function formatPayment(payment) {
1034
1295
  return formatMinor(payment.total, payment.currency);
1035
1296
  }
1036
1297
  function formatPrice(prices, marketId) {
1037
- if (!prices || prices.length === 0) return "";
1038
- const price = marketId ? prices.find((p) => p.market === marketId) || prices[0] : prices[0];
1039
- if (!price) return "";
1298
+ if (!prices || prices.length === 0 || !marketId) return "";
1299
+ const price = prices.find((p) => p.market === marketId);
1300
+ if (!price || !Number.isSafeInteger(price.amount) || price.amount < 0 || !price.currency) return "";
1040
1301
  return formatMinor(price.amount, price.currency);
1041
1302
  }
1042
1303
  function getPriceAmount(prices, marketId) {
1043
- if (!prices || prices.length === 0) return 0;
1044
- const price = prices.find((p) => p.market === marketId) || prices[0];
1045
- return price?.amount || 0;
1304
+ if (!prices || prices.length === 0 || !marketId) return null;
1305
+ const price = prices.find((p) => p.market === marketId);
1306
+ return price && Number.isSafeInteger(price.amount) && price.amount >= 0 ? price.amount : null;
1046
1307
  }
1047
1308
 
1048
1309
  // src/utils/validation.ts
@@ -1143,6 +1404,7 @@ function formatDate(date, locale) {
1143
1404
  async function fetchSvgContent(mediaObject) {
1144
1405
  if (!mediaObject) return null;
1145
1406
  const svgUrl = getImageUrl(mediaObject, false);
1407
+ if (!svgUrl) return null;
1146
1408
  try {
1147
1409
  const response = await fetch(svgUrl);
1148
1410
  if (!response.ok) {
@@ -1235,9 +1497,10 @@ function getFirstAvailableFCId(variant, quantity = 1) {
1235
1497
  // src/index.ts
1236
1498
  function createUtilitySurface(apiConfig) {
1237
1499
  return {
1238
- getImageUrl: (imageBlock, isBlock = true) => getImageUrl(imageBlock, isBlock),
1500
+ getImageUrl: (imageBlock, isBlock2 = true) => getImageUrl(imageBlock, isBlock2),
1239
1501
  getBlockValue,
1240
1502
  getBlockTextValue,
1503
+ getBlockContentValue,
1241
1504
  getBlockValues,
1242
1505
  getBlockLabel,
1243
1506
  getBlockObjectValues,
@@ -1272,29 +1535,50 @@ function createUtilitySurface(apiConfig) {
1272
1535
  getFirstAvailableFCId
1273
1536
  };
1274
1537
  }
1275
- var CONTACT_STORAGE_KEY = "arky_contact_session";
1276
- function readContactSession() {
1538
+ function defaultStorefrontSessionStorage() {
1277
1539
  if (typeof window === "undefined") return null;
1278
1540
  try {
1279
- const raw = localStorage.getItem(CONTACT_STORAGE_KEY);
1280
- return raw ? JSON.parse(raw) : null;
1541
+ return window.localStorage;
1281
1542
  } catch {
1282
1543
  return null;
1283
1544
  }
1284
1545
  }
1285
- function writeContactSession(s) {
1286
- if (typeof window === "undefined") return;
1287
- if (s) {
1288
- localStorage.setItem(CONTACT_STORAGE_KEY, JSON.stringify(s));
1289
- } else {
1290
- localStorage.removeItem(CONTACT_STORAGE_KEY);
1291
- }
1546
+ function storefrontSessionStorageKey(baseUrl, storeId) {
1547
+ const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/, "").toLowerCase();
1548
+ return `arky_contact_session:${encodeURIComponent(normalizedBaseUrl)}:${encodeURIComponent(storeId)}`;
1292
1549
  }
1293
- function createStorefront(config) {
1550
+ function createStorefrontClient(config) {
1294
1551
  const locale = config.locale || "en";
1295
1552
  const initialMarket = config.market || "";
1296
1553
  const listeners = /* @__PURE__ */ new Set();
1297
1554
  let bareIdentifyPromise = null;
1555
+ let identityTail = Promise.resolve();
1556
+ const sessionStorage = config.sessionStorage || defaultStorefrontSessionStorage();
1557
+ let memorySession = null;
1558
+ function readContactSession() {
1559
+ if (!sessionStorage) return memorySession;
1560
+ try {
1561
+ const raw = sessionStorage.getItem(
1562
+ storefrontSessionStorageKey(config.baseUrl, config.storeId)
1563
+ );
1564
+ return raw ? JSON.parse(raw) : null;
1565
+ } catch {
1566
+ return memorySession;
1567
+ }
1568
+ }
1569
+ function writeContactSession(session) {
1570
+ memorySession = session;
1571
+ if (!sessionStorage) return;
1572
+ const key = storefrontSessionStorageKey(config.baseUrl, config.storeId);
1573
+ try {
1574
+ if (session) {
1575
+ sessionStorage.setItem(key, JSON.stringify(session));
1576
+ } else {
1577
+ sessionStorage.removeItem(key);
1578
+ }
1579
+ } catch {
1580
+ }
1581
+ }
1298
1582
  function toPublic(s) {
1299
1583
  return s ? { contact: s.contact, store: s.store, market: s.market } : null;
1300
1584
  }
@@ -1350,19 +1634,23 @@ function createStorefront(config) {
1350
1634
  const contactApi = storefrontApi.crm.contact;
1351
1635
  function identify(params) {
1352
1636
  if (params?.market !== void 0) apiConfig.market = params.market;
1637
+ const market = apiConfig.market;
1353
1638
  const isBareCall = !params?.email && !params?.verify;
1354
1639
  if (isBareCall && bareIdentifyPromise) return bareIdentifyPromise;
1355
- const promise = (async () => {
1640
+ const run = async () => {
1356
1641
  try {
1357
- const result = await contactApi.identify({
1358
- market: apiConfig.market,
1359
- email: params?.email,
1360
- verify: params?.verify
1361
- });
1642
+ const result = await (params?.verify ? contactApi.requestCode({
1643
+ market,
1644
+ email: params.email
1645
+ }) : contactApi.identify({
1646
+ market,
1647
+ email: params?.email
1648
+ }));
1362
1649
  return {
1363
1650
  contact: result.contact,
1364
1651
  store: result.store,
1365
- market: result.market
1652
+ market: result.market,
1653
+ verification_challenge: result.verification_challenge
1366
1654
  };
1367
1655
  } catch (err) {
1368
1656
  const e = err;
@@ -1370,21 +1658,34 @@ function createStorefront(config) {
1370
1658
  if (isBareCall && status === 401) {
1371
1659
  updateSession(() => null);
1372
1660
  const result = await contactApi.identify({
1373
- market: apiConfig.market
1661
+ market
1374
1662
  });
1375
1663
  return {
1376
1664
  contact: result.contact,
1377
1665
  store: result.store,
1378
- market: result.market
1666
+ market: result.market,
1667
+ verification_challenge: result.verification_challenge
1379
1668
  };
1380
1669
  }
1381
1670
  throw err;
1382
1671
  }
1383
- })().catch((err) => {
1384
- if (isBareCall) bareIdentifyPromise = null;
1385
- throw err;
1386
- });
1387
- if (isBareCall) bareIdentifyPromise = promise;
1672
+ };
1673
+ const promise = identityTail.then(run);
1674
+ identityTail = promise.then(
1675
+ () => void 0,
1676
+ () => void 0
1677
+ );
1678
+ if (isBareCall) {
1679
+ bareIdentifyPromise = promise;
1680
+ void promise.then(
1681
+ () => {
1682
+ if (bareIdentifyPromise === promise) bareIdentifyPromise = null;
1683
+ },
1684
+ () => {
1685
+ if (bareIdentifyPromise === promise) bareIdentifyPromise = null;
1686
+ }
1687
+ );
1688
+ }
1388
1689
  return promise;
1389
1690
  }
1390
1691
  async function verify(params) {
@@ -1427,17 +1728,12 @@ function createStorefront(config) {
1427
1728
  };
1428
1729
  },
1429
1730
  store: storefrontApi.store,
1430
- cart: createCartController(storefrontApi.eshop.cart),
1431
1731
  cms: storefrontApi.cms,
1432
1732
  eshop: storefrontApi.eshop,
1433
1733
  crm: storefrontApi.crm,
1434
1734
  action: storefrontApi.action,
1435
1735
  experiments: storefrontApi.experiments,
1436
1736
  support: createStorefrontSupportApi(apiConfig),
1437
- setStoreId: (storeId) => {
1438
- apiConfig.storeId = storeId;
1439
- bareIdentifyPromise = null;
1440
- },
1441
1737
  getStoreId: () => apiConfig.storeId,
1442
1738
  setMarket: (key) => {
1443
1739
  apiConfig.market = key;
@@ -1451,14 +1747,40 @@ function createStorefront(config) {
1451
1747
  utils: createUtilitySurface(apiConfig)
1452
1748
  };
1453
1749
  }
1750
+ function createStorefront(config) {
1751
+ const sessionStorage = config.sessionStorage || defaultStorefrontSessionStorage() || void 0;
1752
+ const scopedConfig = {
1753
+ ...config,
1754
+ sessionStorage
1755
+ };
1756
+ const client = createStorefrontClient(scopedConfig);
1757
+ Object.defineProperty(client, "forStore", {
1758
+ enumerable: true,
1759
+ configurable: false,
1760
+ writable: false,
1761
+ value: (storeId) => createStorefront({
1762
+ ...scopedConfig,
1763
+ storeId,
1764
+ market: client.getMarket(),
1765
+ locale: client.getLocale()
1766
+ })
1767
+ });
1768
+ return client;
1769
+ }
1454
1770
 
1455
1771
  // src/payments/stripe.ts
1772
+ async function defaultStripeLoader(publishableKey, options) {
1773
+ const { loadStripe } = await import('@stripe/stripe-js');
1774
+ return loadStripe(publishableKey, options);
1775
+ }
1456
1776
  function normalizeCurrency(currency) {
1457
1777
  return currency.trim().toLowerCase();
1458
1778
  }
1459
- async function createStripeConfirmationTokenController(config) {
1460
- const { loadStripe } = await import('@stripe/stripe-js');
1461
- const stripe = await loadStripe(config.publishableKey);
1779
+ async function createStripeConfirmationTokenController(config, stripeLoader = defaultStripeLoader) {
1780
+ const stripe = await stripeLoader(
1781
+ config.publishableKey,
1782
+ config.connectedAccountId ? { stripeAccount: config.connectedAccountId } : void 0
1783
+ );
1462
1784
  if (!stripe) throw new Error("Stripe failed to initialize");
1463
1785
  let elements = createElements(stripe, config);
1464
1786
  let paymentElement = elements.create("payment", {
@@ -1547,11 +1869,11 @@ function firstLocalized(value, locale) {
1547
1869
  }
1548
1870
  return "";
1549
1871
  }
1550
- function findBlock(blocks, keys) {
1872
+ function findBlock2(blocks, keys) {
1551
1873
  return (blocks || []).find((block) => keys.includes(block.key)) || null;
1552
1874
  }
1553
1875
  function blockText(blocks, keys, locale) {
1554
- const block = findBlock(blocks, keys);
1876
+ const block = findBlock2(blocks, keys);
1555
1877
  if (!block) return "";
1556
1878
  return firstLocalized(block.value, locale);
1557
1879
  }
@@ -1567,15 +1889,32 @@ function providerName(provider, locale) {
1567
1889
  function entitySlug(entity, locale) {
1568
1890
  return entity.slug?.[locale] || entity.slug?.en || Object.values(entity.slug || {})[0] || entity.id;
1569
1891
  }
1570
- function priceForMarket(prices, market, fallbackCurrency) {
1571
- const price = prices.find((candidate) => candidate.market === market) || prices[0];
1572
- return {
1573
- amount: price?.amount || 0,
1574
- market: price?.market || market,
1575
- currency: price?.currency || fallbackCurrency || "",
1576
- compare_at: price?.compare_at,
1577
- contact_list_id: price?.contact_list_id
1578
- };
1892
+ function priceForMarket(prices, market, marketCurrency) {
1893
+ const marketKey = market.trim();
1894
+ if (!marketKey) throw new Error("A market is required to select a product price");
1895
+ const currency = marketCurrency?.trim().toUpperCase();
1896
+ if (!currency) throw new Error(`Market ${marketKey} does not have an authoritative currency`);
1897
+ const marketPrices = prices.filter((candidate) => candidate.market === marketKey);
1898
+ if (marketPrices.length === 0) {
1899
+ throw new Error(`Product is not priced for market ${marketKey}`);
1900
+ }
1901
+ if (marketPrices.some(
1902
+ (candidate) => !Number.isSafeInteger(candidate.amount) || candidate.amount < 0 || candidate.currency.trim().toUpperCase() !== currency
1903
+ )) {
1904
+ throw new Error(`Product has an invalid price for market ${marketKey}`);
1905
+ }
1906
+ const authorizedPrices = marketPrices.filter((candidate) => candidate.contact_list_id);
1907
+ if (authorizedPrices.length > 0) {
1908
+ return authorizedPrices.reduce(
1909
+ (lowest, candidate) => candidate.amount < lowest.amount ? candidate : lowest
1910
+ );
1911
+ }
1912
+ const basePrices = marketPrices.filter((candidate) => !candidate.contact_list_id);
1913
+ if (basePrices.length !== 1) {
1914
+ throw new Error(`Product does not have one base price for market ${marketKey}`);
1915
+ }
1916
+ const price = basePrices[0];
1917
+ return price;
1579
1918
  }
1580
1919
  function availableStock(client, variant) {
1581
1920
  const fromUtility = client.utils.getAvailableStock(variant);
@@ -1594,8 +1933,9 @@ function locationToAddress(location) {
1594
1933
  street2: null
1595
1934
  };
1596
1935
  }
1597
- function normalizeForms(forms) {
1598
- return forms;
1936
+ function createFormEntry(formId, fields) {
1937
+ if (!formId.trim()) throw new Error("formId is required");
1938
+ return { form_id: formId, fields };
1599
1939
  }
1600
1940
  function toProductCheckoutItems(items) {
1601
1941
  return items.map((item) => ({
@@ -1607,36 +1947,97 @@ function toProductCheckoutItems(items) {
1607
1947
  }));
1608
1948
  }
1609
1949
  function toServiceCheckoutItems(items) {
1610
- const groups = /* @__PURE__ */ new Map();
1611
- for (const item of items) {
1612
- const key = `${item.service_id}:${item.provider_id}`;
1613
- const slot = { from: item.from, to: item.to };
1614
- const existing = groups.get(key);
1615
- if (existing) {
1616
- existing.slots.push(slot);
1950
+ return items.map((item) => ({
1951
+ type: "service",
1952
+ id: item.id,
1953
+ service_id: item.service_id,
1954
+ provider_id: item.provider_id,
1955
+ slots: [...item.slots].sort((a, b) => a.from - b.from),
1956
+ forms: item.forms
1957
+ }));
1958
+ }
1959
+ function formValueError(field, message) {
1960
+ return new Error(`Invalid value for form field '${field.key}': ${message}`);
1961
+ }
1962
+ function isValidGeoLocation(value) {
1963
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1964
+ const location = value;
1965
+ if (location.label !== void 0 && location.label !== null && typeof location.label !== "string") {
1966
+ return false;
1967
+ }
1968
+ const coordinates = location.coordinates;
1969
+ if (!coordinates || typeof coordinates !== "object") return false;
1970
+ return Number.isFinite(coordinates.lat) && Number.isFinite(coordinates.lon) && coordinates.lat >= -90 && coordinates.lat <= 90 && coordinates.lon >= -180 && coordinates.lon <= 180;
1971
+ }
1972
+ function buildFormField(field, value) {
1973
+ const common = { id: field.id, key: field.key };
1974
+ switch (field.type) {
1975
+ case "text":
1976
+ if (typeof value !== "string") throw formValueError(field, "expected text");
1977
+ if (field.required && value.trim().length === 0) throw formValueError(field, "required text is blank");
1978
+ return { ...common, type: "text", value };
1979
+ case "number":
1980
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1981
+ throw formValueError(field, "expected a finite number");
1982
+ }
1983
+ if (field.min !== null && field.min !== void 0 && value < field.min) {
1984
+ throw formValueError(field, `must be at least ${field.min}`);
1985
+ }
1986
+ if (field.max !== null && field.max !== void 0 && value > field.max) {
1987
+ throw formValueError(field, `must be at most ${field.max}`);
1988
+ }
1989
+ return { ...common, type: "number", value };
1990
+ case "boolean":
1991
+ if (typeof value !== "boolean") throw formValueError(field, "expected a boolean");
1992
+ return { ...common, type: "boolean", value };
1993
+ case "date":
1994
+ if (typeof value !== "number" || !Number.isSafeInteger(value)) {
1995
+ throw formValueError(field, "expected an integer timestamp");
1996
+ }
1997
+ return { ...common, type: "date", value };
1998
+ case "geo_location":
1999
+ if (!isValidGeoLocation(value)) throw formValueError(field, "expected valid coordinates");
2000
+ return { ...common, type: "geo_location", value };
2001
+ case "select": {
2002
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
2003
+ throw formValueError(field, "expected a list of options");
2004
+ }
2005
+ const selected = value;
2006
+ if (field.required && selected.length === 0) throw formValueError(field, "at least one option is required");
2007
+ if (new Set(selected).size !== selected.length || selected.some((item) => !field.options.includes(item))) {
2008
+ throw formValueError(field, "contains an unknown or duplicate option");
2009
+ }
2010
+ return { ...common, type: "select", value: selected };
2011
+ }
2012
+ }
2013
+ }
2014
+ function isEmptyOptionalValue(field, value) {
2015
+ if (field.required) return false;
2016
+ if (field.type === "text") return value === "";
2017
+ if (field.type === "select") return Array.isArray(value) && value.length === 0;
2018
+ if (field.type === "geo_location") {
2019
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0);
2020
+ }
2021
+ return false;
2022
+ }
2023
+ function buildFormFields(schema, values) {
2024
+ const knownKeys = new Set(schema.map((field) => field.key));
2025
+ const unknownKey = Object.keys(values).find((key) => !knownKeys.has(key));
2026
+ if (unknownKey) throw new Error(`Form field '${unknownKey}' is not defined by the form schema`);
2027
+ const fields = [];
2028
+ for (const field of schema) {
2029
+ const value = values[field.key];
2030
+ if (value === void 0) {
2031
+ if (field.required) throw formValueError(field, "required value is missing");
1617
2032
  continue;
1618
2033
  }
1619
- groups.set(key, {
1620
- type: "service",
1621
- id: item.id,
1622
- service_id: item.service_id,
1623
- provider_id: item.provider_id,
1624
- slots: [slot],
1625
- forms: item.forms || []
1626
- });
2034
+ if (isEmptyOptionalValue(field, value)) continue;
2035
+ fields.push(buildFormField(field, value));
1627
2036
  }
1628
- return [...groups.values()].map((item) => ({
1629
- ...item,
1630
- slots: [...item.slots].sort((a, b) => a.from - b.from)
1631
- }));
2037
+ return fields;
1632
2038
  }
1633
- function formFieldsFromBlocks(blocks) {
1634
- return blocks.map((block) => ({
1635
- id: block.id,
1636
- key: block.key,
1637
- type: block.type,
1638
- value: block.value
1639
- }));
2039
+ function createFormEntryFromValues(form, values) {
2040
+ return createFormEntry(form.id, buildFormFields(form.schema, values));
1640
2041
  }
1641
2042
  function getFormBlockType(field) {
1642
2043
  if (field.key === "email") return "email";
@@ -1646,11 +2047,15 @@ function getFormBlockType(field) {
1646
2047
  }
1647
2048
  function getFormBlockValue(field) {
1648
2049
  if (field.type === "boolean") return false;
1649
- if (field.type === "number") return field.min ?? 0;
2050
+ if (field.type === "select") return [];
1650
2051
  if (field.type === "geo_location") return {};
2052
+ if (field.type === "number" || field.type === "date") return void 0;
1651
2053
  return "";
1652
2054
  }
1653
2055
  function formSchemaToBlock(field) {
2056
+ const min = field.type === "number" ? field.min : void 0;
2057
+ const max = field.type === "number" ? field.max : void 0;
2058
+ const options = field.type === "select" ? field.options : void 0;
1654
2059
  return {
1655
2060
  id: field.id,
1656
2061
  key: field.key,
@@ -1658,9 +2063,9 @@ function formSchemaToBlock(field) {
1658
2063
  properties: {
1659
2064
  isRequired: field.required,
1660
2065
  minValues: field.required ? 1 : 0,
1661
- min: field.min,
1662
- max: field.max,
1663
- options: field.options,
2066
+ min,
2067
+ max,
2068
+ options,
1664
2069
  pattern: field.key === "email" ? "^.+@.+\\..+$" : field.key === "phone" ? "^.{6,20}$" : void 0
1665
2070
  },
1666
2071
  value: getFormBlockValue(field)
@@ -1703,15 +2108,13 @@ function createServiceInitialState() {
1703
2108
  service: null,
1704
2109
  availability: null,
1705
2110
  providers: [],
2111
+ serviceProviders: [],
1706
2112
  selectedProviderId: null,
1707
2113
  currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
1708
2114
  calendar: [],
1709
2115
  selectedDate: null,
1710
- startDate: null,
1711
- endDate: null,
1712
2116
  slots: [],
1713
2117
  selectedSlot: null,
1714
- cart: [],
1715
2118
  timezone: typeof window !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "UTC",
1716
2119
  tzGroups: {},
1717
2120
  loading: false,
@@ -1721,7 +2124,6 @@ function createServiceInitialState() {
1721
2124
  quoteError: null,
1722
2125
  currency: null,
1723
2126
  dateTimeConfirmed: false,
1724
- isMultiDay: false,
1725
2127
  availablePaymentMethods: [],
1726
2128
  cartId: null,
1727
2129
  promoCode: null
@@ -1742,8 +2144,9 @@ function normalizeTimezoneGroups(groups) {
1742
2144
  function firstFiniteNumber(...values) {
1743
2145
  return values.find((value) => typeof value === "number" && Number.isFinite(value));
1744
2146
  }
1745
- function initialize(config) {
2147
+ function initializeStore(config) {
1746
2148
  const client = createStorefront(config);
2149
+ const formClients = /* @__PURE__ */ new Map([[client.getStoreId(), client]]);
1747
2150
  const session = nanostores.atom(client.session);
1748
2151
  const locale = nanostores.atom(config.locale || client.getLocale());
1749
2152
  const market_key = nanostores.atom(config.market || client.getMarket());
@@ -1754,7 +2157,10 @@ function initialize(config) {
1754
2157
  const store = value?.store;
1755
2158
  const methods = value?.market?.payment_methods || [];
1756
2159
  const hasCreditCard = methods.some((method) => method.type === "credit_card");
1757
- return { provider: store?.payment || null, enabled: hasCreditCard && !!store?.payment };
2160
+ return {
2161
+ provider: store?.payment || null,
2162
+ enabled: hasCreditCard && !!store?.payment
2163
+ };
1758
2164
  });
1759
2165
  const cart = nanostores.atom(null);
1760
2166
  const product_items = nanostores.atom([]);
@@ -1795,7 +2201,10 @@ function initialize(config) {
1795
2201
  );
1796
2202
  const service_item_count = nanostores.computed(
1797
2203
  [cart, service_items],
1798
- (cartValue, items) => Math.max(rawServiceItemCount(cartValue), items.length)
2204
+ (cartValue, items) => Math.max(
2205
+ rawServiceItemCount(cartValue),
2206
+ items.reduce((total, item) => total + Math.max(1, item.slots.length), 0)
2207
+ )
1799
2208
  );
1800
2209
  const item_count = nanostores.computed(
1801
2210
  [cart, product_item_count, service_item_count],
@@ -1835,8 +2244,15 @@ function initialize(config) {
1835
2244
  error: null
1836
2245
  });
1837
2246
  const service_state = nanostores.map(createServiceInitialState());
1838
- const service_form_node = nanostores.atom(null);
1839
- const service_form_blocks = nanostores.computed(service_form_node, (node) => node?.blocks || []);
2247
+ const service_form_definitions = /* @__PURE__ */ new Map();
2248
+ const service_form_state = nanostores.map({
2249
+ provider_id: null,
2250
+ groups: [],
2251
+ loading: false,
2252
+ error: null
2253
+ });
2254
+ const service_form_groups = nanostores.computed(service_form_state, (state) => state.groups);
2255
+ const service_form_blocks = nanostores.computed(service_form_groups, (groups) => groups.flatMap((group) => group.blocks));
1840
2256
  client.onAuthStateChanged((value) => session.set(value));
1841
2257
  currency.subscribe((value) => service_state.setKey("currency", value));
1842
2258
  session.subscribe((value) => {
@@ -1851,25 +2267,30 @@ function initialize(config) {
1851
2267
  function currentLocale() {
1852
2268
  return locale.get() || client.getLocale() || "en";
1853
2269
  }
1854
- function currentCurrency() {
1855
- return currency.get() || market.get()?.currency || null;
2270
+ function clientForStore(storeId) {
2271
+ const existing = formClients.get(storeId);
2272
+ if (existing) return existing;
2273
+ const scoped = client.forStore(storeId);
2274
+ formClients.set(storeId, scoped);
2275
+ return scoped;
1856
2276
  }
1857
2277
  function currentStripePublishableKey() {
1858
2278
  const provider = payment_config.get()?.provider;
1859
- return provider?.publishable_key || provider?.publishableKey || provider?.publicKey || null;
2279
+ return provider?.publishable_key || null;
2280
+ }
2281
+ function currentStripeConnectedAccountId() {
2282
+ const provider = payment_config.get()?.provider;
2283
+ return provider?.connected_account_id || null;
1860
2284
  }
1861
2285
  function currentPaymentAmount() {
1862
2286
  return Math.max(
1863
2287
  0,
1864
- firstFiniteNumber(
1865
- quote.get()?.charge_amount,
1866
- quote.get()?.payment?.total,
1867
- cart.get()?.quote_snapshot?.charge_amount,
1868
- cart.get()?.quote_snapshot?.payment?.total,
1869
- cart.get()?.quote_snapshot?.total
1870
- ) ?? 0
2288
+ firstFiniteNumber(quote.get()?.charge_amount, cart.get()?.quote_snapshot?.charge_amount, cart.get()?.quote_snapshot?.total) ?? 0
1871
2289
  );
1872
2290
  }
2291
+ function currentPaymentCurrency() {
2292
+ return quote.get()?.money?.currency?.trim() || cart.get()?.quote_snapshot?.money?.currency?.trim() || null;
2293
+ }
1873
2294
  function setPaymentController(controller) {
1874
2295
  const current = payment_controller.get();
1875
2296
  if (current && current !== controller) {
@@ -1886,10 +2307,24 @@ function initialize(config) {
1886
2307
  if (!publishableKey) {
1887
2308
  throw new Error("Stripe publishable key is required to mount card payment");
1888
2309
  }
2310
+ const hasExplicitAmount = options.amount !== void 0;
2311
+ const hasExplicitCurrency = Boolean(options.currency?.trim());
2312
+ if (hasExplicitAmount !== hasExplicitCurrency) {
2313
+ throw new Error("Stripe amount and currency must be supplied together");
2314
+ }
2315
+ const amount = hasExplicitAmount ? options.amount : currentPaymentAmount();
2316
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
2317
+ throw new Error("A positive minor-unit payment amount is required to mount card payment");
2318
+ }
2319
+ const paymentCurrency = hasExplicitCurrency ? options.currency.trim() : currentPaymentCurrency();
2320
+ if (!paymentCurrency || !/^[a-z]{3}$/i.test(paymentCurrency)) {
2321
+ throw new Error("An explicit three-letter payment currency is required to mount card payment");
2322
+ }
1889
2323
  const controller = await createStripeConfirmationTokenController({
1890
2324
  publishableKey,
1891
- amount: Math.max(0, options.amount ?? currentPaymentAmount()),
1892
- currency: options.currency || currentCurrency() || "usd",
2325
+ connectedAccountId: options.connectedAccountId || currentStripeConnectedAccountId() || void 0,
2326
+ amount,
2327
+ currency: paymentCurrency,
1893
2328
  ...options.appearance ? { appearance: options.appearance } : {}
1894
2329
  });
1895
2330
  controller.mount(target);
@@ -1915,10 +2350,24 @@ function initialize(config) {
1915
2350
  }
1916
2351
  async function identify(params = {}) {
1917
2352
  if (params.market) setMarket(params.market);
1918
- const result = await client.identify({ ...params, market: params.market || currentMarketKey() });
1919
- session.set(result);
2353
+ const result = await client.identify({
2354
+ ...params,
2355
+ market: params.market || currentMarketKey()
2356
+ });
2357
+ session.set({
2358
+ contact: result.contact,
2359
+ store: result.store,
2360
+ market: result.market
2361
+ });
1920
2362
  return result;
1921
2363
  }
2364
+ async function identifyContactEmailIfMissing(email) {
2365
+ const normalizedEmail = email.trim().toLowerCase();
2366
+ if (!normalizedEmail) throw new Error("Contact email is required");
2367
+ const current = session.get();
2368
+ if (current?.contact.email === normalizedEmail) return current;
2369
+ return identify({ email: normalizedEmail });
2370
+ }
1922
2371
  function setMarket(key) {
1923
2372
  market_key.set(key);
1924
2373
  client.setMarket(key);
@@ -1943,7 +2392,9 @@ function initialize(config) {
1943
2392
  const refreshRevision = cartWriteRevision;
1944
2393
  cartRequest = (async () => {
1945
2394
  await ensureSession();
1946
- const response = await client.cart.refresh({ market: currentMarketKey() });
2395
+ const response = await client.eshop.cart.current({
2396
+ market: currentMarketKey()
2397
+ });
1947
2398
  await applyCartResponse(response, { ifRevision: refreshRevision });
1948
2399
  return response;
1949
2400
  })();
@@ -1957,9 +2408,9 @@ function initialize(config) {
1957
2408
  cart_status.setKey("loading", false);
1958
2409
  }
1959
2410
  }
1960
- async function buildProductCartItem(item, source) {
2411
+ async function buildProductCartItem(item, source, productHint) {
1961
2412
  try {
1962
- const product = await client.eshop.product.get({ id: item.product_id });
2413
+ const product = productHint?.id === item.product_id ? productHint : await client.eshop.product.get({ id: item.product_id });
1963
2414
  const variant = product.variants.find((candidate) => candidate.id === item.variant_id);
1964
2415
  if (!variant) return null;
1965
2416
  return {
@@ -1970,7 +2421,7 @@ function initialize(config) {
1970
2421
  product_slug: entitySlug(product, currentLocale()),
1971
2422
  variant_attributes: variant.attributes,
1972
2423
  requires_shipping: variant.requires_shipping !== false,
1973
- price: priceForMarket(variant.prices, currentMarketKey(), currentCurrency()),
2424
+ price: priceForMarket(variant.prices, currentMarketKey(), market.get()?.currency),
1974
2425
  quantity: item.quantity,
1975
2426
  added_at: source.created_at ? source.created_at * 1e3 : Date.now(),
1976
2427
  max_stock: availableStock(client, variant)
@@ -1992,18 +2443,15 @@ function initialize(config) {
1992
2443
  provider = await client.eshop.provider.get({ id: item.provider_id });
1993
2444
  } catch {
1994
2445
  }
1995
- for (const [index, slot] of item.slots.entries()) {
1996
- rows.push({
1997
- id: item.id || createId(`service_${index}`),
1998
- service_id: item.service_id,
1999
- provider_id: item.provider_id,
2000
- from: slot.from,
2001
- to: slot.to,
2002
- forms: item.forms || [],
2003
- service_name: service ? serviceName(service, currentLocale()) : item.service_id,
2004
- provider_name: provider ? providerName(provider, currentLocale()) : item.provider_id
2005
- });
2006
- }
2446
+ rows.push({
2447
+ id: item.id || createId("service"),
2448
+ service_id: item.service_id,
2449
+ provider_id: item.provider_id,
2450
+ slots: item.slots,
2451
+ forms: item.forms || [],
2452
+ service_name: service ? serviceName(service, currentLocale()) : item.service_id,
2453
+ provider_name: provider ? providerName(provider, currentLocale()) : item.provider_id
2454
+ });
2007
2455
  }
2008
2456
  return rows;
2009
2457
  }
@@ -2018,11 +2466,9 @@ function initialize(config) {
2018
2466
  quote.set(response.quote_snapshot || null);
2019
2467
  const items = response.items || [];
2020
2468
  const products = await Promise.all(
2021
- items.filter((item) => item.type === "product").map((item) => buildProductCartItem(item, response))
2022
- );
2023
- const services = await buildServiceCartItems(
2024
- items.filter((item) => item.type === "service")
2469
+ items.filter((item) => item.type === "product").map((item) => buildProductCartItem(item, response, options.productHint))
2025
2470
  );
2471
+ const services = await buildServiceCartItems(items.filter((item) => item.type === "service"));
2026
2472
  product_items.set(products.filter((item) => item !== null));
2027
2473
  service_items.set(services);
2028
2474
  return response;
@@ -2038,21 +2484,17 @@ function initialize(config) {
2038
2484
  cart_status.setKey("error", null);
2039
2485
  try {
2040
2486
  const current = cart.get() || await ensureCart();
2041
- const response = await client.cart.update({
2487
+ const response = await client.eshop.cart.update({
2042
2488
  id: current.id,
2043
2489
  market: currentMarketKey(),
2044
2490
  items: checkoutItems(input),
2045
- shipping_address: input.shipping_address || void 0,
2046
- billing_address: input.billing_address || void 0,
2047
- forms: normalizeForms(input.forms),
2048
- promo_code: input.promo_code === void 0 ? promo_code.get() || void 0 : input.promo_code || void 0,
2049
- payment_method_key: input.payment_method_key || void 0,
2050
- shipping_method_id: input.shipping_method_id || cart_status.get().selected_shipping_method_id || void 0
2491
+ shipping_address: input.shipping_address,
2492
+ billing_address: input.billing_address,
2493
+ forms: input.forms,
2494
+ promo_code: input.promo_code === null ? "" : input.promo_code === void 0 ? promo_code.get() || void 0 : input.promo_code,
2495
+ payment_method_key: input.payment_method_key === null ? "" : input.payment_method_key,
2496
+ 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
2051
2497
  });
2052
- if (input.promo_code !== void 0) promo_code.set(input.promo_code);
2053
- if (input.shipping_method_id !== void 0) {
2054
- cart_status.setKey("selected_shipping_method_id", input.shipping_method_id);
2055
- }
2056
2498
  await applyCartResponse(response, { ifRevision: writeRevision });
2057
2499
  return response;
2058
2500
  } catch (error) {
@@ -2067,7 +2509,7 @@ function initialize(config) {
2067
2509
  const writeRevision = nextCartWriteRevision();
2068
2510
  try {
2069
2511
  const current = cart.get() || await ensureCart();
2070
- const response = await client.cart.addItem({
2512
+ const response = await client.eshop.cart.addItem({
2071
2513
  id: current.id,
2072
2514
  item: {
2073
2515
  type: "product",
@@ -2076,7 +2518,7 @@ function initialize(config) {
2076
2518
  quantity
2077
2519
  }
2078
2520
  });
2079
- await applyCartResponse(response, { ifRevision: writeRevision });
2521
+ await applyCartResponse(response, { ifRevision: writeRevision, productHint: product });
2080
2522
  return response;
2081
2523
  } catch (error) {
2082
2524
  cart_status.setKey("error", readErrorMessage(error, "Failed to add product to cart."));
@@ -2099,11 +2541,9 @@ function initialize(config) {
2099
2541
  product_items.set(product_items.get().filter((candidate) => candidate.id !== itemId));
2100
2542
  const current = cart.get();
2101
2543
  if (!current || !item) return null;
2102
- const response = await client.cart.removeItem({
2544
+ const response = await client.eshop.cart.removeItem({
2103
2545
  id: current.id,
2104
- item_id: item.id,
2105
- product_id: item.product_id,
2106
- variant_id: item.variant_id
2546
+ item_id: item.id
2107
2547
  });
2108
2548
  await applyCartResponse(response, { ifRevision: writeRevision });
2109
2549
  return response;
@@ -2125,7 +2565,7 @@ function initialize(config) {
2125
2565
  const current = cart.get();
2126
2566
  clearLocalCart();
2127
2567
  if (!current) return null;
2128
- const response = await client.cart.clear({ id: current.id });
2568
+ const response = await client.eshop.cart.clear({ id: current.id });
2129
2569
  await applyCartResponse(response, { ifRevision: writeRevision });
2130
2570
  return response;
2131
2571
  }
@@ -2146,7 +2586,7 @@ function initialize(config) {
2146
2586
  cart_status.setKey("quote_error", null);
2147
2587
  try {
2148
2588
  const current = await syncCart(input);
2149
- const response = await client.cart.quote({ id: current.id });
2589
+ const response = await client.eshop.cart.quote({ id: current.id });
2150
2590
  quote.set(response);
2151
2591
  return response;
2152
2592
  } catch (error) {
@@ -2164,23 +2604,17 @@ function initialize(config) {
2164
2604
  try {
2165
2605
  const current = await syncCart(input);
2166
2606
  const quoteValue = quote.get();
2167
- const paymentMethodKey = input.payment_method_key || current.payment_method_key || quoteValue?.payment?.payment_method_key || void 0;
2168
- let chargeAmount = firstFiniteNumber(
2169
- quoteValue?.charge_amount,
2170
- quoteValue?.payment?.total,
2171
- current.quote_snapshot?.charge_amount,
2172
- current.quote_snapshot?.payment?.total
2173
- );
2607
+ const paymentMethodKey = input.payment_method_key || current.payment_method_key || quoteValue?.money?.payment_method_key || void 0;
2608
+ let chargeAmount = firstFiniteNumber(quoteValue?.charge_amount, current.quote_snapshot?.charge_amount);
2174
2609
  if (paymentMethodKey === "credit_card" && chargeAmount === void 0) {
2175
- const latestQuote = await client.cart.quote({ id: current.id });
2610
+ const latestQuote = await client.eshop.cart.quote({ id: current.id });
2176
2611
  quote.set(latestQuote);
2177
- chargeAmount = firstFiniteNumber(
2178
- latestQuote.charge_amount,
2179
- latestQuote.payment?.total,
2180
- latestQuote.total
2181
- );
2612
+ chargeAmount = firstFiniteNumber(latestQuote.charge_amount, latestQuote.total);
2613
+ }
2614
+ if (paymentMethodKey === "credit_card" && (typeof chargeAmount !== "number" || !Number.isSafeInteger(chargeAmount) || chargeAmount < 0)) {
2615
+ throw new Error("Card checkout requires a non-negative integer charge amount in minor units");
2182
2616
  }
2183
- const needsConfirmationToken = paymentMethodKey === "credit_card" && (chargeAmount === void 0 || chargeAmount > 0);
2617
+ const needsConfirmationToken = paymentMethodKey === "credit_card" && typeof chargeAmount === "number" && chargeAmount > 0;
2184
2618
  let confirmationTokenId;
2185
2619
  let returnUrl = input.return_url;
2186
2620
  const paymentController = input.payment ?? payment_controller.get();
@@ -2194,7 +2628,7 @@ function initialize(config) {
2194
2628
  confirmationTokenId = token.confirmation_token_id;
2195
2629
  returnUrl = token.return_url || returnUrl;
2196
2630
  }
2197
- const response = await client.cart.checkout({
2631
+ const response = await client.eshop.cart.checkout({
2198
2632
  id: current.id,
2199
2633
  payment_method_key: paymentMethodKey,
2200
2634
  confirmation_token_id: confirmationTokenId,
@@ -2213,8 +2647,8 @@ function initialize(config) {
2213
2647
  service_items: input.service_items || service_items.get(),
2214
2648
  shipping_address: input.shipping_address || null,
2215
2649
  billing_address: input.billing_address || null,
2216
- total: quoteValue?.payment?.total || quoteValue?.total || response.payment?.total,
2217
- currency: quoteValue?.payment?.currency || currentCurrency(),
2650
+ total: response.payment.amount,
2651
+ currency: response.payment.currency,
2218
2652
  payment_method_key: paymentMethodKey || null,
2219
2653
  created_at: Date.now()
2220
2654
  };
@@ -2232,7 +2666,7 @@ function initialize(config) {
2232
2666
  }
2233
2667
  function serviceCalendar() {
2234
2668
  const state = service_state.get();
2235
- const { currentMonth, selectedDate, startDate, endDate, availability, selectedProviderId } = state;
2669
+ const { currentMonth, selectedDate, availability, selectedProviderId } = state;
2236
2670
  const year = currentMonth.getFullYear();
2237
2671
  const monthIndex = currentMonth.getMonth();
2238
2672
  const first = new Date(year, monthIndex, 1);
@@ -2255,18 +2689,12 @@ function initialize(config) {
2255
2689
  for (let day = 1; day <= last.getDate(); day++) {
2256
2690
  const date = new Date(year, monthIndex, day);
2257
2691
  const iso = `${year}-${String(monthIndex + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
2258
- const isSelected = iso === selectedDate || iso === startDate || iso === endDate;
2259
- let isInRange = false;
2260
- if (startDate && endDate) {
2261
- const time = date.getTime();
2262
- isInRange = time > new Date(startDate).getTime() && time < new Date(endDate).getTime();
2263
- }
2264
2692
  cells.push({
2265
2693
  date,
2266
2694
  iso,
2267
2695
  available: hasAvailableSlotsForDate(availability, iso, selectedProviderId),
2268
- isSelected,
2269
- isInRange,
2696
+ isSelected: iso === selectedDate,
2697
+ isInRange: false,
2270
2698
  isToday: date.getTime() === today.getTime(),
2271
2699
  blank: false
2272
2700
  });
@@ -2303,46 +2731,38 @@ function initialize(config) {
2303
2731
  })
2304
2732
  }));
2305
2733
  }
2306
- function toServiceCartItem(slot) {
2307
- return {
2308
- id: slot.id,
2309
- service_id: slot.serviceId,
2310
- provider_id: slot.providerId,
2311
- from: slot.from,
2312
- to: slot.to,
2313
- forms: [],
2314
- service_name: slot.serviceName,
2315
- date_text: slot.dateText,
2316
- time_text: slot.timeText,
2317
- is_multi_day: slot.isMultiDay
2318
- };
2319
- }
2320
- function fromServiceCartItem(item) {
2734
+ function toServiceCartItem(slots, forms = []) {
2735
+ const orderedSlots = [...slots].sort((left, right) => left.from - right.from || left.to - right.to);
2736
+ const [first] = orderedSlots;
2737
+ if (!first) throw new Error("At least one service slot is required");
2738
+ if (orderedSlots.some((slot) => slot.serviceId !== first.serviceId || slot.providerId !== first.providerId)) {
2739
+ throw new Error("A booking can only contain slots for one service and provider");
2740
+ }
2741
+ if (orderedSlots.some((slot) => !Number.isSafeInteger(slot.from) || !Number.isSafeInteger(slot.to) || slot.from >= slot.to)) {
2742
+ throw new Error("A booking contains an invalid service slot");
2743
+ }
2744
+ for (let index = 1; index < orderedSlots.length; index += 1) {
2745
+ if (orderedSlots[index - 1].to !== orderedSlots[index].from) {
2746
+ throw new Error("A multi-slot booking must contain adjacent slots");
2747
+ }
2748
+ }
2321
2749
  return {
2322
- id: item.id,
2323
- serviceId: item.service_id,
2324
- providerId: item.provider_id,
2325
- from: item.from,
2326
- to: item.to,
2327
- serviceName: item.service_name || "",
2328
- date: item.date_text || "",
2329
- dateText: item.date_text || "",
2330
- timeText: item.time_text || formatServiceSlotTime(item.from, item.to, service_state.get().timezone),
2331
- isMultiDay: item.is_multi_day
2750
+ id: createId("booking"),
2751
+ service_id: first.serviceId,
2752
+ provider_id: first.providerId,
2753
+ slots: orderedSlots.map(({ from, to }) => ({ from, to })),
2754
+ forms,
2755
+ service_name: first.serviceName,
2756
+ date_text: first.dateText,
2757
+ time_text: first.timeText,
2758
+ is_multi_day: orderedSlots.length > 1
2332
2759
  };
2333
2760
  }
2334
- function setServiceCartFromServiceItems(items) {
2335
- const next = items.map(fromServiceCartItem);
2336
- const current = service_state.get().cart;
2337
- if (JSON.stringify(current) !== JSON.stringify(next)) {
2338
- service_state.setKey("cart", next);
2339
- }
2340
- }
2341
- async function syncServiceCart(slots) {
2761
+ async function syncServiceCart(items) {
2342
2762
  try {
2343
2763
  return await syncCart({
2344
2764
  product_items: product_items.get(),
2345
- service_items: slots.map(toServiceCartItem)
2765
+ service_items: items
2346
2766
  });
2347
2767
  } catch (error) {
2348
2768
  service_state.setKey("quoteError", readErrorMessage(error, "Failed to sync service cart."));
@@ -2359,18 +2779,22 @@ function initialize(config) {
2359
2779
  const service_can_proceed = nanostores.computed(service_state, (state) => {
2360
2780
  const step = serviceCurrentStepName();
2361
2781
  if (step === "datetime") {
2362
- return state.isMultiDay ? !!(state.startDate && state.endDate && state.selectedSlot) : !!(state.selectedDate && state.selectedSlot);
2782
+ return !!(state.selectedDate && state.selectedSlot);
2363
2783
  }
2364
2784
  if (step === "review") return true;
2365
2785
  return false;
2366
2786
  });
2367
2787
  const service_month_year = nanostores.computed(
2368
2788
  service_state,
2369
- (state) => state.currentMonth.toLocaleString(void 0, { month: "long", year: "numeric" })
2789
+ (state) => state.currentMonth.toLocaleString(void 0, {
2790
+ month: "long",
2791
+ year: "numeric"
2792
+ })
2370
2793
  );
2371
- const service_chain_start = nanostores.computed(service_state, (state) => {
2372
- if (!state.cart.length) return null;
2373
- return Math.max(...state.cart.map((slot) => slot.to));
2794
+ const service_chain_start = nanostores.computed(service_items, (items) => {
2795
+ const slots = items.flatMap((item) => item.slots);
2796
+ if (!slots.length) return null;
2797
+ return Math.max(...slots.map((slot) => slot.to));
2374
2798
  });
2375
2799
  const service_total_steps = nanostores.computed(service_state, (state) => state.service ? 2 : 0);
2376
2800
  const service_steps = nanostores.computed(service_state, () => ({
@@ -2385,40 +2809,101 @@ function initialize(config) {
2385
2809
  });
2386
2810
  function formatServiceDateDisplay(value) {
2387
2811
  if (!value) return "";
2388
- return new Date(value).toLocaleDateString(void 0, { month: "short", day: "numeric" });
2812
+ return new Date(value).toLocaleDateString(void 0, {
2813
+ month: "short",
2814
+ day: "numeric"
2815
+ });
2389
2816
  }
2390
- function serviceProviderId(provider) {
2391
- return "provider_id" in provider ? provider.provider_id : provider.id;
2817
+ function configuredServiceFormIds(relationship) {
2818
+ const formIds = relationship.forms.map((entry) => entry.form_id.trim());
2819
+ if (formIds.some((formId) => !formId) || new Set(formIds).size !== formIds.length) {
2820
+ throw new Error(`Service provider ${relationship.provider_id} has blank or duplicate configured form IDs`);
2821
+ }
2822
+ return formIds;
2823
+ }
2824
+ function resolveServiceProvider(state, providerId) {
2825
+ const serviceId = state.service?.id;
2826
+ if (!serviceId) return null;
2827
+ const relationships = state.serviceProviders.filter((relationship) => relationship.service_id === serviceId);
2828
+ const targetProviderId = providerId ?? state.selectedSlot?.providerId ?? state.selectedProviderId;
2829
+ if (targetProviderId) {
2830
+ return relationships.find((relationship) => relationship.provider_id === targetProviderId) || null;
2831
+ }
2832
+ return relationships.length === 1 ? relationships[0] : null;
2833
+ }
2834
+ function clearServiceFormState(error = null, loading = false) {
2835
+ service_form_state.set({
2836
+ provider_id: null,
2837
+ groups: [],
2838
+ loading,
2839
+ error
2840
+ });
2392
2841
  }
2393
- function getFirstServiceProviderEntry(state) {
2394
- const serviceWithProviders = state.service;
2395
- const providers = serviceWithProviders?.providers;
2396
- if (!providers?.length) return null;
2397
- if (state.selectedProviderId) {
2398
- const match = providers.find((provider) => provider.provider_id === state.selectedProviderId);
2399
- if (match) return match;
2842
+ function activateServiceProviderForms(providerId, reset = false) {
2843
+ const relationship = resolveServiceProvider(service_state.get(), providerId);
2844
+ if (!relationship) {
2845
+ clearServiceFormState();
2846
+ return null;
2847
+ }
2848
+ const formIds = configuredServiceFormIds(relationship);
2849
+ const current = service_form_state.get();
2850
+ const currentFormIds = current.groups.map((group) => group.form.id);
2851
+ if (!reset && current.provider_id === relationship.provider_id && currentFormIds.length === formIds.length && currentFormIds.every((formId, index) => formId === formIds[index])) {
2852
+ if (current.error) service_form_state.setKey("error", null);
2853
+ return relationship;
2400
2854
  }
2401
- return providers[0];
2855
+ const groups = formIds.map((formId) => {
2856
+ const form = service_form_definitions.get(formId);
2857
+ if (!form) throw new Error(`Configured booking form '${formId}' was not loaded`);
2858
+ return {
2859
+ form,
2860
+ blocks: form.schema.map(formSchemaToBlock)
2861
+ };
2862
+ });
2863
+ service_form_state.set({
2864
+ provider_id: relationship.provider_id,
2865
+ groups,
2866
+ loading: false,
2867
+ error: null
2868
+ });
2869
+ return relationship;
2402
2870
  }
2403
- async function loadServiceForm() {
2404
- try {
2405
- const form = await loadForm({ key: "order-form" });
2406
- const blocks = (form.schema || []).map(formSchemaToBlock);
2407
- service_form_node.set({ blocks });
2408
- return blocks;
2409
- } catch {
2410
- service_form_node.set({ blocks: [] });
2411
- return [];
2871
+ async function loadServiceFormDefinitions(relationships) {
2872
+ const formIds = [
2873
+ ...new Set(relationships.flatMap((relationship) => configuredServiceFormIds(relationship)))
2874
+ ];
2875
+ if (formIds.length === 0) return;
2876
+ const forms = await Promise.all(formIds.map((formId) => loadForm({ id: formId })));
2877
+ for (let index = 0; index < forms.length; index += 1) {
2878
+ const form = forms[index];
2879
+ const formId = formIds[index];
2880
+ if (form.id !== formId) {
2881
+ throw new Error(`Configured booking form '${formId}' resolved to '${form.id}'`);
2882
+ }
2412
2883
  }
2884
+ for (const form of forms) service_form_definitions.set(form.id, form);
2885
+ }
2886
+ function configuredServiceFormEntries(relationship) {
2887
+ const formIds = configuredServiceFormIds(relationship);
2888
+ if (formIds.length === 0) return [];
2889
+ activateServiceProviderForms(relationship.provider_id);
2890
+ const state = service_form_state.get();
2891
+ if (state.provider_id !== relationship.provider_id || state.groups.length !== formIds.length || state.groups.some((group, index) => group.form.id !== formIds[index])) {
2892
+ throw new Error(`Booking forms are not ready for provider ${relationship.provider_id}`);
2893
+ }
2894
+ return state.groups.map(
2895
+ (group) => createFormEntryFromValues(
2896
+ group.form,
2897
+ Object.fromEntries(group.blocks.map((block) => [block.key, block.value]))
2898
+ )
2899
+ );
2413
2900
  }
2414
2901
  const service_controller = {
2415
2902
  async initialize() {
2416
2903
  service_state.setKey("tzGroups", normalizeTimezoneGroups(client.utils.tzGroups));
2417
2904
  await ensureCart();
2418
- setServiceCartFromServiceItems(service_items.get());
2419
2905
  const methods = session.get()?.market?.payment_methods || [];
2420
2906
  if (methods.length) service_state.setKey("availablePaymentMethods", methods);
2421
- await loadServiceForm();
2422
2907
  },
2423
2908
  setTimezone(tz) {
2424
2909
  service_state.setKey("timezone", tz);
@@ -2427,39 +2912,62 @@ function initialize(config) {
2427
2912
  if (state.selectedDate) {
2428
2913
  service_state.setKey("slots", computeServiceSlots(state.selectedDate));
2429
2914
  service_state.setKey("selectedSlot", null);
2915
+ service_state.setKey("quote", null);
2916
+ service_state.setKey("quoteError", null);
2917
+ activateServiceProviderForms();
2430
2918
  }
2431
2919
  },
2432
2920
  async select(service) {
2433
- service_state.setKey("loading", true);
2921
+ service_form_definitions.clear();
2922
+ clearServiceFormState(null, true);
2923
+ service_state.set({
2924
+ ...service_state.get(),
2925
+ service: null,
2926
+ serviceProviders: [],
2927
+ providers: [],
2928
+ selectedProviderId: null,
2929
+ availability: null,
2930
+ selectedDate: null,
2931
+ slots: [],
2932
+ selectedSlot: null,
2933
+ dateTimeConfirmed: false,
2934
+ quote: null,
2935
+ quoteError: null,
2936
+ loading: true
2937
+ });
2434
2938
  try {
2435
- const isMultiDayBlock = service.blocks?.find((block) => block.key === "isMultiDay");
2436
- const blockValue = isMultiDayBlock?.value;
2437
- const isMultiDay = Array.isArray(blockValue) ? blockValue[0] === true : blockValue === true;
2438
2939
  const [fullService, serviceProviders] = await Promise.all([
2439
2940
  client.eshop.service.get({ id: service.id }),
2440
- client.eshop.service.findProviders({ service_id: service.id })
2941
+ client.eshop.service.findProviders({
2942
+ service_id: service.id
2943
+ })
2944
+ ]);
2945
+ const providerIds = [...new Set(serviceProviders.map((relationship) => relationship.provider_id))];
2946
+ const [providerResults] = await Promise.all([
2947
+ Promise.all(providerIds.map((id) => client.eshop.provider.get({ id }).catch(() => null))),
2948
+ loadServiceFormDefinitions(serviceProviders)
2441
2949
  ]);
2442
- const providerIds = [...new Set(serviceProviders.map(serviceProviderId))];
2443
- const providerResults = await Promise.all(
2444
- providerIds.map((id) => client.eshop.provider.get({ id }).catch(() => null))
2445
- );
2446
2950
  service_state.set({
2447
2951
  ...service_state.get(),
2448
2952
  service: fullService,
2953
+ serviceProviders,
2449
2954
  providers: providerResults.filter((provider) => provider !== null),
2450
2955
  selectedProviderId: null,
2451
2956
  availability: null,
2452
2957
  selectedDate: null,
2453
- startDate: null,
2454
- endDate: null,
2455
2958
  slots: [],
2456
2959
  selectedSlot: null,
2457
2960
  currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
2458
2961
  loading: false,
2459
- isMultiDay
2962
+ dateTimeConfirmed: false,
2963
+ quote: null,
2964
+ quoteError: null
2460
2965
  });
2966
+ activateServiceProviderForms();
2461
2967
  await service_controller.loadMonth();
2462
2968
  } catch (error) {
2969
+ service_form_definitions.clear();
2970
+ clearServiceFormState(readErrorMessage(error, "Failed to load booking forms."));
2463
2971
  service_state.setKey("loading", false);
2464
2972
  throw error;
2465
2973
  }
@@ -2502,90 +3010,71 @@ function initialize(config) {
2502
3010
  void service_controller.loadMonth();
2503
3011
  },
2504
3012
  selectProvider(providerId) {
3013
+ const state = service_state.get();
3014
+ if (providerId && !resolveServiceProvider(state, providerId)) {
3015
+ throw new Error(`Provider ${providerId} is not configured for the selected service`);
3016
+ }
2505
3017
  service_state.set({
2506
- ...service_state.get(),
3018
+ ...state,
2507
3019
  selectedProviderId: providerId,
2508
3020
  selectedDate: null,
2509
- startDate: null,
2510
- endDate: null,
2511
3021
  slots: [],
2512
- selectedSlot: null
3022
+ selectedSlot: null,
3023
+ dateTimeConfirmed: false,
3024
+ quote: null,
3025
+ quoteError: null
2513
3026
  });
3027
+ activateServiceProviderForms(providerId);
2514
3028
  void service_controller.loadMonth();
2515
3029
  },
2516
3030
  selectDate(cell) {
2517
3031
  if (cell.blank || !cell.available) return;
2518
- service_state.setKey("dateTimeConfirmed", false);
2519
3032
  const state = service_state.get();
2520
- if (state.isMultiDay) {
2521
- if (!state.startDate) {
2522
- service_state.setKey("startDate", cell.iso);
2523
- service_state.setKey("selectedDate", cell.iso);
2524
- service_state.setKey("endDate", null);
2525
- service_state.setKey("selectedSlot", null);
2526
- } else if (!state.endDate) {
2527
- if (cell.date.getTime() < new Date(state.startDate).getTime()) {
2528
- service_state.setKey("startDate", cell.iso);
2529
- service_state.setKey("endDate", state.startDate);
2530
- } else {
2531
- service_state.setKey("endDate", cell.iso);
2532
- }
2533
- service_controller.createMultiDaySlots();
2534
- } else {
2535
- service_state.setKey("startDate", cell.iso);
2536
- service_state.setKey("selectedDate", cell.iso);
2537
- service_state.setKey("endDate", null);
2538
- service_state.setKey("selectedSlot", null);
2539
- }
2540
- service_controller.updateCalendar();
2541
- } else {
2542
- service_state.set({
2543
- ...state,
2544
- selectedDate: cell.iso,
2545
- slots: computeServiceSlots(cell.iso),
2546
- selectedSlot: null
2547
- });
2548
- service_state.setKey("calendar", serviceCalendar());
2549
- }
3033
+ service_state.set({
3034
+ ...state,
3035
+ selectedDate: cell.iso,
3036
+ slots: computeServiceSlots(cell.iso),
3037
+ selectedSlot: null,
3038
+ dateTimeConfirmed: false,
3039
+ quote: null,
3040
+ quoteError: null
3041
+ });
3042
+ activateServiceProviderForms();
3043
+ service_state.setKey("calendar", serviceCalendar());
2550
3044
  },
2551
- createMultiDaySlots() {
3045
+ selectTimeSlot(slot) {
2552
3046
  const state = service_state.get();
2553
- if (!state.startDate || !state.endDate || !state.availability) return;
2554
- const slots = [];
2555
- for (let day = new Date(state.startDate); day <= new Date(state.endDate); day.setDate(day.getDate() + 1)) {
2556
- const iso = day.toISOString().slice(0, 10);
2557
- for (const slot of getSlotsForDate(state.availability, iso, state.selectedProviderId)) {
2558
- slots.push({
2559
- id: `${state.service?.id || "service"}-${slot.from}-${slots.length}`,
2560
- serviceId: state.service?.id || "",
2561
- providerId: slot.providerId,
2562
- from: slot.from,
2563
- to: slot.to,
2564
- timeText: formatServiceSlotTime(slot.from, slot.to, state.timezone),
2565
- dateText: new Date(slot.from * 1e3).toLocaleDateString([], {
2566
- weekday: "short",
2567
- month: "short",
2568
- day: "numeric",
2569
- timeZone: state.timezone
2570
- }),
2571
- isMultiDay: true
2572
- });
3047
+ if (slot) {
3048
+ if (!state.service || slot.serviceId !== state.service.id) {
3049
+ throw new Error("The selected slot does not belong to the selected service");
3050
+ }
3051
+ if (state.selectedProviderId && slot.providerId !== state.selectedProviderId) {
3052
+ throw new Error("The selected slot does not belong to the selected provider");
3053
+ }
3054
+ if (!resolveServiceProvider(state, slot.providerId)) {
3055
+ throw new Error(`Provider ${slot.providerId} is not configured for the selected service`);
2573
3056
  }
2574
3057
  }
2575
- service_state.setKey("slots", slots);
2576
- service_state.setKey("selectedSlot", slots.length === 1 ? slots[0] : null);
2577
- },
2578
- selectTimeSlot(slot) {
2579
- service_state.setKey("dateTimeConfirmed", false);
2580
- service_state.setKey("selectedSlot", slot);
3058
+ service_state.set({
3059
+ ...state,
3060
+ selectedSlot: slot,
3061
+ dateTimeConfirmed: false,
3062
+ quote: null,
3063
+ quoteError: null
3064
+ });
3065
+ activateServiceProviderForms(slot?.providerId);
2581
3066
  },
2582
3067
  resetDateSelection() {
2583
- service_state.setKey("selectedDate", null);
2584
- service_state.setKey("startDate", null);
2585
- service_state.setKey("endDate", null);
2586
- service_state.setKey("slots", []);
2587
- service_state.setKey("selectedSlot", null);
2588
- service_state.setKey("dateTimeConfirmed", false);
3068
+ service_state.set({
3069
+ ...service_state.get(),
3070
+ selectedDate: null,
3071
+ slots: [],
3072
+ selectedSlot: null,
3073
+ dateTimeConfirmed: false,
3074
+ quote: null,
3075
+ quoteError: null
3076
+ });
3077
+ activateServiceProviderForms();
2589
3078
  },
2590
3079
  updateCalendar() {
2591
3080
  service_state.setKey("calendar", serviceCalendar());
@@ -2598,70 +3087,79 @@ function initialize(config) {
2598
3087
  }
2599
3088
  }
2600
3089
  },
2601
- async addToCart() {
3090
+ async addToCart(explicitSlots) {
2602
3091
  const state = service_state.get();
2603
- const serviceBlocks = state.service?.forms || [];
2604
- const enrich = (slot) => ({
3092
+ const slots = explicitSlots || (state.selectedSlot ? [state.selectedSlot] : []);
3093
+ if (slots.length === 0) return;
3094
+ const first = slots[0];
3095
+ if (!state.service || first.serviceId !== state.service.id) {
3096
+ throw new Error("The booking slots do not belong to the selected service");
3097
+ }
3098
+ const relationship = resolveServiceProvider(state, first.providerId);
3099
+ if (!relationship) {
3100
+ throw new Error(`Provider ${first.providerId} is not configured for the selected service`);
3101
+ }
3102
+ let forms;
3103
+ try {
3104
+ forms = configuredServiceFormEntries(relationship);
3105
+ } catch (error) {
3106
+ service_form_state.setKey("error", readErrorMessage(error, "Booking forms are invalid."));
3107
+ throw error;
3108
+ }
3109
+ const displayName = serviceName(state.service, currentLocale());
3110
+ const enriched = slots.map((slot) => ({
2605
3111
  ...slot,
2606
- serviceName: state.service ? serviceName(state.service, currentLocale()) : "",
2607
- date: slot.dateText,
2608
- serviceBlocks
2609
- });
2610
- const selected = state.isMultiDay && state.slots.length > 0 ? state.slots.map(enrich) : state.selectedSlot ? [enrich(state.selectedSlot)] : [];
2611
- if (!selected.length) return;
2612
- const nextCart = [...state.cart, ...selected];
3112
+ serviceName: displayName,
3113
+ date: slot.dateText
3114
+ }));
3115
+ const nextItems = [...service_items.get(), toServiceCartItem(enriched, forms)];
3116
+ await syncServiceCart(nextItems);
2613
3117
  service_state.set({
2614
- ...state,
2615
- cart: nextCart,
3118
+ ...service_state.get(),
2616
3119
  selectedDate: null,
2617
- startDate: null,
2618
- endDate: null,
2619
3120
  slots: [],
2620
- selectedSlot: null
3121
+ selectedSlot: null,
3122
+ dateTimeConfirmed: false,
3123
+ quote: null,
3124
+ quoteError: null
2621
3125
  });
2622
- await syncServiceCart(nextCart);
3126
+ activateServiceProviderForms(service_state.get().selectedProviderId, true);
2623
3127
  service_state.setKey("calendar", serviceCalendar());
2624
3128
  },
2625
- async removeFromCart(slotId) {
2626
- const nextCart = service_state.get().cart.filter((slot) => slot.id !== slotId);
2627
- service_state.setKey("cart", nextCart);
2628
- await syncServiceCart(nextCart);
3129
+ async removeFromCart(bookingId) {
3130
+ await syncServiceCart(service_items.get().filter((item) => item.id !== bookingId));
2629
3131
  },
2630
3132
  async clearCart() {
2631
- service_state.setKey("cart", []);
2632
3133
  await syncServiceCart([]);
2633
3134
  },
2634
3135
  async checkout(paymentMethodId, forms = []) {
2635
3136
  const state = service_state.get();
2636
- if (!state.cart.length) return { success: false, error: "Cart is empty" };
3137
+ const items = service_items.get();
3138
+ if (!items.length) throw new Error("Cart is empty");
2637
3139
  service_state.setKey("loading", true);
2638
3140
  try {
2639
3141
  const result = await checkout({
2640
- service_items: state.cart.map((slot) => ({
2641
- ...toServiceCartItem(slot),
2642
- forms: []
2643
- })),
3142
+ service_items: items,
2644
3143
  payment_method_key: paymentMethodId,
2645
3144
  promo_code: state.promoCode || void 0,
2646
3145
  forms
2647
3146
  });
2648
3147
  service_state.setKey("cartId", cart.get()?.id || null);
2649
- return { success: true, data: result };
2650
- } catch (error) {
2651
- return { success: false, error: readErrorMessage(error, "Checkout failed.") };
3148
+ return result;
2652
3149
  } finally {
2653
3150
  service_state.setKey("loading", false);
2654
3151
  }
2655
3152
  },
2656
3153
  async fetchQuote(paymentMethodId, promoCode) {
2657
- const state = service_state.get();
2658
- if (!state.cart.length) return null;
3154
+ service_state.get();
3155
+ const items = service_items.get();
3156
+ if (!items.length) return null;
2659
3157
  service_state.setKey("fetchingQuote", true);
2660
3158
  service_state.setKey("quoteError", null);
2661
3159
  try {
2662
3160
  service_state.setKey("promoCode", promoCode || null);
2663
3161
  const response = await fetchQuote({
2664
- service_items: state.cart.map(toServiceCartItem),
3162
+ service_items: items,
2665
3163
  payment_method_key: paymentMethodId,
2666
3164
  promo_code: promoCode || void 0
2667
3165
  });
@@ -2689,6 +3187,9 @@ function initialize(config) {
2689
3187
  if (current === "datetime") {
2690
3188
  service_state.setKey("selectedSlot", null);
2691
3189
  service_state.setKey("dateTimeConfirmed", false);
3190
+ service_state.setKey("quote", null);
3191
+ service_state.setKey("quoteError", null);
3192
+ activateServiceProviderForms();
2692
3193
  }
2693
3194
  },
2694
3195
  nextStep() {
@@ -2698,17 +3199,20 @@ function initialize(config) {
2698
3199
  },
2699
3200
  getServicePrice() {
2700
3201
  const state = service_state.get();
2701
- if (state.quote?.total !== void 0) return String(state.quote.total);
2702
- const provider = getFirstServiceProviderEntry(state);
2703
- if (!provider?.prices) return "";
2704
- return client.utils.formatPrice(provider.prices) || "0";
3202
+ const relationship = resolveServiceProvider(state);
3203
+ if (!relationship) return "";
3204
+ try {
3205
+ const price = priceForMarket(relationship.prices, currentMarketKey(), market.get()?.currency);
3206
+ return client.utils.formatPrice([price]);
3207
+ } catch {
3208
+ return "";
3209
+ }
2705
3210
  },
2706
3211
  formatDateDisplay: formatServiceDateDisplay,
2707
- serviceItemsFromSlots(slots) {
2708
- return slots.map(toServiceCartItem);
3212
+ serviceItemsFromSlots(slots, forms = []) {
3213
+ return slots.length ? [toServiceCartItem(slots, forms)] : [];
2709
3214
  }
2710
3215
  };
2711
- service_items.subscribe((items) => setServiceCartFromServiceItems(items));
2712
3216
  async function loadEntry(params, options) {
2713
3217
  cms_state.setKey("loading", true);
2714
3218
  cms_state.setKey("error", null);
@@ -2718,7 +3222,10 @@ function initialize(config) {
2718
3222
  if (entryParams.id) {
2719
3223
  const entry2 = await client.cms.entry.get(entryParams, options);
2720
3224
  const cacheKey = entryParams.key || entryParams.id || entry2.id;
2721
- cms_state.setKey("entries", { ...cms_state.get().entries, [cacheKey]: entry2 });
3225
+ cms_state.setKey("entries", {
3226
+ ...cms_state.get().entries,
3227
+ [cacheKey]: entry2
3228
+ });
2722
3229
  return entry2;
2723
3230
  }
2724
3231
  if (!entryParams.collection_id || !entryParams.key) {
@@ -2737,7 +3244,10 @@ function initialize(config) {
2737
3244
  if (!entry) {
2738
3245
  throw new Error("CMS entry not found");
2739
3246
  }
2740
- cms_state.setKey("entries", { ...cms_state.get().entries, [entryParams.key]: entry });
3247
+ cms_state.setKey("entries", {
3248
+ ...cms_state.get().entries,
3249
+ [entryParams.key]: entry
3250
+ });
2741
3251
  return entry;
2742
3252
  } catch (error) {
2743
3253
  cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS entry."));
@@ -2746,13 +3256,22 @@ function initialize(config) {
2746
3256
  cms_state.setKey("loading", false);
2747
3257
  }
2748
3258
  }
3259
+ function formCacheKey(params) {
3260
+ const storeId = params.store_id || client.getStoreId();
3261
+ const identifier = params.id ? `id:${params.id}` : params.key ? `key:${params.key}` : "missing";
3262
+ return `${storeId}:${identifier}`;
3263
+ }
2749
3264
  async function loadForm(params, options) {
2750
3265
  cms_state.setKey("loading", true);
2751
3266
  cms_state.setKey("error", null);
2752
3267
  try {
2753
- const form = await client.cms.form.get(params, options);
2754
- const key = params.key || params.id || form.key || form.id;
2755
- cms_state.setKey("forms", { ...cms_state.get().forms, [key]: form });
3268
+ const storeId = params.store_id || client.getStoreId();
3269
+ const formClient = clientForStore(storeId);
3270
+ const form = await formClient.cms.form.get({ ...params, store_id: storeId }, options);
3271
+ const forms = { ...cms_state.get().forms };
3272
+ forms[formCacheKey({ id: form.id, store_id: storeId })] = form;
3273
+ forms[formCacheKey({ key: form.key, store_id: storeId })] = form;
3274
+ cms_state.setKey("forms", forms);
2756
3275
  return form;
2757
3276
  } catch (error) {
2758
3277
  cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS form."));
@@ -2761,12 +3280,26 @@ function initialize(config) {
2761
3280
  cms_state.setKey("loading", false);
2762
3281
  }
2763
3282
  }
2764
- async function submitFormByKey(key, fieldsOrBlocks, options) {
2765
- const forms = cms_state.get().forms;
2766
- const form = forms[key] || await loadForm({ key });
2767
- const fields = fieldsOrBlocks.length > 0 && "properties" in fieldsOrBlocks[0] ? formFieldsFromBlocks(fieldsOrBlocks) : fieldsOrBlocks;
2768
- const payload = { form_id: form.id, fields };
2769
- return client.cms.form.submit(payload, options);
3283
+ async function ensureFormClient(storeId) {
3284
+ const formClient = clientForStore(storeId);
3285
+ const marketKey = currentMarketKey();
3286
+ if (formClient === client) {
3287
+ await ensureSession();
3288
+ } else if (!formClient.session || marketKey && formClient.session.market?.key !== marketKey) {
3289
+ await formClient.identify({ market: marketKey });
3290
+ }
3291
+ return formClient;
3292
+ }
3293
+ async function submitForm(params, options) {
3294
+ const storeId = params.store_id || client.getStoreId();
3295
+ const formClient = await ensureFormClient(storeId);
3296
+ return formClient.cms.form.submit({ ...params, store_id: storeId }, options);
3297
+ }
3298
+ async function submitFormByKey(params, options) {
3299
+ const storeId = params.store_id || client.getStoreId();
3300
+ const form = await loadForm({ key: params.key, store_id: storeId }, options);
3301
+ const entry = createFormEntryFromValues(form, params.values);
3302
+ return submitForm({ store_id: storeId, form_id: form.id, fields: entry.fields }, options);
2770
3303
  }
2771
3304
  async function loadProducts(params = {}, options) {
2772
3305
  eshop_state.setKey("loading_products", true);
@@ -2869,38 +3402,32 @@ function initialize(config) {
2869
3402
  destroy: destroyPaymentController
2870
3403
  },
2871
3404
  applyPromoCode(code, input = {}) {
2872
- promo_code.set(code);
2873
3405
  return fetchQuote({ ...input, promo_code: code });
2874
3406
  },
2875
3407
  removePromoCode(input = {}) {
2876
- promo_code.set(null);
2877
3408
  return fetchQuote({ ...input, promo_code: null });
2878
3409
  },
2879
3410
  selectShippingMethod(id) {
2880
3411
  cart_status.setKey("selected_shipping_method_id", id);
2881
3412
  },
2882
3413
  locationToAddress,
3414
+ createFormEntry,
2883
3415
  buildItems: checkoutItems,
2884
3416
  buildProductItems: toProductCheckoutItems,
2885
3417
  buildServiceItems: toServiceCheckoutItems
2886
3418
  };
2887
3419
  const product_store = {
2888
3420
  get: (params, options) => client.eshop.product.get(params, options),
2889
- find: loadProducts,
2890
- list: loadProducts,
2891
- loadListing: loadProducts,
2892
- loadDetail: (params, options) => client.eshop.product.get(params, options)
3421
+ list: loadProducts
2893
3422
  };
2894
3423
  const service_store = {
2895
3424
  get: (params, options) => client.eshop.service.get(params, options),
2896
- find: loadServices,
2897
3425
  list: loadServices,
2898
- loadListing: loadServices,
2899
- loadDetail: (params, options) => client.eshop.service.get(params, options),
2900
3426
  listProviders: (params, options) => client.eshop.service.findProviders(params, options),
2901
- findProviders: (params, options) => client.eshop.service.findProviders(params, options),
2902
3427
  getAvailability: loadAvailability,
2903
3428
  state: service_state,
3429
+ form_state: service_form_state,
3430
+ form_groups: service_form_groups,
2904
3431
  form_blocks: service_form_blocks,
2905
3432
  current_step_name: service_current_step_name,
2906
3433
  can_proceed: service_can_proceed,
@@ -2917,7 +3444,6 @@ function initialize(config) {
2917
3444
  nextMonth: service_controller.nextMonth,
2918
3445
  selectProvider: service_controller.selectProvider,
2919
3446
  selectDate: service_controller.selectDate,
2920
- createMultiDaySlots: service_controller.createMultiDaySlots,
2921
3447
  selectTimeSlot: service_controller.selectTimeSlot,
2922
3448
  resetDateSelection: service_controller.resetDateSelection,
2923
3449
  updateCalendar: service_controller.updateCalendar,
@@ -2941,8 +3467,8 @@ function initialize(config) {
2941
3467
  currency,
2942
3468
  allowed_payment_methods,
2943
3469
  payment_config,
2944
- cart: cart_store,
2945
3470
  identify,
3471
+ identifyContactEmailIfMissing,
2946
3472
  verify: client.verify,
2947
3473
  me: client.me,
2948
3474
  logout: client.logout,
@@ -2967,7 +3493,7 @@ function initialize(config) {
2967
3493
  },
2968
3494
  form: {
2969
3495
  get: loadForm,
2970
- submit: (params, options) => client.cms.form.submit(params, options),
3496
+ submit: submitForm,
2971
3497
  submitByKey: submitFormByKey
2972
3498
  },
2973
3499
  taxonomy: client.cms.taxonomy
@@ -2978,7 +3504,7 @@ function initialize(config) {
2978
3504
  service: service_store,
2979
3505
  provider: {
2980
3506
  get: (params, options) => client.eshop.provider.get(params, options),
2981
- find: loadProviders
3507
+ list: loadProviders
2982
3508
  },
2983
3509
  order: client.eshop.order,
2984
3510
  cart: cart_store
@@ -3001,11 +3527,37 @@ function initialize(config) {
3001
3527
  utils: client.utils
3002
3528
  };
3003
3529
  }
3530
+ function initialize(config) {
3531
+ const stores = /* @__PURE__ */ new Map();
3532
+ const createScope = (scopeConfig) => {
3533
+ const existing = stores.get(scopeConfig.storeId);
3534
+ if (existing) return existing;
3535
+ const store = initializeStore(scopeConfig);
3536
+ stores.set(scopeConfig.storeId, store);
3537
+ Object.defineProperty(store, "forStore", {
3538
+ enumerable: true,
3539
+ configurable: false,
3540
+ writable: false,
3541
+ value: (storeId) => createScope({
3542
+ ...config,
3543
+ storeId,
3544
+ market: store.getMarket(),
3545
+ locale: store.getLocale()
3546
+ })
3547
+ });
3548
+ return store;
3549
+ };
3550
+ return createScope(config);
3551
+ }
3004
3552
 
3005
3553
  exports.COMMON_ACTION_KEYS = COMMON_ACTION_KEYS;
3554
+ exports.buildFormFields = buildFormFields;
3006
3555
  exports.createCartController = createCartController;
3556
+ exports.createFormEntry = createFormEntry;
3557
+ exports.createFormEntryFromValues = createFormEntryFromValues;
3007
3558
  exports.createStorefront = createStorefront;
3008
3559
  exports.createStripeConfirmationTokenController = createStripeConfirmationTokenController;
3560
+ exports.getBlockContentValue = getBlockContentValue;
3009
3561
  exports.initialize = initialize;
3010
3562
  //# sourceMappingURL=storefront.cjs.map
3011
3563
  //# sourceMappingURL=storefront.cjs.map