• Модуль интеграций
  • Сообщество
  • Блог
Документация
Плагины и интеграцииВсе расширения для Medusa от сообществаСтартерыЗапускайте проекты быстрее с готовыми решениями
ЭкспертыПодберите специалиста для разработки и развития вашего проекта на MedusaКейсыПосмотрите примеры Medusa в продакшене и успешные внедрения
Меч Moscow
Комплексная e-commerce платформа на Medusa для московского fashion-бренда

Меч Moscow · Fashion

Gorgo снижает затраты на адаптацию Medusa к локальным рынкам.

Мы разрабатываем плагины интеграции, осуществляем поддержку и развиваем сообщество разработчиков на Medusa в Telegram.

  • Ресурсы Medusa
  • Плагины и интеграции
  • Модуль интеграций
  • Стартеры
  • Эксперты
  • Кейсы
  • Medusa Чат в Telegram
  • Medusa Новости в Telegram
  • Документация Gorgo
  • Связаться с нами
  • TelegramGitHub
Плагины
E

Einvoice

Электронные счета по EN 16931 для Medusa v2: генерация и проверка XML в форматах XRechnung, Peppol BIS 3.0 и Factur-X (CII) из ваших заказов, локально и бесплатно.

npm install medusa-plugin-einvoice
Категория
Другое
Создано
Attestwire
Версия
0.1.0
Последнее обновление
1 неделю назад
Ежемесячные загрузки
Загрузка данных
Звезды на Github
0
npmNPMGitHubGithub

medusa-plugin-einvoice

EN 16931 e-invoicing for Medusa v2. When an order is placed, generate a compliant XRechnung, Peppol BIS 3.0 or Factur-X (CII) XML document from the order, validate it against the business rules before storing it, and attach the verdict to the order.

Generation and validation run locally and for free via — a zero-dependency TypeScript implementation of the rules. There is no API to sign up for, no per-invoice cost, and no network call in the default configuration.

Quickstart

npm install medusa-plugin-einvoice
1// medusa-config.ts
2module.exports = defineConfig({
3 plugins: [
4 {
5 resolve: "medusa-plugin-einvoice",
6 options: {
7 seller: {
8 name: "Beispiel Handels GmbH",
9 vatId: "DE123456789",
10 legalRegistrationId: "HRB 12345 B",
11 address: {
12 line1: "Hauptstraße 1",
13 city: "Berlin",
14 postalCode: "10115",
15 countryCode: "DE",
16 },
17 // BR-DE-2/5/6/7: all three are mandatory for XRechnung.
18 contact: {
19 name: "Buchhaltung",
20 email: "rechnungen@beispiel.example",
21 phone: "+49 30 1234567",
22 },
23 electronicAddress: { schemeId: "9930", value: "DE123456789" },
24 },
25 // BR-DE-1: XRechnung requires payment instructions.
26 payment: {
27 meansCode: "58",
28 iban: "DE02120300000000202051",
29 accountName: "Beispiel Handels GmbH",
30 },
31 paymentTermDays: 30,
32 },
33 },
34 ],
35})
npx medusa db:migrate

That is the whole setup. Place an order and the plugin generates the document, validates it, stores it, and writes a report to . The order details page in the admin gains an E-invoice panel with the status, any rule failures, and a download button.

Node 20.19+ or 22.12+ is required (the same floor as Medusa 2.19). This is load-bearing rather than incidental: the validation engine is an ESM-only package and a Medusa plugin compiles to CommonJS, so the plugin relies on Node's support, which is unflagged from exactly those versions.

What it produces

Buyer countryProfile ()Syntax
DEUBL 2.1
FRCII D16B
anywhere elseUBL 2.1

Override globally with , or per country with .

The Factur-X boundary, stated plainly

For a French buyer this plugin emits the Factur-X CII XML payload. It does not build the PDF/A-3 container that makes a file a Factur-X invoice. The underlying library reads that container () and deliberately does not write one.

So: if your counterparty wants the XML — which is what portals, access points and the Chorus Pro/PPF pipeline consume — you have it. If you need the human-readable PDF with the XML embedded, you need a PDF/A-3 step this plugin does not provide. Take the XML from and embed it with a PDF library of your choice.

Validation-first

Nothing is stored before it is validated, and a document that fails is stored flagged and logged rule by rule:

1[einvoice] RE-2026-1042: xrechnung-ubl FAILED validation with 1 error(s).
2[einvoice] BR-DE-15 (BT-10): A German public-sector buyer requires a Leitweg-ID …
3[einvoice] fix: set buyerReference to the Leitweg-ID your client gave you
4[einvoice] https://attestwire.com/rules/BR-DE-15

The same findings land on the order:

1// order.metadata.einvoice
2{
3 "invoice_number": "RE-2026-1042",
4 "profile": "xrechnung-ubl",
5 "valid": false,
6 "errors": [{ "rule": "BR-DE-15", "field": "BT-10", "fix": "…", "docs": "https://attestwire.com/rules/BR-DE-15" }],
7 "notes": [{ "code": "VAT_INTRA_COMMUNITY", "level": "info", "message": "…" }],
8 "reconciliation": { "matches": true, "deltas": [ /* … */ ] }
9}

An invoice never blocks an order. A failed generation is logged and stored; the checkout is unaffected.

Mapping decisions

This is the part worth reading before you trust it in production.

Tax-inclusive pricing. EN 16931's BT-146 is a net unit price. When is set, VAT is divided out at 8 decimals before the quantity is applied — rounding the unit price to 2 decimals first is what makes a multi-line order a cent short. The line amount is then rounded as BR-DEC-* requires.

Discounts become line allowances (BG-27), not document allowances. A Medusa promotion arrives as an on the item it discounted. A line allowance inherits that line's VAT treatment; a document allowance carries its own category and rate, so hoisting a per-item discount to document level means inventing a rate for it — and getting the VAT breakdown wrong as soon as an order mixes 19 % and 7 % goods. An adjustment that is itself tax-inclusive has VAT removed at the line's rate. A negative adjustment becomes a line charge (BG-28), because BR-41/BR-42 require both to be stated positively.

Shipping becomes a document charge (BG-21, reason code ). A shipping-method promotion becomes a document allowance (BG-20) at the shipping rate — it is the one discount with no line to sit on. Set if you would rather see freight as an invoice line.

Totals are computed, not copied. The engine derives BT-106…BT-115 from the lines, so the document is always internally consistent. Medusa's own totals are then compared and any disagreement is reported as a note. Set to write Medusa's figures into the document instead and let BR-CO-10…16 reject the mismatch outright — useful for catching an accounting bug, disruptive as a default.

VAT categories are inferred, and every inference leaves a note. Medusa knows how much tax it charged, never why it charged none. Zero can mean four different things:

SituationCategoryOption
rate > 0—
zero, same country
zero, EU buyer with a VAT id ( for goods)
zero, EU buyer without a VAT id + warning—
zero, non-EU buyer—

, and also put the standard exemption sentence in BT-22.

Multi-currency passes through as BT-5. If VAT must be reported in another currency (BT-6/BT-111) the plugin does not attempt it: the required exchange rate is not on the order, and inventing one would be a tax statement.

Multiple tax lines at different rates on one item are summed, because EN 16931 allows one category and one rate per line — with a warning.

Buyer identity comes from the billing address, falling back to shipping with a warning. Company name wins over person name. The buyer's VAT id and Peppol endpoint come from order metadata (, ), because Medusa has nowhere else to keep them.

Optional hosted layer

Everything above works with no account. Setting adds two things, neither of which anything depends on:

1options: {
2 attestwireApiKey: process.env.ATTESTWIRE_API_KEY,
3 createValidationRecords: true, // default when a key is set
4 checkRuleCurrency: true, // default when a key is set
5}
  • Validation Records. A shareable URL proving the check ran, for a buyer's AP desk or an auditor, stored on the order as . The invoice itself never leaves your process — the hosted side keeps a SHA-256 fingerprint and the findings, nothing more.
  • Rule-currency check. One call to at boot. If the pinned library trails the current ruleset you get one warning naming the rule ids added since. Silence when current.

Both fail soft. An invoice is never blocked by a network problem, and the local verdict is always the one that produced your XML.

API

1GET /admin/orders/:id/einvoice → the stored document + report
2GET /admin/orders/:id/einvoice?format=xml → download the XML
3POST /admin/orders/:id/einvoice { force } → generate / regenerate

Programmatically, from anywhere in your app:

1import { generateEInvoiceWorkflow } from "medusa-plugin-einvoice/workflows"
2
3const { result } = await generateEInvoiceWorkflow(container).run({
4 input: { order_id: "order_123" },
5})

Or without Medusa at all — the mapping layer is framework-free:

1import { buildEInvoice } from "medusa-plugin-einvoice/lib"
2
3const { xml, valid, errors } = buildEInvoice(order, options)

Configuration reference

OptionDefaultMeaning
—Required. Name, address, VAT id, contact.
Fixed profile, or country-derived.
Per-country override.
Also accepts .
Prefixed to the order's .
—Order metadata key that overrides the number.
BT-10 / Leitweg-ID.
BT-49, .
BT-48.
BT-130 unit of measure.
Domestic zero rate.
for goods.
—BT-120 per category. has no default.
Freight as a line instead of BG-21.
/ / —BG-16, BT-20, BT-9.
Write Medusa's totals into BT-106…115.
Reconciliation tolerance.
Treat warnings as failures.
Keep the XML in the plugin's table.
Write the report to .
—Enables the hosted layer.

Scope, honestly

In scope and working: order → EN 16931 XML (UBL and CII), local validation with rule ids and docs links, storage, order metadata report, admin panel, download endpoint, workflow API, tax-inclusive pricing, promotions, shipping, multi-currency, reverse charge and export.

Not in scope, and here is what you would need instead:

  • PDF/A-3 Factur-X containers. XML payload only. See above.
  • Transmission. Nothing is sent anywhere. No Peppol access point, no Chorus Pro, no email. The document is generated and stored; delivery is yours.
  • Credit notes and refunds. A Medusa return or claim does not yet produce a BT-3 document. The underlying library supports credit notes fully (one field), so this is a mapping gap, not a capability gap.
  • Order edits. An invoice is generated once. Editing an order afterwards does not amend it — a corrected invoice legally needs its own number and a BG-3 reference back, which the plugin does not mint.
  • as a trigger. Its payload is a payment id, not an order id; resolving one to the other reliably is left to your own subscriber.
  • Sequential invoice numbering. The number is the order's with an optional prefix. Several jurisdictions require a gap-free sequence per seller, which an order id is not.
  • Storing seller data per sales channel or region. One seller, from config.

Development

1npm install
2npm test # 42 mapping tests, every fixture validated by the engine
3npm run typecheck
4npm run build # medusa plugin:build

The test suite's standing rule: every fixture that should produce a sendable invoice must come back from the engine, and one that should not must fail by naming the rule.

Regenerating the migration

Only needed after changing the model in . The Medusa CLI builds its own MikroORM config for this command, so it reads /// rather than , and it connects to a database named , which must exist:

1DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres DB_PASSWORD=postgres \
2 npm run db:generate

The diff is taken against , which is committed next to the migrations. Keep it committed — without it the next run emits a duplicate create-table migration instead of an incremental one.

License

MIT

Еще в этой категории

Посмотреть все
Другое
Gati logo

Gati

От Devx Commerce

Синхронизируйте Medusa с Gati ERP

Загрузка данных
npm
Другое
Product Reviews logo

Product Reviews

От Lambda Curry

Добавляйте рейтинги, отзывы и модерацию товаров

Загрузка данных
GitHubnpm
Другое
Variant Images logo

Variant Images

От Betanoir

Организуйте и загружайте варианты изображений в Medusa

Загрузка данных
GitHubnpm