Skip to content

Membership products (Vendor)

Membership plans under Customers let a vendor define subscription-style products for their customers (audience vendor_customer). This is separate from admin SaaS vendor plans (platform_vendor).


  • /dashboard/membership-products — registered before /dashboard/customers/:id so membership-products is not captured as a customer id.
  • Module: customer (VendorModuleGuard + @RequireVendorModule('customer')).
  • Sidebar entry uses the same module; owners bypass module membership checks per Vendor overview.

  • List supports search, status filter, pagination, and optional store scope when creating/editing (empty store = vendor-wide / not scoped to one store — see UI copy “All stores / not scoped”).
  • code: optional on create/update. If omitted or sent empty, the API assigns a unique alphanumeric code (same character set as promo codes in shared UI helpers). Clearing the code on PATCH replaces it with a newly generated code.
  • sort_order: still stored and used for list ordering on the server; the vendor dashboard form and list do not expose it (defaults apply).
  • price_cents / duration_days: plan list price (minor units) and billing cycle length in days — stored as columns on membership_products, not inside benefits_json.
  • benefits_json is a JSON array validated by the API for perks only (discounts, loyalty, free-delivery threshold, early-access tag). The vendor create/edit modal maps flat fields to these { kind, value } rows.

Implemented in apps/vendor-web/src/services/vendor-membership-products.api.ts:

MethodPathPurpose
GET/vendor/membership-productsList (page, limit, search, status, store_id)
GET/vendor/membership-products/:idDetail
POST/vendor/membership-productsCreate
PATCH/vendor/membership-products/:idUpdate
DELETE/vendor/membership-products/:idDelete

The backend enforces that store_id (when present) belongs to the authenticated vendor.