From f52de3d8f8b8fc18de7f8551063a8f3aa47e438c Mon Sep 17 00:00:00 2001 From: Azeez Muibi Date: Wed, 26 Mar 2025 15:05:52 +0100 Subject: [PATCH] Updated Disbursement --- .idea/digifi-BankEmulator.iml | 2 +- .idea/misc.xml | 7 +++ app/api/routes/routes.py | 30 ++++++++++-- app/api/schemas/disbursement.py | 51 +++++++++++++------ app/api/services/__init__.py | 2 + app/api/services/disbursement.py | 54 +++++++++++--------- app/api/services/repayment.py | 64 ++++++++++++++++++++++++ app/api/services/status_call.py | 84 ++++++++++++++++++++++++++++++++ 8 files changed, 249 insertions(+), 45 deletions(-) create mode 100644 .idea/misc.xml create mode 100644 app/api/services/repayment.py create mode 100644 app/api/services/status_call.py diff --git a/.idea/digifi-BankEmulator.iml b/.idea/digifi-BankEmulator.iml index c956989..6e6fb1f 100644 --- a/.idea/digifi-BankEmulator.iml +++ b/.idea/digifi-BankEmulator.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1d3ce46 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index 01b6092..767a86a 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -1,4 +1,4 @@ -from flask import Flask, Blueprint, request, jsonify, send_from_directory +from flask import Flask, Blueprint, request, jsonify, send_from_directory import os from app.api.services import ( RACCheckService, @@ -10,9 +10,11 @@ from app.api.services import ( TokenValidationService, LienCheckService, NewTransactionCheckService, + RepaymentService, # Added RepaymentService + StatusCallService, # Added StatusCallService ) from app.utils.logger import logger -from app.api.middlewares import require_api_key, require_app_id, enforce_json +from app.api.middlewares import require_api_key, require_app_id, enforce_json api = Blueprint("api", __name__) @@ -32,7 +34,6 @@ def swagger_json(): return send_from_directory(swagger_dir, "digifi_swagger.json") - @api.route('/swagger/') def serve_paths(filename): swagger_dir = os.path.join("swagger") @@ -53,7 +54,6 @@ def rac_check(): @require_api_key @require_app_id def disbursement(): - data = request.get_json() # logger.info(f"Disbursement request received: {data}") response = DisbursementService.process_request(data) @@ -129,7 +129,27 @@ def new_transaction_check(): response = NewTransactionCheckService.process_request(data) return response +# Repayment Endpoint - Added based on updated Repayment.json +@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 + +# StatusCall Endpoint - Added based on updated StatusCall.json +@api.route('/StatusCall', methods=['POST']) +@require_api_key +@require_app_id +def status_call(): + data = request.get_json() + # logger.info(f"StatusCall request received: {data}") + response = StatusCallService.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 + return {"status": "ok"}, 200 \ No newline at end of file diff --git a/app/api/schemas/disbursement.py b/app/api/schemas/disbursement.py index 16f7aa1..6172e51 100644 --- a/app/api/schemas/disbursement.py +++ b/app/api/schemas/disbursement.py @@ -1,17 +1,38 @@ -from marshmallow import Schema, fields +from marshmallow import Schema, fields, validate -# Disbursement Schema +# Schema for the fees details object +class FeesDetailsSchema(Schema): + collectAmountInterest = fields.Float(required=True, description="Interest Amount to be collected immediately after loan is provided (Only for 30 days)") + collectAmountMgtFee = fields.Float(required=True, description="Management Fee Amount to be collected immediately after loan is provided") + collectAmountInsurance = fields.Float(required=True, description="Insurance Amount to be collected immediately after loan is provided") + collectAmountVAT = fields.Float(required=True, description="VAT Amount to be collected immediately after loan is provided") + +# Disbursement Request 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 + requestId = fields.Str(required=True, description="Unique identifier of request") + countryCode = fields.Str(required=True, description="Unique country code. Please refer to Country Codes table") + transactionId = fields.Str(required=True, description="Unique identifier of transaction in Simbrella system") + debtId = fields.Str(required=True, description="Unique identifier of a loan in Simbrella system that is going to be collected (it correlates with provision request)") + customerId = fields.Str(required=True, description="Unique identifier of a user") + accountId = fields.Str(required=True, description="Specific identifier of a user's account") + productId = fields.Str(required=True, description="Identifier of a product to be provided to a user") + provideAmount = fields.Float(required=True, description="Amount of loan (including service fee) to be provided on a specific account of a user") + totalFees = fields.Float(required=True, description="Total amount of all fees combined") + feesDetails = fields.Nested(FeesDetailsSchema, required=True, description="Detailed breakdown of all fees") + countryId = fields.Str(required=True, description="Set to static value '01'") + comment = fields.Str(required=False, description="Any additional comment for provided loan operation") + +# Disbursement Response Schema +class DisbursementResponseSchema(Schema): + requestId = fields.Str(required=True, description="Unique identifier of request") + countryCode = fields.Str(required=True, description="Unique country code.") + transactionId = fields.Str(required=True, description="Unique ID of customer's USSD session. Must be consistent throughout whole USSD journey") + debtId = fields.Str(required=True, description="Unique identifier of a loan in Simbrella system that is going to be collected (it correlates with provision request)") + customerId = fields.Str(required=True, description="Unique identifier of a user") + accountId = fields.Str(required=True, description="Specific identifier of a user's account") + productId = fields.Str(required=True, description="Identifier of a product to be provided to a user") + provideAmount = fields.Float(required=True, description="Amount of loan (including service fee) to be provided on a specific account of a user") + totalFees = fields.Float(required=True, description="Total amount of all fees combined") + feesDetails = fields.Nested(FeesDetailsSchema, required=True, description="Detailed breakdown of all fees") + resultCode = fields.Str(required=True, description="Result code of executed transaction, e.g. (00 – Success etc.) see result codes table") + resultDescription = fields.Str(required=True, description="Description of provided result code") \ No newline at end of file diff --git a/app/api/services/__init__.py b/app/api/services/__init__.py index 085ead8..c58bbf7 100644 --- a/app/api/services/__init__.py +++ b/app/api/services/__init__.py @@ -7,3 +7,5 @@ from app.api.services.revoke_enable_consent import RevokeEnableConsentService from app.api.services.token_validation import TokenValidationService from app.api.services.lien_check import LienCheckService from app.api.services.new_transaction_check import NewTransactionCheckService +from app.api.services.repayment import RepaymentService +from app.api.services.status_call import StatusCallService \ No newline at end of file diff --git a/app/api/services/disbursement.py b/app/api/services/disbursement.py index c94c910..245d2f7 100644 --- a/app/api/services/disbursement.py +++ b/app/api/services/disbursement.py @@ -1,8 +1,7 @@ from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger -from app.api.helpers.response_helper import ResponseHelper -from app.api.schemas.disbursement import DisbursementSchema +from app.api.schemas.disbursement import DisbursementSchema, DisbursementResponseSchema class DisbursementService: @staticmethod @@ -23,40 +22,47 @@ class DisbursementService: schema = DisbursementSchema() validated_data = schema.load(data) # Raises ValidationError if invalid + # Extract fees details for easier access + fees_details = validated_data.get('feesDetails', {}) + # Simulated processing logic + # In a real implementation, this would interact with your business 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", + "requestId": validated_data.get('requestId'), + "countryCode": validated_data.get('countryCode'), + "transactionId": validated_data.get('transactionId'), + "debtId": validated_data.get('debtId'), + "customerId": validated_data.get('customerId'), + "accountId": validated_data.get('accountId'), + "productId": validated_data.get('productId'), + "provideAmount": validated_data.get('provideAmount'), + "totalFees": validated_data.get('totalFees'), + "feesDetails": { + "collectAmountInterest": fees_details.get('collectAmountInterest'), + "collectAmountMgtFee": fees_details.get('collectAmountMgtFee'), + "collectAmountInsurance": fees_details.get('collectAmountInsurance'), + "collectAmountVAT": fees_details.get('collectAmountVAT') + }, "resultCode": "00", "resultDescription": "Loan Request Completed Successfully!" } + # Validate the response using the response schema + response_schema = DisbursementResponseSchema() + validated_response = response_schema.dump(response_data) - # return ResponseHelper.success( - # data=response_data, - # message="Disbursement completed successfully" - # ) - - return response_data + return jsonify(validated_response) except ValidationError as err: logger.error(f"Validation Error: {err.messages}") return jsonify({ - "message": "Validation exception" - }) , 422 + "resultCode": "01", + "resultDescription": f"Validation error: {err.messages}" + }), 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) return jsonify({ - "message": "Internal Server Error" - }) , 500 + "resultCode": "08", + "resultDescription": f"Error occurred: {str(e)}" + }), 500 \ No newline at end of file diff --git a/app/api/services/repayment.py b/app/api/services/repayment.py new file mode 100644 index 0000000..779c0d3 --- /dev/null +++ b/app/api/services/repayment.py @@ -0,0 +1,64 @@ +from flask import jsonify +from app.utils.logger import logger + + +class RepaymentService: + @staticmethod + def process_request(data): + """ + Process a repayment request from Simbrella to FirstBank + + Args: + data (dict): The request data containing repayment details + + Returns: + flask.Response: JSON response with the result of the repayment operation + """ + try: + # Log the incoming request + logger.info(f"Processing repayment request: {data}") + + # Validate required fields + required_fields = [ + "requestId", "countryCode", "transactionId", "debtId", + "customerId", "accountId", "productId", "collectAmount", + "collectionMethod", "lienAmount" + ] + + for field in required_fields: + if field not in data: + logger.error(f"Missing required field: {field}") + return jsonify({ + "resultCode": "01", + "resultDescription": f"Missing required field: {field}" + }), 400 + + # Process the repayment request + # This is where you would implement the actual business logic + # For now, we'll just return a successful response + + response = { + "requestId": data.get("requestId"), + "countryCode": data.get("countryCode"), + "transactionId": data.get("transactionId"), + "debtId": data.get("debtId"), + "customerId": data.get("customerId"), + "accountId": data.get("accountId"), + "productId": data.get("productId"), + "collectAmount": data.get("collectAmount"), + "penalCharge": data.get("penalCharge", 0), + "lienAmount": data.get("lienAmount"), + "comment": data.get("comment", ""), + "resultCode": "00", + "resultDescription": "Loan Collection Successful" + } + + logger.info(f"Repayment response: {response}") + return jsonify(response) + + except Exception as e: + logger.error(f"Error processing repayment request: {str(e)}") + return jsonify({ + "resultCode": "08", + "resultDescription": "Error occurred" + }), 500 \ No newline at end of file diff --git a/app/api/services/status_call.py b/app/api/services/status_call.py new file mode 100644 index 0000000..2437c35 --- /dev/null +++ b/app/api/services/status_call.py @@ -0,0 +1,84 @@ +from flask import jsonify +from app.utils.logger import logger + + +class StatusCallService: + @staticmethod + def process_request(data): + """ + Process a status call request to check transaction status + + Args: + data (dict): The request data containing transaction details to check + + Returns: + flask.Response: JSON response with the status of the transaction + """ + try: + # Log the incoming request + logger.info(f"Processing status call request: {data}") + + # Validate required fields + required_fields = [ + "transactionId", "transactionType", "customerId" + ] + + for field in required_fields: + if field not in data: + logger.error(f"Missing required field: {field}") + return jsonify({ + "resultCode": "01", + "resultDescription": f"Missing required field: {field}" + }), 400 + + # Process the status call request based on transaction type + transaction_type = data.get("transactionType") + + # Prepare the response based on transaction type + if transaction_type == "Disbursement": + status = { + "providedAmount": 1000.00, + "collectedAmount": 0.00, + "resultCode": "00", + "resultDescription": "Loan Provision is successful" + } + elif transaction_type == "Collection": + status = { + "providedAmount": 0.00, + "collectedAmount": 100.00, + "resultCode": "00", + "resultDescription": "Loan Collection is successful" + } + elif transaction_type == "PenalCharge": + status = { + "transactionId": data.get("transactionId"), + "providedAmount": 0.00, + "collectedAmount": 100.00, + "resultCode": "00", + "resultDescription": "Penal Charge is successful" + } + else: + logger.error(f"Invalid transaction type: {transaction_type}") + return jsonify({ + "resultCode": "01", + "resultDescription": f"Invalid transaction type: {transaction_type}" + }), 400 + + response = { + "requestId": data.get("requestId", ""), + "countryCode": data.get("countryCode", "NGR"), + "transactionId": data.get("transactionId"), + "Status": status, + "resultCode": "00", + "resultDescription": "SUCCESS" + } + + logger.info(f"Status call response: {response}") + return jsonify(response) + + except Exception as e: + logger.error(f"Error processing status call request: {str(e)}") + return jsonify({ + "resultCode": "08", + "resultDescription": "Error occurred" + }), 500 \ No newline at end of file