Client Payments API
Retrieves payments that belong to the authenticated client (or, for staff roles, to any client records that share the signed‑in user’s email).
Base URL: /api/client/payments
Auth: HTTP‑only JWT cookie (token) issued by POST /api/auth/login
Required roles: admin, finance, or client (authorization middleware)
Content type: application/json
GET /api/client/payments/
Summary
Return all payment records whose payments.clientId matches client(s) whose email equals the authenticated user’s email.
- If the caller’s role is
client, this yields that client’s own payments. - If the caller’s role is
adminorfinance, results are still filtered by the caller’s email, which typically means no client data is returned unless a client record exists with the same email address. (This is current server behavior.)
Ordering & pagination
- No explicit ordering is applied by the API; treat the returned list as unsorted.
- Pagination is not implemented.
Authorization
Requires a valid session cookie:
Cookie: token=<jwt>
Query parameters
None.
Request body
None.
Response — 200 OK
An array of Payment objects:
[
{
"id": 123,
"clientId": 45,
"amount": 25000,
"paymentDate": "2025-02-10T00:00:00.000Z",
"paymentType": "income",
"description": "Initial booking",
"serviceType": "installment_payment",
"company": 3,
"companyName": "Acme Properties"
}
]
Payment object
| Field | Type | Notes |
|---|---|---|
id | integer | Payment identifier. |
clientId | integer | ID of the client who made/owns the payment. |
amount | number | Monetary amount. |
paymentDate | string | ISO 8601 date‑time. |
paymentType | string | Common values: "income" or "expense". |
description | string|null | Free‑text note. |
serviceType | string|null | E.g. "installment_payment"; other service types may appear. |
company | integer | Company ID that owns the record. |
companyName | string | Human‑readable company name (via join). |
Errors
| Status | When | Example payload |
|---|---|---|
| 401 | Missing/invalid session or req.user absent | {"message":"Unauthorized"} |
| 500 | Any unhandled server/database error | {"message":"Error retrieving payments"} |
cURL example
curl -X GET https://<host>/api/client/payments \
-H "Cookie: token=<jwt>"
JavaScript (fetch) example
const res = await fetch("/api/client/payments", {
credentials: "include"
});
if (!res.ok) throw new Error("Failed to load payments");
const data = await res.json();
console.log(data); // Array of payments
Security & data access
- Endpoint is protected by role-based middleware:
authorize(["admin","finance","client"]). - Result set is always constrained by the authenticated user’s email:
- Backend first finds client rows with
clients.email == req.user.email. - It then returns payments whose
payments.clientIdis in that set.
- Backend first finds client rows with
- There is no company scoping or additional filtering on this endpoint.
Implementation notes (server)
- SQL shape (via Drizzle):
SELECT payments.* , companies.name AS companyNameFROM payments LEFT JOIN companies ON payments.company = companies.idWHERE payments.clientId IN (SELECT id FROM clients WHERE email = req.user.email)
- File:
server/routes/client/payments.ts(handler:getClientPayment).
Planned extensions (non‑breaking)
- Add explicit ordering, e.g.,
ORDER BY payments.paymentDate DESC. - Add optional pagination (
page,pageSize) and date filters (from,to). - Allow admins/finance to query by
clientIdoremail(with proper authorization).