Repository navigation
Mercur v2.3.7
A smaller release with one new building block and a batch of fixes. Offers
learn about conditions, so operators can define "new", "refurbished" or "used"
and sellers pick one per listing. The admin order list gets a payment status
filter, server-side search in its filters and smarter search by order number.
Two security fixes close cross-seller gaps in the vendor API and in cart
promotions, and payouts no longer call the provider before a row is written.
⚠️ Before you upgrade
- Run migrations — the offer module adds an
offer_conditiontable and
offer.condition_id(seeded with a defaultnewcondition), and the payout
module widens thepayout.statuscheck constraint to acceptprocessing
(#1645, #1641). - Payouts are briefly
pending.createPayoutsnow inserts the row
before calling the provider, sopayout.createdfires for a pending row and
readers that assumed every row is already sent should checkstatus(#1641). - Seller promotions are scoped on every cart refresh. A vendor promotion
without seller-specific target rules, or an automatic one, no longer
discounts other sellers' items. If your storefront relied on that, it was a
bug (#1648).
✨ Highlights
🏷️ Offer conditions
Operators define the physical conditions an offer may be listed under through
/admin/offer-conditions (code, label, is_active, rank), and sellers pick
an active one via condition_id when creating or updating an offer. Admin,
vendor and store offer responses expose condition.{id,code,label} and filter
on condition_id. Deleting a condition in use is refused; deactivating keeps
existing offers but blocks new assignments. API only for now, with
createOfferConditionsWorkflow / update / delete hooks and
offer_condition.* events. (#1645)
📋 Admin order list
- Filter order groups by payment status. The status is computed from
payment collections, soGET /admin/order-groups?payment_status=captured
matches exactly what the list shows (#1639). - Search by the numbers operators actually see:
264,#264or#G258now
match the group's display id or any child order's display id (#1638). - Customer, Store and Sales channel filters search on the server, so stores
with more than 1000 customers or sellers can select any of them (#1640).
🔒 Security
Both issues below were reported privately by @gregbarajas, with clean
end-to-end reproductions on a v2.3.6 starter that made the fixes
straightforward. Thank you, Greg!
- Close cross-seller authorization gaps in the vendor API:
fieldstraversal
into other sellers' members, payment details and invite tokens, product
detail routes on another seller's drafts, capture of shared cart payments,
and offer / inventory body ids pointing at another seller's items or
locations (GHSA-mhch-gq7f-v5p7, #1649). - Keep seller promotions scoped to the seller's own items on every cart
refresh, not only when applied throughPOST /store/carts/:id/promotions
(GHSA-w4vq-6jvh-cfpg, #1648).
🐛 Fixes
- Insert the payout row before calling the provider, use the payout id as the
defaultidempotency_key, and accept theprocessingstatus in the
database (#1641). - Intersect
idandseller_idfilters onGET /admin/order-groupsinstead
of returning every group (#1637). - Rebuild the Variants table on every attribute change in Products → Create in
both panels, and surface create errors as a toast instead of a silent 400
(#1650).
Full Changelog: v2.3.6...v2.3.7