@wix/himalaya-cli 0.1493.0 → 0.1495.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +5 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/live-activity/src/index.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/extensions.d.ts +52 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/index.d.ts +180 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/extensions.d.ts +16 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/display.d.ts +24 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/live-activity.mjs +3 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bookings.mjs +4 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ecom.mjs +14 -0
- package/dist/cli.mjs +3585 -2540
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/himi-json-output.md +46 -4
- package/guides/iap-products.md +68 -9
- package/guides/one-app-to-himi-app.md +80 -5
- package/guides/qa-builds.md +2 -2
- package/guides/widgets.md +5 -3
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +4442 -1532
- package/templates/beauty-salon/himalaya.content.json +1 -1
- package/templates/beauty-salon/tier5-src/_shared/himi-store-collection.ts +6 -3
- package/templates/beauty-salon/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/beauty-salon/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/beauty-salon/tier5-src/himi-booking-service/index.ts +514 -117
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +256 -19
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +196 -34
- package/templates/beauty-salon/tier5-src/himi-store-saved/index.ts +21 -5
- package/templates/beauty-salon/tier5-src/himi-store-shop/index.ts +14 -2
- package/templates/beauty-salon/tokens.json +21 -0
- package/templates/branded-lite/dev/index.ts +4427 -1510
- package/templates/branded-lite/himalaya.content.json +1 -1
- package/templates/branded-lite/tier5-src/_shared/himi-store-collection.ts +6 -3
- package/templates/branded-lite/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/branded-lite/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/branded-lite/tier5-src/himi-booking-service/index.ts +514 -117
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +256 -19
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +196 -34
- package/templates/branded-lite/tier5-src/himi-store-saved/index.ts +21 -5
- package/templates/branded-lite/tier5-src/himi-store-shop/index.ts +14 -2
- package/templates/branded-lite/tokens.json +9 -0
- package/templates/community-club/dev/index.ts +1 -1
- package/templates/community-club/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/community-club/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/community-club/tokens.json +21 -0
- package/templates/events-meetups/dev/index.ts +1 -1
- package/templates/events-meetups/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/events-meetups/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/events-meetups/tokens.json +21 -0
- package/templates/gym-fitness/dev/index.ts +4439 -1529
- package/templates/gym-fitness/himalaya.content.json +1 -1
- package/templates/gym-fitness/tier5-src/_shared/himi-store-collection.ts +6 -3
- package/templates/gym-fitness/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/gym-fitness/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/gym-fitness/tier5-src/himi-booking-service/index.ts +514 -117
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +256 -19
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +196 -34
- package/templates/gym-fitness/tier5-src/himi-store-saved/index.ts +21 -5
- package/templates/gym-fitness/tier5-src/himi-store-shop/index.ts +14 -2
- package/templates/gym-fitness/tokens.json +21 -0
- package/templates/nonprofit-cause/dev/index.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/nonprofit-cause/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/nonprofit-cause/tokens.json +21 -0
- package/templates/podcast-creator/dev/index.ts +1 -1
- package/templates/podcast-creator/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/podcast-creator/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/podcast-creator/tokens.json +21 -0
- package/templates/portfolio-creative/dev/index.ts +2569 -1013
- package/templates/portfolio-creative/himalaya.content.json +1 -1
- package/templates/portfolio-creative/tier5-src/_shared/himi-store-collection.ts +6 -3
- package/templates/portfolio-creative/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/portfolio-creative/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +256 -19
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +196 -34
- package/templates/portfolio-creative/tier5-src/himi-store-saved/index.ts +21 -5
- package/templates/portfolio-creative/tier5-src/himi-store-shop/index.ts +14 -2
- package/templates/portfolio-creative/tokens.json +21 -0
- package/templates/restaurant/dev/index.ts +1 -1
- package/templates/restaurant/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/restaurant/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/restaurant/tokens.json +21 -0
- package/templates/retail-store/dev/index.ts +2541 -985
- package/templates/retail-store/himalaya.content.json +1 -1
- package/templates/retail-store/tier5-src/_shared/himi-store-collection.ts +6 -3
- package/templates/retail-store/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/retail-store/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +256 -19
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +196 -34
- package/templates/retail-store/tier5-src/himi-store-saved/index.ts +21 -5
- package/templates/retail-store/tier5-src/himi-store-shop/index.ts +14 -2
- package/templates/retail-store/tokens.json +21 -0
- package/templates/yoga-studio/dev/index.ts +1618 -264
- package/templates/yoga-studio/tier5-src/_shared/himi-store-saved.ts +7 -1
- package/templates/yoga-studio/tier5-src/_shared/wix-service-price.ts +18 -0
- package/templates/yoga-studio/tier5-src/himi-booking-service/index.ts +514 -117
- package/templates/yoga-studio/tokens.json +21 -0
|
@@ -7,9 +7,12 @@ export const storeCollectionSaving: CollectionSaving = {
|
|
|
7
7
|
const products = new Map(readSavedProducts().map(row => [row.id, row]));
|
|
8
8
|
for (const row of rows) {
|
|
9
9
|
if (typeof row.id !== 'string' || typeof row.name !== 'string') continue;
|
|
10
|
-
products.
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const previous = products.get(row.id);
|
|
11
|
+
const freshPrice = typeof row.commercePrice === 'string' ? row.commercePrice : typeof row.subtitle === 'string' ? row.subtitle : '';
|
|
12
|
+
const wasPrice = typeof row.wasPrice === 'string' ? row.wasPrice || undefined : previous?.price === freshPrice ? previous?.wasPrice : undefined;
|
|
13
|
+
products.set(row.id, { ...previous, wasPrice, id: row.id, name: row.name,
|
|
14
|
+
price: freshPrice || previous?.price || '',
|
|
15
|
+
image: typeof row.image === 'string' && row.image ? row.image : previous?.image ?? '' });
|
|
13
16
|
}
|
|
14
17
|
const selected = ids.map(id => products.get(id));
|
|
15
18
|
if (selected.some(row => !row)) throw new Error('Saved product metadata is unavailable');
|
|
@@ -5,6 +5,8 @@ export interface SavedProduct {
|
|
|
5
5
|
name: string;
|
|
6
6
|
price: string;
|
|
7
7
|
image: string;
|
|
8
|
+
wasPrice?: string;
|
|
9
|
+
swatches?: string[];
|
|
8
10
|
}
|
|
9
11
|
type StorageLike = { getItem(key: string): string | null; setItem(key: string, value: string): void };
|
|
10
12
|
const KEY = "himi-store:saved-products:v1";
|
|
@@ -24,7 +26,11 @@ export function readSavedProducts(): SavedProduct[] {
|
|
|
24
26
|
if (!value || typeof value !== "object") continue;
|
|
25
27
|
const row = value as Record<string, unknown>;
|
|
26
28
|
if (typeof row.id !== "string" || !row.id.trim() || typeof row.name !== "string") continue;
|
|
27
|
-
|
|
29
|
+
const swatches = Array.isArray(row.swatches)
|
|
30
|
+
? row.swatches.filter((color): color is string => typeof color === "string" && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(color)).slice(0, 5)
|
|
31
|
+
: [];
|
|
32
|
+
rows.set(row.id, { id: row.id, name: row.name, price: typeof row.price === "string" ? row.price : "", image: typeof row.image === "string" ? row.image : "",
|
|
33
|
+
...(typeof row.wasPrice === "string" && row.wasPrice ? { wasPrice: row.wasPrice } : {}), ...(swatches.length ? { swatches } : {}) });
|
|
28
34
|
}
|
|
29
35
|
return [...rows.values()];
|
|
30
36
|
}
|
|
@@ -99,6 +99,24 @@ export function serviceIsChargeable(service: unknown): boolean {
|
|
|
99
99
|
return amount != null && amount > 0;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Format one Bookings v2 Money value (`{ value, currency }`, `value` a decimal string) — a
|
|
104
|
+
* price, a deposit, a VARIED minimum, a cancellation fee. A whole amount drops its cents
|
|
105
|
+
* ("₪90", not "₪90.00"), as the service screen shows it. "" when it carries no usable
|
|
106
|
+
* amount. The currency comes from the Money itself, then from `fallbackCurrency`.
|
|
107
|
+
*/
|
|
108
|
+
export function formatServiceMoney(money: unknown, fallbackCurrency = "USD"): string {
|
|
109
|
+
const m = (money ?? {}) as { value?: unknown; amount?: unknown; currency?: unknown };
|
|
110
|
+
const amount = numberOf(m.value ?? m.amount);
|
|
111
|
+
if (amount == null) return "";
|
|
112
|
+
const currency = stringOf(m.currency) || fallbackCurrency;
|
|
113
|
+
try {
|
|
114
|
+
return formatCurrency(amount, currency, "en", Number.isInteger(amount) ? 0 : undefined);
|
|
115
|
+
} catch {
|
|
116
|
+
return `${amount}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
/**
|
|
103
121
|
* Format a Wix Bookings service's price for display, e.g. "₪111" / "$20".
|
|
104
122
|
*
|
|
@@ -2,11 +2,12 @@ import { getStoreProduct } from "../_shared/himi-store.js";
|
|
|
2
2
|
import { iapPriceCopy } from "../_shared/iap-worker.js";
|
|
3
3
|
import { withIapPresentation } from "../_shared/iap-presentation.js";
|
|
4
4
|
import { type IapCommerce, classifyCart } from "@himalaya/iap-checkout";
|
|
5
|
-
import { createCheckoutFromCurrentCart, createOrder, getCurrentCart, removeLineItemsFromCurrentCart, updateLineItemsQuantity, type WixCartLineItemDetail } from "@himalaya/wix-ecom";
|
|
5
|
+
import { addCouponToCurrentCart, calculateCurrentCart, createCheckoutFromCurrentCart, createOrder, getCurrentCart, removeCouponFromCurrentCart, removeLineItemsFromCurrentCart, updateLineItemsQuantity, type WixCartDetail, type WixCartLineItemDetail } from "@himalaya/wix-ecom";
|
|
6
6
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
7
7
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
8
8
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
9
9
|
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
10
|
+
import { formatCurrency } from "../_shared/engine-intl.js";
|
|
10
11
|
|
|
11
12
|
/** Wix Stores appDefId — the vertical that owns a cart/ecom order, and therefore the
|
|
12
13
|
* `appId` its cashier order is paid under. (Same constant Wix's own tooling uses.) */
|
|
@@ -26,6 +27,7 @@ async function resolveMsid(): Promise<string> {
|
|
|
26
27
|
import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
|
|
27
28
|
import { getAll, upsert, remove as dbRemove, type DbEntity } from "@himalaya/local-db";
|
|
28
29
|
import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-error.js";
|
|
30
|
+
import { isProductSaved, readSavedProducts, toggleSavedProduct } from "../_shared/himi-store-saved.js";
|
|
29
31
|
|
|
30
32
|
// Offline-first (data-offline): the cart is READ-THROUGH — paint the last-synced line
|
|
31
33
|
// items from the on-device `cart` entity first (instant, works offline), then
|
|
@@ -36,8 +38,37 @@ import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-erro
|
|
|
36
38
|
// The checkout redirect path is unchanged. Every local-db call is wrapped so a build
|
|
37
39
|
// without the `local_database` grant behaves exactly as before (online-only).
|
|
38
40
|
const ENTITY = "cart";
|
|
41
|
+
function cachedUnitPrice(total: string, quantity: number): string {
|
|
42
|
+
if (quantity <= 1) return total;
|
|
43
|
+
const match = /\d(?:[\d.,]|[ \u00a0\u202f](?=\d))*/u.exec(total);
|
|
44
|
+
if (!match) return total;
|
|
45
|
+
const raw = match[0];
|
|
46
|
+
const compact = raw.replace(/[ \u00a0\u202f]/g, "");
|
|
47
|
+
const last = Math.max(compact.lastIndexOf("."), compact.lastIndexOf(","));
|
|
48
|
+
const digits = last >= 0 && compact.length - last - 1 <= 2 ? compact.length - last - 1 : 0;
|
|
49
|
+
const decimal = digits ? compact[last] : ".";
|
|
50
|
+
const numeric = digits ? `${compact.slice(0, last).replace(/[,.]/g, "")}.${compact.slice(last + 1)}` : compact.replace(/[,.]/g, "");
|
|
51
|
+
const amount = Number(numeric);
|
|
52
|
+
if (!Number.isFinite(amount)) return total;
|
|
53
|
+
const [whole, fraction] = (amount / quantity).toFixed(digits).split(".");
|
|
54
|
+
const spaceGrouping = raw.match(/[ \u00a0\u202f](?=\d)/u)?.[0];
|
|
55
|
+
const grouped = spaceGrouping ? whole.replace(/\B(?=(\d{3})+(?!\d))/g, spaceGrouping) : whole;
|
|
56
|
+
return total.replace(raw, `${grouped}${digits ? decimal + fraction : ""}`);
|
|
57
|
+
}
|
|
39
58
|
async function readCachedItems(): Promise<Row[]> {
|
|
40
|
-
try {
|
|
59
|
+
try {
|
|
60
|
+
return ((await getAll(ENTITY)) as unknown as Row[]).map(item => {
|
|
61
|
+
const quantity = Math.max(1, Math.floor(Number(item.quantity) || 1));
|
|
62
|
+
let saved = false;
|
|
63
|
+
try { saved = isProductSaved(item.catalogReference?.catalogItemId ?? item.productId ?? ""); } catch { /* keep the cached line usable */ }
|
|
64
|
+
const unitPrice = item.unitPrice || cachedUnitPrice(item.total || item.commerceTotal || "", quantity);
|
|
65
|
+
return { ...item, catalogItemId: item.catalogReference?.catalogItemId ?? item.productId ?? "", quantity, quantityLabel: String(quantity), atMinQuantity: quantity <= 1, unitPrice,
|
|
66
|
+
wasPrice: item.wasPrice ?? "", swatches: item.swatches ?? [], option1: item.option1 ?? "", option2: item.option2 ?? "", option3: item.option3 ?? "",
|
|
67
|
+
noOption1: !item.option1, noOption2: !item.option2, noOption3: !item.option3,
|
|
68
|
+
stockMessage: item.stockMessage ?? "", noStockMessage: !item.stockMessage,
|
|
69
|
+
inStockMessage: item.inStockMessage ?? "", noInStockMessage: !item.inStockMessage, saved, notSaved: !saved };
|
|
70
|
+
});
|
|
71
|
+
} catch { return []; }
|
|
41
72
|
}
|
|
42
73
|
/** Mirror the cart Wix returned: upsert its lines and drop any cached line it no longer has
|
|
43
74
|
* (one removed elsewhere, or a removal whose answer was lost), so the offline paint never
|
|
@@ -54,6 +85,8 @@ async function cacheDrop(id: string): Promise<void> { try { await dbRemove(ENTIT
|
|
|
54
85
|
|
|
55
86
|
interface Row {
|
|
56
87
|
catalogReference?: { appId: string; catalogItemId: string; options?: Record<string, unknown> };
|
|
88
|
+
productId?: string;
|
|
89
|
+
catalogItemId?: string;
|
|
57
90
|
id: string;
|
|
58
91
|
name: string;
|
|
59
92
|
title: string;
|
|
@@ -64,19 +97,45 @@ interface Row {
|
|
|
64
97
|
/** The "−" control at qty 1 removes the line, so it never dead-ends. */
|
|
65
98
|
atMinQuantity: boolean;
|
|
66
99
|
total: string;
|
|
100
|
+
unitPrice: string;
|
|
101
|
+
wasPrice?: string;
|
|
102
|
+
swatches?: string[];
|
|
67
103
|
commerceTotal: string;
|
|
68
104
|
image: string;
|
|
69
105
|
initial: string;
|
|
70
106
|
hasImage: boolean;
|
|
71
107
|
noImage: boolean;
|
|
108
|
+
option1: string; option2: string; option3: string;
|
|
109
|
+
noOption1: boolean; noOption2: boolean; noOption3: boolean;
|
|
110
|
+
stockMessage: string; noStockMessage: boolean;
|
|
111
|
+
inStockMessage: string; noInStockMessage: boolean;
|
|
112
|
+
saved: boolean;
|
|
113
|
+
notSaved: boolean;
|
|
72
114
|
}
|
|
73
115
|
interface State {
|
|
74
116
|
status: "loading" | "ready" | "error";
|
|
117
|
+
errorKind: "offline" | "server" | "";
|
|
75
118
|
items: Row[];
|
|
76
119
|
subtotal: string;
|
|
77
120
|
commerceSubtotal?: string;
|
|
78
121
|
emptyText: string;
|
|
79
122
|
checkoutHint: string;
|
|
123
|
+
itemCount: number;
|
|
124
|
+
promoCode: string;
|
|
125
|
+
appliedCode: string;
|
|
126
|
+
appliedLabel: string;
|
|
127
|
+
couponId: string;
|
|
128
|
+
promoError: string;
|
|
129
|
+
promoBusy: boolean;
|
|
130
|
+
noPromoError: boolean;
|
|
131
|
+
hasCoupon: boolean;
|
|
132
|
+
discount: string;
|
|
133
|
+
noDiscount: boolean;
|
|
134
|
+
noCheckoutHint: boolean;
|
|
135
|
+
showPaidToast: boolean;
|
|
136
|
+
noPaidToast: boolean;
|
|
137
|
+
noCoupon: boolean;
|
|
138
|
+
subtotalLabel: string;
|
|
80
139
|
isLoading: boolean;
|
|
81
140
|
notLoading: boolean;
|
|
82
141
|
/** Standard error quad (the himi-store retryBlock contract): the descriptor
|
|
@@ -84,6 +143,8 @@ interface State {
|
|
|
84
143
|
* failed load shows the retry card instead of a blank list + blank subtotal. */
|
|
85
144
|
isErrored: boolean;
|
|
86
145
|
notErrored: boolean;
|
|
146
|
+
noOfflineError: boolean;
|
|
147
|
+
noServerError: boolean;
|
|
87
148
|
hideContent: boolean;
|
|
88
149
|
noEmpty: boolean;
|
|
89
150
|
noItems: boolean;
|
|
@@ -91,7 +152,14 @@ interface State {
|
|
|
91
152
|
|
|
92
153
|
function itemId(arg: unknown): string {
|
|
93
154
|
if (typeof arg === "string") return arg;
|
|
94
|
-
|
|
155
|
+
if (!arg || typeof arg !== "object") return "";
|
|
156
|
+
const payload = arg as {
|
|
157
|
+
id?: string;
|
|
158
|
+
item?: { id?: string };
|
|
159
|
+
args?: { id?: string };
|
|
160
|
+
params?: { id?: string };
|
|
161
|
+
};
|
|
162
|
+
return payload.id ?? payload.item?.id ?? payload.args?.id ?? payload.params?.id ?? "";
|
|
95
163
|
}
|
|
96
164
|
|
|
97
165
|
function mediaUrl(value: unknown): string {
|
|
@@ -130,11 +198,37 @@ function mediaUrl(value: unknown): string {
|
|
|
130
198
|
}
|
|
131
199
|
|
|
132
200
|
function row(item: WixCartLineItemDetail, index: number): Row {
|
|
201
|
+
const raw = item as WixCartLineItemDetail & Record<string, unknown>;
|
|
133
202
|
const quantity = item.quantity ?? 1;
|
|
134
203
|
const name = item.productName?.original || copyParam("cart.itemFallbackName", "Cart item");
|
|
135
204
|
const image = mediaUrl(item);
|
|
205
|
+
const lines = Array.isArray(raw.descriptionLines) ? raw.descriptionLines as Array<{ name?: { original?: string }; plainText?: { original?: string }; colorInfo?: { original?: string; code?: string } }> : [];
|
|
206
|
+
const opts = lines.slice(0, 3).map(line => {
|
|
207
|
+
const label = line.name?.original?.trim() ?? "";
|
|
208
|
+
const name = label && label === label.toUpperCase() ? label.charAt(0) + label.slice(1).toLowerCase() : label;
|
|
209
|
+
const value = line.plainText?.original || line.colorInfo?.original || "";
|
|
210
|
+
return [name, value].filter(Boolean).join(": ");
|
|
211
|
+
});
|
|
212
|
+
const inventory = raw.inventory as { quantity?: number } | undefined;
|
|
213
|
+
const availabilityData = raw.availability as { status?: string; quantityAvailable?: number } | undefined;
|
|
214
|
+
const stockCount = Number(availabilityData?.quantityAvailable ?? raw.inventoryQuantity ?? inventory?.quantity);
|
|
215
|
+
const availability = String(availabilityData?.status ?? raw.availabilityStatus ?? "").toUpperCase();
|
|
216
|
+
const stockMessage = availability === "NOT_AVAILABLE" || availability === "NOT_FOUND" || availability.includes("OUT")
|
|
217
|
+
? copyParam("cart.outOfStock", "Out of stock")
|
|
218
|
+
: availability === "PARTIALLY_AVAILABLE" && Number.isFinite(stockCount) && stockCount > 0
|
|
219
|
+
? copyTemplate("cart.onlyLeft", "Only {count} left", { count: stockCount }) : "";
|
|
220
|
+
const inStock = !stockMessage && (availability === "AVAILABLE" || availability === "IN_STOCK" || availability === "PARTIALLY_AVAILABLE");
|
|
221
|
+
const total = (raw.lineItemPrice as { formattedAmount?: string } | undefined)?.formattedAmount
|
|
222
|
+
?? (raw.totalPriceAfterTax as { formattedAmount?: string } | undefined)?.formattedAmount
|
|
223
|
+
?? (raw.totalPriceBeforeTax as { formattedAmount?: string } | undefined)?.formattedAmount
|
|
224
|
+
?? item.price?.formattedAmount ?? "";
|
|
225
|
+
const unitPrice = item.price?.formattedAmount ?? "";
|
|
226
|
+
const fullPrice = (raw.fullPrice as { formattedAmount?: string } | undefined)?.formattedAmount ?? "";
|
|
227
|
+
const swatches = lines.map(line => line.colorInfo?.code ?? "").filter(code => /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(code)).slice(0, 5);
|
|
228
|
+
const saved = (() => { try { return isProductSaved(String((item as WixCartLineItemDetail & { catalogReference?: { catalogItemId?: string } }).catalogReference?.catalogItemId ?? "")); } catch { return false; } })();
|
|
136
229
|
return {
|
|
137
230
|
catalogReference: (item as WixCartLineItemDetail & { catalogReference?: Row["catalogReference"] }).catalogReference,
|
|
231
|
+
catalogItemId: (item as WixCartLineItemDetail & { catalogReference?: Row["catalogReference"] }).catalogReference?.catalogItemId ?? "",
|
|
138
232
|
id: item.id || `line-${index}`,
|
|
139
233
|
name,
|
|
140
234
|
title: name,
|
|
@@ -142,33 +236,66 @@ function row(item: WixCartLineItemDetail, index: number): Row {
|
|
|
142
236
|
quantity,
|
|
143
237
|
quantityLabel: `${quantity}`,
|
|
144
238
|
atMinQuantity: quantity <= 1,
|
|
145
|
-
total
|
|
146
|
-
|
|
239
|
+
total,
|
|
240
|
+
unitPrice,
|
|
241
|
+
wasPrice: fullPrice && fullPrice !== unitPrice ? fullPrice : "",
|
|
242
|
+
swatches,
|
|
243
|
+
commerceTotal: total,
|
|
147
244
|
image,
|
|
148
245
|
initial: (name.trim()[0] || "C").toUpperCase(),
|
|
149
246
|
hasImage: Boolean(image),
|
|
150
247
|
noImage: !image,
|
|
248
|
+
option1: opts[0] ?? "", option2: opts[1] ?? "", option3: opts[2] ?? "",
|
|
249
|
+
noOption1: !opts[0], noOption2: !opts[1], noOption3: !opts[2],
|
|
250
|
+
stockMessage, noStockMessage: !stockMessage,
|
|
251
|
+
inStockMessage: inStock ? copyParam("cart.inStock", "In Stock") : "",
|
|
252
|
+
noInStockMessage: !inStock,
|
|
253
|
+
saved,
|
|
254
|
+
notSaved: !saved,
|
|
151
255
|
};
|
|
152
256
|
}
|
|
153
257
|
|
|
154
258
|
/** POST /ecom/v1/carts/{cartId}/update-line-items-quantity via @himalaya/wix-ecom. */
|
|
155
|
-
function updateLineItemQuantity(cartId: string, lineItemId: string, quantity: number): Promise<
|
|
259
|
+
function updateLineItemQuantity(cartId: string, lineItemId: string, quantity: number): Promise<{ cart?: WixCartDetail }> {
|
|
156
260
|
return updateLineItemsQuantity(cartId, [{ id: lineItemId, quantity: Math.max(1, quantity) }]);
|
|
157
261
|
}
|
|
158
262
|
|
|
159
263
|
function derive(state: State): State {
|
|
264
|
+
let savedIds: Set<string> | null = null;
|
|
265
|
+
try { savedIds = new Set(readSavedProducts().map(row => row.id)); }
|
|
266
|
+
catch { /* saved storage unavailable: retain the saved flags already shown on each row */ }
|
|
267
|
+
const items = savedIds ? state.items.map(item => {
|
|
268
|
+
const productId = item.catalogReference?.catalogItemId ?? item.productId;
|
|
269
|
+
if (!productId) return item;
|
|
270
|
+
const saved = savedIds.has(productId);
|
|
271
|
+
return { ...item, saved, notSaved: !saved };
|
|
272
|
+
}) : state.items;
|
|
273
|
+
const itemCount = items.reduce((n, item) => n + item.quantity, 0);
|
|
160
274
|
return {
|
|
161
275
|
...state,
|
|
276
|
+
items,
|
|
277
|
+
promoBusy: promoInFlight && state.promoBusy,
|
|
162
278
|
isLoading: state.status === "loading",
|
|
163
279
|
notLoading: state.status !== "loading",
|
|
164
280
|
isErrored: state.status === "error",
|
|
165
281
|
notErrored: state.status !== "error",
|
|
282
|
+
noOfflineError: state.status !== "error" || state.errorKind !== "offline",
|
|
283
|
+
noServerError: state.status !== "error" || state.errorKind !== "server",
|
|
166
284
|
// Exactly one of {content, error card, empty card} renders: content only on
|
|
167
285
|
// "ready" (an errored cart must not paint a blank subtotal), the error card
|
|
168
286
|
// on "error", the empty card on ready + no items.
|
|
169
287
|
hideContent: state.status !== "ready",
|
|
170
|
-
noEmpty: state.status !== "ready" ||
|
|
171
|
-
noItems:
|
|
288
|
+
noEmpty: state.status !== "ready" || items.length > 0,
|
|
289
|
+
noItems: items.length === 0,
|
|
290
|
+
itemCount,
|
|
291
|
+
hasCoupon: Boolean(state.appliedCode),
|
|
292
|
+
appliedLabel: state.appliedCode ? copyTemplate("cart.promoApplied", "Promo code {code}", { code: state.appliedCode }) : "",
|
|
293
|
+
noPromoError: !state.promoError,
|
|
294
|
+
noDiscount: !state.discount,
|
|
295
|
+
noCheckoutHint: !state.checkoutHint || state.showPaidToast,
|
|
296
|
+
noPaidToast: !state.showPaidToast,
|
|
297
|
+
noCoupon: !state.appliedCode,
|
|
298
|
+
subtotalLabel: copyTemplate("cart.subtotalCount", "Subtotal ({count})", { count: itemCount }),
|
|
172
299
|
};
|
|
173
300
|
}
|
|
174
301
|
|
|
@@ -205,21 +332,61 @@ async function removeLine(state: State, id: string): Promise<State> {
|
|
|
205
332
|
}
|
|
206
333
|
}
|
|
207
334
|
|
|
208
|
-
async function refresh(state: State, checkoutHint = ""): Promise<State> {
|
|
209
|
-
const cart = await getCurrentCart();
|
|
335
|
+
async function refresh(state: State, checkoutHint = "", updatedCart?: WixCartDetail, calculatedCart?: unknown): Promise<State> {
|
|
336
|
+
const cart = updatedCart ?? await getCurrentCart();
|
|
210
337
|
const items = (cart.lineItems ?? []).map(row);
|
|
211
338
|
await writeCachedItems(items);
|
|
339
|
+
const rawCart = cart as unknown as Record<string, unknown>;
|
|
340
|
+
const summary = (rawCart.priceSummary ?? rawCart.totals ?? {}) as Record<string, unknown>;
|
|
341
|
+
const discountRaw = (summary.discount ?? summary.discountAmount) as { formattedAmount?: string } | string | undefined;
|
|
342
|
+
const applied = couponDetails(cart);
|
|
343
|
+
let discount = formattedDiscount(typeof discountRaw === "string" ? discountRaw : discountRaw?.formattedAmount ?? "");
|
|
344
|
+
if (applied.code && !discount) {
|
|
345
|
+
const calculated = calculatedCart ?? await calculateCurrentCart().catch(() => null);
|
|
346
|
+
discount = calculatedDiscount(calculated, String(rawCart.currency ?? rawCart.paymentCurrency ?? "")) || (state.appliedCode === applied.code ? state.discount : "");
|
|
347
|
+
}
|
|
212
348
|
return derive({
|
|
213
349
|
...state,
|
|
214
350
|
status: "ready",
|
|
215
351
|
items,
|
|
216
352
|
subtotal: cart.subtotal?.formattedAmount ?? "",
|
|
217
353
|
commerceSubtotal: cart.subtotal?.formattedAmount ?? "",
|
|
354
|
+
discount,
|
|
355
|
+
appliedCode: applied.code,
|
|
356
|
+
couponId: applied.id,
|
|
218
357
|
emptyText: items.length ? "" : copyParam("cart.emptyText", "Your cart is empty."),
|
|
358
|
+
...(items.length ? {} : { promoCode: "", appliedCode: "", couponId: "", discount: "", promoError: "" }),
|
|
219
359
|
checkoutHint,
|
|
360
|
+
showPaidToast: false,
|
|
220
361
|
});
|
|
221
362
|
}
|
|
222
363
|
|
|
364
|
+
function formattedDiscount(value: string): string {
|
|
365
|
+
if (!value || !/[1-9]/.test(value)) return "";
|
|
366
|
+
return /^[−-]/.test(value.trim()) ? value : `−${value}`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function calculatedDiscount(result: unknown, currency: string): string {
|
|
370
|
+
const discount = (result as { summary?: { priceSummary?: { discount?: { amount?: string; convertedAmount?: string } } } } | null)?.summary?.priceSummary?.discount;
|
|
371
|
+
const amount = Number(discount?.amount);
|
|
372
|
+
if (!Number.isFinite(amount) || amount === 0 || !currency) return "";
|
|
373
|
+
return formattedDiscount(formatCurrency(Math.abs(amount), currency, "en"));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function invalidCouponFailure(error: unknown): boolean {
|
|
377
|
+
const status = Number((error as { status?: unknown; statusCode?: unknown } | null)?.status ?? (error as { statusCode?: unknown } | null)?.statusCode);
|
|
378
|
+
const message = String((error as Error | null)?.message ?? error ?? "");
|
|
379
|
+
return status === 400 || status === 422 || /\bHTTP (?:400|422)\b|INVALID_COUPON|COUPON_(?:NOT_FOUND|EXPIRED)/i.test(message);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function couponDetails(result: unknown): { id: string; code: string } {
|
|
383
|
+
const envelope = result as { cart?: Record<string, unknown> } | null;
|
|
384
|
+
const cart = envelope?.cart ?? result as Record<string, unknown> | null;
|
|
385
|
+
const discounts = cart?.appliedDiscounts as Array<{ coupon?: { id?: string; code?: string }; id?: string; code?: string }> | { discounts?: Array<{ coupon?: { id?: string; code?: string }; id?: string; code?: string }> } | undefined;
|
|
386
|
+
const coupon = (Array.isArray(discounts) ? discounts : discounts?.discounts)?.find(entry => entry.coupon?.code || entry.code);
|
|
387
|
+
return { id: coupon?.coupon?.id ?? coupon?.id ?? "", code: coupon?.coupon?.code ?? coupon?.code ?? "" };
|
|
388
|
+
}
|
|
389
|
+
|
|
223
390
|
/** The state slice a SETTLED order leaves behind: the order consumed the cart, so the
|
|
224
391
|
* lines AND the subtotal go with it (`refresh` and the error path both clear the pair —
|
|
225
392
|
* leaving a stale subtotal in state is the class of defect #864 fixed on the render side).
|
|
@@ -233,33 +400,41 @@ async function refresh(state: State, checkoutHint = ""): Promise<State> {
|
|
|
233
400
|
* the subtotal and emptyText resets were missing. It survived because nothing typechecks
|
|
234
401
|
* Tier-5 workers: esbuild erases types, and no tsconfig includes tier5-bundles-src.
|
|
235
402
|
*/
|
|
236
|
-
function cartConsumedByOrder(): Pick<State, "items" | "subtotal" | "emptyText" | "checkoutHint"> {
|
|
403
|
+
function cartConsumedByOrder(): Pick<State, "items" | "subtotal" | "emptyText" | "checkoutHint" | "showPaidToast" | "promoCode" | "appliedCode" | "couponId" | "discount"> {
|
|
237
404
|
return {
|
|
238
405
|
items: [],
|
|
239
406
|
subtotal: "",
|
|
240
407
|
emptyText: "",
|
|
241
408
|
checkoutHint: copyParam("cart.checkoutPaidMessage", "Order placed. Thanks!"),
|
|
409
|
+
showPaidToast: true,
|
|
410
|
+
promoCode: "", appliedCode: "", couponId: "", discount: "",
|
|
242
411
|
};
|
|
243
412
|
}
|
|
244
413
|
|
|
245
414
|
let cartPricing: IapCommerce;
|
|
246
415
|
const defineService = withIapPresentation<State>({
|
|
247
416
|
onCreate: commerce => { cartPricing = commerce; },
|
|
248
|
-
prefix: "cart", transientKeys: ["items", "subtotal"], contentActions: ["load", "remove", "increment", "decrement"],
|
|
417
|
+
prefix: "cart", transientKeys: ["items", "subtotal", "promoBusy"], contentActions: ["load", "remove", "increment", "decrement"],
|
|
249
418
|
items: state => state.items.flatMap(row => row.catalogReference?.catalogItemId ? [{ reference: { kind: "storesProduct", id: row.catalogReference.catalogItemId }, commerceDisplayPrice: row.commerceTotal ?? row.total }] : []),
|
|
250
419
|
present: (state, price, allowed) => ({ ...state,
|
|
251
420
|
items: state.items.map(row => ({ ...row, total: row.catalogReference?.catalogItemId ? price(row.catalogReference.catalogItemId, row.commerceTotal ?? row.total) : row.total })),
|
|
252
421
|
subtotal: state.items.length > 0 && state.items.every(row => row.catalogReference?.catalogItemId && allowed(row.catalogReference.catalogItemId)) ? state.commerceSubtotal ?? state.subtotal : "" }),
|
|
253
422
|
});
|
|
254
423
|
|
|
424
|
+
let promoInFlight = false;
|
|
255
425
|
const svc = defineService<State, {
|
|
256
426
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
427
|
+
setPromo: (state: State, arg?: unknown) => State;
|
|
428
|
+
beginPromo: (state: State) => State;
|
|
429
|
+
applyPromo: (state: State) => Promise<State>;
|
|
430
|
+
removePromo: (state: State) => Promise<State>;
|
|
257
431
|
remove: (state: State, arg?: unknown) => Promise<State>;
|
|
432
|
+
save: (state: State, arg?: unknown) => State;
|
|
258
433
|
increment: (state: State, arg?: unknown) => Promise<State>;
|
|
259
434
|
decrement: (state: State, arg?: unknown) => Promise<State>;
|
|
260
435
|
checkout: (state: State) => Promise<State>;
|
|
261
436
|
}>("himiStoreCart", {
|
|
262
|
-
state: derive({ status: "loading", items: [], subtotal: "", emptyText: "", checkoutHint: "", isLoading: true, notLoading: false, isErrored: false, notErrored: true, hideContent: true, noEmpty: true, noItems: true }),
|
|
437
|
+
state: derive({ status: "loading", errorKind: "", items: [], subtotal: "", subtotalLabel: "", emptyText: "", checkoutHint: "", showPaidToast: false, noPaidToast: true, itemCount: 0, promoCode: "", appliedCode: "", appliedLabel: "", couponId: "", promoError: "", promoBusy: false, noPromoError: true, hasCoupon: false, discount: "", noDiscount: true, noCheckoutHint: true, noCoupon: true, isLoading: true, notLoading: false, isErrored: false, notErrored: true, noOfflineError: true, noServerError: true, hideContent: true, noEmpty: true, noItems: true }),
|
|
263
438
|
actions: {
|
|
264
439
|
load: async (state, arg) => {
|
|
265
440
|
stashCopy(arg);
|
|
@@ -271,11 +446,61 @@ const svc = defineService<State, {
|
|
|
271
446
|
// 2) Revalidate from Wix and refresh the cache.
|
|
272
447
|
try {
|
|
273
448
|
return await refresh(state);
|
|
274
|
-
} catch {
|
|
449
|
+
} catch (error) {
|
|
275
450
|
// Offline / fetch failure: keep the cached items instead of a blank error state.
|
|
276
451
|
return cached.length
|
|
277
452
|
? base
|
|
278
|
-
: derive({ ...state, status: "error", items: [], subtotal: "", emptyText: copyParam("cart.loadErrorText", "Cart could not load."), checkoutHint: "" });
|
|
453
|
+
: derive({ ...state, status: "error", errorKind: isOfflineFailure(error) ? "offline" : "server", items: [], subtotal: "", emptyText: copyParam("cart.loadErrorText", "Cart could not load."), checkoutHint: "" });
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
setPromo: (state, arg) => derive({ ...state, promoCode: (typeof arg === "string" ? arg : String((arg as { value?: unknown } | undefined)?.value ?? "")).slice(0, 100), promoError: "" }),
|
|
457
|
+
beginPromo: state => derive({ ...state, promoBusy: true, promoError: "" }),
|
|
458
|
+
applyPromo: async (state): Promise<State> => {
|
|
459
|
+
const code = state.promoCode.trim();
|
|
460
|
+
if (promoInFlight) return svc.getState() as State;
|
|
461
|
+
if (!code) return derive({ ...state, promoError: copyParam("cart.promoEmpty", "Enter a promo code.") });
|
|
462
|
+
promoInFlight = true;
|
|
463
|
+
let added = false;
|
|
464
|
+
try {
|
|
465
|
+
await svc.beginPromo();
|
|
466
|
+
state = svc.getState() as State;
|
|
467
|
+
await addCouponToCurrentCart(code);
|
|
468
|
+
added = true;
|
|
469
|
+
const calculated = await calculateCurrentCart().catch(() => null);
|
|
470
|
+
const refreshed = await refresh(state, "", undefined, calculated);
|
|
471
|
+
return refreshed.appliedCode
|
|
472
|
+
? derive({ ...refreshed, promoBusy: false, promoCode: "", promoError: "" })
|
|
473
|
+
: derive({ ...refreshed, promoBusy: false, promoCode: state.promoCode, promoError: copyParam("cart.promoInvalid", "This promo code is not valid.") });
|
|
474
|
+
} catch (error) {
|
|
475
|
+
const mayHaveLanded = added || writeMayHaveLanded(error);
|
|
476
|
+
const refreshed = mayHaveLanded ? await refresh(state).catch(() => null) : null;
|
|
477
|
+
if (refreshed?.appliedCode === code) return derive({ ...refreshed, promoBusy: false, promoCode: "", promoError: "" });
|
|
478
|
+
return derive({ ...state, promoBusy: false, promoError: mayHaveLanded
|
|
479
|
+
? copyParam("cart.promoUnconfirmed", "Could not confirm the promo code. Refresh your cart.")
|
|
480
|
+
: isOfflineFailure(error)
|
|
481
|
+
? copyParam("cart.promoOffline", "Connect to the internet to use a promo code.")
|
|
482
|
+
: invalidCouponFailure(error)
|
|
483
|
+
? copyParam("cart.promoInvalid", "This promo code is not valid.")
|
|
484
|
+
: copyParam("cart.promoUnavailable", "Could not apply the promo code. Try again.") });
|
|
485
|
+
} finally {
|
|
486
|
+
promoInFlight = false;
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
removePromo: async state => {
|
|
490
|
+
if (!state.couponId) return derive({ ...state, appliedCode: "", discount: "" });
|
|
491
|
+
let removed = false;
|
|
492
|
+
try {
|
|
493
|
+
await removeCouponFromCurrentCart(state.couponId);
|
|
494
|
+
removed = true;
|
|
495
|
+
await calculateCurrentCart().catch(() => null);
|
|
496
|
+
return derive({ ...(await refresh(state)), appliedCode: "", couponId: "", promoError: "" });
|
|
497
|
+
} catch (error) {
|
|
498
|
+
const mayHaveLanded = removed || writeMayHaveLanded(error);
|
|
499
|
+
const refreshed = mayHaveLanded ? await refresh(state).catch(() => null) : null;
|
|
500
|
+
if (refreshed && !refreshed.appliedCode) return derive({ ...refreshed, promoError: "" });
|
|
501
|
+
return derive({ ...state, promoError: mayHaveLanded
|
|
502
|
+
? copyParam("cart.promoRemoveUnconfirmed", "Could not confirm promo removal. Refresh your cart.")
|
|
503
|
+
: copyParam("cart.promoRemoveFailed", "Could not remove the promo code.") });
|
|
279
504
|
}
|
|
280
505
|
},
|
|
281
506
|
remove: async (state, arg) => {
|
|
@@ -283,14 +508,24 @@ const svc = defineService<State, {
|
|
|
283
508
|
if (!id) return state;
|
|
284
509
|
return removeLine(state, id);
|
|
285
510
|
},
|
|
511
|
+
save: (state, arg) => {
|
|
512
|
+
const id = itemId(arg);
|
|
513
|
+
const item = state.items.find(candidate => candidate.id === id);
|
|
514
|
+
const productId = item?.catalogReference?.catalogItemId ?? item?.productId;
|
|
515
|
+
if (!item || !productId) return state;
|
|
516
|
+
try {
|
|
517
|
+
const saved = toggleSavedProduct({ id: productId, name: item.name, price: item.unitPrice, wasPrice: item.wasPrice, image: item.image, swatches: item.swatches });
|
|
518
|
+
return derive({ ...state, items: state.items.map(candidate => (candidate.catalogReference?.catalogItemId ?? candidate.productId) === productId ? { ...candidate, saved, notSaved: !saved } : candidate) });
|
|
519
|
+
} catch { return derive({ ...state, checkoutHint: copyParam("cart.saveError", "Could not save this item.") }); }
|
|
520
|
+
},
|
|
286
521
|
increment: async (state, arg) => {
|
|
287
522
|
const id = itemId(arg);
|
|
288
523
|
const line = state.items.find((candidate) => candidate.id === id);
|
|
289
524
|
if (!line) return state;
|
|
290
525
|
try {
|
|
291
526
|
const cartId = (await getCurrentCart()).id || "";
|
|
292
|
-
await updateLineItemQuantity(cartId, id, line.quantity + 1);
|
|
293
|
-
return await refresh(state);
|
|
527
|
+
const updated = await updateLineItemQuantity(cartId, id, line.quantity + 1);
|
|
528
|
+
return await refresh(state, "", updated.cart);
|
|
294
529
|
} catch {
|
|
295
530
|
return derive({ ...state, checkoutHint: copyParam("cart.quantityFailedMessage", "Could not update the quantity.") });
|
|
296
531
|
}
|
|
@@ -303,8 +538,8 @@ const svc = defineService<State, {
|
|
|
303
538
|
if (line.quantity <= 1) return removeLine(state, id);
|
|
304
539
|
try {
|
|
305
540
|
const cartId = (await getCurrentCart()).id || "";
|
|
306
|
-
await updateLineItemQuantity(cartId, id, line.quantity - 1);
|
|
307
|
-
return await refresh(state);
|
|
541
|
+
const updated = await updateLineItemQuantity(cartId, id, line.quantity - 1);
|
|
542
|
+
return await refresh(state, "", updated.cart);
|
|
308
543
|
} catch {
|
|
309
544
|
return derive({ ...state, checkoutHint: copyParam("cart.quantityFailedMessage", "Could not update the quantity.") });
|
|
310
545
|
}
|
|
@@ -349,6 +584,7 @@ const svc = defineService<State, {
|
|
|
349
584
|
const order = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
350
585
|
const snapshotId = order.paymentGatewayOrderId ?? "";
|
|
351
586
|
if (!snapshotId && !hosted) {
|
|
587
|
+
await writeCachedItems([]);
|
|
352
588
|
return derive({ ...state, ...cartConsumedByOrder() });
|
|
353
589
|
}
|
|
354
590
|
|
|
@@ -377,6 +613,7 @@ const svc = defineService<State, {
|
|
|
377
613
|
});
|
|
378
614
|
if (pay.kind === "paid") {
|
|
379
615
|
// Settled in place — the cart is consumed by the order.
|
|
616
|
+
await writeCachedItems([]);
|
|
380
617
|
return derive({ ...state, ...cartConsumedByOrder() });
|
|
381
618
|
}
|
|
382
619
|
if (pay.kind === "handoff") {
|