# nopCommerce Connector

> Read-only nopCommerce import: products, customers and orders read straight from the nopCommerce database on a schedule - no REST API needed.

- **What it is:** Read-only nopCommerce import: products, customers and orders read straight from the nopCommerce database on a schedule - no REST API needed.
- **For:** eCommerce
- **Odoo versions:** 16.0, 17.0, 18.0, 19.0, 20.0 - Community and Enterprise
- **Runs on:** Odoo.sh or Odoo you host yourself - not Odoo Online, which accepts no third-party apps
- **Price:** 199 EUR, one-time, on the Odoo Apps Store
- **Technical name:** bambooforge_nopcommerce_connector
- **Run for real:** Tested for real against nopCommerce 4.80 and the current release: 47 products, 6 customers and 5 orders read straight from the store's MySQL database with 0 errors, every order total matching the source exactly.
- **Not for:** The connector reads products, customers and orders from the nopCommerce MySQL database and writes the corresponding Odoo records. The MySQL credentials are stored in admin-only fields.; Read-only: the connector never writes to, updates or deletes anything in nopCommerce. It opens the database connection only to run SELECT queries.; In scope today: product/customer/order import, order-status mapping, scheduled sync, dry-run, validation and rollback.; Out of scope / roadmap: refund/credit-note import and inbound webhooks are not yet supported. Export back to nopCommerce is not supported (read-only by design). Tax auto-matching is best-effort, name-based and never auto-creates taxes; multi-warehouse stock routing is not handled.
- **Manual:** https://docs.bambooforge.dev/bambooforge_nopcommerce_connector/
- **Live demo:** https://demo20.bambooforge.dev/odoo/action-bambooforge_nopcommerce_connector.action_nopcommerce_control_tower
- **Store page:** https://apps.odoo.com/apps/modules/20.0/bambooforge_nopcommerce_connector
- **Manual last updated:** 2026-10-15

---
# nopCommerce Connector

A read-only bridge from your nopCommerce store into Odoo 20: import products,
customers and orders on a schedule by reading the nopCommerce **MySQL database
directly** — with a resilient job queue, dry-run safety, validation and rollback
so you stay in control. nopCommerce ships no REST API by default, so the connector
never depends on one: it connects to the database with read-only credentials and
never writes back.

This page is the complete manual. If you follow it top to bottom you can install,
connect, run your first sync, automate it, and fix the common issues **without
contacting support**.

## Matching the storefront total

A storefront charges shipping and grants discounts outside the product lines, so
an order rebuilt from those lines alone is worth less than the order the customer
paid for: the sale looks smaller than it was and never reconciles against the
platform payout.

With **Match Storefront Totals** on the instance (on by default):

- the shipping charge is imported as its own order line, on a clearly named
  service product;
- whatever still separates Odoo from the platform total — platform discounts,
  fees, rounding — is posted as a single adjustment line rather than dropped;
- the platform total and the remaining difference are stored on the order and
  shown on its connector tab;
- an order that still does not match within **Total Tolerance** raises a
  validation warning, so the gap is visible instead of silent.

Imported lines deliberately carry no Odoo tax: the storefront has already
computed tax and it is kept on the order's own tax amount field. Switch the
option off if you would rather Odoo recompute everything from your own price
lists and tax rules.

### Overview

The connector imports data from nopCommerce into Odoo by reading the nopCommerce
MySQL database directly (via the `pymysql` library) with the credentials you
supply. nopCommerce has no built-in REST API, so there is no API key to generate
and no plugin to install on the store side — only read access to the database. It
covers:

- **Products** — import nopCommerce products into Odoo product templates (name,
  SKU, price, categories; optionally stock and image URLs).
- **Customers** — import nopCommerce customers (name, email, company, phone, role)
  into Odoo contacts.
- **Orders** — import nopCommerce orders into Odoo sale orders (line items, totals,
  payment method), mapping each order's nopCommerce status to a sale-order action
  (keep draft, confirm, cancel, or ignore).

Direction of sync: **Odoo ← nopCommerce** (import only). The connector is
read-only: it never writes to, updates, or deletes anything in nopCommerce, and
nopCommerce never pushes to Odoo. Synchronisation runs on a schedule, like a
database mirror. Money is read verbatim — nopCommerce stores decimal major units.

Every database read goes through a queue: nothing is written to Odoo until a job
runs, jobs retry with back-off on transient failures, and a circuit breaker pauses
an instance that keeps failing.

### Tested against the real thing

Automated tests run against a bundled mock store on every release; the line below is the part no mock can give you - the connector pointed at the real platform, with real records and real order totals.

Tested for real against nopCommerce 4.80 and the current release: 47 products, 6 customers and 5 orders read straight from the store's MySQL database with 0 errors, every order total matching the source exactly.

### Requirements

- **Odoo**: 20.0, Community or Enterprise.
- **nopCommerce**: live-tested against nopCommerce 4.80 on MySQL/MariaDB. The
  connector reads the standard nopCommerce schema (PascalCase tables/columns), so
  the 4.x line on MySQL is supported. The MySQL server must be reachable from the
  Odoo server.
- **Python**: the Odoo server needs the **pymysql** library
  (`pip install pymysql`). It is the only extra dependency; without it the
  connector cannot open a database connection and Test Connection fails with a
  clear message.
- **Odoo access**: any internal user can use the connector screens. The MySQL
  **User** and **Password** are stored in system-only fields, so only the
  **Settings / Administration** user can read or change them.
- **Network**: direct TCP connectivity from the Odoo server to the nopCommerce
  MySQL port (default `3306`). By default the connector refuses
  internal/loopback/private storefront hosts as an SSRF safeguard (see
  *Safety features*).

### Installation

1. Install the `pymysql` Python library on the Odoo server
   (`pip install pymysql`) and restart Odoo so it is importable.
2. Copy `bambooforge_nopcommerce_connector` into your Odoo addons path.
3. Restart the Odoo service.
4. Open **Apps**, click **Update Apps List**, search for *nopCommerce*, and press
   **Activate / Install**. Dependencies (*Sales*, *Contacts*, *Invoicing*) install
   automatically.

No live nopCommerce store is required to evaluate the mapping: a bundled set of
nopCommerce-row-shaped mock fixtures lets you install the module, run schema
introspection and explore the field mappings against sample data. Running a real
import, however, needs a reachable MySQL database (see Step 1).

### Step 1 — Grant read-only access to the nopCommerce MySQL database

The connector authenticates by connecting to the nopCommerce database, so the
first step is on your **database server**, not in the nopCommerce admin. Create a
dedicated MySQL user that can only *read* the nopCommerce database, and make sure
the Odoo server can reach it over the network.

1. Open a MySQL/MariaDB client (e.g. `mysql` CLI, phpMyAdmin) as an
   administrator on the server that hosts the nopCommerce database.
2. Create a read-only user and grant it `SELECT` on the nopCommerce database
   only. Replace the database name, username, password and host to match your
   setup:
   
   ```
   CREATE USER 'odoo_ro'@'%' IDENTIFIED BY 'a-strong-password';
   GRANT SELECT ON nopcommerce.* TO 'odoo_ro'@'%';
   FLUSH PRIVILEGES;
   ```
   
   Grant **SELECT only** — the connector never needs INSERT, UPDATE or DELETE. A
   read-only grant is the safest way to guarantee nopCommerce is never modified.
   If your Odoo server has a fixed IP, replace `'%'` with that IP for a tighter
   grant (e.g. `'odoo_ro'@'10.0.0.5'`).
3. Allow the connection over the network:
   
   - Make sure MySQL listens on an address the Odoo server can reach (check
     `bind-address` in `my.cnf`; `127.0.0.1` only allows connections from
     the same host).
   - Open the MySQL TCP port (default **3306**) in any firewall between the Odoo
     server and the database server.
   - If Odoo and nopCommerce share one host, the database is reachable at
     `127.0.0.1:3306` — but note that a loopback/private host triggers the SSRF
     guard on the *storefront* URL (see Step 2 and *Safety features*).
4. From the Odoo server, confirm the user can connect and read, for example:
   
   ```
   mysql -h <db-host> -P 3306 -u odoo_ro -p nopcommerce -e "SELECT 1;"
   ```
   
   A clean `1` means the credentials, network path and port are correct.

### Step 2 — Create the connection in Odoo

Open **nopCommerce Connector ▸ Configuration ▸ Instances** and create a record.

![nopCommerce instance / connection cockpit](01-connection-cockpit.png)

Key fields:

| Field | What to enter |
|---|---|
| Name | A label for this store, e.g. *My Live Store*. |
| MySQL Host | Hostname or IP of the nopCommerce MySQL server. Default `127.0.0.1`. |
| MySQL Port | TCP port of the MySQL server. Default `3306`. |
| MySQL Database | Name of the nopCommerce database/schema, e.g. `nopcommerce`. |
| MySQL User | The read-only user from Step 1, e.g. `odoo_ro` (administrators only). |
| MySQL Password | The password for that user (administrators only). |
| Base URL | Your storefront root, e.g. `https://store.example.com`. Used **only** to build product image links (`/images/thumbs/...`); it is not used for data access. |
| Allow internal host | Off by default. Turn on only to point the storefront Base URL at localhost or a private network (lowers the SSRF guard — see *Safety features*). |

Then click **Test Connection**. The connector opens a database connection and runs
`SELECT 1`. A green *Connected* state means the host, port, database name and
credentials are correct. On the first successful connect, the default order-status
mapping table is seeded automatically. If it fails, the exact error is shown on the
form and recorded in **Logs** (see *Troubleshooting*).

Tip: use **Quick Setup** (button on the instance) to pick a use case (catalog only,
B2C full sync, …), seed default field mappings and order-status rules, and apply a
recommended schedule in one step.

### Step 3 — First sync (dry-run, then live)

New instances start with **Dry-run** ON. In dry-run, import jobs *simulate* writes:
instead of changing data they produce **Validation Results** you can review under
**nopCommerce Connector ▸ Operations ▸ Validation Results**. This lets you confirm
what *would* happen before anything is written.

To run a first import:

1. On the instance, click **Import Products** (and/or **Import Customers**,
   **Import Orders**). This enqueues jobs; it does not block the UI.
2. Jobs are processed by the **nopCommerce Queue Processor** scheduled action
   (every minute), or immediately if you run it manually from
   **Operations ▸ Queue Jobs**.
3. Review **Validation Results** while still in dry-run.
4. When satisfied, open the instance, turn **Dry-run** OFF, and run the imports
   again to write the records for real.

Imported records land in the standard Odoo apps: **Sales ▸ Products**,
**Contacts**, and **Sales ▸ Orders**. Each carries its nopCommerce reference so
re-imports update the same record instead of duplicating it.

### Order status mapping

nopCommerce stores each order's status as a numeric `OrderStatusId`, which the
connector normalizes to a status slug (`pending`, `processing`, `complete`,
`cancelled`). Under **Configuration ▸ Order State Mappings** each instance gets a
default table that decides what happens to the Odoo sale order when an order
arrives in a given status:

![nopCommerce order state mapping table](04-order-state-mapping.png)

| nopCommerce status | Default Odoo action |
|---|---|
| pending | Keep draft |
| processing | Confirm sale order |
| complete | Confirm sale order |
| cancelled | Cancel sale order |

Change any row to **Ignore (do nothing)**, **Keep draft**, **Confirm sale order**
or **Cancel sale order**. Unknown/unmapped statuses default to *Ignore*, so a
status you have not mapped never triggers a destructive transition.

### Field mapping & customization

- **Field Mappings** (Configuration) map nopCommerce columns to Odoo fields per
  model. Use **Generate suggested mappings** on the instance to seed the
  business-critical ones, then adjust. The mapping board flags fields that need
  attention.
- **Schema Fields** lists the discovered nopCommerce columns per resource. Run
  **Schema Introspection** on the instance to refresh it (it samples live rows; if
  the database is unreachable it falls back to the bundled sample row so you can
  still review the mapping).
- **Stock / images / tax** are opt-in toggles on the instance: *Sync stock*
  (reads `StockQuantity`), *Sync images* (and *max images*), *Sync taxes* /
  *Auto-match taxes* (best effort, name-based, never auto-creates taxes). They are
  off by default; turn on only what you need.

### Automation (scheduled actions)

Sync is **schedule-driven only**. nopCommerce has no REST API and emits no events,
so there are no real-time webhooks — the connector polls the database on a cron, like
a database mirror. The module ships these scheduled actions
(**Settings ▸ Technical ▸ Scheduled Actions**):

| Scheduled action | Default | Purpose |
|---|---|---|
| nopCommerce Queue Processor | every 1 min | Processes queued import jobs. |
| nopCommerce Reconciliation | every 15 min | Re-reads recent rows for enabled models and re-queues anything missing. |
| nopCommerce Maintenance | every 1 hr | Recovers stale/locked jobs and trims old logs. |
| nopCommerce Flow Scheduler | every 5 min | Runs scheduled sync flows. |
| nopCommerce Flow Metrics | every 1 hr | Aggregates flow-run metrics. |
| nopCommerce Auto Recover | every 15 min | Reopens a tripped circuit breaker once the database is healthy. |

Turn on **Auto import** / **Auto reconcile** per model on the instance to let the
scheduled actions keep things in sync hands-free.

### Safety features

- **Read-only by design** — the connector only ever runs `SELECT` queries
  against nopCommerce; it never writes, updates or deletes there. Use a read-only
  MySQL grant (Step 1) to enforce this at the database level.
- **Dry-run mode** — simulate writes and review Validation Results before going
  live.
- **Business validation profiles** — *Minimal / Standard / Strict* gate risky
  writes into Odoo.
- **Resilient queue** — every read/import action is a job with retry and
  exponential back-off.
- **Circuit breaker** — after repeated failures an instance auto-pauses
  (*Tripped*); the Auto Recover action reopens it once the database responds again.
- **Rollback snapshots** — when enabled, imports capture a snapshot so you can undo
  a batch from **Operations ▸ Rollback Snapshots**.
- **SSRF guard** — the connector refuses an internal/loopback/private storefront
  Base URL unless *Allow internal host* is explicitly enabled.

### Where nopCommerce shows up in Odoo

The connector stays out of the way on records that have nothing to do
with nopCommerce:

- **Sales orders** - the *nopCommerce* tab, *Queue nopCommerce Export* and *nopCommerce Binding*
  appear only on orders that came from nopCommerce, are bound to nopCommerce, or belong
  to a customer bound to nopCommerce.
- **Products and contacts** - the *nopCommerce* tab appears once an active nopCommerce
  connection exists; the *nopCommerce Binding* button inside it appears once the
  record is bound.
- List views, the sync queue and the control tower are unchanged.

### Troubleshooting

| Symptom | Cause and fix |
|---|---|
| Test Connection fails: "pymysql ... is required" | The `pymysql` library is not installed on the Odoo server. Run `pip install pymysql` and restart Odoo. |
| Test Connection fails: access denied / authentication error | Wrong MySQL user or password, or the user lacks rights on this database. Re-check the **MySQL User / Password** and the `GRANT SELECT` from Step 1. |
| Test Connection fails: can't connect / host not reachable / timeout | The Odoo server cannot reach the MySQL host:port. Check the **MySQL Host** and **Port**, that MySQL's `bind-address` allows remote connections, and that port 3306 is open in the firewall. |
| Test Connection fails: "MySQL database name is required" | The **MySQL Database** field is empty. Enter the nopCommerce schema name. |
| "Base URL ... is not allowed" / refused internal host | The storefront **Base URL** points at localhost or a private IP. Enable *Allow internal host* on the instance (test/self-hosted only). |
| Orders import but never confirm | The order's nopCommerce status is mapped to *Keep draft* or *Ignore*. Adjust **Order State Mappings**. |
| Jobs stay in *Pending* | The Queue Processor is off or the instance is paused. Check **Scheduled Actions** is active and the instance is not *Paused*. |
| Instance shows *Tripped* | Circuit breaker tripped after repeated failures. Fix the database/credentials; Auto Recover reopens it, or click **Resume**. |
| Records imported twice | Imports are keyed by the nopCommerce id, so this should not happen. If it does, check that two instances do not point at the same database. |
| Nothing happens after Import | You are in **Dry-run**. Review Validation Results, then turn dry-run off and re-run. |

For anything else, **Operations ▸ Logs** records every database read and error with
a timestamp.

### Frequently asked questions

**Which versions are supported?**
Odoo 20.0 on the Odoo side. On the store side the connector was live-tested on
nopCommerce 4.80 on MySQL/MariaDB and reads the standard 4.x schema. Validate your
exact build with the bundled sample fixtures first.

**Do I need nopCommerce installed to evaluate it?**
Not for the mapping. Bundled nopCommerce-row-shaped sample fixtures let you install
the module, run schema introspection and explore the field mappings. A real import
needs a reachable MySQL database.

**How does authentication work?**
There is no API key. nopCommerce has no REST API, so the connector connects to the
nopCommerce **MySQL database** with read-only credentials (host, port, database,
user, password). Generate those credentials on your database server (see Step 1).

**Does this write anything back to nopCommerce?**
No. The connector is read-only: it only runs `SELECT` queries and never writes,
updates or deletes in nopCommerce. Use a read-only MySQL grant to enforce this at
the database level.

**How does real-time sync work?**
There is no real-time/webhook path — nopCommerce has no REST API and emits no
events. Sync is scheduled: the connector polls the database on a cron (every minute
for the queue, every 15 minutes for reconciliation). Tighten the cron interval if
you need fresher data.

**Is it safe to run against production data?**
Yes. It only reads nopCommerce. On the Odoo side, dry-run is ON by default,
validation blocks risky writes, and rollback snapshots let you undo. You decide
when to go live.

**What support and refund policy do I get?**
Every request is answered within 24 hours, setup help included. If you report a bug
within 2 months of purchase and it is not resolved within 15 days, you are entitled
to a full money-back refund.

### Data, privacy & limits

- The connector reads products, customers and orders from the nopCommerce MySQL
  database and writes the corresponding Odoo records. The MySQL credentials are
  stored in admin-only fields.
- **Read-only**: the connector never writes to, updates or deletes anything in
  nopCommerce. It opens the database connection only to run `SELECT` queries.
- **In scope today**: product/customer/order import, order-status mapping,
  scheduled sync, dry-run, validation and rollback.
- **Out of scope / roadmap**: refund/credit-note import and inbound webhooks are
  not yet supported. Export back to nopCommerce is not supported (read-only by
  design). Tax auto-matching is best-effort, name-based and never auto-creates
  taxes; multi-warehouse stock routing is not handled.

### Support & updates

- Support: [support@bambooforge.dev](mailto:support@bambooforge.dev) — answered within 24 hours, setup help
  included.
- Refund: report a bug within 2 months of purchase; if unresolved within 15 days,
  full refund.
- Full source is included. Updates track the supported Odoo 20 / nopCommerce 4.x
  line.

### Upgrading & version compatibility

This build targets **Odoo 20.0**. Each Odoo major series (16.0, 17.0, 18.0, 19.0, 20.0) has its
own dedicated build of this module — always install the build that matches your Odoo
version. Mixing a build with a different Odoo series is not supported.

## Patch upgrades (same series, e.g. 20.0.1.0.0 → later)

1. **Back up** your database and filestore first.
2. Replace the module folder with the newer build.
3. Restart Odoo with the module updated:
   
   ```
   ./odoo-bin -c your.conf -u bambooforge_nopcommerce_connector -d your_db
   ```
4. Odoo applies any schema/data changes automatically. Your existing records and
   configuration are preserved.

## Cross-version migration (moving to a newer Odoo)

Upgrading Odoo itself is a database migration handled by Odoo's standard upgrade
tooling. When you migrate the database to the next Odoo series, install the matching
build of this module for that series. Data created by this module carries over with
the database migration.

After any upgrade the module's scheduled actions resume on their normal cadence —
no manual re-activation is required.

### Uninstallation

You can remove this module at any time from **Apps → (this module) → Uninstall**,
or from the command line. Uninstalling is clean and reversible by reinstalling —
but note what is and is not deleted.

## What is removed

- The module's own tables and every record in them (20 models, prefixed `nopcommerce.*`) — this is the data this module created.
- The menus, actions, views and reports this module installed.
- Its scheduled actions (cron jobs) — they stop immediately on uninstall.
- Connection records, credentials, field mappings, queue jobs and sync logs stored in Odoo.

## What is preserved

- **Your remote platform is never touched.** Uninstalling only removes the Odoo-side
  connector; products, customers and orders on the external store/service are untouched.
- Records already imported into standard Odoo models (e.g. contacts, products, sales
  orders) remain — they are ordinary Odoo records once created.
- Attachments and chatter messages on standard records are kept.

As always, take a database backup before uninstalling in production.

### Changelog

## 20.0.1.2.5

- Built for Odoo 20: record rules and access rights rewritten onto Odoo 20's single ir.access model, the screens rebuilt on Owl 3, and every icon moved to the icon set Odoo 20 ships.

## 20.0.1.0.0

Current release for Odoo 20.0. This build includes:

- Read-only Odoo 20 <- nopCommerce connector: imports products, customers and orders by reading the nopCommerce MySQL database directly (no REST API needed) on a schedule.
- With a live Sync Control Tower, resilient queue, dead-letter console, conflict workbench and dry-run safety.

Feature additions and fixes ship as new builds on the Odoo Apps store; this page and
the module's `version` reflect the current published release. Always keep the build
matched to your Odoo series (see *Upgrading & version compatibility*).

### How we make sure it works

Before every release, **101 automated checks** run against this app on Odoo 16, 17, 18, 19 and 20.
Among them:

- Nothing at all is an empty row not a crash.
- A product shows its binding button only once bound.
- Import to odoo true but dry run never moves stock.
- Push stock read only raises even for negative.
- Stock export job records clean skip no write.

Tested for real against nopCommerce 4.80 and the current release: 47 products, 6 customers and 5 orders read straight from the store's MySQL database with 0 errors, every order total matching the source exactly.

The complete list is in the source you receive.
