Payments

Testing

Build and test your whole integration in test mode. Test payments use Solana devnet and a test MUNY mint, and never mix with live records.

Test mode and live mode#

Test modeLive mode
Networkdevnet (or the simulated chain locally)mainnet-beta
API keysmk_test_…mk_live_…
TokenTest MUNY mintMUNY mint
Recordslivemode: falselivemode: true

Each environment runs against one network, and every payment, link and refund is stamped with it. Queries are always partitioned by network, so test data can't show up in live reports. Keys only work in their own mode. Checkout and the dashboard show a clear TEST MODE banner.

Simulate a customer payment#

Locally (SOLANA_DRIVER=mock) there are no real wallets. Use the simulate endpoint — or the "Simulate test payment" button on hosted checkout — to have a test customer send a real-shaped transfer through the simulated ledger. Verification, webhooks, notifications and the dashboard update exactly as they would in production.

ts
const payment = await muny.paymentIntents.create({ amount: "500", reference: "TEST-1" });

// A test customer pays — a simulated on-chain transfer carrying the payment's reference
await muny.publicCheckout.simulate(payment.id);

// Verification runs exactly as in production
await sleep(3000);
(await muny.paymentIntents.retrieve(payment.id)).status; // "confirmed"
curl
curl -X POST http://localhost:4040/v1/checkout/$PAYMENT_ID/simulate \
  -H "Content-Type: application/json" -d '{}'
POST /v1/checkout/:id/simulate is refused on mainnet and with the RPC driver.

Exercise failure paths

ts
await muny.publicCheckout.simulate(id, { amount: "499" });          // underpaid → failed
await muny.publicCheckout.simulate(id, { amount: "501" });          // overpaid  → failed
await muny.publicCheckout.simulate(id, { wrong_recipient: true });  // stays open, failure_code wrong_recipient
await muny.publicCheckout.simulate(id, { wrong_mint: true });       // stays open, failure_code wrong_mint
await muny.publicCheckout.simulate(id, { omit_reference: true });   // never attributed to this payment

End-to-end smoke test#

The repository ships a script that runs the full loop against a running API: create, check out, pay, verify, and the treasury balance increases.

bash
pnpm --filter @muny/api smoke:payments

created   cb6e9980-… requires_payment 500 MUNY
checkout  http://localhost:3030/pay/cb6e9980-…
customer paid (simulated) sig 5G6Y91XWA6aw6erE…
  status: submitted
  status: confirmed
verified  confirmed payer 6qZ4fgzV usd snapshot 60
treasury  2450320 → 2450820

Real wallets on devnet#

To test with Phantom, Solflare or Backpack, switch the API to the RPC driver on devnet, fund a devnet wallet with SOL for fees and some test MUNY, and pay from hosted checkout. See the Solana setup guide in the repository (docs/solana.md).

bash
# .env — real devnet wallets
SOLANA_NETWORK=devnet
SOLANA_DRIVER=rpc
SOLANA_RPC_URL=https://api.devnet.solana.com
MUNY_MINT_ADDRESS_DEVNET=<your test MUNY mint>
FEE_PAYER_PRIVATE_KEY=<devnet fee payer, base58>
  • Switch your wallet to devnet before paying, or verification fails with wrong_network or not_found.
  • Use a webhook tunnel (e.g. cloudflared) to receive events locally.
  • Read Verify payments to understand each failure code you might see.