Inspiration

A payment fails in the worst possible way when PayPal processes it but the answer never reaches you. The buyer sees a spinner, taps Pay again, and now there may be two charges, or money frozen for something that never finished. Most payment code assumes the answer arrives. We wanted to build for the day it doesn't.

What if PayPal took the payment, but your app never got the confirmation?

SlothPay is our answer: a safety layer that never guesses about money.

What it does

SlothPay sits between a buyer and the PayPal sandbox and makes a payment survive a lost answer.

  • No double charge. Every payment has one stable identity. An idempotency key, a lock, an open-payment check and a stable PayPal request id (saved before the call is sent) make a repeated click, a retry and a concurrent request resolve to one payment.
  • Lost-answer recovery. If the outcome is unknown, the payment becomes UNCERTAIN. SlothPay does not retry the money move. It asks PayPal what really happened, at 0.3, 1, 2, 4 and 8 seconds, and moves to the true state.
  • Two payment paths. SmartPay captures at once. ConditionalPay holds the funds and captures only when a condition is verified. Silence never captures, and an unmet condition voids the hold.
  • Plain, safe next steps. The dashboard shows a simple status and only the actions the current state allows.
  • Proof. A tamper-proof audit trail, verified PayPal webhooks, and a shareable Trust Passport that separates what PayPal confirmed from what we only recorded.
  • Speed you can see. Every payment's time is split into SlothPay's own time, PayPal's time and the buyer's time, so the cost of safety is measurable. A Speed panel and a Compare page show it.
  • Optional AI helper. Explains a payment in plain words and reads an invoice into a draft. It only sees masked facts and can never move money.

Everything runs against the PayPal sandbox. No real money moves.

How we built it

We planned the whole project as 19 ordered tasks, each with a goal, dependencies, a prompt and a test it had to pass, and built them one at a time with an AI coding assistant (Claude). Every change was run, read and reviewed by us before merging.

  • Backend: Python and FastAPI, PostgreSQL with Alembic migrations, Redis for locks, replay protection and rate limits, optional Temporal for the ConditionalPay deadline timer.
  • The rule that matters most: a payment state machine in plain code. The AI explains; the state machine decides what may happen to money.
  • Frontend: Next.js 16 with TypeScript and Tailwind, with live status updates.
  • Security: signed session cookies, rate limits, startup guards that refuse unsafe settings, forged-webhook rejection, and a secret scanner.
  • Speed (Task 19): one shared PayPal connection with a pre-warmed token, an unnecessary PayPal read removed, live updates and monitoring moved after the response, and per-payment timing stored in a payment_timings table.
  • Deployment: the API, database and Redis on Render, the dashboard on Vercel, with background workers inside the API because the free tier has none.
  • Testing: over 1,000 automated tests, including PostgreSQL-backed integration tests, run against a local fake PayPal plus sandbox checks.

Challenges we ran into

  • The lost answer is the hard case. If you retry, you may charge twice. If you wait, money may be stuck. The solution was to save before sending, reuse the same request id, and let PayPal tell us the truth.
  • Making "safe" fast. Safety steps cost time. We measured first, then cut waste (a shared connection, one fewer PayPal read, work after the response) without ever skipping save-before-send, the duplicate checks or the state machine.
  • Our first benchmark was wrong. It counted our deliberate waiting as SlothPay's own time and reported a huge overhead. We fixed the accounting so waiting is never counted against us.
  • "Works on my machine." Failures appeared only on some computers, such as leftover local settings and a random test that depended on the last character of a token. Fixing them taught us to isolate tests from the developer's environment.
  • Free hosting limits. The free API sleeps when idle, and the free database expires after 30 days. We documented both instead of hiding them.

What we learned

  • Design for the unknown outcome first. Everything else gets simpler.
  • A deterministic state machine plus an AI that can only explain is easier to trust than an AI that decides.
  • Measure before optimizing, and label every number honestly. Our speed figures are sandbox and simulator numbers, not real PayPal speed (in a simulator run, SlothPay's added time was p95 47 ms against a 100 ms target).
  • Review catches what tests miss: our own pull-request reviews found missing documentation and a missing rate limit.

Honest limits

  • PayPal sandbox only. No live payments.
  • A declined capture is a known gap, and about 10% of lost "create order" answers can wait for a check until a rescue action runs.
  • The hosted demo has no Temporal worker and no failure-simulation panel, so the video shows those running locally.
  • The optional signed-webhook extension has not been verified with a live Zapier.

What's next

Offering a new payment method after a declined capture, automatic expiry of held payments on the hosted demo, an audited reset for the signed condition webhook, and testing the Zapier flow end to end.

Try it

Open the hosted demo at https://slothpay-web.onrender.com/ (the first load can take about a minute), log in as alice, bob or carol, create a SmartPay payment and approve it with a PayPal sandbox buyer account. The code and setup steps are in the repository README.

Built With

Share this project:

Updates

Submission history