@base44/app-plugin-commerce 0.1.8 → 0.1.12

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.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Storefront API client — the thin layer between the visitor UI you build and
3
+ * the commerce/storefront-* functions. Framework-free. Create it ONCE and
4
+ * import that instance everywhere (wrapping it in a React context is fine —
5
+ * never create a second copy):
6
+ *
7
+ * // src/lib/storefront.js
8
+ * import { createStorefront } from "@/commerce/utils";
9
+ * import { base44 } from "@/api/base44Client";
10
+ * export const store = createStorefront(base44);
11
+ *
12
+ * It owns the two things hand-rolled clients keep getting wrong:
13
+ *
14
+ * - The cart_token lifecycle: the token is sent with every cart/checkout call
15
+ * and re-persisted from every response (a stale token silently starts a
16
+ * fresh cart), and it is cleared when checkout consumes the cart.
17
+ * - The store-info split: payment_gateways, currency, countries/currencies
18
+ * live ONLY on get-store-info — the cart view never carries them.
19
+ * getStoreInfo() caches the call; read them from there, never off a cart.
20
+ */
21
+
22
+ /** The stable error code a failed storefront call carries, if any. */
23
+ export function storefrontErrorCode(e) {
24
+ return e?.response?.data?.code ?? e?.data?.code ?? e?.code ?? null;
25
+ }
26
+
27
+ export function createStorefront(base44, { storageKey = "cart_token", storage } = {}) {
28
+ const bag = storage ?? (typeof localStorage !== "undefined" ? localStorage : null);
29
+
30
+ // Every function returns the envelope { success, data }; with the SDK the
31
+ // payload is res.data.data. Exposed for calls beyond the methods below.
32
+ const inv = (fn, payload) =>
33
+ base44.functions.invoke(fn, payload).then((r) => r.data.data);
34
+
35
+ const token = () => bag?.getItem(storageKey) || undefined;
36
+ const remember = (cart) => {
37
+ // ALWAYS re-persist — the backend may have started a fresh cart.
38
+ if (cart?.cart_token && bag) bag.setItem(storageKey, cart.cart_token);
39
+ return cart;
40
+ };
41
+ const forget = () => bag?.removeItem(storageKey);
42
+
43
+ const cartAction = (action, payload = {}) =>
44
+ inv("commerce/storefront-cart", { action, cart_token: token(), ...payload }).then(remember);
45
+
46
+ let infoPromise = null;
47
+
48
+ return {
49
+ inv,
50
+
51
+ // ── store info (cached) ──────────────────────────────────────────────
52
+ /** settings, payment_gateways, countries, currencies — this call ONLY. */
53
+ getStoreInfo() {
54
+ infoPromise ??= inv("commerce/storefront-catalog", { action: "get-store-info" })
55
+ .catch((e) => { infoPromise = null; throw e; });
56
+ return infoPromise;
57
+ },
58
+
59
+ // ── catalog ──────────────────────────────────────────────────────────
60
+ listProducts(params = {}) {
61
+ return inv("commerce/storefront-catalog", { action: "list-products", ...params });
62
+ },
63
+ /** getProduct("slug") or getProduct({ id }) */
64
+ getProduct(ref) {
65
+ const by = typeof ref === "string" ? { slug: ref } : ref;
66
+ return inv("commerce/storefront-catalog", { action: "get-product", ...by });
67
+ },
68
+ listCategories() {
69
+ return inv("commerce/storefront-catalog", { action: "list-categories" });
70
+ },
71
+ listRibbons() {
72
+ return inv("commerce/storefront-catalog", { action: "list-ribbons" });
73
+ },
74
+
75
+ // ── cart (token handled internally; every call returns the full view) ─
76
+ /** The current cart view, or null when there is none (an expired token self-clears). */
77
+ async getCart() {
78
+ if (!token()) return null;
79
+ try {
80
+ return await cartAction("get");
81
+ } catch (e) {
82
+ if (["cart_not_found", "cart_expired"].includes(storefrontErrorCode(e))) {
83
+ forget();
84
+ return null;
85
+ }
86
+ throw e;
87
+ }
88
+ },
89
+ /** addItem(view.addToCart) — a product with attributes needs the resolved variation_id. */
90
+ addItem(item) {
91
+ return cartAction("add-item", { quantity: 1, ...item });
92
+ },
93
+ updateItem(item_key, quantity) {
94
+ return cartAction("update-item", { item_key, quantity }); // ≤0 removes
95
+ },
96
+ removeItem(item_key) {
97
+ return cartAction("remove-item", { item_key });
98
+ },
99
+ applyCoupon(code) {
100
+ return cartAction("apply-coupon", { code });
101
+ },
102
+ removeCoupon() {
103
+ return cartAction("remove-coupon");
104
+ },
105
+ /** Recalculates shipping options + cost; 400 shipping_not_available belongs on the address form. */
106
+ setShippingAddress(address) {
107
+ return cartAction("set-shipping-address", { address });
108
+ },
109
+ chooseShippingMethod(method_id) {
110
+ return cartAction("choose-shipping-method", { method_id });
111
+ },
112
+
113
+ // ── checkout & return page ───────────────────────────────────────────
114
+ /** place-order; on success the cart is consumed, so the stored token is cleared. */
115
+ async placeOrder(details) {
116
+ const res = await inv("commerce/storefront-checkout", {
117
+ action: "place-order",
118
+ cart_token: token(),
119
+ return_url: typeof location !== "undefined" ? location.origin : undefined,
120
+ ...details,
121
+ });
122
+ forget();
123
+ return res;
124
+ },
125
+ /** The whole /order-received page in one call — pass nothing to read the URL params. */
126
+ completeReturn(params) {
127
+ const q = params ?? (typeof location !== "undefined"
128
+ ? new URLSearchParams(location.search)
129
+ : new URLSearchParams());
130
+ return inv("commerce/payments", {
131
+ action: "complete-return",
132
+ order_id: q.get("order_id"),
133
+ order_key: q.get("order_key"),
134
+ payment: q.get("payment") ?? undefined,
135
+ return_url: typeof location !== "undefined" ? location.origin : undefined,
136
+ });
137
+ },
138
+ };
139
+ }