Integration
Mobile & API integration guide
This page is the handoff pack for a mobile app (or any external client) that needs to talk to the live Payrano Solutions backend and match the web product one-to-one. It covers the architecture, every public endpoint with sample payloads, the data model, the settlement model, and the exact design tokens used on this site.
v1.4Last updated 10 August 2026· Written for external engineering teams
1. Backend & base URLs
Payrano runs on Supabase (Postgres, Auth, Storage) behind a TanStack Start server layer deployed on the edge. There is no Firebase, no separate Node service, and no self-managed database.
There are two API surfaces:
- Payrano REST API —
https://payranosolutions.com/api/public/v1/*. Plain JSON over HTTPS, CORS-enabled (Access-Control-Allow-Origin: *) so a mobile client can call it directly. This is the recommended surface: it never exposes tables and its shape is stable. - Supabase Data API — the project URL plus its publishable key, used with
@supabase/supabase-jsfor sign-in, session refresh and realtime. Every table is protected by row-level security scoped toauth.uid(), so a signed-in merchant can only ever read their own rows.
Stable environments: https://payranosolutions.com (production) and https://payrano-usdt-gateway.lovable.app (same build, useful as a fallback host). Use production for anything real.
Two values the mobile app must be configured with:
SUPABASE_URL=<project url> # ask the Payrano owner SUPABASE_ANON_KEY=<publishable key> # begins with sb_publishable_ PAYRANO_API_BASE=https://payranosolutions.com/api/public/v1
Both Supabase values are public by design (they are shipped in the web bundle), but they are not printed on this page — the Payrano owner sends them from the project's backend settings. The service-role key is never shared and must never appear in a mobile binary.
2. Authentication
Users sign in today with email + password via Supabase Auth (with password reset by email). Google/social sign-in is not enabled yet. Payrano also acts as an OAuth identity provider for the Payrano Wallet app through a consent flow, so a wallet-style client can reuse the same accounts later.
Mobile should use the Supabase client and then reuse the access token:
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: { persistSession: true, autoRefreshToken: true },
});
const { data, error } = await supabase.auth.signInWithPassword({
email, password,
});
const token = data.session.access_token; // send as Bearer to /wallet/* endpointsTwo distinct credential types — do not mix them up:
- User session (Bearer token) — a signed-in person. Used by the
/wallet/*endpoints and every Supabase table read. - Merchant API key pair — machine-to-machine, issued in the dashboard under API Keys. Sent as
x-payrano-key-idandx-payrano-key-secretheaders. Only active merchants with approved KYB can authenticate. Never embed a key pair in a consumer mobile app; use the user session for merchant-app screens.
3. Merchant REST API
GET/api/public/v1/keys/verify
Auth: API key pair
Confirms a key pair before saving it. Also accepts POST.
{
"ok": true,
"authenticated": true,
"merchant": {
"id": "9f1c…",
"business_name": "Dice Cafe",
"logo_url": null,
"settlement_address_configured": true
}
}POST/api/public/v1/payments
Auth: API key pair
Creates a payment request (invoice) and returns a hosted checkout URL.
// request
{
"amount": 149.5,
"reference": "ORDER-1042", // optional, auto-generated when omitted
"description": "Table 4 — dinner", // optional
"expires_in_minutes": 30 // optional, 5–1440
}
// 201 response
{
"id": "6b0e2c7a-…",
"amount": 149.5,
"net_amount": 149.5,
"fee_amount": 0,
"fee_wallet": null,
"description": "Table 4 — dinner",
"reference": "ORDER-1042",
"wallet_address": "TXk…", // merchant's own TRC20 address
"status": "waiting",
"expires_at": "2026-08-17T07:05:00.000Z",
"created_at": "2026-08-17T06:35:00.000Z",
"checkout_url": "https://payranosolutions.com/pay/6b0e2c7a-…"
}Errors: 401 invalid_api_key, 400 invalid_input, 409 settlement_address_missing.
GET/api/public/v1/payments?id=… | ?reference=…
Auth: API key pair
Looks up one payment owned by the authenticated merchant. Expired-but-still-waiting payments are flipped to expired on read. Calling it with no lookup key returns a 200 connection probe instead of a 404.
{
"id": "6b0e2c7a-…",
"amount": 149.5,
"reference": "ORDER-1042",
"wallet_address": "TXk…",
"status": "completed", // waiting | confirming | completed | expired
"expires_at": "…",
"confirmed_at": "…",
"created_at": "…"
}For dashboard lists (payments history, KPIs, transactions), read the tables through the Supabase client with the user session — RLS already scopes rows to the signed-in merchant, and realtime gives you live settlement updates without polling:
const { data } = await supabase
.from("payments")
.select("id, amount, reference, status, created_at, confirmed_at")
.order("created_at", { ascending: false })
.limit(50);
supabase.channel("payments")
.on("postgres_changes",
{ event: "UPDATE", schema: "public", table: "payments" },
(p) => refresh(p.new))
.subscribe();4. Wallet & payer API
These endpoints power the wallet/payer side. Bearer endpoints take the Supabase access token as Authorization: Bearer <token>; the rest are public.
GET/api/public/v1/wallet/me
Auth: Bearer token
{
"user_id": "…",
"email": "merchant@example.com",
"handle": { "handle": "dicecafe", "chain": "tron", "address": "TXk…", "display_name": "Dice Cafe", "active": true },
"merchant": { "id": "…", "business_name": "Dice Cafe", "kyc_status": "approved", "status": "active" }
}GET / POST/api/public/v1/wallet/links
Auth: Bearer token
Lists or registers addresses this account owns. POST body: { chain, address, label?, message?, signature? } — supplying a signed Payrano ownership challenge marks the link verified (EIP-191 for EVM chains, TRON signature for TRON).
{ "links": [ { "id": "…", "chain": "tron", "address": "TXk…", "provider": "payrano", "label": "Payrano Wallet", "verified_at": "…", "created_at": "…" } ] }POST/api/public/v1/wallet/handles
Auth: Bearer token
Claims or updates the caller's $handle. Body: { handle, chain, address, display_name? }. Handles match ^[a-z0-9][a-z0-9._-]{2,29}$. Errors: 409 handle_taken, 400 invalid_address.
GET/api/public/v1/resolve/{handle}
Auth: Public
Handle lookup for scan/pay flows — routing data only, never contact info.
{ "handle": "dicecafe", "display_name": "Dice Cafe", "chain": "tron", "address": "TXk…" }GET/api/public/v1/wallet/invoices/{reference}
Auth: Public
The invoice behind a Payrano pay link, so a wallet can render it natively.
{
"id": "…", "merchant_name": "Dice Cafe", "merchant_logo_url": null,
"amount": 149.5, "net_amount": 149.5, "fee_amount": 0,
"reference": "ORDER-1042", "wallet_address": "TXk…",
"chain": "tron", "asset": "USDT",
"status": "waiting", "expires_at": "…", "confirmed_at": null,
"checkout_url": "https://payranosolutions.com/pay/…"
}GET/api/public/v1/reputation/{address}
Auth: Public
Public payment reputation for a payer address (Labs feature).
{ "address": "TXk…", "count": 14, "volume": 4820.75, "merchants": 3, "streak": 2, "firstSeen": "…", "recent": [ { "id": "…", "amount": 149.5, "reference": "ORDER-1042", "created_at": "…" } ] }Pay links follow a stable query contract the wallet builds against: /pay?chain=&address=&amount=&asset=&contract=&decimals=&ref=&callback=, plus the native scheme payrano://pay?…. Reuse it for scan-to-pay and deep-link handling in the app.
5. Database model
Postgres on Supabase, all in the public schema, all with RLS. The tables a mobile app will touch:
merchants— one row per account, keyed to the auth user id:business_name,email,logo_url,deposit_address(TRC20 settlement address),webhook_url,status,kyc_status,gasless_enabled.payments— invoices:amount,net_amount,fee_amount,reference,description,wallet_address,chain,asset,status(waiting | confirming | completed | expired),expires_at,confirmed_at, plus optionalpay_with_*swap fields.transactions— on-chain legs matched to a payment:tx_hash,wallet,amount,status. Read-only for merchants.api_keys—label,key_id,revoked,last_used_at. The secret column is not readable by clients.kyb_applications/kyb_documents— onboarding: legal name, licence details, director contact,status(draft → submitted → under_review → approved | rejected) and uploaded files in a private storage bucket.wallet_links,payment_handles,merchant_assets— linked addresses (with signature verification),$handlerouting, and per-chain settlement addresses.plans,merchant_billing,platform_fees— the subscription model (flat monthly tiers, 0% per-transaction fee).- Labs tables:
subscription_plans,subscription_mandates,payment_receipts,gas_sponsorships. - Platform:
user_roles(admin | merchant, checked through a security-definer function),audit_logs,waitlist_leads,platform_settings(admin-only).
Generated TypeScript types for every table live in the web repo at src/integrations/supabase/types.ts — copy that file into the mobile project for full type safety instead of hand-writing models.
6. TRON / USDT settlement
Payrano is non-custodial. There is no third-party processor (no NOWPayments, no CoinPayments) and no Payrano-controlled hot wallet. The customer sends USDT directly to the merchant's own TRC20 address; Payrano records the request and reconciles it against the reference.
Chain metadata (TRON, Ethereum, BSC, Polygon, Bitcoin), address validation, explorer URLs and wallet deep links are implemented client-side in the web app and can be mirrored in the mobile app. TRON is the only settlement chain in production; the others exist for pay-with-anything (Labs).
Important gap to plan around:
Confirmation today is reference-driven rather than driven by a persistent node watcher. If the mobile app promises live "payment received" push notifications, a chain indexer has to exist first — the recommended shape is a TronGrid (or self-hosted full-node) poller that watches each merchant's deposit_address for USDT TRC20 transfers, writes a transactions row, flips the matching payment to completed, and then fans out push. No TronGrid API key is in use yet, so the mobile team should not assume one exists.
7. Design system (for pixel parity)
Payrano is dark-first with a light mode. Colours are OKLCH tokens; typography is Inter throughout. Use these values verbatim so the app and the site look like one product.
Dark (default) background oklch(0.06 0 0) foreground near-white primary oklch(0.585 0.235 288) /* purple */ primary-glow oklch(0.66 0.2 268) primary-foreground oklch(0.99 0 0) Light background oklch(0.99 0.002 288) primary oklch(0.545 0.235 288) primary-glow oklch(0.62 0.2 268) Type Inter (400 / 500 / 600), tight tracking on headings Radius cards 1rem–1.5rem, buttons fully rounded (pill) Surface translucent "glass" panels: 1px border at ~60% opacity + soft shadow Accents purple at 12% opacity for icon chips and badges
The full token set (surfaces, sidebar, borders, muted, destructive, both themes) lives in src/styles.css in the web repo. The logo is a transparent "P" mark — request the asset rather than screenshotting it, so corners stay clean on any background.
8. What to build first
Recommended first three surfaces, in order:
- Merchant dashboard — balances, today's volume, recent payments. Pure Supabase reads with the user session; nothing new needed on the backend.
- Create + share a payment link with QR — the highest-value mobile action (take a payment at the counter). One
POST /paymentscall, then rendercheckout_urlas a QR and a share sheet. - Push on incoming payment — worth doing third because it depends on the chain watcher described in section 6. Until that exists, use Supabase realtime on the
paymentstable so the app updates instantly the moment a payment is marked completed.
Scan-to-pay, $handle lookup and multi-chain wallet balances belong to the Payrano Wallet product rather than the merchant app — the endpoints in section 4 already support them when you get there.
9. OpenAPI / Postman
A machine-readable spec for every endpoint above is served at https://payranosolutions.com/api/public/v1/openapi.json. Import it straight into Postman, Insomnia or a codegen tool instead of copying payloads by hand.
Rest of the product documentation, including the payment lifecycle and security model.
Back to docs