Merge branch 'oluyemi' of DigiFi/digifi-EventManager into master

This commit is contained in:
2025-04-17 21:07:23 +00:00
committed by Gogs
12 changed files with 346 additions and 19 deletions
+56 -10
View File
@@ -1,6 +1,9 @@
import requests import requests
from app.config import settings from app.config import settings
from app.helpers.response_helper import ResponseHelper
from app.services.loan import LoanService
from app.utils.auth import get_headers from app.utils.auth import get_headers
from app.utils.extras import preprocess_loan_charges_data
from app.utils.logger import logger from app.utils.logger import logger
from flask import jsonify, current_app from flask import jsonify, current_app
from app.services.transactions import TransactionService from app.services.transactions import TransactionService
@@ -26,21 +29,42 @@ class SimbrellaClient:
logger.info(f"Transaction id: {data['transactionId']}, was not found") logger.info(f"Transaction id: {data['transactionId']}, was not found")
return 0 return 0
# Fetch the loan based on the transaction_id
logger.info(f"Fetching the loan with transaction ID: {data['transactionId']}")
loan = LoanService.get_loan_by_transaction_id(transaction_id=data['transactionId'])
logger.info(f"Response from database: {loan}")
# If loan is not found
if not loan:
logger.info(f"Could not find loan with transaction id: {data['transactionId']}")
return 0
loan_data = loan.to_dict()
logger.info(f"Here is your loan data: {loan_data}")
loan_charges = preprocess_loan_charges_data([loan_charge.to_dict() for loan_charge in loan.loan_charges])
logger.info(f"Here are your loan_charges: {loan_charges}")
mgt_fee = loan_charges.get("MGTFEE")['amount']
vat_fee = loan_charges.get("VAT")['amount']
disbursement_data ={ disbursement_data ={
"requestId": data['requestId'], "requestId": data['requestId'],
"transactionId": data['transactionId'], "transactionId": data['transactionId'],
"debtId": "273194670", "debtId": loan_data['debtId'],
"customerId": data['customerId'], "customerId": data['customerId'],
"accountId": data['accountId'], "accountId": data['accountId'],
"productId": "101", "productId": loan_data['productId'],
"provideAmount": 100000, "provideAmount": loan_data['currentLoanAmount'],
"collectAmountInterest": 5000, "collectAmountInterest": 5000,
"collectAmountMgtFee": 1000, "collectAmountMgtFee": mgt_fee,
"collectAmountInsurance": 1000, "collectAmountInsurance": 1000,
"collectAmountVAT": 75, "collectAmountVAT": vat_fee,
"countryId": "01", "countryId": "01",
"comment": "Loan Disbursement", "comment": "Loan Disbursement",
} }
try: try:
logger.info(f"Here is your Disbursement Request data ****** : {disbursement_data}") logger.info(f"Here is your Disbursement Request data ****** : {disbursement_data}")
response = requests.post(api_url, json=disbursement_data, timeout=10, headers=get_headers()) response = requests.post(api_url, json=disbursement_data, timeout=10, headers=get_headers())
@@ -48,10 +72,8 @@ class SimbrellaClient:
except Exception as e: except Exception as e:
logger.info(f"Failed to call Disbursement endpoint: {e}") logger.info(f"Failed to call Disbursement endpoint: {e}")
#raise
return 0 return 0
# return jsonify(response.json()), response.status_code
return 1 return 1
@staticmethod @staticmethod
@@ -99,10 +121,11 @@ class SimbrellaClient:
def verify_transaction(): def verify_transaction():
try: try:
return { data = {
"status": "00", "status": "00",
"message": "Transaction verified" "message": "Transaction verified"
} }
return ResponseHelper.success(data, "Successful")
except Exception as e: except Exception as e:
logger.info(f"Failed to call TransactionVerify endpoint: {e}") logger.info(f"Failed to call TransactionVerify endpoint: {e}")
@@ -114,7 +137,7 @@ class SimbrellaClient:
try: try:
logger.info(f"Here is your Disbursement Request data ***** : {data}") logger.info(f"Here is your Disbursement Request data ***** : {data}")
return data return ResponseHelper.success(data, "Successful")
except Exception as e: except Exception as e:
logger.info(f"Failed to call Disbursement endpoint: {e}") logger.info(f"Failed to call Disbursement endpoint: {e}")
@@ -126,8 +149,31 @@ class SimbrellaClient:
try: try:
logger.info(f"Here is your Payment Callback Request data ***** : {data}") logger.info(f"Here is your Payment Callback Request data ***** : {data}")
return data return ResponseHelper.success(data, "Successful")
except Exception as e: except Exception as e:
logger.info(f"Failed to call Payment Callback endpoint: {e}") logger.info(f"Failed to call Payment Callback endpoint: {e}")
raise
@staticmethod
def penal_charge(data):
api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}/PenalCharge"
logger.info(f"Calling Penal Charge endpoint with data: {data}")
try:
logger.info(f"Here is your Penal Charge Request data ***** : {data}")
try:
logger.info(f"Here is your Penal Charge Request data ****** : {data}")
response = requests.post(api_url, json=data, timeout=10, headers=get_headers())
logger.info(f"Penal Charge response: {response.json()}")
return ResponseHelper.success(response.json(), "Successful")
except Exception as e:
logger.info(f"Failed to call Penal Charge endpoint: {e}")
return ResponseHelper.error("An error occurred", 500)
except Exception as e:
logger.info(f"Failed to call Penal Charge endpoint: {e}")
raise raise
+5 -1
View File
@@ -1,4 +1,8 @@
from .transactions import Transaction from .transactions import Transaction
from .repayment import Repayment from .repayment import Repayment
from .loan import Loan
from .loan_charge import LoanCharge
from .customer import Customer
from .account import Account
__all__ = ['Transaction', 'Repayment'] __all__ = ['Transaction', 'Repayment', 'Loan', 'LoanCharge', 'Customer', 'Account']
+25
View File
@@ -0,0 +1,25 @@
from datetime import datetime, timezone
from sqlalchemy.orm import relationship
from app.extensions import db
class Account(db.Model):
__tablename__ = 'accounts'
id = db.Column(db.String(50), primary_key=True)
customer_id = db.Column(db.String(50), nullable=False)
account_type = db.Column(db.String(50))
status = db.Column(db.String(20), default='active')
lien_amount = db.Column(db.Float, default=0.0)
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
customer = relationship(
"Customer",
primaryjoin="Customer.id == Account.customer_id",
foreign_keys=[customer_id],
back_populates="accounts",
)
def __repr__(self):
return f'<Account {self.id}>'
+41
View File
@@ -0,0 +1,41 @@
from datetime import datetime, timezone
from sqlalchemy.orm import relationship
from app.extensions import db
class Customer(db.Model):
__tablename__ = 'customers'
id = db.Column(db.String(50), primary_key=True)
msisdn = db.Column(db.String(20), unique=True, nullable=False)
country_code = db.Column(db.String(3), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
accounts = relationship(
"Account",
primaryjoin="Customer.id == Account.customer_id",
foreign_keys="Account.customer_id",
back_populates="customer",
)
loans = relationship(
"Loan",
primaryjoin="Customer.id == Loan.customer_id",
foreign_keys="Loan.customer_id",
back_populates="customer",
)
@classmethod
def get_customer(cls, customer_id):
"""
Get customer by ID.
"""
customer = cls.query.filter_by(id=customer_id).first()
if not customer:
raise ValueError(f"Customer does not exist")
return customer
def __repr__(self):
return f'<Customer {self.id}>'
+64
View File
@@ -0,0 +1,64 @@
from datetime import datetime, timezone
from app.extensions import db
from sqlalchemy.orm import relationship
class Loan(db.Model):
__tablename__ = "loans"
id = db.Column(
db.Integer,
primary_key=True,
autoincrement=True,
)
customer_id = db.Column(db.String(50), nullable=False)
transaction_id = db.Column(db.String(50), nullable=True)
account_id = db.Column(db.String(50), nullable=False)
offer_id = db.Column(db.String(20), nullable=False)
product_id = db.Column(db.String(20), nullable=True)
collection_type = db.Column(db.String(20), nullable=True)
current_loan_amount = db.Column(db.Float, nullable=True)
initial_loan_amount = db.Column(db.Float, nullable=False)
default_penalty_fee = db.Column(db.Float, default=0)
continuous_fee = db.Column(db.Float, default=0)
status = db.Column(db.String(20), default='pending')
due_date = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
customer = relationship(
"Customer",
primaryjoin="Customer.id == Loan.customer_id",
foreign_keys=[customer_id],
back_populates="loans",
)
loan_charges = relationship(
"LoanCharge",
primaryjoin="Loan.id == LoanCharge.loan_id",
foreign_keys="LoanCharge.loan_id",
back_populates="loan",
)
def __repr__(self):
return f"<Loan {self.id}>"
def to_dict(self):
"""
Convert the Loan object to a dictionary format for JSON serialization.
"""
return {
'debtId': self.id,
'initialLoanAmount': self.initial_loan_amount,
'currentLoanAmount': self.current_loan_amount,
'defaultPenaltyFee': self.default_penalty_fee,
'continuousFee': self.continuous_fee,
'collectionType': self.collection_type,
'status': self.status,
'productId': self.product_id,
'dueDate': self.due_date.isoformat() if self.due_date else None,
'loanDate': self.created_at.isoformat if self.created_at else None
}
@classmethod
def get_loan_by_transaction_id(cls, transaction_id):
return cls.query.filter_by(transaction_id=transaction_id).first()
+49
View File
@@ -0,0 +1,49 @@
from datetime import datetime, timezone, timedelta
from os.path import devnull
from app.extensions import db
from sqlalchemy.orm import relationship
class LoanCharge(db.Model):
__tablename__ = 'loan_charges'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
loan_id = db.Column(db.Integer, nullable=False)
transaction_id = db.Column(db.String(50), nullable=True)
code = db.Column(db.String(50), nullable=False)
amount = db.Column(db.Float, default=0.0)
percent = db.Column(db.Float, default=0.0)
description = db.Column(db.Text, nullable=True)
due = db.Column(db.Integer, nullable=False)
due_date = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
loan = relationship(
"Loan",
primaryjoin="LoanCharge.loan_id == Loan.id",
foreign_keys=[loan_id],
back_populates="loan_charges",
)
def __repr__(self):
return f"<LoanCharge {self.id} - Loan {self.loan_id} - {self.code}>"
def to_dict(self):
"""
Convert the Loan charge object to a dictionary format for JSON serialization.
"""
return {
'id': self.id,
'loanId': self.loan_id,
'transactionId': self.transaction_id,
'code': self.code,
'amount': self.amount,
'percent': self.percent,
'description': self.description,
'due': self.due
}
@classmethod
def get_loan_charge_by_debt_id(cls, debt_id):
return cls.query.filter_by(loan_id=debt_id)
+13
View File
@@ -20,6 +20,19 @@ class Transaction(db.Model):
def __repr__(self): def __repr__(self):
return f'<Transaction {self.id}>' return f'<Transaction {self.id}>'
def to_dict(self):
"""
Convert the Transaction object to a dictionary format for JSON serialization.
"""
return {
'id': self.id,
'transaction_id': self.transaction_id,
'account_id': self.account_id,
'customer_id': self.customer_id,
'type': self.type,
'channel': self.channel,
}
@classmethod @classmethod
def get_transaction_by_transaction_id(cls, transaction_id): def get_transaction_by_transaction_id(cls, transaction_id):
return cls.query.filter_by(transaction_id=transaction_id).first() return cls.query.filter_by(transaction_id=transaction_id).first()
+4 -4
View File
@@ -15,7 +15,7 @@ def health():
@auth_bp.route("/login", methods=["POST"]) @auth_bp.route("/login", methods=["POST"])
def login(): def login():
data = request.json data = request.get_json()
api_url = f"{BASE_URL}/login" api_url = f"{BASE_URL}/login"
response = requests.post(api_url, json=data) response = requests.post(api_url, json=data)
@@ -26,7 +26,7 @@ def login():
@auth_bp.route("/status-call", methods=["POST"]) @auth_bp.route("/status-call", methods=["POST"])
def status_call(): def status_call():
data = request.json data = request.get_json()
api_url = f"{BASE_URL}/StatusCall" api_url = f"{BASE_URL}/StatusCall"
# response = requests.post(api_url, json=data, headers=get_headers()) # response = requests.post(api_url, json=data, headers=get_headers())
@@ -49,7 +49,7 @@ def status_call():
@auth_bp.route("/sms", methods=["POST"]) @auth_bp.route("/sms", methods=["POST"])
def sms(): def sms():
data = request.json data = request.get_json()
api_url = f"{BASE_URL}/SMS" api_url = f"{BASE_URL}/SMS"
# response = requests.post(api_url, json=data, headers=get_headers()) # response = requests.post(api_url, json=data, headers=get_headers())
@@ -66,7 +66,7 @@ def sms():
@auth_bp.route("/bulk-sms", methods=["POST"]) @auth_bp.route("/bulk-sms", methods=["POST"])
def bulk_sms(): def bulk_sms():
data = request.json data = request.get_json()
api_url = f"{BASE_URL}/BulkSMS" api_url = f"{BASE_URL}/BulkSMS"
# response = requests.post(api_url, json=data, headers=get_headers()) # response = requests.post(api_url, json=data, headers=get_headers())
+14 -4
View File
@@ -1,6 +1,7 @@
from flask import Blueprint, request, jsonify, current_app from flask import Blueprint, request, jsonify, current_app
import requests import requests
from app.config import settings from app.config import settings
from app.helpers.response_helper import ResponseHelper
from app.utils.auth import get_headers from app.utils.auth import get_headers
from app.utils.logger import logger from app.utils.logger import logger
from app.integrations.simbrella import SimbrellaClient from app.integrations.simbrella import SimbrellaClient
@@ -14,7 +15,7 @@ def verify_transaction():
response = SimbrellaClient.verify_transaction() response = SimbrellaClient.verify_transaction()
return jsonify(response), 200 return response
@autocall_bp.route("/refresh-disbursement", methods=["GET"]) @autocall_bp.route("/refresh-disbursement", methods=["GET"])
def disbursement(): def disbursement():
@@ -23,14 +24,23 @@ def disbursement():
response = SimbrellaClient.verify_transaction() response = SimbrellaClient.verify_transaction()
return jsonify(response), 200 return response
@autocall_bp.route("/payment-callback", methods=["POST"]) @autocall_bp.route("/payment-callback", methods=["POST"])
def payment_callback(): def payment_callback():
data = request.json() data = request.get_json()
logger.info(f"Calling Callback Components") logger.info(f"Calling Callback Components")
response = SimbrellaClient.payment_callback(data) response = SimbrellaClient.payment_callback(data)
return jsonify(response), 200 return response
@autocall_bp.route("/penal-charge", methods=["POST"])
def penal_charge():
data = request.get_json()
logger.info(f"Calling Penal Charge Endpoints")
response = SimbrellaClient.penal_charge(data[0])
return response
+17
View File
@@ -0,0 +1,17 @@
from app.models import Loan, LoanCharge
class LoanService:
@classmethod
def get_loan_by_transaction_id(cls, transaction_id):
"""
Get the loan by transaction ID
"""
return Loan.get_loan_by_transaction_id(transaction_id)
@classmethod
def get_loan_charge_by_debt_id(cls, debt_id):
"""
Get the loan charge by debt ID
"""
return LoanCharge.get_loan_charge_by_debt_id(debt_id)
+16
View File
@@ -0,0 +1,16 @@
def preprocess_loan_charges_data(data):
"""
Preprocesses the data into a dictionary for efficient lookups by 'code'.
Args:
data: A list of dictionaries.
Returns:
A dictionary where keys are 'code' values and values are the corresponding dictionaries from the input data.
If multiple items have the same code, the last one encountered will be stored.
"""
preprocessed = {}
for item in data:
if 'code' in item:
preprocessed[item['code']] = item
return preprocessed
+42
View File
@@ -113,6 +113,48 @@ paths:
/autocall/payment-callback: /autocall/payment-callback:
get: get:
summary: The Payment callback summary: The Payment callback
responses:
200:
description: A successful response
/autocall/penal-charge:
post:
summary: Penal Charge Request
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
type: object
properties:
transactionId:
type: string
example: "T004"
fbnTransactionId:
type: string
example: "Tr201712RK9232P115"
debtId:
type: string
example: "273194670"
customerId:
type: string
example: "CN621868"
accountId:
type: string
example: "2017821799"
penalCharge:
type: number
example: "1.2"
lienAmount:
type: number
example: "101.2"
countryId:
type: string
example: "01"
comment:
type: string
example: "Testing PenalCharge"
responses: responses:
200: 200:
description: A successful response description: A successful response