Skip to content
achacon.dev
All work

2026

Peer-to-peer payment reconciliation

Checkout gateways for three payment apps, and a matcher that confirms the order when the payment alert arrives.

Role
Sole engineer: gateways, matching engine, and mail verification
Stack
PHPWordPressWooCommerceGoogle Apps ScriptHMACDKIM / DMARCREST API
  1. Trigger: 01Payment alertNotification lands in the inbox
  2. Process: 02Signed forwardInbox script HMACs the request
  3. Process: 03Authenticity checkDKIM or DMARC, never SPF alone
  4. Process: 04MatchedOrder number, then amount, then payer
  5. Output: 05Order confirmedAnything unclear waits in review
An alert clears two separate checks before it can touch an order.

The problem

Orders paid by peer-to-peer transfer sat On hold until somebody read the inbox and matched the payment to an order by hand. It was slow for the customer and got worse with volume. A payment email is also the kind of thing worth forging.

What I built

  • Nine checkout gateways: three payment apps across three receiving accounts, generated from one canonical source by a clone script, so a fix gets written once instead of nine times. Each stamps its own account reference into the order data and reports on itself from its own admin screen.
  • Payment details stay hidden until the order exists. Show a QR code at checkout and someone sends a round number matching no order. Show it on the confirmation page and both the amount and the order number are already real.
  • An inbox script forwards each payment alert to a signed endpoint. Two separate layers of trust: the signature proves the script sent the request, and a second check proves the payment service sent the email, requiring DKIM or DMARC alignment. I never accept SPF on its own, since it vouches only for the envelope sender's domain.
  • Matching runs in tiers: order number from the payment note, then a unique exact amount, then a payer-name tiebreak. Split payments accumulate against the order, underpayments stay on hold, and an overpayment confirms the order and flags the excess for a manual refund.
  • Anything ambiguous goes to a review queue instead of a guess. Every email, decision and override lands in an append-only audit log, and dry-run mode ships on, so you can watch a week of decisions before it touches an order.
  • The parser handles two generations of one bank's template. The newer one forwards the sender's memo and the older one leaves it in the sender's app, so the order-number tier only runs when the memo is there.