Kascado / Blog / Your WooCommerce order status never says "paid"

Your WooCommerce order status never says "paid"

A pallet ships on a Tuesday. The customer is on NET 30. Six weeks later a cheque arrives covering three orders, short by the value of a carton that turned up damaged. Somewhere in that sequence your store should have recorded that you were owed money, and later that most of it came back.

It recorded neither.

Open the order and it still says "processing". It said "processing" the day it shipped, it says it today, and it will say it next year. Nothing about the status changed when the cheque cleared, because nothing in WooCommerce was watching for the cheque.

This catches out almost every wholesale store that moves onto payment terms, and the reason is not a bug or a setting anyone forgot to tick. It is that WooCommerce order statuses answer a different question from the one you are asking.

What the statuses actually describe

WooCommerce ships with seven order statuses: pending payment, processing, on hold, completed, cancelled, refunded and failed.

Read them as a group and the pattern is clear. They describe where an order sits in fulfilment. Has it come in, is it being picked, has it gone out, was it stopped. That is a warehouse question, and for a store taking card payments at checkout it is the only question worth asking, because payment already happened before the order existed.

Two of them gesture at money. "Pending payment" means the order was placed but nothing was taken. "On hold" means stock is reserved while you wait. Both of them mean the same thing from your side of the counter: we have not been paid yet.

There is no status that means we have been paid.

No WooCommerce order status means "the money arrived". The closest any of them gets is "we are still waiting".

Which gateway you use decides which of these an order lands in. WooCommerce core sets direct bank transfer and cheque payments to "on hold", and cash on delivery to "processing". A custom invoice gateway, or a payment terms plugin, can set whichever it likes. So two wholesale stores running the same terms can have their unpaid orders sitting in two different statuses, and neither one is wrong, because neither status was ever about payment.

The date_paid field is not what it sounds like

WooCommerce does keep a date_paid field on every order, and it looks like the answer. It is worth knowing exactly what goes into it.

WooCommerce writes that date when an order reaches a status it treats as paid, which by default means processing or completed. It does not check whether money moved. It checks the status.

For a card gateway those are the same thing. The gateway confirms the payment, the order moves to processing, and the date is the moment the money was authorised. That is genuinely useful.

For everything else it comes apart, in one of two ways.

An order on bank transfer or cheque starts life on hold. It sits there until somebody in your office opens it and changes the status by hand. That is the moment date_paid gets written. If the cheque arrived on the 3rd and nobody got to it until the 11th, the field says the 11th. If nobody ever gets to it, the field stays empty on an order that was settled in full months ago.

An order placed on cash on delivery, or through an invoice gateway that sets orders straight to processing, is worse. It reaches a paid status the moment the customer checks out, so date_paid is stamped before the pallet has left the building and weeks before anything reaches your bank. The field will tell you that order was paid, and nobody has paid anything.

In both cases the field carries no amount. It is a timestamp. It cannot record that a customer paid 8,000 against an invoice of 15,870, because WooCommerce has nowhere to put that idea.

An order is paid or it is not

This is the part that breaks a wholesale business, and it is structural rather than cosmetic.

In WooCommerce, payment is a property of an order, and it is binary. The order is settled or it is outstanding. There is no concept of a part payment, so there is no way to record that a customer sent 8,000 towards an invoice of 15,870 and still owes the rest.

Wholesale payments almost never match invoices one to one. A customer who orders weekly pays monthly, in one amount, covering whatever they decide to cover. They round down because of a short shipment. They pay four invoices and leave the fifth because it is in dispute. They overpay by 200 and expect it knocked off next time.

None of that fits in a field that holds a date.

And there is no customer-level view at all

Even if every order were tidily marked, you would still be missing the number you actually want.

WooCommerce organises everything around the order. There is no screen anywhere in the admin that says this customer owes you 10,211 across two unpaid invoices, the oldest of which is 108 days past due. To get that you would open every order for that customer, check each status, remember what each one was for, and add them up.

With 14 wholesale accounts and 30 open orders that is a slow morning. With 200 accounts it is somebody's job.

WooCommerce can tell you everything about an order and nothing about a customer's balance.

Which is where the spreadsheet comes from

Every wholesale store that sells on terms ends up with the same file. It lists customers down one side and what they owe across the other, and it is rebuilt by hand, usually monthly, usually by whoever has been there longest.

It exists because the store cannot answer the question, so somebody answers it manually. And because it is maintained separately, it drifts. It disagrees with the store, it disagrees with the bank, and at month end somebody spends an afternoon working out which of the three is right.

The spreadsheet is not the problem. It is the symptom. The problem is that the order status was never going to tell you what you needed, and marking orders "completed" more diligently was never going to fix it, because "completed" means shipped.

What actually fixes it

You need a second record running alongside your orders: a ledger that tracks, per customer, what was charged and what has been paid against it.

That record has to do three things WooCommerce order statuses cannot.

It has to hold a balance per customer, not per order. The question is never "is order 164740 paid", it is "what does Bridgeport Supply owe me, and how late is the oldest part of it".

It has to accept a payment that does not match an invoice. One amount, applied across however many open invoices it covers, oldest first, with whatever is left over held as credit against the next order.

It has to keep history. Not a status that gets overwritten, but a list of what happened and when, so that any balance on any screen can be traced back to the entries that produced it.

That is what Kascado Accounts Receivable does. It is free, it runs inside your WooCommerce admin, and it keeps that ledger alongside your orders without touching them. You nominate which payment methods mean "pay later", orders placed through them become charges automatically, and you record payments as the money actually turns up. The documentation covers the setup.

It does not change your order statuses, and it should not. "Processing" genuinely does mean the order is in fulfilment. The mistake was ever expecting it to mean anything else.

One thing worth checking today

Before any of that, open your store and filter orders by the payment method your wholesale customers use. Bank transfer, cheque, whatever your invoice gateway is called.

Look at how far back they go, and what status they are sitting in. Then pick three at random and work out, from the store alone, whether you were ever paid for them.

If you cannot, that is the gap. It is not a reporting problem, and it will not be fixed by a better report, because the data was never recorded. The only way out is to start recording it, and the sooner you start the less history you have to reconstruct.

Knowing which of those accounts have gone quiet on you is a related question with a similar answer, and the Customer Dashboard covers that side of it.

All posts