Skip to content

Accounting (Xero, QuickBooks)

Paid add-on. A completed payment files a customer and an invoice in Xero or QuickBooks Online. One invoice per transaction, written in the background.

It is the last link in the commercial chain: the form takes the money, the accounts record it. The module does that, and nothing else — no bank reconciliation, no credit note, no stock, no return, no reverse synchronisation, no modification of a finalised invoice.

Two things set it apart from the twenty-seven modules preceding it, and they are the first two sections.

Xero has a draft. QuickBooks does not.

That is the fact deciding everything.

At Xero, a DRAFT invoice creates no entry and appears in no report. An accountant reads it over, corrects it, approves it. That is exactly what is meant by a draft, and it is what the plan asked for: “start with a draft rather than a final invoice”.

At QuickBooks Online, the API does not allow creating an invoice that does not post: an invoice created touches the books immediately. The only unposted object is the estimate, which is a commercial proposal — manufacturing one for an order already paid for would write an untruth into somebody’s accounts.

That difference is not smoothed over. The default mode being the draft, a form connected to QuickBooks writes nothing until the administrator has explicitly chosen the posted invoice. Three screens say so: the form’s panel, the module’s screen, and the reconciliation.

The silence is deliberate. It is better than a site posting invoices without its owner’s knowledge.

And that refusal is recorded

A first version stopped before even creating the tracking row: the site wrote nothing, and kept no trace of it. The reconciliation screen — whose whole purpose is to show what is missing from the books — showed precisely not that case.

The row is now filed, as a failure, with its reason. An uninvoiced payment that appears nowhere is the worse of the two possible errors.

Nothing is recomputed

Neither the price, nor the discount, nor the tax. Everything comes from the summary saved with the transaction, which is to say from what was displayed to the customer and then debited.

A price, a rate or a label can change afterwards. An invoice composed from the form’s configuration would then show something other than what was taken — and an invoice that does not match the debit is an accounting error, not a display detail.

The amounts stay in whole cents, as in the payments core, and are only divided at the moment of composing the request. Making the round trip at each step would have reopened the discrepancy the core closes.

If it does not balance, nothing is written

Lines, less the discount, plus the tax if it is added on: the result must land on the amount taken, to within two cents — the tolerance of a tax rounding on a discounted subtotal, not that of a structural divergence.

Beyond that, the module refuses. A document that does not balance leaves whoever reads it over with no means of knowing which of the two figures to believe.

The discount is a line, not a shaved price

It would have been shorter to take the discount off the unit prices: the lines would have landed on the total debited, and nothing would have looked wrong.

That would be an invoice lying about its contents. The customer ordered an item at its price and obtained a reduction; books showing the item cheaper lose the trace of the commercial gesture, and the promotional code with it.

The two services express it differently, and each receives its own: a negative amount line at Xero, which has no invoice-level discount line; a DiscountLineDetail in last position at QuickBooks, which has one, and whose amount is positive — it is the type that says it is taken off.

This point cost us a defect. The original balance check compared the lines against the total without taking off the discount: every order carrying a promotional code came back as “does not balance”, writing nothing. Nothing gave it away, since orders without a discount went through.

The tax, and its limit

An order without tax is announced as such: NoTax at Xero, NotApplicable at QuickBooks. Without that, the service applies the sales account’s default rate, and the invoice claims a tax the customer never paid.

When a tax was taken, the mode is transmitted — tax-inclusive or tax-exclusive prices, getting this wrong shifts the total by the whole tax — but the rate applied remains the accounting system’s: this module does not impose its own, because a form’s rate corresponds to no tax code at the provider’s.

That is the acknowledged limit, and one more reason for the draft to be the default mode: a human reads the figure over before it enters the books. At QuickBooks, where there is no draft, it is one more reason to switch the posted invoice on only knowingly.

The payment is secured before we get here

A submission whose payment does not suit is refused before any recording, by the payment module’s guard. This module therefore attaches itself to the recorded submission, and only checks that the transaction attached did complete — a transaction can turn to failure after the fact, between the scheduling and the scheduler’s pass.

It also refuses a submission sent to the trash or marked as spam: you do not invoice what you have just removed from your own premises.

Idempotence has two storeys

UNIQUE (transaction_id) stops two crossing tasks writing twice, and the row claim — an UPDATE … WHERE state <> 'sent' with a fifteen-minute lease — stops two simultaneous scheduler passes.

The idempotency key covers the case the constraint does not: the request succeeds, and the response is lost on the way. It is derived from the transaction, and therefore stable from one attempt to the next, and the service then returns the first invoice instead of writing a second. Xero reads it in an Idempotency-Key header, QuickBooks in a requestid parameter: the interface asks for only one, and each provider places it where its service expects it.

It is the guarantee that would cost the most to miss. A spreadsheet duplicate is deleted; an invoice duplicate is cancelled by a reverse entry that has to be justified, and gets noticed at the close, a long time afterwards.

Neither service, however, honours a key for more than a finite time. A resend long after the first attempt may therefore write a second invoice, and the screen says so at the moment of offering it.

The screen answers an accountant’s question

Not “what went out”, but “what was taken in and is not in my books”. The join is made on the transactions table, not on the documents one: a row missing from both sides would never be seen.

Every refusal reason is explained in plain words there, because the three most frequent are not failures but deliberate refusals by the module: draft_unsupported, does_not_balance, no_order_summary.

Resending an invoice exists only there, and not in a submission’s detail as in every other module. It is a deliberate detour: replaying an accounting entry without having first looked at what is actually in the accounts means accepting that you might file a second.

A transaction already invoiced is, besides, refused at resend, and the screen invites you to open the invoice rather than write another.

The company is not guessed

realmId at QuickBooks, tenant id at Xero: without it, a valid token does not know which accounts to write into, and a valid token with the wrong one would write into another company’s books.

At QuickBooks, it arrives with the authorisation callback and nowhere else. At Xero, it is asked of api.xero.com/connections just after the exchange; the first tenant is kept, and the screen shows the company’s name — that is how you notice you have connected to the wrong one.

A connection with no company is not held to be ready, and the “Test the connection” button does not ask “are you there” but “which books am I writing into”.

The customer is designated, not guessed

Taking “the first field that looks like a name” would have invoiced in the name of a “Your company name” field where the person was wanted, or the other way round.

With no field designated, the invoice carries the submission’s number — Customer #128 — because an invoice must carry a customer, and a customer named by what we actually know is better than a refusal to write over a setting somebody forgot.

At QuickBooks, the customer is searched for before being created: without that, every order from the same person would create a new one, and the customer file would double every month. At Xero, the contact is filed with the invoice, the service taking care of it itself.

The log carries no amount

The delivery log receives the HTTP code and the reason for a refusal. Neither the lines, nor the totals, nor the customer’s name, nor the accounting company’s id: that log is consulted alongside the submissions, and a merchant’s turnover does not belong in it.

Erasing a submission does not cancel the invoice

The tracking row goes with the submission. The invoice stays in the client’s accounts.

Erasing a submission is an act of data erasure; cancelling an invoice is an accounting act done through a reverse entry, and it is not decided from a forms screen.

What is not attempted, and what is retried

Not replayed: a payment that did not complete, an inactive submission, a summary absent or not balancing, a draft asked of a service that has none, an expired authorisation. None of those cases is repaired by trying again, and each carries its reason on screen.

Replayed, three times, at one then five then thirty minutes: a network failure, a 429, a 5xx.

Schema

slf_accounting_documents: one row per invoiced transaction, with UNIQUE (transaction_id), the remote document’s id, the state the service gave it, the reason for the last problem and the number of attempts.

There is no automatic requeue, unlike the other modules: resending goes through the reconciliation screen.

The connections live in an option, indexed by provider. The tokens and the client secret are encrypted there; the client id, the company’s id and its name are not — they are not secrets, and reading them in clear is exactly what you want when looking for why invoices are arriving at the wrong company.

What V1 does not do

No credit note, no refund passed on, no modification of an invoice already written, no payment attached to the invoice, no reminder, no product or sales account chosen per line, no organisation selection when one application covers several, no reading from the accounts to the site.