Transactions Module
What this module solves
Section titled “What this module solves”Records and maintains money movement with strict validation, wallet/category ownership checks, and OCR-assisted batch creation.
Endpoints
Section titled “Endpoints”POST /api/v1/transactionPOST /api/v1/transaction/receiptGET /api/v1/transactionGET /api/v1/transaction/:idPATCH /api/v1/transaction/:idDELETE /api/v1/transaction/:id
Validation rules
Section titled “Validation rules”- Create: positive numeric
amount, valid date string, supportedcurrency, requiredwalletIdandcategoryId. - Update: partial update allowed but at least one field required.
- Receipt flow: multipart file
receiptpluswalletIdandcategoryId. - Receipt upload is parsed by Multer (
receipt) in the presentation layer before invoking the OCR use case. - Schemas:
backend/src/Shared/Schemas/transactionSchema.tsbackend/src/Shared/Schemas/ocrSchema.ts
Internal flow
Section titled “Internal flow”| Route | Controller | Use case | Repository / Service chain | Route file trace |
|---|---|---|---|---|
POST /transaction | TransactionController.create | AddTransactionImpl | TransactionRepositoryImpl, WalletRepositoryImpl, CategoryRepositoryImpl, BudgetAllocationRepositoryImpl | backend/src/Presentation/Routes/TransactionRoutes.ts |
POST /transaction/receipt | TransactionController.createFromReceipt | TransactionOcrImpl -> AddTransactionImpl | OcrServiceImpl, repositories above | backend/src/Presentation/Routes/TransactionRoutes.ts |
GET /transaction | TransactionController.listUserTransactions | ListTransactionImpl | TransactionRepositoryImpl | backend/src/Presentation/Routes/TransactionRoutes.ts |
GET /transaction/:id | TransactionController.getById | GetTransactionImpl | TransactionRepositoryImpl | backend/src/Presentation/Routes/TransactionRoutes.ts |
PATCH /transaction/:id | TransactionController.update | UpdateTransactionImpl | TransactionRepositoryImpl | backend/src/Presentation/Routes/TransactionRoutes.ts |
DELETE /transaction/:id | TransactionController.delete | DeleteTransactionImpl | TransactionRepositoryImpl | backend/src/Presentation/Routes/TransactionRoutes.ts |
Controller: backend/src/Presentation/Controllers/TransactionController.ts.
Common errors
Section titled “Common errors”INVALID_TRANSACTION_ID(400).TRANSACTION_NOT_FOUND(404).TRANSACTION_OWNER_MISMATCH(403).WALLET_NOT_FOUND/INVALID_CATEGORY_EXIST_PARAMS(400/404 path dependent).OCR_PARSE_FAILED/OCR_RESPONSE_FAILED(500) for receipt ingestion.
Testing notes
Section titled “Testing notes”- E2E file:
backend/src/tests/e2e/transaction/transaction-crud.e2e.test.ts. - Unit file:
backend/src/tests/unit/add-transaction-impl.test.ts.
Quick snippets
Section titled “Quick snippets”1) cURL request example
Section titled “1) cURL request example”curl -X POST "https://budgeti-backend.johandercampos.com/api/v1/transaction" \ -H "Authorization: Bearer <jwt>" \ -H "Content-Type: application/json" \ -d '{ "amount": 45.9, "description": "Lunch", "date": "2026-02-20T13:00:00.000Z", "currency": "USD", "walletId": "wlt_123", "categoryId": "cat_123", "type": "expense" }'2) Success JSON response example
Section titled “2) Success JSON response example”{ "code": "TRANSACTION_CREATED", "message": "Transaction created successfully", "transaction": { "id": "txn_123", "amount": -45.9, "description": "Lunch", "date": "2026-02-20T13:00:00.000Z", "currency": "USD", "walletId": "wlt_123", "categoryId": "cat_123" }}3) Common error JSON response example
Section titled “3) Common error JSON response example”{ "code": "INVALID_TRANSACTION_ID", "message": "Invalid transaction identifier", "error": "Invalid transaction identifier"}4) Tiny internal trace snippet
Section titled “4) Tiny internal trace snippet”// TransactionController.createconst amount = req.body.type === 'expense' ? -Math.abs(Number(req.body.amount)) : Math.abs(Number(req.body.amount));
const transaction = await createTransactionImpl.execute( { amount, date: parseIsoDateOrThrow(String(req.body.date), 'Invalid transaction date'), walletId: req.body.walletId, categoryId: req.body.categoryId, }, req.userId!,);