I recently put together a custom payment gateway for WooCommerce—an offshoot of a client project that I since spent quite some hours tweaking and refining—and, as always, had to make quite a few design choices. One particular type of choice had to do with order statuses throughout the payment processing flow.
This specific payment method required quite a few external API calls, all of which—however unlikely—could possibly fail. If that was to happen, I asked myself, should I then mark the order ‘On-hold’, ‘Failed’, or simply leave it ‘Pending’?
Also, the nature of this payment service is such that it takes at least a day or so to finally verify and confirm payment. That alone made me want to use ‘On-hold’ (which would also reduce stock) only if payment coming through was close to guaranteed.
As mentioned in the WooCommerce documentation, an order should never really stay ‘Pending’ (i.e., ‘unpaid’) unless something went wrong communicating with the external payment provider. (Seems ‘Pending’ is an option, then, for dealing with a failed API call!)
Note: an order that remains pending for a little while will automatically get canceled. ‘On-hold’ pretty much means payment’s on the way but unconfirmed.
‘Failed’ really means ‘payment failed’. (‘Processing’ means: in the process of being packaged or shipped, something like that. And ‘Complete’ is just that: fully done with.)
But: different gateways will sometimes deal with this differently. There’s no definite right or wrong, really.
One possible solution
In the end, I decided to keep things simple and went with the following reasoning: any payment failure, whatever the cause, results in a failed order. Success leads to an ‘On-hold’ status.
The final verification (the one that hopefully results in a ‘Processing’ status) is always done through a separate WP cron job.
Order notices are used to communicate back to customers in case anything goes wrong.
Error notices are not displayed on the so-called ‘Pay for Order’ screen—I only found that out during testing—as that page doesn’t use any ‘cart logic’, or if the cart’s already been emptied. (For this, I chose to not empty the cart until pretty much the end of the order being processed. There are downsides to this, too, but I by now considered the overall chance of ending up in this situation sufficiently small.)