@base44/app-plugin-commerce 0.3.3 → 0.3.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@base44/app-plugin-commerce",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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",
|
|
@@ -22,7 +22,7 @@ A fresh install has **no settings and no catalog**. One admin-only, idempotent c
|
|
|
22
22
|
|
|
23
23
|
**`store_name` is required on a first seed** — the app's name as the platform shows it (`base44/config.jsonc` → `name` can be stale; ask if unsure). **`currency`** is an ISO code (`"EUR"`); formatting follows the viewer's locale, nothing else to set. Explicit values always win, first seed and re-runs alike.
|
|
24
24
|
|
|
25
|
-
The working call — `name` is the only required product key; give each product the keys its own catalog entry actually has and leave the rest out. The **full key list** (sale windows, downloads, tax, backorders,
|
|
25
|
+
The working call — `name` is the only required product key; give each product the keys its own catalog entry actually has and leave the rest out. The **full key list** (sale windows, downloads, tax, backorders, upsells…) lives in `api-admin.md` — open it only if the catalog needs one:
|
|
26
26
|
|
|
27
27
|
```js
|
|
28
28
|
try {
|
|
@@ -45,10 +45,14 @@ try {
|
|
|
45
45
|
categories: ["Shoes"], // get-or-created by display name
|
|
46
46
|
ribbons: ["Best Seller"], // flat labels, not a hierarchy
|
|
47
47
|
|
|
48
|
+
weight: 0.248, // real fields, in the store's units —
|
|
49
|
+
dimensions: { length: 31, width: 12, height: 11 }, // never a meta_data row
|
|
50
|
+
|
|
48
51
|
// Descriptive spec rows (productSpecs). NOT axes, NOT ribbons; `_` hides.
|
|
52
|
+
// Qualities only — a weight or a size here is a string nothing can read.
|
|
49
53
|
meta_data: [
|
|
50
54
|
{ key: "Material", value: "Recycled knit upper" },
|
|
51
|
-
{ key: "
|
|
55
|
+
{ key: "Care", value: "Machine wash cold" },
|
|
52
56
|
],
|
|
53
57
|
|
|
54
58
|
attributes: [ // the axes → one selector each
|
|
@@ -57,8 +61,9 @@ try {
|
|
|
57
61
|
],
|
|
58
62
|
default_options: { Size: "42", Color: "Black" },
|
|
59
63
|
variations: [ // omit entirely → all combos auto-generated
|
|
60
|
-
{ options: { Size: "41", Color: "Black" }, stock_quantity: 4 },
|
|
64
|
+
{ options: { Size: "41", Color: "Black" }, stock_quantity: 4, weight: 0.242 },
|
|
61
65
|
{ options: { Size: "42", Color: "White" }, regular_price: 94, stock_quantity: 3,
|
|
66
|
+
weight: 0.254, // per variant, never inherited from the parent
|
|
62
67
|
image: "https://…/sneaker-white.jpg" }, // per-variation image for a visual axis
|
|
63
68
|
],
|
|
64
69
|
},
|
|
@@ -136,9 +141,4 @@ Online card payments are **off by default**: the seeded store takes the manual `
|
|
|
136
141
|
- [ ] `coupons` seeded only if a coupon field exists ([`./02-storefront.md`](./02-storefront.md)).
|
|
137
142
|
- [ ] Cards off, or on with the provider file copied whole.
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
- Product slugs come from the seed response's `catalog.products[]` — link pages by slug, never by a client-side map.
|
|
142
|
-
- Payments: report at handover where they landed (default = offline on, card off) — the owner must never learn it from a customer.
|
|
143
|
-
- Turning card payments on or off later is one more seed call: `{ payment_methods: ["offline", "card"] }`.
|
|
144
|
-
- Seed-time `locations` is THE shipping path; patching `commerce.ShippingTaxLocation` is the day-2 route.
|
|
144
|
+
Then copy this file's `carry_forward` lines (in its front matter) into your working notes, and do not re-read this file.
|
|
@@ -55,6 +55,8 @@ A kit update re-copies `shared/commerce/` and restores the stub — re-run the c
|
|
|
55
55
|
|
|
56
56
|
Checkout (`place-order`, `card` gateway) creates the order `pending`, runs `createCardPayment`, stores its `reference` on the order and redirects to `url`; return URLs carry `order_id`/`order_key`/`payment=`. Confirmation is **two idempotent paths** (the second a no-op): the customer return (`/order-received` → `commerce/payments` `complete-return` → `checkCardPaymentPaid` → `processing` with all side-effects; `useOrderReturn` is that page in one hook) and the webhook (covers the closed tab). `create-link` mints a fresh page for any unpaid order the same way; `admin-refunds` `refund_payment: true` calls `refundCardPayment` **before** writing locally (unimplemented → `501`; record without the flag instead). Gateway enabled with no provider behind it → `503 no_card_payment_provider`; every other payment option is **manual** (on-hold, the option's description as payment instructions) and needs no code.
|
|
57
57
|
|
|
58
|
+
⚑ **Card payment cannot complete in the preview pane.** Providers serve their payment page `frame-ancestors 'none'`, so the redirect dead-ends in the frame. `useCheckout`'s `placeOrder` refuses up front — `{ ok: false, error: { code: "card_payment_in_preview" } }`, nothing created — because `place-order` takes the order and its stock hold *before* minting the payment page, and an attempt that dies at the redirect would strand an unpayable order. Manual gateways are unaffected. **Test card checkout in the published store**; a card flow that appears to do nothing in preview and works published is this, not broken provider wiring.
|
|
59
|
+
|
|
58
60
|
## Implementation rules
|
|
59
61
|
|
|
60
62
|
For a **custom** provider (the shipped files already obey all of these):
|
|
@@ -9,6 +9,22 @@ import {
|
|
|
9
9
|
shippingSlice,
|
|
10
10
|
} from "./address";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Card payment cannot complete in the app builder's preview pane. The
|
|
14
|
+
* provider's payment page is a full-page redirect and providers serve theirs
|
|
15
|
+
* `frame-ancestors 'none'`, so the redirect navigates the pane, the provider
|
|
16
|
+
* refuses to render in it, and checkout looks like it silently did nothing. A
|
|
17
|
+
* storefront runs framed in exactly one place, so "framed" is read as "preview".
|
|
18
|
+
*/
|
|
19
|
+
function inPreview() {
|
|
20
|
+
if (typeof window === "undefined") return false;
|
|
21
|
+
try {
|
|
22
|
+
return window.self !== window.top;
|
|
23
|
+
} catch {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
const EMPTY_ADDRESS = Object.freeze({
|
|
13
29
|
first_name: "",
|
|
14
30
|
last_name: "",
|
|
@@ -70,6 +86,12 @@ function resolvePaymentMethod(gateways, picked) {
|
|
|
70
86
|
* handle the result yourself (a manual result carries
|
|
71
87
|
* `result.payment_instructions`).
|
|
72
88
|
*
|
|
89
|
+
* **A card order will not place from the preview pane** — providers refuse to
|
|
90
|
+
* be framed, so the redirect can only dead-end there. `placeOrder` resolves
|
|
91
|
+
* `{ ok: false, error: { code: "card_payment_in_preview" } }` without
|
|
92
|
+
* creating anything; manual gateways are unaffected. Test card checkout in
|
|
93
|
+
* the published store.
|
|
94
|
+
*
|
|
73
95
|
* Both navigations are **full page loads** (`window.location.assign`): the
|
|
74
96
|
* provider hop leaves the app, and the receipt boots from the URL alone
|
|
75
97
|
* (`order_id` + `order_key`), so a reload there is correct and shareable.
|
|
@@ -215,6 +237,18 @@ export function useCheckout(options = {}) {
|
|
|
215
237
|
const placeOrder = useCallback(
|
|
216
238
|
async (extra = {}) => {
|
|
217
239
|
if (placing) return { ok: false, error: { code: "placing", message: "Order already being placed." } };
|
|
240
|
+
// Refuse a card checkout from the preview pane before the network, not
|
|
241
|
+
// after it: `place-order` creates the order and takes its stock hold
|
|
242
|
+
// *before* the payment page exists, so an attempt that can only die at
|
|
243
|
+
// the redirect would strand a real pending order nobody can pay.
|
|
244
|
+
if (paymentMethod === "card" && inPreview()) {
|
|
245
|
+
const error = {
|
|
246
|
+
code: "card_payment_in_preview",
|
|
247
|
+
message: "Card payment can't be completed in preview. Open the published store to pay.",
|
|
248
|
+
};
|
|
249
|
+
setOrderError(error);
|
|
250
|
+
return { ok: false, error };
|
|
251
|
+
}
|
|
218
252
|
// Safe as `onClick={checkout.placeOrder}`: a DOM/React event is not
|
|
219
253
|
// `extra`. Spreading one into the order payload would send a circular
|
|
220
254
|
// SyntheticEvent as order fields — an explicit object still forwards.
|