Stack guide

Next.js and Stripe Connect: refunds across server actions and routes

Next.js App Router gives you three places refunds might live — server actions, route handlers, legacy API routes — and only opinions about which is right. Money paths change the calculus: server actions are public endpoints needing re-authentication from scratch, Edge runtime cannot touch secret-dependent crypto, and one misnamed environment variable drops both Connect flags without any error ever appearing.

Route handlers vs server actions for money

Server actions re-authenticate from scratch on every invocation — treat arguments as hostile, verify session and ownership before any Stripe call. Route handlers suit webhook receivers (signature verification lives naturally there). Mixing concerns across both is fine; trusting either implicitly is not.

The Edge/Node split

Signature verification runs happily on Edge; anything requiring node:crypto-backed secret material does not. Production architecture mirrors this: Edge receiver verifies and enqueues, Node workers do detection and recovery work. Local dev hides the boundary until deploy surprises you.

The silent env-var trap

Flags sourced from configuration (process.env.REVERSE_TRANSFERS === "true") fail closed when staging variables misspell names — refunds succeed, transfers strand, nothing logs. Hard-code true flags; reserve configuration for genuinely variable policy like floors.

// Fragile: config-gated correctness
const flags = process.env.CONNECT_REVERSALS ? {...} : {};

// Robust: correctness isn't configurable
await stripe.refunds.create({ charge, reverse_transfer: true, refund_application_fee: true });

RSC caching showing stale amounts

Cached components rendering amount_refunded predate reversals landing elsewhere. Route from cached views; decide from fresh fetches at action time — the payload-staleness lesson wearing frontend clothing.

Common questions

Can refunds run entirely on Edge runtime?

Signature verification yes; anything touching secret-derived crypto needs Node runtime. Plan the split deliberately rather than discovering it in production logs.

Does this require a FeeGuard integration?

No — the page stands alone as stack guidance. FeeGuard observes your event stream externally rather than embedding in it.