@hartl-services/medusa-storefront 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -2
  3. package/dist/browser/account-verification-channel.d.ts +11 -0
  4. package/dist/browser/account-verification-channel.js +32 -0
  5. package/dist/browser/actions.d.ts +53 -0
  6. package/dist/browser/actions.js +1 -0
  7. package/dist/browser/affiliate-referral.d.ts +152 -0
  8. package/dist/browser/affiliate-referral.js +85 -0
  9. package/dist/browser/browser-session.d.ts +3 -0
  10. package/dist/browser/browser-session.js +27 -0
  11. package/dist/browser/cart-mutations.d.ts +36 -0
  12. package/dist/browser/cart-mutations.js +137 -0
  13. package/dist/browser/cart-refresh.d.ts +11 -0
  14. package/dist/browser/cart-refresh.js +33 -0
  15. package/dist/browser/client.d.ts +15 -0
  16. package/dist/browser/client.js +20 -0
  17. package/dist/browser/fresh-order-marker.d.ts +11 -0
  18. package/dist/browser/fresh-order-marker.js +30 -0
  19. package/dist/browser/index.d.ts +946 -0
  20. package/dist/browser/index.js +70 -0
  21. package/dist/browser/query-invalidation.d.ts +4 -0
  22. package/dist/browser/query-invalidation.js +39 -0
  23. package/dist/browser/query-keys.d.ts +43 -0
  24. package/dist/browser/query-keys.js +28 -0
  25. package/dist/browser/query-options.d.ts +781 -0
  26. package/dist/browser/query-options.js +165 -0
  27. package/dist/browser/region.d.ts +5 -0
  28. package/dist/browser/region.js +15 -0
  29. package/dist/browser/unwrap-medusa-action.d.ts +7 -0
  30. package/dist/browser/unwrap-medusa-action.js +25 -0
  31. package/dist/core/action-result.d.ts +17 -0
  32. package/dist/core/action-result.js +12 -0
  33. package/dist/core/addresses.d.ts +46 -0
  34. package/dist/core/addresses.js +137 -0
  35. package/dist/core/affiliate/affiliate-application-errors.d.ts +2 -0
  36. package/dist/core/affiliate/affiliate-application-errors.js +14 -0
  37. package/dist/core/affiliate/affiliate-commission-presentation.d.ts +15 -0
  38. package/dist/core/affiliate/affiliate-commission-presentation.js +28 -0
  39. package/dist/core/affiliate/affiliate-coupon-code-presentation.d.ts +10 -0
  40. package/dist/core/affiliate/affiliate-coupon-code-presentation.js +22 -0
  41. package/dist/core/affiliate/affiliate-coupon-label.d.ts +3 -0
  42. package/dist/core/affiliate/affiliate-coupon-label.js +4 -0
  43. package/dist/core/affiliate/affiliate-date-range.d.ts +6 -0
  44. package/dist/core/affiliate/affiliate-date-range.js +16 -0
  45. package/dist/core/affiliate/affiliate-rate-presentation.d.ts +1 -0
  46. package/dist/core/affiliate/affiliate-rate-presentation.js +8 -0
  47. package/dist/core/affiliate/partner-company-address.d.ts +24 -0
  48. package/dist/core/affiliate/partner-company-address.js +31 -0
  49. package/dist/core/auth/customer-sign-in.d.ts +36 -0
  50. package/dist/core/auth/customer-sign-in.js +55 -0
  51. package/dist/core/auth/link-token.d.ts +8 -0
  52. package/dist/core/auth/link-token.js +11 -0
  53. package/dist/core/auth/login-code-auto-send.d.ts +7 -0
  54. package/dist/core/auth/login-code-auto-send.js +11 -0
  55. package/dist/core/auth/password-rule-label.d.ts +4 -0
  56. package/dist/core/auth/password-rule-label.js +13 -0
  57. package/dist/core/cart/cart-line-item-image-url.d.ts +9 -0
  58. package/dist/core/cart/cart-line-item-image-url.js +14 -0
  59. package/dist/core/cart/cart-normalization.d.ts +2 -0
  60. package/dist/core/cart/cart-normalization.js +11 -0
  61. package/dist/core/cart/cart-price-breakdown.d.ts +30 -0
  62. package/dist/core/cart/cart-price-breakdown.js +45 -0
  63. package/dist/core/cart/cart-tax-breakdown.d.ts +17 -0
  64. package/dist/core/cart/cart-tax-breakdown.js +18 -0
  65. package/dist/core/cart/coupon-promotions.d.ts +8 -0
  66. package/dist/core/cart/coupon-promotions.js +9 -0
  67. package/dist/core/cart/payment-session.d.ts +3 -0
  68. package/dist/core/cart/payment-session.js +12 -0
  69. package/dist/core/cart/refunded-cart.d.ts +27 -0
  70. package/dist/core/cart/refunded-cart.js +46 -0
  71. package/dist/core/catalog/category-filter.d.ts +16 -0
  72. package/dist/core/catalog/category-filter.js +33 -0
  73. package/dist/core/catalog/inventory.d.ts +10 -0
  74. package/dist/core/catalog/inventory.js +9 -0
  75. package/dist/core/catalog/medusa-product-image.d.ts +8 -0
  76. package/dist/core/catalog/medusa-product-image.js +13 -0
  77. package/dist/core/catalog/product-price.d.ts +8 -0
  78. package/dist/core/catalog/product-price.js +21 -0
  79. package/dist/core/catalog/selected-variant.d.ts +7 -0
  80. package/dist/core/catalog/selected-variant.js +9 -0
  81. package/dist/core/checkout/checkout-account-offer-state.d.ts +41 -0
  82. package/dist/core/checkout/checkout-account-offer-state.js +32 -0
  83. package/dist/core/checkout/checkout-address-draft.d.ts +39 -0
  84. package/dist/core/checkout/checkout-address-draft.js +78 -0
  85. package/dist/core/checkout/checkout-address-form.d.ts +24 -0
  86. package/dist/core/checkout/checkout-address-form.js +99 -0
  87. package/dist/core/checkout/checkout-coupon.d.ts +9 -0
  88. package/dist/core/checkout/checkout-coupon.js +13 -0
  89. package/dist/core/checkout/checkout-steps.d.ts +20 -0
  90. package/dist/core/checkout/checkout-steps.js +44 -0
  91. package/dist/core/checkout/customer-login-required.d.ts +12 -0
  92. package/dist/core/checkout/customer-login-required.js +14 -0
  93. package/dist/core/checkout/payment-authorization.d.ts +8 -0
  94. package/dist/core/checkout/payment-authorization.js +9 -0
  95. package/dist/core/checkout/stripe-redirect-return.d.ts +13 -0
  96. package/dist/core/checkout/stripe-redirect-return.js +29 -0
  97. package/dist/core/errors.d.ts +12 -0
  98. package/dist/core/errors.js +18 -0
  99. package/dist/core/fields.d.ts +18 -0
  100. package/dist/core/fields.js +20 -0
  101. package/dist/core/form-values.d.ts +162 -0
  102. package/dist/core/form-values.js +234 -0
  103. package/dist/core/format/money.d.ts +7 -0
  104. package/dist/core/format/money.js +6 -0
  105. package/dist/core/i18n.d.ts +24 -0
  106. package/dist/core/i18n.js +15 -0
  107. package/dist/core/image-url.d.ts +18 -0
  108. package/dist/core/image-url.js +34 -0
  109. package/dist/core/index.d.ts +48 -0
  110. package/dist/core/index.js +49 -0
  111. package/dist/core/order/order-payment-summary.d.ts +15 -0
  112. package/dist/core/order/order-payment-summary.js +48 -0
  113. package/dist/core/plugin-features.d.ts +21 -0
  114. package/dist/core/plugin-features.js +30 -0
  115. package/dist/core/query-string.d.ts +6 -0
  116. package/dist/core/query-string.js +16 -0
  117. package/dist/core/region.d.ts +11 -0
  118. package/dist/core/region.js +13 -0
  119. package/dist/core/report-error.d.ts +12 -0
  120. package/dist/core/report-error.js +1 -0
  121. package/dist/core/routing-locale.d.ts +1 -0
  122. package/dist/core/routing-locale.js +25 -0
  123. package/dist/core/settle-within.d.ts +8 -0
  124. package/dist/core/settle-within.js +14 -0
  125. package/dist/core/tax-compliance/address-tax-identification.d.ts +37 -0
  126. package/dist/core/tax-compliance/address-tax-identification.js +51 -0
  127. package/dist/core/tax-compliance/tax-identification.d.ts +18 -0
  128. package/dist/core/tax-compliance/tax-identification.js +58 -0
  129. package/dist/core/texts-de.d.ts +136 -0
  130. package/dist/core/texts-de.js +152 -0
  131. package/dist/react/cart-error-boundary.d.ts +24 -0
  132. package/dist/react/cart-error-boundary.js +57 -0
  133. package/dist/react/context.d.ts +17 -0
  134. package/dist/react/context.js +46 -0
  135. package/dist/react/index.d.ts +12 -0
  136. package/dist/react/index.js +14 -0
  137. package/dist/react/use-account.d.ts +214 -0
  138. package/dist/react/use-account.js +294 -0
  139. package/dist/react/use-affiliate.d.ts +566 -0
  140. package/dist/react/use-affiliate.js +573 -0
  141. package/dist/react/use-auth.d.ts +136 -0
  142. package/dist/react/use-auth.js +402 -0
  143. package/dist/react/use-cart.d.ts +57 -0
  144. package/dist/react/use-cart.js +137 -0
  145. package/dist/react/use-checkout-address-form.d.ts +156 -0
  146. package/dist/react/use-checkout-address-form.js +95 -0
  147. package/dist/react/use-checkout.d.ts +268 -0
  148. package/dist/react/use-checkout.js +873 -0
  149. package/dist/react/use-countdown.d.ts +5 -0
  150. package/dist/react/use-countdown.js +23 -0
  151. package/dist/react/use-link-token.d.ts +12 -0
  152. package/dist/react/use-link-token.js +17 -0
  153. package/dist/react/use-plugin-feature.d.ts +16 -0
  154. package/dist/react/use-plugin-feature.js +31 -0
  155. package/dist/react/use-verification-watch.d.ts +7 -0
  156. package/dist/react/use-verification-watch.js +49 -0
  157. package/dist/registry/account.json +248 -0
  158. package/dist/registry/affiliate.json +302 -0
  159. package/dist/registry/cart.json +127 -0
  160. package/dist/registry/checkout.json +169 -0
  161. package/dist/registry/form.json +145 -0
  162. package/dist/registry/registry.json +917 -0
  163. package/dist/registry/setup.json +76 -0
  164. package/dist/server/affiliate.d.ts +124 -0
  165. package/dist/server/affiliate.js +145 -0
  166. package/dist/server/cart.d.ts +10 -0
  167. package/dist/server/cart.js +113 -0
  168. package/dist/server/catalog.d.ts +28 -0
  169. package/dist/server/catalog.js +82 -0
  170. package/dist/server/checkout.d.ts +6 -0
  171. package/dist/server/checkout.js +12 -0
  172. package/dist/server/context.d.ts +38 -0
  173. package/dist/server/context.js +34 -0
  174. package/dist/server/customer.d.ts +84 -0
  175. package/dist/server/customer.js +362 -0
  176. package/dist/server/documents.d.ts +29 -0
  177. package/dist/server/documents.js +55 -0
  178. package/dist/server/index.d.ts +258 -0
  179. package/dist/server/index.js +45 -0
  180. package/dist/server/orders.d.ts +9 -0
  181. package/dist/server/orders.js +31 -0
  182. package/dist/server/plugin-features.d.ts +13 -0
  183. package/dist/server/plugin-features.js +52 -0
  184. package/dist/server/region.d.ts +6 -0
  185. package/dist/server/region.js +17 -0
  186. package/dist/server/route-response.d.ts +2 -0
  187. package/dist/server/route-response.js +17 -0
  188. package/dist/server/run-medusa-action.d.ts +7 -0
  189. package/dist/server/run-medusa-action.js +40 -0
  190. package/dist/server/session.d.ts +29 -0
  191. package/dist/server/session.js +106 -0
  192. package/dist/server/tax-compliance.d.ts +26 -0
  193. package/dist/server/tax-compliance.js +85 -0
  194. package/package.json +86 -3
@@ -0,0 +1,137 @@
1
+ import { checkoutStorePaths } from "@hartl-services/medusa-contracts/paths/checkout";
2
+ import { buildReplacementCart, getCustomerCouponCodes } from "../core/cart/refunded-cart.js";
3
+ import { hasCartPromotionCode } from "../core/checkout/checkout-coupon.js";
4
+ import { isPluginFeatureEnabled } from "../core/plugin-features.js";
5
+ // One per browser page, shared by every active-cart fetch: concurrent fetches
6
+ // of a refunded cart share one replacement.
7
+ const refundedCartReplacements = new Map();
8
+ // Cart creation/mutation runs through the browser-credentialed client, not a
9
+ // Server Action - Medusa's own host-only cookies must be attached directly
10
+ // from the browser.
11
+ export function createCartMutations({ actions, createClient, referral, reportCheckoutError }) {
12
+ async function createCartNow(data) {
13
+ const sdk = createClient();
14
+ const { cart } = await sdk.store.cart.create(data);
15
+ await actions.syncCartId(cart.id);
16
+ return (await actions.applyCustomerDefaultsToActiveCart()) ?? cart;
17
+ }
18
+ // Best effort through the coupon field's own path (affiliate coupon first,
19
+ // Medusa promotion on 404): a code that no longer applies is simply left off.
20
+ // A shipping coupon cannot apply before a shipping method is chosen.
21
+ async function reapplyCouponCodes(codes) {
22
+ if (codes.length === 0) {
23
+ return;
24
+ }
25
+ const cart = await actions.getActiveCart();
26
+ if (!cart) {
27
+ return;
28
+ }
29
+ try {
30
+ const useAffiliatePath = isPluginFeatureEnabled(await actions.fetchPluginFeatures(), "affiliate", "coupons");
31
+ for (const code of codes.filter((code) => !hasCartPromotionCode(cart, code))) {
32
+ await referral.applyCheckoutCoupon(cart.id, code, { useAffiliatePath }).catch((error) => reportCheckoutError(error, { cartId: cart.id }));
33
+ }
34
+ }
35
+ catch (error) {
36
+ // The replacement cart already exists; only its coupons are missing.
37
+ reportCheckoutError(error, { cartId: cart.id });
38
+ }
39
+ }
40
+ async function createCart(data) {
41
+ await referral.waitForAffiliateReferralCapture();
42
+ return await createCartNow(data);
43
+ }
44
+ async function addCartLineItem(cartId, data) {
45
+ const sdk = createClient();
46
+ const { cart } = await sdk.store.cart.createLineItem(cartId, data);
47
+ return cart;
48
+ }
49
+ return {
50
+ createCart,
51
+ // Swaps a cart whose payment was refunded (see refunded-cart.ts) for a fresh
52
+ // one with the same contents, so the customer simply pays again. Concurrent
53
+ // active-cart fetches share one replacement. Deliberately skips the referral
54
+ // wait: a "?ref=" capture refreshes the active cart itself and would wait on
55
+ // this replacement in turn; it then captures onto the new cart.
56
+ replaceRefundedCart(cart) {
57
+ const pending = refundedCartReplacements.get(cart.id);
58
+ if (pending) {
59
+ return pending;
60
+ }
61
+ const replacement = createCartNow(buildReplacementCart(cart))
62
+ .then(() => reapplyCouponCodes(getCustomerCouponCodes(cart)))
63
+ .then(() => actions.getActiveCart())
64
+ .catch((error) => {
65
+ // The refunded cart stays usable for display; its own error handling applies.
66
+ reportCheckoutError(error, { cartId: cart.id });
67
+ return cart;
68
+ })
69
+ .finally(() => refundedCartReplacements.delete(cart.id));
70
+ refundedCartReplacements.set(cart.id, replacement);
71
+ return replacement;
72
+ },
73
+ addCartLineItem,
74
+ async updateCartLineItem(cartId, lineItemId, data) {
75
+ const sdk = createClient();
76
+ const { cart } = await sdk.store.cart.updateLineItem(cartId, lineItemId, data);
77
+ return cart;
78
+ },
79
+ async removeCartLineItem(cartId, lineItemId) {
80
+ const sdk = createClient();
81
+ return await sdk.store.cart.deleteLineItem(cartId, lineItemId);
82
+ },
83
+ async updateCart(cartId, data) {
84
+ const sdk = createClient();
85
+ const { cart } = await sdk.store.cart.update(cartId, data);
86
+ return cart;
87
+ },
88
+ async addCartShippingMethod(cartId, data) {
89
+ const sdk = createClient();
90
+ const { cart } = await sdk.store.cart.addShippingMethod(cartId, data);
91
+ return cart;
92
+ },
93
+ async addCartPromotion(cartId, data) {
94
+ const sdk = createClient();
95
+ const { cart } = await sdk.store.cart.addPromotions(cartId, data);
96
+ return cart;
97
+ },
98
+ async removeCartPromotion(cartId, data) {
99
+ const sdk = createClient();
100
+ const { cart } = await sdk.store.cart.removePromotions(cartId, data);
101
+ return cart;
102
+ },
103
+ async initiateCartPayment(cartId, data) {
104
+ const sdk = createClient();
105
+ const { cart } = await sdk.store.cart.retrieve(cartId);
106
+ return (await sdk.store.payment.initiatePaymentSession(cart, data)).payment_collection;
107
+ },
108
+ // Runs every check the completion runs, without completing. Stripe captures
109
+ // when the payment is confirmed, so a refusal must come before that, not
110
+ // after (Medusa then refunds). Same transport and credentials as completeCart,
111
+ // so the registered-email gate sees the same session.
112
+ async checkCartCompletion(cartId) {
113
+ const sdk = createClient();
114
+ return await sdk.client.fetch(checkoutStorePaths.post.completionCheck(cartId), {
115
+ credentials: "include",
116
+ method: "POST",
117
+ });
118
+ },
119
+ async completeCart(cartId) {
120
+ const sdk = createClient();
121
+ const result = await sdk.store.cart.complete(cartId);
122
+ if (result.type === "order") {
123
+ await actions.clearCartId();
124
+ }
125
+ return result;
126
+ },
127
+ /** Adds one unit of a variant, creating the cart first when there is none. */
128
+ async addProductToCart({ activeCart, quantity = 1, regionId, variantId }) {
129
+ const cart = activeCart ?? (await createCart({ region_id: regionId }));
130
+ await addCartLineItem(cart.id, {
131
+ quantity,
132
+ variant_id: variantId,
133
+ });
134
+ return cart;
135
+ },
136
+ };
137
+ }
@@ -0,0 +1,11 @@
1
+ import type { QueryClient } from "@tanstack/react-query";
2
+ import type { BrowserCartMutations } from "./cart-mutations.js";
3
+ import type { BrowserQueryOptions } from "./query-options.js";
4
+ interface CartRefreshDependencies {
5
+ cart: Pick<BrowserCartMutations, "initiateCartPayment">;
6
+ queries: Pick<BrowserQueryOptions, "activeCart">;
7
+ reportCheckoutError: (error: unknown, context?: Record<string, unknown>) => void;
8
+ }
9
+ export declare function createRefreshCartAfterMutation({ cart, queries, reportCheckoutError }: CartRefreshDependencies): (queryClient: QueryClient, cartId?: string) => Promise<import("@medusajs/types").StoreCart | null>;
10
+ export type RefreshCartAfterMutation = ReturnType<typeof createRefreshCartAfterMutation>;
11
+ export {};
@@ -0,0 +1,33 @@
1
+ import { findPendingPaymentSession, isPaymentSessionOutdated } from "../core/cart/payment-session.js";
2
+ import { invalidateCommerceQueries } from "./query-invalidation.js";
3
+ import { affiliateQueryKeys } from "./query-keys.js";
4
+ // The single "cart price changed" boundary: it reloads the cart, re-initiates a
5
+ // still-pending payment session whose amount no longer matches the cart (Medusa
6
+ // creates a fresh Stripe PaymentIntent / client_secret), and reloads again so
7
+ // the payment step's <Elements key={clientSecret}> remounts on the new secret.
8
+ // When Medusa already deleted the sessions because the amount changed, there is
9
+ // nothing to re-initiate and the payment step's auto-initiate takes over instead.
10
+ export function createRefreshCartAfterMutation({ cart, queries, reportCheckoutError }) {
11
+ return async function refreshCartAfterMutation(queryClient, cartId) {
12
+ await Promise.all([invalidateCommerceQueries(queryClient, cartId), cartId ? queryClient.invalidateQueries({ queryKey: affiliateQueryKeys.cartReferral(cartId) }) : Promise.resolve()]);
13
+ const activeCart = await queryClient.fetchQuery(queries.activeCart());
14
+ if (!activeCart) {
15
+ return null;
16
+ }
17
+ const pendingSession = findPendingPaymentSession(activeCart);
18
+ if (!pendingSession || !isPaymentSessionOutdated(activeCart, pendingSession)) {
19
+ return activeCart;
20
+ }
21
+ try {
22
+ await cart.initiateCartPayment(activeCart.id, { provider_id: pendingSession.provider_id });
23
+ }
24
+ catch (error) {
25
+ // The mutation itself succeeded; a failed re-initiate must not surface as
26
+ // "cart could not be updated". The payment step's auto-initiate retries.
27
+ reportCheckoutError(error, { cartId: activeCart.id, providerId: pendingSession.provider_id });
28
+ return activeCart;
29
+ }
30
+ await invalidateCommerceQueries(queryClient, activeCart.id);
31
+ return await queryClient.fetchQuery(queries.activeCart());
32
+ };
33
+ }
@@ -0,0 +1,15 @@
1
+ import Medusa from "@medusajs/js-sdk";
2
+ import type { Translate } from "../core/i18n.js";
3
+ export interface BrowserClientConfig {
4
+ publicBackendUrl: string;
5
+ publishableKey: string | undefined;
6
+ t: Translate;
7
+ }
8
+ export type CredentialedMedusaClient = Medusa;
9
+ /**
10
+ * Browser-only Medusa client, credentials: "include" - Medusa's own host-only
11
+ * cookies. This transport is mandatory (not a style choice) for cart
12
+ * line-item mutations, cart e-mail changes, completeCart, and affiliate
13
+ * referral/coupon-status calls - never construct a third client elsewhere.
14
+ */
15
+ export declare function createCredentialedClientFactory({ publicBackendUrl, publishableKey, t }: BrowserClientConfig): () => CredentialedMedusaClient;
@@ -0,0 +1,20 @@
1
+ import Medusa from "@medusajs/js-sdk";
2
+ /**
3
+ * Browser-only Medusa client, credentials: "include" - Medusa's own host-only
4
+ * cookies. This transport is mandatory (not a style choice) for cart
5
+ * line-item mutations, cart e-mail changes, completeCart, and affiliate
6
+ * referral/coupon-status calls - never construct a third client elsewhere.
7
+ */
8
+ export function createCredentialedClientFactory({ publicBackendUrl, publishableKey, t }) {
9
+ const baseUrl = publicBackendUrl.replace(/\/+$/, "");
10
+ return function createCredentialedMedusaClient() {
11
+ if (!publishableKey) {
12
+ throw new Error(t("config.publishable_key_missing"));
13
+ }
14
+ return new Medusa({
15
+ auth: { fetchCredentials: "include", type: "session" },
16
+ baseUrl,
17
+ publishableKey,
18
+ });
19
+ };
20
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Marks an order the checkout has just completed, so the confirmation page
3
+ * counts the purchase once: not on a reload and not when opened again from the
4
+ * order history. Lives in sessionStorage, which can be unavailable (private
5
+ * mode, blocked site data); the purchase then simply goes uncounted.
6
+ */
7
+ export declare function createFreshOrderMarker(keyPrefix: string): {
8
+ markFreshOrder(orderId: string): void;
9
+ /** True exactly once per marked order. */
10
+ takeFreshOrder(orderId: string): boolean;
11
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Marks an order the checkout has just completed, so the confirmation page
3
+ * counts the purchase once: not on a reload and not when opened again from the
4
+ * order history. Lives in sessionStorage, which can be unavailable (private
5
+ * mode, blocked site data); the purchase then simply goes uncounted.
6
+ */
7
+ export function createFreshOrderMarker(keyPrefix) {
8
+ return {
9
+ markFreshOrder(orderId) {
10
+ try {
11
+ sessionStorage.setItem(`${keyPrefix}${orderId}`, "1");
12
+ }
13
+ catch {
14
+ // Storage unavailable: nothing to mark.
15
+ }
16
+ },
17
+ /** True exactly once per marked order. */
18
+ takeFreshOrder(orderId) {
19
+ try {
20
+ const key = `${keyPrefix}${orderId}`;
21
+ const isFresh = sessionStorage.getItem(key) !== null;
22
+ sessionStorage.removeItem(key);
23
+ return isFresh;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ },
29
+ };
30
+ }