@base44/app-plugin-commerce 0.1.3 → 0.1.5

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 (86) hide show
  1. package/README.md +11 -11
  2. package/base44/agents/commerce/StoreAdmin.jsonc +7 -7
  3. package/base44/entities/commerce.Order.jsonc +1 -1
  4. package/base44/entities/commerce.PaymentGateway.jsonc +3 -3
  5. package/base44/entities/commerce.Product.jsonc +9 -45
  6. package/base44/entities/commerce.ProductAttribute.jsonc +7 -19
  7. package/base44/entities/commerce.ProductAttributeTerm.jsonc +4 -10
  8. package/base44/entities/commerce.ProductTag.jsonc +1 -8
  9. package/base44/entities/commerce.ProductVariation.jsonc +1 -1
  10. package/base44/entities/commerce.StoreSettings.jsonc +1 -1
  11. package/base44/functions/commerce/admin-products/entry.ts +73 -121
  12. package/base44/functions/commerce/admin-refunds/entry.ts +1 -1
  13. package/base44/functions/commerce/admin-reports/entry.ts +1 -1
  14. package/base44/functions/commerce/admin-tools/entry.ts +4 -3
  15. package/base44/functions/commerce/payments/entry.ts +0 -2
  16. package/base44/functions/commerce/seed-store/defaults.ts +13 -39
  17. package/base44/functions/commerce/seed-store/entry.ts +50 -166
  18. package/base44/functions/commerce/seed-store/sample-data.ts +28 -66
  19. package/base44/functions/commerce/seed-store/seed-catalog.ts +717 -0
  20. package/base44/functions/commerce/storefront-cart/entry.ts +0 -7
  21. package/base44/functions/commerce/storefront-catalog/entry.ts +12 -21
  22. package/base44/functions/commerce/storefront-checkout/entry.ts +5 -12
  23. package/base44/shared/commerce/catalog.ts +134 -0
  24. package/base44/shared/commerce/coupons.ts +4 -7
  25. package/base44/shared/commerce/email-templates.ts +15 -13
  26. package/base44/shared/commerce/emails.ts +4 -3
  27. package/base44/shared/commerce/payments.ts +8 -20
  28. package/base44/shared/commerce/products.ts +24 -0
  29. package/base44/shared/commerce/settings.ts +2 -3
  30. package/base44/shared/commerce/stock.ts +2 -4
  31. package/base44/shared/commerce/totals.ts +37 -42
  32. package/package.json +1 -1
  33. package/scripts/install.js +23 -7
  34. package/skills/commerce/SKILL.md +9 -7
  35. package/skills/commerce/docs/api-admin.md +20 -14
  36. package/skills/commerce/docs/api-storefront.md +39 -35
  37. package/skills/commerce/installation-guidelines.md +8 -7
  38. package/skills/commerce/post-installation.md +205 -84
  39. package/skills/commerce/references/admin-product-form.md +56 -0
  40. package/skills/commerce/references/emails.md +4 -3
  41. package/skills/commerce/references/guest-access-security.md +2 -2
  42. package/skills/commerce/references/limits-and-performance.md +1 -1
  43. package/skills/commerce/references/online-payments.md +8 -8
  44. package/skills/commerce/references/product-render.md +25 -23
  45. package/skills/commerce/references/reviews.md +20 -0
  46. package/skills/commerce/references/storefront-product-page.md +9 -9
  47. package/skills/commerce/references/webhooks.md +3 -1
  48. package/src/commerce/admin/README.md +4 -4
  49. package/src/commerce/admin/context/BasePathContext.jsx +5 -5
  50. package/src/commerce/admin/context/SettingsContext.jsx +4 -4
  51. package/src/commerce/admin/index.jsx +3 -3
  52. package/src/commerce/admin/layout/Sidebar.jsx +1 -9
  53. package/src/commerce/admin/lib/constants.js +0 -7
  54. package/src/commerce/admin/lib/paths.js +6 -6
  55. package/src/commerce/admin/lib/product-utils.js +18 -15
  56. package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +8 -8
  57. package/src/commerce/admin/pages/products/ProductEditor.jsx +10 -12
  58. package/src/commerce/admin/pages/products/ProductsList.jsx +3 -15
  59. package/src/commerce/admin/pages/products/components/AttributesSection.jsx +426 -0
  60. package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +33 -69
  61. package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +1 -1
  62. package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +1 -14
  63. package/src/commerce/admin/pages/products/components/tabs/ModifiersTab.jsx +19 -0
  64. package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +728 -0
  65. package/src/commerce/admin/pages/reports/Reports.jsx +3 -3
  66. package/src/commerce/admin/pages/settings/EmailsSettings.jsx +12 -14
  67. package/src/commerce/admin/pages/settings/GeneralSettings.jsx +6 -116
  68. package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +101 -62
  69. package/src/commerce/admin/pages/settings/SettingsLayout.jsx +2 -6
  70. package/src/commerce/admin/pages/settings/TaxSettings.jsx +0 -1
  71. package/src/commerce/admin/pages/status/WebhookEditor.jsx +3 -3
  72. package/src/commerce/admin/pages/status/Webhooks.jsx +2 -2
  73. package/src/commerce/admin/routes.jsx +10 -19
  74. package/src/commerce/utils/index.js +2 -2
  75. package/src/commerce/utils/shipping-promos.js +9 -6
  76. package/src/commerce/utils/variants.js +21 -21
  77. package/src/commerce/admin/pages/products/AttributeTerms.jsx +0 -180
  78. package/src/commerce/admin/pages/products/Attributes.jsx +0 -183
  79. package/src/commerce/admin/pages/products/Tags.jsx +0 -150
  80. package/src/commerce/admin/pages/products/components/tabs/AdvancedTab.jsx +0 -48
  81. package/src/commerce/admin/pages/products/components/tabs/AttributesTab.jsx +0 -208
  82. package/src/commerce/admin/pages/products/components/tabs/ExternalTab.jsx +0 -41
  83. package/src/commerce/admin/pages/products/components/tabs/GeneralTab.jsx +0 -103
  84. package/src/commerce/admin/pages/products/components/tabs/InventoryTab.jsx +0 -93
  85. package/src/commerce/admin/pages/products/components/tabs/ShippingTab.jsx +0 -86
  86. package/src/commerce/admin/pages/products/components/tabs/VariationsTab.jsx +0 -377
@@ -10,10 +10,21 @@ import { createClientFromRequest } from "npm:@base44/sdk";
10
10
  import { HttpError, requireAdmin } from "../../../shared/commerce/auth.ts";
11
11
  import { getSettings } from "../../../shared/commerce/settings.ts";
12
12
  import { deriveStockStatus } from "../../../shared/commerce/stock.ts";
13
+ import { isVariable } from "../../../shared/commerce/products.ts";
13
14
  import { sendStockEmail } from "../../../shared/commerce/emails.ts";
14
15
  import { dispatch } from "../../../shared/commerce/webhooks.ts";
15
16
  import { pageSlice, scanAll, textMatch } from "../../../shared/commerce/scan.ts";
16
- import { round2 } from "../../../shared/commerce/money.ts";
17
+ import {
18
+ adjustTermCounts,
19
+ assertUniqueSku,
20
+ bumpCount,
21
+ derivePricing,
22
+ deriveStock,
23
+ ensureUniqueCode,
24
+ ensureUniqueSlug,
25
+ rollUpParent,
26
+ slugify,
27
+ } from "../../../shared/commerce/catalog.ts";
17
28
 
18
29
  const ok = (data: unknown, status = 200) => Response.json({ success: true, data }, { status });
19
30
  const fail = (status: number, error: string, code?: string) =>
@@ -55,87 +66,6 @@ Deno.serve(async (req) => {
55
66
  }
56
67
  });
57
68
 
58
- // ── derivations ──────────────────────────────────────────────────────────────
59
-
60
- function slugify(name: string): string {
61
- return String(name || "")
62
- .toLowerCase()
63
- .trim()
64
- .replace(/[^a-z0-9]+/g, "-")
65
- .replace(/^-+|-+$/g, "") || "product";
66
- }
67
-
68
- /** price/on_sale from the sale window. Mutates rec. */
69
- function derivePricing(rec: any): void {
70
- const now = Date.now();
71
- const from = rec.date_on_sale_from ? new Date(rec.date_on_sale_from).getTime() : -Infinity;
72
- const to = rec.date_on_sale_to ? new Date(rec.date_on_sale_to).getTime() : Infinity;
73
- const saleActive = rec.sale_price != null && rec.sale_price !== "" && now >= from && now <= to;
74
- rec.on_sale = !!saleActive;
75
- const effective = saleActive ? rec.sale_price : rec.regular_price;
76
- if (effective != null && effective !== "") rec.price = round2(Number(effective));
77
- }
78
-
79
- /** stock_status derivation when stock is managed. Mutates rec. */
80
- function deriveStock(rec: any, outThreshold: number, managed: boolean): void {
81
- if (managed) {
82
- rec.stock_status = deriveStockStatus(
83
- Number(rec.stock_quantity ?? 0),
84
- rec.backorders ?? "no",
85
- outThreshold,
86
- );
87
- } else if (!rec.stock_status) {
88
- rec.stock_status = "instock";
89
- }
90
- }
91
-
92
- // ── uniqueness ───────────────────────────────────────────────────────────────
93
-
94
- async function ensureUniqueSlug(
95
- sr: any,
96
- slug: string,
97
- selfId?: string,
98
- entity = "commerce.Product",
99
- scope?: Record<string, any>,
100
- ): Promise<string> {
101
- let candidate = slug;
102
- for (let i = 2; i < 100; i++) {
103
- const hits = (await sr.entities[entity].filter({ ...(scope ?? {}), slug: candidate }, undefined, 2)) ?? [];
104
- if (!hits.some((p: any) => p.id !== selfId)) return candidate;
105
- candidate = `${slug}-${i}`;
106
- }
107
- return `${slug}-${crypto.randomUUID().slice(0, 6)}`;
108
- }
109
-
110
- /** SKU must be unique across products AND variations. Throws duplicate_sku. */
111
- async function assertUniqueSku(sr: any, sku: string, opts: { productId?: string; variationId?: string }): Promise<void> {
112
- if (!sku) return;
113
- const prods = (await sr.entities["commerce.Product"].filter({ sku }, undefined, 2)) ?? [];
114
- if (prods.some((p: any) => p.id !== opts.productId)) {
115
- throw new HttpError(409, `SKU "${sku}" is already in use by another product.`, "duplicate_sku");
116
- }
117
- const vars = (await sr.entities["commerce.ProductVariation"].filter({ sku }, undefined, 2)) ?? [];
118
- if (vars.some((v: any) => v.id !== opts.variationId)) {
119
- throw new HttpError(409, `SKU "${sku}" is already in use by a product variation.`, "duplicate_sku");
120
- }
121
- }
122
-
123
- // ── taxonomy counts ──────────────────────────────────────────────────────────
124
-
125
- async function adjustTermCounts(sr: any, entity: string, prevIds: string[], nextIds: string[]): Promise<void> {
126
- const added = nextIds.filter((id) => !prevIds.includes(id));
127
- const removed = prevIds.filter((id) => !nextIds.includes(id));
128
- for (const id of added) await bumpCount(sr, entity, id, +1);
129
- for (const id of removed) await bumpCount(sr, entity, id, -1);
130
- }
131
-
132
- async function bumpCount(sr: any, entity: string, id: string, delta: number): Promise<void> {
133
- try {
134
- const rec = await sr.entities[entity].get(id);
135
- if (rec) await sr.entities[entity].update(id, { count: Math.max(0, (rec.count ?? 0) + delta) });
136
- } catch { /* stale reference — recount-terms repairs */ }
137
- }
138
-
139
69
  // ── save ─────────────────────────────────────────────────────────────────────
140
70
 
141
71
  async function save(sr: any, payload: any): Promise<any> {
@@ -155,7 +85,7 @@ async function save(sr: any, payload: any): Promise<any> {
155
85
  rec.slug = await ensureUniqueSlug(sr, slugify(rec.slug), rec.id);
156
86
  await assertUniqueSku(sr, rec.sku, { productId: rec.id });
157
87
  derivePricing(rec);
158
- deriveStock(rec, outThreshold, !!rec.manage_stock && rec.type !== "variable");
88
+ deriveStock(rec, outThreshold, !!rec.manage_stock);
159
89
 
160
90
  const { id: _id, created_date: _cd, updated_date: _ud, created_by: _cb, ...fields } = rec;
161
91
  let saved: any;
@@ -177,10 +107,10 @@ async function save(sr: any, payload: any): Promise<any> {
177
107
  let savedVariations: any[] | undefined;
178
108
  if (Array.isArray(variations)) {
179
109
  savedVariations = await diffVariations(sr, saved, variations, outThreshold);
180
- await rollUpParentStock(sr, saved, savedVariations);
181
- } else if (saved.type === "variable") {
110
+ await rollUpParent(sr, saved, savedVariations);
111
+ } else if (isVariable(saved)) {
182
112
  const existing = await scanAll(sr.entities["commerce.ProductVariation"], { product_id: saved.id });
183
- await rollUpParentStock(sr, saved, existing);
113
+ await rollUpParent(sr, saved, existing);
184
114
  }
185
115
 
186
116
  await dispatch(sr, prev ? "product.updated" : "product.created", saved);
@@ -213,19 +143,6 @@ async function diffVariations(sr: any, parent: any, incoming: any[], outThreshol
213
143
  return out;
214
144
  }
215
145
 
216
- /** Variable parent shows outofstock only when every purchasable variation is out. */
217
- async function rollUpParentStock(sr: any, parent: any, variations: any[]): Promise<void> {
218
- if (parent.type !== "variable" || parent.manage_stock) return;
219
- const live = variations.filter((v) => (v.status ?? "publish") === "publish");
220
- const status = live.length && live.every((v) => v.stock_status === "outofstock")
221
- ? "outofstock"
222
- : "instock";
223
- if (status !== parent.stock_status) {
224
- await sr.entities["commerce.Product"].update(parent.id, { stock_status: status });
225
- parent.stock_status = status;
226
- }
227
- }
228
-
229
146
  // ── delete / batch / duplicate ───────────────────────────────────────────────
230
147
 
231
148
  async function remove(sr: any, id: string): Promise<any> {
@@ -355,10 +272,9 @@ async function categoryWithDescendants(sr: any, rootId: string): Promise<Set<str
355
272
  }
356
273
 
357
274
  async function search(sr: any, payload: any): Promise<any> {
358
- const { q, category_id, type, stock_status, status, sort = "-created_date", limit = 20, skip = 0 } = payload;
275
+ const { q, category_id, stock_status, status, sort = "-created_date", limit = 20, skip = 0 } = payload;
359
276
 
360
277
  const query: Record<string, any> = {};
361
- if (type) query.type = type;
362
278
  if (status) query.status = status;
363
279
  if (stock_status) query.stock_status = stock_status;
364
280
 
@@ -393,10 +309,10 @@ function taxonomyEntity(taxonomy: string): string {
393
309
  }
394
310
 
395
311
  /**
396
- * Upsert a category, tag, attribute or attribute term. Exists so an agent (or
312
+ * Upsert a category, tag, attribute or attribute value. Exists so an agent (or
397
313
  * any API caller) can create these — referencing one from a product is useless
398
314
  * while the record itself can only be made in the admin UI. An attribute plus
399
- * its terms is what a variable product's `attributes[].options` draws on.
315
+ * its values is what a product's `attributes[].options` draws on.
400
316
  */
401
317
  async function saveTerm(sr: any, payload: any): Promise<any> {
402
318
  const entity = taxonomyEntity(payload.taxonomy);
@@ -407,30 +323,29 @@ async function saveTerm(sr: any, payload: any): Promise<any> {
407
323
  const fields: Record<string, any> = { name };
408
324
 
409
325
  if (entity === "commerce.ProductAttributeTerm") {
410
- // A term without its attribute is unreachable, and its slug is only required
411
- // to be unique inside that attribute.
326
+ // A value without its attribute is unreachable.
412
327
  const attributeId = String(term.attribute_id ?? "").trim();
413
- if (!attributeId) throw new HttpError(400, "term.attribute_id is required for an attribute term", "invalid_payload");
328
+ if (!attributeId) throw new HttpError(400, "term.attribute_id is required for an attribute value", "invalid_payload");
414
329
  if (!(await sr.entities["commerce.ProductAttribute"].get(attributeId))) {
415
330
  throw new HttpError(404, `Attribute ${attributeId} not found`, "not_found");
416
331
  }
417
332
  fields.attribute_id = attributeId;
418
- fields.description = term.description ?? "";
419
- fields.menu_order = Number(term.menu_order ?? 0) || 0;
420
- fields.slug = await ensureUniqueSlug(sr, slugify(term.slug || name), term.id, entity, { attribute_id: attributeId });
333
+ fields.order = Number(term.order ?? 0) || 0;
421
334
  } else if (entity === "commerce.ProductAttribute") {
422
- fields.type = term.type ?? "select";
423
- fields.order_by = term.order_by ?? "menu_order";
424
- fields.has_archives = !!term.has_archives;
425
- fields.slug = await ensureUniqueSlug(sr, slugify(term.slug || name), term.id, entity);
426
- } else {
335
+ fields.code = await ensureUniqueCode(sr, slugify(term.code || name), term.id);
336
+ fields.order = Number(term.order ?? 0) || 0;
337
+ } else if (entity === "commerce.ProductCategory") {
427
338
  fields.description = term.description ?? "";
428
339
  fields.slug = await ensureUniqueSlug(sr, slugify(term.slug || name), term.id, entity);
429
- if (entity === "commerce.ProductCategory") {
430
- fields.parent_id = term.parent_id ?? "";
431
- if (term.image !== undefined) fields.image = term.image;
432
- if (term.menu_order !== undefined) fields.menu_order = Number(term.menu_order) || 0;
433
- }
340
+ fields.parent_id = term.parent_id ?? "";
341
+ if (term.image !== undefined) fields.image = term.image;
342
+ if (term.menu_order !== undefined) fields.menu_order = Number(term.menu_order) || 0;
343
+ } else if (!term.id) {
344
+ // Tags are created by typing a name on the product form, so "create" has to
345
+ // mean get-or-create: a second Best Seller would split the tag in two.
346
+ const existing = (await scanAll(sr.entities[entity], null, "name"))
347
+ .find((t: any) => String(t.name ?? "").toLowerCase() === name.toLowerCase());
348
+ if (existing) return existing;
434
349
  }
435
350
 
436
351
  if (term.id) {
@@ -439,12 +354,47 @@ async function saveTerm(sr: any, payload: any): Promise<any> {
439
354
  // parent_id === own id would make the tree unwalkable (categoryWithDescendants loops on it).
440
355
  if (fields.parent_id === term.id) fields.parent_id = "";
441
356
  await sr.entities[entity].update(term.id, fields);
357
+ // Products store an attribute value by *name*, so a rename that stops here
358
+ // would leave every product and variant pointing at a value that is gone.
359
+ if (entity === "commerce.ProductAttributeTerm" && existing.name !== fields.name) {
360
+ await renameAttributeValue(sr, fields.attribute_id, existing.name, fields.name);
361
+ }
442
362
  return { ...existing, ...fields, id: term.id };
443
363
  }
444
364
  const withCount = entity === "commerce.ProductAttribute" ? fields : { ...fields, count: 0 };
445
365
  return await sr.entities[entity].create(withCount);
446
366
  }
447
367
 
368
+ /** Rewrite a renamed value across every product and variant that used it. */
369
+ async function renameAttributeValue(sr: any, attributeId: string, from: string, to: string): Promise<void> {
370
+ const products = await scanAll(sr.entities["commerce.Product"]);
371
+ for (const product of products) {
372
+ const rows = product.attributes ?? [];
373
+ if (!rows.some((a: any) => a.attribute_id === attributeId && (a.options ?? []).includes(from))) continue;
374
+
375
+ await sr.entities["commerce.Product"].update(product.id, {
376
+ attributes: rows.map((a: any) =>
377
+ a.attribute_id === attributeId
378
+ ? { ...a, options: (a.options ?? []).map((o: string) => (o === from ? to : o)) }
379
+ : a
380
+ ),
381
+ default_attributes: (product.default_attributes ?? []).map((d: any) =>
382
+ d.attribute_id === attributeId && d.option === from ? { ...d, option: to } : d
383
+ ),
384
+ });
385
+
386
+ const variations = await scanAll(sr.entities["commerce.ProductVariation"], { product_id: product.id });
387
+ for (const v of variations) {
388
+ if (!(v.attributes ?? []).some((a: any) => a.attribute_id === attributeId && a.option === from)) continue;
389
+ await sr.entities["commerce.ProductVariation"].update(v.id, {
390
+ attributes: v.attributes.map((a: any) =>
391
+ a.attribute_id === attributeId && a.option === from ? { ...a, option: to } : a
392
+ ),
393
+ });
394
+ }
395
+ }
396
+ }
397
+
448
398
  /**
449
399
  * Delete a term. For a category or tag, products keep the id in
450
400
  * category_ids/tag_ids — same as the admin UI, and the storefront skips ids that
@@ -494,13 +444,15 @@ async function deleteTerm(sr: any, payload: any): Promise<any> {
494
444
  /** Terms for the taxonomy, so a caller can reuse an existing one before creating a duplicate. */
495
445
  async function listTerms(sr: any, payload: any): Promise<any> {
496
446
  const entity = taxonomyEntity(payload.taxonomy);
497
- const sort = entity === "commerce.ProductCategory" || entity === "commerce.ProductAttributeTerm"
447
+ const sort = entity === "commerce.ProductCategory"
498
448
  ? "menu_order"
449
+ : entity === "commerce.ProductAttributeTerm" || entity === "commerce.ProductAttribute"
450
+ ? "order"
499
451
  : "name";
500
452
  const query = entity === "commerce.ProductAttributeTerm" && payload.attribute_id
501
453
  ? { attribute_id: String(payload.attribute_id) }
502
454
  : null;
503
455
  let rows = await scanAll(sr.entities[entity], query, sort);
504
- if (payload.q) rows = rows.filter((t: any) => textMatch(t.name, payload.q) || textMatch(t.slug, payload.q));
456
+ if (payload.q) rows = rows.filter((t: any) => textMatch(t.name, payload.q) || textMatch(t.code, payload.q));
505
457
  return pageSlice(rows, Number(payload.limit ?? 100), Number(payload.skip ?? 0));
506
458
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * commerce/admin-refunds — create/delete order refunds with optional restock and
3
- * (placeholder) gateway refunds.
3
+ * provider refunds through shared/commerce/payments.
4
4
  *
5
5
  * Actions: create | delete
6
6
  */
@@ -30,7 +30,7 @@ Deno.serve(async (req) => {
30
30
  case "top-sellers": return ok(await topSellers(sr, payload));
31
31
  case "stock": return ok(await stockReport(sr));
32
32
  case "orders-totals": return ok(await ordersTotals(sr));
33
- case "products-totals": return ok(await groupCount(sr.entities["commerce.Product"], "type"));
33
+ case "products-totals": return ok(await groupCount(sr.entities["commerce.Product"], "status"));
34
34
  case "customers-totals": return ok(await customersTotals(sr));
35
35
  case "coupons-totals": return ok(await groupCount(sr.entities["commerce.Coupon"], "discount_type"));
36
36
  case "reviews-totals": return ok(await groupCount(sr.entities["commerce.ProductReview"], "status"));
@@ -116,9 +116,10 @@ async function status(sr: any): Promise<any> {
116
116
  settings_groups: groups,
117
117
  counts,
118
118
  checks: {
119
- has_payment_gateways: (counts.PaymentGateway as number) > 0,
120
- has_default_zone: (counts.ShippingZone as number) > 0,
121
- taxes_enabled: settings.general?.enable_taxes ?? null,
119
+ // counts is keyed by the namespaced entity name — `counts.PaymentGateway`
120
+ // is undefined, which silently reported both checks as false.
121
+ has_payment_gateways: (counts["commerce.PaymentGateway"] as number) > 0,
122
+ has_default_zone: (counts["commerce.ShippingZone"] as number) > 0,
122
123
  },
123
124
  };
124
125
  }
@@ -120,7 +120,6 @@ Deno.serve(async (req: Request) => {
120
120
  successUrl: payload.success_url,
121
121
  cancelUrl: payload.cancel_url,
122
122
  returnUrl: payload.return_url,
123
- storeUrl: settings.general?.store_url,
124
123
  returnPath: settings.general?.order_received_path,
125
124
  });
126
125
  const link = await startOnlinePayment(sr, order, {
@@ -164,7 +163,6 @@ Deno.serve(async (req: Request) => {
164
163
  successUrl: payload.success_url,
165
164
  cancelUrl: payload.cancel_url,
166
165
  returnUrl: payload.return_url,
167
- storeUrl: settings.general?.store_url,
168
166
  returnPath: settings.general?.order_received_path,
169
167
  });
170
168
  const link = await startOnlinePayment(sr, result.order, {
@@ -24,17 +24,8 @@ export const SETTINGS_DEFAULTS: Array<{ group_id: string; values: Record<string,
24
24
  {
25
25
  group_id: "general",
26
26
  values: {
27
- store_name: "", // seed-store payload supplies it (the app/store name)
28
- // Public URL of the shopfront (e.g. https://shop.example.com). Used to
29
- // build the page a customer returns to after paying; when empty the
30
- // origin of the calling app is used instead.
31
- store_url: "",
32
27
  // Storefront route customers return to after paying; a mismatch is a 404.
33
28
  order_received_path: "/order-received",
34
- address: { address_1: "", address_2: "", city: "", state: "", postcode: "", country: "US" },
35
- enable_taxes: true,
36
- enable_coupons: true,
37
- calc_discounts_sequentially: false,
38
29
  currency: "USD",
39
30
  currency_position: "left", // left | right | left_space | right_space
40
31
  thousand_sep: ",",
@@ -70,7 +61,7 @@ export const SETTINGS_DEFAULTS: Array<{ group_id: string; values: Record<string,
70
61
  group_id: "tax",
71
62
  values: {
72
63
  prices_include_tax: false,
73
- tax_based_on: "shipping", // shipping | billing | base
64
+ tax_based_on: "shipping", // shipping | billing
74
65
  shipping_tax_class: "inherit", // inherit | standard | <class slug>
75
66
  display_prices_shop: "excl", // incl | excl
76
67
  display_prices_cart: "excl",
@@ -85,7 +76,9 @@ export const SETTINGS_DEFAULTS: Array<{ group_id: string; values: Record<string,
85
76
  {
86
77
  group_id: "emails",
87
78
  values: {
88
- from_name: "", // blank: emails.ts inherits general.store_name
79
+ // Subject line and sender name both. seed-store's payload supplies it;
80
+ // blank means SendEmail gets no from_name and Base44 sends as the app's name.
81
+ store_name: "",
89
82
  admin_recipients: [],
90
83
  ...EMAIL_TYPE_DEFAULTS,
91
84
  },
@@ -94,35 +87,16 @@ export const SETTINGS_DEFAULTS: Array<{ group_id: string; values: Record<string,
94
87
 
95
88
  export const GATEWAY_DEFAULTS = [
96
89
  {
97
- slug: "bacs",
98
- title: "Direct bank transfer",
99
- description: "Make your payment directly into our bank account. Please use your Order ID as the payment reference.",
90
+ slug: "offline",
91
+ title: "Offline payment",
92
+ description: "Pay outside the store — bank transfer on the details below, or cash on delivery. Please quote your order number.",
100
93
  enabled: true,
101
94
  order: 0,
102
- method_title: "Direct bank transfer",
103
- method_description: "Take payments in person via BACS. Orders are set on-hold until payment clears.",
95
+ method_title: "Offline payment",
96
+ method_description:
97
+ "Any payment settled outside the store: bank transfer, cash on delivery, or whatever you arrange with the customer. Orders are set on-hold until you confirm the money arrived.",
104
98
  settings: { account_details: [] as any[] },
105
99
  },
106
- {
107
- slug: "cheque",
108
- title: "Check payments",
109
- description: "Please send a check to our store address.",
110
- enabled: false,
111
- order: 1,
112
- method_title: "Check payments",
113
- method_description: "Take payments by check. Orders are set on-hold until payment clears.",
114
- settings: {},
115
- },
116
- {
117
- slug: "cod",
118
- title: "Cash on delivery",
119
- description: "Pay with cash upon delivery.",
120
- enabled: true,
121
- order: 2,
122
- method_title: "Cash on delivery",
123
- method_description: "Have your customers pay with cash (or by other means) upon delivery.",
124
- settings: { enable_for_methods: [] as string[], enable_for_virtual: false },
125
- },
126
100
  {
127
101
  slug: "stripe",
128
102
  title: "Credit card",
@@ -132,10 +106,10 @@ export const GATEWAY_DEFAULTS = [
132
106
  // storefront gateway list and the admin both check that live — so enabling it
133
107
  // here cannot strand a shopper on an unusable option.
134
108
  enabled: true,
135
- order: 3,
136
- method_title: "Online card payments",
109
+ order: 1,
110
+ method_title: "Online card payments (Stripe by default)",
137
111
  method_description:
138
- "Card payments through the connected payment provider (Stripe out of the box). Connect the provider's connector to start taking payments; customers only see this option once it is connected.",
112
+ "Card payments through the connected payment provider (Stripe out of the box). Set the provider up in the app's integrations to start taking payments; customers only see this option once it is connected.",
139
113
  settings: { connector: "stripe" },
140
114
  },
141
115
  ];