在 Next.js + Supabase 应用中集成自托管加密支付结账,支持 ETH、BTC、SOL、USDC、USDT 及 9 条链的25+ 币种,无需支付处理机构。
Adds a complete, production-ready crypto payment system to any Next.js 14+ App Router app backed by Supabase. No Stripe, no CryptAPI, no payment processor — you keep 100% of every payment.
Supported chains & coins: ETH, Base, Polygon, Arbitrum, Optimism, BNB Chain, Avalanche, Solana, Bitcoin — including USDC/USDT/DAI stablecoins on each chain.
Architecture:
Security model: Mnemonic never touches client or source code. User cannot influence destination address or amount. Payment confirmation is blockchain-verified, not webhook-forgeable.
All template files are in resources/. Copy them into your project at these paths:
Resource fileDestination in your projectcrypto-wallets.ts``src/lib/crypto-wallets.ts``crypto-checkout-route.ts``src/app/api/billing/crypto-checkout/route.ts``crypto-checkout-status-route.ts``src/app/api/billing/crypto-checkout/[id]/status/route.ts``check-crypto-payments-route.ts``src/app/api/cron/check-crypto-payments/route.ts``crypto-payment-modal.tsx``src/components/dashboard/crypto-payment-modal.tsx``005_crypto_payments.sql``supabase/migrations/005_crypto_payments.sql``006_crypto_direct.sql``supabase/migrations/006_crypto_direct.sql
bash
npm install ethers @scure/bip32 @scure/bip39 @scure/base @noble/hashes @solana/web3.js qrcode
npm install -D @types/qrcode
In Supabase SQL editor (or via supabase db push), run both migration files in order:
005_crypto_payments.sql006_crypto_direct.sqlenv
# Generate mnemonic: https://iancoleman.io/bip39/ (12 or 24 words — NEVER commit this)
CRYPTO_MASTER_MNEMONIC=word1 word2 word3 ... word12
# Protects the cron endpoint — generate with:
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
CRON_SECRET=your-32-char-hex-string
The cron endpoint POST /api/cron/check-crypto-payments must be called every 60 seconds.
Vercel Cron ( vercel.json):
json
{
"crons": [
{
"path": "/api/cron/check-crypto-payments",
"schedule": "* * * * *"
}
]
}
Add the Authorization header in your cron config or Vercel dashboard:
text
Authorization: Bearer <your CRON_SECRET>
Fly.io machines / self-hosted: Use a scheduled task or external cron (e.g. cron-job.org) hitting the endpoint with Authorization: Bearer <CRON_SECRET>.
The checkout route handles three payment type values — edit to match your app's data model:
TypeWhat it doesRequired body fieldscreditsAdds credits to user profilepackId``planUpgrades user planplan, months (optional for lifetime)addonApplies a one-time resource addonaddonId, botId (optional)
To customise: In crypto-checkout-route.ts, replace the CREDIT_PACKS, PLANS, and validAddons references with your own constants. The amount must always come from server-side constants — never from user input.
In check-crypto-payments-route.ts, update applyPayment() to perform whatever action your app takes when a payment is confirmed (add credits, unlock features, update subscription, etc.).
tsx
import { CryptoPaymentModal } from '@/components/dashboard/crypto-payment-modal'
// Example: pay for a credit pack
<CryptoPaymentModal
target={{
type: 'credits',
packId: '1000',
label: '1,000 Credits',
amountUsd: 10,
}}
onClose={() => setModalOpen(false)}
/>
The modal handles coin selection, QR display, countdown timer, and polls for confirmation automatically.
Each payment gets a unique deposit address derived from your master mnemonic:
m/44'/60'/0'/0/{index} — checksummed Ethereum address, valid on all EVM chainsm/84'/0'/0'/0/{index} — native SegWit P2WPKH ( bc1q...)m/44'/501'/{index}'/0' — SLIP-0010 ed25519 (matches Phantom/Solflare)The derivation_index increments per payment and is stored in the DB. A UNIQUE constraint prevents two active payments from sharing an address.
Implication: One mnemonic controls all your payment wallets. Back it up securely. If you ever need to sweep funds, import the mnemonic into MetaMask (for EVM), Phantom (for SOL), or a BIP84 wallet (for BTC) and derive the child accounts.
ThingWhereSupported coins listSUPPORTED_COINS in crypto-checkout-route.tsQuick-pick coins shown firstQUICK_PICKS in crypto-payment-modal.tsxPayment window (default 1hr)expiresAt in crypto-checkout-route.tsBalance tolerance (default 0.5%)minRequired in check-crypto-payments-route.tsPayment types + fulfilment logicapplyPayment() in check-crypto-payments-route.tsRate limit (default 3 req/5min)checkRateLimit call in crypto-checkout-route.ts
crypto-wallets.ts is server-only — never import it in client components or pagesCRYPTO_MASTER_MNEMONIC must never appear in source code, git history, or logstimingSafeEqual to prevent timing attacks on CRON_SECRETwallet_type + derivation_index)"CRYPTO_MASTER_MNEMONIC is not set" — The env var is missing. Add it to .env.local (development) or your hosting provider's environment settings (production).
Payments not confirming — Check that your cron job is running and the Authorization: Bearer <CRON_SECRET> header matches exactly. Check Vercel/server logs for [check-crypto] lines.
"Failed to allocate payment address" — Extremely rare concurrent collision (3 retries exhausted). Safe to retry.
EVM token not detected — Ensure the contract address for that token+chain exists in ERC20_CONTRACTS in crypto-wallets.ts. Check etherscan.io for the canonical USDC/USDT address on your chain.