@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.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/base44/agents/commerce/StoreAdmin.jsonc +64 -0
  4. package/base44/entities/commerce.Cart.jsonc +73 -0
  5. package/base44/entities/commerce.Coupon.jsonc +113 -0
  6. package/base44/entities/commerce.Customer.jsonc +96 -0
  7. package/base44/entities/commerce.DownloadPermission.jsonc +54 -0
  8. package/base44/entities/commerce.EmailLog.jsonc +43 -0
  9. package/base44/entities/commerce.Order.jsonc +287 -0
  10. package/base44/entities/commerce.OrderNote.jsonc +31 -0
  11. package/base44/entities/commerce.OrderRefund.jsonc +64 -0
  12. package/base44/entities/commerce.PaymentGateway.jsonc +48 -0
  13. package/base44/entities/commerce.Product.jsonc +291 -0
  14. package/base44/entities/commerce.ProductAttribute.jsonc +39 -0
  15. package/base44/entities/commerce.ProductAttributeTerm.jsonc +38 -0
  16. package/base44/entities/commerce.ProductCategory.jsonc +51 -0
  17. package/base44/entities/commerce.ProductReview.jsonc +48 -0
  18. package/base44/entities/commerce.ProductTag.jsonc +30 -0
  19. package/base44/entities/commerce.ProductVariation.jsonc +167 -0
  20. package/base44/entities/commerce.ShippingClass.jsonc +30 -0
  21. package/base44/entities/commerce.ShippingZone.jsonc +41 -0
  22. package/base44/entities/commerce.ShippingZoneMethod.jsonc +84 -0
  23. package/base44/entities/commerce.StoreSettings.jsonc +23 -0
  24. package/base44/entities/commerce.TaxClass.jsonc +23 -0
  25. package/base44/entities/commerce.TaxRate.jsonc +68 -0
  26. package/base44/entities/commerce.Webhook.jsonc +57 -0
  27. package/base44/entities/commerce.WebhookDelivery.jsonc +45 -0
  28. package/base44/functions/commerce/admin-coupons/entry.ts +100 -0
  29. package/base44/functions/commerce/admin-customers/entry.ts +141 -0
  30. package/base44/functions/commerce/admin-orders/entry.ts +396 -0
  31. package/base44/functions/commerce/admin-orders/helpers.ts +246 -0
  32. package/base44/functions/commerce/admin-products/entry.ts +506 -0
  33. package/base44/functions/commerce/admin-refunds/entry.ts +158 -0
  34. package/base44/functions/commerce/admin-reports/entry.ts +283 -0
  35. package/base44/functions/commerce/admin-reviews/entry.ts +66 -0
  36. package/base44/functions/commerce/admin-tools/entry.ts +261 -0
  37. package/base44/functions/commerce/admin-webhooks/entry.ts +52 -0
  38. package/base44/functions/commerce/payment-webhook/entry.ts +135 -0
  39. package/base44/functions/commerce/payments/entry.ts +238 -0
  40. package/base44/functions/commerce/seed-store/defaults.ts +162 -0
  41. package/base44/functions/commerce/seed-store/entry.ts +310 -0
  42. package/base44/functions/commerce/seed-store/sample-data.ts +349 -0
  43. package/base44/functions/commerce/storefront-account/entry.ts +207 -0
  44. package/base44/functions/commerce/storefront-cart/cart-pricing.ts +258 -0
  45. package/base44/functions/commerce/storefront-cart/entry.ts +283 -0
  46. package/base44/functions/commerce/storefront-catalog/entry.ts +459 -0
  47. package/base44/functions/commerce/storefront-checkout/cart-pricing.ts +258 -0
  48. package/base44/functions/commerce/storefront-checkout/entry.ts +485 -0
  49. package/base44/shared/commerce/auth.ts +60 -0
  50. package/base44/shared/commerce/coupons.ts +257 -0
  51. package/base44/shared/commerce/data/continents.ts +75 -0
  52. package/base44/shared/commerce/data/countries.ts +307 -0
  53. package/base44/shared/commerce/data/currencies.ts +46 -0
  54. package/base44/shared/commerce/email-templates.ts +240 -0
  55. package/base44/shared/commerce/emails.ts +225 -0
  56. package/base44/shared/commerce/money.ts +66 -0
  57. package/base44/shared/commerce/orders.ts +251 -0
  58. package/base44/shared/commerce/payments.ts +495 -0
  59. package/base44/shared/commerce/reviews.ts +36 -0
  60. package/base44/shared/commerce/scan.ts +57 -0
  61. package/base44/shared/commerce/sequence.ts +35 -0
  62. package/base44/shared/commerce/settings.ts +57 -0
  63. package/base44/shared/commerce/shipping.ts +215 -0
  64. package/base44/shared/commerce/stock.ts +227 -0
  65. package/base44/shared/commerce/stripe.ts +463 -0
  66. package/base44/shared/commerce/tax.ts +136 -0
  67. package/base44/shared/commerce/totals.ts +314 -0
  68. package/base44/shared/commerce/webhooks.ts +116 -0
  69. package/package.json +37 -0
  70. package/scripts/install.js +156 -0
  71. package/skills/commerce/SKILL.md +62 -0
  72. package/skills/commerce/docs/api-admin.md +186 -0
  73. package/skills/commerce/docs/api-storefront.md +408 -0
  74. package/skills/commerce/installation-guidelines.md +91 -0
  75. package/skills/commerce/post-installation.md +157 -0
  76. package/skills/commerce/references/emails.md +13 -0
  77. package/skills/commerce/references/guest-access-security.md +18 -0
  78. package/skills/commerce/references/limits-and-performance.md +16 -0
  79. package/skills/commerce/references/media-and-downloads.md +4 -0
  80. package/skills/commerce/references/online-payments.md +201 -0
  81. package/skills/commerce/references/product-render.md +87 -0
  82. package/skills/commerce/references/scheduled-work.md +19 -0
  83. package/skills/commerce/references/storefront-product-page.md +83 -0
  84. package/skills/commerce/references/webhooks.md +8 -0
  85. package/src/commerce/admin/README.md +107 -0
  86. package/src/commerce/admin/bot/Markdown.jsx +138 -0
  87. package/src/commerce/admin/bot/StoreAdminBot.jsx +249 -0
  88. package/src/commerce/admin/bot/pipe-tables.js +116 -0
  89. package/src/commerce/admin/components/AddressForm.jsx +78 -0
  90. package/src/commerce/admin/components/ConfirmDialog.jsx +52 -0
  91. package/src/commerce/admin/components/CountrySelect.jsx +81 -0
  92. package/src/commerce/admin/components/DataTable.jsx +192 -0
  93. package/src/commerce/admin/components/DateRangePicker.jsx +91 -0
  94. package/src/commerce/admin/components/EmptyState.jsx +17 -0
  95. package/src/commerce/admin/components/MediaUploader.jsx +116 -0
  96. package/src/commerce/admin/components/MetaDataEditor.jsx +45 -0
  97. package/src/commerce/admin/components/MoneyInput.jsx +50 -0
  98. package/src/commerce/admin/components/PageHeader.jsx +29 -0
  99. package/src/commerce/admin/components/RichTextarea.jsx +21 -0
  100. package/src/commerce/admin/components/SearchSelect.jsx +142 -0
  101. package/src/commerce/admin/components/StatusBadge.jsx +17 -0
  102. package/src/commerce/admin/context/BasePathContext.jsx +26 -0
  103. package/src/commerce/admin/context/SettingsContext.jsx +207 -0
  104. package/src/commerce/admin/hooks/useAsync.js +46 -0
  105. package/src/commerce/admin/hooks/useDebounce.js +11 -0
  106. package/src/commerce/admin/hooks/useMoney.js +52 -0
  107. package/src/commerce/admin/hooks/usePagedList.js +83 -0
  108. package/src/commerce/admin/hooks/usePaymentProvider.js +27 -0
  109. package/src/commerce/admin/hooks/useRealtime.js +129 -0
  110. package/src/commerce/admin/index.jsx +34 -0
  111. package/src/commerce/admin/layout/AccessDenied.jsx +54 -0
  112. package/src/commerce/admin/layout/AdminLayout.jsx +33 -0
  113. package/src/commerce/admin/layout/AuthGuard.jsx +84 -0
  114. package/src/commerce/admin/layout/Sidebar.jsx +130 -0
  115. package/src/commerce/admin/layout/Topbar.jsx +94 -0
  116. package/src/commerce/admin/lib/api.js +55 -0
  117. package/src/commerce/admin/lib/constants.js +157 -0
  118. package/src/commerce/admin/lib/format.js +27 -0
  119. package/src/commerce/admin/lib/geo-data.js +125 -0
  120. package/src/commerce/admin/lib/order-utils.js +147 -0
  121. package/src/commerce/admin/lib/paths.js +35 -0
  122. package/src/commerce/admin/lib/product-utils.js +55 -0
  123. package/src/commerce/admin/pages/Dashboard.jsx +245 -0
  124. package/src/commerce/admin/pages/coupons/CouponEditor.jsx +565 -0
  125. package/src/commerce/admin/pages/coupons/CouponsList.jsx +172 -0
  126. package/src/commerce/admin/pages/customers/CustomerEditor.jsx +318 -0
  127. package/src/commerce/admin/pages/customers/CustomersList.jsx +169 -0
  128. package/src/commerce/admin/pages/orders/OrderEditor.jsx +952 -0
  129. package/src/commerce/admin/pages/orders/OrdersList.jsx +227 -0
  130. package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +149 -0
  131. package/src/commerce/admin/pages/orders/components/DownloadPermissionsPanel.jsx +119 -0
  132. package/src/commerce/admin/pages/orders/components/LineItemsTable.jsx +208 -0
  133. package/src/commerce/admin/pages/orders/components/OrderNotesPanel.jsx +123 -0
  134. package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +199 -0
  135. package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +239 -0
  136. package/src/commerce/admin/pages/orders/components/TotalsBox.jsx +52 -0
  137. package/src/commerce/admin/pages/products/AttributeTerms.jsx +180 -0
  138. package/src/commerce/admin/pages/products/Attributes.jsx +183 -0
  139. package/src/commerce/admin/pages/products/Categories.jsx +236 -0
  140. package/src/commerce/admin/pages/products/ProductEditor.jsx +267 -0
  141. package/src/commerce/admin/pages/products/ProductsList.jsx +391 -0
  142. package/src/commerce/admin/pages/products/Reviews.jsx +255 -0
  143. package/src/commerce/admin/pages/products/Tags.jsx +150 -0
  144. package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +132 -0
  145. package/src/commerce/admin/pages/products/components/PublishBox.jsx +101 -0
  146. package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +243 -0
  147. package/src/commerce/admin/pages/products/components/tabs/AdvancedTab.jsx +48 -0
  148. package/src/commerce/admin/pages/products/components/tabs/AttributesTab.jsx +208 -0
  149. package/src/commerce/admin/pages/products/components/tabs/DownloadsTab.jsx +91 -0
  150. package/src/commerce/admin/pages/products/components/tabs/ExternalTab.jsx +41 -0
  151. package/src/commerce/admin/pages/products/components/tabs/GeneralTab.jsx +103 -0
  152. package/src/commerce/admin/pages/products/components/tabs/InventoryTab.jsx +93 -0
  153. package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +102 -0
  154. package/src/commerce/admin/pages/products/components/tabs/ShippingTab.jsx +86 -0
  155. package/src/commerce/admin/pages/products/components/tabs/VariationsTab.jsx +377 -0
  156. package/src/commerce/admin/pages/reports/Reports.jsx +416 -0
  157. package/src/commerce/admin/pages/settings/EmailsSettings.jsx +240 -0
  158. package/src/commerce/admin/pages/settings/GeneralSettings.jsx +232 -0
  159. package/src/commerce/admin/pages/settings/InventorySettings.jsx +146 -0
  160. package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +260 -0
  161. package/src/commerce/admin/pages/settings/ProductsSettings.jsx +118 -0
  162. package/src/commerce/admin/pages/settings/SettingsLayout.jsx +53 -0
  163. package/src/commerce/admin/pages/settings/ShippingSettings.jsx +304 -0
  164. package/src/commerce/admin/pages/settings/ShippingZoneEditor.jsx +514 -0
  165. package/src/commerce/admin/pages/settings/TaxRatesTable.jsx +231 -0
  166. package/src/commerce/admin/pages/settings/TaxSettings.jsx +281 -0
  167. package/src/commerce/admin/pages/settings/useGroupForm.jsx +76 -0
  168. package/src/commerce/admin/pages/status/WebhookEditor.jsx +296 -0
  169. package/src/commerce/admin/pages/status/Webhooks.jsx +53 -0
  170. package/src/commerce/admin/routes.jsx +151 -0
  171. package/src/commerce/utils/index.js +19 -0
  172. package/src/commerce/utils/shipping-promos.js +99 -0
  173. package/src/commerce/utils/variants.js +411 -0
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Order lifecycle engine. transitionOrder() is the ONLY place status changes
3
+ * happen — it derives side effects from flag diffs (stock_reduced,
4
+ * coupon_usages_counted, download_permissions_granted) so double-effects are
5
+ * impossible even when an admin forces unusual transitions.
6
+ *
7
+ * Side-effect matrix (see plan Part 3): stock, coupon usage, dates, emails,
8
+ * webhooks, download permissions per entering status.
9
+ */
10
+ import { getSettings } from "./settings.ts";
11
+ import { reduceStock, restoreStock } from "./stock.ts";
12
+ import { countUsage, uncountUsage } from "./coupons.ts";
13
+ import { sendOrderEmail } from "./emails.ts";
14
+ import { dispatch } from "./webhooks.ts";
15
+
16
+ export const ORDER_STATUSES = [
17
+ "pending", "processing", "on-hold", "completed", "cancelled", "refunded", "failed",
18
+ ] as const;
19
+
20
+ export interface TransitionOpts {
21
+ /** True when creating a brand-new order (fires order.created + creation effects). */
22
+ isCreation?: boolean;
23
+ /** Custom system note; a default "status changed" note is added otherwise. */
24
+ note?: string;
25
+ /** Suppress the automatic order note. */
26
+ skipNote?: boolean;
27
+ /** Who did it ("system" default; admin email for admin actions). */
28
+ actor?: string;
29
+ /** Pre-fetched settings groups (perf). */
30
+ settings?: Record<string, any>;
31
+ /** Extra fields to persist alongside the status patch. */
32
+ extraPatch?: Record<string, any>;
33
+ }
34
+
35
+ /**
36
+ * Transition `order` to `newStatus`, applying every lifecycle side effect exactly
37
+ * once. Mutates and returns the in-memory order; persists the patch.
38
+ */
39
+ export async function transitionOrder(sr: any, order: any, newStatus: string, opts: TransitionOpts = {}): Promise<any> {
40
+ const prev = order.status;
41
+ const isCreation = !!opts.isCreation;
42
+ if (!isCreation && prev === newStatus) return order;
43
+ if (!ORDER_STATUSES.includes(newStatus as any)) {
44
+ throw new Error(`Unknown order status: ${newStatus}`);
45
+ }
46
+
47
+ const settings = opts.settings ?? (await getSettings(sr));
48
+ const now = new Date().toISOString();
49
+ const patch: Record<string, any> = { status: newStatus, ...(opts.extraPatch ?? {}) };
50
+
51
+ const ensureReduced = async () => {
52
+ if (!order.stock_reduced) {
53
+ await reduceStock(sr, order, { settings }); // sets order.stock_reduced=true
54
+ patch.stock_reduced = true;
55
+ }
56
+ };
57
+ const restore = async () => {
58
+ if (order.stock_reduced) {
59
+ await restoreStock(sr, order, { settings }); // sets order.stock_reduced=false
60
+ patch.stock_reduced = false;
61
+ }
62
+ };
63
+ const count = async () => {
64
+ if (!order.coupon_usages_counted && (order.coupon_lines || []).length) {
65
+ await countUsage(sr, order);
66
+ order.coupon_usages_counted = true;
67
+ patch.coupon_usages_counted = true;
68
+ }
69
+ };
70
+ const uncount = async () => {
71
+ if (order.coupon_usages_counted) {
72
+ await uncountUsage(sr, order);
73
+ order.coupon_usages_counted = false;
74
+ patch.coupon_usages_counted = false;
75
+ }
76
+ };
77
+ const clearHold = () => {
78
+ if (order.hold_expires_at) {
79
+ order.hold_expires_at = null;
80
+ patch.hold_expires_at = null;
81
+ }
82
+ };
83
+ const grant = async () => {
84
+ if (!order.download_permissions_granted) {
85
+ const granted = await grantDownloadPermissions(sr, order);
86
+ if (granted > 0) {
87
+ order.download_permissions_granted = true;
88
+ patch.download_permissions_granted = true;
89
+ }
90
+ }
91
+ };
92
+ const revoke = async () => {
93
+ if (order.download_permissions_granted) {
94
+ await revokeDownloadPermissions(sr, order);
95
+ order.download_permissions_granted = false;
96
+ patch.download_permissions_granted = false;
97
+ }
98
+ };
99
+ const email = (type: string) => sendOrderEmail(sr, type, order, { settings });
100
+
101
+ switch (newStatus) {
102
+ case "pending":
103
+ if (isCreation) {
104
+ await ensureReduced();
105
+ await email("new_order");
106
+ }
107
+ break;
108
+ case "processing":
109
+ await ensureReduced();
110
+ clearHold();
111
+ await count();
112
+ if (!order.date_paid) { order.date_paid = now; patch.date_paid = now; }
113
+ await email("new_order"); // deduped if already sent
114
+ await email("processing_order");
115
+ if (await isVirtualOnly(sr, order)) await grant();
116
+ break;
117
+ case "on-hold":
118
+ await ensureReduced();
119
+ clearHold();
120
+ await count();
121
+ await email("new_order");
122
+ await email("on_hold_order");
123
+ break;
124
+ case "completed":
125
+ await ensureReduced();
126
+ await count();
127
+ if (!order.date_paid) { order.date_paid = now; patch.date_paid = now; }
128
+ order.date_completed = now;
129
+ patch.date_completed = now;
130
+ await email("completed_order");
131
+ await grant();
132
+ break;
133
+ case "cancelled":
134
+ await restore();
135
+ await uncount();
136
+ await email("cancelled_order");
137
+ await revoke();
138
+ break;
139
+ case "failed":
140
+ await restore();
141
+ await uncount();
142
+ await email("failed_order");
143
+ break;
144
+ case "refunded":
145
+ // stock restocking is handled per-refund by commerce/admin-refunds; coupons stay counted
146
+ await email("refunded_order");
147
+ await revoke();
148
+ break;
149
+ }
150
+
151
+ order.status = newStatus;
152
+ Object.assign(order, patch);
153
+ await sr.entities["commerce.Order"].update(order.id, patch);
154
+
155
+ if (!opts.skipNote) {
156
+ const note = opts.note ??
157
+ (isCreation
158
+ ? `Order created via ${order.created_via || "checkout"} (${newStatus}).`
159
+ : `Order status changed from ${prev} to ${newStatus}.`);
160
+ try {
161
+ await sr.entities["commerce.OrderNote"].create({
162
+ order_id: order.id,
163
+ note,
164
+ is_customer_note: false,
165
+ added_by: opts.actor ?? "system",
166
+ });
167
+ } catch (e) { console.error("order note failed:", e); }
168
+ }
169
+
170
+ await dispatch(sr, isCreation ? "order.created" : "order.updated", { ...order });
171
+ return order;
172
+ }
173
+
174
+ /** True when every line item's product is virtual (no physical fulfillment). */
175
+ async function isVirtualOnly(sr: any, order: any): Promise<boolean> {
176
+ const lines = order.line_items || [];
177
+ if (!lines.length) return false;
178
+ for (const line of lines) {
179
+ try {
180
+ const product = await sr.entities["commerce.Product"].get(line.product_id);
181
+ if (!product?.virtual && !product?.downloadable) return false;
182
+ } catch { return false; }
183
+ }
184
+ return true;
185
+ }
186
+
187
+ /**
188
+ * Create DownloadPermission rows for every downloadable file on the order's
189
+ * lines. Returns how many permissions were created.
190
+ */
191
+ export async function grantDownloadPermissions(sr: any, order: any): Promise<number> {
192
+ let created = 0;
193
+ for (const line of order.line_items || []) {
194
+ let product: any = null;
195
+ let variation: any = null;
196
+ try { product = line.product_id ? await sr.entities["commerce.Product"].get(line.product_id) : null; } catch { /* skip */ }
197
+ try { variation = line.variation_id ? await sr.entities["commerce.ProductVariation"].get(line.variation_id) : null; } catch { /* skip */ }
198
+ const src = variation?.downloadable ? variation : (product?.downloadable ? product : null);
199
+ if (!src) continue;
200
+
201
+ const limit = src.download_limit ?? -1;
202
+ const expiryDays = src.download_expiry ?? -1;
203
+ const expires = expiryDays > 0
204
+ ? new Date(Date.now() + expiryDays * 86_400_000).toISOString()
205
+ : null;
206
+
207
+ for (const dl of src.downloads || []) {
208
+ await sr.entities["commerce.DownloadPermission"].create({
209
+ order_id: order.id,
210
+ order_key: order.order_key ?? "",
211
+ customer_email: order.billing?.email ?? "",
212
+ product_id: line.product_id,
213
+ variation_id: line.variation_id ?? "",
214
+ download_name: dl.name ?? "Download",
215
+ file_url: dl.file_url ?? "",
216
+ downloads_remaining: limit,
217
+ access_expires: expires,
218
+ download_count: 0,
219
+ });
220
+ created++;
221
+ }
222
+ }
223
+ return created;
224
+ }
225
+
226
+ /** Remove every download permission attached to the order. */
227
+ export async function revokeDownloadPermissions(sr: any, order: any): Promise<void> {
228
+ try {
229
+ await sr.entities["commerce.DownloadPermission"].deleteMany({ order_id: order.id });
230
+ } catch (e) {
231
+ console.error("revokeDownloadPermissions failed:", e);
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Strip internals before handing an order to its customer (commerce/storefront-account,
237
+ * checkout responses): no ip/user-agent, no lifecycle flags, no email ledger.
238
+ */
239
+ export function serializeOrderForCustomer(order: any): any {
240
+ const {
241
+ customer_ip: _ip,
242
+ customer_user_agent: _ua,
243
+ stock_reduced: _sr,
244
+ coupon_usages_counted: _cc,
245
+ download_permissions_granted: _dp,
246
+ hold_expires_at: _he,
247
+ emails_sent: _es,
248
+ ...safe
249
+ } = order ?? {};
250
+ return safe;
251
+ }