Full-stack Engineer

Vidasend

A cross-border remittance product I built end to end: Next.js frontend, Fastify/Postgres backend. Designed so a payment provider going down mid-transfer, or a settlement failing partway through, can't silently cause customer fund loss.

  • TypeScript
  • Next.js
  • Fastify
  • PostgreSQL (RLS)
  • BullMQ (redis)
  • Dokploy

The problem

Vidasend needed to move money between Nigeria and Brazil, but no single payment provider covers the two corridor reliably. I was building it front-to-back alone (UI, API, database, and the infra it ran on), and the system had to add new providers without rewriting transfer logic every time, while holding customer financial data to a standard that survives a series B audit.

Constraints

  • All the structurally different provider APIs with different auth, webhook, and settlement models had to look identical to the rest of the app.
  • Financial data needed row-level isolation between tenants at the database layer... not just in application code, since a single missed check anywhere in the stack could leak one customer's transfers to another.
  • I was the only engineer on it, across every layer, which meant no one else was going to catch a bad assumption before it shipped.

Architecture decisions

  • 01Designed a factory-pattern provider abstraction: each provider implements a common transfer/quote/webhook interface, and the factory resolves which one to use per corridor at runtime. Adding a provider means writing an adapter, meaning we never have to touch business logic and risk breaking anything when adding a new provider or integrating our own payment infra when it's built (hybrid).
  • 02Pushed authorization into Postgres itself with row-level security policies and SECURITY DEFINER functions, so access control holds even if I miss a check somewhere in the application layer.
  • 03Treated every provider webhook as untrusted input by default: verified, idempotency-checked, and reconciled against internal state before a transfer status changes.
  • 04Found an atomicity gap during an audit pass: settling an order and cancelling one were separate, un-transacted steps, so a failure partway through settlement plus a concurrent cancel could permanently trap a customer's funds. Fixed it by collapsing each state transition into a single SECURITY DEFINER function that owns both the ledger and the order status change atomically, then verified the exact failure scenario against real data before closing it.
  • 05Used AI agents to scaffold the repetitive parts of each provider adapter, which freed up the time I actually needed to think about the security-sensitive parts.

What I'd do differently

Knowing what I know now, I'd introduce the provider-abstraction contract on day one instead of after the second provider forced a refactor. The interface should be designed for N providers even when you're shipping with one. I'd also push every multi-step financial state transition into a single atomic database function from the start, rather than letting a fix for that arrive after an audit caught it.