@base44/app-plugin-commerce 0.1.6 → 0.1.8
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/README.md +12 -12
- package/base44/agents/commerce/StoreAdmin.jsonc +2 -2
- package/base44/entities/commerce.Cart.jsonc +1 -1
- package/base44/entities/commerce.Coupon.jsonc +5 -0
- package/base44/entities/commerce.Order.jsonc +6 -7
- package/base44/entities/commerce.OrderRefund.jsonc +1 -1
- package/base44/entities/commerce.PaymentGateway.jsonc +1 -1
- package/base44/entities/commerce.Product.jsonc +6 -16
- package/base44/entities/{commerce.ProductTag.jsonc → commerce.ProductRibbon.jsonc} +2 -2
- package/base44/entities/commerce.ProductVariation.jsonc +1 -9
- package/base44/entities/commerce.ShippingTaxLocation.jsonc +85 -0
- package/base44/entities/commerce.Webhook.jsonc +1 -1
- package/base44/functions/commerce/admin-orders/helpers.ts +7 -13
- package/base44/functions/commerce/admin-products/entry.ts +11 -17
- package/base44/functions/commerce/admin-refunds/entry.ts +10 -9
- package/base44/functions/commerce/admin-reports/entry.ts +3 -3
- package/base44/functions/commerce/admin-tools/entry.ts +9 -36
- package/base44/functions/commerce/payment-webhook/entry.ts +50 -89
- package/base44/functions/commerce/payments/entry.ts +46 -42
- package/base44/functions/commerce/seed-store/defaults.ts +35 -42
- package/base44/functions/commerce/seed-store/entry.ts +68 -46
- package/base44/functions/commerce/seed-store/sample-data.ts +2 -15
- package/base44/functions/commerce/seed-store/seed-catalog.ts +105 -55
- package/base44/functions/commerce/storefront-cart/cart-pricing.ts +6 -11
- package/base44/functions/commerce/storefront-cart/entry.ts +36 -2
- package/base44/functions/commerce/storefront-catalog/entry.ts +55 -72
- package/base44/functions/commerce/storefront-checkout/cart-pricing.ts +6 -11
- package/base44/functions/commerce/storefront-checkout/entry.ts +43 -56
- package/base44/shared/commerce/card-payment.ts +80 -0
- package/base44/shared/commerce/coupons.ts +16 -10
- package/base44/shared/commerce/emails.ts +30 -18
- package/base44/shared/commerce/money.ts +11 -24
- package/base44/shared/commerce/payments.ts +55 -286
- package/base44/shared/commerce/scan.ts +1 -1
- package/base44/shared/commerce/sequence.ts +1 -1
- package/base44/shared/commerce/settings.ts +4 -9
- package/base44/shared/commerce/shipping.ts +65 -133
- package/base44/shared/commerce/tax.ts +48 -96
- package/base44/shared/commerce/totals.ts +77 -91
- package/package.json +1 -1
- package/scripts/install.js +28 -7
- package/skills/commerce/SKILL.md +14 -14
- package/skills/commerce/docs/api-admin.md +23 -26
- package/skills/commerce/docs/api-storefront.md +67 -61
- package/skills/commerce/installation-guidelines.md +8 -8
- package/skills/commerce/post-installation.md +60 -39
- package/skills/commerce/references/admin-product-form.md +15 -12
- package/skills/commerce/references/emails.md +2 -2
- package/skills/commerce/references/guest-access-security.md +2 -2
- package/skills/commerce/references/online-payments.md +24 -166
- package/skills/commerce/references/product-render.md +18 -18
- package/skills/commerce/references/reviews.md +14 -8
- package/skills/commerce/references/storefront-product-page.md +1 -1
- package/src/commerce/admin/README.md +5 -6
- package/src/commerce/admin/bot/Markdown.jsx +1 -1
- package/src/commerce/admin/hooks/useMoney.js +13 -22
- package/src/commerce/admin/layout/AuthGuard.jsx +1 -1
- package/src/commerce/admin/lib/constants.js +2 -29
- package/src/commerce/admin/lib/order-utils.js +1 -1
- package/src/commerce/admin/pages/coupons/CouponEditor.jsx +131 -140
- package/src/commerce/admin/pages/coupons/CouponsList.jsx +14 -7
- package/src/commerce/admin/pages/orders/OrderEditor.jsx +3 -3
- package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +17 -28
- package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +5 -11
- package/src/commerce/admin/pages/products/Categories.jsx +147 -177
- package/src/commerce/admin/pages/products/ProductEditor.jsx +23 -18
- package/src/commerce/admin/pages/products/Reviews.jsx +37 -1
- package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +33 -47
- package/src/commerce/admin/pages/products/components/PublishBox.jsx +13 -34
- package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +29 -29
- package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +14 -44
- package/src/commerce/admin/pages/reports/Reports.jsx +2 -2
- package/src/commerce/admin/pages/settings/EmailsSettings.jsx +101 -68
- package/src/commerce/admin/pages/settings/GeneralSettings.jsx +40 -38
- package/src/commerce/admin/pages/settings/InventorySettings.jsx +1 -41
- package/src/commerce/admin/pages/settings/LocationEditor.jsx +377 -0
- package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +137 -119
- package/src/commerce/admin/pages/settings/SettingsLayout.jsx +2 -4
- package/src/commerce/admin/pages/settings/ShippingTaxSettings.jsx +191 -0
- package/src/commerce/admin/routes.jsx +6 -12
- package/src/commerce/utils/index.js +2 -2
- package/src/commerce/utils/shipping-promos.js +45 -49
- package/src/commerce/utils/variants.js +1 -1
- package/base44/entities/commerce.ShippingClass.jsonc +0 -30
- package/base44/entities/commerce.ShippingZone.jsonc +0 -41
- package/base44/entities/commerce.ShippingZoneMethod.jsonc +0 -84
- package/base44/entities/commerce.TaxClass.jsonc +0 -23
- package/base44/entities/commerce.TaxRate.jsonc +0 -68
- package/base44/shared/commerce/stripe.ts +0 -463
- package/src/commerce/admin/hooks/usePaymentProvider.js +0 -27
- package/src/commerce/admin/pages/settings/ProductsSettings.jsx +0 -118
- package/src/commerce/admin/pages/settings/ShippingSettings.jsx +0 -304
- package/src/commerce/admin/pages/settings/ShippingZoneEditor.jsx +0 -514
- package/src/commerce/admin/pages/settings/TaxRatesTable.jsx +0 -231
- package/src/commerce/admin/pages/settings/TaxSettings.jsx +0 -280
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* Used by: commerce/storefront-cart `totals` (preview), commerce/storefront-checkout
|
|
4
4
|
* `place-order` (authoritative), commerce/admin-orders `create`/`recalculate`.
|
|
5
5
|
*
|
|
6
|
-
* Pipeline
|
|
6
|
+
* Pipeline: resolve lines → coupons → fees → shipping → tax → totals.
|
|
7
7
|
* All internal math is ex-tax; tax-inclusive catalog prices are netted first.
|
|
8
|
+
* Shipping options and tax both come from the ONE matched
|
|
9
|
+
* commerce.ShippingTaxLocation (see shared/commerce/shipping.ts + tax.ts).
|
|
8
10
|
*/
|
|
9
11
|
import { round2, distributeProportionally } from "./money.ts";
|
|
10
|
-
import { applyRates, extractInclusiveTax,
|
|
11
|
-
import {
|
|
12
|
+
import { applyRates, DEFAULT_TAX_GROUP, extractInclusiveTax, shippingTaxFor, sumTax, taxRatesFor } from "./tax.ts";
|
|
13
|
+
import { availableRates, matchLocation } from "./shipping.ts";
|
|
12
14
|
import { applyCoupons } from "./coupons.ts";
|
|
13
15
|
import { getSetting } from "./settings.ts";
|
|
14
16
|
import { uuid } from "./sequence.ts";
|
|
@@ -24,7 +26,7 @@ export interface TotalsItem {
|
|
|
24
26
|
export interface TotalsFee {
|
|
25
27
|
name: string;
|
|
26
28
|
amount: number;
|
|
27
|
-
|
|
29
|
+
tax_group?: string;
|
|
28
30
|
tax_status?: string; // taxable | none
|
|
29
31
|
line_id?: string;
|
|
30
32
|
}
|
|
@@ -35,11 +37,9 @@ export interface TotalsInput {
|
|
|
35
37
|
fees?: TotalsFee[];
|
|
36
38
|
billing?: any;
|
|
37
39
|
shipping_address?: any;
|
|
38
|
-
chosenShippingMethodId?: string; //
|
|
40
|
+
chosenShippingMethodId?: string; // the chosen shipping rate id
|
|
39
41
|
settings: Record<string, any>; // groups object from getSettings()
|
|
40
|
-
|
|
41
|
-
zones?: any[];
|
|
42
|
-
zoneMethods?: any[];
|
|
42
|
+
locations?: any[]; // commerce.ShippingTaxLocation records
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export interface TotalsResult {
|
|
@@ -57,17 +57,16 @@ export interface TotalsResult {
|
|
|
57
57
|
total_tax: number;
|
|
58
58
|
total: number;
|
|
59
59
|
prices_include_tax: boolean;
|
|
60
|
-
available_shipping_methods: Array<{ id: string;
|
|
61
|
-
|
|
60
|
+
available_shipping_methods: Array<{ id: string; title: string; cost: number }>;
|
|
61
|
+
matched_location_id: string | null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
/** Effective tax
|
|
65
|
-
function effectiveTax(product: any, variation?: any): {
|
|
64
|
+
/** Effective tax group/status with variation "parent" status inheritance. */
|
|
65
|
+
function effectiveTax(product: any, variation?: any): { tax_group: string; tax_status: string } {
|
|
66
66
|
const status = variation?.tax_status && variation.tax_status !== "parent"
|
|
67
67
|
? variation.tax_status
|
|
68
68
|
: product.tax_status ?? "taxable";
|
|
69
|
-
|
|
70
|
-
return { tax_class: cls, tax_status: status };
|
|
69
|
+
return { tax_group: product.tax_group || DEFAULT_TAX_GROUP, tax_status: status };
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
function resolveTaxAddress(input: TotalsInput): any {
|
|
@@ -76,18 +75,28 @@ function resolveTaxAddress(input: TotalsInput): any {
|
|
|
76
75
|
return input.shipping_address ?? input.billing ?? {}; // "shipping" default
|
|
77
76
|
}
|
|
78
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the location for an address; with no address, a store with exactly
|
|
80
|
+
* ONE location still resolves to it — its options and taxes are the same for
|
|
81
|
+
* every address, so a cart can price before the customer types anything.
|
|
82
|
+
*/
|
|
83
|
+
function locationFor(locations: any[], addr: any): any | null {
|
|
84
|
+
if (addr?.country) return matchLocation(locations, addr);
|
|
85
|
+
return locations.length === 1 ? locations[0] : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
79
88
|
/** Run the full totals pipeline. Pure aside from reading its inputs. */
|
|
80
89
|
export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
81
90
|
const settings = input.settings ?? {};
|
|
82
91
|
const pricesIncludeTax = !!getSetting(settings, "tax", "prices_include_tax", false);
|
|
83
|
-
const
|
|
84
|
-
const
|
|
92
|
+
const locations = input.locations ?? [];
|
|
93
|
+
const taxLocation = locationFor(locations, resolveTaxAddress(input));
|
|
85
94
|
|
|
86
95
|
// ── 1. resolve lines (ex-tax) ────────────────────────────────────────────
|
|
87
96
|
const lines = (input.items || []).map((it) => {
|
|
88
97
|
const src = it.variation ?? it.product;
|
|
89
|
-
const {
|
|
90
|
-
const rates = tax_status === "taxable" ?
|
|
98
|
+
const { tax_group, tax_status } = effectiveTax(it.product, it.variation);
|
|
99
|
+
const rates = tax_status === "taxable" ? taxRatesFor(taxLocation, tax_group) : [];
|
|
91
100
|
let unitPrice = Number(src.price ?? it.product?.price ?? 0);
|
|
92
101
|
let subtotal = round2(unitPrice * it.quantity);
|
|
93
102
|
if (pricesIncludeTax && rates.length) {
|
|
@@ -103,7 +112,7 @@ export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
|
103
112
|
sku: (it.variation?.sku || it.product?.sku) ?? "",
|
|
104
113
|
quantity: it.quantity,
|
|
105
114
|
unit_price: unitPrice,
|
|
106
|
-
|
|
115
|
+
tax_group,
|
|
107
116
|
tax_status,
|
|
108
117
|
rates,
|
|
109
118
|
subtotal,
|
|
@@ -114,7 +123,6 @@ export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
|
114
123
|
taxes: [] as any[],
|
|
115
124
|
attributes: it.attributes ?? [],
|
|
116
125
|
meta_data: it.meta_data ?? [],
|
|
117
|
-
shipping_class_id: (it.variation?.shipping_class_id || it.product?.shipping_class_id) ?? "",
|
|
118
126
|
virtual: !!(it.variation?.virtual ?? it.product?.virtual),
|
|
119
127
|
product: it.product,
|
|
120
128
|
variation: it.variation,
|
|
@@ -134,7 +142,7 @@ export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
|
134
142
|
const feeLines = (input.fees || []).map((f) => ({
|
|
135
143
|
line_id: f.line_id ?? uuid(),
|
|
136
144
|
name: f.name,
|
|
137
|
-
|
|
145
|
+
tax_group: f.tax_group ?? DEFAULT_TAX_GROUP,
|
|
138
146
|
tax_status: f.tax_status ?? "taxable",
|
|
139
147
|
total: round2(Number(f.amount) || 0),
|
|
140
148
|
total_tax: 0,
|
|
@@ -145,44 +153,41 @@ export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
|
145
153
|
const shippingEnabled = getSetting(settings, "shipping", "enable_shipping", true) !== false;
|
|
146
154
|
const needsShipping = lines.some((l) => !l.virtual);
|
|
147
155
|
const shippingLines: any[] = [];
|
|
148
|
-
let available: Array<{ id: string;
|
|
149
|
-
|
|
156
|
+
let available: Array<{ id: string; title: string; cost: number }> = [];
|
|
157
|
+
const shipLocation = shippingEnabled && needsShipping
|
|
158
|
+
? locationFor(locations, input.shipping_address)
|
|
159
|
+
: null;
|
|
150
160
|
|
|
151
|
-
if (
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
total: chosen.cost,
|
|
172
|
-
total_tax: 0,
|
|
173
|
-
taxes: [] as any[],
|
|
174
|
-
});
|
|
175
|
-
}
|
|
161
|
+
if (shipLocation) {
|
|
162
|
+
const cart = { itemsSubtotal, itemsSubtotalAfterDiscount: itemsAfterDiscount };
|
|
163
|
+
const offers = availableRates(shipLocation, cart, coupons);
|
|
164
|
+
available = offers.map((o) => ({
|
|
165
|
+
id: o.rate.id,
|
|
166
|
+
title: o.rate.name || "Shipping",
|
|
167
|
+
cost: o.cost,
|
|
168
|
+
}));
|
|
169
|
+
if (input.chosenShippingMethodId) {
|
|
170
|
+
const chosen = offers.find((o) => o.rate.id === input.chosenShippingMethodId);
|
|
171
|
+
if (chosen) {
|
|
172
|
+
shippingLines.push({
|
|
173
|
+
line_id: uuid(),
|
|
174
|
+
method_id: "rate",
|
|
175
|
+
instance_id: chosen.rate.id,
|
|
176
|
+
method_title: chosen.rate.name || "Shipping",
|
|
177
|
+
total: chosen.cost,
|
|
178
|
+
total_tax: 0,
|
|
179
|
+
taxes: [] as any[],
|
|
180
|
+
});
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
// ── 5. tax ───────────────────────────────────────────────────────────────
|
|
181
|
-
// aggregated per rate for order-level tax_lines
|
|
182
|
-
const agg = new Map<string, {
|
|
183
|
-
const bump = (
|
|
184
|
-
const key =
|
|
185
|
-
const cur = agg.get(key) ?? {
|
|
186
|
+
// aggregated per rate label for order-level tax_lines
|
|
187
|
+
const agg = new Map<string, { label: string; rate_percent: number; tax_total: number; shipping_tax_total: number }>();
|
|
188
|
+
const bump = (label: string, ratePercent: number, tax: number, shippingTax: number) => {
|
|
189
|
+
const key = `${label}|${ratePercent}`;
|
|
190
|
+
const cur = agg.get(key) ?? { label, rate_percent: ratePercent, tax_total: 0, shipping_tax_total: 0 };
|
|
186
191
|
cur.tax_total = round2(cur.tax_total + tax);
|
|
187
192
|
cur.shipping_tax_total = round2(cur.shipping_tax_total + shippingTax);
|
|
188
193
|
agg.set(key, cur);
|
|
@@ -195,32 +200,28 @@ export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
|
195
200
|
l.subtotal_tax = sumTax(subApplied);
|
|
196
201
|
l.total_tax = sumTax(totApplied);
|
|
197
202
|
l.taxes = totApplied.map((t, i) => ({
|
|
198
|
-
|
|
203
|
+
label: t.rate.name || "Tax",
|
|
199
204
|
total: t.amount,
|
|
200
205
|
subtotal: subApplied[i]?.amount ?? 0,
|
|
201
206
|
}));
|
|
202
|
-
for (const t of totApplied) bump(t.rate, t.amount, 0);
|
|
207
|
+
for (const t of totApplied) bump(t.rate.name || "Tax", t.rate.rate, t.amount, 0);
|
|
203
208
|
}
|
|
204
209
|
for (const fee of feeLines) {
|
|
205
210
|
if (fee.tax_status !== "taxable") continue;
|
|
206
|
-
const rates =
|
|
211
|
+
const rates = taxRatesFor(taxLocation, fee.tax_group);
|
|
207
212
|
const applied = applyRates(fee.total, rates);
|
|
208
213
|
fee.total_tax = sumTax(applied);
|
|
209
|
-
fee.taxes = applied.map((t) => ({
|
|
210
|
-
for (const t of applied) bump(t.rate, t.amount, 0);
|
|
214
|
+
fee.taxes = applied.map((t) => ({ label: t.rate.name || "Tax", total: t.amount }));
|
|
215
|
+
for (const t of applied) bump(t.rate.name || "Tax", t.rate.rate, t.amount, 0);
|
|
211
216
|
}
|
|
212
|
-
if (shippingLines.length) {
|
|
213
|
-
// shipping_tax_class "inherit" = first taxable line's class (intended behavior)
|
|
214
|
-
const cfg = getSetting(settings, "tax", "shipping_tax_class", "inherit");
|
|
215
|
-
const cls = cfg === "inherit"
|
|
216
|
-
? (lines.find((l) => l.tax_status === "taxable")?.tax_class ?? "standard")
|
|
217
|
-
: cfg;
|
|
218
|
-
const rates = matchTaxRates(taxRates, taxAddress, cls).filter((r) => r.shipping !== false);
|
|
217
|
+
if (shippingLines.length && taxLocation) {
|
|
219
218
|
for (const sl of shippingLines) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
sl.
|
|
223
|
-
|
|
219
|
+
const tax = shippingTaxFor(taxLocation, sl.total);
|
|
220
|
+
if (tax <= 0) continue;
|
|
221
|
+
sl.total_tax = tax;
|
|
222
|
+
sl.taxes = [{ label: "Shipping tax", total: tax }];
|
|
223
|
+
const pct = taxLocation.shipping_tax?.type === "fixed" ? 0 : Number(taxLocation.shipping_tax?.value) || 0;
|
|
224
|
+
bump("Shipping tax", pct, 0, tax);
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -243,12 +244,11 @@ export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
|
243
244
|
|
|
244
245
|
const total = round2(itemsSubtotal - discountTotal + feesTotal + shippingTotal + totalTax);
|
|
245
246
|
|
|
246
|
-
const taxLines = [...agg.values()].map(({
|
|
247
|
-
rate_id:
|
|
248
|
-
rate_code:
|
|
249
|
-
label
|
|
250
|
-
rate_percent
|
|
251
|
-
compound: !!rate.compound,
|
|
247
|
+
const taxLines = [...agg.values()].map(({ label, rate_percent, tax_total, shipping_tax_total }) => ({
|
|
248
|
+
rate_id: "",
|
|
249
|
+
rate_code: label.replace(/\s+/g, "-").toUpperCase(),
|
|
250
|
+
label,
|
|
251
|
+
rate_percent,
|
|
252
252
|
tax_total,
|
|
253
253
|
shipping_tax_total,
|
|
254
254
|
}));
|
|
@@ -269,7 +269,7 @@ export function calculateTotals(input: TotalsInput): TotalsResult {
|
|
|
269
269
|
total,
|
|
270
270
|
prices_include_tax: pricesIncludeTax,
|
|
271
271
|
available_shipping_methods: available,
|
|
272
|
-
|
|
272
|
+
matched_location_id: shipLocation?.id ?? taxLocation?.id ?? null,
|
|
273
273
|
};
|
|
274
274
|
}
|
|
275
275
|
|
|
@@ -283,7 +283,7 @@ function stripLine(l: any): any {
|
|
|
283
283
|
sku: l.sku,
|
|
284
284
|
quantity: l.quantity,
|
|
285
285
|
price: l.quantity ? round2(l.total / l.quantity) : 0, // post-discount ex-tax unit price
|
|
286
|
-
|
|
286
|
+
tax_group: l.tax_group,
|
|
287
287
|
subtotal: l.subtotal,
|
|
288
288
|
subtotal_tax: l.subtotal_tax,
|
|
289
289
|
total: l.total,
|
|
@@ -293,17 +293,3 @@ function stripLine(l: any): any {
|
|
|
293
293
|
meta_data: l.meta_data,
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
|
-
|
|
297
|
-
function buildRateCode(rate: any): string {
|
|
298
|
-
const parts = [rate.country || "ANY", rate.state || "", (rate.name || "TAX").replace(/\s+/g, "-"), String(rate.priority ?? 1)];
|
|
299
|
-
return parts.filter(Boolean).join("-").toUpperCase();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function defaultMethodTitle(methodId: string): string {
|
|
303
|
-
switch (methodId) {
|
|
304
|
-
case "flat_rate": return "Flat rate";
|
|
305
|
-
case "free_shipping": return "Free shipping";
|
|
306
|
-
case "local_pickup": return "Local pickup";
|
|
307
|
-
default: return methodId;
|
|
308
|
-
}
|
|
309
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@base44/app-plugin-commerce",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Base44 Commerce plugin — entities, backend functions, shared commerce engine, admin UI and the commerce skill, shipped as copyable source",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"base44",
|
package/scripts/install.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* ../base44/agents/commerce/ → ../../../base44/agents/commerce/
|
|
19
19
|
* ../src/commerce/admin/ → ../../../src/commerce/admin/
|
|
20
20
|
* ../src/commerce/utils/ → ../../../src/commerce/utils/
|
|
21
|
-
* ../skills/commerce/ →
|
|
21
|
+
* ../skills/commerce/ → ../../../.agents/skills/commerce/
|
|
22
22
|
*
|
|
23
23
|
* Merge semantics: directories are merged — files owned by the template are
|
|
24
24
|
* overwritten (so re-running after a template update is safe), everything
|
|
@@ -89,12 +89,19 @@
|
|
|
89
89
|
// ── retired files ─────────────────────────────────────────────────────────
|
|
90
90
|
// Copying merges, so a template file that gets renamed would otherwise linger
|
|
91
91
|
// in the app forever — and stale guidance is worse than none, since agents
|
|
92
|
-
// read whatever is in skills/. Anything this template used to own and
|
|
93
|
-
// longer ships is listed here and removed on install.
|
|
92
|
+
// read whatever is in .agents/skills/. Anything this template used to own and
|
|
93
|
+
// no longer ships is listed here and removed on install.
|
|
94
94
|
const RETIRED = [
|
|
95
|
-
|
|
95
|
+
// The commerce skill moved from skills/commerce/ to .agents/skills/commerce/;
|
|
96
|
+
// remove the old copy so agents don't keep reading the stale one.
|
|
97
|
+
["skills", "commerce"],
|
|
98
|
+
[".agents", "skills", "commerce", "references", "stripe-payments.md"], // → references/online-payments.md
|
|
99
|
+
// Payments moved to the two-file model (card-payment.ts + the webhook);
|
|
100
|
+
// the Stripe adapter and the provider-status hook are gone.
|
|
101
|
+
["base44", "shared", "commerce", "stripe.ts"],
|
|
102
|
+
["src", "commerce", "admin", "hooks", "usePaymentProvider.js"],
|
|
96
103
|
// Attributes and their values are edited in the Attributes section of the
|
|
97
|
-
// Price & Inventory
|
|
104
|
+
// Price & Inventory section, ribbons in the product sidebar; the product type and the
|
|
98
105
|
// Advanced tab are gone entirely.
|
|
99
106
|
["src", "commerce", "admin", "pages", "products", "Attributes.jsx"],
|
|
100
107
|
["src", "commerce", "admin", "pages", "products", "AttributeTerms.jsx"],
|
|
@@ -109,6 +116,20 @@
|
|
|
109
116
|
// are per variant, so the product-level Shipping tab has nothing left.
|
|
110
117
|
["src", "commerce", "admin", "pages", "products", "components", "tabs", "AttributesTab.jsx"],
|
|
111
118
|
["src", "commerce", "admin", "pages", "products", "components", "tabs", "ShippingTab.jsx"],
|
|
119
|
+
// The zone/class tax-and-shipping model became one Shipping & Tax Location
|
|
120
|
+
// (commerce.ShippingTaxLocation + the settings/shipping-tax screen), and
|
|
121
|
+
// ProductTag became commerce.ProductRibbon.
|
|
122
|
+
["base44", "entities", "commerce.TaxClass.jsonc"],
|
|
123
|
+
["base44", "entities", "commerce.TaxRate.jsonc"],
|
|
124
|
+
["base44", "entities", "commerce.ShippingClass.jsonc"],
|
|
125
|
+
["base44", "entities", "commerce.ShippingZone.jsonc"],
|
|
126
|
+
["base44", "entities", "commerce.ShippingZoneMethod.jsonc"],
|
|
127
|
+
["base44", "entities", "commerce.ProductTag.jsonc"],
|
|
128
|
+
["src", "commerce", "admin", "pages", "settings", "ProductsSettings.jsx"],
|
|
129
|
+
["src", "commerce", "admin", "pages", "settings", "TaxSettings.jsx"],
|
|
130
|
+
["src", "commerce", "admin", "pages", "settings", "TaxRatesTable.jsx"],
|
|
131
|
+
["src", "commerce", "admin", "pages", "settings", "ShippingSettings.jsx"],
|
|
132
|
+
["src", "commerce", "admin", "pages", "settings", "ShippingZoneEditor.jsx"],
|
|
112
133
|
];
|
|
113
134
|
let retiredRemoved = 0;
|
|
114
135
|
for (const parts of RETIRED) {
|
|
@@ -144,7 +165,7 @@
|
|
|
144
165
|
{ label: "utils", from: ["src", "commerce", "utils"], to: ["src", "commerce", "utils"] },
|
|
145
166
|
// Commerce skill — SKILL.md, install/post-install guides, references/
|
|
146
167
|
// and docs/, the guidance agents read before working on the store.
|
|
147
|
-
{ label: "skills", from: ["skills", "commerce"], to: ["skills", "commerce"] },
|
|
168
|
+
{ label: "skills", from: ["skills", "commerce"], to: [".agents", "skills", "commerce"] },
|
|
148
169
|
];
|
|
149
170
|
for (const job of dirJobs) {
|
|
150
171
|
const src = path.join(templateRoot, ...job.from);
|
|
@@ -159,7 +180,7 @@
|
|
|
159
180
|
(retiredRemoved ? ` (${retiredRemoved} retired file${retiredRemoved === 1 ? "" : "s"} removed)` : ""),
|
|
160
181
|
);
|
|
161
182
|
console.log(
|
|
162
|
-
"\nNext steps (see skills/commerce/installation-guidelines.md + post-installation.md):\n" +
|
|
183
|
+
"\nNext steps (see .agents/skills/commerce/installation-guidelines.md + post-installation.md):\n" +
|
|
163
184
|
" 1. No deps to add: sonner, recharts and react-markdown ship with the default\n" +
|
|
164
185
|
" Base44 template — check package.json and npm i only what is truly missing;\n" +
|
|
165
186
|
" do not re-install packages already listed as dependencies\n" +
|
package/skills/commerce/SKILL.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: commerce
|
|
3
|
-
description: Base44 Commerce template —
|
|
3
|
+
description: Base44 Commerce template — 20 commerce.* entities, 16 commerce/* backend functions (storefront + admin APIs + online payments), the shared commerce engine under base44/shared/commerce/, the Store Management UI mounted at /store-admin, and the commerce/StoreAdmin agent (admin copilot). Read before working on store features — the admin UI, storefront building, payment provider wiring, scheduled maintenance, emails, webhooks, downloads, or scaling limits.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Commerce
|
|
7
7
|
|
|
8
|
-
Operational guidance for extending, operating and building on the Base44 Commerce Template. This file is the map: it stays short, and each topic links to a focused reference under [
|
|
8
|
+
Operational guidance for extending, operating and building on the Base44 Commerce Template. This file is the map: it stays short, and each topic links to a focused reference under [`.agents/skills/commerce/references/`](./references/) — open one only when your task touches that area. The API references live in [`.agents/skills/commerce/docs/`](./docs/).
|
|
9
9
|
|
|
10
10
|
> **If you are a Base44 agent working inside the runtime, read this first:**
|
|
11
11
|
> - **Don't read the whole codebase up front.** Start with this file, then open only the files your current task touches plus the matching reference below, pulling in more as you go. Reading everything first just burns context.
|
|
12
|
-
> - **Don't weaken the admin gating.** The UI guard, entity RLS and function guards form three enforcement layers — see [
|
|
12
|
+
> - **Don't weaken the admin gating.** The UI guard, entity RLS and function guards form three enforcement layers — see [`.agents/skills/commerce/post-installation.md`](./post-installation.md) — keep all of them intact when changing routes or schemas.
|
|
13
13
|
|
|
14
14
|
## IMPORTANT — first-time installation
|
|
15
15
|
|
|
16
|
-
If the template was just installed (or you are installing it right now), read [
|
|
16
|
+
If the template was just installed (or you are installing it right now), read [`.agents/skills/commerce/post-installation.md`](./post-installation.md) **before anything else**: embedding the admin pages, the three-layer admin-role enforcement (do not weaken), seeding the store's data — **one `commerce/seed-store` call takes the whole catalog** (products with attributes; variants, categories, ribbons and Shipping & Tax Locations created internally — §2) — and the **storefront quick start** (§3): logic-only chunks for product list → product page → cart → checkout that make reading the full API docs unnecessary for the happy path. The full install-from-scratch steps are in [`.agents/skills/commerce/installation-guidelines.md`](./installation-guidelines.md).
|
|
17
17
|
|
|
18
18
|
## Working on the UI
|
|
19
19
|
|
|
20
|
-
- **Admin UI** (`src/commerce/admin/`) — a complete store back office ships with the template, and it is **yours to change**: restyle it, add or remove pages, rework flows, extend it however the app needs. To understand the backend it talks to, read [
|
|
21
|
-
- **Storefront** — **no visitor UI ships**; the storefront *API* is complete (token-based cart), plus framework-free helpers in [`src/commerce/utils/`](../../src/commerce/utils/) (`variants.js`, `shipping-promos.js` — import from `@/commerce/utils`). Start from the logic-only quick start in [`post-installation.md` §3](./post-installation.md#3-storefront-quick-start--logic-only) — it covers the whole buy path; go to [
|
|
20
|
+
- **Admin UI** (`src/commerce/admin/`) — a complete store back office ships with the template, and it is **yours to change**: restyle it, add or remove pages, rework flows, extend it however the app needs. To understand the backend it talks to, read [`.agents/skills/commerce/docs/api-admin.md`](./docs/api-admin.md) — every admin function/action plus the direct-entity-CRUD contract. The only invariant is the admin-role gating (see above).
|
|
21
|
+
- **Storefront** — **no visitor UI ships**; the storefront *API* is complete (token-based cart), plus framework-free helpers in [`src/commerce/utils/`](../../src/commerce/utils/) (`variants.js`, `shipping-promos.js` — import from `@/commerce/utils`). Start from the logic-only quick start in [`post-installation.md` §3](./post-installation.md#3-storefront-quick-start--logic-only) — it covers the whole buy path; go to [`.agents/skills/commerce/docs/api-storefront.md`](./docs/api-storefront.md) for anything beyond it. Navigation comes from three actions — `list-categories` (tree), `list-ribbons` (flat, with counts) and `list-attributes` (filter UIs). What to render in the grid vs. the product page — and which fields only one of the two calls returns — is [`references/product-render.md`](./references/product-render.md); **ribbons are the most-skipped part of it and belong in both views**.
|
|
22
22
|
|
|
23
23
|
### If you build a storefront, these four are not optional
|
|
24
24
|
|
|
@@ -32,11 +32,11 @@ Agents keep shipping storefronts that miss these, and each one breaks buying out
|
|
|
32
32
|
```
|
|
33
33
|
`add-item` **rejects a product with attributes unless it gets a `variation_id`** (`400 variation_required`), so a page that ignores this cannot sell anything.
|
|
34
34
|
|
|
35
|
-
2. **Checkout must present shipping options and send a choice.**
|
|
35
|
+
2. **Checkout must present shipping options and send a choice.** Call `set-shipping-address` **as soon as the customer provides an address** — it recalculates the options and cost, and fails with `400 shipping_not_available` when the store doesn't ship there. Then read `shipping_status` on the returned cart: `auto_selected` (one option, already applied) · `chosen` · `choice_required` → **you must show `available_shipping_methods` and call `choose-shipping-method`** · `missing_address` → collect the address first (a single-location store shows its options even before one). `place-order` refuses with `400 shipping_method_required` until then — that is not a bug to work around. (`chosen_shipping_method` on the cart is the rate **id**, a string — display the choice by looking it up in `available_shipping_methods` for its `title`/`cost`, never by rendering the id or dotting into it.)
|
|
36
36
|
|
|
37
|
-
3. **
|
|
37
|
+
3. **Handle card payments, and build `/order-received`.** Choosing the card gateway returns `payment.checkout_url` — redirect there (until a payment provider is implemented it answers `503 no_card_payment_provider`; offer the other methods). Every payment link comes back to `/order-received`, which **you must implement**: call `commerce/payments` `complete-return` with the query params and render its `state`. Without that page a customer pays into a 404 and the order is never marked paid. Also persist the `cart_token` from **every** cart response — `add-item` silently starts a fresh cart when the stored token is stale.
|
|
38
38
|
|
|
39
|
-
4. **Never advertise what isn't configured.** "Free shipping over €150" must come from a real `
|
|
39
|
+
4. **Never advertise what isn't configured.** "Free shipping over €150" must come from a real shipping rate that is free or carries a `free_over` threshold on a Shipping & Tax Location. Locations are admin-only data, so a storefront cannot read them: the live answer is the cart's `available_shipping_methods` after `set-shipping-address`, and `shipping-promos.js` normalizes the rules wherever the records *are* in hand. No rule means no banner.
|
|
40
40
|
|
|
41
41
|
All functions return the envelope `{ success, data }` (or `{ success, error, code }`); with the SDK the body is on `res.data`:
|
|
42
42
|
|
|
@@ -47,15 +47,15 @@ const { products, has_next } = res.data.data; // res.data = envelope, .data =
|
|
|
47
47
|
|
|
48
48
|
## Topic references
|
|
49
49
|
|
|
50
|
-
Open the matching file under
|
|
50
|
+
Open the matching file under `.agents/skills/commerce/references/` only when a task touches its area:
|
|
51
51
|
|
|
52
52
|
| Topic | Read when the task involves | Reference |
|
|
53
53
|
|---|---|---|
|
|
54
|
-
| Product rendering (list + page) | what to show in a product grid vs. the product page, field availability across `list-products`/`get-product`, **
|
|
54
|
+
| Product rendering (list + page) | what to show in a product grid vs. the product page, field availability across `list-products`/`get-product`, **ribbons in both views**, variant pricing on cards, adding a page-only field to the listing call | [`references/product-render.md`](./references/product-render.md) |
|
|
55
55
|
| Variant selection | attribute-level selectors, resolving a selection to a variation, availability states, incomplete-selection pricing, add-to-cart contract | [`references/storefront-product-page.md`](./references/storefront-product-page.md) |
|
|
56
|
-
| Reviews | stars on cards and the product page, the review list + submit form (backend ships complete),
|
|
57
|
-
| Admin product form | changing the product editor — its
|
|
58
|
-
| Online payments | **any storefront or checkout work** —
|
|
56
|
+
| Reviews | stars on cards and the product page, the review list + submit form (public by email, backend ships complete), the auto-approve toggle, UI-enforced policies (login-gated, verified-only, required rating) | [`references/reviews.md`](./references/reviews.md) |
|
|
57
|
+
| Admin product form | changing the product editor — its stacked sections are **Price & Inventory** (tax group, then the attributes, then a row per variant, or a single *Base price* row when there are none), **Modifiers** (`meta_data`), **Downloads**, **Linked products**; one **Visible** toggle drives `status`. Variants reconcile from the attribute values automatically: no generate step, no per-variant delete. Weight and dimensions are per variant. | [`references/admin-product-form.md`](./references/admin-product-form.md) |
|
|
58
|
+
| Online payments | **any storefront or checkout work** — the order side (checkout, confirmation, payment links, refund records) is premade; wiring a provider means implementing exactly two files (`shared/commerce/card-payment.ts` + the payment webhook) | [`references/online-payments.md`](./references/online-payments.md) |
|
|
59
59
|
| Scheduled work | recurring maintenance — stock-hold release, abandoned-cart cleanup, webhook-log pruning, counter-drift repair | [`references/scheduled-work.md`](./references/scheduled-work.md) |
|
|
60
60
|
| Emails | transactional order emails, per-type overrides, deliverability, the email log | [`references/emails.md`](./references/emails.md) |
|
|
61
61
|
| Webhooks | outbound webhooks, HMAC signing, delivery log, auto-disable behavior | [`references/webhooks.md`](./references/webhooks.md) |
|