Start from the ledger, not the button
Checkout is the easy part. The hard part is proving, weeks later, that every payment that left a customer's account landed in yours exactly once. We design the flow around a payment record we own and treat the provider as a source of truth we reconcile against.
- Create your own reference before you call the provider
- Store the amount and currency you expect alongside the reference
- Never treat a redirect alone as proof of payment
Treat webhooks as the source of truth
A customer can close the tab before the redirect lands, so the redirect is a nicety and the webhook is the contract. We verify the signature, look up our own record by reference, and apply the state change idempotently, so a retried delivery cannot double-credit an order.
- Verify the signature before parsing the body
- Ignore a webhook that arrives out of order or after the fact
- Log the raw payload so disputes are answerable
Handle the decline paths
A decline is normal, not exceptional. Some cards fail on international transactions, some banks time out, and some customers simply run out of patience. The checkout should explain what happened and offer a clear next step instead of a dead end.
- Show a specific reason where the provider gives one
- Offer a retry without re-entering the whole form
- Keep a support reference the customer can quote
Reconcile on a schedule
We run a daily job that pulls settled transactions and compares them to our records, flagging anything missing on either side. Small studios do not need a finance team to sleep at night; they need a check that runs without being asked.
- Match on the reference you generated, not the provider's id
- Surface mismatches in a dashboard a human actually reads
- Keep the audit trail immutable



