← Writing

Designing Aurion's payment flow on Solana

Accepting crypto payments on Solana is straightforward in the tutorial. The parts nobody writes about are where Aurion actually lives.

The shape of the problem

A merchant wants to accept payments. Their customers hold a mess of different tokens — USDC, USDT, bonk, whatever. The merchant wants to receive a single stablecoin. They don’t want to run a wallet, hold volatility, or build swap plumbing.

So the payment service sits in the middle: accept whatever the customer pays in, settle to the merchant in the target asset.

That one sentence hides three hard problems: swap routing, price staleness, and RPC reliability.

Swap routing through Jupiter

Jupiter is the obvious choice for routing. It has the liquidity picture on Solana better than anyone else does.

What it doesn’t give you for free is a stable quote. A quote at T+0 is not the quote at T+5 seconds. If the customer takes ten seconds to approve the transaction, the slippage window has moved, and you need to decide whether to accept the new rate, fail the payment, or refresh.

Aurion does this explicitly:

Silent slippage is the thing that burns merchants.

RPC is the real SPOF

If you’re building on Solana and you haven’t thought about RPC provider failover, you’re one outage away from finding out.

Aurion treats RPC as an abstraction with at least two providers behind it. Any single call can fall through to the backup. Sustained failures on the primary flip the service to the backup for a cooldown period before re-testing.

The shape of the code:

async function withRpcFailover<T>(fn: (conn: Connection) => Promise<T>): Promise<T> {
  for (const conn of activeConnections()) {
    try {
      return await fn(conn);
    } catch (err) {
      if (!isTransient(err)) throw err;
      markDegraded(conn);
    }
  }
  throw new Error('all RPC providers exhausted');
}

Nothing fancy. But having this in place means a bad 30-minute stretch from one provider doesn’t take the whole service down.

Pricing and staleness

Every payment touches a price. Live prices from one source are risky — outliers and brief glitches show up. We use a short-lived cache with a staleness check: prices older than N seconds are refreshed before use, and a price that moves more than a sanity threshold in a single refresh is treated as suspect and re-fetched.

This is boring code, and that’s the point. The interesting code is the code that decides when not to trust itself.

Idempotency and the ledger

Payments need idempotency. Customer retries, client timeouts, and weird network blips all happen, and a payment service that double-charges on retry is one that loses its merchant relationship.

Aurion’s flow assigns an idempotency key to each attempt, stores the final state in a settlement ledger before acknowledging to the client, and treats retries with the same key as lookups rather than new operations.

The ledger survives process restarts. It has to — “we crashed and lost the fact that you paid” is not a recoverable user experience.

What I’d change

If I rebuilt this from scratch today, I’d lean harder on event sourcing for the settlement flow. The current ledger is a table of final states; an event log would make auditing and replay much nicer, at the cost of some up-front complexity.

But I wouldn’t change the quote-staleness logic, the RPC failover, or the idempotency model. Those are the parts holding the whole thing up.

The meta-lesson

The happy path on Solana payments is two pages of tutorial code. Ninety percent of the work — and all of the production reliability — lives in the edges: staleness, failover, idempotency, and the discipline to fail loudly rather than silently.

← All writing Book a call →
Book a call → WhatsApp