v2.3.7

mercurjs/mercurv2.3.7Oct 8, 2026by github-actions[bot]

Upgrading?

AI Summary

A smaller release introducing 'Offer Conditions' for listing states, enhancing the Admin Order List with server-side search and payment status filtering, and addressing security vulnerabilities.

Key Highlights

  • Introduced 'Offer Conditions' to define listing states like 'new', 'refurbished', or 'used'.
  • Enhanced Admin Order List with server-side search for customers, sellers, and sales channels.
  • Fixed security vulnerabilities in the vendor API and seller promotions to prevent cross-seller authorization issues.
  • Updated payout logic to insert rows before calling the provider to ensure proper status tracking.

Breaking Changes

  • Migrations are required: the `offer_condition` table is added and the `payout.status` constraint is widened.
  • Payouts are briefly `pending` because the row is inserted before the provider is called.
  • Seller promotions are now strictly scoped to the seller's own items, which may break existing logic relying on broader application.

New Features

  • Offer Conditions management via `/admin/offer-conditions`.
  • Payment status filter in the Admin Order List.
  • Smart order search matching display IDs (e.g., `#264`).
  • Server-side filtering for customers, sellers, and sales channels.

Full Release Notes

# 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_condition` table and
  `offer.condition_id` (seeded with a default `new` condition), and the payout
  module widens the `payout.status` check constraint to accept `processing`
  (#1645, #1641).
- **Payouts are briefly `pending`.** `createPayouts` now inserts the row
  before calling the provider, so `payout.created` fires for a pending row and
  readers that assumed every row is already sent should check `status` (#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, so `GET /admin/order-groups?payment_status=captured`
  matches exactly what the list shows (#1639).
- Search by the numbers operators actually see: `264`, `#264` or `#G258` now
  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: `fields` traversal
  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 through `POST /store/carts/:id/promotions`
  (GHSA-w4vq-6jvh-cfpg, #1648).

## 🐛 Fixes
- Insert the payout row before calling the provider, use the payout id as the
  default `idempotency_key`, and accept the `processing` status in the
  database (#1641).
- Intersect `id` and `seller_id` filters on `GET /admin/order-groups` instead
  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**: https://github.com/mercurjs/mercur/compare/v2.3.6...v2.3.7

Related Repositories