Build notes

Software that asks your customers for money

4 August 2026 · the capability is easy, the refusals are the work

A customer asks about a chain. The shop sends three photos, quotes a price, and she says she will take it. Then the conversation stops. Following up is somebody's job, which means it is nobody's job, and on Thursday she buys it somewhere else. Nothing went wrong that anyone could point at.

That is the gap this note is about. She was ready. The shop was willing. The software knew both things and did nothing with either, because knowing and acting were never connected.

So we connected them. A drip sequence step, a behaviour rule, a milestone, a completed Flow, or the customer simply typing "how do I pay" can now produce a real payment request in the thread, priced from the shop's own product library, settling into the shop's own gateway account.

Building that took about a day. Everything since has been spent on what it must refuse to do, and that is the part worth writing down. Automation that asks for money is a category of software where the failure mode is not a bug report. It is a customer who feels harassed, or a shop that ships a ring it was never paid for.

Anyone can make a bot send a payment link. The product is in the five times it declines to.

One path, not five

Before any of the refusals, one structural decision makes them possible. All five surfaces call the same function. Not "similar logic in five places", one function.

This sounds like tidiness and is not. Guards written per-surface protect the surface whose author was thinking about them that day. A consent check on the sequence step and not on the Flow completion is not a partial win, it is a false sense of one. With a single path, a guard added on a Tuesday afternoon applies to a Flow somebody built in March.

The five refusals

1. It will not ask someone who said stop

cost to us: a database blip means nobody gets asked, and that revenue is simply gone

Consent is checked before anything is sent, which is unremarkable. What matters is the direction it fails in. If the consent lookup itself errors, the request is refused. Not logged and continued. Refused.

The tempting alternative is to treat an unreadable consent record as no objection, because the sale is right there and the error is probably transient. That choice converts every database hiccup into a message somebody did not agree to receive, on a channel where the regulator and the platform both take that seriously, and the person who finds out is the customer.

2. It will not ask twice for the same thing

cost to us: a genuinely stalled request needs a human to clear it

An automation that runs nightly against a segment will happily ask the same person for the same item every night until they pay or block you. So an open request for a given contact and item blocks a second one.

This guard is also where we found the more interesting bug. A checkout the gateway refused used to leave the order stranded in its initial state, which the duplicate check counted as an open request. The result was software that answered "already requested" forever, about a person who had never once been asked. The state machine now has an explicit path for a refused checkout, so a failure is a failure rather than a permanent silence.

3. It will not sell what is not there

cost to us: a shop with untracked stock has to say so explicitly

Stock is decremented when an order is fulfilled, which is correct: a made-to-order piece can be days from dispatch. But it means a plain "is there stock" check passes repeatedly for the same unit. We measured it before fixing it. One item in stock, and the system was willing to ask fifty people to pay for it.

Fifty real payments, into the shop's real account, for one ring. The shop then gets to explain itself to forty-nine people and refund them, having done nothing wrong except trust its software.

The fix counts what is already promised: every open order containing that product has committed its units even though nothing has left the shelf. No reservation state was introduced, deliberately, because a reservation is a thing that can leak a hold when an order is abandoned and then quietly hide inventory nobody can sell.

4. It will not shout through a closed window

cost to us: a template has to be approved by Meta before this works at all

WhatsApp allows freeform messages only inside 24 hours of the customer's last message. Automation, by its nature, usually fires outside that. Inside the window we send the native interactive payment card. Outside it, an approved template.

There is a real constraint underneath that is worth knowing if you are building this yourself. Meta fixes a template's button URL at approval time, apart from a single trailing variable, so a template physically cannot carry an arbitrary gateway checkout link. We send a stable redirect ending in a per-order token, and resolve that token to whatever link the order actually has. The side effect is that the gateway becomes swappable without touching the approved template.

5. It will not call a deposit a sale

cost to us: an order sits open and somebody has to chase it

The links this product creates cannot be part-paid. But a shop can paste in a link it made by hand in its own gateway dashboard, where part payment can be switched on, and that gateway's notifications reach us regardless of who created the link.

Our webhook used to read any successful payment event as full settlement. On a ₹2,500 order, a ₹500 advance would have marked it paid, sent the customer a receipt, run fulfilment, decremented stock and closed the order. The shop packs a ring, having been paid a fifth of it, and finds out weeks later.

It now compares what arrived against what is owed. A shortfall leaves the order exactly where it was, records the real numbers in the ledger, and messages the shop with the opposite instruction from a sale:

Part payment received: ₹500.00 of ₹2,500.00
Still outstanding: ₹2,000.00
Customer: 9198xxxxxxx
This order is NOT paid and has not been fulfilled.
Collect the balance before sending anything.

And the customer receives nothing at all, because a receipt is a promise the shop has not yet been paid to keep.

The half of this nobody demos

The refusals above are about not doing harm. The quieter problem is a system that is technically correct and still leaves people worse off, which is where two of the sweeps came from.

What used to happenWhat it feels like from the other side
A customer who had part-paid was chased by the abandoned-cart reminder, because the poller read a part payment as no payment. You send money, and the shop's system asks you to pay. You are now arguing with a robot about whether you paid.
An order past its deadline was expired by a cron, regardless of whether money had arrived against it. Your deposit is attached to an order the shop's own software has written off.
A payment landed and the buyer was thanked, while the person who has to pack the thing was told nothing. You paid on Tuesday. It ships when somebody happens to open a dashboard.

All three are the same shape, and it is the shape this codebase keeps rediscovering: a feature reports success while doing nothing useful for a person. Fixing them is unglamorous. It is also most of the difference between software that collects payments and software a shop can actually run on.

What it cost, stated plainly

A decision with no cost is a slogan, so here are the ones we are carrying.

Unattended money requests are gated behind a second capability, separate from ordinary commerce, and it is off by default. A shop that wants automation to ask on its behalf has to turn that on deliberately. That is friction we chose, on the grounds that "our software started asking my customers for money" should never be something anyone discovers.

We also refuse partial payments rather than half-supporting them. Supporting instalments properly means deciding what a balance reminder looks like, what happens when it is never paid, and what the shop sees in between. Until that exists, an underpayment is an anomaly we stop and hand to a human, and we would rather state that than let the behaviour be whatever the code happens to do.

And the gateway is the shop's own. The money settles into their account, at their rates, under their relationship with their provider. We are not in the payment path, which also means we cannot fix a payment problem for you, only tell you plainly that one happened.

What is still open

Refunds run through the gateway dashboard today, not through us. Disputes and chargebacks are unhandled. Partial payments are refused rather than supported, as above. The message that tells a buyer their order has shipped is waiting on template approval at Meta, and until it clears, a dispatch days after payment usually cannot be delivered at all, because the service window has closed. That last one is a genuine limitation of the channel and we would rather name it than let you find it.

The absence of surprise is the whole product. It is also the only thing here you cannot demo.

What a shop should get from this is boring. Your software should not ask a customer for money they have already sent, should not sell a ring twice, and should not tell you an order is paid when a fifth of it is. You should not have to check. That is the same standard we are trying to hold with our own customers, and it is built the same way: by refusing to let small unspoken gaps accumulate.

Get the next one on WhatsApp

Message the line and type NOTES. The latest one comes straight back, in the same thread, from the number that sends everything else. Reply STOP whenever you like and it stops.

Send NOTES on WhatsApp

Ask our WhatsApp number what it costs

The most honest demo we have is the purchase itself. Message the line and type pricing: you will get our live catalog as a WhatsApp list, the real number of slots left, and a payment link on whatever you tap. Every guard described above is running on that conversation while you have it.

Written 4 August 2026. We append when the facts change. Related: the belief this comes from, the WhatsApp payments implementation details, what we found in our own billing, all build notes.