@juo/blocks 2.0.0-alpha.1 → 2.0.0-alpha.10

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 (86) hide show
  1. package/dist/{ExtensionRoot-DRpD5EcC.js → ExtensionRoot-CG9dH98z.js} +1 -1
  2. package/dist/{bridge-Mz0CqG6E.js → bridge-Bss7Xq3D.js} +7 -0
  3. package/dist/core.js +1289 -54
  4. package/dist/{css-runtime-B7tZrBIp.js → css-runtime-CjZl0K48.js} +23 -17
  5. package/dist/editor/bridge.d.ts.map +1 -1
  6. package/dist/editor/components.d.ts +11 -0
  7. package/dist/editor/components.d.ts.map +1 -1
  8. package/dist/editor/handler.d.ts.map +1 -1
  9. package/dist/editor.js +37 -10
  10. package/dist/{gridstack-adapter-BGKGC-eO.js → gridstack-adapter-C1Q8gbsH.js} +2 -2
  11. package/dist/languages.d.ts +51 -0
  12. package/dist/languages.d.ts.map +1 -0
  13. package/dist/main.d.ts +1 -0
  14. package/dist/main.d.ts.map +1 -1
  15. package/dist/{morph-CT5hc5Qp.js → morph-LwttdRML.js} +1 -1
  16. package/dist/preact.js +1 -1
  17. package/dist/{project-gridstack-PxN_DIfP.js → project-gridstack-CRZ_7HoT.js} +1 -1
  18. package/dist/react.js +1 -1
  19. package/dist/services/customer/adapter.d.ts +14 -1
  20. package/dist/services/customer/adapter.d.ts.map +1 -1
  21. package/dist/services/customer/adapter.test.d.ts +2 -0
  22. package/dist/services/customer/adapter.test.d.ts.map +1 -0
  23. package/dist/services/customer/customerService.d.ts.map +1 -1
  24. package/dist/services/customer/customerService.test.d.ts +2 -0
  25. package/dist/services/customer/customerService.test.d.ts.map +1 -0
  26. package/dist/services/customer/index.d.ts +2 -2
  27. package/dist/services/customer/index.d.ts.map +1 -1
  28. package/dist/services/customer/types.d.ts +29 -0
  29. package/dist/services/customer/types.d.ts.map +1 -1
  30. package/dist/services/login/adapter.d.ts.map +1 -1
  31. package/dist/services/login/index.d.ts +1 -0
  32. package/dist/services/login/index.d.ts.map +1 -1
  33. package/dist/services/login/loginService.d.ts +1 -0
  34. package/dist/services/login/loginService.d.ts.map +1 -1
  35. package/dist/services/login/loginService.test.d.ts +2 -0
  36. package/dist/services/login/loginService.test.d.ts.map +1 -0
  37. package/dist/services/login/refreshLock.d.ts +28 -0
  38. package/dist/services/login/refreshLock.d.ts.map +1 -0
  39. package/dist/services/login/refreshLock.test.d.ts +2 -0
  40. package/dist/services/login/refreshLock.test.d.ts.map +1 -0
  41. package/dist/services/order/adapter.d.ts +11 -1
  42. package/dist/services/order/adapter.d.ts.map +1 -1
  43. package/dist/services/order/adapter.test.d.ts +2 -0
  44. package/dist/services/order/adapter.test.d.ts.map +1 -0
  45. package/dist/services/order/index.d.ts +2 -2
  46. package/dist/services/order/index.d.ts.map +1 -1
  47. package/dist/services/order/types.d.ts +56 -6
  48. package/dist/services/order/types.d.ts.map +1 -1
  49. package/dist/services/product/adapter.d.ts +42 -0
  50. package/dist/services/product/adapter.d.ts.map +1 -1
  51. package/dist/services/product/index.d.ts +1 -1
  52. package/dist/services/product/index.d.ts.map +1 -1
  53. package/dist/services/settings/types.d.ts +15 -0
  54. package/dist/services/settings/types.d.ts.map +1 -1
  55. package/dist/services/subscription/adapter.d.ts +56 -1
  56. package/dist/services/subscription/adapter.d.ts.map +1 -1
  57. package/dist/services/subscription/index.d.ts +4 -1
  58. package/dist/services/subscription/index.d.ts.map +1 -1
  59. package/dist/services/subscription/instantOrderErrors.d.ts +91 -0
  60. package/dist/services/subscription/instantOrderErrors.d.ts.map +1 -0
  61. package/dist/services/subscription/lineRemoval.d.ts +56 -0
  62. package/dist/services/subscription/lineRemoval.d.ts.map +1 -0
  63. package/dist/services/subscription/lineRemoval.test.d.ts +2 -0
  64. package/dist/services/subscription/lineRemoval.test.d.ts.map +1 -0
  65. package/dist/services/subscription/paymentInstrument.d.ts +119 -0
  66. package/dist/services/subscription/paymentInstrument.d.ts.map +1 -0
  67. package/dist/services/subscription/paymentInstrument.test.d.ts +2 -0
  68. package/dist/services/subscription/paymentInstrument.test.d.ts.map +1 -0
  69. package/dist/services/subscription/store.d.ts.map +1 -1
  70. package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
  71. package/dist/services/subscription/types.d.ts +351 -11
  72. package/dist/services/subscription/types.d.ts.map +1 -1
  73. package/dist/theme-state.d.ts +10 -1
  74. package/dist/theme-state.d.ts.map +1 -1
  75. package/dist/utils/index.d.ts +7 -0
  76. package/dist/utils/index.d.ts.map +1 -1
  77. package/dist/vue/context.d.ts +25 -1
  78. package/dist/vue/context.d.ts.map +1 -1
  79. package/dist/vue/useTranslatedProp.test.d.ts +2 -0
  80. package/dist/vue/useTranslatedProp.test.d.ts.map +1 -0
  81. package/dist/vue.js +37 -3
  82. package/dist/web-components/editor/InlineText.d.ts +1 -0
  83. package/dist/web-components/editor/InlineText.d.ts.map +1 -1
  84. package/dist/web-components-editor.js +15 -7
  85. package/dist/web-components-runtime.js +2 -2
  86. package/package.json +1 -1
package/dist/core.js CHANGED
@@ -3,12 +3,33 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { e as createContext, f as d, m as w, n as defineBlock, h as createBlockInstance, j as getDefinedBlocks, p as registerBlock, E, u, s as subscribeContext } from "./block-BjJztStr.js";
5
5
  import { B, q, t, S, v, _, x, c, y, g, z, A, D, F, G, i, H, b, I, o, J, k, K, l, L, w as w2 } from "./block-BjJztStr.js";
6
- import { G as GRID_V1_ENGINE_ID, d as clampRect, e as applyFillExpansion, f as compactItems, g as findFreeRect, D as DEFAULT_GRID } from "./css-runtime-B7tZrBIp.js";
7
- import { h, i as i2, L as L2, P, a, T, b as b2, j, k as k2, l as l2, m, n, o as o2, p, q as q2, s, t as t2, u as u2, v as v2, c as c2, w as w3, r } from "./css-runtime-B7tZrBIp.js";
6
+ import { G as GRID_V1_ENGINE_ID, d as clampRect, e as applyFillExpansion, f as compactItems, g as findFreeRect, D as DEFAULT_GRID } from "./css-runtime-CjZl0K48.js";
7
+ import { h, i as i2, L as L2, P, a, T, b as b2, j, k as k2, l as l2, m, n, o as o2, p, q as q2, s, t as t2, u as u2, v as v2, c as c2, w as w3, r } from "./css-runtime-CjZl0K48.js";
8
8
  import { reportBlockWarning, reportBlockError } from "./observability.js";
9
- import { a as createEditorBridge, E as createSelectBlockMessage } from "./bridge-Mz0CqG6E.js";
10
- import { p as p2 } from "./project-gridstack-PxN_DIfP.js";
9
+ import { a as createEditorBridge, E as createSelectBlockMessage } from "./bridge-Bss7Xq3D.js";
10
+ import { p as p2 } from "./project-gridstack-CRZ_7HoT.js";
11
11
  import { clearLayoutModeAdapter, getLayoutModeAdapter, registerLayoutModeAdapter } from "./layout-mode-adapter-BBd4JRQd.js";
12
+ const SUPPORTED_UI_LANGUAGES = [
13
+ { code: "cs", label: "Czech" },
14
+ { code: "da", label: "Danish" },
15
+ { code: "nl", label: "Dutch" },
16
+ { code: "en", label: "English" },
17
+ { code: "fr", label: "French" },
18
+ { code: "de", label: "German" },
19
+ { code: "el", label: "Greek" },
20
+ { code: "hu", label: "Hungarian" },
21
+ { code: "pl", label: "Polish" },
22
+ { code: "pt", label: "Portuguese" },
23
+ { code: "sk", label: "Slovak" },
24
+ { code: "sl", label: "Slovenian" },
25
+ { code: "es", label: "Spanish" }
26
+ ];
27
+ function toLanguageSelectOptions(languages) {
28
+ return languages.map(({ code, label }) => ({ label, value: code }));
29
+ }
30
+ const SUPPORTED_UI_LANGUAGE_OPTIONS = toLanguageSelectOptions(
31
+ SUPPORTED_UI_LANGUAGES
32
+ );
12
33
  function createState() {
13
34
  const discountCode = d(null);
14
35
  const products = d([]);
@@ -273,33 +294,233 @@ function createCustomerService(options) {
273
294
  const result = await options.adapter.getCurrent();
274
295
  if (result._tag === "Success") current.value = result.data;
275
296
  return result;
297
+ },
298
+ createPaymentMethod(params) {
299
+ return options.adapter.createPaymentMethod(params);
300
+ },
301
+ async updateAttributes(attributes) {
302
+ var _a;
303
+ const merged = { ...(_a = current.value) == null ? void 0 : _a.attributes, ...attributes };
304
+ const result = await options.adapter.updateAttributes(merged);
305
+ if (result._tag === "Success") current.value = result.data;
306
+ return result;
276
307
  }
277
308
  };
278
309
  }
279
310
  const CustomerServiceContext = createContext("services/customer");
311
+ const PAYMENT_INSTRUMENT_TYPES = [
312
+ "credit_card",
313
+ "paypal",
314
+ "shop_pay",
315
+ "sepa_direct_debit",
316
+ "blik",
317
+ "mbway",
318
+ "bacs",
319
+ "manual"
320
+ ];
321
+ const INSTRUMENT_TYPE_TO_NAME = {
322
+ credit_card: null,
323
+ paypal: "PayPal",
324
+ blik: "BLIK",
325
+ sepa_direct_debit: "SEPA",
326
+ mbway: "MB WAY",
327
+ shop_pay: "Shop Pay",
328
+ bacs: "Bacs",
329
+ manual: "Shopify Checkout"
330
+ };
331
+ function getPaymentInstrumentName(instrument) {
332
+ const name = INSTRUMENT_TYPE_TO_NAME[instrument.type] ?? null;
333
+ if (name != null) return name;
334
+ return instrument.type === "credit_card" ? instrument.brand || instrument.type : instrument.type;
335
+ }
336
+ function getPaymentInstrumentDigits(instrument) {
337
+ switch (instrument.type) {
338
+ case "credit_card":
339
+ case "shop_pay":
340
+ case "bacs":
341
+ return instrument.maskedNumber.slice(-4) || null;
342
+ case "sepa_direct_debit":
343
+ return instrument.consumerAccount.slice(-4) || null;
344
+ default:
345
+ return null;
346
+ }
347
+ }
348
+ function getPaymentInstrumentDetail(instrument) {
349
+ switch (instrument.type) {
350
+ case "paypal":
351
+ return instrument.paypalAccountEmail ?? null;
352
+ case "sepa_direct_debit":
353
+ return instrument.consumerName ?? null;
354
+ case "blik":
355
+ return instrument.label ?? null;
356
+ case "mbway":
357
+ return instrument.phone ?? null;
358
+ default:
359
+ return null;
360
+ }
361
+ }
362
+ function getPaymentInstrumentExpiry(instrument, now = /* @__PURE__ */ new Date()) {
363
+ if (instrument.type !== "credit_card" && instrument.type !== "shop_pay") {
364
+ return null;
365
+ }
366
+ const { expiryMonth: month, expiryYear: year } = instrument;
367
+ if (month == null || year == null) return null;
368
+ const nowYear = now.getFullYear();
369
+ const nowMonth = now.getMonth() + 1;
370
+ const expiresThisMonth = year === nowYear && month === nowMonth;
371
+ const isExpired = year < nowYear || year === nowYear && month < nowMonth;
372
+ return { month, year, isExpired, expiresThisMonth };
373
+ }
374
+ function serializeV1PaymentInstrument(raw) {
375
+ if (raw == null || typeof raw !== "object") return null;
376
+ const r2 = raw;
377
+ if (typeof r2.type !== "string") return null;
378
+ switch (r2.type) {
379
+ case "credit-card":
380
+ return {
381
+ type: "credit_card",
382
+ brand: r2.brand ?? "",
383
+ maskedNumber: r2.maskedNumber ?? "",
384
+ ...r2.expiryMonth != null ? { expiryMonth: r2.expiryMonth } : {},
385
+ ...r2.expiryYear != null ? { expiryYear: r2.expiryYear } : {},
386
+ ...r2.cardholderName != null ? { cardholderName: r2.cardholderName } : {}
387
+ };
388
+ case "paypal":
389
+ return { type: "paypal", paypalAccountEmail: r2.paypalAccountEmail ?? null };
390
+ case "shop-pay":
391
+ return {
392
+ type: "shop_pay",
393
+ maskedNumber: r2.maskedNumber ?? "",
394
+ ...r2.expiryMonth != null ? { expiryMonth: r2.expiryMonth } : {},
395
+ ...r2.expiryYear != null ? { expiryYear: r2.expiryYear } : {},
396
+ ...r2.cardholderName != null ? { cardholderName: r2.cardholderName } : {}
397
+ };
398
+ case "sepa-direct-debit":
399
+ return {
400
+ type: "sepa_direct_debit",
401
+ consumerName: r2.consumerName ?? "",
402
+ consumerBic: r2.consumerBic ?? null,
403
+ consumerAccount: r2.maskedConsumerAccount ?? ""
404
+ };
405
+ case "blik":
406
+ return { type: "blik" };
407
+ case "mbway":
408
+ return { type: "mbway", ...r2.phone != null ? { phone: r2.phone } : {} };
409
+ case "bacs":
410
+ return { type: "bacs", maskedNumber: r2.maskedNumber ?? "" };
411
+ case "manual":
412
+ return { type: "manual" };
413
+ default:
414
+ return null;
415
+ }
416
+ }
280
417
  function createMockCustomerAdapter() {
281
- const customer = { id: "customer-1", name: "Customer 1" };
418
+ let customer = { id: "customer-1", name: "Customer 1", attributes: {} };
282
419
  return {
283
420
  async getCurrent() {
284
421
  return { _tag: "Success", data: customer };
422
+ },
423
+ async updateAttributes(attributes) {
424
+ await new Promise((resolve) => setTimeout(resolve, 300));
425
+ customer = { ...customer, attributes: { ...attributes } };
426
+ return { _tag: "Success", data: customer };
427
+ },
428
+ async createPaymentMethod(params) {
429
+ await new Promise((resolve) => setTimeout(resolve, 300));
430
+ if (params.provider === "shopify") {
431
+ return {
432
+ _tag: "Success",
433
+ data: {
434
+ kind: "email_sent",
435
+ paymentMethodId: "pm_mock_1",
436
+ instrument: { type: "credit_card", brand: "visa", maskedNumber: "•••• 4242" }
437
+ }
438
+ };
439
+ }
440
+ if (params.provider === "mollie") {
441
+ return {
442
+ _tag: "Success",
443
+ data: { kind: "redirect", redirectUrl: params.returnUrl }
444
+ };
445
+ }
446
+ return { _tag: "Failure", error: new Error(`Payment provider "${params.provider}" is not supported`) };
285
447
  }
286
448
  };
287
449
  }
450
+ function fromV1Customer(body) {
451
+ return {
452
+ id: body.id,
453
+ name: body.displayName,
454
+ email: body.email ?? void 0,
455
+ attributes: Object.fromEntries(
456
+ body.customAttributes.map(({ key, value }) => [key, value])
457
+ )
458
+ };
459
+ }
288
460
  function createApiCustomerAdapter(fetcher) {
289
461
  return {
290
462
  async getCurrent() {
291
463
  try {
292
- const res = await fetcher("/customer/info");
464
+ const res = await fetcher("/customer/v1/customer");
293
465
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
294
- const data = await res.json();
295
- const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
296
466
  return {
297
467
  _tag: "Success",
298
- data: { id: data.id, name, email: data.email }
468
+ data: fromV1Customer(await res.json())
299
469
  };
300
470
  } catch (e) {
301
471
  return { _tag: "Failure", error: e };
302
472
  }
473
+ },
474
+ async updateAttributes(attributes) {
475
+ try {
476
+ const res = await fetcher("/customer/v1/customer", {
477
+ method: "PATCH",
478
+ headers: { "Content-Type": "application/json" },
479
+ body: JSON.stringify({ customAttributes: attributes })
480
+ });
481
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
482
+ return {
483
+ _tag: "Success",
484
+ data: fromV1Customer(await res.json())
485
+ };
486
+ } catch (e) {
487
+ return { _tag: "Failure", error: e };
488
+ }
489
+ },
490
+ async createPaymentMethod(params) {
491
+ try {
492
+ if (params.provider === "shopify") {
493
+ const res = await fetcher("/customer/v1/payment-methods", {
494
+ method: "POST"
495
+ });
496
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
497
+ const body = await res.json();
498
+ return {
499
+ _tag: "Success",
500
+ data: {
501
+ kind: "email_sent",
502
+ paymentMethodId: body.id,
503
+ instrument: serializeV1PaymentInstrument(body.instrument) ?? void 0
504
+ }
505
+ };
506
+ }
507
+ if (params.provider === "mollie") {
508
+ const res = await fetcher("/customer/payment-methods", {
509
+ method: "POST",
510
+ headers: { "Content-Type": "application/json" },
511
+ body: JSON.stringify({ redirectUrl: params.returnUrl })
512
+ });
513
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
514
+ const body = await res.json();
515
+ return {
516
+ _tag: "Success",
517
+ data: { kind: "redirect", redirectUrl: body.redirectUrl }
518
+ };
519
+ }
520
+ return { _tag: "Failure", error: new Error(`Payment provider "${params.provider}" is not supported`) };
521
+ } catch (e) {
522
+ return { _tag: "Failure", error: e };
523
+ }
303
524
  }
304
525
  };
305
526
  }
@@ -319,6 +540,33 @@ function uuid() {
319
540
  (c3) => (c3 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c3 / 4).toString(16)
320
541
  );
321
542
  }
543
+ const REFRESH_LOCK_NAME = "juo:auth:refresh";
544
+ const LOCK_WAIT_TIMEOUT_MS = 1e4;
545
+ let fallbackQueue = Promise.resolve();
546
+ function runQueued(run) {
547
+ const result = fallbackQueue.then(run, run);
548
+ fallbackQueue = result.catch(() => void 0);
549
+ return result;
550
+ }
551
+ function withRefreshLock(run) {
552
+ const locks = typeof navigator === "undefined" ? void 0 : navigator.locks;
553
+ if (locks == null) {
554
+ return runQueued(run);
555
+ }
556
+ const controller = new AbortController();
557
+ const timer = setTimeout(() => controller.abort(), LOCK_WAIT_TIMEOUT_MS);
558
+ let granted = false;
559
+ return locks.request(REFRESH_LOCK_NAME, { signal: controller.signal }, () => {
560
+ granted = true;
561
+ clearTimeout(timer);
562
+ return run();
563
+ }).catch((error) => {
564
+ if (!granted && controller.signal.aborted) {
565
+ return run();
566
+ }
567
+ throw error;
568
+ }).finally(() => clearTimeout(timer));
569
+ }
322
570
  function createMockLoginAdapter(opts = {}) {
323
571
  const appUrl = opts.appUrl ?? "https://admin.juo.io";
324
572
  return {
@@ -418,6 +666,12 @@ function createApiLoginAdapter(fetcher, opts) {
418
666
  });
419
667
  }
420
668
  }
669
+ if (res.status === 401) {
670
+ const data = await res.json().catch(() => null);
671
+ if (data != null && typeof data === "object" && "message" in data && data.message === "Customer not found") {
672
+ throw new AuthenticationError(AuthenticationErrorReason.Missing);
673
+ }
674
+ }
421
675
  if (!res.ok) {
422
676
  throw new Error(`Unexpected response: ${res.statusText}`);
423
677
  }
@@ -457,20 +711,61 @@ class AuthenticationError extends Error {
457
711
  const STORAGE_DELEGATED_TOKEN = "delegated-token";
458
712
  const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
459
713
  const STORAGE_STATE_KEY = "state";
460
- function storage(key, value) {
461
- const storageKey = `auth:${key}`;
714
+ function webStorage(kind) {
462
715
  try {
463
- if (value === null) {
464
- sessionStorage.removeItem(storageKey);
465
- } else if (value !== void 0) {
466
- sessionStorage.setItem(storageKey, value);
467
- } else {
468
- return sessionStorage.getItem("auth:" + key);
716
+ return (kind === "local" ? localStorage : sessionStorage) ?? null;
717
+ } catch (error) {
718
+ console.warn(`Failed to reach ${kind} storage: ${error}`);
719
+ return null;
720
+ }
721
+ }
722
+ function readFrom(store, key) {
723
+ try {
724
+ return (store == null ? void 0 : store.getItem(key)) ?? null;
725
+ } catch (error) {
726
+ console.warn(`Failed to read web storage: ${error}`);
727
+ return null;
728
+ }
729
+ }
730
+ function writeTo(store, key, value) {
731
+ try {
732
+ if (store != null && store.getItem(key) !== value) {
733
+ store.setItem(key, value);
469
734
  }
470
735
  } catch (error) {
471
- console.warn(`Failed to use session storage: ${error}`);
736
+ console.warn(`Failed to write web storage: ${error}`);
737
+ }
738
+ }
739
+ function removeFrom(store, key) {
740
+ try {
741
+ store == null ? void 0 : store.removeItem(key);
742
+ } catch (error) {
743
+ console.warn(`Failed to clear web storage: ${error}`);
472
744
  }
473
- return null;
745
+ }
746
+ function storage(key, value) {
747
+ const storageKey = `auth:${key}`;
748
+ const isRefreshToken = key === STORAGE_REFRESH_TOKEN_KEY;
749
+ const store = webStorage(isRefreshToken ? "local" : "session");
750
+ const legacyStore = isRefreshToken ? webStorage("session") : null;
751
+ if (value === null) {
752
+ removeFrom(store, storageKey);
753
+ removeFrom(legacyStore, storageKey);
754
+ return null;
755
+ }
756
+ if (value !== void 0) {
757
+ writeTo(store, storageKey, value);
758
+ return null;
759
+ }
760
+ const stored = readFrom(store, storageKey);
761
+ if (stored !== null || legacyStore == null) {
762
+ return stored;
763
+ }
764
+ const legacy = readFrom(legacyStore, storageKey);
765
+ if (legacy !== null) {
766
+ writeTo(store, storageKey, legacy);
767
+ }
768
+ return legacy;
474
769
  }
475
770
  function nonce(len = 10) {
476
771
  return uuid().replace(/-/g, "").slice(0, len);
@@ -510,25 +805,75 @@ function createLoginService(init) {
510
805
  E(() => {
511
806
  storage(STORAGE_STATE_KEY, state.value);
512
807
  });
808
+ if (typeof window !== "undefined") {
809
+ window.addEventListener("storage", (event) => {
810
+ if (event.storageArea !== webStorage("local") || event.key !== `auth:${STORAGE_REFRESH_TOKEN_KEY}`) {
811
+ return;
812
+ }
813
+ if (event.newValue == null) {
814
+ accessToken.value = null;
815
+ expires.value = null;
816
+ refreshToken.value = null;
817
+ return;
818
+ }
819
+ if (event.newValue !== refreshToken.value) {
820
+ refreshToken.value = event.newValue;
821
+ }
822
+ });
823
+ }
513
824
  function storeTokens(data) {
514
825
  const { access_token, refresh_token, expires_in } = data;
515
826
  accessToken.value = access_token;
516
- refreshToken.value = refresh_token;
517
- expires.value = new Date(Date.now() + expires_in);
827
+ if (typeof refresh_token === "string" && refresh_token !== "") {
828
+ refreshToken.value = refresh_token;
829
+ }
830
+ const lifetime = Number.isFinite(expires_in) ? expires_in : 300;
831
+ const margin = Math.min(60, lifetime / 2);
832
+ expires.value = new Date(Date.now() + (lifetime - margin) * 1e3);
833
+ }
834
+ async function rotate(token) {
835
+ const result = await adapter.authenticate({ refreshToken: token });
836
+ if (result._tag === "Failure") {
837
+ throw result.error;
838
+ }
839
+ if (refreshToken.value === token) {
840
+ storeTokens(result.data);
841
+ } else {
842
+ accessToken.value = null;
843
+ expires.value = null;
844
+ }
845
+ return result.data.access_token;
846
+ }
847
+ async function refreshLocked() {
848
+ const stored = storage(STORAGE_REFRESH_TOKEN_KEY);
849
+ if (stored != null && stored !== refreshToken.value) {
850
+ refreshToken.value = stored;
851
+ }
852
+ const current = refreshToken.value;
853
+ if (current == null) {
854
+ throw new AuthenticationError(
855
+ "expired"
856
+ /* Expired */
857
+ );
858
+ }
859
+ try {
860
+ return await rotate(current);
861
+ } catch (error) {
862
+ const latest = storage(STORAGE_REFRESH_TOKEN_KEY);
863
+ if (latest != null && latest !== current) {
864
+ refreshToken.value = latest;
865
+ return await rotate(latest);
866
+ }
867
+ throw error;
868
+ }
518
869
  }
519
870
  async function getToken() {
520
871
  if (refreshToken.value == null) return null;
521
872
  if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
522
- accessToken.value = new Promise(
523
- (resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
524
- if (result._tag === "Failure") {
525
- reject(result.error);
526
- return;
527
- }
528
- storeTokens(result.data);
529
- resolve(result.data.access_token);
530
- }).catch(reject)
531
- );
873
+ accessToken.value = withRefreshLock(refreshLocked).catch((error) => {
874
+ accessToken.value = null;
875
+ throw error;
876
+ });
532
877
  }
533
878
  return Promise.resolve(accessToken.value);
534
879
  }
@@ -691,7 +1036,10 @@ function createMockOrderAdapter() {
691
1036
  currencyCode: "USD",
692
1037
  totalPrice: 89.97,
693
1038
  financialStatus: "PAID",
1039
+ paymentStatus: "paid",
1040
+ paidAt: "2024-10-15T09:31:00Z",
694
1041
  fulfillmentStatus: "FULFILLED",
1042
+ subscriptionIds: ["subscription_1"],
695
1043
  lines: [
696
1044
  {
697
1045
  id: "line_1",
@@ -716,7 +1064,10 @@ function createMockOrderAdapter() {
716
1064
  currencyCode: "USD",
717
1065
  totalPrice: 45.99,
718
1066
  financialStatus: "PAID",
1067
+ paymentStatus: "paid",
1068
+ paidAt: "2024-09-20T14:16:00Z",
719
1069
  fulfillmentStatus: "PARTIALLY_FULFILLED",
1070
+ subscriptionIds: [],
720
1071
  lines: [
721
1072
  {
722
1073
  id: "line_2",
@@ -761,11 +1112,28 @@ function mapOrder(order) {
761
1112
  currencyCode: order.currencyCode,
762
1113
  totalPrice: order.totalPrice,
763
1114
  financialStatus: order.financialStatus,
1115
+ // Defaulted rather than required so an older backend (which returns neither
1116
+ // field) degrades to "settled, time unknown" instead of producing an Order
1117
+ // with holes in it. Matches the endpoint's own fallback for an order it
1118
+ // never tracked.
1119
+ paymentStatus: order.paymentStatus ?? "paid",
1120
+ paidAt: order.paidAt ?? null,
764
1121
  fulfillmentStatus: order.fulfillmentStatus,
765
1122
  lines: order.items.map(mapOrderLine),
766
- customAttributes: order.customAttributes
1123
+ customAttributes: order.customAttributes,
1124
+ subscriptionIds: order.subscriptionIds ?? []
767
1125
  };
768
1126
  }
1127
+ function quoteSearchValue(value) {
1128
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1129
+ }
1130
+ function buildOrdersQuery(params) {
1131
+ const callerQuery = (params == null ? void 0 : params.query) != null && params.query.trim() !== "" ? params.query : null;
1132
+ const subscriptionTerm = (params == null ? void 0 : params.subscriptionId) != null ? `subscriptionId:${quoteSearchValue(params.subscriptionId)}` : null;
1133
+ if (callerQuery == null) return subscriptionTerm;
1134
+ if (subscriptionTerm == null) return callerQuery;
1135
+ return `(${callerQuery}) AND ${subscriptionTerm}`;
1136
+ }
769
1137
  function createApiOrderAdapter(fetcher) {
770
1138
  return {
771
1139
  async getOrders(params) {
@@ -773,9 +1141,10 @@ function createApiOrderAdapter(fetcher) {
773
1141
  if ((params == null ? void 0 : params.limit) != null) query.set("limit", String(params.limit));
774
1142
  if ((params == null ? void 0 : params.after) != null) query.set("after", params.after);
775
1143
  if ((params == null ? void 0 : params.before) != null) query.set("before", params.before);
776
- if ((params == null ? void 0 : params.query) != null) query.set("query", params.query);
777
1144
  if ((params == null ? void 0 : params.includeOneTime) != null) query.set("includeOneTime", String(params.includeOneTime));
778
1145
  if ((params == null ? void 0 : params.sort) != null) query.set("sort", params.sort);
1146
+ const searchQuery = buildOrdersQuery(params);
1147
+ if (searchQuery != null) query.set("query", searchQuery);
779
1148
  const qs = query.toString();
780
1149
  const res = await fetcher(`/customer/v1/orders${qs ? `?${qs}` : ""}`);
781
1150
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
@@ -1064,6 +1433,13 @@ function mapVariant(variant, currencyCode) {
1064
1433
  } : {}
1065
1434
  };
1066
1435
  }
1436
+ const IDENTIFIER = String.raw`[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*`;
1437
+ const VALUE = String.raw`(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|${IDENTIFIER})`;
1438
+ const TERM = String.raw`(?:NOT\s+|-)?${IDENTIFIER}:(?:>=|<=|>|<)?${VALUE}`;
1439
+ const FILTER_QUERY = new RegExp(String.raw`^${TERM}(?:\s+(?:AND|OR)\s+${TERM})*$`);
1440
+ function isFilterQuery(query) {
1441
+ return FILTER_QUERY.test(query);
1442
+ }
1067
1443
  function mapProduct(product, currencyCode) {
1068
1444
  return {
1069
1445
  id: product.id,
@@ -1116,7 +1492,11 @@ function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
1116
1492
  try {
1117
1493
  const params = new URLSearchParams();
1118
1494
  if (opts2 == null ? void 0 : opts2.query) {
1119
- params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
1495
+ const trimmed = opts2.query.trim();
1496
+ params.set(
1497
+ "query",
1498
+ isFilterQuery(trimmed) ? trimmed : `"${trimmed.replace(/"/g, '\\"')}"`
1499
+ );
1120
1500
  }
1121
1501
  if (opts2 == null ? void 0 : opts2.pagination) {
1122
1502
  if ("after" in opts2.pagination)
@@ -1310,6 +1690,49 @@ const DISCOUNT_REJECTION_REASONS = [
1310
1690
  function toAttributeArray(next) {
1311
1691
  return Object.entries(next).map(([key, value]) => ({ key, value }));
1312
1692
  }
1693
+ const MOCK_SHIPPING_METHODS = [
1694
+ {
1695
+ handle: "standard-shipping",
1696
+ title: "Standard shipping",
1697
+ price: { amount: "0.00", currencyCode: "EUR" }
1698
+ },
1699
+ {
1700
+ handle: "dhl-express",
1701
+ title: "DHL Express",
1702
+ price: { amount: "9.99", currencyCode: "EUR" }
1703
+ },
1704
+ {
1705
+ handle: "next-day",
1706
+ title: "Next day delivery",
1707
+ price: { amount: "14.99", currencyCode: "EUR" }
1708
+ }
1709
+ ];
1710
+ function toShippingAddressBody(address) {
1711
+ return {
1712
+ firstName: address.firstName,
1713
+ lastName: address.lastName,
1714
+ address1: address.address1,
1715
+ zip: address.zip,
1716
+ city: address.city,
1717
+ ...address.address2 ? { address2: address.address2 } : {},
1718
+ ...address.province ? { province: address.province } : {},
1719
+ ...address.phone ? { phone: address.phone } : {},
1720
+ ...address.company ? { company: address.company } : {}
1721
+ };
1722
+ }
1723
+ function isStaleSubscriptionMessage(message) {
1724
+ return message ? message.toLowerCase().includes("changed since last fetch") : false;
1725
+ }
1726
+ function toNonEmptyAttributeArray(next) {
1727
+ if (next == null) return [];
1728
+ return toAttributeArray(next).filter(
1729
+ ({ key, value }) => key !== "" && value !== ""
1730
+ );
1731
+ }
1732
+ const MOCK_INSTANT_ORDER_MIN_ITEMS = 1;
1733
+ const MOCK_INSTANT_ORDER_MAX_ITEMS = 20;
1734
+ const MOCK_INSTANT_ORDER_MIN_QUANTITY = 1;
1735
+ const MOCK_INSTANT_ORDER_MAX_QUANTITY = 10;
1313
1736
  function createMockSubscriptionAdapter(options = {}) {
1314
1737
  const richBase = (id, itemId, renewalId) => ({
1315
1738
  id,
@@ -1373,7 +1796,24 @@ function createMockSubscriptionAdapter(options = {}) {
1373
1796
  renewal: { id: renewalId, isInActiveProcessState: false },
1374
1797
  lastPaymentStatus: "succeeded",
1375
1798
  pausePeriod: null,
1376
- isEditable: true
1799
+ isEditable: true,
1800
+ // Shaped like the v1 mapping produces it: one option slot filled, no
1801
+ // presentmentTitle (v1 has no such field). Previews would otherwise render
1802
+ // ContractSelect's address / method rows as "-".
1803
+ deliveryMethod: {
1804
+ address: {
1805
+ firstName: "Ada",
1806
+ lastName: "Lovelace",
1807
+ address1: "Wilcza 12/4",
1808
+ zip: "00-538",
1809
+ city: "Warsaw",
1810
+ country: "Poland",
1811
+ countryCode: "PL"
1812
+ },
1813
+ shippingOption: { title: "Standard shipping" },
1814
+ pickupOption: null,
1815
+ localDeliveryOption: null
1816
+ }
1377
1817
  });
1378
1818
  const mockSubscriptions = [
1379
1819
  {
@@ -1442,6 +1882,20 @@ function createMockSubscriptionAdapter(options = {}) {
1442
1882
  lastPaymentStatus: "succeeded",
1443
1883
  pausePeriod: null,
1444
1884
  isEditable: true,
1885
+ deliveryMethod: {
1886
+ address: {
1887
+ firstName: "Ada",
1888
+ lastName: "Lovelace",
1889
+ address1: "Wilcza 12/4",
1890
+ zip: "00-538",
1891
+ city: "Warsaw",
1892
+ country: "Poland",
1893
+ countryCode: "PL"
1894
+ },
1895
+ shippingOption: { title: "DHL Express" },
1896
+ pickupOption: null,
1897
+ localDeliveryOption: null
1898
+ },
1445
1899
  customAttributes: [
1446
1900
  { key: "subscription_tier", value: "premium" },
1447
1901
  { key: "gift_message", value: "Enjoy your skincare routine!" }
@@ -1473,6 +1927,14 @@ function createMockSubscriptionAdapter(options = {}) {
1473
1927
  frequency: "2 WEEK",
1474
1928
  createdAt: "2024-09-15T00:00:00Z",
1475
1929
  updatedAt: "2024-10-10T00:00:00Z",
1930
+ // Pickup, with no address — the other branch of the option coalescing in
1931
+ // ContractSelect's delivery-method label.
1932
+ deliveryMethod: {
1933
+ address: null,
1934
+ shippingOption: null,
1935
+ pickupOption: { title: "Pickup in store" },
1936
+ localDeliveryOption: null
1937
+ },
1476
1938
  customAttributes: []
1477
1939
  },
1478
1940
  // Per-status fixtures: groundwork for the deferred editor preview picker
@@ -1481,18 +1943,55 @@ function createMockSubscriptionAdapter(options = {}) {
1481
1943
  {
1482
1944
  ...richBase("sub_paused", "item_paused", "ren_paused"),
1483
1945
  status: "paused",
1484
- pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" }
1946
+ pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" },
1947
+ // BLIK with a non-recurring alias — the shape that gates the
1948
+ // re-authorization prompt in RenewalSummary. Only the legacy-sourced
1949
+ // payment method can express this, so previews would otherwise never
1950
+ // exercise the branch.
1951
+ customerPaymentMethod: {
1952
+ id: "pm_blik",
1953
+ instrument: { type: "blik", label: "my-blik-alias" },
1954
+ active: true,
1955
+ isRecurring: false
1956
+ }
1485
1957
  },
1486
1958
  {
1487
1959
  ...richBase("sub_canceled", "item_canceled", "ren_canceled"),
1488
1960
  status: "canceled",
1489
- pausePeriod: null
1961
+ pausePeriod: null,
1962
+ // SEPA — identified by a masked IBAN rather than card digits.
1963
+ customerPaymentMethod: {
1964
+ id: "pm_sepa",
1965
+ instrument: {
1966
+ type: "sepa_direct_debit",
1967
+ consumerName: "Ada Lovelace",
1968
+ consumerBic: "ABCDEFGH",
1969
+ consumerAccount: "DE12****6789"
1970
+ },
1971
+ active: true,
1972
+ isRecurring: true
1973
+ }
1490
1974
  },
1491
1975
  {
1492
1976
  ...richBase("sub_failed", "item_failed", "ren_failed"),
1493
1977
  status: "failed",
1494
1978
  lastPaymentStatus: "failed",
1495
- pausePeriod: null
1979
+ pausePeriod: null,
1980
+ // An expired card — pairs with the failed payment, and exercises
1981
+ // getPaymentInstrumentExpiry's isExpired branch.
1982
+ customerPaymentMethod: {
1983
+ id: "pm_expired_card",
1984
+ instrument: {
1985
+ type: "credit_card",
1986
+ brand: "mastercard",
1987
+ maskedNumber: "************5100",
1988
+ expiryMonth: 1,
1989
+ expiryYear: 2024,
1990
+ cardholderName: "Ada Lovelace"
1991
+ },
1992
+ active: true,
1993
+ isRecurring: true
1994
+ }
1496
1995
  },
1497
1996
  {
1498
1997
  ...richBase("sub_expired", "item_expired", "ren_expired"),
@@ -1518,6 +2017,60 @@ function createMockSubscriptionAdapter(options = {}) {
1518
2017
  description: "Lightweight daily sun protection"
1519
2018
  }
1520
2019
  ];
2020
+ const mockInstantOrderImages = {
2021
+ "io-product-1": "https://images.unsplash.com/photo-1447933601403-0c6688de566e?w=200",
2022
+ "io-product-2": "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=200"
2023
+ };
2024
+ const mockInstantOrderProducts = [
2025
+ {
2026
+ id: "io-product-1",
2027
+ title: "Single-Origin Espresso",
2028
+ hasOnlyDefaultVariant: false,
2029
+ featuredImage: {
2030
+ url: mockInstantOrderImages["io-product-1"],
2031
+ altText: "Single-Origin Espresso"
2032
+ },
2033
+ variants: {
2034
+ items: [
2035
+ {
2036
+ id: "io-variant-1",
2037
+ title: "250 g",
2038
+ image: { url: mockInstantOrderImages["io-product-1"] },
2039
+ price: { amount: "12.00", currencyCode: "USD" },
2040
+ availableForSale: true
2041
+ },
2042
+ {
2043
+ id: "io-variant-2",
2044
+ title: "1 kg",
2045
+ image: { url: mockInstantOrderImages["io-product-1"] },
2046
+ price: { amount: "38.00", currencyCode: "USD" },
2047
+ availableForSale: true
2048
+ }
2049
+ ]
2050
+ }
2051
+ },
2052
+ {
2053
+ id: "io-product-2",
2054
+ title: "Ceramic Pour-Over Dripper",
2055
+ hasOnlyDefaultVariant: true,
2056
+ featuredImage: {
2057
+ url: mockInstantOrderImages["io-product-2"],
2058
+ altText: "Ceramic Pour-Over Dripper"
2059
+ },
2060
+ variants: {
2061
+ items: [
2062
+ {
2063
+ id: "io-variant-3",
2064
+ title: "Default Title",
2065
+ image: { url: mockInstantOrderImages["io-product-2"] },
2066
+ price: { amount: "24.50", currencyCode: "USD" },
2067
+ availableForSale: true
2068
+ }
2069
+ ]
2070
+ }
2071
+ }
2072
+ ];
2073
+ let placedInstantOrders = 0;
1521
2074
  return {
1522
2075
  async getById(id) {
1523
2076
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1588,8 +2141,11 @@ function createMockSubscriptionAdapter(options = {}) {
1588
2141
  ...variant && product && !product.hasOnlyDefaultVariant ? { variantTitle: variant.title } : {},
1589
2142
  ...(variant == null ? void 0 : variant.image) ? { variantImage: variant.image } : {},
1590
2143
  variantAvailableForSale: true,
1591
- billingPolicy: ((_f = subscription.items[0]) == null ? void 0 : _f.billingPolicy) ?? subscription.billingPolicy ?? null,
1592
- deliveryPolicy: ((_g = subscription.items[0]) == null ? void 0 : _g.deliveryPolicy) ?? subscription.deliveryPolicy ?? null
2144
+ // An explicit per-line billingPolicy wins; otherwise inherit the plan's.
2145
+ billingPolicy: item.billingPolicy ?? ((_f = subscription.items[0]) == null ? void 0 : _f.billingPolicy) ?? subscription.billingPolicy ?? null,
2146
+ deliveryPolicy: ((_g = subscription.items[0]) == null ? void 0 : _g.deliveryPolicy) ?? subscription.deliveryPolicy ?? null,
2147
+ ...item.recurringCycleLimit != null ? { recurringCycleLimit: item.recurringCycleLimit } : {},
2148
+ ...item.customAttributes != null ? { customAttributes: toAttributeArray(item.customAttributes) } : {}
1593
2149
  });
1594
2150
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1595
2151
  return { _tag: "Success", data: void 0 };
@@ -1617,6 +2173,9 @@ function createMockSubscriptionAdapter(options = {}) {
1617
2173
  if (update.variantId !== void 0) item.variantId = update.variantId;
1618
2174
  if (update.quantity !== void 0) item.quantity = update.quantity;
1619
2175
  if (update.billingPolicy !== void 0) item.billingPolicy = update.billingPolicy;
2176
+ if (update.customAttributes !== void 0) {
2177
+ item.customAttributes = toAttributeArray(update.customAttributes);
2178
+ }
1620
2179
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1621
2180
  return { _tag: "Success", data: void 0 };
1622
2181
  },
@@ -1703,6 +2262,92 @@ function createMockSubscriptionAdapter(options = {}) {
1703
2262
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1704
2263
  return { _tag: "Success", data: { ...subscription } };
1705
2264
  },
2265
+ async updateDeliveryAddress({
2266
+ subscriptionId,
2267
+ address
2268
+ }) {
2269
+ var _a;
2270
+ await new Promise((resolve) => setTimeout(resolve, 300));
2271
+ const subscription = mockSubscriptions.find(
2272
+ (sub) => sub.id === subscriptionId
2273
+ );
2274
+ if (!subscription) {
2275
+ return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
2276
+ }
2277
+ const previous = (_a = subscription.deliveryMethod) == null ? void 0 : _a.address;
2278
+ subscription.deliveryMethod = {
2279
+ ...subscription.deliveryMethod,
2280
+ address: {
2281
+ firstName: address.firstName,
2282
+ lastName: address.lastName,
2283
+ address1: address.address1,
2284
+ zip: address.zip,
2285
+ city: address.city,
2286
+ ...address.address2 ? { address2: address.address2 } : {},
2287
+ ...address.province ? { province: address.province } : {},
2288
+ ...address.phone ? { phone: address.phone } : {},
2289
+ ...address.company ? { company: address.company } : {},
2290
+ ...(previous == null ? void 0 : previous.country) != null ? { country: previous.country } : {},
2291
+ ...(previous == null ? void 0 : previous.countryCode) != null ? { countryCode: previous.countryCode } : {}
2292
+ }
2293
+ };
2294
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2295
+ return { _tag: "Success", data: void 0 };
2296
+ },
2297
+ async getAvailableShippingMethods({
2298
+ subscriptionId
2299
+ }) {
2300
+ var _a, _b, _c;
2301
+ await new Promise((resolve) => setTimeout(resolve, 300));
2302
+ const subscription = mockSubscriptions.find(
2303
+ (sub) => sub.id === subscriptionId
2304
+ );
2305
+ if (!subscription) {
2306
+ return { _tag: "Failure", error: "Subscription not found" };
2307
+ }
2308
+ const currentTitle = (_b = (_a = subscription.deliveryMethod) == null ? void 0 : _a.shippingOption) == null ? void 0 : _b.title;
2309
+ const currencyCode = ((_c = subscription.items[0]) == null ? void 0 : _c.price.currencyCode) ?? "EUR";
2310
+ return {
2311
+ _tag: "Success",
2312
+ data: MOCK_SHIPPING_METHODS.map((method) => ({
2313
+ ...method,
2314
+ price: { ...method.price, currencyCode },
2315
+ isActive: method.title === currentTitle
2316
+ }))
2317
+ };
2318
+ },
2319
+ async updateDeliveryMethod({
2320
+ subscriptionId,
2321
+ shippingMethodHandle
2322
+ }) {
2323
+ await new Promise((resolve) => setTimeout(resolve, 300));
2324
+ const subscription = mockSubscriptions.find(
2325
+ (sub) => sub.id === subscriptionId
2326
+ );
2327
+ if (!subscription) {
2328
+ return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
2329
+ }
2330
+ const method = MOCK_SHIPPING_METHODS.find(
2331
+ (m2) => m2.handle === shippingMethodHandle
2332
+ );
2333
+ if (!method) {
2334
+ return {
2335
+ _tag: "Failure",
2336
+ error: {
2337
+ kind: "UNKNOWN",
2338
+ message: `Provided shipping method handle "${shippingMethodHandle}" is not available`
2339
+ }
2340
+ };
2341
+ }
2342
+ subscription.deliveryMethod = {
2343
+ ...subscription.deliveryMethod,
2344
+ shippingOption: { title: method.title },
2345
+ pickupOption: null,
2346
+ localDeliveryOption: null
2347
+ };
2348
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2349
+ return { _tag: "Success", data: void 0 };
2350
+ },
1706
2351
  async resume(params) {
1707
2352
  await new Promise((resolve) => setTimeout(resolve, 300));
1708
2353
  const subscription = mockSubscriptions.find(
@@ -1832,27 +2477,165 @@ function createMockSubscriptionAdapter(options = {}) {
1832
2477
  async addUpsellProduct() {
1833
2478
  await new Promise((resolve) => setTimeout(resolve, 400));
1834
2479
  return { _tag: "Success", data: void 0 };
2480
+ },
2481
+ async getInstantOrderProducts({
2482
+ query
2483
+ }) {
2484
+ await new Promise((resolve) => setTimeout(resolve, 300));
2485
+ const term = query == null ? void 0 : query.trim().toLowerCase();
2486
+ const products = term ? mockInstantOrderProducts.filter(
2487
+ (product) => product.title.toLowerCase().includes(term)
2488
+ ) : [...mockInstantOrderProducts];
2489
+ return {
2490
+ _tag: "Success",
2491
+ data: {
2492
+ // Deliberately independent of `query`, mirroring the endpoint: a search
2493
+ // that matches nothing must not read as "the feature is unavailable"
2494
+ // and make a consumer hide its entry point mid-interaction.
2495
+ available: mockInstantOrderProducts.length > 0,
2496
+ requiresCheckout: false,
2497
+ products,
2498
+ hasNextPage: false,
2499
+ endCursor: null
2500
+ }
2501
+ };
2502
+ },
2503
+ async placeInstantOrder({
2504
+ items,
2505
+ shippingMethodTitle
2506
+ }) {
2507
+ var _a;
2508
+ await new Promise((resolve) => setTimeout(resolve, 400));
2509
+ const malformed = items.length < MOCK_INSTANT_ORDER_MIN_ITEMS || items.length > MOCK_INSTANT_ORDER_MAX_ITEMS || items.some(
2510
+ (item) => !Number.isInteger(item.quantity) || item.quantity < MOCK_INSTANT_ORDER_MIN_QUANTITY || item.quantity > MOCK_INSTANT_ORDER_MAX_QUANTITY
2511
+ );
2512
+ if (malformed) {
2513
+ return {
2514
+ _tag: "Failure",
2515
+ error: {
2516
+ kind: "INVALID_REQUEST",
2517
+ message: "Malformed item list"
2518
+ }
2519
+ };
2520
+ }
2521
+ const variants = mockInstantOrderProducts.flatMap(
2522
+ (product) => product.variants.items
2523
+ );
2524
+ const unknown = items.find(
2525
+ (item) => !variants.some((variant) => variant.id === item.variantId)
2526
+ );
2527
+ if (unknown) {
2528
+ return {
2529
+ _tag: "Failure",
2530
+ error: {
2531
+ kind: "INVALID_REQUEST",
2532
+ message: `Unknown variant ${unknown.variantId}`
2533
+ }
2534
+ };
2535
+ }
2536
+ const rate = MOCK_SHIPPING_METHODS.find(
2537
+ (method) => method.title === shippingMethodTitle
2538
+ );
2539
+ if (shippingMethodTitle != null && rate == null) {
2540
+ return {
2541
+ _tag: "Failure",
2542
+ error: {
2543
+ kind: "INVALID_REQUEST",
2544
+ message: `Shipping method "${shippingMethodTitle}" is not available for this order`
2545
+ }
2546
+ };
2547
+ }
2548
+ const total = items.reduce(
2549
+ (sum, item) => {
2550
+ const variant = variants.find((v3) => v3.id === item.variantId);
2551
+ return sum + Number((variant == null ? void 0 : variant.price.amount) ?? 0) * item.quantity;
2552
+ },
2553
+ // Only when a rate was chosen: an instant order left on the
2554
+ // subscription's own delivery method has no separate delivery price the
2555
+ // mock catalog can know.
2556
+ Number((rate == null ? void 0 : rate.price.amount) ?? 0)
2557
+ );
2558
+ placedInstantOrders += 1;
2559
+ return {
2560
+ _tag: "Success",
2561
+ data: {
2562
+ orderId: `io-order-${placedInstantOrders}`,
2563
+ totalPrice: total.toFixed(2),
2564
+ currencyCode: ((_a = variants[0]) == null ? void 0 : _a.price.currencyCode) ?? "USD",
2565
+ status: "paid"
2566
+ }
2567
+ };
1835
2568
  }
1836
2569
  };
1837
2570
  }
1838
- function normalizeInstrumentType(type) {
1839
- return type.replace(/-/g, "_");
2571
+ function mapV1Instrument(raw) {
2572
+ return serializeV1PaymentInstrument(raw);
2573
+ }
2574
+ function mapV1BillingAddress(raw) {
2575
+ if (raw == null || typeof raw !== "object") return null;
2576
+ const address = {
2577
+ firstName: raw.firstName ?? null,
2578
+ lastName: raw.lastName ?? null,
2579
+ address1: raw.address1 ?? null,
2580
+ address2: raw.address2 ?? null,
2581
+ zip: raw.zip ?? null,
2582
+ city: raw.city ?? null,
2583
+ country: raw.country ?? null,
2584
+ countryCode: raw.countryCode ?? null,
2585
+ province: raw.province ?? null,
2586
+ provinceCode: raw.provinceCode ?? null,
2587
+ phone: raw.phone ?? null,
2588
+ company: raw.company ?? null
2589
+ };
2590
+ return Object.values(address).some((v3) => v3 != null) ? address : null;
2591
+ }
2592
+ function mapV1DeliveryAddress(raw) {
2593
+ if (raw == null || typeof raw !== "object") return null;
2594
+ const address = {
2595
+ ...raw.firstName != null ? { firstName: raw.firstName } : {},
2596
+ ...raw.lastName != null ? { lastName: raw.lastName } : {},
2597
+ ...raw.address1 != null ? { address1: raw.address1 } : {},
2598
+ ...raw.address2 != null ? { address2: raw.address2 } : {},
2599
+ ...raw.zip != null ? { zip: raw.zip } : {},
2600
+ ...raw.city != null ? { city: raw.city } : {},
2601
+ ...raw.country != null ? { country: raw.country } : {},
2602
+ ...raw.countryCode != null ? { countryCode: raw.countryCode } : {},
2603
+ ...raw.province != null ? { province: raw.province } : {},
2604
+ ...raw.provinceCode != null ? { provinceCode: raw.provinceCode } : {},
2605
+ ...raw.phone != null ? { phone: raw.phone } : {},
2606
+ ...raw.company != null ? { company: raw.company } : {}
2607
+ };
2608
+ return Object.keys(address).length > 0 ? address : null;
2609
+ }
2610
+ function mapV1DeliveryMethod(raw) {
2611
+ const address = mapV1DeliveryAddress(raw == null ? void 0 : raw.deliveryAddress);
2612
+ const method = raw == null ? void 0 : raw.deliveryMethod;
2613
+ if (address == null && (method == null || typeof method !== "object")) {
2614
+ return null;
2615
+ }
2616
+ const option = method != null && typeof method === "object" ? {
2617
+ ...method.title != null ? { title: method.title } : {},
2618
+ ...method.description != null ? { description: method.description } : {}
2619
+ } : null;
2620
+ return {
2621
+ address,
2622
+ shippingOption: (method == null ? void 0 : method.type) === "shipping" ? option : null,
2623
+ pickupOption: (method == null ? void 0 : method.type) === "pickup" ? option : null,
2624
+ localDeliveryOption: (method == null ? void 0 : method.type) === "local-delivery" ? option : null
2625
+ };
1840
2626
  }
1841
2627
  function mapPaymentMethod(rawPm) {
1842
2628
  if (!rawPm) return null;
1843
2629
  if (typeof rawPm === "string") {
1844
2630
  return { id: rawPm };
1845
2631
  }
2632
+ const instrument = mapV1Instrument(rawPm.instrument);
2633
+ const billingAddress = mapV1BillingAddress(rawPm.billingAddress);
1846
2634
  return {
1847
2635
  id: rawPm.id ?? "",
1848
- ...rawPm.instrument != null ? {
1849
- instrument: {
1850
- ...rawPm.instrument,
1851
- type: normalizeInstrumentType(rawPm.instrument.type)
1852
- }
1853
- } : {},
1854
- ...rawPm.active != null ? { active: rawPm.active } : {},
1855
- ...rawPm.isRecurring != null ? { isRecurring: rawPm.isRecurring } : {}
2636
+ ...instrument != null ? { instrument } : {},
2637
+ // v1 is the ONLY customer-facing source of a billing address.
2638
+ ...billingAddress != null ? { billingAddress } : {}
1856
2639
  };
1857
2640
  }
1858
2641
  function mapSubscription(raw) {
@@ -1906,6 +2689,8 @@ function mapSubscription(raw) {
1906
2689
  // Map customerPaymentMethod — expanded to an object (id + instrument) via the
1907
2690
  // adapter's ?expand=paymentMethod request; mapPaymentMethod tolerates a bare id too.
1908
2691
  customerPaymentMethod: mapPaymentMethod(raw.paymentMethod),
2692
+ // v1 returns deliveryAddress + deliveryMethod on every read, no expand needed.
2693
+ deliveryMethod: mapV1DeliveryMethod(raw),
1909
2694
  frequency,
1910
2695
  createdAt: raw.createdAt,
1911
2696
  updatedAt: raw.updatedAt,
@@ -1922,6 +2707,51 @@ async function readErrorBody(res) {
1922
2707
  return {};
1923
2708
  }
1924
2709
  }
2710
+ async function readErrorMessage(res) {
2711
+ const body = await readErrorBody(res);
2712
+ return typeof body.message === "string" ? body.message : void 0;
2713
+ }
2714
+ const PLACE_INSTANT_ORDER_ERROR_KINDS = {
2715
+ 400: "INVALID_REQUEST",
2716
+ 403: "REFUSED",
2717
+ 409: "DUPLICATE_REQUEST",
2718
+ 429: "RATE_LIMITED"
2719
+ };
2720
+ function mapInstantOrderProducts(body) {
2721
+ const currencyCode = body.currencyCode;
2722
+ const products = (body.products ?? []).map(
2723
+ (product) => {
2724
+ const image = product.featuredImage ? {
2725
+ url: product.featuredImage.url,
2726
+ ...product.featuredImage.altText != null ? { altText: product.featuredImage.altText } : {}
2727
+ } : null;
2728
+ return {
2729
+ id: product.productId,
2730
+ title: product.title,
2731
+ // Shopify's own flag, not a count of the variants this response happens
2732
+ // to carry — the two differ whenever some are filtered.
2733
+ hasOnlyDefaultVariant: product.hasOnlyDefaultVariant,
2734
+ featuredImage: image,
2735
+ variants: {
2736
+ items: (product.variants ?? []).map((variant) => ({
2737
+ id: variant.id,
2738
+ title: variant.title,
2739
+ image,
2740
+ price: { amount: variant.price, currencyCode },
2741
+ availableForSale: variant.availableForSale
2742
+ }))
2743
+ }
2744
+ };
2745
+ }
2746
+ );
2747
+ return {
2748
+ available: body.available,
2749
+ requiresCheckout: body.requiresCheckout,
2750
+ products,
2751
+ hasNextPage: body.hasNextPage,
2752
+ endCursor: body.endCursor ?? null
2753
+ };
2754
+ }
1925
2755
  function createApiSubscriptionAdapter(fetcher) {
1926
2756
  const adapter = {
1927
2757
  async search(options) {
@@ -1977,14 +2807,33 @@ function createApiSubscriptionAdapter(fetcher) {
1977
2807
  return { _tag: "Failure", error: e };
1978
2808
  }
1979
2809
  },
2810
+ // Uses the legacy POST /customer/subscriptions/{id}/lines rather than
2811
+ // POST /customer/v1/subscriptions/{id}/items: only the legacy endpoint
2812
+ // accepts a per-line `billingPolicy`. It answers 204 with no body.
2813
+ // Behavioural differences vs the v1 route, both intentional here:
2814
+ // - it rejects membership subscriptions with 403 (v1 does not),
2815
+ // - it skips the availableForSale check (v1 enforces it), so callers are
2816
+ // responsible for only offering purchasable variants — which the
2817
+ // product picker already does.
1980
2818
  async addItem({ subscriptionId, item }) {
1981
2819
  try {
2820
+ const attributes = toNonEmptyAttributeArray(item.customAttributes);
2821
+ const body = {
2822
+ productVariantId: item.variantId,
2823
+ quantity: item.quantity,
2824
+ // `!= null` on purpose: null means "inherit"/"no limit", and the
2825
+ // endpoint rejects an explicit null on either field.
2826
+ ...item.billingPolicy != null ? { billingPolicy: item.billingPolicy } : {},
2827
+ ...item.recurringCycleLimit != null ? { renewalLimit: item.recurringCycleLimit } : {},
2828
+ ...item.shippingMethodHandle != null ? { shippingMethodHandle: item.shippingMethodHandle } : {},
2829
+ ...attributes.length > 0 ? { customAttributes: attributes } : {}
2830
+ };
1982
2831
  const res = await fetcher(
1983
- `/customer/v1/subscriptions/${subscriptionId}/items`,
2832
+ `/customer/subscriptions/${subscriptionId}/lines`,
1984
2833
  {
1985
2834
  method: "POST",
1986
2835
  headers: { "Content-Type": "application/json" },
1987
- body: JSON.stringify({ variant: item.variantId, quantity: item.quantity })
2836
+ body: JSON.stringify(body)
1988
2837
  }
1989
2838
  );
1990
2839
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
@@ -2011,7 +2860,11 @@ function createApiSubscriptionAdapter(fetcher) {
2011
2860
  ...update.variantId !== void 0 ? { productVariantId: update.variantId } : {},
2012
2861
  ...update.quantity !== void 0 ? { quantity: update.quantity } : {},
2013
2862
  ...update.billingPolicy !== void 0 ? { billingPolicy: update.billingPolicy } : {},
2014
- ...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {}
2863
+ ...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {},
2864
+ // Record shape (not the array the add-line endpoint takes). Guarded on
2865
+ // `!== undefined` so `{}` — clear every customer-visible attribute —
2866
+ // reaches the endpoint instead of being dropped as falsy.
2867
+ ...update.customAttributes !== void 0 ? { customAttributes: update.customAttributes } : {}
2015
2868
  };
2016
2869
  const res = await fetcher(
2017
2870
  `/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
@@ -2182,6 +3035,121 @@ function createApiSubscriptionAdapter(fetcher) {
2182
3035
  return { _tag: "Failure", error: e };
2183
3036
  }
2184
3037
  },
3038
+ // Legacy endpoint on purpose — see SubscriptionAdapter.updateDeliveryAddress
3039
+ // for why the documented v1 `deliveryAddress` patch is not used here.
3040
+ async updateDeliveryAddress({ subscriptionId, address }) {
3041
+ try {
3042
+ const res = await fetcher(
3043
+ `/customer/subscriptions/${subscriptionId}/shipping-address`,
3044
+ {
3045
+ method: "PUT",
3046
+ headers: { "Content-Type": "application/json" },
3047
+ body: JSON.stringify(toShippingAddressBody(address))
3048
+ }
3049
+ );
3050
+ if (!res.ok) {
3051
+ if (res.status === 403) {
3052
+ return { _tag: "Failure", error: { kind: "NOT_ALLOWED" } };
3053
+ }
3054
+ if (res.status === 404) {
3055
+ return {
3056
+ _tag: "Failure",
3057
+ error: { kind: "SUBSCRIPTION_NOT_FOUND" }
3058
+ };
3059
+ }
3060
+ if (res.status === 400) {
3061
+ const body = await readErrorBody(res);
3062
+ const message = typeof body.message === "string" ? body.message : void 0;
3063
+ if (isStaleSubscriptionMessage(message)) {
3064
+ return { _tag: "Failure", error: { kind: "STALE" } };
3065
+ }
3066
+ return {
3067
+ _tag: "Failure",
3068
+ error: {
3069
+ kind: "INVALID_ADDRESS",
3070
+ ...message ? { message } : {}
3071
+ }
3072
+ };
3073
+ }
3074
+ return {
3075
+ _tag: "Failure",
3076
+ error: { kind: "UNKNOWN", message: `HTTP ${res.status}` }
3077
+ };
3078
+ }
3079
+ return { _tag: "Success", data: void 0 };
3080
+ } catch (e) {
3081
+ return {
3082
+ _tag: "Failure",
3083
+ error: {
3084
+ kind: "NETWORK",
3085
+ message: e instanceof Error ? e.message : void 0
3086
+ }
3087
+ };
3088
+ }
3089
+ },
3090
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
3091
+ async getAvailableShippingMethods({ subscriptionId }) {
3092
+ try {
3093
+ const res = await fetcher(
3094
+ `/customer/subscriptions/${subscriptionId}/available-shipping-methods`
3095
+ );
3096
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
3097
+ const data = await res.json();
3098
+ return { _tag: "Success", data };
3099
+ } catch (e) {
3100
+ return { _tag: "Failure", error: e };
3101
+ }
3102
+ },
3103
+ // Legacy endpoint — no /customer/v1 equivalent exists yet. No `extra` is
3104
+ // sent: pickup points are out of scope, and omitting it is what tells the
3105
+ // endpoint to leave any existing parcel-locker attributes alone.
3106
+ async updateDeliveryMethod({ subscriptionId, shippingMethodHandle }) {
3107
+ try {
3108
+ const res = await fetcher(
3109
+ `/customer/subscriptions/${subscriptionId}/shipping-method`,
3110
+ {
3111
+ method: "POST",
3112
+ headers: { "Content-Type": "application/json" },
3113
+ body: JSON.stringify({ shippingMethodHandle })
3114
+ }
3115
+ );
3116
+ if (!res.ok) {
3117
+ if (res.status === 403) {
3118
+ return { _tag: "Failure", error: { kind: "NOT_ALLOWED" } };
3119
+ }
3120
+ if (res.status === 404) {
3121
+ return {
3122
+ _tag: "Failure",
3123
+ error: { kind: "SUBSCRIPTION_NOT_FOUND" }
3124
+ };
3125
+ }
3126
+ if (res.status === 400) {
3127
+ const body = await readErrorBody(res);
3128
+ const message = typeof body.message === "string" ? body.message : void 0;
3129
+ if (isStaleSubscriptionMessage(message)) {
3130
+ return { _tag: "Failure", error: { kind: "STALE" } };
3131
+ }
3132
+ return {
3133
+ _tag: "Failure",
3134
+ error: { kind: "UNKNOWN", ...message ? { message } : {} }
3135
+ };
3136
+ }
3137
+ return {
3138
+ _tag: "Failure",
3139
+ error: { kind: "UNKNOWN", message: `HTTP ${res.status}` }
3140
+ };
3141
+ }
3142
+ return { _tag: "Success", data: void 0 };
3143
+ } catch (e) {
3144
+ return {
3145
+ _tag: "Failure",
3146
+ error: {
3147
+ kind: "NETWORK",
3148
+ message: e instanceof Error ? e.message : void 0
3149
+ }
3150
+ };
3151
+ }
3152
+ },
2185
3153
  async resume({ subscriptionId }) {
2186
3154
  try {
2187
3155
  const res = await fetcher(
@@ -2314,6 +3282,116 @@ function createApiSubscriptionAdapter(fetcher) {
2314
3282
  }
2315
3283
  },
2316
3284
  // Legacy endpoint — no /customer/v1 equivalent exists yet.
3285
+ async getInstantOrderProducts({ subscriptionId, query, after, limit }) {
3286
+ try {
3287
+ const params = new URLSearchParams();
3288
+ if (query) params.set("q", query);
3289
+ if (after != null) params.set("after", after);
3290
+ if (limit != null) params.set("limit", String(limit));
3291
+ const search = params.toString() ? `?${params.toString()}` : "";
3292
+ const res = await fetcher(
3293
+ `/customer/subscriptions/${subscriptionId}/instant-orders/products${search}`
3294
+ );
3295
+ if (!res.ok) {
3296
+ if (res.status === 404) {
3297
+ return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
3298
+ }
3299
+ const message = await readErrorMessage(res);
3300
+ return {
3301
+ _tag: "Failure",
3302
+ error: { kind: "UNKNOWN", message: message ?? `HTTP ${res.status}` }
3303
+ };
3304
+ }
3305
+ const body = await res.json().catch(() => null);
3306
+ if (body == null) {
3307
+ return {
3308
+ _tag: "Failure",
3309
+ error: { kind: "UNKNOWN", message: "Malformed response" }
3310
+ };
3311
+ }
3312
+ return { _tag: "Success", data: mapInstantOrderProducts(body) };
3313
+ } catch (e) {
3314
+ return {
3315
+ _tag: "Failure",
3316
+ error: {
3317
+ kind: "NETWORK",
3318
+ message: e instanceof Error ? e.message : void 0
3319
+ }
3320
+ };
3321
+ }
3322
+ },
3323
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
3324
+ //
3325
+ // No X-Last-Updated-At header: an instant order never mutates the
3326
+ // subscription, so the optimistic-lock preValidation (which only fires when
3327
+ // the header is present) has nothing to guard here.
3328
+ async placeInstantOrder({
3329
+ subscriptionId,
3330
+ requestKey,
3331
+ items,
3332
+ shippingMethodTitle
3333
+ }) {
3334
+ try {
3335
+ const res = await fetcher(
3336
+ `/customer/subscriptions/${subscriptionId}/instant-orders`,
3337
+ {
3338
+ method: "POST",
3339
+ headers: { "Content-Type": "application/json" },
3340
+ body: JSON.stringify({
3341
+ requestKey,
3342
+ // Narrowed to what the endpoint accepts: the rest of
3343
+ // PostableSubscriptionItem describes a subscription line (its
3344
+ // billing policy, its cycle limit), and an instant order is not one.
3345
+ items: items.map(({ variantId, quantity }) => ({
3346
+ variantId,
3347
+ quantity
3348
+ })),
3349
+ // Basket-level, not per-line: the endpoint prices one delivery
3350
+ // rate for the whole instant order.
3351
+ ...shippingMethodTitle != null ? { shippingMethodTitle } : {}
3352
+ })
3353
+ }
3354
+ );
3355
+ if (!res.ok) {
3356
+ if (res.status === 404) {
3357
+ return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
3358
+ }
3359
+ const message = await readErrorMessage(res);
3360
+ const kind = PLACE_INSTANT_ORDER_ERROR_KINDS[res.status] ?? "UNKNOWN";
3361
+ return {
3362
+ _tag: "Failure",
3363
+ error: { kind, message: message ?? `HTTP ${res.status}` }
3364
+ };
3365
+ }
3366
+ const body = await res.json().catch(() => null);
3367
+ if (body == null) {
3368
+ return {
3369
+ _tag: "Failure",
3370
+ error: { kind: "UNKNOWN", message: "Malformed response" }
3371
+ };
3372
+ }
3373
+ return {
3374
+ _tag: "Success",
3375
+ data: {
3376
+ orderId: body.orderId,
3377
+ totalPrice: body.totalPrice ?? null,
3378
+ currencyCode: body.currencyCode,
3379
+ status: body.status,
3380
+ ...body.paymentUrl != null ? { paymentUrl: body.paymentUrl } : {},
3381
+ ...body.reason != null ? { reason: body.reason } : {}
3382
+ }
3383
+ };
3384
+ } catch (e) {
3385
+ return {
3386
+ _tag: "Failure",
3387
+ error: {
3388
+ kind: "NETWORK",
3389
+ message: e instanceof Error ? e.message : void 0
3390
+ }
3391
+ };
3392
+ }
3393
+ },
3394
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
2317
3395
  async getLastOrder({ subscriptionId }) {
2318
3396
  try {
2319
3397
  const res = await fetcher(
@@ -3083,6 +4161,19 @@ function getCurrentSubscriptionId() {
3083
4161
  }
3084
4162
  return null;
3085
4163
  }
4164
+ async function countRatesTitled(adapter, subscriptionId, title) {
4165
+ try {
4166
+ const available = await adapter.getAvailableShippingMethods({
4167
+ subscriptionId
4168
+ });
4169
+ if (available._tag !== "Success") {
4170
+ return 0;
4171
+ }
4172
+ return available.data.filter((option) => option.title === title).length;
4173
+ } catch {
4174
+ return 0;
4175
+ }
4176
+ }
3086
4177
  function createSubscriptionService(adapter) {
3087
4178
  const current = d(null);
3088
4179
  const hasPendingBilling = w(
@@ -3217,6 +4308,27 @@ function createSubscriptionService(adapter) {
3217
4308
  },
3218
4309
  reschedule: (subscriptionId, newDate) => adapter.reschedule({ subscriptionId, newDate }),
3219
4310
  updateCustomAttributes: (subscriptionId, customAttributes) => adapter.updateCustomAttributes({ subscriptionId, customAttributes }),
4311
+ updateDeliveryAddress: async (subscriptionId, address) => {
4312
+ const result = await adapter.updateDeliveryAddress({
4313
+ subscriptionId,
4314
+ address
4315
+ });
4316
+ if (result._tag === "Success") {
4317
+ await reload();
4318
+ }
4319
+ return result;
4320
+ },
4321
+ getAvailableShippingMethods: (subscriptionId) => adapter.getAvailableShippingMethods({ subscriptionId }),
4322
+ updateDeliveryMethod: async (subscriptionId, shippingMethodHandle) => {
4323
+ const result = await adapter.updateDeliveryMethod({
4324
+ subscriptionId,
4325
+ shippingMethodHandle
4326
+ });
4327
+ if (result._tag === "Success") {
4328
+ await reload();
4329
+ }
4330
+ return result;
4331
+ },
3220
4332
  resume: (subscriptionId) => adapter.resume({ subscriptionId }),
3221
4333
  pause: (subscriptionId) => adapter.pause({ subscriptionId }),
3222
4334
  reactivate: (subscriptionId) => adapter.reactivate({ subscriptionId }),
@@ -3227,10 +4339,116 @@ function createSubscriptionService(adapter) {
3227
4339
  getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
3228
4340
  getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
3229
4341
  getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
3230
- addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
4342
+ addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 }),
4343
+ getInstantOrderProducts: (subscriptionId, params) => adapter.getInstantOrderProducts ? adapter.getInstantOrderProducts({ subscriptionId, ...params }) : Promise.resolve({
4344
+ _tag: "Success",
4345
+ data: {
4346
+ available: false,
4347
+ requiresCheckout: false,
4348
+ products: [],
4349
+ hasNextPage: false,
4350
+ endCursor: null
4351
+ }
4352
+ }),
4353
+ placeInstantOrder: async (subscriptionId, requestKey, items, options) => {
4354
+ const placeOrder = adapter.placeInstantOrder;
4355
+ if (!placeOrder) {
4356
+ return {
4357
+ _tag: "Failure",
4358
+ error: { kind: "UNSUPPORTED" }
4359
+ };
4360
+ }
4361
+ const chosenTitle = options == null ? void 0 : options.shippingMethodTitle;
4362
+ if (chosenTitle != null) {
4363
+ const matches = await countRatesTitled(
4364
+ adapter,
4365
+ subscriptionId,
4366
+ chosenTitle
4367
+ );
4368
+ if (matches > 1) {
4369
+ return {
4370
+ _tag: "Failure",
4371
+ error: {
4372
+ kind: "AMBIGUOUS_SHIPPING_METHOD",
4373
+ message: `${matches} available shipping methods are titled "${chosenTitle}", so the title cannot say which one was chosen.`
4374
+ }
4375
+ };
4376
+ }
4377
+ }
4378
+ return placeOrder({ subscriptionId, requestKey, items, ...options });
4379
+ }
3231
4380
  };
3232
4381
  return service;
3233
4382
  }
4383
+ function isOneTimeLine(line) {
4384
+ return line.isOneTime === true || line.recurringCycleLimit === 1;
4385
+ }
4386
+ function hasValue(value) {
4387
+ return typeof value === "string" && value.length > 0;
4388
+ }
4389
+ function hasIdentity(line) {
4390
+ return hasValue(line.subscriptionId) && hasValue(line.subscriptionLineId);
4391
+ }
4392
+ function isSameLine(a2, b3) {
4393
+ return hasValue(a2.subscriptionLineId) && a2.subscriptionLineId === b3.subscriptionLineId;
4394
+ }
4395
+ function belongsToSameSubscription(a2, b3) {
4396
+ return hasValue(a2.subscriptionId) && a2.subscriptionId === b3.subscriptionId;
4397
+ }
4398
+ function parentLineFor(child, lines) {
4399
+ if (!hasValue(child.parentLineId)) {
4400
+ return void 0;
4401
+ }
4402
+ return lines.find(
4403
+ (candidate) => belongsToSameSubscription(candidate, child) && (candidate.subscriptionLineId === child.parentLineId || candidate.id === child.parentLineId)
4404
+ );
4405
+ }
4406
+ function productKey(line, lines) {
4407
+ if (hasValue(line.parentProductId)) {
4408
+ return `product:${line.parentProductId}`;
4409
+ }
4410
+ if (hasValue(line.parentLineId)) {
4411
+ const parent = parentLineFor(line, lines);
4412
+ if (hasValue(parent == null ? void 0 : parent.productId)) {
4413
+ return `product:${parent.productId}`;
4414
+ }
4415
+ return `line:${line.parentLineId}`;
4416
+ }
4417
+ const lineIdentity = hasValue(line.subscriptionLineId) ? line.subscriptionLineId : line.id;
4418
+ if (!hasValue(lineIdentity)) {
4419
+ return null;
4420
+ }
4421
+ const isReferencedAsParent = lines.some(
4422
+ (candidate) => belongsToSameSubscription(candidate, line) && (hasValue(line.productId) && candidate.parentProductId === line.productId || hasValue(candidate.parentLineId) && (candidate.parentLineId === line.subscriptionLineId || candidate.parentLineId === line.id))
4423
+ );
4424
+ if (isReferencedAsParent && hasValue(line.productId)) {
4425
+ return `product:${line.productId}`;
4426
+ }
4427
+ return `line:${lineIdentity}`;
4428
+ }
4429
+ function remainingRecurringProductKeys(line, lines) {
4430
+ const removedProductKey = productKey(line, lines);
4431
+ return new Set(
4432
+ lines.filter(
4433
+ (item) => belongsToSameSubscription(item, line) && !isOneTimeLine(item)
4434
+ ).map((item) => productKey(item, lines)).filter((key) => key != null && key !== removedProductKey)
4435
+ );
4436
+ }
4437
+ function canRemoveLine(line, lines) {
4438
+ if (!hasIdentity(line)) {
4439
+ return false;
4440
+ }
4441
+ const targetExists = lines.some(
4442
+ (item) => belongsToSameSubscription(item, line) && isSameLine(item, line)
4443
+ );
4444
+ if (!targetExists) {
4445
+ return false;
4446
+ }
4447
+ if (isOneTimeLine(line)) {
4448
+ return true;
4449
+ }
4450
+ return remainingRecurringProductKeys(line, lines).size > 0;
4451
+ }
3234
4452
  function getDefaultBlockForAction(actionType, actionConfig, blockId) {
3235
4453
  const blockName = `action:${actionType}`;
3236
4454
  const definedBlocks = getDefinedBlocks();
@@ -4206,8 +5424,12 @@ export {
4206
5424
  OrderServiceContext,
4207
5425
  OverlayServiceContext,
4208
5426
  P as PAGE_ROOT_SLOT,
5427
+ PAYMENT_INSTRUMENT_TYPES,
4209
5428
  ProductServiceContext,
5429
+ REFRESH_LOCK_NAME,
4210
5430
  RouterServiceContext,
5431
+ SUPPORTED_UI_LANGUAGES,
5432
+ SUPPORTED_UI_LANGUAGE_OPTIONS,
4211
5433
  SchedulesServiceContext,
4212
5434
  S as SelectedBlockContext,
4213
5435
  SettingsServiceContext,
@@ -4224,6 +5446,8 @@ export {
4224
5446
  c as backfillInstanceDefaults,
4225
5447
  b2 as buildGridLayout,
4226
5448
  j as buildLayoutCss,
5449
+ buildOrdersQuery,
5450
+ canRemoveLine,
4227
5451
  canvasMode,
4228
5452
  clampRect,
4229
5453
  k2 as classifyRowDividers,
@@ -4277,11 +5501,16 @@ export {
4277
5501
  emitLayoutSetRects,
4278
5502
  q2 as expandFillLayoutDocument,
4279
5503
  findFreeRect,
5504
+ fromV1Customer,
4280
5505
  getActiveSurfaceContext,
4281
5506
  getDefaultBlockForAction,
4282
5507
  getDefinedBlocks,
4283
5508
  F as getDevtoolsBlockState,
4284
5509
  getLayoutModeAdapter,
5510
+ getPaymentInstrumentDetail,
5511
+ getPaymentInstrumentDigits,
5512
+ getPaymentInstrumentExpiry,
5513
+ getPaymentInstrumentName,
4285
5514
  gridV1Engine,
4286
5515
  hasDefaultBlockForAction,
4287
5516
  G as hasRenderedContent,
@@ -4289,6 +5518,9 @@ export {
4289
5518
  i as injectContext,
4290
5519
  insertRectRelative,
4291
5520
  H as isEditorPreview,
5521
+ isFilterQuery,
5522
+ isOneTimeLine,
5523
+ isStaleSubscriptionMessage,
4292
5524
  b as isStubDefinition,
4293
5525
  t2 as layoutScopeSelector,
4294
5526
  I as layoutSystemEnabled,
@@ -4306,6 +5538,7 @@ export {
4306
5538
  resetBreakpoint,
4307
5539
  r as resolveLayout,
4308
5540
  k as serializeBlocks,
5541
+ serializeV1PaymentInstrument,
4309
5542
  setActiveSurfaceContext,
4310
5543
  K as setDevtoolsBlockProp,
4311
5544
  setLayoutCommandDispatcher,
@@ -4313,7 +5546,9 @@ export {
4313
5546
  l as setLayoutSystemEnabled,
4314
5547
  d as signal,
4315
5548
  subscribeContext,
5549
+ toLanguageSelectOptions,
4316
5550
  L as untracked,
4317
5551
  waitForDevBlocksUrl,
5552
+ withRefreshLock,
4318
5553
  w2 as withWrapper
4319
5554
  };