26 Commits

Author SHA1 Message Date
Chinenye Nmoh 5794ddfa0c added bearer token authentication 2025-10-29 17:39:31 +01:00
Chinenye Nmoh 1293f28bf2 Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-BankEmulator into sync_payload
pulled latest master changes
2025-10-29 07:17:25 +01:00
ameye db36278ae7 Merge branch 'partial_payment' of DigiFi/digifi-BankEmulator into master 2025-10-28 19:24:45 +00:00
VivianDee b48ccacd7c [add]: Make all customers valid 2025-09-26 12:03:33 +01:00
ameye 4e52459d51 Merge branch 'partial_payment' of DigiFi/digifi-BankEmulator into master 2025-08-29 19:18:35 +00:00
VivianDee c0f1845c5d [add]: Partial loan collection 2025-08-29 12:51:28 +01:00
Chinenye Nmoh 6869b77428 Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-BankEmulator into sync_payload
pulled changes
2025-07-13 13:44:34 +01:00
Chinenye Nmoh 73c0377033 pull changes 2025-07-13 13:44:27 +01:00
ameye cfc40b89dc Merge branch 'health_check' of DigiFi/digifi-BankEmulator into master 2025-06-28 13:50:03 +00:00
VivianDee eb7c0f6221 [add]: health check 2025-06-27 15:58:12 +01:00
Chinenye Nmoh 02b4ba4a5a Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-BankEmulator into sync_payload
pulled
2025-06-26 14:01:31 +01:00
ameye d69bcd11ea Merge branch 'randomize_rac_check_values' of DigiFi/digifi-BankEmulator into master 2025-06-25 17:32:57 +00:00
VivianDee c4d7db5c59 Update rac_check.py 2025-06-25 17:31:02 +01:00
Chinenye Nmoh b468b2ad4b Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-BankEmulator into sync_payload
pulled
2025-06-25 13:22:11 +01:00
CHIEFSOFT\ameye d359532775 Fbn trax 2025-06-21 10:52:09 -04:00
CHIEFSOFT\ameye 2b12e1ab0b Partial simulation 2025-06-21 10:41:53 -04:00
vivian.d.simbrellang.com 23b352d9c3 Merge branch 'rac_check' of DigiFi/digifi-BankEmulator into master 2025-06-12 14:33:11 +00:00
VivianDee f665d7b8e4 Update rac_check.py 2025-06-12 15:32:00 +01:00
Chinenye Nmoh 7b1da3b095 expanded disbursement endpoint 2025-06-09 21:08:02 +01:00
ameye 855f343626 Merge branch 'sync_payload' of DigiFi/digifi-BankEmulator into master 2025-06-09 19:40:35 +00:00
Chinenye Nmoh e7279a8c65 expanded disbursement endpoint 2025-06-09 20:31:32 +01:00
CHIEFSOFT\ameye 3e4fad5418 responseCode 2025-06-05 22:50:45 -04:00
CHIEFSOFT\ameye 4b018a26e9 res 2025-06-05 22:46:46 -04:00
CHIEFSOFT\ameye dfdfa51583 res[pose code 2025-06-05 22:43:38 -04:00
CHIEFSOFT\ameye 82053f41ce Fix emulator data 2025-06-05 18:18:56 -04:00
ameye 0833d0d0f2 Merge branch 'rac_check_update' of DigiFi/digifi-BankEmulator into master 2025-06-05 14:49:16 +00:00
24 changed files with 603 additions and 112 deletions
+31 -2
View File
@@ -1,17 +1,21 @@
from flask import Flask from flask import Flask, jsonify
import os import os
from flask_swagger_ui import get_swaggerui_blueprint from flask_swagger_ui import get_swaggerui_blueprint
from flask_cors import CORS from flask_cors import CORS
from app.config import Config from app.config import Config
from app.api.routes import api from app.api.routes import api, auth_bp
from app.errors import register_error_handlers from app.errors import register_error_handlers
from flask_jwt_extended import JWTManager
def create_app(): def create_app():
""" Factory function to create a Flask app instance """ """ Factory function to create a Flask app instance """
app = Flask(__name__) app = Flask(__name__)
# Load configuration # Load configuration
app.config.from_object(Config) app.config.from_object(Config)
jwt = JWTManager(app)
CORS(app) CORS(app)
@@ -21,10 +25,35 @@ def create_app():
# Register blueprints with /api prefix for the main API routes # Register blueprints with /api prefix for the main API routes
app.register_blueprint(api, url_prefix='/api') app.register_blueprint(api, url_prefix='/api')
# Register blueprints with /auth prefix for the authentication routes
app.register_blueprint(auth_bp, url_prefix='/api/Auth')
swagger_ui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL) swagger_ui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL)
app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL) app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL)
def jwt_error(message, code=401):
return jsonify({
"status": "error",
"message": message
}), code
@jwt.unauthorized_loader
def unauthorized_response(callback):
return jwt_error("Unauthorized access")
@jwt.expired_token_loader
def expired_token_callback(jwt_header, jwt_payload):
return jwt_error("Expired token")
@jwt.invalid_token_loader
def invalid_token_callback(error):
return jwt_error("Invalid authentication token.", 422)
@jwt.revoked_token_loader
def revoked_token_callback(jwt_header, jwt_payload):
return jwt_error("This token has been revoked. Please log in again.")
# Error Handlers # Error Handlers
register_error_handlers(app) register_error_handlers(app)
+1
View File
@@ -1,4 +1,5 @@
from flask import request, jsonify from flask import request, jsonify
from app.utils.logger import logger
def enforce_json(): def enforce_json():
+1
View File
@@ -1 +1,2 @@
from .routes import api from .routes import api
from .authentication import auth_bp
+24
View File
@@ -0,0 +1,24 @@
from flask import Blueprint, request, jsonify
from app.utils.logger import logger
from app.api.middlewares import enforce_json
from app.api.services.generate_token import GenerateTokenService
auth_bp = Blueprint("api/Auth", __name__)
# Enforce json
@auth_bp.before_request
def cors_middleware():
"""Middleware applied globally to all API routes in this blueprint"""
return enforce_json()
@auth_bp.route('/generate-token', methods=['POST'])
def get_token():
try:
data = request.get_json()
logger.info(f"GenerateToken request received: {data}")
response = GenerateTokenService.process_request(data)
return response
except Exception as e:
logger.exception("Unhandled exception in /GenerateToken route", exc_info=e)
return jsonify({"message": "Unhandled server error"}), 500
+47 -23
View File
@@ -1,4 +1,5 @@
from flask import Flask, Blueprint, request, jsonify, send_from_directory from flask import Flask, Blueprint, request, jsonify, send_from_directory
import sys
import os import os
from app.api.services import ( from app.api.services import (
RACCheckService, RACCheckService,
@@ -13,7 +14,10 @@ from app.api.services import (
CompleteRACcheckService CompleteRACcheckService
) )
from app.utils.logger import logger from app.utils.logger import logger
from app.api.middlewares import require_api_key, require_app_id, enforce_json from app.api.middlewares import enforce_json
from flask_jwt_extended import (jwt_required)
api = Blueprint("api", __name__) api = Blueprint("api", __name__)
@@ -41,10 +45,11 @@ def serve_paths(filename):
# RACCheck Endpoint # RACCheck Endpoint
@api.route('/rac-check', methods=['POST']) @api.route('/rac-check', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def rac_check(): def rac_check():
logger.info("RACCheck request received")
try: try:
logger.info("RACCheck inside try request received")
data = request.get_json() data = request.get_json()
response = RACCheckService.process_request(data) response = RACCheckService.process_request(data)
return response return response
@@ -54,8 +59,7 @@ def rac_check():
# CompleteRACcheck Endpoint # CompleteRACcheck Endpoint
@api.route('/CompleteRACcheck', methods=['POST']) @api.route('/CompleteRACcheck', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def complete_rac_check(): def complete_rac_check():
try: try:
data = request.get_json() data = request.get_json()
@@ -67,8 +71,7 @@ def complete_rac_check():
# Disbursement Endpoint # Disbursement Endpoint
@api.route('/DisburseLoan', methods=['POST']) @api.route('/DisburseLoan', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def disbursement(): def disbursement():
try: try:
data = request.get_json() data = request.get_json()
@@ -81,8 +84,7 @@ def disbursement():
# CollectLoan Endpoint # CollectLoan Endpoint
@api.route('/CollectLoan', methods=['POST']) @api.route('/CollectLoan', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def collect_loan(): def collect_loan():
try: try:
data = request.get_json() data = request.get_json()
@@ -95,8 +97,7 @@ def collect_loan():
# TransactionVerify Endpoint # TransactionVerify Endpoint
@api.route('/TransactionVerify', methods=['POST']) @api.route('/TransactionVerify', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def transaction_verify(): def transaction_verify():
try: try:
data = request.get_json() data = request.get_json()
@@ -109,8 +110,7 @@ def transaction_verify():
# PenalCharge Endpoint # PenalCharge Endpoint
@api.route('/CollectPenalFee', methods=['POST']) @api.route('/CollectPenalFee', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def penal_charge(): def penal_charge():
try: try:
data = request.get_json() data = request.get_json()
@@ -124,8 +124,7 @@ def penal_charge():
# RevokeEnableConsent Endpoint # RevokeEnableConsent Endpoint
@api.route('/RevokeEnableConsent', methods=['POST']) @api.route('/RevokeEnableConsent', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def revoke_enable_consent(): def revoke_enable_consent():
data = request.get_json() data = request.get_json()
# logger.info(f"RevokeEnableConsent request received: {data}") # logger.info(f"RevokeEnableConsent request received: {data}")
@@ -134,8 +133,7 @@ def revoke_enable_consent():
# TokenValidation Endpoint # TokenValidation Endpoint
@api.route('/TokenValidation', methods=['POST']) @api.route('/TokenValidation', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def token_validation(): def token_validation():
data = request.get_json() data = request.get_json()
# logger.info(f"TokenValidation request received: {data}") # logger.info(f"TokenValidation request received: {data}")
@@ -144,8 +142,7 @@ def token_validation():
# LienCheck Endpoint # LienCheck Endpoint
@api.route('/LienCheck', methods=['POST']) @api.route('/LienCheck', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def lien_check(): def lien_check():
data = request.get_json() data = request.get_json()
# logger.info(f"LienCheck request received: {data}") # logger.info(f"LienCheck request received: {data}")
@@ -154,15 +151,42 @@ def lien_check():
# NewTransactionCheck Endpoint # NewTransactionCheck Endpoint
@api.route('/NewTransactionCheck', methods=['POST']) @api.route('/NewTransactionCheck', methods=['POST'])
@require_api_key @jwt_required()
@require_app_id
def new_transaction_check(): def new_transaction_check():
data = request.get_json() data = request.get_json()
# logger.info(f"NewTransactionCheck request received: {data}") # logger.info(f"NewTransactionCheck request received: {data}")
response = NewTransactionCheckService.process_request(data) response = NewTransactionCheckService.process_request(data)
return response return response
# Health Check Endpoint # Health Check Endpoint
@api.route('/health', methods=['GET']) @api.route('/system-health-check', methods=['GET'])
@jwt_required()
def health_check(): def health_check():
return {"status": "ok"} , 200 """Basic system health check"""
try:
checks = {
"python_version": sys.version_info >= (3, 6),
"disk_space": os.statvfs('/').f_bavail * os.statvfs('/').f_frsize > 500 * 1024 * 1024,
"system_operational": True
}
if all(checks.values()):
return jsonify({
"status": "Active",
"responseCode": "00",
"responseMessage": "Successful"
}), 200
else:
return jsonify({
"status": "Degraded",
"responseCode": "01",
"responseMessage": "System check failed"
}), 200
except Exception as e:
return jsonify({
"status": "Error",
"responseCode": "99",
"responseMessage": f"Health check failed: {str(e)}"
}), 500
+8 -3
View File
@@ -16,15 +16,20 @@ class CollectLoanSchema(Schema):
comment = fields.Str(allow_none=True) comment = fields.Str(allow_none=True)
class CollectLoanResponseSchema(Schema): class CollectLoanResponseSchema(Schema):
responseCode = fields.Str(allow_none=True)
responseDescr = fields.Str(allow_none=True)
fullDescription = fields.Str(allow_none=True)
transactionId = fields.Str(allow_none=True) transactionId = fields.Str(allow_none=True)
fbnTransactionId = fields.Str(allow_none=True)
debtId = fields.Str(allow_none=True) debtId = fields.Str(allow_none=True)
customerId = fields.Str(allow_none=True) customerId = fields.Str(allow_none=True)
accountId = fields.Str(allow_none=True) accountId = fields.Str(allow_none=True)
productId = fields.Str(allow_none=True) productId = fields.Str(allow_none=True)
amountCollected = fields.Float(required=True) amountCollected = fields.Float(required=True)
interestCollected = fields.Float(required=True)
penalChargeCollected = fields.Float(required=True)
lienAmount = fields.Float(required=True)
countryId = fields.Str(allow_none=True) countryId = fields.Str(allow_none=True)
comment = fields.Str(allow_none=True) comment = fields.Str(allow_none=True)
responseCode = fields.Str(allow_none=True)
responseMessage = fields.Str(allow_none=True) responseMessage = fields.Str(allow_none=True)
responseDescr = fields.Str(allow_none=True)
fullDescription = fields.Str(allow_none=True)
+9 -1
View File
@@ -2,7 +2,7 @@ from marshmallow import Schema, fields
class DisbursementSchema(Schema): class DisbursementSchema(Schema):
transactionId = fields.Str(required=False, allow_none=True) transactionId = fields.Str(required=False, allow_none=True)
FbnTransactionId = fields.Str(required=False, allow_none=True) fbnTransactionId = fields.Str(required=False, allow_none=True)
debtId = fields.Str(required=False, allow_none=True) debtId = fields.Str(required=False, allow_none=True)
customerId = fields.Str(required=False, allow_none=True) customerId = fields.Str(required=False, allow_none=True)
accountId = fields.Str(required=False, allow_none=True) accountId = fields.Str(required=False, allow_none=True)
@@ -16,6 +16,7 @@ class DisbursementSchema(Schema):
comment = fields.Str(required=False, allow_none=True) comment = fields.Str(required=False, allow_none=True)
class DisburseLoanResponseSchema(Schema): class DisburseLoanResponseSchema(Schema):
transactionId = fields.Str(allow_none=True) transactionId = fields.Str(allow_none=True)
fbnTransactionId = fields.Str(allow_none=True) fbnTransactionId = fields.Str(allow_none=True)
@@ -31,5 +32,12 @@ class DisburseLoanResponseSchema(Schema):
countryId = fields.Str(allow_none=True) countryId = fields.Str(allow_none=True)
responseCode = fields.Str(allow_none=True) responseCode = fields.Str(allow_none=True)
responseMessage = fields.Str(allow_none=True) responseMessage = fields.Str(allow_none=True)
disburseResult = fields.Str(allow_none=True)
disburseDate = fields.Str(allow_none=True)
disburseVerify = fields.Str(allow_none=True)
disburseDescription = fields.Str(allow_none=True)
verifyResult = fields.Str(allow_none=True)
verifyDescription = fields.Str(allow_none=True)
+18
View File
@@ -0,0 +1,18 @@
from marshmallow import Schema, fields
class GenerateTokenRequestSchema(Schema):
username = fields.Str(required=True)
password = fields.Str(required=True)
grant_type = fields.Str(required=True)
class GenerateTokenResponseSchema(Schema):
access_token = fields.Str(required=True)
token_type = fields.Str(required=True)
expires_in = fields.Int(required=True)
userName = fields.Str(required=False, allow_none=True)
ipaddress = fields.Str(required=False, allow_none=True)
errorMessage = fields.Str(required=False, allow_none=True)
issued = fields.DateTime(required=False, allow_none=True)
expires = fields.DateTime(required=False, allow_none=True)
+1
View File
@@ -8,3 +8,4 @@ from app.api.services.token_validation import TokenValidationService
from app.api.services.lien_check import LienCheckService from app.api.services.lien_check import LienCheckService
from app.api.services.new_transaction_check import NewTransactionCheckService from app.api.services.new_transaction_check import NewTransactionCheckService
from app.api.services.complete_rac_check_service import CompleteRACcheckService from app.api.services.complete_rac_check_service import CompleteRACcheckService
from app.api.services.generate_token import GenerateTokenService
+68 -24
View File
@@ -1,21 +1,23 @@
import random
from flask import request, jsonify from flask import request, jsonify
from marshmallow import ValidationError from marshmallow import ValidationError
from app.utils.logger import logger from app.utils.logger import logger
from app.api.helpers.response_helper import ResponseHelper from app.api.helpers.response_helper import ResponseHelper
from app.api.schemas.collect_loan import CollectLoanSchema, CollectLoanResponseSchema from app.api.schemas.collect_loan import CollectLoanSchema, CollectLoanResponseSchema
from app.config import Config
"""
Process the CollectLoan request.
Args:
data (dict): The request data.
Returns:
tuple: JSON response and status code.
"""
class CollectLoanService: class CollectLoanService:
@staticmethod @staticmethod
def process_request(data): def process_request(data):
"""
Process the CollectLoan request.
Args:
data (dict): The request data.
Returns:
tuple: JSON response and status code.
"""
try: try:
logger.info("Processing CollectLoan request") logger.info("Processing CollectLoan request")
@@ -23,21 +25,63 @@ class CollectLoanService:
schema = CollectLoanSchema() schema = CollectLoanSchema()
validated_data = schema.load(data) validated_data = schema.load(data)
# Simulated processing logic amountForCollection = validated_data.get("collectAmount")
response_data = { productId = validated_data.get("productId")
"transactionId": validated_data.get("transactionId", "T002"), customerId = validated_data.get("customerId")
"debtId": validated_data.get("debtId", "273194670"), amountCollected = amountForCollection
"customerId": validated_data.get("customerId", "CN621868"), responseDescr= "Loan Collection Successful EMULATOR"
"accountId": validated_data.get("accountId", "2017821799"), fullDescription= "Loan collection completed successfully EMULATOR"
"productId": validated_data.get("productId", "101"), responseMessage= "Loan collection completed successfully EMULATOR"
"amountCollected": validated_data.get("collectAmount", 60000.00), interestCollected = amountForCollection * 0.03
"countryId": validated_data.get("countryId", "01"), lienAmount = validated_data.get("lienAmount", 0)
"comment": validated_data.get("comment", "Testing CollectionLoanRequest"),
"responseCode": "00", isValid = not (
"responseDescr": "Loan Collection Successful", int(customerId[-1]) in [2, 7, 9]
"fullDescription": "Loan collection completed successfully", )
"responseMessage": "Loan collection completed successfully" if Config.MIN_AMOUNT_FOR_COLLECTION <= amountForCollection <= Config.MAX_AMOUNT_FOR_COLLECTION:
} amountCollected = amountForCollection if lienAmount >= amountForCollection else 0
responseDescr = "Partial Loan Collection Successful EMULATOR"
fullDescription = "Partial Loan collection completed successfully EMULATOR"
responseMessage = "Partial Loan collection completed successfully EMULATOR"
response_data = {
"transactionId": validated_data.get("transactionId"),
"fbnTransactionId": validated_data.get("fbnTransactionId"),
"debtId": validated_data.get("debtId"),
"customerId": validated_data.get("customerId"),
"accountId": validated_data.get("accountId"),
"productId": validated_data.get("productId"),
"amountCollected": amountCollected,
"interestCollected": interestCollected,
"penalChargeCollected": 0,
"lienAmount": lienAmount if amountCollected == 0 else 0,
"countryId": validated_data.get("countryId"),
"comment": validated_data.get("comment", "Testing CollectionLoanRequest EMULATOR"),
"responseCode": "00",
"responseDescr": responseDescr,
"fullDescription": fullDescription,
"responseMessage": responseMessage
}
else:
response_data = {
"transactionId": validated_data.get("transactionId"),
"fbnTransactionId": validated_data.get("fbnTransactionId"),
"debtId": validated_data.get("debtId"),
"customerId": validated_data.get("customerId"),
"accountId": validated_data.get("accountId"),
"productId": validated_data.get("productId"),
"amountCollected": amountCollected,
"interestCollected": interestCollected,
"penalChargeCollected": 0,
"amountCollected": amountCollected,
"countryId": validated_data.get("countryId"),
"comment": validated_data.get("comment", "Testing CollectionLoanRequest EMULATOR"),
"responseCode": "00",
"responseDescr": responseDescr,
"fullDescription": fullDescription,
"responseMessage": responseMessage
}
# Validate and serialize the response data # Validate and serialize the response data
response_schema = CollectLoanResponseSchema() response_schema = CollectLoanResponseSchema()
+9 -2
View File
@@ -2,6 +2,7 @@ from flask import request, jsonify
from marshmallow import ValidationError from marshmallow import ValidationError
from app.utils.logger import logger from app.utils.logger import logger
from app.api.schemas.disbursement import DisbursementSchema, DisburseLoanResponseSchema from app.api.schemas.disbursement import DisbursementSchema, DisburseLoanResponseSchema
import datetime
class DisbursementService: class DisbursementService:
@staticmethod @staticmethod
@@ -26,7 +27,7 @@ class DisbursementService:
# For demo purposes, we simulate a response using the validated data # For demo purposes, we simulate a response using the validated data
response_data = { response_data = {
"transactionId": validated_data.get("transactionId"), "transactionId": validated_data.get("transactionId"),
"FbnTransactionId": validated_data.get("FbnTransactionId"), # Example or generated value "fbnTransactionId": validated_data.get("fbnTransactionId"), # Example or generated value
"debtId": validated_data.get("debtId"), "debtId": validated_data.get("debtId"),
"customerId": validated_data.get("customerId"), "customerId": validated_data.get("customerId"),
"accountId": validated_data.get("accountId"), "accountId": validated_data.get("accountId"),
@@ -38,7 +39,13 @@ class DisbursementService:
"collectAmountVAT": validated_data.get("collectAmountVAT"), "collectAmountVAT": validated_data.get("collectAmountVAT"),
"countryId": validated_data.get("countryId"), "countryId": validated_data.get("countryId"),
"responseCode": "00", # success code example "responseCode": "00", # success code example
"responseMessage": "Loan Request Completed Successfully!" "responseMessage": "Loan Request Completed Successfully!",
"disburseVerify": datetime.datetime.now().isoformat(),
"verifyResult": "00",
"verifyDescription": "Collect Status retrieved successfully.",
"disburseDate": datetime.datetime.now().isoformat(),
"disburseResult": "00",
"disburseDescription": "Loan Request Completed Successfully!",
} }
# Serialize response # Serialize response
+89
View File
@@ -0,0 +1,89 @@
import datetime
from datetime import timedelta
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.generate_token import GenerateTokenRequestSchema, GenerateTokenResponseSchema
from app.config import Config
from flask_jwt_extended import (
create_access_token,
)
class GenerateTokenService:
USERNAME = Config.BANK_CALL_BASIC_AUTH_USERNAME
PASSWORD = Config.BANK_CALL_BASIC_AUTH_PASSWORD
TYPE = Config.BANK_GRANT_TYPE
@staticmethod
def process_request(data):
"""
Process the GenerateToken request.
Args:
data (dict): The request JSON payload.
Returns:
tuple: (JSON response, status code)
"""
try:
logger.info("Processing GenerateToken request")
# Step 1: Validate input using schema
schema = GenerateTokenRequestSchema()
validated_data = schema.load(data)
logger.info(f"Validated data: {validated_data}")
username = validated_data.get("username")
password = validated_data.get("password")
grant_type = validated_data.get("grant_type")
if password != GenerateTokenService.PASSWORD or username != GenerateTokenService.USERNAME or grant_type != GenerateTokenService.TYPE:
return {
"message": "Invalid credentials",
"status": 401
}
expires_in = 1800
identity = username
# Step 2: Generate JWT token
access_token = create_access_token(identity=identity, expires_delta=timedelta(seconds=expires_in))
# Step 3: Get client IP address
ipaddress = request.remote_addr or "127.0.0.1"
# Step 4: Build response timestamps
issued_time = datetime.datetime.utcnow()
expires_time = issued_time + datetime.timedelta(seconds=expires_in)
# Step 5: Construct response payload
response_data = {
"access_token": access_token,
"token_type": "bearer",
"expires_in": expires_in,
"userName": username,
"ipaddress": ipaddress,
"errorMessage": "",
"issued": issued_time,
"expires": expires_time
}
# Serialize with response schema
response_schema = GenerateTokenResponseSchema()
response_json = response_schema.dump(response_data)
return jsonify(response_json), 200
except ValidationError as err:
logger.error(f"Validation Error: {err.messages}")
return jsonify({
"message": "Validation exception",
"errors": err.messages
}), 422
except Exception as e:
logger.error(f"An error occurred while generating token: {str(e)}", exc_info=True)
return jsonify({
"message": "Internal Server Error"
}), 500
+47 -36
View File
@@ -25,53 +25,64 @@ class RACCheckService:
schema = RACCheckSchema() schema = RACCheckSchema()
validated_data = schema.load(data) validated_data = schema.load(data)
# Simulated RAC check logic — create racResponse manually or via logic customer_id = validated_data["customerId"]
# rac_response = { is_valid = True
# "hasSalaryAccount": True,
# "bvnValidated": True,
# "creditBureauCheck": False, try:
# "crmsCheck": True, salary_count = int(str(customer_id)[-1]) + 1
# "accountStatus": True, if salary_count < 1 or salary_count > 6:
# "hasLien": False, salary_count = 3
# "noBouncedCheck": True, except ValueError:
# "isWhitelisted": True, salary_count = 3
# "hasPastDueLoan": False
# } salary_payments = {}
total_salary = 0
for i in range(1, salary_count + 1):
salary = (((salary_count + i) * 7919) % 200000 + 10000) * 5
salary_payments[f"salarypaymenT_{i}"] = salary
total_salary += salary
average_salary = total_salary // salary_count if salary_count > 0 else 0
rac_response = { rac_response = {
"procesS_DATE": datetime.strptime("2025-06-05", "%Y-%m-%d").date(), "procesS_DATE": datetime.strptime("2025-06-05", "%Y-%m-%d").date(),
"ciF_ID": "416405737", "ciF_ID": "416405737",
"customeR_id": "7032744", "customeR_id": customer_id,
"salaccT_1": "4142904114", "salaccT_1": "4142904114",
"alerT_PHONE": "2348039301606", "alerT_PHONE": "2348039301606",
"averagE_SALARY": 5000, "averagE_SALARY": average_salary,
"loaN_OUSTANDING_BAL": 0, "loaN_OUSTANDING_BAL": 0,
"emi": 1000, "emi": 1000,
"eliG_AMT": 25000, "eliG_AMT": 25000,
"rule1_45day_sal": True, "rule1_45day_sal": is_valid,
"rule2_2m_sal": True, "rule2_2m_sal": is_valid,
"rule3_no_bounced_check": True, "rule3_no_bounced_check": is_valid,
"rule4_current_loan_payments": True, "rule4_current_loan_payments": True if is_valid is False else is_valid,
"rule5_no_past_due_fadv_loan": True, "rule5_no_past_due_fadv_loan": is_valid,
"rule6_no_past_due_other_loan": True, "rule6_no_past_due_other_loan": is_valid,
"rule7_consistent_salary_amount": True, "rule7_consistent_salary_amount": is_valid,
"rule8_whitelisted": True, "rule8_whitelisted": True if is_valid is False else is_valid,
"rule9_regular_account": True, "rule9_regular_account": True if is_valid is False else is_valid,
"rule10_bvn_validation": True, "rule10_bvn_validation": is_valid,
"rule11_CRC_no_delinquency": True, "rule11_CRC_no_delinquency": is_valid,
"rule12_CRMS_no_delinquency": True, "rule12_CRMS_no_delinquency": True if is_valid is False else is_valid,
"rule13_BVN_ignore": True, "rule13_BVN_ignore": is_valid,
"rule14_no_lien": True, "rule14_no_lien": is_valid,
"rule15_null_ignore": True, "rule15_null_ignore": True if is_valid is False else is_valid,
"overalL_ELIG": True, "overalL_ELIG": is_valid
"salarypaymenT_1": 180000, # "salarypaymenT_1": 180000,
"salarypaymenT_2": 50000, # "salarypaymenT_2": 50000,
"salarypaymenT_3": 70000, # "salarypaymenT_3": 70000,
"salarypaymenT_4": 0, # "salarypaymenT_4": 0,
"salarypaymenT_5": 0, # "salarypaymenT_5": 0,
"salarypaymenT_6": 0 # "salarypaymenT_6": 0
} }
rac_response.update(salary_payments)
full_response = { full_response = {
"transactionId": validated_data["transactionId"], "transactionId": validated_data["transactionId"],
+9
View File
@@ -1,5 +1,7 @@
import os import os
from re import M
from dotenv import load_dotenv from dotenv import load_dotenv
from datetime import timedelta
class Config: class Config:
"""Base configuration for Flask app""" """Base configuration for Flask app"""
@@ -8,8 +10,15 @@ class Config:
API_URL = '/api/swagger.json' API_URL = '/api/swagger.json'
DEBUG = True DEBUG = True
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "753fc155-6a63-4314-bd97-ae91d61dbafe")
JWT_ACCESS_TOKEN_EXPIRES = timedelta(seconds=int(os.getenv("JWT_ACCESS_TOKEN_EXPIRES", 1800)))
VALID_APP_ID = os.getenv("VALID_APP_ID", "app1") VALID_APP_ID = os.getenv("VALID_APP_ID", "app1")
VALID_API_KEY = os.getenv("VALID_API_KEY", "test-api-key-12345") VALID_API_KEY = os.getenv("VALID_API_KEY", "test-api-key-12345")
MIN_AMOUNT_FOR_COLLECTION = int(os.getenv("MIN_AMOUNT_FOR_COLLECTION", 10000))
MAX_AMOUNT_FOR_COLLECTION = int(os.getenv("MAX_AMOUNT_FOR_COLLECTION", 25000))
BANK_CALL_BASIC_AUTH_USERNAME = os.environ.get("BANK_CALL_BASIC_AUTH_USERNAME", "simbrella")
BANK_CALL_BASIC_AUTH_PASSWORD = os.environ.get("BANK_CALL_BASIC_AUTH_PASSWORD", "G7$k9@pL2!qR")
BANK_GRANT_TYPE = os.getenv("BANK_GRANT_TYPE", "password")
# SQLALCHEMY_DATABASE_URI =os.environ.get("DATABASE_URL", "database_url") # SQLALCHEMY_DATABASE_URI =os.environ.get("DATABASE_URL", "database_url")
# SQLALCHEMY_TRACK_MODIFICATIONS = False # SQLALCHEMY_TRACK_MODIFICATIONS = False
+35 -20
View File
@@ -28,6 +28,15 @@
} }
], ],
"tags": [ "tags": [
{
"name": "Auth",
"description": "Get access token for verification",
"externalDocs": {
"description": "Find out more",
"url": "https://www.simbrellang.net"
}
},
{ {
"name": "RACCheck", "name": "RACCheck",
"description": "Risk Acceptance Criteria Request", "description": "Risk Acceptance Criteria Request",
@@ -110,6 +119,12 @@
} }
], ],
"paths": { "paths": {
"/api/Auth/generate-token": {
"$ref": "swagger/paths/GenerateToken.json"
},
"/api/system-health-check": {
"$ref": "swagger/paths/HealthCheck.json"
},
"/api/rac-check": { "/api/rac-check": {
"$ref": "swagger/paths/RACCheck.json" "$ref": "swagger/paths/RACCheck.json"
}, },
@@ -143,6 +158,12 @@
}, },
"components": { "components": {
"schemas": { "schemas": {
"GenerateTokenRequest": {
"$ref": "./schemas/GenerateTokenRequest.json"
},
"GenerateTokenResponse": {
"$ref": "./schemas/GenerateTokenResponse.json"
},
"RACCheckRequest": { "RACCheckRequest": {
"$ref": "./schemas/RACCheckRequest.json" "$ref": "./schemas/RACCheckRequest.json"
}, },
@@ -214,24 +235,18 @@
} }
}, },
"securitySchemes": { "securitySchemes": {
"api_key": { "BearerAuth": {
"type": "apiKey", "type": "http",
"name": "x-api-key", "scheme": "bearer",
"in": "header" "bearerFormat": "JWT",
}, "description": "Standard Authorization header using the Bearer scheme. Example: 'Bearer {token}'"
"app_id": { }
"type": "apiKey", }
"name": "App-Id", },
"in": "header"
} "security": [
} {
}, "BearerAuth": []
"security": [ }
{ ]
"api_key": []
},
{
"app_id": []
}
]
} }
+56
View File
@@ -0,0 +1,56 @@
{
"post": {
"tags": [
"GenerateToken"
],
"summary": "Generate Access Token Request",
"description": "Generate Access Token Request",
"operationId": "GenerateToken",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/GenerateTokenRequest.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/GenerateTokenRequest.json"
}
},
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "../schemas/GenerateTokenRequest.json"
}
}
}
},
"responses": {
"200": {
"description": "GenerateToken Successful",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/GenerateTokenResponse.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/GenerateTokenResponse.json"
}
}
}
},
"400": {
"description": "Invalid request"
},
"422": {
"description": "Validation exception"
},
"500": {
"description": "Internal server error"
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"get": {
"tags": ["System"],
"summary": "System Health Check",
"description": "Returns the current health status of the system",
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/HealthCheckResponse.json"
}
}
}
}
}
}
}
@@ -70,6 +70,38 @@
"type": "string", "type": "string",
"example": "Loan Request Completed Successfully!", "example": "Loan Request Completed Successfully!",
"nullable": true "nullable": true
},
"disburseDate": {
"type": "string",
"format": "date-time",
"example": "2023-10-01T12:00:00Z",
"nullable": true
},
"disburseResult": {
"type": "string",
"example": "00",
"nullable": true
},
"disburseDescription": {
"type": "string",
"example": "Loan Request Completed Successfully!",
"nullable": true
},
"disburseVerify": {
"type": "string",
"format": "date-time",
"example": "2023-10-01T12:00:00Z",
"nullable": true
},
"verifyResult": {
"type": "string",
"example": "00",
"nullable": true
},
"verifyDescription": {
"type": "string",
"example": "Collect Status retrieved successfully.",
"nullable": true
} }
}, },
"required": [ "required": [
@@ -0,0 +1,21 @@
{
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"grant_type":{
"type":"string"
}
},
"required": [
"username",
"password",
"grant_type"
]
}
@@ -0,0 +1,41 @@
{
"type": "object",
"properties": {
"access_token": {
"type": "string",
"format": "eyjhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwic3ViIjoiYWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
},
"token_type": {
"type": "string",
"format": "bearer"
},
"expires_in": {
"type": "integer",
"format": "1800"
},
"userName": {
"type": "string",
"format": "sinbrella"
},
"ipaddress": {
"type": "string",
"format": "127.0.0.1"
},
"errorMessage":{
"type":"string"
},
"issued": {
"type": "string",
"format":"date-time"
},
"expires": {
"type": "string",
"format": "date-time"
}
},
"xml": {
"name": "##default"
}
}
@@ -0,0 +1,17 @@
{
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "Active"
},
"responseCode": {
"type": "string",
"example": "00"
},
"responseMessage": {
"type": "string",
"example": "Successful"
}
}
}
@@ -0,0 +1,17 @@
{
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "Active"
},
"responseCode": {
"type": "string",
"example": "00"
},
"responseMessage": {
"type": "string",
"example": "Successful"
}
}
}
+2
View File
@@ -6,6 +6,8 @@ Flask-Cors==3.0.10
gunicorn gunicorn
flask-swagger-ui flask-swagger-ui
python-dotenv python-dotenv
flask-jwt-extended==4.7.1