Escrow for P2P classifieds Marketplace, powered by Monnify. A buyer's payment is held in escrow until they confirm the item arrived - the seller is only paid out once the buyer is satisfied.
Built for the Monnify Developer Challenge.
Buying from informal listings (Jiji/Marketplace-style) carries real scam risk: payment happens before the buyer can verify the goods. Trustee removes that risk using Monnify's Checkout, Verification, Disbursement, and Webhook APIs to hold and release funds.
- Next.js 16 (App Router) + TypeScript, Tailwind CSS + shadcn/ui
- TanStack Query for client-side data fetching
- NextAuth v5 (Credentials) + Prisma 7 (Postgres, via
@prisma/adapter-pg) - Supabase (Postgres in production, Storage for listing photos)
- Monnify sandbox APIs for Checkout, Verification, and Disbursements
-
Install dependencies
pnpm install
-
Start Postgres locally
docker compose up -d
This runs Postgres 16 on
localhost:5432(user/password/db:trustee). If port5432is already taken by another local Postgres, either stop that one or change the host port indocker-compose.ymlandDATABASE_URLtogether. -
Configure environment variables
cp .env.example .env
Fill in:
NEXTAUTH_SECRET- generate withopenssl rand -base64 32MONNIFY_API_KEY,MONNIFY_SECRET_KEY,MONNIFY_CONTRACT_CODE- from your Monnify sandbox dashboardSUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY- from your Supabase project (used for Storage; Postgres itself can stay local via Docker for dev)
-
Run the database migration
pnpm exec prisma migrate dev -
Seed the admin account
pnpm seed
Creates (or promotes) an admin user so you can reach the admin tabs on
/dashboard. Defaults toadmin@trustee.dev/AdminPassword123!- override withADMIN_EMAIL,ADMIN_PASSWORD,ADMIN_NAMEin.envfirst if you want different credentials. Re-running it against an existing email just promotes that account toADMINwithout touching its password. -
Start the app
pnpm dev
Open http://localhost:3000.
Monnify webhook URLs are registered in the Monnify dashboard, not via API - so local testing needs a public tunnel:
ngrok http 3000
# or: cloudflared tunnel --url http://localhost:3000Then register https://<your-tunnel>.ngrok.io/api/webhooks/monnify as the
Transaction Completion webhook URL in your sandbox dashboard.
Note: Monnify's sandbox does not send the monnify-signature header
(production only) - signature verification in this codebase is skipped in
non-production environments accordingly.
- Sign up twice - once as a seller, once as a buyer (two browsers/incognito windows, or two accounts).
- Seller: add a payout bank account at
/profile/bank-details- it's verified live against Monnify's Name Enquiry API before saving. - Seller: create a listing at
/sell/newwith a photo. - Buyer: open the listing and click Buy now (or add it to the cart and check out). You're redirected to Monnify's hosted checkout.
- Sandbox payment: on the checkout page, switch to Pay with Transfer and use the Monnify Bank Simulator to complete the transfer for the exact amount shown.
- Back on Trustee (redirected to
/orders/[id], or/cart/checkout-successfor a cart checkout), the order flips to Funds in escrow (via webhook, or the redirect-back page's own Verify Transaction fallback if the webhook hasn't arrived yet). - Seller: marks the order Shipped from the order page.
- Buyer: clicks Confirm receipt - this triggers the Monnify disbursement to the seller's verified account.
- If Monnify's MFA/OTP is enabled on the account (the default), the transfer shows up on the Payouts tab of
/dashboard(visible to admin users) waiting for the OTP emailed to the account owner. Paste it in to authorize - the order then flips to Released. - See
docs/action-points.mdfor the one-time Monnify account setup (enabling disbursements, wallet account number) needed before this step works.
- If Monnify's MFA/OTP is enabled on the account (the default), the transfer shows up on the Payouts tab of
- Disputes: instead of confirming, the buyer can Report a problem - this opens the order to an admin, who resolves it from the Disputes tab of
/dashboardby releasing to the seller or refunding the buyer.
docs/features.md- feature checklist and build statusdocs/action-points.md- manual setup steps only you can complete (Monnify account config, Supabase project)