跳到正文
DEV Community· Elay osetrov·· 4 小時前AI 評分50

用單一 Cloudflare Worker 收取 USDC 出售數碼檔案(無需 Stripe 或資料庫)

Selling digital files for USDC with one Cloudflare Worker (no Stripe, no database)

AI 導讀

這套方案以單一 Cloudflare Worker 在 Base 收取 USDC,出售數碼檔案,無需支付處理商、資料庫或伺服器端錢包連線。訂單以精確金額標記,並用 HMAC-SHA256 簽署;Worker 核對交易後提供限時下載連結,也支援 x402 付款。免費方案只適合總容量約 3 MB 以下的小型檔案,錯額退款仍須手動處理。

正文

I wanted to sell two small PDFs for crypto without a payment processor, a database, or a wallet connection on the server. The result is one Cloudflare Worker (free plan) that only receives USDC on Base to a public address. Here is how it works, including the parts that are weak.

Live example: Setrov Store.

The core problem: which payment belongs to which order?

A plain transfer to an address carries no order ID. If two people buy the same $9 file, two identical 9.000000 USDC transfers arrive and you can't tell them apart.

Fix: exact-amount tagging. USDC has 6 decimals, so each order gets a random tag in the low digits:

base price  9.000000
order tag   0.004217   (random 4-digit tag, 1000..9999 millionths)
pay exactly 9.004217 USDC

The buyer pays that exact amount. A transfer of any other amount simply doesn't match that order.

Stateless orders with HMAC

There's no database. The order is a token: product | exactAmount | createdAt, signed with HMAC-SHA256 using a Worker secret. The order page URL contains the token, so the Worker can rebuild the order from the URL and check the signature. Rotating the secret invalidates every open order.

const sig = await crypto.subtle.sign('HMAC', key, enc.encode(payload));
const token = b64url(payload) + '.' + b64url(sig);

Verifying the transaction hash

The buyer pastes the tx hash. The Worker calls a public Base RPC (eth_getTransactionReceipt), then checks:

  1. receipt status is success, and it has at least 2 confirmations
  2. a USDC Transfer log (USDC contract address, topic0 = Transfer) to the store address
  3. log value == the order's exact amount
  4. the block timestamp is after the order was created and less than 24h old

If all of that passes, it returns a signed download link that expires in 2 hours. No private key is involved anywhere: the server only reads public chain data.

Machine buyers: x402

The same Worker exposes /x402/buy/<slug>. An unpaid request gets HTTP 402 with an accepts[] block (scheme exact, network base, amount, payTo, USDC asset). An x402 client signs an EIP-3009 authorization and retries with an X-PAYMENT header. The Worker forwards it to a facilitator (I use PayAI) for /verify, then /settle, and returns the download link. The facilitator submits the transaction, so the server still holds no keys.

curl -i https://setrov-store.elayosetrov78.workers.dev/x402/buy/ai-agent-prompt-pack
# HTTP/2 402  { "x402Version":1, "accepts":[{ "maxAmountRequired":"9000000", ... }] }

Honest limits

  • Wrong amount = manual handling. If someone pays 9.00 instead of 9.004217, the check rejects it and you refund by hand.
  • Replay guard is best-effort. Without a database, reuse of a tx across orders is blocked with the Cache API, which is per-datacenter. The random tag makes accidental matches unlikely (about 1 in 9000), but it's not a ledger.
  • Links can be shared during their 2-hour window, like any download link.
  • Public RPC rate limits can delay verification ("try again in a minute").
  • Size: files are embedded in the Worker bundle, so this only works for small files (under ~3 MB total on the free plan). Bigger files need R2 or another host.
  • Taxes and accounting are still your job. No KYC on the server doesn't remove your own obligations.

When this makes sense

Small digital goods, a crypto-native audience, and no wish to run a database. For anything bigger (subscriptions, refunds at scale, files larger than a few MB) use a real payment stack.

If you want to see it working, the store sells an AI Agent Prompt Pack for 9 USDC; it's also on Payhip for card payments.

來源:DEV Community · dev.to