Checkout Without Compromise
A production-grade Bitcoin checkout UI for BTCPay Server. Mobile-first, real-time confirmation, framework agnostic.
Built for merchants who don't have payment processor options — and don't want one.
Screen recording of the live flow (place files in docs/assets/ — see PLACE_FILES_HERE.txt).
Every merchant running BTCPay is staring at the same generic checkout. SovSats is the application layer that was missing.
Customer sees confirmation the moment payment hits the mempool. No waiting. No "keep this page open" anxiety. The UX your customers deserve.
Your backend waits for real on-chain confirmation before fulfilling. Protects you from fraud. Both phases handled correctly, out of the box.
Pure TypeScript core with no framework deps. Adapters for Next.js App Router and Express ship with the package. Your setup, your rules.
Drop in BtcNexusCheckout, wire your BTCPay URL and API key, done. The whole checkout — pay state, polling, confirmation — is one file.
No merchant dashboard to sign up for. No approval process. You own your BTCPay instance, you own your checkout.
BTCPAY_SERVER_URL, BTCPAY_STORE_ID, BTCPAY_API_KEY, BTCPAY_WEBHOOK_SECRET
Two files, two lines each. Create and poll endpoints are ready.
Pass invoice data as props. Handle onSettled to fulfill your order. Done.
export { POST } from "sovsats/adapters/next";
import { makeWebhookHandler } from "sovsats/adapters/next"; export const POST = makeWebhookHandler({ onSettled: async (invoiceId) => { // fulfill order here — fully confirmed await fulfillOrder(invoiceId); }, });
import { BtcNexusCheckout } from "sovsats/react"; import { buildBitcoinUri } from "sovsats"; <BtcNexusCheckout invoiceId={invoice.invoiceId} pollEndpoint="/api/payments/btcpay" storeName="Your Store" usdTotal="$49.99" btcAddress={btc.address} btcAmount={btc.due} bitcoinUri={buildBitcoinUri(btc.address, btc.due, invoice.invoiceId)} orderId="ORD-12345" callbacks={{ onSettled: (id) => router.push(`/success`), }} />
The two-phase confirmation split is the part most implementations get wrong. SovSats handles it correctly by default.
| BTCPay Status | SovSats Behavior | Safe For |
|---|---|---|
| NEW | Polling · waiting for mempool | — |
| PROCESSING | Fires onProcessing · UX redirect | UX redirect only |
| SETTLED | Fires onSettled · stops polling | Order fulfillment ✓ |
| EXPIRED | Shows expired state · stops polling | — |
Built for the merchants traditional processors won't touch. Your customers still need to pay. Now they can — with a checkout that doesn't look like an afterthought.
Four env vars. One component. No approval process.
You own the checkout. You own the stack.