A checkout should refuse the order it cannot fulfil
Billing systems take the money first and provision second. That ordering is correct, and it means every gap in your provisioning becomes a charge you have to explain and a refund you have to make.
Almost every hosting platform charges before it provisions. The order is paid, then the account is created, the machine built, the zone written. This is the right way round — provisioning first and billing second means building things for people who then do not pay — but it has a consequence that is easy to miss until it happens to you.
Every gap in provisioning becomes a customer who has been charged and not served.
Not an error. Not a failed request they can retry. A completed payment, a confirmation email, an invoice, and nothing behind it.
Where it actually goes wrong
The interesting cases are not the dramatic ones. A provisioning API being down is obvious, alerts fire, and you deal with it. The expensive failures are quieter — the order that was never buildable in the first place:
- A hosting plan ordered without a domain, when the panel needs one to create anything.
- A server plan with no size and no operating system image, because the product was never bound to a backend, or was bound to one that has since been removed.
- A DNS service ordered for a domain that is not delegated to your nameservers.
- A plan whose quotas exceed anything the platform behind it can actually allocate.
Each of these is knowable before the card is charged. In every case the information needed to refuse is sitting right there at the moment the customer clicks Add to cart.
Put the guard where the customer still has context
There are three places a bad order can be stopped, and they are not equally good.
At provisioning is where you will end up if you do nothing. The check runs after payment. You are now writing an apology, issuing a refund, and hoping the customer's first impression survives it.
At checkout is better but still late. The customer has entered payment details and is committed. An error here reads as a broken shop.
At add-to-cart is where it belongs. The customer is still configuring. They have the form open. If the refusal names the missing thing — enter the domain this hosting is for — they type it and continue. That is not an error, it is just the form working.
The test for whether your guard is in the right place: does the message tell them something they can act on in the next five seconds?
Refuse loudly, not silently
There is a tempting middle path where the order is accepted and quietly filled in with defaults — pick the first available size, generate a hostname, choose a region. Sometimes that is right. Often it produces a service the customer did not ask for, in a location they did not want, which they will discover later and ask you to move.
Silent defaults are only acceptable when the value genuinely does not matter to the customer and you would be happy to defend the choice afterwards. A generated hostname passes that test. A region does not.
And there is a sharper version of this. If a product has no provisioning configuration at all — nothing pinned by the operator, nothing offered by a backend — then there is no default to fall back on. There is nothing to build. The only honest answer is to stop, and to say that the customer should not be charged for a server that cannot be created.
The part everyone postpones
Even with good guards, provisioning will fail sometimes. The backend times out. A quota was exhausted an hour ago. Something upstream changed shape.
When that happens in a pay-first flow, you need an operator retry: a way for a human to look at a paid order that did not provision, fix the cause, and run it again — without asking the customer to reorder, and without hand-writing database rows.
Ship it in the same release as the flow that charges. Not the next one. The gap between we can take money for this and we can recover when it fails is measured in support tickets, and it is always longer than it looked on the plan.
The short version
Charging first is fine. It just moves the burden of correctness earlier. Anything your provisioning layer requires, your cart should already have demanded — and anything your provisioning layer cannot supply, your cart should already have refused.


