Skip to content

Payments

The boring half of payments is the whole job

A billing feature starts as a simple invoice tool. Then it grows a state machine with disputes, refunds, and ACH. The boring states are where the money lives.

AP

Alex Pavlov

May 12, 2026 · 6 min read

Every payments project begins with the same sentence: "we just need a simple invoice tool." The word "just" is doing the heaviest lifting in that sentence, and it is about to throw its back out.

On a healthcare platform we built billing that started exactly there. It became a real payments system: connected Stripe accounts, card and ACH, and an invoice state machine with every unglamorous state. Draft, sent, unpaid, partially paid, refunded, voided, disputed. Eight states, and each one is a place money can quietly go wrong.

The demo is a button. The product is everything after it.

Anyone can charge a card on the happy path. The job is what happens when the network times out after the charge succeeds but before your server hears back. Did the customer pay? Do you retry? If you retry wrong, you charge them twice, and now you have a refund, an apology, and a trust problem that costs more than the sale.

What "simple" billing actually contains

  • An invoice state machine that survives partial payments without losing the plot.
  • Idempotency keys, so a retry does not become a second charge.
  • Webhook handling that is correct even when webhooks arrive late, twice, or out of order.
  • Reconciliation that ties your numbers to Stripe's numbers to the penny.
  • Disputes and refunds as first-class states, not as a support ticket and a sigh.

Why correctness beats speed here

You can ship a fast payments feature or a correct one. In healthcare and finance the correct one is the only one that counts, because the cost of a double charge is not a bug ticket. It is a refund, a chargeback fee, and a customer who now reads every invoice you send with one eyebrow up. We shipped this one on a tight deadline without cutting the safety corners, which is the only acceptable way to be fast about money.

When you do not need any of this

If a hosted checkout link covers your entire flow, take the link and go build something else. You do not need a custom payments system to sell one thing at one price to one kind of buyer. Call us when the flow grows a second party, a second currency, or a second way to go wrong. That is when "just an invoice tool" stops being just anything.

Frequently asked

Why is a custom payments system harder than it looks?

The happy path is one button. The real work is the failure modes: network timeouts after a successful charge, duplicate webhooks, partial payments, refunds, and disputes. Each needs an explicit state and idempotent handling so you never charge twice or lose a payment.

When should I use a hosted checkout link instead of a custom build?

Use a hosted link when you sell a small number of fixed-price items to one type of buyer. Build custom when you have multiple parties, multiple currencies, subscriptions, marketplaces, or complex invoicing rules that a hosted page cannot express.

What does payment reconciliation mean?

Reconciliation is making your own records of money agree with the payment processor's records to the penny. Without it, small discrepancies from fees, refunds, and disputes accumulate until your books no longer match reality.

Have a workflow that needs this?

Tell us the shape of the problem. Scoped estimate, usually within 3 to 5 business days.

Estimate project