Wednesday, April 22, 2026
Day 20 — Stripe wires up
PaymentIntents created, webhooks reconciling, Pay button on the client invoice. The first time a barn gets a Stripe payout from this codebase will be a real moment.
1 min read · Backfilled retrospective — written 2026-05-04
Daily entry
Day 20of building EquinePilot
Currently Day 77 · founder build log
Milestones reached
- ✓Stripe payments
Spent today wiring up Stripe end-to-end on the invoice payments flow. The shape:
- Manager generates an invoice on the 1st (already worked).
- Client opens StableSync on their phone, sees the invoice, taps Pay.
- Stripe Elements form takes the card. PaymentIntent is created server-side via
POST /api/billing/invoices/[cycleId]/pay-intent— server checks the invoice belongs to the requesting client (no enumerating other clients' invoices), reuses an existing intent if one is already pending for the same balance, refuses if the invoice is already paid in full. - On success, Stripe fires
payment_intent.succeededto our webhook. Webhook handler verifies the signature, mapsmetadata.cycleIdto the actual invoice, marks it paid, records the payment method. - Client gets a confirmation; manager sees it as paid in their list.
The tricky part of today wasn't the happy path — it was idempotency. A retried webhook can't double-mark an invoice paid. A re-clicked Pay button can't create two intents. The fix in both directions: check current state before mutating. If the invoice is already paid, the webhook returns 200 without doing anything (Stripe needs a 2xx or it'll keep retrying). If a PaymentIntent already exists for the balance, the API returns the existing one rather than creating a new one. Wrote tests for all of that — would have hated to find this in production.
Also added a per-barn enable/disable flag for Stripe. Some barns will collect outside Stripe and just want to mark things paid manually — that flow already worked, so the Pay button just hides itself when Stripe isn't enabled.
The first time a barn gets a Stripe payout from this codebase will be a real moment. Today made that moment possible.
Tomorrow: auth hardening — password reset, forced first-login change, graceful session expiry. The unglamorous-but-essential week.