A modern, mobile-first agricultural marketplace bridging smallholder farmers and commercial agricultural producers with individual buyers and bulk institutions.
- Overview
- Key Features & Capabilities
- Tech Stack
- Project Architecture & Directory Structure
- Environment Configuration (.env)
- Getting Started & Local Development
- M-Pesa STK Push Setup & Testing
- WebSocket Protocol & Event Reference
- REST API Endpoints Reference
- Production Deployment
- License
AgroMarket solves the traditional friction in agricultural commodity trading by establishing a direct, transparent pipeline between farmers and buyers. It eliminates exploitative intermediaries, provides real-time fair pricing, guarantees quality through admin product moderation, and provides instant mobile checkout with Safaricom M-Pesa STK push and real-time payment reconciliation.
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β BUYER β β AGROMARKET β βFARMER/SELLER β
β ββββββββββββ β β ββββββββββββ β β ββββββββββββ β
β Browse Crops β ββββββ> β Marketplace β <ββββββ β List Produce β
β STK Checkout β ββββββ> β Escrow/Order β ββββββ> β Bulk Orders β
β Live Trackingβ <ββββββ β /ws Realtime β <ββββββ β Chat / Payoutβ
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
- Small Purchases (Retail / Household): Buy in standard retail units (e.g., kg, bunches, bags) with instant cart calculation and full settlement.
- Bulk Purchases (B2B / Commercial / Institutional): Buy at discounted wholesale rates with minimum quantity thresholds.
- Split Payment Workflow (Deposit & Balance):
- Buyers can place bulk orders by paying a 50% commitment deposit upfront via M-Pesa STK Push.
- The remaining 50% balance is settled upon delivery and physical inspection.
Modeled after enterprise fintech standards:
- OAuth 2.0 In-Memory Token Caching: Tokens are cached for ~55 minutes, preventing Daraja rate-limiting and optimizing latency.
- Support for Paybill & Till Numbers:
CustomerPayBillOnlinefor Paybill business shortcodes.CustomerBuyGoodsOnlinefor Till Numbers / Buy Goods.
- Smart Phone Number Sanitization: Automatically normalizes standard Kenyan phone formats (
07...,01...,+254...,254...). - Complete Audit Trail (
stk_push_responses): Logs every initiated STK prompt withCheckoutRequestID,MerchantRequestID, amounts, raw callbacks, and result statuses. - Built-in Mock Simulation Mode: Automatic local fallback for offline development or testing without live Daraja credentials.
Mounted on /ws with bi-directional JSON messaging:
- Instant Payment Confirmation (
payment.status): Immediately updates the buyer's checkout screen and order tracking timeline the exact millisecond Safaricom processes the M-Pesa PIN. - Live Order Status Broadcasting (
order.status): Real-time notifications when orders transition betweenplaced,approved,packed,shipped, anddelivered. - Direct Peer-to-Peer Messaging: Real-time chat between buyers and farmers regarding crop conditions, custom bulk agreements, and delivery logistics.
- Multi-Device Session Sync: Intelligently routes messages across multiple active browser tabs or mobile sessions for the same user.
- Automated Payout Splitting: When an order contains items from multiple farmers, the system splits and calculates each supplier's payout ledger automatically.
- Payout Preferences: Farmers can configure preferred disbursement channels:
- M-Pesa Mobile Money (Phone Number)
- Bank Transfer (Bank Name & Account Number)
- PayPal (Email Address)
- Admin Clearance Portal: Dedicated administrator view to review, approve, and disburse pending supplier payouts.
- Buyer: Marketplace exploration, advanced filters, cart management, checkout with M-Pesa STK/Stripe, live order tracking, chat with sellers.
- Seller (Farmer): Product listing creation, harvest date scheduling, bulk pricing tiers, earnings analytics, order fulfillment, payout settings.
- Admin: Quality control & product verification, user role governance, platform metrics, supplier payout approvals.
- Framework: React 18 + TypeScript + Vite
- Routing: Wouter (Ultra-lightweight client-side router)
- Server State & Caching: TanStack React Query v5
- Client State: Zustand (Persistent authentication store)
- UI & Component System: shadcn/ui + Radix UI primitives
- Styling: Tailwind CSS +
tailwindcss-animate - Icons & Visuals: Lucide React + React Icons
- Runtime: Node.js 20+ (ES Modules)
- Framework: Express.js with TypeScript
- Database & ORM: PostgreSQL + Drizzle ORM
- Real-Time Engine: ws (Native WebSocket Server)
- Authentication: JWT (JSON Web Tokens) with OTP email verification
- File Uploads: Multer with static disk storage
- Mobile Payments: Safaricom Daraja API (Lipa Na M-Pesa Online STK Push, Query, Callbacks)
- Card Payments: Stripe API (Checkout Sessions & Webhooks)
- SMS & Alerts: Twilio & Africa's Talking
AgroMarket/
βββ client/ # Frontend React SPA
β βββ public/ # Static assets, logos, and uploads
β βββ src/
β βββ components/ # Reusable UI components & dialogs
β β βββ ui/ # shadcn/ui base primitives (button, card, dialog, etc.)
β β βββ Header.tsx # Navigation header with cart badge
β β βββ MobileNav.tsx # Mobile responsive bottom navbar
β β βββ OrderTrackingTimeline.tsx # Visual stepper for order stages
β βββ hooks/ # Custom React hooks
β β βββ useAuth.ts # Authentication convenience hook
β β βββ usePaymentSocket.ts # Real-time M-Pesa & order WebSocket subscriber
β β βββ use-toast.ts # Interactive notification toast hook
β βββ lib/ # API client, axios interceptors, query client
β βββ pages/ # Application views / routes
β β βββ auth/ # Login, Register, VerifyOtp
β β βββ AdminDashboard.tsx # Product approvals & payout clearance
β β βββ SellerDashboard.tsx # Farmer inventory, earnings, and listings
β β βββ Cart.tsx # Shopping cart & STK push checkout modal
β β βββ OrderTracking.tsx # Live order progress & receipt verification
β β βββ ProductDetail.tsx # Single product with bulk pricing options
β β βββ Chat.tsx # Real-time WebSocket messaging interface
β βββ store/ # Zustand global state (authStore)
β βββ App.tsx # Root router & layout wrapper
β
βββ server/ # Backend Express API & WebSocket Server
β βββ services/
β β βββ mpesa.ts # Enterprise MpesaService (Daraja STK push & callbacks)
β βββ db.ts # PostgreSQL Drizzle database connection
β βββ index.ts # HTTP and WebSocket server entry point
β βββ routes.ts # REST API endpoints & webhook controllers
β βββ socket.ts # WebSocket connection registry & event broadcasting
β βββ storage.ts # Database repository / access layer
β βββ vite.ts # Development Vite SSR / proxy integration
β
βββ shared/
β βββ schema.ts # Drizzle database tables, relations, and Zod schemas
β
βββ scripts/
β βββ seed_admin.ts # Admin user database seeder
β
βββ drizzle.config.ts # Drizzle Kit migration configuration
βββ package.json # Dependencies and project scripts
βββ tailwind.config.ts # Tailwind CSS design system tokens
βββ tsconfig.json # TypeScript compiler configuration
Create a .env file in the root directory:
# ==============================================================================
# DATABASE CONFIGURATION
# ==============================================================================
DATABASE_URL=postgres://appuser:apppassword@localhost:5432/agromarket?sslmode=disable
# ==============================================================================
# SERVER & SECURITY
# ==============================================================================
PORT=8010
JWT_SECRET=agro-market-super-secret-key-1234
NODE_ENV=development
STANDALONE_API=true
# ==============================================================================
# SAFARICOM DARAJA M-PESA CONFIGURATION
# ==============================================================================
MPESA_ENV=sandbox # 'sandbox' or 'production'
MPESA_ACCOUNT_TYPE=till # 'till' (Buy Goods) or 'paybill'
MPESA_CONSUMER_KEY=your_consumer_key
MPESA_CONSUMER_SECRET=your_consumer_secret
MPESA_SHORTCODE=174379 # Business Shortcode (or 174379 for Sandbox)
MPESA_TILL_NUMBER=174379 # Online Till Number (used when ACCOUNT_TYPE=till)
MPESA_PASSKEY=bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919
MPESA_CALLBACK_URL=https://your-domain.ngrok-free.app/api/payments/mpesa/callback
MPESA_MOCK_MODE=false # Set to 'true' to force mock mode in dev
# ==============================================================================
# STRIPE PAYMENTS (CARD CHECKOUT)
# ==============================================================================
STRIPE_PUBLISHABLE_KEY=pk_test_placeholder
STRIPE_SECRET_KEY=sk_test_placeholder
STRIPE_WEBHOOK_SECRET=whsec_placeholder
# ==============================================================================
# SMS & NOTIFICATIONS (OPTIONAL)
# ==============================================================================
TWILIO_ACCOUNT_SID=placeholder_sid
TWILIO_AUTH_TOKEN=placeholder_token
TWILIO_PHONE_NUMBER=placeholder_phone
AFRICAS_TALKING_USERNAME=sandbox
AFRICAS_TALKING_API_KEY=placeholder_api_key- Node.js: v20.x or higher installed (Download Node.js)
- PostgreSQL: v14+ running locally or a hosted instance (Neon, Supabase, AWS RDS)
- Git
Clone the repository and install all dependencies:
git clone https://github.com/Timothy970/AgroMarket.git
cd AgroMarket
npm installEnsure PostgreSQL is running and your DATABASE_URL is set in .env.
To push schema changes directly to your PostgreSQL database:
npm run db:pushOr generate and execute formal migration files:
npm run db:migrateSeed default admin and test records into the database:
npm run db:seed-adminStart both the Express Backend API (Port 8010) and Vite Frontend Dev Server (Port 5173) concurrently:
npm run devOpen your browser and navigate to:
π http://localhost:5173
- Register for an account on the Safaricom Developer Portal (Daraja).
- Create a new Sandbox App and enable Lipa Na M-Pesa Online.
- Copy your
Consumer KeyandConsumer Secretto.env. - The default sandbox shortcode is
174379with the official sandbox passkey:bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919
Because Safaricom Daraja needs a publicly reachable HTTPS endpoint to deliver callback webhooks:
- Start an ngrok tunnel pointing to your backend port:
ngrok http 8010
- Copy the generated forwarding HTTPS URL and update
.env:MPESA_CALLBACK_URL=https://abc123.ngrok-free.app/api/payments/mpesa/callback
If you do not have live Daraja credentials configured (or leave placeholders in .env), the system automatically activates Mock Mode:
- You can enter any Safaricom phone number in checkout.
- The server will acknowledge the request with simulated IDs.
- After 3 seconds, a realistic Safaricom callback will execute automatically, triggering database reconciliation and broadcasting real-time WebSocket confirmation.
AgroMarket exposes a unified WebSocket server at ws://localhost:8010/ws (or wss://<domain>/ws).
Upon connecting, clients send an auth frame:
{
"type": "auth",
"token": "<JWT_ACCESS_TOKEN>"
}To subscribe to a specific order's live updates:
{
"type": "subscribe_order",
"orderId": "3b29c0b1-1234-4567-8901-abcdef012345"
}Emitted as soon as an M-Pesa STK callback arrives or is simulated:
{
"type": "payment.status",
"event": "payment.status",
"channel": "payments.user-uuid",
"data": {
"orderId": "order-uuid",
"checkoutRequestId": "ws_CO_123456789",
"status": "success",
"message": "Payment received successfully via M-Pesa",
"amount": 2500,
"receipt": "NL29X8YZ01",
"phoneNumber": "254712345678",
"timestamp": "2026-09-11T12:00:00.000Z"
}
}Emitted when an order stage updates:
{
"type": "order.status",
"event": "order.status",
"orderId": "order-uuid",
"data": {
"orderId": "order-uuid",
"status": "approved",
"message": "Order payment confirmed",
"timestamp": "2026-09-11T12:00:00.000Z"
}
}{
"type": "message",
"message": {
"id": "msg-uuid",
"senderId": "user-1",
"receiverId": "user-2",
"productId": "product-uuid",
"content": "Hello, is the harvest ready for pickup?",
"createdAt": "2026-09-11T12:00:00.000Z"
}
}POST /api/auth/registerβ Request OTP for user registration.POST /api/auth/loginβ Request OTP for login.POST /api/auth/verifyβ Verify OTP and receive JWT access token.GET /api/auth/meβ Retrieve current authenticated user profile.PUT /api/auth/profileβ Update user profile & payout preferences.
GET /api/categoriesβ List all active product categories.POST /api/categoriesβ Create category (Admin).GET /api/productsβ Browse approved products with filters (category,search,minPrice,maxPrice,location).GET /api/products/:idβ Get product detail with bulk tiers.POST /api/productsβ Create new product listing (Seller).PUT /api/products/:idβ Update listing (Seller).DELETE /api/products/:idβ Remove listing (Seller).GET /api/products/seller/mineβ Get seller's own products.PATCH /api/products/:id/approvalβ Moderate/Approve product (Admin).
GET /api/cartβ Get user's cart items with pricing & totals breakdown.POST /api/cartβ Add product to cart with purchase mode (smallorbulk).PUT /api/cart/:idβ Update cart item quantity.DELETE /api/cart/:idβ Remove single item from cart.DELETE /api/cartβ Clear entire cart.
POST /api/ordersβ Create new order with item snapshots.GET /api/ordersβ List user's order history.GET /api/orders/:idβ Get detailed order summary & tracking timeline.PATCH /api/orders/:id/statusβ Update status (approved,shipped,delivered).PATCH /api/orders/:id/paymentβ Update payment flags (depositPaid,balancePaid).
POST /api/payments/mpesa/stkpushβ Initiate Lipa Na M-Pesa STK Push.POST /api/payments/mpesa/callbackβ Daraja STK webhook receiver.GET /api/payments/mpesa/query/:checkoutRequestIdβ Query Daraja STK transaction status.POST /api/payments/stripe/create-checkout-sessionβ Create Stripe card checkout session.POST /api/payments/stripe/webhookβ Stripe webhook receiver.
GET /api/supplier-payoutsβ View supplier payout ledger (Admin / Seller).PATCH /api/supplier-payouts/:id/approveβ Approve payout amount (Admin).POST /api/supplier-payouts/:id/payβ Mark payout as disbursed (Admin).
GET /api/chat/conversationsβ List active user chat conversations.GET /api/chat/messages/:receiverIdβ Fetch chat history with specific user.
npm run buildThis compiles the Vite frontend into dist/public and bundles the Express server into dist/index.js.
npm run db:migratenpm startThe server will bind to process.env.PORT (default 8010) and serve both the REST API and the static React production client.
This project is licensed under the MIT License β see the LICENSE file for details.
