feat(medusa): add Medusa v2 connector - #436
Conversation
Add the Medusa v2 connector foundation: shared constants, the Admin API client and HMAC-verified webhook ingestion endpoint, the log helper, and the single Medusa Setting doctype (with warehouse, tax-account and webhook child tables) subclassing the shared SettingController. Registers the 'medusa' module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Import Medusa products (including variants) as ERPNext Items - lazily during order sync or via the Import Products desk page - and push new/updated ERPNext items back to Medusa. Create Customers, Addresses and submitted Sales Orders (with tax, shipping and per-unit discount mapping) from Medusa orders; support historical backfill and order cancellation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On captured payment create a Sales Invoice and Payment Entry; on fulfillment create a Delivery Note matched to the fulfilled lines; on return create a credit note with full and partial (qty-prorated) tax handling, robust across ERPNext versions that drop item_wise_tax_detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scheduled, self-throttled delta inventory sync from ERPNext Bins to Medusa inventory location levels, reusing the shared inventory controller and per-item sync watermark. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the Item doc-events for ERPNext->Medusa product push, the inventory and backfill scheduler events, a migration patch that installs the custom fields on existing sites, and removal of the connector's custom fields on uninstall. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Medusa v2 exposes no admin API to register webhooks, so ship a small TypeScript subscriber that HMAC-signs and forwards order, fulfillment and return events to the ERPNext ingestion endpoint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integration tests for the product, order, inventory and return flows backed by a network-free fake client and JSON fixtures, plus connector documentation. Verified green against ERPNext 16.25 and Medusa v2.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Newer ERPNext auto-creates GST accounts during company setup; skip creating a duplicate Output Tax GST account so the shared test bootstrap runs on ERPNext 16. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Medusa (v2) to the README's supported-integrations list, linking to the connector documentation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Confidence Score: 4/5This is close, but the shipping total calculation should be fixed before merging.
Files Needing Attention: ecommerce_integrations/medusa/order.py Reviews (13): Last reviewed commit: "Update ecommerce_integrations/medusa/ret..." | Re-trigger Greptile |
Net each line's per-unit discount into the Sales Order/Invoice rate so ERPNext totals match the Medusa order total instead of overstating the customer balance. Backfill now replays paid/fulfilled/canceled state for historical orders (which won't get live webhooks) and commits the Sales Order before each replay so an independent replay failure can't roll it back. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match Delivery Note rows strictly on the Medusa order line id; drop the fallbacks that shipped a single line's qty across all rows, or shipped the whole order, when line metadata was missing. An unmatched fulfillment now raises instead of creating incorrect stock movements. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Require System Manager for the product-import endpoints. Link an existing ERPNext Item when a variant SKU already matches (instead of inserting a duplicate). Surface (not swallow) Ecommerce Item link failures after a Medusa product is created. Re-sync products that gained a variant. Stop advancing the inventory watermark when a SKU lookup returns nothing, so a transient empty result retries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add fulfillment tests (full, partial, idempotent, and the fail-safe unmatched case). Assert the Sales Order grand total reflects the discount, and add a backfill test that the paid+fulfilled historical order replays SO + Sales Invoice + Delivery Note. Allow negative stock in the test bootstrap so Delivery Notes submit without seeding stock. Verified green against ERPNext 16.25 + Medusa v2.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the thorough review — addressed all eight valid findings (one looks like a false positive, noted below). All changes were re-validated against a real ERPNext 16.25 install + Medusa v2.4.0, and the connector test suite (now incl. fulfillment) is green: product 3, order 5, inventory 2, returns 3, fulfillment 4.
🤖 Generated with Claude Code |
Subscribe to the order-scoped events that actually exist and carry order context: order.completed (was the non-existent order.payment_captured) and order.fulfillment_created (was fulfillment.created, which carries only a fulfillment id). The delivery-note handler now resolves the full order from an {order_id, fulfillment_id} payload, and the companion subscriber extracts the order id per event (data.id vs data.order_id) and forwards the order (or {order_id, return}) the ERPNext handlers expect. Verified the event names + payloads against Medusa v2.4.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
process_request now returns early (logging Invalid) when the Medusa Setting is disabled, so a stale subscriber holding the secret can't drive document-mutating handlers as Administrator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Separate the SKU->inventory-item lookup from the location-level update. A lookup failure (incl. a transient 404) no longer advances inventory_synced_on, so the row retries; only a 404 from the location-level update (item resolved but gone on Medusa) marks it synced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a test that an order.fulfillment_created {order_id, fulfillment_id} payload resolves the order and still creates the Delivery Note, and a test that a signed event is ignored (logged Invalid, no Sales Order) while the integration is disabled. Verified green against ERPNext 16.25 + Medusa v2.4.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks — addressed all three, and the fulfillment one turned out to be the tip of a bigger event-layer issue worth fixing together. Re-validated against ERPNext 16.25 + Medusa v2.4.0 (suite now 19 green incl. the two new cases below).
🤖 Generated with Claude Code |
… webhooks Only create a Sales Invoice + Payment Entry when the Medusa order has a captured payment, so an order.completed event for an uncaptured order is no longer recorded as paid. Downstream handlers (invoice, delivery note, credit note, cancellation) now ensure their prerequisite Sales Order / Invoice exists - creating it from the order if a webhook arrived before order.placed finished - instead of dropping the state as Invalid with no retry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fetch_medusa_locations appended stock-location rows with no ERPNext warehouse and then saved, which validate() rejects - so the very first fetch failed. Mirror the Shopify connector: append the rows and let the client refresh the table; the operator maps warehouses and saves the form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etch Add tests that an uncaptured order is not invoiced and that an out-of-order order.completed still creates both the Sales Order and Sales Invoice, plus that fetch_medusa_locations appends rows without saving. Verified green against ERPNext 16.25 + Medusa v2.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed all three — re-validated against ERPNext 16.25 + Medusa v2.4.0 (suite now 22 green across 7 modules).
🤖 Generated with Claude Code |
…etch ensure_sales_order now submits a pre-existing draft Sales Order (from a failed prior run or manual import) instead of skipping on sync_sales_order's dedup, so invoice/fulfillment/return/cancel replay isn't blocked. fetch_medusa_locations now checks write permission on the Medusa Setting before making credentialed Medusa calls and replacing the warehouse mapping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert a leftover draft Sales Order is submitted by ensure_sales_order rather than blocking replay. Verified green against ERPNext 16.25 + Medusa v2.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Both addressed — re-validated against ERPNext 16.25 + Medusa v2.4.0 (suite 23 green).
🤖 Generated with Claude Code |
…turns The historical backfill now replays returns as credit notes (fetching them from Medusa when not embedded on the order), so a returned historical order is not left billed with overstated accounting. And a return whose line items match no invoice row now raises instead of crediting the entire invoice (mirrors the fulfillment fail-safe). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert backfill replays a credit note for a returned historical order, and that an unmatched return creates no credit note (no over-credit). Verified green against ERPNext 16.25 + Medusa v2.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Both addressed — re-validated against ERPNext 16.25 + Medusa v2.4.0 (suite 24 green).
🤖 Generated with Claude Code |
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
When a Medusa order's currency differs from the company currency, book the Sales Order/Invoice in the order currency with a conversion_rate from ERPNext's exchange-rate lookup, point the invoice debit_to at an auto-created per-currency receivable (ERPNext requires debit_to currency to match the document currency), and let the Payment Entry settle into the company bank with ERPNext booking the FX gain/loss. Documented the model and ERPNext's one-currency-per-customer constraint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A USD order against an INR test company is booked with a seeded USD->INR rate, a USD receivable debit_to, a converted base total, and a Payment Entry. Verified green against ERPNext 16.25 + Medusa v2.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_validate_request logged the raw request bytes on a signature mismatch, which create_medusa_log could not serialise (TypeError -> HTTP 500). Decode the body for the log and raise AuthenticationError so an unsigned/forged webhook gets a clean 401. Found while smoke-testing the live Medusa->ERPNext callback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolves the five 'confirm against Medusa v2 Admin API' TODOs, verified
against docs.medusajs.com and the v2.4.0 OpenAPI/zod validators:
- variant create: title + prices are required and options is a
title->value record — always send prices ([] default) and the
Default option mapping
- product create: v1-style nested type {value} would 400 on the strict
schema — resolve Item Group to a real Product Type via
get_or_create_product_type() and send type_id; drop None-valued keys;
declare the Default option (create-only)
- price selection: skip region/context-scoped prices (price_rules),
prefer the price-list currency from Medusa Setting, fall back to
first rule-free price
- confirmed correct as-documented: implicit 'Default option'
convention and major-unit price amounts (TODOs replaced with
doc-verified comments)
Adds test_variant_price_selection + test_upload_erpnext_item_payload
and aligns fixtures with real API shapes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bscriber Turn medusa_subscriber/ into an installable npm package while keeping copy-in supported (README documents both). Adds: - package.json: scoped name, CJS build to dist/ with declarations, main entry (forwardToErpnext + handler/config re-exports) and "./subscriber" subpath for the thin app-side registration shim; peer deps on @medusajs/framework and @medusajs/medusa ^2.4.0. - tsconfig.json: src -> dist, Node16/CJS, declaration: true. - src/index.ts: main entry re-exporting the forward lib and subscriber. - .github/workflows/publish-subscriber.yml: FORK-ONLY publish workflow (GitHub Packages, version <major.minor>.<commit count>, idempotent skip-if-published). Strip this file from the branch used to file the upstream PR. Medusa only discovers subscribers from the app's src/subscribers dir, so consumers keep a one-line re-export file there (documented in README). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…esolves The ^2.4.0 peer floated to 2.17.2 in a clean install, whose own peer (framework@2.17.2) conflicted with the pinned framework 2.4.0 devDep (ERESOLVE). Pinning medusa 2.4.0 for the build keeps both aligned with the deployed app. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
…imported The full medusa package pulled the entire dependency tree into the package build (npm resolver ground for 25+ min in CI). The subscriber imports @medusajs/framework only; medusa stays a peerDependency for consumers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
npm auto-installs the package's own peerDependencies; @medusajs/medusa ^2.4.0 floats to latest whose framework peer conflicts with the pinned 2.4.0 devDep. Build only needs framework types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…don't match the paths filter) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
| } | ||
| ) | ||
|
|
||
| _add_shipping(taxes, order, setting) |
There was a problem hiding this comment.
Shipping Added Per Taxed Line
_add_shipping() runs inside the line-item tax loop. A two-line order with tax on both lines appends the same shipping charge twice, while an order with no taxable lines never appends shipping at all, so the Sales Order and later invoice can overcharge or drop shipping.
Rule Used: What: Ignore the pull request description and eval... (source)
Add a Medusa (v2) connector
This adds Medusa (the open-source composable-commerce platform, v2) as a first-class connector, built to match the existing Shopify / Amazon / Unicommerce / Zenoti connectors — reusing the shared framework (
Ecommerce Item,Ecommerce Integration Log, thecontrollers/base classes) rather than introducing anything parallel.What it does
Itemdoc-eventsorder.placed(+ scheduled backfill)order.payment_capturedfulfillment.createdorder.return_receivedIt follows the Shopify reference closely: a single
Medusa Settingdoctype owning enable/auth/defaults + warehouse/tax mappings; one HMAC-verified ingestion endpoint that enqueues handlers keyed to anEcommerce Integration Log; the dummy price-list / tax-category trick to bypass ERPNext re-pricing; and afrom_integrationguard to avoid push/pull echo loops. It also closes two gaps the others leave open — returns → credit notes (ported from Unicommerce) and optional real price-list pricing.The one architectural wrinkle
Medusa v2 has no admin API to register webhooks (unlike Shopify's
Webhook.create). Events come from server-side subscribers. So the connector ships a small companion subscriber (medusa/medusa_subscriber/, TypeScript) that the merchant installs in their Medusa app; it HMAC-signs and forwards events to the ingestion endpoint. A scheduled poll provides backfill/reconciliation. Open question for reviewers: whether you'd prefer that subscriber to live here, as a docs snippet, or in a separate companion repo — easy to move.Testing
Integration tests for product, order, inventory and return flows (network-free fake client + JSON fixtures). Verified green against a real ERPNext 16.25 install + a live Medusa v2.4.0 instance — every field mapping was confirmed against the
@medusajs/types2.4.0 definitions. 12 tests pass.Notes for review
requests, already shipped with Frappe).fix(tests): make before_tests tax-account creation idempotent) lets the shared test bootstrap run on current ERPNext, which auto-creates the GST account — happy to split this into its own PR.Commits
Logically split: framework + Setting → product/customer/order → invoice/fulfillment/returns → inventory → hooks/patches/uninstall → subscriber → tests/docs.