A robust, full-stack application for managing B2B orders and tracking payments.
This repository is split into a cleanly separated Frontend (Next.js) and Backend (Node.js/Express).
- Frontend: Next.js (App Router), React, Tailwind CSS, TypeScript, Axios, Playwright (E2E Testing).
- Backend: Node.js, Express, TypeScript, Zod (Validation), Jest (Testing). Built using Clean Architecture (Domain, Application, Infrastructure, Interface layers).
- Database: MongoDB (with Mongoose).
- CI/CD: GitHub Actions (for automated linting and testing).
- Node.js (v18+)
- MongoDB (Local instance or MongoDB Atlas cluster)
- npm or yarn
-
Clone the repository:
git clone https://github.com/dejetem/orders-and-settlements cd orders-and-settlements -
Backend Setup:
cd backend npm install # Create a .env file based on defaults or use your own MongoDB URI echo "PORT=5000\nMONGODB_URI=mongodb://127.0.0.1:27017/orders-app\nJWT_SECRET=super_secret_key_change_me_in_prod\nNODE_ENV=development\nFRONTEND_URL=http://localhost:3000" > .env # Start the backend server npm run dev
-
Frontend Setup:
# Open a new terminal tab cd frontend npm install # Create a .env.local file with the API URL echo "NEXT_PUBLIC_API_URL=http://localhost:5000/api" > .env.local # Start the frontend dev server npm run dev
-
Access the Application: Open your browser and navigate to
http://localhost:3000. You can create an account and start managing orders!
The backend exposes a clean REST API running on http://localhost:5000/api.
POST /auth/register- Register a new user (email, password)POST /auth/login- Authenticate and receive an HTTP-Only cookie.POST /auth/logout- Clear authentication cookies.GET /auth/me- Get current user profile.
GET /orders- Fetch all orders for the authenticated user.- Query Params:
?status=pending|partially_paid|paid|overdue
- Query Params:
GET /orders/export- Export orders to a CSV file.- Query Params:
?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
- Query Params:
POST /orders- Create a new order with line items.GET /orders/:id- Get details of a specific order, including its payment history.PUT /orders/:id- Update an existing order.DELETE /orders/:id- Delete a specific order.GET /orders/:id/audit-logs- Get audit logs for a specific order.POST /orders/:id/payments- Record a new payment against an order.
Order status is dynamically derived based on the dueDate and amountPaid relative to the total.
| Condition | Status |
|---|---|
amountPaid == 0 && dueDate >= today |
pending |
amountPaid == 0 && dueDate < today |
overdue |
0 < amountPaid < total && dueDate >= today |
partially_paid |
0 < amountPaid < total && dueDate < today |
overdue |
amountPaid >= total |
paid |
Edge Case Handling:
- Overdue but fully paid later: If an order becomes
overdueand a user subsequently makes a payment that bringsamountPaidequal tototal, the status is immediately updated topaid. - Floating Point Math: JavaScript floats are notoriously inaccurate for currency. To avoid rounding errors (e.g.,
$10.01 + $20.02 != $30.03), the frontend accepts dollars/cents as decimals, but the API and Database strictly store all monetary values in cents (integers).
Handling concurrent payments is a critical requirement in financial systems.
Scenario: Two users (or one impatient user clicking twice) attempt to pay $600 simultaneously on an order that only has $1000 due.
Solution: Optimistic Concurrency Control (OCC)
- I implemented OCC using Mongoose's built-in
__v(version key) on the Order schema. - When
POST /orders/:id/paymentsis called, the order is fetched. - If both requests fetch version
1and validate the amount against the balance, the first request will save the updated balance and increment the version to2. - When the second request attempts to save, Mongoose will throw a
VersionErrorbecause it's trying to save over version1, but the database is already at version2. - The API catches this error and cleanly returns a
409 Conflict, completely preventing the overpayment race condition without requiring heavy distributed locks.
- Tenant Data Isolation: Orders and users are securely scoped to an
Organization. Queries inherently filter byorganizationId, providing true B2B SaaS multi-tenancy. - Full Audit Logging: An
AuditLogcollection automatically tracksORDER_CREATEDandPAYMENT_ADDEDevents (event sourcing pattern), recording exactly who made a change and what the previous/new status was. - Idempotency Keys: The frontend sends an
Idempotency-Keyheader with payment requests. The backend enforces uniqueness to guarantee that network retries never result in duplicate charges. - End-to-End Testing: The project includes a Playwright test suite (
frontend/tests/order-flow.spec.ts) that asserts the entire order and payment lifecycle.
- Authentication: I used a standard Email/Password JWT setup with HTTP-only cookies. While OAuth (Google/Microsoft) is common in B2B, email/password was explicitly marked as "sufficient" for the assignment.
- Immutability: I chose to allow payments to simply append to the order's
amountPaidand automatically recalculate status, leaving the original order's line items editable in theory. In a strict financial system, once a payment is made, the order invoice should become completely immutable (read-only) for auditability.