Freshh for Craft CMS

Configuration

When an order is sent

Sync automatically is the master switch. With it off, orders only reach FreshBooks when you send them by hand or from the console — a good way to try the mapping on real orders first.

Send an order when decides what makes an order eligible:

SettingWhat triggers the sync
Commerce completes itThe moment Commerce completes the order
It reaches a statusThe first time it reaches one of the statuses you tick
NeverNothing automatic; manual and console only

Delay before sending is worth a few seconds. It lets the payment transaction land before the invoice is built, so the invoice arrives already paid rather than being paid a moment later by a second call.

Nothing about FreshBooks happens during checkout. Syncing is queued, always — a FreshBooks outage cannot fail an order.

The invoice number

FreshBooks will allocate a number itself, but two invoices created in the same second then race for it. Freshh therefore always sends its own, from the order reference, the order number, the short order number, or an object template.

A prefix is applied on top. An order that somehow has no reference falls back to its short number, so there is always something to send.

Lines, tax and discounts

This is the part worth understanding, because the two systems do arithmetic differently. FreshBooks builds an invoice from qty × unit_cost plus percentage taxes. Commerce stores totals and tax amounts.

Discountsline gives each discount its own negative line, which is clearest for accounting. net folds line-level discounts into the unit price and only breaks out order-level ones.

Tax included in the price — a FreshBooks line has no inclusive-tax concept at all. split sends the net unit cost plus the rate, which comes to the same total and keeps the tax visible in FreshBooks' reports. ignore sends the gross price and no tax.

Combined tax name — a FreshBooks line carries two taxes. A third and fourth are summed under this name, and the sync says so rather than dropping them.

Refuse invoices that do not balance — leave this on. Freshh computes the total FreshBooks will reach and compares it with the order. An invoice a pound out is worse than no invoice: nobody notices until a reconciliation fails months later, by which time there are hundreds of them.

Add a rounding line when needed absorbs the odd cent between the two systems' arithmetic. Largest rounding difference is the ceiling on that — a gap bigger than it is not rounding, it is a mapping problem, and is reported instead of hidden.

Clients

Clients are matched on email, not on the Craft user, because most stores take guest checkouts and three guest orders from one address are one client rather than three.

Update clients on every sync is off by default: a merchant who has tidied a client record in FreshBooks does not want the next guest checkout undoing it.

FreshBooks rejects a client with no surname, so Fallback surname covers an order that has no name at all.

Payments and refunds

Each successful purchase or capture becomes a FreshBooks payment against the invoice. The payment type per gateway is free text on FreshBooks' side; the settings screen offers the values its own UI uses.

Include the gateway reference puts the transaction reference in the payment note, which is what makes a chargeback traceable later.

FreshBooks has no negative payment, so a refund becomes a credit note — the instrument FreshBooks' own documentation points at for exactly this.

What comes back

FreshBooks callbacks tell Craft when an invoice is viewed, paid or written off. They need a publicly reachable URL; press Register callbacks and FreshBooks posts a verification code to the site, which Freshh answers automatically.

Note changes on the order adds a line to the order's history. Change the order status too is off by default — turning it on lets FreshBooks move orders, which also fires Commerce's status emails.

If FreshBooks cannot reach the site — local development, staging behind basic auth, an intranet — run freshh/sync/pull on a schedule instead.

Logging

Every call Freshh makes is logged with its payload, status code and duration. Keep request and response bodies is worth it while a connection is being set up and the first thing to turn off once it works. Tokens and secrets are redacted either way.