Finsoft is a comprehensive backend system designed for managing financial transactions and accounts. It provides functionalities to handle user accounts, destinations, and transactions securely and efficiently. Built with Flask and PostgreSQL, it offers a RESTful API for external systems to interact with.
The system is designed to allow users to create and manage accounts, track transactions, and perform operations like deposits and withdrawals. It integrates with banking institutions and provides seamless access to account balances, transaction histories, and more. The software ensures security with features like JWT authentication and encryption for sensitive data.
- Account Management: Users can create, update, and manage their accounts.
- Transaction Handling: Supports operations such as withdrawals and balance transfers.
- Bank Integration: Interacts with banks to facilitate secure transactions.
- User Authentication: Uses JWT tokens for secure user sessions and authentication.
- Balance Overview: Retrieves the total available balance for the user from their linked accounts.
- Backend: Flask
- Database: PostgreSQL
- Authentication: JWT (JSON Web Tokens)
- ORM: SQLAlchemy
- Testing: Pytest
- Other Libraries: Flask-CORS, Flask-Bcrypt, Flask-Migrate
The Finsoft project follows a modular architecture pattern, organizing the application into well-defined layers and components. This structure is designed to ensure scalability, maintainability, and separation of concerns. The main components include Repositories, Services, Controllers, Entities, DTOs, and other utility modules.
Repositories manage the interaction with the database, encapsulating the logic required to fetch, store, and update entities. They provide an abstraction layer over raw SQL queries and allow for easier data manipulation. Each repository typically corresponds to an entity and exposes methods to perform CRUD operations.
The Service layer contains the business logic and coordinates data access via repositories. Services process data and ensure that business rules are followed before passing the results back to the controllers. They act as a bridge between the controllers and the data repositories.
Controllers are responsible for handling HTTP requests and returning appropriate responses. They manage the input and output of data, ensuring that the correct data is fetched from the service layer and serialized into the required response format (usually JSON). Controllers are also in charge of request validation, error handling, and authorization checks.
Entities represent the core business models in the application. Each entity corresponds to a table in the database and contains fields that map to columns in those tables. The entities are defined using SQLAlchemy ORM and are responsible for data storage and retrieval.
DTOs are used to transfer data between the application layers. They are often used to encapsulate data to be sent in API responses or to receive data in API requests. DTOs provide a lightweight way to pass data without exposing internal models directly.
The utils folder contains helper functions and utility classes that perform common tasks across the application, such as encryption, formatting, logging, or handling application-specific logic that doesn't belong to other layers.
Serializers are used to convert entities or DTOs into JSON or other formats required for API responses. They provide a convenient way to structure the output data, ensuring that it meets the API’s specification. This separation allows for flexible handling of different response formats.
Middleware components handle cross-cutting concerns such as authentication, logging, and request validation. They sit between the client’s request and the controller, intercepting and modifying requests and responses as needed.
The migrations folder contains files generated by Flask-Migrate to handle database schema changes. It helps to version the database structure and makes it easier to manage changes across environments.
src/
finsoft/
config/ # Configuration files for the app (e.g., database settings, environment variables)
controllers/ # Controllers for handling HTTP requests and responses
db/ # Database models and migrations for schema management
dto/ # Data Transfer Objects (DTOs) for structured data input/output
entities/ # Core business models (e.g., Account, User)
middleware/ # Middleware for authentication, authorization, and other cross-cutting concerns
migrations/ # Database migrations for schema evolution and versioning
repositories/ # Repositories for managing data access and interaction with the database
serializers/ # Helpers for serializing models into JSON or other formats
services/ # Business logic and core services coordinating the repositories
utils/ # Utility functions and helper classes for common tasks
__about__.py # Project metadata
app.py # Main entry point for the Flask application
requirements.txt # List of project dependencies
test.py # Test cases for the application
Ensure that the following tools are installed on your machine:
- Docker
- Docker Compose
-
Clone the Repository
First, clone the repository to your local machine:
git clone https://github.com/yourusername/finsoft.git cd finsoft -
Create the .env File
Create a .env file in the root of your project and add the necessary environment variables:
APP_SECRET_KEY=your_secret_key DATABASE=postgresql://postgres:your_password@db:5432/finsoft FLASK_ENV=production FLASK_APP=src/finsoft/app.py POSTGRES_USER=postgres POSTGRES_PASSWORD=your_password POSTGRES_DB=finsoft
-
** Run the project **
docker compose up --builder
This command will start both the Flask API and the PostgreSQL database containers. The Flask application will be accessible at http://localhost:50051.
-
Access the Database (Optional) If you need to access the PostgreSQL database running in Docker, you can use the following command to enter the PostgreSQL container:
docker-compose exec db psql -U postgres -d finsoft
- URL:
/api/v1/users - Method:
POST - Description: Creates a new user in the system with the provided
full_name,email, andpassword.
{
"full_name": "John Doe",
"email": "johndoe@example.com",
"password": "securepassword123"
}{
"id": "cfc36b93-b41a-48bc-bb76-812c20665058",
"full_name": "John Doe",
"email": "johndoe@example.com",
"created_at": "2024-11-13T15:30:00",
"updated_at": "2024-11-13T15:30:00"
}{
"code": 409,
"error": "El usuario ya existe"
}- URL:
/api/v1/auth/login - Method:
POST - Description: Authenticates a user and returns a JWT token for authorized access.
{
"email": "user@example.com",
"password": "securepassword123"
}{
"user": {
"id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"full_name": "John Doe",
"email": "user@example.com",
"created_at": "2024-11-13T15:30:00",
"updated_at": "2024-11-13T15:30:00"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjc4YjUyMjgtZjhmZC00ZjU0LTk4ODktN2FhYTI3ZDgwNTA1IiwiZW1haWwiOiJpcnZpbjYyNjlAZ21haWwuY29tIiwianRpIjoiZTIzMjM5ZTEtNGQ1NS00ZTc4LWFhOTAtNmQwNmRlNmU0NTE4IiwiZXhwIjoxNzMxNjE3MDQ0fQ.Ke1YhV3NtkHRdlX34tj0-b3xvrla_NQnzRX-Xpa191U",
"expires_at": "2024-12-01T00:00:00Z"
}- URL:
/api/v1/auth/logout - Method:
POST - Description: Logs out the user by revoking their JWT token.
Authorization: Bearer <JWT_TOKEN>{
"message": "Logged out successfully"
}- URL:
/api/v1/accounts - Method:
GET - Description: Fetches all accounts for the authenticated.
Authorization: Bearer <JWT_TOKEN>[
{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"clabe": "0303030303030033",
"balance": "0.00"
},
{
"id": "fcf69be4-b29a-4e15-874d-13feca03668c",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"clabe": "0000101000100101",
"balance": "20000.00"
}
]- URL:
/api/v1/accounts - Method:
POST - Description: Creates a new account for the authenticated user.
Authorization: Bearer <JWT_TOKEN>{
"clabe": "0303030303030033"
}{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"clabe": "0303030303030033",
"balance": "0.00"
}{
"code": 422,
"error": "Esta clabe ya fue registrada anteriormente"
}- URL:
/api/v1/accounts/<account_id> - Method:
GET - Description: Fetches the account with the given ID.
Authorization: Bearer <JWT_TOKEN>{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"clabe": "0303030303030033",
"balance": "0.00"
}- URL:
/api/v1/accounts/<account_id> - Method:
DELETE - Description: Deletes the account with the given ID.
Authorization: Bearer <JWT_TOKEN>{
"status": "Ok",
"message": "Cuenta eliminada correctamente"
}- URL:
/api/v1/accounts/<account_id> - Method:
PUT - Description: Updates the balance of the account with the given ID.
Authorization: Bearer <JWT_TOKEN>{
"balance": 5000.00
}{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"clabe": "0303030303030033",
"balance": "5000.00"
}- URL:
/api/v1/accounts/available-balance - Method:
GET - Description: Fetches the total available balance across all accounts for the authenticated user.
Authorization: Bearer <JWT_TOKEN>{
"amount": "15000.00",
"currency": "MXN"
}- URL:
/api/v1/destination-accounts - Method:
GET - Description: Fetches all destination accounts for the authenticated user.
Authorization: Bearer <JWT_TOKEN>[
{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"account_number": "01002029399393",
"friendly_name": "Miguel",
"holder_full_name": "Pedro Chan Vargas",
"verification_status": "unverified"
},
{
"id": "fcf69be4-b29a-4e15-874d-13feca03668c",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"account_number": "91919191919191",
"friendly_name": "Javier",
"holder_full_name": "Ana Pérez",
"verification_status": "verified"
}
]- URL:
/api/v1/destination-accounts/<account_id> - Method:
GET - Description: Fetches the destination account with the given ID.
Authorization: Bearer <JWT_TOKEN>{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"account_number": "01002029399393",
"friendly_name": "Miguel",
"holder_full_name": "Pedro Chan Vargas",
"verification_status": "unverified"
}- URL:
/api/v1/destination-accounts - Method:
POST - Description: Creates a new destination account for the authenticated user.
Authorization: Bearer <JWT_TOKEN>{
"account_number": "01002029399393",
"friendly_name": "Miguel",
"holder_name": "Pedro Chan Vargas"
}{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"account_number": "01002029399393",
"friendly_name": "Miguel",
"holder_full_name": "Pedro Chan Vargas",
"verification_status": "unverified"
}{
"code": 409,
"error": "Esta cuenta ya fue registrada anteriormente"
}- URL:
/api/v1/destination-accounts/<account_id>/verify - Method:
PUT - Description: Updates the verification status of the destination account.
Authorization: Bearer <JWT_TOKEN>{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"user_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"account_number": "01002029399393",
"friendly_name": "Miguel",
"holder_full_name": "Pedro Chan Vargas",
"verification_status": "verified"
}- URL:
/api/v1/destination-accounts/<account_id> - Method:
DELETE - Description: Deletes the destination account with the given ID.
Authorization: Bearer <JWT_TOKEN>{
"status": "Ok",
"message": "Cuenta eliminada correctamente"
}- URL:
/api/v1/transactions/ - Method:
GET - Description: Fetches all transactions for the authenticated user.
Authorization: Bearer <JWT_TOKEN>[
{
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3",
"origin_account_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"destination_account_id": "fcf69be4-b29a-4e15-874d-13feca03668c",
"amount": "5000.00",
"description": "Transfer between accounts",
"transaction_type": "TRANSFER",
"status": "COMPLETED"
},
{
"id": "9b76fe58-17f7-493e-9eb7-7fa659e4ef34",
"origin_account_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"destination_account_id": "external_account_01",
"amount": "1000.00",
"description": "Transfer to external account",
"transaction_type": "WITHDRAWAL",
"status": "PENDING"
}
]- URL:
/api/v1/transactions/transfer - Method:
POST - Description: Transfers funds between two accounts.
Authorization: Bearer <JWT_TOKEN>{
"origin_account_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"destination_account_id": "fcf69be4-b29a-4e15-874d-13feca03668c",
"amount": "5000.00",
"description": "Transfer between accounts"
}{
"amount": 1.0,
"currency": "MXN",
"description": "Comprate algo bonito",
"id": "c03cbb55-29a9-40b4-b3ee-31c3d99937bc",
"origin_account": {
"balance": "19399.00",
"clabe": "0303030303030033",
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3"
},
"status": "COMPLETED",
"target_account": {
"balance": "201.00",
"clabe": "010101002212",
"id": "69498975-ff6f-4094-a274-32b6f6cabcae"
},
"transaction_type": "TRANSFER"
}{
"code": 404,
"error": "Destination account not found"
}{
"code": 404,
"error": "Origin account not found"
}{
"code": 400,
"error": "Insufficient balance in origin account"
}- URL:
/api/v1/transactions/withdrawal - Method:
POST - Description: Withdraws funds from an account to an external account.
Authorization: Bearer <JWT_TOKEN>{
"origin_account_id": "d519bf26-d125-4c9f-b798-f5b4659c3b06",
"external_destination_account_id": "external_account_01",
"amount": "1000.00",
"description": "Transfer to external account"
}{
"amount": 400.0,
"currency": "MXN",
"description": "Comprate algo bonito",
"destination_external_account": {
"account_number": "010020293993933",
"bank_code": "012",
"bank_name": "BBVA Bancomer",
"friendly_name": "Miguel",
"holder_full_name": "Pedro Chan Vargas",
"id": "3a291235-c2ab-4b72-b91b-2ce7580ec786",
"verification_status": "verified"
},
"id": "1b483276-dfe5-4d47-bdf3-f814d316a5ae",
"origin_account": {
"balance": "18999.00",
"clabe": "0303030303030033",
"id": "8b79da75-2592-4c86-96c8-d5c8091b04a3"
},
"status": "COMPLETED",
"transaction_type": "WITHDRAWAL"
}{
"code": 404,
"error": "Destination account not found"
}{
"code": 404,
"error": "Origin account not found"
}{
"code": 400,
"error": "Insufficient balance in origin account"
}-
Increase Test Coverage: Aim to achieve a higher percentage of code coverage in tests to ensure all critical paths are well-tested and reliable.
-
Add Integration Tests: Implement integration tests to verify that different components of the system work together as expected.
-
Implement Caching Mechanisms: Introduce caching for frequently accessed data to reduce load on the database and improve system performance.
-
Enhance Error Handling: Create a centralized error handling system to manage exceptions with specific codes.
-
Hexagonal Architecture Pattern Restructure the system to adopt the Hexagonal Architecture (or Ports and Adapters) pattern, enhancing modularity, flexibility, and testability. This approach will make the system more resilient to changes in external dependencies, such as databases or third-party APIs.
-
Data validation Enhance data validation across all endpoints to ensure incoming data meets expected formats, constraints, and business rules before processing.
-
CLABE validation Implement validation for bank CLABE (Clave Bancaria Estandarizada) accounts to ensure that only valid bank account numbers are processed.