Skip to content

Vecnet Admin — operator platform

The back-office where Vecnet personnel configure merchants, manage fees and rolling reserves, audit settlements, and watch treasury. Built as a Next.js 16 App Router app at /Users/yuyo/vecnet-admin. Auth is Clerk (see auth).

Home                          /
Merchants                     /merchants
Finances → /finances          (Treasury landing — gated)
  Accounts                    /finances/accounts
  Journals                    /finances/journals
  Snapshots                   /finances/snapshots
  Settlements                 /finances/settlements
Admin users                   /admin-users (superadmin only)

Every group label is itself a clickable link. Children render whether or not the user is currently on the section.

Merchant detail tabs

Each merchant has its own detail surface at /merchants/[id]. Tabs (some gated):

TabURLRole gateWhat it does
Overview/merchants/[id]noneIdentity, MIDs (onlineMid / terminalMid), status with state-machine switcher (activate / pause / terminate)
Features/featuresnonePayment-method toggles: Online · Terminal · Card · SPEI · OXXO · MercadoPago · Cash vouchers (Paysafe) · Payment links
Checkout/checkout-confignoneLogo URL + 4 brand colors (rest of the config was dropped during the Tonder-scope trim)
Fees/feesfinops, superadminPer-merchant MerchantFee rows: OUT (charged to merchant) + IN (charged to Vecnet). Rate basis points + fixed cents + IVA. Effective windows. The one and only fee editor.
Rolling reserve/rolling-reservefinops, superadminMerchantRollingReserveConfig per channel + liability summary + force-release for superadmin
Settlement/settlementnoneCycle type (daily / weekly / biweekly / monthly / t_plus_n) + cutoff hour + minimum payout + "Run now" action
Finance config/finance-configfinops, superadminThe 4 MerchantFinanceConfig module toggles — Fees · Ledger · Settlements · Daily snapshots. Mirrors Tonder's ConfigTable.
API keys/api-keysnoneLive + test key generation, bcrypt-hashed at rest

Permissions

lib/permissions.ts is the source of truth. Three role values:

RoleWhat they see
superadminEverything. Only role that can manage admin users + force-release reserves.
finopsFees, rolling reserve, settlements, treasury, finance-config. Cannot edit checkout or create merchants.
integrationsMerchants list + create, features, checkout config, settlement view. Hidden from /finances entirely.

Helpers: canViewMargin, canViewInFees, canViewOutFees, canEditFees, canViewRollingReserve, canEditRollingReserve, canForceReleaseReserve, canEditCheckoutConfig, canCreateMerchant, canManageAdminUsers, canViewTreasury. Each is one line over AdminRole.

Tech stack

  • Next.js 16.2.6 (App Router, Turbopack)
  • Prisma 7 client-extensions over PostgreSQL 14 (local via Homebrew)
  • Tailwind v4 + shadcn/ui (Base UI variant)
  • Clerk for auth (replaces NextAuth v5)
  • BullMQ + Redis-optional for background workers
  • @react-pdf/renderer for settlement PDFs
  • exceljs for the Spanish XLSX "Resumen de Liquidación"
  • dayjs + tz plugin for Mexico City cycle math (UTC-6, no DST)
  • Vitest for unit tests (54 passing)

Background workers

Three workers under /workers, each Redis-optional with a --run-once flag for manual triggers and tests:

WorkerCronWhat it does
settlement-scheduler.tshourlyFinds merchants whose cycle just crossed cutoff, composes one Settlement per channel that had activity
rolling-release.tsdailyReleases reserve held entries whose releasedAt has passed
daily-snapshots.ts23:30 America/Mexico_CityUpserts BalanceSnapshot rows for every active account

Without REDIS_URL set the workers log a no-op message and exit, so a fresh clone works without Redis until you need scheduling.

Conventions

  • All money in BigInt cents. Never floats. Rates in basis points (Int).
  • Double-entry: every event opens a Journal, writes 2+ LedgerEntry rows whose debits == credits.
  • LedgerEntry is append-only — enforced by lib/db-extensions.ts which only permits settlementId / journalId / accountId updates on existing rows.
  • Account resolution is lazy via lib/accounts/resolve.ts:resolveAccountId — every event writer goes through it.

Vecnet — Build Spec v0.2 · Obsidian Terminal