Worth understanding because it explains why a payment can sit as waiting for a while, and why we never guess.

The path

1

The customer proves their number

They type it on your page and enter a code we text them. Nothing is sent to a number nobody proved.
2

We write the payment down

Before we ask Safaricom for anything. A payment that leaves somebody’s phone with no record of it cannot be recovered; a record with nothing against it can.
3

We ask for the money

The prompt goes to their phone, from a queue rather than from the web request, so a slow provider never holds up the page.
4

Safaricom tells us what happened

They post the result back to us, and we settle the payment on it.

When Safaricom does not answer

Their message can be lost, delayed, or arrive saying something that is not an answer at all. So we also ask. A payment nobody has answered for is queried repeatedly, backing off as it ages. If the answer is real, the payment settles on it.
An answer that is not an outcome (“system internal error”, “still processing”) settles nothing. Neither tells us whether the money moved, and closing a payment on one would write off a payment that may well have succeeded.
Such a payment stays open and keeps being asked about. If nobody ever gives us a real answer, it eventually records as timed out, which is the honest thing to say when nobody ever told us.

What each outcome means

Asking about one yourself

Any payment that has not settled carries a Check with M-Pesa button. It asks Safaricom right then, with the credentials that payment went out on, and tells you what they said in their own words. The automatic chase runs on its own schedule, which is right for the hundred payments nobody is watching and wrong for the one you are looking at.