From 0bb36be24604dce4fd76ab04b21cca00d32dd293 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sun, 23 Mar 2025 14:25:20 -0400 Subject: [PATCH] first commit --- .gitignore | 4 + .idea/.gitignore | 8 + .idea/digifi-BankToProductCore.iml | 8 + .idea/modules.xml | 8 + .idea/php.xml | 19 ++ Dockerfile | 21 +++ README.md | 75 ++++++++ app.py | 6 + app/__init__.py | 24 +++ app/config.py | 10 + app/errors/__init__.py | 1 + app/errors/handlers.py | 14 ++ app/helpers/response_helper.py | 251 ++++++++++++++++++++++++++ app/middlewares/__init__.py | 3 + app/middlewares/app_id_checker.py | 26 +++ app/middlewares/cors.py | 7 + app/middlewares/verify_api_key.py | 25 +++ app/routes/__init__.py | 1 + app/routes/routes.py | 211 ++++++++++++++++++++++ app/schemas/__init__.py | 0 app/schemas/bulk_sms.py | 6 + app/schemas/collect_loan.py | 16 ++ app/schemas/customer_consent.py | 11 ++ app/schemas/disbursement.py | 18 ++ app/schemas/eligibility_check.py | 11 ++ app/schemas/lien_check.py | 8 + app/schemas/loan_information.py | 5 + app/schemas/new_transaction_check.py | 12 ++ app/schemas/notification_callback.py | 14 ++ app/schemas/penal_charge.py | 14 ++ app/schemas/provide_loan.py | 16 ++ app/schemas/rac_check.py | 23 +++ app/schemas/repayment.py | 11 ++ app/schemas/revoke_enable_consent.py | 13 ++ app/schemas/select_offer.py | 13 ++ app/schemas/sms.py | 7 + app/schemas/token_validation.py | 8 + app/schemas/transaction_verify.py | 12 ++ app/services/__init__.py | 16 ++ app/services/collect_loan.py | 59 ++++++ app/services/customer_consent.py | 50 +++++ app/services/disbursement.py | 62 +++++++ app/services/eligibility_check.py | 73 ++++++++ app/services/lien_check.py | 51 ++++++ app/services/loan_information.py | 63 +++++++ app/services/new_transaction_check.py | 58 ++++++ app/services/notification_callback.py | 50 +++++ app/services/penal_charge.py | 51 ++++++ app/services/provide_loan.py | 55 ++++++ app/services/rac_check.py | 63 +++++++ app/services/repayment.py | 50 +++++ app/services/revoke_enable_consent.py | 54 ++++++ app/services/select_offer.py | 107 +++++++++++ app/services/token_validation.py | 54 ++++++ app/services/transaction_verify.py | 57 ++++++ app/static/css/main.css | 0 app/static/js/main.js | 0 app/templates/index.html | 11 ++ app/utils/logger.py | 13 ++ docker-compose.yml | 11 ++ requirements.txt | 13 ++ wsgi.py | 7 + 62 files changed, 1968 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/digifi-BankToProductCore.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/php.xml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 app.py create mode 100644 app/__init__.py create mode 100644 app/config.py create mode 100644 app/errors/__init__.py create mode 100644 app/errors/handlers.py create mode 100644 app/helpers/response_helper.py create mode 100644 app/middlewares/__init__.py create mode 100644 app/middlewares/app_id_checker.py create mode 100644 app/middlewares/cors.py create mode 100644 app/middlewares/verify_api_key.py create mode 100644 app/routes/__init__.py create mode 100644 app/routes/routes.py create mode 100644 app/schemas/__init__.py create mode 100644 app/schemas/bulk_sms.py create mode 100644 app/schemas/collect_loan.py create mode 100644 app/schemas/customer_consent.py create mode 100644 app/schemas/disbursement.py create mode 100644 app/schemas/eligibility_check.py create mode 100644 app/schemas/lien_check.py create mode 100644 app/schemas/loan_information.py create mode 100644 app/schemas/new_transaction_check.py create mode 100644 app/schemas/notification_callback.py create mode 100644 app/schemas/penal_charge.py create mode 100644 app/schemas/provide_loan.py create mode 100644 app/schemas/rac_check.py create mode 100644 app/schemas/repayment.py create mode 100644 app/schemas/revoke_enable_consent.py create mode 100644 app/schemas/select_offer.py create mode 100644 app/schemas/sms.py create mode 100644 app/schemas/token_validation.py create mode 100644 app/schemas/transaction_verify.py create mode 100644 app/services/__init__.py create mode 100644 app/services/collect_loan.py create mode 100644 app/services/customer_consent.py create mode 100644 app/services/disbursement.py create mode 100644 app/services/eligibility_check.py create mode 100644 app/services/lien_check.py create mode 100644 app/services/loan_information.py create mode 100644 app/services/new_transaction_check.py create mode 100644 app/services/notification_callback.py create mode 100644 app/services/penal_charge.py create mode 100644 app/services/provide_loan.py create mode 100644 app/services/rac_check.py create mode 100644 app/services/repayment.py create mode 100644 app/services/revoke_enable_consent.py create mode 100644 app/services/select_offer.py create mode 100644 app/services/token_validation.py create mode 100644 app/services/transaction_verify.py create mode 100644 app/static/css/main.css create mode 100644 app/static/js/main.js create mode 100644 app/templates/index.html create mode 100644 app/utils/logger.py create mode 100644 docker-compose.yml create mode 100644 requirements.txt create mode 100644 wsgi.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b316843 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +.env +app.log +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/digifi-BankToProductCore.iml b/.idea/digifi-BankToProductCore.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/digifi-BankToProductCore.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7d8aea8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..f324872 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fa0ff11 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# Use an official Python runtime as a parent image +FROM python:3.9-slim + +# Set the working directory in the container +WORKDIR /app + +# Copy the current directory contents into the container at /app +COPY . /app + +# Install dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Expose port 5000 for the Flask app +EXPOSE 5000 + +# Set environment variables +ENV FLASK_APP=app.py +ENV FLASK_RUN_HOST=0.0.0.0 + +# Run the application +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "wsgi:wsgi_app"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4025fbf --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Setup + +This guide provides instructions on how to set up and run the application. + +## Prerequisites + +Ensure you have the following installed on your system: +- **Docker**: Install Docker from [docker.com](https://www.docker.com/get-started) +- **Docker Compose**: Docker Compose is included with Docker Desktop (for macOS/Windows) or can be installed separately on Linux. + +## Steps to Set Up the Application + +### 1. Clone the Repository + +First, clone the repository to your local machine: + +```bash +git clone https://github.com/username/repository.git +cd repository +``` + + +### 2. Create a `.env` File + +Before running the application, create a `.env` file in the root directory and add the required environment variables: + +```bash +touch .env +``` + +Then, open the `.env` file and add the following: + +```ini +# Environment Variables +VALID_API_KEY=testtest-api-key-12345 +VALID_APP_ID=app1 +``` + +This ensures that the application uses secure API keys and app IDs. + +### 3. Run the Application with Docker Compose + +Once you have the repository cloned, you can easily set up and run the application using Docker Compose. Simply execute the following command: + +```bash +docker-compose up --build +``` + +This command will build the Docker image and start the Flask application in a container. By default, the application will be accessible at `http://localhost:5000`. + +### 4. Health Check + +You can check if the Flask application is running by accessing the `/health` endpoint. To perform a health check, run the following command: + +```bash +curl http://localhost:7200/health +``` + +If the application is running properly, you should receive a response similar to this: + +```json +{ + "status": "ok" +} +``` + +### 5. Stop the Application + +To stop the application, use: + +```bash +docker-compose down +``` + +This will stop and remove the containers created by Docker Compose. diff --git a/app.py b/app.py new file mode 100644 index 0000000..f61366d --- /dev/null +++ b/app.py @@ -0,0 +1,6 @@ +from app import create_app + +app = create_app() + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..be3109d --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,24 @@ +from flask import Flask +from flask_cors import CORS +from app.config import Config +from app.routes import api +from app.errors import method_not_allowed, unsupported_media_type + +def create_app(): + """ Factory function to create a Flask app instance """ + app = Flask(__name__) + + # Load configuration + app.config.from_object(Config) + + + CORS(app) + + # Register blueprints + app.register_blueprint(api) + + # Error Handlers + app.register_error_handler(405, method_not_allowed) + app.register_error_handler(415, unsupported_media_type) + + return app diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..5e16347 --- /dev/null +++ b/app/config.py @@ -0,0 +1,10 @@ +import os + +class Config: + """Base configuration for Flask app""" + + # SQLALCHEMY_DATABASE_URI = "mysql://root:password@localhost/flask_app" + # SQLALCHEMY_TRACK_MODIFICATIONS = False + # SECRET_KEY = os.environ.get("SECRET_KEY", "your_secret_key") + + DEBUG = True \ No newline at end of file diff --git a/app/errors/__init__.py b/app/errors/__init__.py new file mode 100644 index 0000000..15c380c --- /dev/null +++ b/app/errors/__init__.py @@ -0,0 +1 @@ +from .handlers import method_not_allowed, unsupported_media_type \ No newline at end of file diff --git a/app/errors/handlers.py b/app/errors/handlers.py new file mode 100644 index 0000000..0b4cbdf --- /dev/null +++ b/app/errors/handlers.py @@ -0,0 +1,14 @@ +from flask import jsonify +from app.helpers.response_helper import ResponseHelper + +def method_not_allowed(error): + return jsonify({"message": "Method Not Allowed"}), 405 + +def not_found(error): + return jsonify({"message": "Resource not found"}), 404 + +def bad_request(error): + return jsonify({"message": "Bad Request"}), 400 + +def unsupported_media_type(error): + return jsonify({"message": "Unsupported Media Type"}), 415 diff --git a/app/helpers/response_helper.py b/app/helpers/response_helper.py new file mode 100644 index 0000000..adcc178 --- /dev/null +++ b/app/helpers/response_helper.py @@ -0,0 +1,251 @@ +from flask import jsonify +from typing import List, Dict, Union, Optional, Any + + +class ResponseHelper: + """ + A helper class for building standardized JSON responses in Flask. + """ + + @staticmethod + def build_response( + status: bool, + message: str, + data: Optional[Union[Dict, List, str]] = None, + status_code: int = 200, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Build a standardized JSON response. + + Args: + status (bool): Indicates whether the request was successful. + message (str): A message describing the result of the request. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + status_code (int): The HTTP status code for the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + response = { + "status": status, + "statusCode": status_code, + "message": message, + "data": data if data is not None else {}, + "error": error if error is not None else {}, + } + return jsonify(response), status_code + + @staticmethod + def success( + data: Optional[Union[Dict, List, str]] = None, + message: str = "Successful", + status_code: int = 200, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a success response. + + Args: + data (Optional[Union[Dict, List, str]]): The data to return in the response. + message (str): A message describing the result of the request. + status_code (int): The HTTP status code for the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(True, message, data, status_code, error) + + @staticmethod + def error( + message: str = "An error occurred", + status_code: int = 400, + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return an error response. + + Args: + message (str): A message describing the error. + status_code (int): The HTTP status code for the response. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, status_code, error) + + @staticmethod + def created( + data: Optional[Union[Dict, List, str]] = None, + message: str = "Resource created successfully", + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for a created resource. + + Args: + data (Optional[Union[Dict, List, str]]): The data to return in the response. + message (str): A message describing the result of the request. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(True, message, data, 201, error) + + @staticmethod + def updated( + data: Optional[Union[Dict, List, str]] = None, + message: str = "Resource updated successfully", + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for an updated resource. + + Args: + data (Optional[Union[Dict, List, str]]): The data to return in the response. + message (str): A message describing the result of the request. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(True, message, data, 200, error) + + @staticmethod + def internal_server_error( + message: str = "Internal Server Error", + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for an internal server error. + + Args: + message (str): A message describing the error. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, 500, error) + + @staticmethod + def unauthorized( + message: str = "Unauthorized", + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for an unauthorized request. + + Args: + message (str): A message describing the error. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, 401, error) + + @staticmethod + def forbidden( + message: str = "Forbidden", + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for a forbidden request. + + Args: + message (str): A message describing the error. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, 403, error) + + @staticmethod + def not_found( + message: str = "Resource not found", + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for a not found resource. + + Args: + message (str): A message describing the error. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, 404, error) + + @staticmethod + def unprocessable_entity( + message: str = "Unprocessable entity", + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for an unprocessable entity. + + Args: + message (str): A message describing the error. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, 422, error) + + @staticmethod + def method_not_allowed( + message: str = "Method Not Allowed", + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for a method not allowed error. + + Args: + message (str): A message describing the error. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, 405, error) + + @staticmethod + def bad_request( + message: str = "Bad Request", + data: Optional[Union[Dict, List, str]] = None, + error: Optional[Union[Dict, str]] = None, + ) -> Dict[str, Any]: + """ + Return a response for a bad request error. + + Args: + message (str): A message describing the error. + data (Optional[Union[Dict, List, str]]): The data to return in the response. + error (Optional[Union[Dict, str]]): Any error details to include in the response. + + Returns: + Dict[str, Any]: A dictionary representing the JSON response. + """ + return ResponseHelper.build_response(False, message, data, 400, error) \ No newline at end of file diff --git a/app/middlewares/__init__.py b/app/middlewares/__init__.py new file mode 100644 index 0000000..c439164 --- /dev/null +++ b/app/middlewares/__init__.py @@ -0,0 +1,3 @@ +from .verify_api_key import require_api_key +from .app_id_checker import require_app_id +from .cors import enforce_json \ No newline at end of file diff --git a/app/middlewares/app_id_checker.py b/app/middlewares/app_id_checker.py new file mode 100644 index 0000000..18797b0 --- /dev/null +++ b/app/middlewares/app_id_checker.py @@ -0,0 +1,26 @@ +from functools import wraps +from flask import request, jsonify +from app.utils.logger import logger +import os + +# Load valid App-IDs from environment variables (comma-separated list) +VALID_APP_ID = os.getenv("VALID_APP_ID", "app1,app2,app3").split(",") + +def require_app_id(f): + """Decorator to enforce App-ID validation.""" + @wraps(f) + def decorated_function(*args, **kwargs): + app_id = request.headers.get("App-ID") + + if not app_id: + logger.error("Unauthorized access: Missing App-ID.") + return jsonify({"message": "Invalid request parameters"}), 400 + + + if app_id not in VALID_APP_ID: + logger.error(f"Unauthorized access: Invalid App-ID {app_id}.") + return jsonify({"message": "Invalid request parameters"}), 400 + + return f(*args, **kwargs) + + return decorated_function diff --git a/app/middlewares/cors.py b/app/middlewares/cors.py new file mode 100644 index 0000000..7df0844 --- /dev/null +++ b/app/middlewares/cors.py @@ -0,0 +1,7 @@ +from flask import request, jsonify + + +def enforce_json(): + """Middleware to enforce JSON Content-Type for incoming requests""" + if request.method in ["POST", "PUT", "PATCH"] and request.content_type != "application/json": + return jsonify({"message": "Invalid request parameters"}), 400 diff --git a/app/middlewares/verify_api_key.py b/app/middlewares/verify_api_key.py new file mode 100644 index 0000000..81644b1 --- /dev/null +++ b/app/middlewares/verify_api_key.py @@ -0,0 +1,25 @@ +from functools import wraps +from flask import request, jsonify +from app.utils.logger import logger +import os + +# Load valid API key from environment variables (fallback for testing) +VALID_API_KEY = os.getenv("VALID_API_KEY", "test-api-key-12345") + +def require_api_key(f): + """Decorator to enforce API key authentication.""" + @wraps(f) + def decorated_function(*args, **kwargs): + api_key = request.headers.get("X-API-KEY") + + if not api_key: + logger.error("Unauthorized access: Missing API key.") + return jsonify({"message": "Invalid request parameters"}), 400 + + if api_key != VALID_API_KEY: + logger.error("Unauthorized access: Invalid API key.") + return jsonify({"message": "Invalid request parameters"}), 400 + + return f(*args, **kwargs) + + return decorated_function diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 0000000..4ebb14a --- /dev/null +++ b/app/routes/__init__.py @@ -0,0 +1 @@ +from .routes import api diff --git a/app/routes/routes.py b/app/routes/routes.py new file mode 100644 index 0000000..2c75412 --- /dev/null +++ b/app/routes/routes.py @@ -0,0 +1,211 @@ +from flask import Blueprint, request, jsonify +from app.services import ( + EligibilityCheckService, + SelectOfferService, + ProvideLoanService, + LoanInformationService, + RepaymentService, + CustomerConsentService, + NotificationCallbackService, + RACCheckService, + DisbursementService, + CollectLoanService, + TransactionVerifyService, + PenalChargeService, + RevokeEnableConsentService, + TokenValidationService, + LienCheckService, + NewTransactionCheckService +) +from app.utils.logger import logger +from app.middlewares import require_api_key, require_app_id, enforce_json + + +api = Blueprint("api", __name__) + + +@api.before_request +def cors_middleware(): + """Middleware applied globally to all API routes in this blueprint""" + return enforce_json() + + +# EligibilityCheck Endpoint +@api.route('/EligibilityCheck', methods=['POST']) +@require_api_key +@require_app_id +def eligibility_check(): + data = request.get_json() + # logger.info(f"EligibilityCheck request received: {data}") + response = EligibilityCheckService.process_request(data) + return response + +# SelectOffer Endpoint +@api.route('/SelectOffer', methods=['POST']) +@require_api_key +@require_app_id +def select_offer(): + data = request.get_json() + # logger.info(f"SelectOffer request received: {data}") + response = SelectOfferService.process_request(data) + return response + + +# ProvideLoan Endpoint +@api.route('/ProvideLoan', methods=['POST']) +@require_api_key +@require_app_id +def provide_loan(): + data = request.get_json() + # logger.info(f"ProvideLoan request received: {data}") + response = ProvideLoanService.process_request(data) + return response + + +# LoanInformation Endpoint +@api.route('/LoanInformation', methods=['GET']) +@require_api_key +@require_app_id +def loan_information(): + data = request.args.to_dict() + # logger.info(f"LoanInformation request received: {data}") + response = LoanInformationService.process_request(data) + return response + + +# Repayment Endpoint +@api.route('/Repayment', methods=['POST']) +@require_api_key +@require_app_id +def repayment(): + data = request.get_json() + # logger.info(f"Repayment request received: {data}") + response = RepaymentService.process_request(data) + return response + + +# CustomerConsent Endpoint +@api.route('/CustomerConsent', methods=['POST']) +@require_api_key +@require_app_id +def customer_consent(): + data = request.get_json() + # logger.info(f"CustomerConsent request received: {data}") + response = CustomerConsentService.process_request(data) + return response + + +# NotificationCallback Endpoint +@api.route('/NotificationCallback', methods=['POST']) +@require_api_key +@require_app_id +def notification_callback(): + data = request.get_json() + # logger.info(f"NotificationCallback request received: {data}") + response = NotificationCallbackService.process_request(data) + return response + + +# RACCheck Endpoint +@api.route('/RACCheck', methods=['POST']) +@require_api_key +@require_app_id +def rac_check(): + data = request.get_json() + # logger.info(f"RACCheck request received: {data}") + response = RACCheckService.process_request(data) + return response + + +# Disbursement Endpoint +@api.route('/Disbursement', methods=['POST']) +@require_api_key +@require_app_id +def disbursement(): + data = request.get_json() + # logger.info(f"Disbursement request received: {data}") + response = DisbursementService.process_request(data) + return response + + +# CollectLoan Endpoint +@api.route('/CollectLoan', methods=['POST']) +@require_api_key +@require_app_id +def collect_loan(): + data = request.get_json() + # logger.info(f"CollectLoan request received: {data}") + response = CollectLoanService.process_request(data) + return response + + +# TransactionVerify Endpoint +@api.route('/TransactionVerify', methods=['POST']) +@require_api_key +@require_app_id +def transaction_verify(): + data = request.get_json() + # logger.info(f"TransactionVerify request received: {data}") + response = TransactionVerifyService.process_request(data) + return response + + +# PenalCharge Endpoint +@api.route('/PenalCharge', methods=['POST']) +@require_api_key +@require_app_id +def penal_charge(): + data = request.get_json() + # logger.info(f"PenalCharge request received: {data}") + response = PenalChargeService.process_request(data) + return response + + +# RevokeEnableConsent Endpoint +@api.route('/RevokeEnableConsent', methods=['POST']) +@require_api_key +@require_app_id +def revoke_enable_consent(): + data = request.get_json() + # logger.info(f"RevokeEnableConsent request received: {data}") + response = RevokeEnableConsentService.process_request(data) + return response + + +# TokenValidation Endpoint +@api.route('/TokenValidation', methods=['POST']) +@require_api_key +@require_app_id +def token_validation(): + data = request.get_json() + # logger.info(f"TokenValidation request received: {data}") + response = TokenValidationService.process_request(data) + return response + + +# LienCheck Endpoint +@api.route('/LienCheck', methods=['POST']) +@require_api_key +@require_app_id +def lien_check(): + data = request.get_json() + # logger.info(f"LienCheck request received: {data}") + response = LienCheckService.process_request(data) + return response + + +# NewTransactionCheck Endpoint +@api.route('/NewTransactionCheck', methods=['POST']) +@require_api_key +@require_app_id +def new_transaction_check(): + data = request.get_json() + # logger.info(f"NewTransactionCheck request received: {data}") + response = NewTransactionCheckService.process_request(data) + return response + + +# Health Check Endpoint +@api.route('/health', methods=['GET']) +def health_check(): + return {"status": "ok"} , 200 \ No newline at end of file diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/bulk_sms.py b/app/schemas/bulk_sms.py new file mode 100644 index 0000000..40c2465 --- /dev/null +++ b/app/schemas/bulk_sms.py @@ -0,0 +1,6 @@ +from marshmallow import Schema, fields +from .sms import SMSSchema + +# Bulk SMS Schema +class BulkSMSSchema(Schema): + requests = fields.List(fields.Nested(SMSSchema), required=True) \ No newline at end of file diff --git a/app/schemas/collect_loan.py b/app/schemas/collect_loan.py new file mode 100644 index 0000000..fb5bc76 --- /dev/null +++ b/app/schemas/collect_loan.py @@ -0,0 +1,16 @@ +from marshmallow import Schema, fields + +# Collect Loan Schema +class CollectLoanSchema(Schema): + transactionId = fields.Str(required=True) + fbnTransactionId = fields.Str(required=True) + debtId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + productId = fields.Str(required=True) + collectAmount = fields.Float(required=True) + penalCharge = fields.Float(required=False) # Optional + collectionMethod = fields.Int(required=True) + lienAmount = fields.Float(required=True) + countryId = fields.Str(required=True) + comment = fields.Str(required=False) # Optional \ No newline at end of file diff --git a/app/schemas/customer_consent.py b/app/schemas/customer_consent.py new file mode 100644 index 0000000..09c2d9c --- /dev/null +++ b/app/schemas/customer_consent.py @@ -0,0 +1,11 @@ +from marshmallow import Schema, fields + +# Customer Consent Schema +class CustomerConsentSchema(Schema): + type = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + requestTime = fields.DateTime(required=True, format="%Y-%m-%d %H:%M:%S.%f") + consentType = fields.Str(required=True) + channel = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/disbursement.py b/app/schemas/disbursement.py new file mode 100644 index 0000000..5f4bead --- /dev/null +++ b/app/schemas/disbursement.py @@ -0,0 +1,18 @@ +from marshmallow import Schema, fields + +# Disbursement Schema +class DisbursementSchema(Schema): + requestId = fields.Str(required=True) + debtId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + productId = fields.Str(required=True) + provideAmount = fields.Float(required=True) + collectAmountInterest = fields.Float(required=False) # Optional + collectAmountMgtFee = fields.Float(required=True) + collectAmountInsurance = fields.Float(required=True) + collectAmountVAT = fields.Float(required=True) + countryId = fields.Str(required=True) + comment = fields.Str(required=False) # Optional + \ No newline at end of file diff --git a/app/schemas/eligibility_check.py b/app/schemas/eligibility_check.py new file mode 100644 index 0000000..016168c --- /dev/null +++ b/app/schemas/eligibility_check.py @@ -0,0 +1,11 @@ +from marshmallow import Schema, fields + +class EligibilityCheckSchema(Schema): + type = fields.Str(required=True) + transactionId = fields.Str(required=True) + countryCode = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + msisdn = fields.Str(required=True) + lienAmount = fields.Float(required=True) + channel = fields.Str(required=True) diff --git a/app/schemas/lien_check.py b/app/schemas/lien_check.py new file mode 100644 index 0000000..7a0912a --- /dev/null +++ b/app/schemas/lien_check.py @@ -0,0 +1,8 @@ +from marshmallow import Schema, fields + +# Lien Check Schema +class LienCheckSchema(Schema): + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + countryId = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/loan_information.py b/app/schemas/loan_information.py new file mode 100644 index 0000000..4d9b5ab --- /dev/null +++ b/app/schemas/loan_information.py @@ -0,0 +1,5 @@ +from marshmallow import Schema, fields + +# Loan Information Schema +class LoanInformationSchema(Schema): + loan_id = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/new_transaction_check.py b/app/schemas/new_transaction_check.py new file mode 100644 index 0000000..9e79864 --- /dev/null +++ b/app/schemas/new_transaction_check.py @@ -0,0 +1,12 @@ +from marshmallow import Schema, fields + +# New Transaction Check Schema +class NewTransactionCheckSchema(Schema): + transactionId = fields.Str(required=True) + debtId = fields.Str(required=True) + transactionType = fields.Str(required=True, metadata={ + "allowed_values": ["Disbursement", "Collection", "PenalCharge"] + }) + fbnTransactionId = fields.Str(required=True) + origTransactionId = fields.Str(required=True) + customerId = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/notification_callback.py b/app/schemas/notification_callback.py new file mode 100644 index 0000000..19e6b4d --- /dev/null +++ b/app/schemas/notification_callback.py @@ -0,0 +1,14 @@ +from marshmallow import Schema, fields + +# Notification Callback Schema +class NotificationCallbackSchema(Schema): + fbnTransactionId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + debtId = fields.Str(required=True) + transactionType = fields.Str(required=True) + amountProvided = fields.Float(required=True) + amountCollected = fields.Float(required=True) + responseCode = fields.Str(required=True) + responseDescription = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/penal_charge.py b/app/schemas/penal_charge.py new file mode 100644 index 0000000..40d6d0a --- /dev/null +++ b/app/schemas/penal_charge.py @@ -0,0 +1,14 @@ +from marshmallow import Schema, fields + + +# Penal Charge Schema +class PenalChargeSchema(Schema): + transactionId = fields.Str(required=True) + fbnTransactionId = fields.Str(required=True) + debtId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + penalCharge = fields.Decimal(required=True) + lienAmount = fields.Decimal(required=True) + countryId = fields.Str(required=True) + comment = fields.Str(required=False) diff --git a/app/schemas/provide_loan.py b/app/schemas/provide_loan.py new file mode 100644 index 0000000..ca82352 --- /dev/null +++ b/app/schemas/provide_loan.py @@ -0,0 +1,16 @@ +from marshmallow import Schema, fields + +# Provide Loan Schema +class ProvideLoanSchema(Schema): + type = fields.Str(required=True) + requestId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + msisdn = fields.Str(required=False) + productId = fields.Str(required=True) + lienAmount = fields.Float(required=True) + requestedAmount = fields.Float(required=True) + collectionType = fields.Int(required=True) + loanType = fields.Int(required=True) + channel = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/rac_check.py b/app/schemas/rac_check.py new file mode 100644 index 0000000..3b858d2 --- /dev/null +++ b/app/schemas/rac_check.py @@ -0,0 +1,23 @@ +from marshmallow import Schema, fields + +class RACItemSchema(Schema): + salaryAccount = fields.Bool(required=True) + bvn = fields.Str(required=True) + crc = fields.Bool(required=True) + crms = fields.Bool(required=True) + accountStatus = fields.Str(required=True) + lien = fields.Bool(required=True) + noBouncedCheck = fields.Bool(required=True) + existingLoan = fields.Bool(required=True) + whitelist = fields.Bool(required=True) + noPastDueSalaryLoan = fields.Bool(required=True) + noPastDueOtherLoans = fields.Bool(required=True) + + +# RAC Check Schema +class RACCheckSchema(Schema): + transactionId = fields.Str(required=True) + fbnTransactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + RAC_Array = fields.List(fields.Nested(RACItemSchema), required=True) \ No newline at end of file diff --git a/app/schemas/repayment.py b/app/schemas/repayment.py new file mode 100644 index 0000000..535419c --- /dev/null +++ b/app/schemas/repayment.py @@ -0,0 +1,11 @@ +from marshmallow import Schema, fields + +# Repayment Schema +class RepaymentSchema(Schema): + type = fields.Str(required=True) + msisdn = fields.Str(required=False) #optional + debtId = fields.Str(required=True) + productId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + channel = fields.Str(required=True) diff --git a/app/schemas/revoke_enable_consent.py b/app/schemas/revoke_enable_consent.py new file mode 100644 index 0000000..ce1a5f5 --- /dev/null +++ b/app/schemas/revoke_enable_consent.py @@ -0,0 +1,13 @@ +from marshmallow import Schema, fields + + +# Revoke Enable Consent Schema +class RevokeEnableConsentSchema(Schema): + transactionId = fields.Str(required=True) + fbnTransactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + processTime = fields.DateTime(required=True) + consentType = fields.Str(required=True) + countryId = fields.Str(required=True) + comment = fields.Str(required=False) diff --git a/app/schemas/select_offer.py b/app/schemas/select_offer.py new file mode 100644 index 0000000..f3544aa --- /dev/null +++ b/app/schemas/select_offer.py @@ -0,0 +1,13 @@ +from marshmallow import Schema, fields + +# Select Offer Schema +class SelectOfferSchema(Schema): + requestId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + msisdn = fields.Str(required=True) + requestedAmount = fields.Float(required=True) + productId = fields.Str(required=True) + channel = fields.Str(required=True) + diff --git a/app/schemas/sms.py b/app/schemas/sms.py new file mode 100644 index 0000000..e8209c4 --- /dev/null +++ b/app/schemas/sms.py @@ -0,0 +1,7 @@ +from marshmallow import Schema, fields + +# SMS Schema +class SMSSchema(Schema): + text = fields.Str(required=True) + dest = fields.Str(required=True) + unicode = fields.Bool(required=True) \ No newline at end of file diff --git a/app/schemas/token_validation.py b/app/schemas/token_validation.py new file mode 100644 index 0000000..c8e067b --- /dev/null +++ b/app/schemas/token_validation.py @@ -0,0 +1,8 @@ +from marshmallow import Schema, fields + +# Token Validation Schema +class TokenValidationSchema(Schema): + RequestId = fields.Str(required=True) + UserId = fields.Str(required=True) + CountryId = fields.Str(required=True) + TokenCode = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/transaction_verify.py b/app/schemas/transaction_verify.py new file mode 100644 index 0000000..021ded4 --- /dev/null +++ b/app/schemas/transaction_verify.py @@ -0,0 +1,12 @@ +from marshmallow import Schema, fields + + +# Transaction Verify Schema +class TransactionVerifySchema(Schema): + counter = fields.Str(required=True) + TransactionId = fields.Str(required=True) + requestID = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + countryId = fields.Str(required=True) # Static value “01” + transactionType = fields.Str(required=True) \ No newline at end of file diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..9a1af9f --- /dev/null +++ b/app/services/__init__.py @@ -0,0 +1,16 @@ +from app.services.eligibility_check import EligibilityCheckService +from app.services.select_offer import SelectOfferService +from app.services.provide_loan import ProvideLoanService +from app.services.loan_information import LoanInformationService +from app.services.repayment import RepaymentService +from app.services.customer_consent import CustomerConsentService +from app.services.notification_callback import NotificationCallbackService +from app.services.rac_check import RACCheckService +from app.services.disbursement import DisbursementService +from app.services.collect_loan import CollectLoanService +from app.services.transaction_verify import TransactionVerifyService +from app.services.penal_charge import PenalChargeService +from app.services.revoke_enable_consent import RevokeEnableConsentService +from app.services.token_validation import TokenValidationService +from app.services.lien_check import LienCheckService +from app.services.new_transaction_check import NewTransactionCheckService \ No newline at end of file diff --git a/app/services/collect_loan.py b/app/services/collect_loan.py new file mode 100644 index 0000000..ec65b5a --- /dev/null +++ b/app/services/collect_loan.py @@ -0,0 +1,59 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.collect_loan import CollectLoanSchema + +class CollectLoanService: + @staticmethod + def process_request(data): + """ + Process the CollectLoan request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing CollectLoan request") + + # Validate input data using CollectLoanSchema + schema = CollectLoanSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "transactionId": "T002", + "debtId": "273194670", + "customerId": "CN621868", + "accountId": "2017821799", + "productId": "101", + "collectAmount": 60000.00, + "penalCharge": 0, + "lienAmount": 20000, + "countryId": "01", + "comment": "Testing CollectionLoanRequest", + "resultCode": "00", + "resultDescription": "Loan Collection Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Loan collection completed successfully" + # ) + return jsonify(response_data), 200 + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/customer_consent.py b/app/services/customer_consent.py new file mode 100644 index 0000000..f37aa47 --- /dev/null +++ b/app/services/customer_consent.py @@ -0,0 +1,50 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.schemas.customer_consent import CustomerConsentSchema + + +class CustomerConsentService: + @staticmethod + def process_request(data): + """ + Process the CustomerConsent request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing CustomerConsent request") + + # Validate input data using the CustomerConsent schema + schema = CustomerConsentSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "resultCode": "00", + "resultDescription": "Request is received" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Customer consent processed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/disbursement.py b/app/services/disbursement.py new file mode 100644 index 0000000..4005077 --- /dev/null +++ b/app/services/disbursement.py @@ -0,0 +1,62 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.disbursement import DisbursementSchema + +class DisbursementService: + @staticmethod + def process_request(data): + """ + Process the Disbursement request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing Disbursement request") + + # Validate input data using DisbursementSchema + schema = DisbursementSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "transactionId": "T001", + "TransactionId": "Tr201712RK9232P115", + "debtId": "273194670", + "customerId": "CN621868", + "accountId": "2017821799", + "productId": "101", + "provideAmount": 100000.0, + "collectAmountInterest": 5000, + "collectAmountMgtFee": 1000, + "collectAmountInsurance": 1000, + "collectAmountVAT": 75, + "countryId": "01", + "resultCode": "00", + "resultDescription": "Loan Request Completed Successfully!" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Disbursement completed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/eligibility_check.py b/app/services/eligibility_check.py new file mode 100644 index 0000000..a450607 --- /dev/null +++ b/app/services/eligibility_check.py @@ -0,0 +1,73 @@ +from flask import session, jsonify +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.eligibility_check import EligibilityCheckSchema +from marshmallow import ValidationError + +class EligibilityCheckService: + @staticmethod + def process_request(data): + """ + Process the EligibilityCheck request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing EligibilityCheck request") + + # Validate input data using Schema + schema = EligibilityCheckSchema() + validated_data = schema.load(data) # Raises an error if invalid + + # Example: Validate data, perform calculations, etc. + if not data: + return ResponseHelper.error("Invalid input data", status_code=400) + + # Simulate processing + response_data = { + "customerId": "CN621868", + "transactionId": "Tr201712RK9232P115", + "msisdn": "3451342", + "eligibleOffers": [ + { + "minamount": 5000, + "maxamount": 20000, + "productId": 101, + "offerid": 101, + "Tenor": 30 + }, + { + "minamount": 20000, + "maxamount": 50000, + "productId": 102, + "offerid": 102, + "Tenor": 60 + } + ], + "resultCode": "00", + "resultDescription": "Successful" + } + + + # Return a success response + # return ResponseHelper.success( + # data=response_data, + # message="Eligibility check completed successfully" + # ) + + return response_data + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 \ No newline at end of file diff --git a/app/services/lien_check.py b/app/services/lien_check.py new file mode 100644 index 0000000..0fbcd5d --- /dev/null +++ b/app/services/lien_check.py @@ -0,0 +1,51 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.lien_check import LienCheckSchema + +class LienCheckService: + @staticmethod + def process_request(data): + """ + Process the LienCheck request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing LienCheck request") + + # Validate input data using LienCheckSchema + schema = LienCheckSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated lien check logic + response_data = { + "lienAmount": 20000.0, + "resultCode": "00", + "resultDescription": "Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Lien check completed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/loan_information.py b/app/services/loan_information.py new file mode 100644 index 0000000..6136cd1 --- /dev/null +++ b/app/services/loan_information.py @@ -0,0 +1,63 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.loan_information import LoanInformationSchema + +class LoanInformationService: + @staticmethod + def process_request(data): + """ + Process the Loan Information request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing LoanInformation request") + + # Validate input data using the imported schema + schema = LoanInformationSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "customerId": "CN621868", + "loans": [ + { + "debtId": "123456789", + "loanDate": "2019-10-18 14:26:21.063", + "dueDate": "2019-11-20 14:26:21.063", + "currentLoanAmount": 8500.0, + "initialLoanAmount": 10000.0, + "defaultFee": 0.0, + "continuousFee": 0.0, + "productId": "101" + } + ], + "resultCode": "00", + "resultDescription": "Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Loan information retrieved successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/new_transaction_check.py b/app/services/new_transaction_check.py new file mode 100644 index 0000000..0eeac65 --- /dev/null +++ b/app/services/new_transaction_check.py @@ -0,0 +1,58 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.new_transaction_check import NewTransactionCheckSchema + +class NewTransactionCheckService: + @staticmethod + def process_request(data): + """ + Process the NewTransactionCheck request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing NewTransactionCheck request") + + # Validate input data using NewTransactionCheckSchema + schema = NewTransactionCheckSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated transaction check logic + response_data = { + "transactionId": "24110114545374721", + "data": { + "transactionId": "241101", + "providedAmount": 1000.00, + "collectedAmount": 0.00, + "resultCode": "00", + "resultDescription": "Loan Provision is successful" + }, + "resultCode": "00", + "resultDescription": "SUCCESS" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="New transaction check completed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/notification_callback.py b/app/services/notification_callback.py new file mode 100644 index 0000000..c587a9c --- /dev/null +++ b/app/services/notification_callback.py @@ -0,0 +1,50 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.notification_callback import NotificationCallbackSchema + +class NotificationCallbackService: + @staticmethod + def process_request(data): + """ + Process the NotificationCallback request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing NotificationCallback request") + + # Validate input data using the NotificationCallback schema + schema = NotificationCallbackSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "resultCode": "00", + "resultDescription": "Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Notification callback processed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/penal_charge.py b/app/services/penal_charge.py new file mode 100644 index 0000000..7b1d1ad --- /dev/null +++ b/app/services/penal_charge.py @@ -0,0 +1,51 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.penal_charge import PenalChargeSchema + + +class PenalChargeService: + @staticmethod + def process_request(data): + """ + Process the PenalCharge request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing PenalCharge request") + + # Validate input data using PenalChargeSchema + schema = PenalChargeSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "resultCode": "00", + "resultDescription": "Penal charge debited successfully" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Penal charge applied successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/provide_loan.py b/app/services/provide_loan.py new file mode 100644 index 0000000..b5b5396 --- /dev/null +++ b/app/services/provide_loan.py @@ -0,0 +1,55 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.provide_loan import ProvideLoanSchema + +class ProvideLoanService: + @staticmethod + def process_request(data): + """ + Process the ProvideLoan request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing ProvideLoan request") + + # Validate input data using the imported schema + schema = ProvideLoanSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Business logic - providing a loan + response_data ={ + "requestId": "202111170001371256908", + "transactionId": "Tr201712RK9232P115", + "customerId": "CN621868", + "accountId": "ACN8263457", + "msisdn": "3451342", + "resultCode": "00", + "resultDescription": "Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Loan successfully provided" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/rac_check.py b/app/services/rac_check.py new file mode 100644 index 0000000..ea7eaa3 --- /dev/null +++ b/app/services/rac_check.py @@ -0,0 +1,63 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.rac_check import RACCheckSchema + +class RACCheckService: + @staticmethod + def process_request(data): + """ + Process the RACCheck request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing RACCheck request") + + # Validate input data using RACCheckSchema + schema = RACCheckSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "resultCode": "00", + "RACResponse": { + "SalaryAccount": "1", + "BVN": "1", + "BVNAttachedToAccount": "1", + "CRMS": "1", + "CRC": "1", + "AccountStatus": "1", + "Lien": "1", + "NoBouncedCheck": "1", + "Whitelist": "1", + "NoPastDueSalaryLoan": "1", + "NoPastDueOtherLoan": "1" + }, + "resultDescription": "RAC Check Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="RAC check completed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/repayment.py b/app/services/repayment.py new file mode 100644 index 0000000..472890d --- /dev/null +++ b/app/services/repayment.py @@ -0,0 +1,50 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.repayment import RepaymentSchema + +class RepaymentService: + @staticmethod + def process_request(data): + """ + Process the Repayment request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing Repayment request") + + # Validate input data using the Repayment schema + schema = RepaymentSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "repayment_id": "67890", + "status": "Paid", + "amount": validated_data.get("amount", 0), # Example: Use validated field + } + + # return ResponseHelper.success( + # data=response_data, + # message="Repayment processed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/revoke_enable_consent.py b/app/services/revoke_enable_consent.py new file mode 100644 index 0000000..fffb5af --- /dev/null +++ b/app/services/revoke_enable_consent.py @@ -0,0 +1,54 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.revoke_enable_consent import RevokeEnableConsentSchema + + +class RevokeEnableConsentService: + @staticmethod + def process_request(data): + """ + Process the RevokeEnableConsent request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing RevokeEnableConsent request") + + # Validate input data using RevokeEnableConsentSchema + schema = RevokeEnableConsentSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "type": "RevokeEnableConsentResponse", + "customerId": "CN621868", + "accountId": "2017821799", + "resultCode": "00", + "resultDescription": "Success" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Consent revocation processed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/select_offer.py b/app/services/select_offer.py new file mode 100644 index 0000000..c0e12e0 --- /dev/null +++ b/app/services/select_offer.py @@ -0,0 +1,107 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.select_offer import SelectOfferSchema + +class SelectOfferService: + @staticmethod + def process_request(data): + """ + Process the SelectOffer request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing SelectOffer request") + + # Validate input data using the imported schema + schema = SelectOfferSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + offers = [ + { + "offerId": "14451", + "productId": "2030", + "amount": 10000.0, + "upfrontPayment": 1000.0, + "interestRate": 3.0, + "managementRate": 1.0, + "managementFee": 1.0, + "insuranceRate": 1.0, + "insuranceFee": 100.0, + "vatRate": 7.5, + "vatAmount": 100.0, + "recommendedRepaymentDates": ["2022-11-30"], + "installmentAmount": 11000.0, + "totalRepaymentAmount": 11000.0 + }, + { + "offerId": "16645", + "productId": "2060", + "amount": 10000.0, + "upfrontPayment": 0.0, + "interestRate": 3.0, + "managementRate": 1.0, + "managementFee": 1.0, + "insuranceRate": 1.0, + "insuranceFee": 100.0, + "vatRate": 7.5, + "vatAmount": 100.0, + "recommendedRepaymentDates": ["2022-11-30", "2023-12-30"], + "installmentAmount": 5761.9, + "totalRepaymentAmount": 11523.8 + }, + { + "offerId": "122212", + "productId": "2090", + "amount": 10000.0, + "upfrontPayment": 0.0, + "interestRate": 10.0, + "managementRate": 1.0, + "managementFee": 1.0, + "insuranceRate": 1.0, + "insuranceFee": 100.0, + "vatRate": 7.5, + "vatAmount": 100.0, + "recommendedRepaymentDates": ["2022-11-30", "2022-12-30", "2023-01-29"], + "installmentAmount": 4021.15, + "totalRepaymentAmount": 12063.45 + } + ] + + # Business logic - selecting an offer + response_data = { + "outstandingDebtAmount": 0, + "requestId": "202111170001371256908", + "transactionId": "1231231321232", + "customerId": "1256907", + "accountId": "5948306019", + "offers": offers, + "resultCode": "00", + "resultDescription": "Successful" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Offer selection completed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/token_validation.py b/app/services/token_validation.py new file mode 100644 index 0000000..3226f23 --- /dev/null +++ b/app/services/token_validation.py @@ -0,0 +1,54 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.token_validation import TokenValidationSchema + + +class TokenValidationService: + @staticmethod + def process_request(data): + """ + Process the TokenValidation request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing TokenValidation request") + + # Validate input data using TokenValidationSchema + schema = TokenValidationSchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated token validation logic + response_data = { + "Authenticated": True, + "AuthenticatedMessage": "The user Oluwole Olusoga has successfully authenticated!", + "ResponseCode": "00", + "ResponseMessage": "Successful", + "RequestId": "SMB1234567" + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Token validation completed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/services/transaction_verify.py b/app/services/transaction_verify.py new file mode 100644 index 0000000..2232835 --- /dev/null +++ b/app/services/transaction_verify.py @@ -0,0 +1,57 @@ +from flask import request, jsonify +from marshmallow import ValidationError +from app.utils.logger import logger +from app.helpers.response_helper import ResponseHelper +from app.schemas.transaction_verify import TransactionVerifySchema + + +class TransactionVerifyService: + @staticmethod + def process_request(data): + """ + Process the TransactionVerify request. + + Args: + data (dict): The request data. + + Returns: + dict: A standardized response. + """ + try: + logger.info("Processing TransactionVerify request") + + # Validate input data using TransactionVerifySchema + schema = TransactionVerifySchema() + validated_data = schema.load(data) # Raises ValidationError if invalid + + # Simulated processing logic + response_data = { + "type": "TransactionCheckResponse", + "nativeId": "FBN20191031104405CN621868", + "customerId": "CN621868", + "accountId": "2017821799", + "providedAmount": 0.0, + "collectedAmount": 7.50, + "resultCode": "00", + "resultDescription": "Collect Status retrieved successfully." + } + + + # return ResponseHelper.success( + # data=response_data, + # message="Transaction verification completed successfully" + # ) + + return response_data + + except ValidationError as err: + logger.error(f"Validation Error: {err.messages}") + return jsonify({ + "message": "Validation exception" + }) , 422 + + except Exception as e: + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/static/css/main.css b/app/static/css/main.css new file mode 100644 index 0000000..e69de29 diff --git a/app/static/js/main.js b/app/static/js/main.js new file mode 100644 index 0000000..e69de29 diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..f29a4e2 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/utils/logger.py b/app/utils/logger.py new file mode 100644 index 0000000..4ee0e98 --- /dev/null +++ b/app/utils/logger.py @@ -0,0 +1,13 @@ +import logging + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", + handlers=[ + # logging.StreamHandler(), + logging.FileHandler("app.log", mode='a') # Log to file + ] +) + +logger = logging.getLogger("DetectionService") diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b4d292a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + digifi-flaska002: + build: . + ports: + - "7200:5000" + environment: + - FLASK_APP=app.py + - FLASK_RUN_HOST=0.0.0.0 + volumes: + - .:/app + restart: always diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..737f663 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +# Flask and Extensions +Flask==2.3.3 +Flask-Marshmallow==0.15.0 +marshmallow==3.19.0 +Flask-Cors==3.0.10 +gunicorn + + + + +# Logging (Python Standard Library, for reference) + + diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..49f70e1 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,7 @@ +from app import create_app + +app = create_app() + +if __name__ != "__main__": + # Expose WSGI app instance for Gunicorn + wsgi_app = app