For Craft Commerce 5
Your store, in your books. Reconciled.
Most accounting connectors are a one-way pipe with a hopeful attitude, and they fail in two expensive ways: a retry writes a second invoice, or Commerce and Zoho quietly disagree on tax until somebody finds it at year end. Zo is built around not doing either.
Rehearses before it writes
The Preview payload button, the console dry run and the sync itself all call the same builders — so what you are reading is byte-for-byte what Zoho receives. Nothing is sent.
$ php craft zo/sync/order 1042 --dry-run
invoice · 4 lines · total $1,248.60 · nothing sent
$ php craft zo/sync/status
1,904 of 1,911 orders in the books
not reconciled: 1 · failed: 0
$ php craft zo/sync/backfill --queue
7 orders queued, paced under Zoho's 100/min
Features
A link table with a unique index, two totals stored side by side, and a connection log that is safe to hand to somebody else.
It cannot make a duplicate
Every Craft ↔ Zoho pair is a row with a unique index, and Zo claims the row before it sends anything — treating the duplicate-key failure as the answer rather than checking first and inserting second.
- A queue retry finds the first attempt's row and stops
- There is one code path that can create a link
Every document is reconciled
Commerce's total and Zoho's total are both stored on the link, because the difference cannot be recomputed later from two systems that have each moved on. If "Not reconciled" is zero, your books agree with your store.
Tax, decided rather than hoped
By default Zo sends no tax rates at all and puts the difference in as one adjustment, so the invoice total equals what the customer paid, to the cent — and a custom adjuster is included without Zo modelling it. Or map your rates and let Zoho compute.
Money moves both ways
Commerce payment transactions become Zoho customer payments, and refunds become credit notes — with the cash movement recorded against your deposit account, which is the part most integrations skip.
- Zoho cannot refund a paid invoice — a credit note is the correct shape
- Products become Zoho items, so sales-by-item reporting works
Connects to the right region, first time
Zoho's eight data centres do not share data, and a token from one is a bare 401 at another. Zo reads the region out of Zoho's own redirect and corrects the setting for you, turning the commonest misconfiguration into a non-event.
A log you can hand to somebody
Every call, with the endpoint, the HTTP status, Zoho's own error code and how long it took. Client secrets, refresh tokens and bearer tokens are redacted before the row is written — not hidden on display.
Frequently Asked Questions
The questions worth answering before you connect it to your books.
$99, once, with a $79/year renewal for continued updates. One edition — no free tier, no trial, and nothing held back for an upgrade. Every store gets payments, refunds, item sync, sales orders, mapped tax rates, custom field mapping, bulk backfill and the full log. If you let the renewal lapse the plugin keeps working; you stop getting updates until you renew.
No, and that is the thing it is built around. Every Craft ↔ Zoho pair is a row in a table with a unique index, and Zo claims that row before it sends anything — a duplicate-key failure is the answer, rather than something to handle. A retry, a redelivered webhook or an impatient second click all find the first attempt's row and stop. The only way to get two is to unlink an order and sync it again, which is why unlink sits behind its own permission.
Not necessarily, which is why Zo checks rather than assumes. In the default mode it sends no tax rates at all and puts the difference between Commerce's total and the line arithmetic in as one adjustment, so the invoice total equals what the customer paid, exactly. In mapped mode Zoho computes the tax itself — properly itemised reports, at the cost of Zoho possibly disagreeing. Either way both totals are stored side by side and the difference is surfaced.
No. Syncing runs through the queue by default, and everything Zo does during checkout fails open — Zoho being down must never stop a customer paying.
Not if you leave "Reuse an existing Zoho contact with the same email" on, which is the default. Zoho also enforces unique contact names, and Zo adopts the existing contact when Zoho says one already exists rather than failing the order or creating a near-duplicate.
Nothing, until you ask. Backfill pushes them in batches, with a five-order rehearsal first and pacing that stays inside both of Zoho's rate limits — 100 calls a minute and a plan-dependent daily cap.
All eight — .com, .eu, .in, .com.au, .jp, .ca, .sa and .com.cn. You do not have to choose correctly up front: the regions do not share data, so a wrong one is a bare 401, and Zo reads the region out of Zoho's own redirect and writes it back to the setting.
Zoho, by default, so its own sequence stays unbroken — in several jurisdictions that is a legal requirement rather than a preference. Craft's order reference goes into Zoho's reference field so you can find either record from the other. One setting switches it if you would rather the two systems share a number.
As credit notes, not payment refunds. Zoho will only refund a customer payment whose mode is autotransaction, and only the unapplied part of it, so a refund against a paid invoice cannot be represented that way. Zo writes a credit note to reverse the revenue and, when a deposit account is configured, records the cash movement against it.
Yes. Client secrets, refresh tokens, authorization codes and bearer tokens are redacted before the row is written, not hidden at display time, so the log is safe to export and hand to whoever is debugging.
No. craft.zo is read-only by design — it reports what has been synced and whether the connection is live, and that is all it can do.
Craft CMS 5.3+, Craft Commerce 5.0+, PHP 8.2+.
No. Zo is an independent plugin built by Justin Holt, and it is not affiliated with, endorsed by, or sponsored by Zoho Corporation. It talks to the Zoho Books public API the same way any third-party integration does. “Zoho” and “Zoho Books” are trademarks of Zoho Corporation.
One price, the whole integration
Zo is $99 with a $79/year renewal. There is no free tier and nothing held back for an upgrade: payments, refunds, item sync, sales orders, mapped tax, custom fields and bulk backfill are all in the one edition.