@base44/app-plugin-commerce 0.1.5 → 0.1.7

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 +11 -11
  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 +84 -31
  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 +76 -41
  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 -191
  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 +4 -5
  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
@@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input";
6
6
  import { Textarea } from "@/components/ui/textarea";
7
7
  import { Label } from "@/components/ui/label";
8
8
  import { Checkbox } from "@/components/ui/checkbox";
9
+ import { Switch } from "@/components/ui/switch";
9
10
  import { Badge } from "@/components/ui/badge";
10
11
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
11
12
  import { Separator } from "@/components/ui/separator";
@@ -29,6 +30,7 @@ import ConfirmDialog from "../../components/ConfirmDialog";
29
30
 
30
31
  const EMPTY = {
31
32
  code: "",
33
+ enabled: true,
32
34
  discount_type: "fixed_cart",
33
35
  amount: null,
34
36
  description: "",
@@ -48,12 +50,6 @@ const EMPTY = {
48
50
  limit_usage_to_x_items: null,
49
51
  };
50
52
 
51
- const TABS = [
52
- { id: "general", label: "General" },
53
- { id: "restriction", label: "Usage restriction" },
54
- { id: "limits", label: "Usage limits" },
55
- ];
56
-
57
53
  const randomCode = () =>
58
54
  Array.from(crypto.getRandomValues(new Uint8Array(8)))
59
55
  .map((b) => "abcdefghijklmnopqrstuvwxyz0123456789"[b % 36])
@@ -132,7 +128,6 @@ export default function CouponEditor() {
132
128
  const [saving, setSaving] = useState(false);
133
129
  const [confirmDelete, setConfirmDelete] = useState(false);
134
130
  const [deleting, setDeleting] = useState(false);
135
- const [tab, setTab] = useState("general");
136
131
 
137
132
  // Selected options with labels for the pickers.
138
133
  const [productOpts, setProductOpts] = useState({ include: [], exclude: [] });
@@ -284,95 +279,80 @@ export default function CouponEditor() {
284
279
  </CardContent>
285
280
  </Card>
286
281
 
287
- {/* Coupon data: vertical tabs */}
282
+ {/* Coupon data: stacked cards, like the product editor's sections */}
288
283
  <Card>
289
- <CardHeader className="pb-3">
290
- <CardTitle className="text-base">Coupon data</CardTitle>
284
+ <CardHeader className="pb-2">
285
+ <CardTitle className="text-base">General</CardTitle>
291
286
  </CardHeader>
292
- <CardContent className="p-0">
293
- <div className="flex min-h-72 flex-col sm:flex-row">
294
- <div className="flex shrink-0 flex-row gap-1 border-b p-2 sm:w-44 sm:flex-col sm:border-b-0 sm:border-r">
295
- {TABS.map((t) => (
296
- <Button
297
- key={t.id}
298
- variant={tab === t.id ? "secondary" : "ghost"}
299
- size="sm"
300
- className="justify-start"
301
- onClick={() => setTab(t.id)}
302
- >
303
- {t.label}
304
- </Button>
305
- ))}
306
- </div>
307
-
308
- <div className="flex-1 space-y-4 p-4">
309
- {tab === "general" && (
310
- <>
311
- <div className="grid gap-1.5 sm:max-w-sm">
312
- <Label>Discount type</Label>
313
- <Select value={coupon.discount_type} onValueChange={set("discount_type")}>
314
- <SelectTrigger>
315
- <SelectValue />
316
- </SelectTrigger>
317
- <SelectContent>
318
- {COUPON_TYPES.map((t) => (
319
- <SelectItem key={t.value} value={t.value}>
320
- {t.label}
321
- </SelectItem>
322
- ))}
323
- </SelectContent>
324
- </Select>
325
- </div>
326
- <div className="grid gap-1.5 sm:max-w-sm">
327
- <Label>Coupon amount {isPercent ? "(%)" : ""}</Label>
328
- {isPercent ? (
329
- <div className="relative">
330
- <Input
331
- type="number"
332
- min="0"
333
- max="100"
334
- step="any"
335
- value={coupon.amount ?? ""}
336
- onChange={(e) =>
337
- set("amount")(e.target.value === "" ? null : Number(e.target.value))
338
- }
339
- className="pr-8"
340
- />
341
- <span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-sm text-muted-foreground">
342
- %
343
- </span>
344
- </div>
345
- ) : (
346
- <MoneyInput value={coupon.amount} onChange={set("amount")} />
347
- )}
348
- </div>
349
- <label className="flex items-start gap-2 text-sm">
350
- <Checkbox
351
- checked={!!coupon.free_shipping}
352
- onCheckedChange={(v) => set("free_shipping")(!!v)}
353
- className="mt-0.5"
354
- />
355
- <span>
356
- Allow free shipping
357
- <span className="block text-xs text-muted-foreground">
358
- Grants access to free-shipping methods that require a coupon.
359
- </span>
360
- </span>
361
- </label>
362
- <div className="grid gap-1.5 sm:max-w-sm">
363
- <Label>Coupon expiry date</Label>
364
- <Input
365
- type="date"
366
- value={coupon.date_expires ? coupon.date_expires.slice(0, 10) : ""}
367
- onChange={(e) => set("date_expires")(e.target.value || null)}
368
- />
369
- </div>
370
- </>
371
- )}
287
+ <CardContent className="space-y-4">
288
+ <div className="grid gap-1.5 sm:max-w-sm">
289
+ <Label>Discount type</Label>
290
+ <Select value={coupon.discount_type} onValueChange={set("discount_type")}>
291
+ <SelectTrigger>
292
+ <SelectValue />
293
+ </SelectTrigger>
294
+ <SelectContent>
295
+ {COUPON_TYPES.map((t) => (
296
+ <SelectItem key={t.value} value={t.value}>
297
+ {t.label}
298
+ </SelectItem>
299
+ ))}
300
+ </SelectContent>
301
+ </Select>
302
+ </div>
303
+ <div className="grid gap-1.5 sm:max-w-sm">
304
+ <Label>Coupon amount {isPercent ? "(%)" : ""}</Label>
305
+ {isPercent ? (
306
+ <div className="relative">
307
+ <Input
308
+ type="number"
309
+ min="0"
310
+ max="100"
311
+ step="any"
312
+ value={coupon.amount ?? ""}
313
+ onChange={(e) =>
314
+ set("amount")(e.target.value === "" ? null : Number(e.target.value))
315
+ }
316
+ className="pr-8"
317
+ />
318
+ <span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-sm text-muted-foreground">
319
+ %
320
+ </span>
321
+ </div>
322
+ ) : (
323
+ <MoneyInput value={coupon.amount} onChange={set("amount")} />
324
+ )}
325
+ </div>
326
+ <label className="flex items-start gap-2 text-sm">
327
+ <Checkbox
328
+ checked={!!coupon.free_shipping}
329
+ onCheckedChange={(v) => set("free_shipping")(!!v)}
330
+ className="mt-0.5"
331
+ />
332
+ <span>
333
+ Allow free shipping
334
+ <span className="block text-xs text-muted-foreground">
335
+ Grants access to free-shipping methods that require a coupon.
336
+ </span>
337
+ </span>
338
+ </label>
339
+ <div className="grid gap-1.5 sm:max-w-sm">
340
+ <Label>Coupon expiry date</Label>
341
+ <Input
342
+ type="date"
343
+ value={coupon.date_expires ? coupon.date_expires.slice(0, 10) : ""}
344
+ onChange={(e) => set("date_expires")(e.target.value || null)}
345
+ />
346
+ </div>
347
+ </CardContent>
348
+ </Card>
372
349
 
373
- {tab === "restriction" && (
374
- <>
375
- <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
350
+ <Card>
351
+ <CardHeader className="pb-2">
352
+ <CardTitle className="text-base">Usage restriction</CardTitle>
353
+ </CardHeader>
354
+ <CardContent className="space-y-4">
355
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
376
356
  <div className="grid gap-1.5">
377
357
  <Label>Minimum spend ({money.code})</Label>
378
358
  <MoneyInput
@@ -467,54 +447,53 @@ export default function CouponEditor() {
467
447
  />
468
448
  </div>
469
449
  </div>
470
- <div className="grid gap-1.5">
471
- <Label>Allowed emails</Label>
472
- <EmailChips
473
- value={coupon.email_restrictions || []}
474
- onChange={set("email_restrictions")}
475
- />
476
- <p className="text-xs text-muted-foreground">
477
- Only these billing emails can use the coupon. Wildcards like{" "}
478
- <code>*@example.com</code> are allowed.
479
- </p>
480
- </div>
481
- </>
482
- )}
450
+ <div className="grid gap-1.5">
451
+ <Label>Allowed emails</Label>
452
+ <EmailChips
453
+ value={coupon.email_restrictions || []}
454
+ onChange={set("email_restrictions")}
455
+ />
456
+ <p className="text-xs text-muted-foreground">
457
+ Only these billing emails can use the coupon. Wildcards like{" "}
458
+ <code>*@example.com</code> are allowed.
459
+ </p>
460
+ </div>
461
+ </CardContent>
462
+ </Card>
483
463
 
484
- {tab === "limits" && (
485
- <div className="max-w-sm space-y-4">
486
- <div className="grid gap-1.5">
487
- <Label>Usage limit per coupon</Label>
488
- <NumberField value={coupon.usage_limit} onChange={set("usage_limit")} />
489
- <p className="text-xs text-muted-foreground">
490
- How many times the coupon can be used in total.
491
- </p>
492
- </div>
493
- <div className="grid gap-1.5">
494
- <Label>Limit usage to X items</Label>
495
- <NumberField
496
- value={coupon.limit_usage_to_x_items}
497
- onChange={set("limit_usage_to_x_items")}
498
- placeholder="Apply to all qualifying items"
499
- />
500
- <p className="text-xs text-muted-foreground">
501
- Maximum number of individual items the coupon applies to (product
502
- discounts).
503
- </p>
504
- </div>
505
- <div className="grid gap-1.5">
506
- <Label>Usage limit per user</Label>
507
- <NumberField
508
- value={coupon.usage_limit_per_user}
509
- onChange={set("usage_limit_per_user")}
510
- />
511
- <p className="text-xs text-muted-foreground">
512
- How many times a single customer (by billing email) can use it.
513
- </p>
514
- </div>
515
- </div>
516
- )}
517
- </div>
464
+ <Card>
465
+ <CardHeader className="pb-2">
466
+ <CardTitle className="text-base">Usage limits</CardTitle>
467
+ </CardHeader>
468
+ <CardContent className="max-w-sm space-y-4">
469
+ <div className="grid gap-1.5">
470
+ <Label>Usage limit per coupon</Label>
471
+ <NumberField value={coupon.usage_limit} onChange={set("usage_limit")} />
472
+ <p className="text-xs text-muted-foreground">
473
+ How many times the coupon can be used in total.
474
+ </p>
475
+ </div>
476
+ <div className="grid gap-1.5">
477
+ <Label>Limit usage to X items</Label>
478
+ <NumberField
479
+ value={coupon.limit_usage_to_x_items}
480
+ onChange={set("limit_usage_to_x_items")}
481
+ placeholder="Apply to all qualifying items"
482
+ />
483
+ <p className="text-xs text-muted-foreground">
484
+ Maximum number of individual items the coupon applies to (product
485
+ discounts).
486
+ </p>
487
+ </div>
488
+ <div className="grid gap-1.5">
489
+ <Label>Usage limit per user</Label>
490
+ <NumberField
491
+ value={coupon.usage_limit_per_user}
492
+ onChange={set("usage_limit_per_user")}
493
+ />
494
+ <p className="text-xs text-muted-foreground">
495
+ How many times a single customer (by billing email) can use it.
496
+ </p>
518
497
  </div>
519
498
  </CardContent>
520
499
  </Card>
@@ -527,6 +506,18 @@ export default function CouponEditor() {
527
506
  <CardTitle className="text-base">Publish</CardTitle>
528
507
  </CardHeader>
529
508
  <CardContent className="space-y-3">
509
+ <label className="flex items-center justify-between text-sm font-medium">
510
+ Enabled
511
+ <Switch
512
+ checked={coupon.enabled !== false}
513
+ onCheckedChange={(v) => set("enabled")(!!v)}
514
+ />
515
+ </label>
516
+ {coupon.enabled === false && (
517
+ <p className="text-xs text-muted-foreground">
518
+ Disabled — customers can't apply this coupon, and carts holding it drop it.
519
+ </p>
520
+ )}
530
521
  {!isNew && (
531
522
  <p className="text-sm text-muted-foreground">
532
523
  Used: <span className="font-medium text-foreground">{coupon.usage_count ?? 0}</span>{" "}
@@ -73,13 +73,20 @@ export default function CouponsList() {
73
73
  label: "Code",
74
74
  sortable: !debouncedQuery,
75
75
  render: (c) => (
76
- <Link
77
- to={href(`coupons/${c.id}`)}
78
- className="inline-block rounded bg-muted px-2 py-0.5 font-mono text-xs font-semibold uppercase tracking-wide hover:bg-muted/70"
79
- onClick={(e) => e.stopPropagation()}
80
- >
81
- {c.code}
82
- </Link>
76
+ <span className="inline-flex items-center gap-2">
77
+ <Link
78
+ to={href(`coupons/${c.id}`)}
79
+ className="inline-block rounded bg-muted px-2 py-0.5 font-mono text-xs font-semibold uppercase tracking-wide hover:bg-muted/70"
80
+ onClick={(e) => e.stopPropagation()}
81
+ >
82
+ {c.code}
83
+ </Link>
84
+ {c.enabled === false && (
85
+ <span className="rounded border px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
86
+ Disabled
87
+ </span>
88
+ )}
89
+ </span>
83
90
  ),
84
91
  },
85
92
  {
@@ -410,7 +410,7 @@ export default function OrderEditor() {
410
410
  sku: variation?.sku || product.sku || "",
411
411
  quantity,
412
412
  price,
413
- tax_class: variation?.tax_class || product.tax_class || "standard",
413
+ tax_group: product.tax_group || "Products",
414
414
  subtotal: price * quantity,
415
415
  subtotal_tax: 0,
416
416
  total: price * quantity,
@@ -797,7 +797,7 @@ export default function OrderEditor() {
797
797
  patchDraft({
798
798
  fee_lines: [
799
799
  ...(draft.fee_lines || []),
800
- { line_id: uid(), name, total: amount, total_tax: 0, tax_status: "taxable", tax_class: "standard" },
800
+ { line_id: uid(), name, total: amount, total_tax: 0, tax_status: "taxable", tax_group: "Products" },
801
801
  ],
802
802
  })
803
803
  }
@@ -812,7 +812,7 @@ export default function OrderEditor() {
812
812
  patchDraft({
813
813
  shipping_lines: [
814
814
  ...(draft.shipping_lines || []),
815
- { line_id: uid(), method_id: "flat_rate", instance_id: "", method_title: name, total: amount, total_tax: 0, taxes: [] },
815
+ { line_id: uid(), method_id: "rate", instance_id: "", method_title: name, total: amount, total_tax: 0, taxes: [] },
816
816
  ],
817
817
  })
818
818
  }
@@ -8,38 +8,35 @@ import { toast } from "sonner";
8
8
 
9
9
  import { call } from "../../../lib/api";
10
10
  import useMoney from "../../../hooks/useMoney";
11
- import usePaymentProvider from "../../../hooks/usePaymentProvider";
12
11
  import { formatDateTime } from "../../../lib/format";
13
12
 
13
+ /** The one gateway slug that pays online (see shared/commerce/payments.ts). */
14
+ const CARD_SLUG = "card";
15
+
14
16
  /**
15
17
  * Payment state for an order, plus the two things an operator wants when an
16
18
  * order is unpaid: a **payment link** to send the customer, and a way to
17
19
  * **re-check** whether the money arrived.
18
20
  *
19
- * Both come from `commerce/payments`, which is provider-neutral — this panel
20
- * never names a payment provider. When none is connected the actions are
21
- * disabled with a "No payment provider connected" tooltip rather than being
22
- * hidden, so the operator can see the capability exists and what unlocks it.
21
+ * Both come from `commerce/payments`. When the store has no card payment
22
+ * provider implemented, the backend answers with a clear error — the buttons
23
+ * stay available so the operator learns what unlocks them.
23
24
  *
24
25
  * Props: { order, onChanged }
25
26
  */
26
27
  export default function PaymentPanel({ order, onChanged }) {
27
28
  const { format } = useMoney();
28
- const provider = usePaymentProvider();
29
29
  const [working, setWorking] = useState(""); // link | verify
30
30
  const [link, setLink] = useState("");
31
31
  const [copied, setCopied] = useState(false);
32
32
 
33
33
  const paid = Boolean(order.date_paid) || ["processing", "completed", "refunded"].includes(order.status);
34
- const known = !provider.loading && Boolean(provider.gatewaySlug);
35
- const isOnline = known && order.payment_method === provider.gatewaySlug;
36
- // With a provider connected, an unpaid order can always be collected online —
37
- // whatever its current method is. An admin order starts with no method at all,
38
- // and "just send them a card link" is a normal thing to want for a manual one;
39
- // the backend records the switch on the order.
40
- const canAct = provider.connected && !paid;
41
- const willSwitchMethod = canAct && known && !isOnline;
42
- const disabledReason = !provider.connected ? "No payment provider connected" : undefined;
34
+ const isCard = order.payment_method === CARD_SLUG;
35
+ // An unpaid order can always be collected by card, whatever its current
36
+ // method — an admin order starts with no method at all, and "just send them
37
+ // a card link" is a normal thing to want for a manual one; the backend
38
+ // records the switch on the order.
39
+ const willSwitchMethod = !paid && !isCard;
43
40
 
44
41
  const createLink = async () => {
45
42
  setWorking("link");
@@ -117,22 +114,21 @@ export default function PaymentPanel({ order, onChanged }) {
117
114
  )}
118
115
  </dl>
119
116
 
120
- {known && !isOnline && !paid && !provider.connected && (
117
+ {!paid && !isCard && (
121
118
  <p className="text-xs text-muted-foreground">
122
119
  {order.payment_method
123
120
  ? `This order is set to be paid outside the store (${order.payment_method_title || order.payment_method}), so mark it paid by moving it to Processing once the money arrives.`
124
- : "No payment method is set on this order. Take payment outside the store and move it to Processing, or connect a payment provider to send the customer a payment link."}
121
+ : "No payment method is set on this order. Take payment outside the store and move it to Processing, or send the customer a payment link."}
125
122
  </p>
126
123
  )}
127
124
 
128
- {!paid && (provider.connected || isOnline) && (
125
+ {!paid && (
129
126
  <>
130
127
  <div className="flex flex-wrap gap-2">
131
128
  <Button
132
129
  variant="outline"
133
130
  size="sm"
134
- disabled={!canAct || Boolean(working) || provider.loading}
135
- title={disabledReason}
131
+ disabled={Boolean(working)}
136
132
  onClick={createLink}
137
133
  >
138
134
  {working === "link" ? (
@@ -145,8 +141,7 @@ export default function PaymentPanel({ order, onChanged }) {
145
141
  <Button
146
142
  variant="outline"
147
143
  size="sm"
148
- disabled={!canAct || Boolean(working) || provider.loading}
149
- title={disabledReason}
144
+ disabled={Boolean(working)}
150
145
  onClick={checkPayment}
151
146
  >
152
147
  {working === "verify" ? (
@@ -158,12 +153,6 @@ export default function PaymentPanel({ order, onChanged }) {
158
153
  </Button>
159
154
  </div>
160
155
 
161
- {!provider.connected && !provider.loading && (
162
- <p className="text-xs text-muted-foreground">
163
- No payment provider is connected, so this order can't be paid online yet.
164
- </p>
165
- )}
166
-
167
156
  {willSwitchMethod && !link && (
168
157
  <p className="text-xs text-muted-foreground">
169
158
  {order.payment_method
@@ -16,7 +16,6 @@ import { toast } from "sonner";
16
16
 
17
17
  import { call } from "../../../lib/api";
18
18
  import useMoney from "../../../hooks/useMoney";
19
- import usePaymentProvider from "../../../hooks/usePaymentProvider";
20
19
  import { formatDate } from "../../../lib/format";
21
20
  import { refundableQty, refundableAmount, round2 } from "../../../lib/order-utils";
22
21
 
@@ -33,15 +32,12 @@ export default function RefundPanel({ order, refunds, onDone }) {
33
32
  const [restock, setRestock] = useState(true);
34
33
  const [viaGateway, setViaGateway] = useState(false);
35
34
  const [busy, setBusy] = useState(false);
36
- const provider = usePaymentProvider();
37
35
 
38
36
  const maxRefund = refundableAmount(order);
39
37
  // Refunding through the provider is only possible when the order was actually
40
- // paid online and a provider is connected — otherwise the money was taken
41
- // outside the store and has to go back the same way.
42
- const canRefundOnline = Boolean(
43
- provider.connected && order.payment_method === provider.gatewaySlug && order.transaction_id,
44
- );
38
+ // paid by card and a payment reference is on record — otherwise the money was
39
+ // taken outside the store and has to go back the same way.
40
+ const canRefundOnline = Boolean(order.payment_method === "card" && order.transaction_id);
45
41
 
46
42
  const unitNet = (line) => (line.quantity ? (line.total || 0) / line.quantity : 0);
47
43
 
@@ -182,15 +178,13 @@ export default function RefundPanel({ order, refunds, onDone }) {
182
178
  <Checkbox checked={restock} onCheckedChange={(v) => setRestock(Boolean(v))} />
183
179
  Restock refunded items
184
180
  </label>
185
- {order.payment_method === provider.gatewaySlug && (
181
+ {order.payment_method === "card" && (
186
182
  <label
187
183
  className={`flex items-center gap-2 text-sm ${canRefundOnline ? "" : "text-muted-foreground"}`}
188
184
  title={
189
185
  canRefundOnline
190
186
  ? undefined
191
- : provider.connected
192
- ? "This order has no online payment on record to refund"
193
- : "No payment provider connected"
187
+ : "This order has no card payment on record to refund"
194
188
  }
195
189
  >
196
190
  <Checkbox