@base44/app-plugin-commerce 0.1.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/LICENSE +21 -0
- package/README.md +117 -0
- package/base44/agents/commerce/StoreAdmin.jsonc +64 -0
- package/base44/entities/commerce.Cart.jsonc +73 -0
- package/base44/entities/commerce.Coupon.jsonc +113 -0
- package/base44/entities/commerce.Customer.jsonc +96 -0
- package/base44/entities/commerce.DownloadPermission.jsonc +54 -0
- package/base44/entities/commerce.EmailLog.jsonc +43 -0
- package/base44/entities/commerce.Order.jsonc +287 -0
- package/base44/entities/commerce.OrderNote.jsonc +31 -0
- package/base44/entities/commerce.OrderRefund.jsonc +64 -0
- package/base44/entities/commerce.PaymentGateway.jsonc +48 -0
- package/base44/entities/commerce.Product.jsonc +291 -0
- package/base44/entities/commerce.ProductAttribute.jsonc +39 -0
- package/base44/entities/commerce.ProductAttributeTerm.jsonc +38 -0
- package/base44/entities/commerce.ProductCategory.jsonc +51 -0
- package/base44/entities/commerce.ProductReview.jsonc +48 -0
- package/base44/entities/commerce.ProductTag.jsonc +30 -0
- package/base44/entities/commerce.ProductVariation.jsonc +167 -0
- package/base44/entities/commerce.ShippingClass.jsonc +30 -0
- package/base44/entities/commerce.ShippingZone.jsonc +41 -0
- package/base44/entities/commerce.ShippingZoneMethod.jsonc +84 -0
- package/base44/entities/commerce.StoreSettings.jsonc +23 -0
- package/base44/entities/commerce.TaxClass.jsonc +23 -0
- package/base44/entities/commerce.TaxRate.jsonc +68 -0
- package/base44/entities/commerce.Webhook.jsonc +57 -0
- package/base44/entities/commerce.WebhookDelivery.jsonc +45 -0
- package/base44/functions/commerce/admin-coupons/entry.ts +100 -0
- package/base44/functions/commerce/admin-customers/entry.ts +141 -0
- package/base44/functions/commerce/admin-orders/entry.ts +396 -0
- package/base44/functions/commerce/admin-orders/helpers.ts +246 -0
- package/base44/functions/commerce/admin-products/entry.ts +506 -0
- package/base44/functions/commerce/admin-refunds/entry.ts +158 -0
- package/base44/functions/commerce/admin-reports/entry.ts +283 -0
- package/base44/functions/commerce/admin-reviews/entry.ts +66 -0
- package/base44/functions/commerce/admin-tools/entry.ts +261 -0
- package/base44/functions/commerce/admin-webhooks/entry.ts +52 -0
- package/base44/functions/commerce/payment-webhook/entry.ts +135 -0
- package/base44/functions/commerce/payments/entry.ts +238 -0
- package/base44/functions/commerce/seed-store/defaults.ts +162 -0
- package/base44/functions/commerce/seed-store/entry.ts +310 -0
- package/base44/functions/commerce/seed-store/sample-data.ts +349 -0
- package/base44/functions/commerce/storefront-account/entry.ts +207 -0
- package/base44/functions/commerce/storefront-cart/cart-pricing.ts +258 -0
- package/base44/functions/commerce/storefront-cart/entry.ts +283 -0
- package/base44/functions/commerce/storefront-catalog/entry.ts +459 -0
- package/base44/functions/commerce/storefront-checkout/cart-pricing.ts +258 -0
- package/base44/functions/commerce/storefront-checkout/entry.ts +485 -0
- package/base44/shared/commerce/auth.ts +60 -0
- package/base44/shared/commerce/coupons.ts +257 -0
- package/base44/shared/commerce/data/continents.ts +75 -0
- package/base44/shared/commerce/data/countries.ts +307 -0
- package/base44/shared/commerce/data/currencies.ts +46 -0
- package/base44/shared/commerce/email-templates.ts +240 -0
- package/base44/shared/commerce/emails.ts +225 -0
- package/base44/shared/commerce/money.ts +66 -0
- package/base44/shared/commerce/orders.ts +251 -0
- package/base44/shared/commerce/payments.ts +495 -0
- package/base44/shared/commerce/reviews.ts +36 -0
- package/base44/shared/commerce/scan.ts +57 -0
- package/base44/shared/commerce/sequence.ts +35 -0
- package/base44/shared/commerce/settings.ts +57 -0
- package/base44/shared/commerce/shipping.ts +215 -0
- package/base44/shared/commerce/stock.ts +227 -0
- package/base44/shared/commerce/stripe.ts +463 -0
- package/base44/shared/commerce/tax.ts +136 -0
- package/base44/shared/commerce/totals.ts +314 -0
- package/base44/shared/commerce/webhooks.ts +116 -0
- package/package.json +37 -0
- package/scripts/install.js +156 -0
- package/skills/commerce/SKILL.md +62 -0
- package/skills/commerce/docs/api-admin.md +186 -0
- package/skills/commerce/docs/api-storefront.md +408 -0
- package/skills/commerce/installation-guidelines.md +91 -0
- package/skills/commerce/post-installation.md +157 -0
- package/skills/commerce/references/emails.md +13 -0
- package/skills/commerce/references/guest-access-security.md +18 -0
- package/skills/commerce/references/limits-and-performance.md +16 -0
- package/skills/commerce/references/media-and-downloads.md +4 -0
- package/skills/commerce/references/online-payments.md +201 -0
- package/skills/commerce/references/product-render.md +87 -0
- package/skills/commerce/references/scheduled-work.md +19 -0
- package/skills/commerce/references/storefront-product-page.md +83 -0
- package/skills/commerce/references/webhooks.md +8 -0
- package/src/commerce/admin/README.md +107 -0
- package/src/commerce/admin/bot/Markdown.jsx +138 -0
- package/src/commerce/admin/bot/StoreAdminBot.jsx +249 -0
- package/src/commerce/admin/bot/pipe-tables.js +116 -0
- package/src/commerce/admin/components/AddressForm.jsx +78 -0
- package/src/commerce/admin/components/ConfirmDialog.jsx +52 -0
- package/src/commerce/admin/components/CountrySelect.jsx +81 -0
- package/src/commerce/admin/components/DataTable.jsx +192 -0
- package/src/commerce/admin/components/DateRangePicker.jsx +91 -0
- package/src/commerce/admin/components/EmptyState.jsx +17 -0
- package/src/commerce/admin/components/MediaUploader.jsx +116 -0
- package/src/commerce/admin/components/MetaDataEditor.jsx +45 -0
- package/src/commerce/admin/components/MoneyInput.jsx +50 -0
- package/src/commerce/admin/components/PageHeader.jsx +29 -0
- package/src/commerce/admin/components/RichTextarea.jsx +21 -0
- package/src/commerce/admin/components/SearchSelect.jsx +142 -0
- package/src/commerce/admin/components/StatusBadge.jsx +17 -0
- package/src/commerce/admin/context/BasePathContext.jsx +26 -0
- package/src/commerce/admin/context/SettingsContext.jsx +207 -0
- package/src/commerce/admin/hooks/useAsync.js +46 -0
- package/src/commerce/admin/hooks/useDebounce.js +11 -0
- package/src/commerce/admin/hooks/useMoney.js +52 -0
- package/src/commerce/admin/hooks/usePagedList.js +83 -0
- package/src/commerce/admin/hooks/usePaymentProvider.js +27 -0
- package/src/commerce/admin/hooks/useRealtime.js +129 -0
- package/src/commerce/admin/index.jsx +34 -0
- package/src/commerce/admin/layout/AccessDenied.jsx +54 -0
- package/src/commerce/admin/layout/AdminLayout.jsx +33 -0
- package/src/commerce/admin/layout/AuthGuard.jsx +84 -0
- package/src/commerce/admin/layout/Sidebar.jsx +130 -0
- package/src/commerce/admin/layout/Topbar.jsx +94 -0
- package/src/commerce/admin/lib/api.js +55 -0
- package/src/commerce/admin/lib/constants.js +157 -0
- package/src/commerce/admin/lib/format.js +27 -0
- package/src/commerce/admin/lib/geo-data.js +125 -0
- package/src/commerce/admin/lib/order-utils.js +147 -0
- package/src/commerce/admin/lib/paths.js +35 -0
- package/src/commerce/admin/lib/product-utils.js +55 -0
- package/src/commerce/admin/pages/Dashboard.jsx +245 -0
- package/src/commerce/admin/pages/coupons/CouponEditor.jsx +565 -0
- package/src/commerce/admin/pages/coupons/CouponsList.jsx +172 -0
- package/src/commerce/admin/pages/customers/CustomerEditor.jsx +318 -0
- package/src/commerce/admin/pages/customers/CustomersList.jsx +169 -0
- package/src/commerce/admin/pages/orders/OrderEditor.jsx +952 -0
- package/src/commerce/admin/pages/orders/OrdersList.jsx +227 -0
- package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +149 -0
- package/src/commerce/admin/pages/orders/components/DownloadPermissionsPanel.jsx +119 -0
- package/src/commerce/admin/pages/orders/components/LineItemsTable.jsx +208 -0
- package/src/commerce/admin/pages/orders/components/OrderNotesPanel.jsx +123 -0
- package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +199 -0
- package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +239 -0
- package/src/commerce/admin/pages/orders/components/TotalsBox.jsx +52 -0
- package/src/commerce/admin/pages/products/AttributeTerms.jsx +180 -0
- package/src/commerce/admin/pages/products/Attributes.jsx +183 -0
- package/src/commerce/admin/pages/products/Categories.jsx +236 -0
- package/src/commerce/admin/pages/products/ProductEditor.jsx +267 -0
- package/src/commerce/admin/pages/products/ProductsList.jsx +391 -0
- package/src/commerce/admin/pages/products/Reviews.jsx +255 -0
- package/src/commerce/admin/pages/products/Tags.jsx +150 -0
- package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +132 -0
- package/src/commerce/admin/pages/products/components/PublishBox.jsx +101 -0
- package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +243 -0
- package/src/commerce/admin/pages/products/components/tabs/AdvancedTab.jsx +48 -0
- package/src/commerce/admin/pages/products/components/tabs/AttributesTab.jsx +208 -0
- package/src/commerce/admin/pages/products/components/tabs/DownloadsTab.jsx +91 -0
- package/src/commerce/admin/pages/products/components/tabs/ExternalTab.jsx +41 -0
- package/src/commerce/admin/pages/products/components/tabs/GeneralTab.jsx +103 -0
- package/src/commerce/admin/pages/products/components/tabs/InventoryTab.jsx +93 -0
- package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +102 -0
- package/src/commerce/admin/pages/products/components/tabs/ShippingTab.jsx +86 -0
- package/src/commerce/admin/pages/products/components/tabs/VariationsTab.jsx +377 -0
- package/src/commerce/admin/pages/reports/Reports.jsx +416 -0
- package/src/commerce/admin/pages/settings/EmailsSettings.jsx +240 -0
- package/src/commerce/admin/pages/settings/GeneralSettings.jsx +232 -0
- package/src/commerce/admin/pages/settings/InventorySettings.jsx +146 -0
- package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +260 -0
- package/src/commerce/admin/pages/settings/ProductsSettings.jsx +118 -0
- package/src/commerce/admin/pages/settings/SettingsLayout.jsx +53 -0
- package/src/commerce/admin/pages/settings/ShippingSettings.jsx +304 -0
- package/src/commerce/admin/pages/settings/ShippingZoneEditor.jsx +514 -0
- package/src/commerce/admin/pages/settings/TaxRatesTable.jsx +231 -0
- package/src/commerce/admin/pages/settings/TaxSettings.jsx +281 -0
- package/src/commerce/admin/pages/settings/useGroupForm.jsx +76 -0
- package/src/commerce/admin/pages/status/WebhookEditor.jsx +296 -0
- package/src/commerce/admin/pages/status/Webhooks.jsx +53 -0
- package/src/commerce/admin/routes.jsx +151 -0
- package/src/commerce/utils/index.js +19 -0
- package/src/commerce/utils/shipping-promos.js +99 -0
- package/src/commerce/utils/variants.js +411 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cart resolution + pricing helpers for storefront functions.
|
|
3
|
+
* NOTE: duplicated in commerce/storefront-cart/ and commerce/storefront-checkout/ — keep in sync.
|
|
4
|
+
* (Base44 functions can only share code via base44/shared/commerce/, which this
|
|
5
|
+
* template treats as engine-only; per-function helpers live beside entry.ts.)
|
|
6
|
+
*/
|
|
7
|
+
import { HttpError } from "../../../shared/commerce/auth.ts";
|
|
8
|
+
import { getSettings } from "../../../shared/commerce/settings.ts";
|
|
9
|
+
import { calculateTotals } from "../../../shared/commerce/totals.ts";
|
|
10
|
+
import { validateCoupon } from "../../../shared/commerce/coupons.ts";
|
|
11
|
+
import { checkPurchasable } from "../../../shared/commerce/stock.ts";
|
|
12
|
+
import { resolveShippingSelection } from "../../../shared/commerce/shipping.ts";
|
|
13
|
+
import { getSetting } from "../../../shared/commerce/settings.ts";
|
|
14
|
+
import { round2 } from "../../../shared/commerce/money.ts";
|
|
15
|
+
import { scanAll } from "../../../shared/commerce/scan.ts";
|
|
16
|
+
|
|
17
|
+
export const CART_TTL_MS = 48 * 60 * 60 * 1000; // 48h, refreshed on touch
|
|
18
|
+
|
|
19
|
+
export function cartExpiry(): string {
|
|
20
|
+
return new Date(Date.now() + CART_TTL_MS).toISOString();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Load an active cart by its bearer token. Throws 404 (not found / expired). */
|
|
24
|
+
export async function loadCart(sr: any, cartToken: string | undefined): Promise<any> {
|
|
25
|
+
if (!cartToken) throw new HttpError(400, "cart_token is required.", "cart_token_required");
|
|
26
|
+
const cart = (await sr.entities["commerce.Cart"].filter({ cart_token: cartToken }, undefined, 1))?.[0];
|
|
27
|
+
if (!cart || cart.status !== "active") {
|
|
28
|
+
throw new HttpError(404, "Cart not found.", "cart_not_found");
|
|
29
|
+
}
|
|
30
|
+
if (cart.expires_at && new Date(cart.expires_at).getTime() < Date.now()) {
|
|
31
|
+
try { await sr.entities["commerce.Cart"].update(cart.id, { status: "abandoned" }); } catch { /* best-effort */ }
|
|
32
|
+
throw new HttpError(404, "Cart has expired.", "cart_expired");
|
|
33
|
+
}
|
|
34
|
+
return cart;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PricingData {
|
|
38
|
+
settings: Record<string, any>;
|
|
39
|
+
taxRates: any[];
|
|
40
|
+
zones: any[];
|
|
41
|
+
zoneMethods: any[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One round of catalog-config reads shared by every priced response. */
|
|
45
|
+
export async function loadPricingData(sr: any): Promise<PricingData> {
|
|
46
|
+
const [settings, taxRates, zones, zoneMethods] = await Promise.all([
|
|
47
|
+
getSettings(sr),
|
|
48
|
+
scanAll(sr.entities["commerce.TaxRate"], {}, "menu_order", 2000),
|
|
49
|
+
scanAll(sr.entities["commerce.ShippingZone"], {}, "order", 500),
|
|
50
|
+
scanAll(sr.entities["commerce.ShippingZoneMethod"], {}, "order", 1000),
|
|
51
|
+
]);
|
|
52
|
+
return { settings, taxRates, zones, zoneMethods };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ResolvedItem {
|
|
56
|
+
item: any; // the stored cart item {item_key, product_id, variation_id, quantity, attributes}
|
|
57
|
+
product: any;
|
|
58
|
+
variation?: any;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve stored cart items to live products/variations. Items whose product
|
|
63
|
+
* vanished or was unpublished are reported in `removed` (they are dropped from
|
|
64
|
+
* the cart and the shopper is told).
|
|
65
|
+
*/
|
|
66
|
+
export async function resolveItems(sr: any, cart: any): Promise<{ resolved: ResolvedItem[]; removed: any[] }> {
|
|
67
|
+
const resolved: ResolvedItem[] = [];
|
|
68
|
+
const removed: any[] = [];
|
|
69
|
+
for (const item of cart.items || []) {
|
|
70
|
+
let product: any = null;
|
|
71
|
+
let variation: any = undefined;
|
|
72
|
+
try { product = item.product_id ? await sr.entities["commerce.Product"].get(item.product_id) : null; } catch { product = null; }
|
|
73
|
+
if (!product || product.status !== "publish") {
|
|
74
|
+
removed.push({ item_key: item.item_key, product_id: item.product_id, reason: "This product is no longer available.", code: "unavailable" });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (item.variation_id) {
|
|
78
|
+
try { variation = await sr.entities["commerce.ProductVariation"].get(item.variation_id); } catch { variation = undefined; }
|
|
79
|
+
if (!variation || (variation.status && variation.status !== "publish")) {
|
|
80
|
+
removed.push({ item_key: item.item_key, product_id: item.product_id, reason: "This product option is no longer available.", code: "unavailable" });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
resolved.push({ item, product, variation });
|
|
85
|
+
}
|
|
86
|
+
return { resolved, removed };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Lines in the shape coupons.ts validateCoupon()/eligibleLines() expect. */
|
|
90
|
+
export function couponCtxLines(resolved: ResolvedItem[]): any[] {
|
|
91
|
+
return resolved.map(({ item, product, variation }) => {
|
|
92
|
+
const src = variation ?? product;
|
|
93
|
+
const price = Number(src?.price ?? 0);
|
|
94
|
+
return {
|
|
95
|
+
line_id: item.item_key,
|
|
96
|
+
product_id: item.product_id,
|
|
97
|
+
variation_id: item.variation_id ?? "",
|
|
98
|
+
quantity: item.quantity,
|
|
99
|
+
unit_price: price,
|
|
100
|
+
subtotal: round2(price * item.quantity),
|
|
101
|
+
discount: 0,
|
|
102
|
+
product,
|
|
103
|
+
variation,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function findCoupon(sr: any, code: string): Promise<any | null> {
|
|
109
|
+
if (!code) return null;
|
|
110
|
+
return (await sr.entities["commerce.Coupon"].filter({ code: String(code).toLowerCase().trim() }, undefined, 1))?.[0] ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface PricedCart {
|
|
114
|
+
view: any; // customer-facing cart payload
|
|
115
|
+
totals: any; // full TotalsResult
|
|
116
|
+
resolved: ResolvedItem[];
|
|
117
|
+
removed: any[];
|
|
118
|
+
validCoupons: any[];
|
|
119
|
+
couponNotices: any[];
|
|
120
|
+
pricingData: PricingData;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Produce the priced view of a cart: resolve items, revalidate stored coupons
|
|
125
|
+
* (silently dropping ones that stopped validating, with notices), run the
|
|
126
|
+
* totals engine in preview mode, and enrich items for display.
|
|
127
|
+
* Persists item removals / coupon drops back onto the Cart record.
|
|
128
|
+
*/
|
|
129
|
+
export async function priceCart(sr: any, cart: any, opts: { pricingData?: PricingData; customerEmail?: string } = {}): Promise<PricedCart> {
|
|
130
|
+
const pricingData = opts.pricingData ?? (await loadPricingData(sr));
|
|
131
|
+
const { settings } = pricingData;
|
|
132
|
+
const { resolved, removed } = await resolveItems(sr, cart);
|
|
133
|
+
|
|
134
|
+
const cartPatch: Record<string, any> = {};
|
|
135
|
+
if (removed.length) {
|
|
136
|
+
const removedKeys = new Set(removed.map((r) => r.item_key));
|
|
137
|
+
cart.items = (cart.items || []).filter((i: any) => !removedKeys.has(i.item_key));
|
|
138
|
+
cartPatch.items = cart.items;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// revalidate stored coupons in application order
|
|
142
|
+
const ctxLines = couponCtxLines(resolved);
|
|
143
|
+
const itemsSubtotal = round2(ctxLines.reduce((a, l) => a + l.subtotal, 0));
|
|
144
|
+
const customerEmail = cart.customer_email || opts.customerEmail || "";
|
|
145
|
+
const validCoupons: any[] = [];
|
|
146
|
+
const keptCodes: string[] = [];
|
|
147
|
+
const couponNotices: any[] = [];
|
|
148
|
+
for (const code of cart.coupon_codes || []) {
|
|
149
|
+
const coupon = await findCoupon(sr, code);
|
|
150
|
+
const res = validateCoupon(coupon, {
|
|
151
|
+
lines: ctxLines,
|
|
152
|
+
itemsSubtotal,
|
|
153
|
+
customerEmail,
|
|
154
|
+
appliedCoupons: validCoupons,
|
|
155
|
+
});
|
|
156
|
+
if (res.valid) {
|
|
157
|
+
validCoupons.push(coupon);
|
|
158
|
+
keptCodes.push(code);
|
|
159
|
+
} else {
|
|
160
|
+
couponNotices.push({ code, error: res.error, error_code: res.code });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (keptCodes.length !== (cart.coupon_codes || []).length) {
|
|
164
|
+
cart.coupon_codes = keptCodes;
|
|
165
|
+
cartPatch.coupon_codes = keptCodes;
|
|
166
|
+
}
|
|
167
|
+
if (Object.keys(cartPatch).length) {
|
|
168
|
+
try { await sr.entities["commerce.Cart"].update(cart.id, cartPatch); } catch { /* view still correct */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const priceWith = (chosenShippingMethodId?: string) => calculateTotals({
|
|
172
|
+
items: resolved.map((r) => ({
|
|
173
|
+
product: r.product,
|
|
174
|
+
variation: r.variation,
|
|
175
|
+
quantity: r.item.quantity,
|
|
176
|
+
attributes: r.item.attributes ?? [],
|
|
177
|
+
})),
|
|
178
|
+
coupons: validCoupons,
|
|
179
|
+
shipping_address: cart.shipping_address,
|
|
180
|
+
chosenShippingMethodId: chosenShippingMethodId || undefined,
|
|
181
|
+
settings,
|
|
182
|
+
taxRates: pricingData.taxRates,
|
|
183
|
+
zones: pricingData.zones,
|
|
184
|
+
zoneMethods: pricingData.zoneMethods,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
let totals = priceWith(cart.chosen_shipping_method);
|
|
188
|
+
|
|
189
|
+
// Keep the cart's shipping choice honest: drop one that is no longer offered
|
|
190
|
+
// (the address or the cart changed) and auto-select when there is exactly one
|
|
191
|
+
// option, so a storefront that only sets an address still gets a shipping
|
|
192
|
+
// line instead of silently shipping for free. `shipping_status` on the view
|
|
193
|
+
// tells the UI whether it still has to ask the customer.
|
|
194
|
+
const shippingSelection = resolveShippingSelection({
|
|
195
|
+
needsShipping: resolved.some((r) => !(r.variation?.virtual ?? r.product.virtual)),
|
|
196
|
+
shippingEnabled: getSetting(settings, "shipping", "enable_shipping", true) !== false,
|
|
197
|
+
chosenMethodId: cart.chosen_shipping_method,
|
|
198
|
+
available: totals.available_shipping_methods,
|
|
199
|
+
});
|
|
200
|
+
if (shippingSelection.method_id !== (cart.chosen_shipping_method || "")) {
|
|
201
|
+
cart.chosen_shipping_method = shippingSelection.method_id;
|
|
202
|
+
try {
|
|
203
|
+
await sr.entities["commerce.Cart"].update(cart.id, { chosen_shipping_method: shippingSelection.method_id });
|
|
204
|
+
} catch { /* view still correct */ }
|
|
205
|
+
totals = priceWith(shippingSelection.method_id);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// display items: engine lines align by index with `resolved`
|
|
209
|
+
const items = resolved.map((r, i) => {
|
|
210
|
+
const priced = totals.line_items[i] ?? {};
|
|
211
|
+
const src = r.variation ?? r.product;
|
|
212
|
+
return {
|
|
213
|
+
item_key: r.item.item_key,
|
|
214
|
+
product_id: r.item.product_id,
|
|
215
|
+
variation_id: r.item.variation_id ?? "",
|
|
216
|
+
quantity: r.item.quantity,
|
|
217
|
+
attributes: r.item.attributes ?? [],
|
|
218
|
+
name: r.product.name ?? "",
|
|
219
|
+
sku: priced.sku ?? "",
|
|
220
|
+
image: r.variation?.image?.src || r.product.images?.[0]?.src || "",
|
|
221
|
+
price: priced.price ?? Number(src?.price ?? 0),
|
|
222
|
+
subtotal: priced.subtotal ?? 0,
|
|
223
|
+
total: priced.total ?? 0,
|
|
224
|
+
total_tax: priced.total_tax ?? 0,
|
|
225
|
+
virtual: !!(r.variation?.virtual ?? r.product.virtual),
|
|
226
|
+
sold_individually: !!r.product.sold_individually,
|
|
227
|
+
purchasable: checkPurchasable(r.product, r.variation, r.item.quantity, settings),
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const view = {
|
|
232
|
+
cart_token: cart.cart_token,
|
|
233
|
+
items,
|
|
234
|
+
coupon_codes: cart.coupon_codes ?? [],
|
|
235
|
+
coupons: totals.coupon_lines,
|
|
236
|
+
coupon_notices: couponNotices,
|
|
237
|
+
removed_items: removed,
|
|
238
|
+
shipping_address: cart.shipping_address ?? null,
|
|
239
|
+
chosen_shipping_method: cart.chosen_shipping_method ?? "",
|
|
240
|
+
available_shipping_methods: totals.available_shipping_methods,
|
|
241
|
+
shipping_status: shippingSelection.state,
|
|
242
|
+
totals: {
|
|
243
|
+
subtotal: totals.subtotal,
|
|
244
|
+
discount_total: totals.discount_total,
|
|
245
|
+
discount_tax: totals.discount_tax,
|
|
246
|
+
shipping_total: totals.shipping_total,
|
|
247
|
+
shipping_tax: totals.shipping_tax,
|
|
248
|
+
cart_tax: totals.cart_tax,
|
|
249
|
+
total_tax: totals.total_tax,
|
|
250
|
+
total: totals.total,
|
|
251
|
+
prices_include_tax: totals.prices_include_tax,
|
|
252
|
+
tax_lines: totals.tax_lines,
|
|
253
|
+
},
|
|
254
|
+
expires_at: cart.expires_at ?? null,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return { view, totals, resolved, removed, validCoupons, couponNotices, pricingData };
|
|
258
|
+
}
|