Orders & Settlements is a small order-operations workspace for tracking customer orders, partial payments, refunds, and outstanding balances.
I built it around one principle: money should be predictable. Order totals are calculated on the server and stored as integer cents, payment rules are enforced in the API, and important changes are recorded in an audit trail.
- Email/password sign up and login
- User-scoped data access
- Orders with multiple line items
- Server-side subtotal and total calculation
- Pending, partially paid, paid, and overdue statuses
- Partial payment recording with overpayment protection
- Order detail view with payment history
- Refunds with refundable-balance protection
- Audit history for order creation, updates, payments, refunds, and status changes
- Dashboard filtering by status
- CSV export with an optional due-date range
- Next.js App Router and TypeScript
- React
- MongoDB with Mongoose (transactions require Atlas or a replica-set-enabled local MongoDB)
- JWT session cookie authentication
- Zod request validation
- Tailwind CSS
- Node.js 20 or newer
- A MongoDB database. MongoDB Atlas is the simplest option; local MongoDB must run as a replica set because the app uses transactions.
npm installCreate .env.local in the project root:
MONGODB_URI=your-mongodb-connection-string
JWT_SECRET=your-long-random-session-secretJWT_SECRET should be a long, random value used only by this application. Do not commit .env.local or share either secret.
If you use local MongoDB, start it with replica-set support and initialize the replica set before using payment or refund flows. A standalone MongoDB process does not support the transactions used by this app.
Start the development server:
npm run devOpen http://localhost:3000, create an account, and create an order.
Useful checks:
npm run lint
npm run buildThe UI accepts currency values, but the API receives and stores amounts in integer cents. For example, $1,000.00 is stored as 100000.
For each order line:
line total = quantity × unit price
The order subtotal and total are the sum of all line totals. There are no order-level taxes or discounts in this assignment.
Zero-value lines are allowed, but an order must have a total greater than $0.00. Quantity must be at least 1 and unit price cannot be negative.
Status is derived from the order total, net paid amount, and due date:
| Status | Rule |
|---|---|
pending |
No net payment has been recorded and the order is not past due |
partially_paid |
Net payment is greater than zero but below the order total, and the order is not past due |
paid |
Net paid amount is equal to or greater than the order total |
overdue |
The due date has passed and the order is not fully paid |
Paid takes precedence over overdue. This means an overdue order becomes paid as soon as its remaining balance is settled.
Multiple payments are supported. The API rejects a payment that exceeds the current amount due and returns the remaining balance in the error response.
Orders become read-only after the first payment. This prevents changing the order total after money has already been allocated against it. The rule is enforced by the API as well as reflected in the UI.
Refunds are stored as a separate entity rather than as negative payments. A refund cannot exceed the net amount paid:
net paid = gross payments − refunds
refundable balance = gross payments − refunds already recorded
A refund can reopen a fully paid order by reducing its net paid amount. Refunds also appear in the order history and audit log.
Audit events are written transactionally with the underlying order, payment, or refund operation. The order detail page shows creation, updates, payments, refunds, and status transitions with timestamps.
The dashboard provides an optional due-date range filter. The export includes:
- Order ID and customer
- Due date and derived status
- Subtotal and total
- Gross paid
- Refunded amount
- Net paid
- Amount due
Leaving either date empty keeps that boundary open.
All order endpoints require the authenticated session cookie.
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/api/auth/signup |
Create an account and start a session |
POST |
/api/auth/login |
Log in and start a session |
POST |
/api/auth/logout |
Clear the current session |
GET |
/api/auth/me |
Return the current user |
GET |
/api/orders |
List the current user's orders; accepts status filter |
POST |
/api/orders |
Create an order with line items |
GET |
/api/orders/:id |
Load order details, payments, refunds, and audit history |
PATCH |
/api/orders/:id |
Update an unpaid order |
DELETE |
/api/orders/:id |
Delete an unpaid order |
GET |
/api/orders/:id/payments |
List payments for an order |
POST |
/api/orders/:id/payments |
Record a payment |
GET |
/api/orders/:id/refunds |
List refunds for an order |
POST |
/api/orders/:id/refunds |
Record a refund |
GET |
/api/orders/:id/audit-log |
List audit events |
GET |
/api/orders/export |
Download CSV; accepts optional from and to dates in YYYY-MM-DD format |
Errors use a consistent shape:
{
"error": {
"message": "Payment exceeds the remaining balance",
"code": "OVERPAYMENT",
"remainingAmountCents": 60000
}
}Validation errors include an issues array from Zod with field-level details.
Order creation, payment recording, refund recording, and audit writes use MongoDB transactions. Payment and refund operations reload the order inside the transaction before calculating the remaining balance, so competing writes are subject to MongoDB's transaction conflict handling rather than relying on client-side values.
Payment and refund requests also carry an idempotency key. Retrying the same request with the same key returns the original mutation instead of creating a duplicate financial event.
For a larger production system, I would add stronger operational safeguards around idempotency keys, payment-provider reconciliation, retry handling, and an explicit atomic conditional update strategy for very high write contention.
The app can be deployed to Vercel with MongoDB Atlas:
- Create a MongoDB Atlas cluster and allow the deployment to connect.
- Import the repository into Vercel.
- Add
MONGODB_URIandJWT_SECRETas Vercel environment variables for the appropriate environments. - Deploy and verify
/api/health. - Test sign up, order creation, payment, refund, and CSV download on the deployed URL.
Live URL: https://orders-settlements.vercel.app/
- Expand the included unit tests into integration tests for payment allocation, refund limits, and authorization boundaries.
- Add idempotency keys for payment and refund requests.
- Add structured logging, monitoring, and alerting.
- Add pagination and aggregation queries for large order histories and exports.
- Add role-based access control and stronger account recovery flows.
- Integrate external payment providers with webhook reconciliation.
- Add rate limiting and more granular security auditing.
- Create an order for
2 × $500 = $1,000. - Record a
$400payment. The order should showpartially_paidand$600due. - Record a
$600payment. The order should showpaidand$0due. - Try to record another
$1payment. The API should reject it as an overpayment. - Record a partial refund and confirm that net paid, amount due, status, refund history, and audit history update together.