Merchants
portal, API and webhooks.
What your merchants get: a portal to take payments without code, an API to create invoices from their own shop, and signed webhooks that tell their backend when an order is paid.
01Which portal
| App | Status in 1.0.0 |
|---|---|
apps/portal (merchant portal) | The full portal described on this page. Runs on the public demo at /portal/ (built with VITE_BASE=/portal/). Not verified The shipped Caddyfile and pnpm run build:web do not build or route it yet; to serve it in production you add it yourself (build @mikodes-checkout/portal and serve apps/portal/dist under /portal/ on the admin domain). |
apps/admin (interim merchant portal) | What the shipped Caddyfile serves on the admin domain. Merchants sign in with their API key (an operator key is refused and never stored). |
02The merchant portal
| Screen | What the merchant does |
|---|---|
| Home | Received today, fees, fee credit, live payments and quick actions. |
| Payments, Refunds | Every payment with on-chain evidence and timeline; refunds signed in the merchant's own wallet (Solana wallet adapter or an EIP-1193 wallet on Base), or sent to the customer as a claim link. |
| Payment links | Reusable links with QR code, an embed.js website button, stats and payments. |
| Invoices | Customer invoices with a live paper preview: send, void, PDF. |
| Subscriptions | Recurring billing: pause, resume, cancel, history. |
| Point of sale | Keypad, QR and live status for in-person sales; a cashier role lands here. |
| Fee credit | Balance, fee and tiers, top-ups and automatic top-up. |
| Webhooks, Developers | Endpoint, test event, deliveries with payload and retry; API key and webhook secret (shown once), copy-paste code in cURL, Node.js and PHP. |
| Team, Security, Notifications, Referral | Invites and roles; two-factor (TOTP), recovery codes, sessions, password; Telegram and e-mail notifications; the merchant's referral link. |
The portal is in English and Slovak, light and dark.



03Create an invoice from a shop
The merchant's backend calls the API with its key (sk_live_…, kept in its own environment as MERCHANT_API_KEY). Amounts are decimal strings, never floats.
curl -s -X POST https://pay.example.com/v1/invoices \
-H "Authorization: Bearer $MERCHANT_API_KEY" -H 'content-type: application/json' \
-d '{"amount":"25.00","asset":"USDC","chain":"solana-mainnet","orderId":"1042","redirectUrl":"https://shop.example/thanks"}'
The response is the invoice with its id and checkoutUrl (<PUBLIC_CHECKOUT_URL>/pay/<id>). Send the customer there. Optional fields: orderId, description, redirectUrl, expiresInSec. Other merchant routes: GET /v1/invoices/:id, GET /v1/invoices, GET /v1/webhook-deliveries. Full reference: docs/CONTRACT.md and docs/CONTRACT-ADMIN.md in the package.

04Invoice statuses
| Status | Meaning |
|---|---|
pending | Waiting for payment. |
confirming | A transaction was seen; waiting for finality. A seen transaction keeps the invoice confirming past its expiry. |
paid | Settled. Wallet pay needs both legs (merchant amount and fee). |
underpaid, overpaid | The amount differs. Decided only on finality; go to review, never paid out automatically. |
expired | Nothing was seen before the expiry. Expired invoices are watched 24 hours more, so a late payment is still recorded. |
flagged | The payer matched the sanctions list. Terminal; no fee credit is debited (what to do). |
05Signed webhooks
For each status change the server sends POST to the merchant's webhook URL with events invoice.confirming, invoice.paid, invoice.underpaid, invoice.overpaid, invoice.expired, invoice.flagged (and webhook.test from the test button). Header:
Mikodes-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>
- Verify the signature over the raw body with the merchant's webhook secret, and compare in constant time.
- Deduplicate on
event.id(also in the headerMikodes-Event-Id). Replays are safe. - Retries at 10 s, 30 s, 1 min, 5 min, 15 min, 1 h and 3 h; after 8 attempts the delivery is a dead letter (both configurable in the console). Every attempt is stored.
- Deliveries to private, loopback, link-local and metadata addresses are refused (SSRF protection).
06What the customer sees
The checkout shows the exact amount, a countdown, a QR code, wallet pay (Phantom, Solflare and other Solana wallets; MetaMask, Rabby, Coinbase Wallet on Base), exchange pay with per-exchange guidance for Binance, OKX and Bybit, live confirmation, and a public status page with on-chain evidence. English and Slovak, light and dark. Underpaid and overpaid invoices go to review.
