@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.
- package/dist/{ExtensionRoot-DRpD5EcC.js → ExtensionRoot-CG9dH98z.js} +1 -1
- package/dist/{bridge-Mz0CqG6E.js → bridge-Bss7Xq3D.js} +7 -0
- package/dist/core.js +1289 -54
- package/dist/{css-runtime-B7tZrBIp.js → css-runtime-CjZl0K48.js} +23 -17
- package/dist/editor/bridge.d.ts.map +1 -1
- package/dist/editor/components.d.ts +11 -0
- package/dist/editor/components.d.ts.map +1 -1
- package/dist/editor/handler.d.ts.map +1 -1
- package/dist/editor.js +37 -10
- package/dist/{gridstack-adapter-BGKGC-eO.js → gridstack-adapter-C1Q8gbsH.js} +2 -2
- package/dist/languages.d.ts +51 -0
- package/dist/languages.d.ts.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/{morph-CT5hc5Qp.js → morph-LwttdRML.js} +1 -1
- package/dist/preact.js +1 -1
- package/dist/{project-gridstack-PxN_DIfP.js → project-gridstack-CRZ_7HoT.js} +1 -1
- package/dist/react.js +1 -1
- package/dist/services/customer/adapter.d.ts +14 -1
- package/dist/services/customer/adapter.d.ts.map +1 -1
- package/dist/services/customer/adapter.test.d.ts +2 -0
- package/dist/services/customer/adapter.test.d.ts.map +1 -0
- package/dist/services/customer/customerService.d.ts.map +1 -1
- package/dist/services/customer/customerService.test.d.ts +2 -0
- package/dist/services/customer/customerService.test.d.ts.map +1 -0
- package/dist/services/customer/index.d.ts +2 -2
- package/dist/services/customer/index.d.ts.map +1 -1
- package/dist/services/customer/types.d.ts +29 -0
- package/dist/services/customer/types.d.ts.map +1 -1
- package/dist/services/login/adapter.d.ts.map +1 -1
- package/dist/services/login/index.d.ts +1 -0
- package/dist/services/login/index.d.ts.map +1 -1
- package/dist/services/login/loginService.d.ts +1 -0
- package/dist/services/login/loginService.d.ts.map +1 -1
- package/dist/services/login/loginService.test.d.ts +2 -0
- package/dist/services/login/loginService.test.d.ts.map +1 -0
- package/dist/services/login/refreshLock.d.ts +28 -0
- package/dist/services/login/refreshLock.d.ts.map +1 -0
- package/dist/services/login/refreshLock.test.d.ts +2 -0
- package/dist/services/login/refreshLock.test.d.ts.map +1 -0
- package/dist/services/order/adapter.d.ts +11 -1
- package/dist/services/order/adapter.d.ts.map +1 -1
- package/dist/services/order/adapter.test.d.ts +2 -0
- package/dist/services/order/adapter.test.d.ts.map +1 -0
- package/dist/services/order/index.d.ts +2 -2
- package/dist/services/order/index.d.ts.map +1 -1
- package/dist/services/order/types.d.ts +56 -6
- package/dist/services/order/types.d.ts.map +1 -1
- package/dist/services/product/adapter.d.ts +42 -0
- package/dist/services/product/adapter.d.ts.map +1 -1
- package/dist/services/product/index.d.ts +1 -1
- package/dist/services/product/index.d.ts.map +1 -1
- package/dist/services/settings/types.d.ts +15 -0
- package/dist/services/settings/types.d.ts.map +1 -1
- package/dist/services/subscription/adapter.d.ts +56 -1
- package/dist/services/subscription/adapter.d.ts.map +1 -1
- package/dist/services/subscription/index.d.ts +4 -1
- package/dist/services/subscription/index.d.ts.map +1 -1
- package/dist/services/subscription/instantOrderErrors.d.ts +91 -0
- package/dist/services/subscription/instantOrderErrors.d.ts.map +1 -0
- package/dist/services/subscription/lineRemoval.d.ts +56 -0
- package/dist/services/subscription/lineRemoval.d.ts.map +1 -0
- package/dist/services/subscription/lineRemoval.test.d.ts +2 -0
- package/dist/services/subscription/lineRemoval.test.d.ts.map +1 -0
- package/dist/services/subscription/paymentInstrument.d.ts +119 -0
- package/dist/services/subscription/paymentInstrument.d.ts.map +1 -0
- package/dist/services/subscription/paymentInstrument.test.d.ts +2 -0
- package/dist/services/subscription/paymentInstrument.test.d.ts.map +1 -0
- package/dist/services/subscription/store.d.ts.map +1 -1
- package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
- package/dist/services/subscription/types.d.ts +351 -11
- package/dist/services/subscription/types.d.ts.map +1 -1
- package/dist/theme-state.d.ts +10 -1
- package/dist/theme-state.d.ts.map +1 -1
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/vue/context.d.ts +25 -1
- package/dist/vue/context.d.ts.map +1 -1
- package/dist/vue/useTranslatedProp.test.d.ts +2 -0
- package/dist/vue/useTranslatedProp.test.d.ts.map +1 -0
- package/dist/vue.js +37 -3
- package/dist/web-components/editor/InlineText.d.ts +1 -0
- package/dist/web-components/editor/InlineText.d.ts.map +1 -1
- package/dist/web-components-editor.js +15 -7
- package/dist/web-components-runtime.js +2 -2
- 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-
|
|
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-
|
|
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-
|
|
10
|
-
import { p as p2 } from "./project-gridstack-
|
|
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
|
-
|
|
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/
|
|
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:
|
|
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
|
|
461
|
-
const storageKey = `auth:${key}`;
|
|
714
|
+
function webStorage(kind) {
|
|
462
715
|
try {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
517
|
-
|
|
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 =
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1592
|
-
|
|
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
|
|
1839
|
-
return
|
|
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
|
-
...
|
|
1849
|
-
|
|
1850
|
-
|
|
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/
|
|
2832
|
+
`/customer/subscriptions/${subscriptionId}/lines`,
|
|
1984
2833
|
{
|
|
1985
2834
|
method: "POST",
|
|
1986
2835
|
headers: { "Content-Type": "application/json" },
|
|
1987
|
-
body: JSON.stringify(
|
|
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
|
};
|