Erpy for Craft CMS

Erpy for Exact Online

Erpy for Exact Online connects Craft Commerce to Exact Online through its REST API. It works with every country installation Exact runs: the Netherlands, Belgium, Germany, France, Spain, the United Kingdom and the United States. The add-on is free; it needs Erpy, which is the paid part and owns the sync engine, the identity map, mapping, the queue and the log.

Install

composer require justinholtweb/craft-erpy-exactonline
php craft plugin/install erpy-exactonline

Then Erpy → Connections → New connection and pick Exact Online. The add-on has no settings screen of its own; everything lives on the connection.

What it syncs

EntityDirectionDelta syncPage size
CustomersERP → CommerceYes, on Modified60
ProductsERP → CommerceYes, on Modified60
PricesERP → CommerceYes, on Modified60
InventoryERP → CommerceNo60
OrdersCommerce → ERP
Order statusERP → CommerceYes60
ShipmentsERP → CommerceYes60
InvoicesERP → CommerceYes60
CreditERP → CommerceNo60

The connector declares multi-company support: one connection per division. The page size of 60 is Exact's own, and paging follows the __next link Exact returns.

Connecting

Exact uses the OAuth 2.0 authorization code flow: somebody approves access once in a browser, and Erpy keeps it alive from then on.

  1. Pick the region. Exact runs a separate installation per country, and they do not share data. Choose the one your login belongs to (start.exactonline.nl, .be, .de, .fr, .es, .co.uk or .com).
  2. Register an app in the Exact App Centre. Copy the Redirect URI shown on the connection form into the app's redirect URI. Exact compares it character for character.
  3. Fill in the Client ID and Client secret from that app, and save the connection.
  4. Approve access once. Erpy sends you to Exact's consent screen and stores the tokens it gets back on the connection. Until that has happened, Test connection reports "Not connected yet".
FieldWhat to put there
RegionThe country installation your login belongs to.
Redirect URIRead-only. Copy it into your Exact app.
Client ID / Client secretFrom your App Centre registration.
DivisionThe numeric division code.
Warehouse codeStock is read from this warehouse. Leave blank for the company total.
Base price list IDCurrently has no effect: every sales item price is synced.

Set the division explicitly before going live. Left blank, the connector uses the login's current division. That is whichever division the user last had open in Exact. Test connection shows both the login's current division and the one in use, so you can check.

Things to know about Exact Online

Refresh tokens rotate. Every refresh invalidates the previous refresh token and issues a new one. Keep the old one and the connection dies the second time it is used. Erpy writes the rotated token back to the connection. It also means two integrations sharing one app registration will keep logging each other out. If Test connection reports a 401 saying the refresh token is no longer valid, that is almost always the cause. Give Erpy its own app and approve access again.

A wrong division is a silent failure. Everything in Exact is scoped to a division, and asking the wrong one returns an empty, entirely successful response. That is worse than an error, which is why the setting's instructions say so twice.

The rate limit is real, and daily. Exact allows roughly one call a second and a hard cap per day, and answers 429 beyond either. The transport stays under the per-minute limit and honours Exact's Retry-After for the rest. Schedule large syncs accordingly: a full catalogue pull spends daily allowance a delta sync does not.

Stock is economic stock. Exact publishes on-hand less what is already committed to sales orders, which is the figure a storefront should quote.

Order status is numeric. 12 is open, 20 partially delivered, 21 complete, 45 cancelled.

Your order number is YourRef. The Commerce order number goes out in the sales order's YourRef field (up to 50 characters). A retry checks it before creating a second sales order, and order status comes back matched on it.

Every order needs an account and every line needs an item. The connector finds the Exact account by the linked customer, then by customer code, then by email address. An order that matches none is refused. So is a line whose SKU is not an Exact item code. Set a Guest customer code on the order mapping if guests check out.

Credit arrives without a balance. Credit reads the sales credit line and the blocked flag from the account, but the balance comes through as zero.

Dates look strange in the raw payload. Exact's OData v2 dates arrive as /Date(1596499200000)/: milliseconds since the epoch, wrapped. The connector converts them, but a mapping rule that reads a date from raw. gets the wrapped string.

Correcting a field

The customer sync reads the credit limit from the account's CreditLinePurchase field, which is the supplier-side limit. If you want the sales credit line on the customer record, correct it on the Customers mapping:

raw.CreditLineSales   →   creditLimit        transform: number

The same pattern fixes a SKU. The connector reads the item's Code; if you sell under a different item field, map raw.<thatField> → sku on the Products mapping.

See Field mapping for the rule syntax and the full transform list, Syncing for runs and watermarks, and Troubleshooting when something does not arrive.