@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.
Files changed (95) hide show
  1. package/README.md +12 -12
  2. package/base44/agents/commerce/StoreAdmin.jsonc +2 -2
  3. package/base44/entities/commerce.Cart.jsonc +1 -1
  4. package/base44/entities/commerce.Coupon.jsonc +5 -0
  5. package/base44/entities/commerce.Order.jsonc +6 -7
  6. package/base44/entities/commerce.OrderRefund.jsonc +1 -1
  7. package/base44/entities/commerce.PaymentGateway.jsonc +1 -1
  8. package/base44/entities/commerce.Product.jsonc +6 -16
  9. package/base44/entities/{commerce.ProductTag.jsonc → commerce.ProductRibbon.jsonc} +2 -2
  10. package/base44/entities/commerce.ProductVariation.jsonc +1 -9
  11. package/base44/entities/commerce.ShippingTaxLocation.jsonc +85 -0
  12. package/base44/entities/commerce.Webhook.jsonc +1 -1
  13. package/base44/functions/commerce/admin-orders/helpers.ts +7 -13
  14. package/base44/functions/commerce/admin-products/entry.ts +11 -17
  15. package/base44/functions/commerce/admin-refunds/entry.ts +10 -9
  16. package/base44/functions/commerce/admin-reports/entry.ts +3 -3
  17. package/base44/functions/commerce/admin-tools/entry.ts +9 -36
  18. package/base44/functions/commerce/payment-webhook/entry.ts +50 -89
  19. package/base44/functions/commerce/payments/entry.ts +46 -42
  20. package/base44/functions/commerce/seed-store/defaults.ts +35 -42
  21. package/base44/functions/commerce/seed-store/entry.ts +68 -46
  22. package/base44/functions/commerce/seed-store/sample-data.ts +2 -15
  23. package/base44/functions/commerce/seed-store/seed-catalog.ts +105 -55
  24. package/base44/functions/commerce/storefront-cart/cart-pricing.ts +6 -11
  25. package/base44/functions/commerce/storefront-cart/entry.ts +36 -2
  26. package/base44/functions/commerce/storefront-catalog/entry.ts +55 -72
  27. package/base44/functions/commerce/storefront-checkout/cart-pricing.ts +6 -11
  28. package/base44/functions/commerce/storefront-checkout/entry.ts +43 -56
  29. package/base44/shared/commerce/card-payment.ts +80 -0
  30. package/base44/shared/commerce/coupons.ts +16 -10
  31. package/base44/shared/commerce/emails.ts +30 -18
  32. package/base44/shared/commerce/money.ts +11 -24
  33. package/base44/shared/commerce/payments.ts +55 -286
  34. package/base44/shared/commerce/scan.ts +1 -1
  35. package/base44/shared/commerce/sequence.ts +1 -1
  36. package/base44/shared/commerce/settings.ts +4 -9
  37. package/base44/shared/commerce/shipping.ts +65 -133
  38. package/base44/shared/commerce/tax.ts +48 -96
  39. package/base44/shared/commerce/totals.ts +77 -91
  40. package/package.json +1 -1
  41. package/scripts/install.js +28 -7
  42. package/skills/commerce/SKILL.md +14 -14
  43. package/skills/commerce/docs/api-admin.md +23 -26
  44. package/skills/commerce/docs/api-storefront.md +67 -61
  45. package/skills/commerce/installation-guidelines.md +8 -8
  46. package/skills/commerce/post-installation.md +60 -39
  47. package/skills/commerce/references/admin-product-form.md +15 -12
  48. package/skills/commerce/references/emails.md +2 -2
  49. package/skills/commerce/references/guest-access-security.md +2 -2
  50. package/skills/commerce/references/online-payments.md +24 -166
  51. package/skills/commerce/references/product-render.md +18 -18
  52. package/skills/commerce/references/reviews.md +14 -8
  53. package/skills/commerce/references/storefront-product-page.md +1 -1
  54. package/src/commerce/admin/README.md +5 -6
  55. package/src/commerce/admin/bot/Markdown.jsx +1 -1
  56. package/src/commerce/admin/hooks/useMoney.js +13 -22
  57. package/src/commerce/admin/layout/AuthGuard.jsx +1 -1
  58. package/src/commerce/admin/lib/constants.js +2 -29
  59. package/src/commerce/admin/lib/order-utils.js +1 -1
  60. package/src/commerce/admin/pages/coupons/CouponEditor.jsx +131 -140
  61. package/src/commerce/admin/pages/coupons/CouponsList.jsx +14 -7
  62. package/src/commerce/admin/pages/orders/OrderEditor.jsx +3 -3
  63. package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +17 -28
  64. package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +5 -11
  65. package/src/commerce/admin/pages/products/Categories.jsx +147 -177
  66. package/src/commerce/admin/pages/products/ProductEditor.jsx +23 -18
  67. package/src/commerce/admin/pages/products/Reviews.jsx +37 -1
  68. package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +33 -47
  69. package/src/commerce/admin/pages/products/components/PublishBox.jsx +13 -34
  70. package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +29 -29
  71. package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +14 -44
  72. package/src/commerce/admin/pages/reports/Reports.jsx +2 -2
  73. package/src/commerce/admin/pages/settings/EmailsSettings.jsx +101 -68
  74. package/src/commerce/admin/pages/settings/GeneralSettings.jsx +40 -38
  75. package/src/commerce/admin/pages/settings/InventorySettings.jsx +1 -41
  76. package/src/commerce/admin/pages/settings/LocationEditor.jsx +377 -0
  77. package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +137 -119
  78. package/src/commerce/admin/pages/settings/SettingsLayout.jsx +2 -4
  79. package/src/commerce/admin/pages/settings/ShippingTaxSettings.jsx +191 -0
  80. package/src/commerce/admin/routes.jsx +6 -12
  81. package/src/commerce/utils/index.js +2 -2
  82. package/src/commerce/utils/shipping-promos.js +45 -49
  83. package/src/commerce/utils/variants.js +1 -1
  84. package/base44/entities/commerce.ShippingClass.jsonc +0 -30
  85. package/base44/entities/commerce.ShippingZone.jsonc +0 -41
  86. package/base44/entities/commerce.ShippingZoneMethod.jsonc +0 -84
  87. package/base44/entities/commerce.TaxClass.jsonc +0 -23
  88. package/base44/entities/commerce.TaxRate.jsonc +0 -68
  89. package/base44/shared/commerce/stripe.ts +0 -463
  90. package/src/commerce/admin/hooks/usePaymentProvider.js +0 -27
  91. package/src/commerce/admin/pages/settings/ProductsSettings.jsx +0 -118
  92. package/src/commerce/admin/pages/settings/ShippingSettings.jsx +0 -304
  93. package/src/commerce/admin/pages/settings/ShippingZoneEditor.jsx +0 -514
  94. package/src/commerce/admin/pages/settings/TaxRatesTable.jsx +0 -231
  95. package/src/commerce/admin/pages/settings/TaxSettings.jsx +0 -280
@@ -3,87 +3,83 @@
3
3
  * ("Free shipping over €150", "You're €30 away from free shipping") states a
4
4
  * real rule instead of an invented number.
5
5
  *
6
- * A free-shipping claim is only true if a `free_shipping` shipping-zone method
7
- * exists and its requirement is met — the totals engine offers that method
8
- * exactly when `min_amount`/coupon conditions hold, so anything the UI promises
9
- * outside these rules is a promise the checkout will not keep.
6
+ * A free-shipping claim is only true if a Shipping & Tax Location carries a
7
+ * shipping rate that is free — `cost: 0` — or becomes free at a threshold
8
+ * (`free_over`). The totals engine zeroes the rate exactly when the discounted
9
+ * items subtotal reaches `free_over`, so anything the UI promises outside
10
+ * these rules is a promise the checkout will not keep.
10
11
  *
11
- * **Zones are admin-only data.** `commerce.ShippingZone` and
12
- * `commerce.ShippingZoneMethod` are `read: {role: "admin"}` like every entity, so
13
- * a visitor cannot list them — these helpers run where the records are already in
14
- * hand: a backend function, an admin screen, or a storefront action you add that
15
- * projects the rules itself.
12
+ * **Locations are admin-only data.** `commerce.ShippingTaxLocation` is
13
+ * `read: {role: "admin"}` like every entity, so a visitor cannot list them —
14
+ * these helpers run where the records are already in hand: a backend function,
15
+ * an admin screen, or a storefront action you add that projects the rules.
16
16
  *
17
17
  * ```js
18
- * const rules = freeShippingRules(zones, methods);
18
+ * const rules = freeShippingRules(locations);
19
19
  * // no rules → do not advertise free shipping anywhere.
20
20
  * ```
21
21
  *
22
22
  * For the *live* answer on a real cart, use the cart view's
23
- * `available_shipping_methods` after `set-shipping-address` — that is computed by
24
- * the same engine and needs no extra exposure.
23
+ * `available_shipping_methods` after `set-shipping-address` — that is computed
24
+ * by the same engine and needs no extra exposure.
25
25
  *
26
26
  * Framework-free and dependency-free. See
27
- * `skills/commerce/docs/api-storefront.md` for the surrounding rules.
27
+ * `.agents/skills/commerce/docs/api-storefront.md` for the surrounding rules.
28
28
  */
29
29
 
30
30
  /**
31
- * Normalized free-shipping rules, one per configured `free_shipping` method.
31
+ * Normalized free-shipping rules, one per rate that is free or has a
32
+ * `free_over` threshold.
32
33
  *
33
34
  * @returns {Array<{
34
- * zone_id: string, zone_name: string, method_id: string, title: string,
35
- * requires: "" | "coupon" | "min_amount" | "either" | "both",
36
- * min_amount: number|null, needs_coupon: boolean, ignore_discounts: boolean,
37
- * unconditional: boolean
35
+ * location_id: string, location_name: string, rate_id: string, title: string,
36
+ * min_amount: number|null, unconditional: boolean
38
37
  * }>}
39
38
  */
40
- export function freeShippingRules(zones, methods) {
41
- const zoneById = new Map((zones ?? []).map((z) => [z.id, z]));
42
- return (methods ?? [])
43
- .filter((m) => m?.method_id === "free_shipping" && m.enabled !== false)
44
- .map((m) => {
45
- const s = m.settings ?? {};
46
- const requires = s.requires ?? "";
47
- const min = Number(s.min_amount);
48
- return {
49
- zone_id: m.zone_id ?? "",
50
- zone_name: zoneById.get(m.zone_id)?.name ?? "",
51
- method_id: m.id,
52
- title: m.title || "Free shipping",
53
- requires,
54
- min_amount: ["min_amount", "either", "both"].includes(requires) && Number.isFinite(min) ? min : null,
55
- needs_coupon: ["coupon", "either", "both"].includes(requires),
56
- ignore_discounts: !!s.ignore_discounts,
57
- unconditional: !requires,
58
- };
59
- });
39
+ export function freeShippingRules(locations) {
40
+ const out = [];
41
+ for (const location of locations ?? []) {
42
+ for (const rate of location?.shipping_rates ?? []) {
43
+ const threshold = Number(rate?.free_over);
44
+ const free = Number(rate?.cost ?? 0) === 0;
45
+ const hasThreshold = rate?.free_over != null && Number.isFinite(threshold);
46
+ if (!free && !hasThreshold) continue;
47
+ out.push({
48
+ location_id: location.id ?? "",
49
+ location_name: location.name ?? "",
50
+ rate_id: rate.id ?? "",
51
+ title: rate.name || "Free shipping",
52
+ min_amount: free ? null : threshold,
53
+ unconditional: free,
54
+ });
55
+ }
56
+ }
57
+ return out;
60
58
  }
61
59
 
62
60
  /**
63
61
  * The threshold to advertise, or `null` when the store has none — in which case
64
62
  * **say nothing about free shipping**. Picks the lowest configured threshold
65
- * (the one a shopper can actually reach); an unconditional free-shipping method
66
- * yields `0`, i.e. "free shipping" with no threshold at all.
63
+ * (the one a shopper can actually reach); an always-free rate yields `0`,
64
+ * i.e. "free shipping" with no threshold at all.
67
65
  *
68
- * Thresholds are per shipping zone, so a global banner is only honest when the
69
- * rule covers the customer's zone — pass `zoneId` once you know it (after
66
+ * Thresholds are per location, so a global banner is only honest when the rule
67
+ * covers the customer's location — pass `locationId` once you know it (after
70
68
  * `set-shipping-address`, the cart's `available_shipping_methods` is the
71
69
  * authoritative answer for that address).
72
70
  */
73
- export function freeShippingThreshold(rules, zoneId = null) {
74
- const scoped = (rules ?? []).filter((r) => (zoneId ? r.zone_id === zoneId : true));
71
+ export function freeShippingThreshold(rules, locationId = null) {
72
+ const scoped = (rules ?? []).filter((r) => (locationId ? r.location_id === locationId : true));
75
73
  if (!scoped.length) return null;
76
74
  if (scoped.some((r) => r.unconditional)) return 0;
77
- const amounts = scoped
78
- .filter((r) => !r.needs_coupon && r.min_amount != null)
79
- .map((r) => r.min_amount);
75
+ const amounts = scoped.filter((r) => r.min_amount != null).map((r) => r.min_amount);
80
76
  return amounts.length ? Math.min(...amounts) : null;
81
77
  }
82
78
 
83
79
  /**
84
80
  * Progress toward a threshold, for a "spend X more" nudge. `subtotal` should be
85
- * the cart's `totals.subtotal` — or `subtotal - discount_total` when the rule
86
- * does not `ignore_discounts`, matching how the engine evaluates it.
81
+ * the cart's `totals.subtotal - totals.discount_total` — the engine compares
82
+ * `free_over` against the discounted items subtotal.
87
83
  *
88
84
  * @returns {{threshold: number, qualifies: boolean, remaining: number}|null}
89
85
  * `null` when there is no threshold to talk about.
@@ -12,7 +12,7 @@
12
12
  * Framework-free, dependency-free, no I/O — feed them the `{ product,
13
13
  * variations }` pair from `commerce/storefront-catalog` `get-product` (the
14
14
  * admin's variation editor can use them too). See
15
- * `skills/commerce/references/storefront-product-page.md` for the UI rules.
15
+ * `.agents/skills/commerce/references/storefront-product-page.md` for the UI rules.
16
16
  *
17
17
  * A `selection` throughout is a plain object keyed by axis key (see
18
18
  * `attributeKey`): `{ "<color-attr-id>": "Red", "<size-attr-id>": "M" }`.
@@ -1,30 +0,0 @@
1
- {
2
- "name": "commerce.ShippingClass",
3
- "type": "object",
4
- "description": "Shipping class assignable to products; flat-rate methods can charge per class.",
5
- "properties": {
6
- "name": {
7
- "type": "string",
8
- "minLength": 1
9
- },
10
- "slug": {
11
- "type": "string",
12
- "description": "Unique slug (enforced in admin UI / functions)"
13
- },
14
- "description": {
15
- "type": "string"
16
- },
17
- "count": {
18
- "type": "integer",
19
- "default": 0,
20
- "description": "Derived product count. Maintained by commerce/admin-products; repair via commerce/admin-tools recount-terms."
21
- }
22
- },
23
- "required": ["name"],
24
- "rls": {
25
- "read": { "user_condition": { "role": "admin" } },
26
- "create": { "user_condition": { "role": "admin" } },
27
- "update": { "user_condition": { "role": "admin" } },
28
- "delete": { "user_condition": { "role": "admin" } }
29
- }
30
- }
@@ -1,41 +0,0 @@
1
- {
2
- "name": "commerce.ShippingZone",
3
- "type": "object",
4
- "description": "Geographic shipping zone. Zones are matched by 'order' ascending; the seeded 'Rest of the world' zone (order 999, no locations) catches unmatched addresses.",
5
- "properties": {
6
- "name": {
7
- "type": "string",
8
- "minLength": 1
9
- },
10
- "order": {
11
- "type": "integer",
12
- "default": 0,
13
- "description": "Match priority, ascending"
14
- },
15
- "locations": {
16
- "type": "array",
17
- "description": "Zone locations; empty = matches everything (fallback zone)",
18
- "items": {
19
- "type": "object",
20
- "properties": {
21
- "type": {
22
- "type": "string",
23
- "enum": ["country", "state", "postcode", "continent"],
24
- "description": "Location type"
25
- },
26
- "code": {
27
- "type": "string",
28
- "description": "Country code (US), state code (US:CA), postcode pattern (90*, 1000...2000), or continent code (EU)"
29
- }
30
- }
31
- }
32
- }
33
- },
34
- "required": ["name"],
35
- "rls": {
36
- "read": { "user_condition": { "role": "admin" } },
37
- "create": { "user_condition": { "role": "admin" } },
38
- "update": { "user_condition": { "role": "admin" } },
39
- "delete": { "user_condition": { "role": "admin" } }
40
- }
41
- }
@@ -1,84 +0,0 @@
1
- {
2
- "name": "commerce.ShippingZoneMethod",
3
- "type": "object",
4
- "description": "A shipping method instance attached to a zone. The settings object shape depends on method_id.",
5
- "properties": {
6
- "zone_id": {
7
- "type": "string",
8
- "description": "ShippingZone id"
9
- },
10
- "method_id": {
11
- "type": "string",
12
- "enum": ["flat_rate", "free_shipping", "local_pickup"],
13
- "description": "Method type"
14
- },
15
- "title": {
16
- "type": "string",
17
- "description": "Customer-facing method title"
18
- },
19
- "enabled": {
20
- "type": "boolean",
21
- "default": true
22
- },
23
- "order": {
24
- "type": "integer",
25
- "default": 0,
26
- "description": "Display order within the zone"
27
- },
28
- "settings": {
29
- "type": "object",
30
- "description": "Per-method settings. flat_rate: {cost, tax_status, class_costs, no_class_cost, calculation_type}; free_shipping: {requires, min_amount, ignore_discounts}; local_pickup: {cost, tax_status}",
31
- "properties": {
32
- "cost": {
33
- "type": "number",
34
- "description": "flat_rate/local_pickup base cost"
35
- },
36
- "tax_status": {
37
- "type": "string",
38
- "enum": ["taxable", "none"],
39
- "description": "flat_rate/local_pickup"
40
- },
41
- "class_costs": {
42
- "type": "array",
43
- "description": "flat_rate: additional cost per shipping class present in the cart",
44
- "items": {
45
- "type": "object",
46
- "properties": {
47
- "shipping_class_id": { "type": "string" },
48
- "cost": { "type": "number" }
49
- }
50
- }
51
- },
52
- "no_class_cost": {
53
- "type": "number",
54
- "description": "flat_rate: cost applied for items without a shipping class"
55
- },
56
- "calculation_type": {
57
- "type": "string",
58
- "enum": ["class", "order"],
59
- "description": "flat_rate: charge per class present (class) or most expensive class only (order)"
60
- },
61
- "requires": {
62
- "type": "string",
63
- "enum": ["", "coupon", "min_amount", "either", "both"],
64
- "description": "free_shipping: requirement for the method to be offered"
65
- },
66
- "min_amount": {
67
- "type": "number",
68
- "description": "free_shipping: minimum items subtotal"
69
- },
70
- "ignore_discounts": {
71
- "type": "boolean",
72
- "description": "free_shipping: compare min_amount against pre-discount subtotal"
73
- }
74
- }
75
- }
76
- },
77
- "required": ["zone_id", "method_id"],
78
- "rls": {
79
- "read": { "user_condition": { "role": "admin" } },
80
- "create": { "user_condition": { "role": "admin" } },
81
- "update": { "user_condition": { "role": "admin" } },
82
- "delete": { "user_condition": { "role": "admin" } }
83
- }
84
- }
@@ -1,23 +0,0 @@
1
- {
2
- "name": "commerce.TaxClass",
3
- "type": "object",
4
- "description": "Tax class grouping products for rate matching. Seeded: standard, reduced-rate, zero-rate.",
5
- "properties": {
6
- "slug": {
7
- "type": "string",
8
- "minLength": 1,
9
- "description": "Unique slug (enforced in admin UI / seed)"
10
- },
11
- "name": {
12
- "type": "string",
13
- "minLength": 1
14
- }
15
- },
16
- "required": ["slug", "name"],
17
- "rls": {
18
- "read": { "user_condition": { "role": "admin" } },
19
- "create": { "user_condition": { "role": "admin" } },
20
- "update": { "user_condition": { "role": "admin" } },
21
- "delete": { "user_condition": { "role": "admin" } }
22
- }
23
- }
@@ -1,68 +0,0 @@
1
- {
2
- "name": "commerce.TaxRate",
3
- "type": "object",
4
- "description": "A tax rate matched by location and tax class. Applied by shared/tax per priority/compound rules.",
5
- "properties": {
6
- "country": {
7
- "type": "string",
8
- "default": "",
9
- "description": "ISO 3166-1 alpha-2 country code; empty = any"
10
- },
11
- "state": {
12
- "type": "string",
13
- "default": "",
14
- "description": "State/province code; empty = any"
15
- },
16
- "postcodes": {
17
- "type": "array",
18
- "items": { "type": "string" },
19
- "description": "Postcode patterns; supports wildcard (90*) and numeric range (1000...2000); empty = any"
20
- },
21
- "cities": {
22
- "type": "array",
23
- "items": { "type": "string" },
24
- "description": "City names; empty = any"
25
- },
26
- "rate": {
27
- "type": "number",
28
- "description": "Percentage rate, e.g. 20 for 20%"
29
- },
30
- "name": {
31
- "type": "string",
32
- "minLength": 1,
33
- "description": "Label, e.g. VAT"
34
- },
35
- "priority": {
36
- "type": "integer",
37
- "default": 1,
38
- "description": "Only one matching rate per priority is applied; lower priorities apply first"
39
- },
40
- "compound": {
41
- "type": "boolean",
42
- "default": false,
43
- "description": "Applied on top of previously accumulated taxes"
44
- },
45
- "shipping": {
46
- "type": "boolean",
47
- "default": true,
48
- "description": "Rate also applies to shipping"
49
- },
50
- "tax_class": {
51
- "type": "string",
52
- "default": "standard",
53
- "description": "TaxClass slug this rate belongs to"
54
- },
55
- "menu_order": {
56
- "type": "integer",
57
- "default": 0,
58
- "description": "Tie-breaker among rates of the same priority"
59
- }
60
- },
61
- "required": ["rate", "name"],
62
- "rls": {
63
- "read": { "user_condition": { "role": "admin" } },
64
- "create": { "user_condition": { "role": "admin" } },
65
- "update": { "user_condition": { "role": "admin" } },
66
- "delete": { "user_condition": { "role": "admin" } }
67
- }
68
- }