Overview

EU and Polish consumer-protection duties of an online shop, supported in Medusa v2 - Omnibus price history, unit prices, GPSR product safety data, the online withdrawal function and the reviews and personalised pricing disclosures.

@zanreal/medusa-consumer-compliance is a Medusa v2 plugin for a shop that sells to consumers in Poland (and, with the same rules, elsewhere in the EU). It keeps the data and runs the processes that consumer law expects of a store's backend, and hands the frontend everything it needs to show it.

The plugin is headless: it ships no frontend code, and everything a frontend needs is in the Store API, described in Frontend API contract.

Not legal advice. The plugin supports compliance; it does not deliver it. Labels, terms, the withdrawal instructions and every decision listed under open legal questions need a lawyer's review. The legal basis of every rule is cited in Legal basis and compliance matrix.

What it covers

DutyWhat the plugin does
Omnibus: lowest price in the 30 days before a reductionRecords every price change per variant and pricing context, computes the reference price by the Polish rules, exposes it with the label the frontend must print. See Omnibus.
Unit priceStores net quantity, unit and exemptions per variant, computes the unit price from the live price. See Unit prices.
GPSR product informationManufacturers and EU responsible persons as reusable records, warnings in Polish, validation of what is missing. See Product safety.
Online withdrawal function (Directive 2023/2673)A two-step Store API flow, per-item eligibility and deadlines, the acknowledgement on a durable medium, an admin register. See Withdrawal.
Checkout consentsConsents chosen by the cart contents, enforced when the cart is completed, frozen verbatim on the order. See Checkout consents.
Reviews and personalised pricingEditable texts served by the Store API. See Disclosures.

What it does not do: render anything in your frontend, decide whether a product is perishable or covered by GPSR, or classify your discount codes. Those are store decisions, and the plugin makes each of them an explicit field instead of a guess.

How it fits together

 pricing module ──events──> price history subscriber ──┐
 price lists (scheduled) ──hourly sweep────────────────┤
                                                        v
                                    consumer_compliance_price_history
                                                        │
 frontend ── GET /store/products/:id/compliance ──────┤──> Omnibus reference + unit price + GPSR block
 frontend ── POST /store/consumer-compliance/withdrawals (+ /confirm) ──> withdrawal record ──> acknowledgement e-mail
 admin ── product widgets, Settings > Consumer compliance, Withdrawals register

Everything is a Medusa module with its own tables and migrations, written through workflows. The plugin adds no module links, so installing it touches no core table.

Install

This package is not on npm yet. It installs as a git dependency, pinned to a commit:

package.json
{
  "dependencies": {
    "@zanreal/medusa-consumer-compliance": "github:zanreal-labs/medusa-consumer-compliance#ee3a8f2db91d3845a61d3c4e57d59f47ad7a318a"
  }
}

Pin to the commit you tested against. There is no published tag yet, so #main would move under you on the next push; a pinned commit means the same thing tomorrow that it means today.

The package compiles itself on install - prepare runs medusa plugin:build, which turns the checked-out source into the .medusa/server output its exports point at. pnpm 10 and newer refuse to run that script for a dependency they do not already trust, so a fresh install needs it allowed once:

pnpm-workspace.yaml
allowBuilds:
  "@zanreal/medusa-consumer-compliance@https://codeload.github.com/zanreal-labs/medusa-consumer-compliance/tar.gz/ee3a8f2db91d3845a61d3c4e57d59f47ad7a318a": true

The key is the exact tarball URL pnpm resolves the pinned commit to, so it carries the same SHA as the dependency line. Move both together. Then register the plugin:

medusa-config.ts
module.exports = defineConfig({
  plugins: [
    {
      resolve: "@zanreal/medusa-consumer-compliance",
      options: {
        withdrawal: {
          seller: { name: "Shop sp. z o.o.", address: "ul. Prosta 1, 00-001 Warszawa", email: "shop@example.com" },
        },
      },
    },
  ],
});

Then run the migrations and record the current prices once:

npx medusa db:migrate
# then in the admin: Settings > Consumer compliance > Price history > Record current prices now

The hourly sweep would do the same within the hour. Every option is described in Options and admin API.

Admin

  • Product page: a compliance card (GPSR data, Omnibus treatment, withdrawal exception, unit prices per variant) and a price history card (chart, table, reference price, external price windows).
  • Order page: the customer's consents (Zgody klienta) and withdrawals submitted for the order.
  • Withdrawals (sidebar): the register of every withdrawal statement.
  • Settings > Consumer compliance: disclosure texts, manufacturers and responsible persons, the list of products with GPSR gaps, price history status.

The admin is translated into Polish and English.

On this page