59 Commits

Author SHA1 Message Date
VivianDee c550c8c356 [add]: loan total amount 2025-06-20 15:58:53 +01:00
ameye 3cf6c94786 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-20 13:30:08 +00:00
Chinenye Nmoh 79b22e6d4f changed amount to salaryAmount 2025-06-20 13:49:10 +01:00
ameye 1d0409d072 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-20 10:52:31 +00:00
Chinenye Nmoh bcd9513a10 added loop 2025-06-20 11:47:21 +01:00
Chinenye Nmoh 684833bd66 added loop 2025-06-19 23:23:06 +01:00
Chinenye Nmoh 7fbb659fc6 added salary table 2025-06-19 21:30:21 +01:00
vivian.d.simbrellang.com 3be765bf41 Merge branch 'pending_salaries' of DigiFi/digifi-EventManager into master 2025-06-19 12:25:26 +00:00
VivianDee 31e38da473 [add]: pending salaries 2025-06-19 12:49:15 +01:00
CHIEFSOFT\ameye a105cf2a99 collect_loan_user_salary_detect 2025-06-19 06:54:54 -04:00
CHIEFSOFT\ameye cc826a0469 added return 2025-06-19 06:52:07 -04:00
CHIEFSOFT\ameye 45678d8288 start added 2025-06-19 06:46:49 -04:00
CHIEFSOFT\ameye 11aab30a19 salaryAmount 2025-06-19 06:09:52 -04:00
CHIEFSOFT\ameye 500e749acb Reafctor import 2025-06-18 23:29:09 -04:00
ameye 405c837499 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-18 22:05:28 +00:00
Chinenye Nmoh ad25be1856 added salary table 2025-06-18 22:25:45 +01:00
CHIEFSOFT\ameye 8755ed0825 added URL 2025-06-18 14:03:41 -04:00
CHIEFSOFT\ameye e8e52480b1 swagger update 2025-06-18 12:21:56 -04:00
ameye 36cddd9327 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-17 16:17:25 +00:00
Chinenye Nmoh 9158a8c3cd added endpoint to swagger 2025-06-17 15:35:07 +01:00
CHIEFSOFT\ameye b574c985db collect_loan_user_initiated 2025-06-17 07:18:11 -04:00
CHIEFSOFT\ameye e87ceabe47 salary detect 2025-06-17 07:11:11 -04:00
CHIEFSOFT\ameye 68669784de Added Call Paths 2025-06-17 06:58:24 -04:00
ameye 257c84e4c1 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-16 20:32:56 +00:00
Chinenye Nmoh 87a876252d added repayment data 2025-06-16 19:50:24 +01:00
ameye e0ca6f0e1c Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-10 18:03:30 +00:00
Chinenye Nmoh fc19f39378 added repayment data to db 2025-06-10 18:22:50 +01:00
ameye 217b63efcf Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-06 23:05:47 +00:00
Chinenye Nmoh b2816b947e added collect loan 2025-06-06 14:10:58 -04:00
Chinenye Nmoh 321de4bd6f added collect loan 2025-06-06 14:02:35 -04:00
Chinenye Nmoh c0d5893a9b added collect loan 2025-06-06 13:56:50 -04:00
ameye 08e53f4834 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-05 19:54:24 +00:00
Chinenye Nmoh b8a40bb638 added verify date 2025-06-05 20:03:18 +01:00
CHIEFSOFT\ameye 8c5be70a02 added 2 endpoint 2025-06-05 10:50:19 -04:00
CHIEFSOFT\ameye 50769f7faf ref 2025-06-05 07:13:35 -04:00
ameye 67363ff6e5 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-04 22:58:22 +00:00
Chinenye Nmoh 680ec9e9e0 made loan data dynamic and populated result fields 2025-06-04 18:55:53 +01:00
ameye 5bbcaefe7f Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-06-03 19:13:31 +00:00
Chinenye Nmoh 15e012c071 commited loan date 2025-06-03 17:14:02 +01:00
Chinenye Nmoh 2872f6c75c returned disbured_date 2025-06-03 11:16:26 +01:00
CHIEFSOFT\ameye 4619f83d46 Fix default topics 2025-06-02 12:08:19 -04:00
CHIEFSOFT\ameye 9fc3104c79 Verify transation s 2025-05-31 21:44:52 -04:00
CHIEFSOFT\ameye 1a4f0c1332 python-dateutil 2025-05-30 20:50:20 -04:00
CHIEFSOFT\ameye ef2dbc3800 Include 2025-05-30 20:47:53 -04:00
CHIEFSOFT\ameye cf2e1fc2a7 get loan 2025-05-30 20:36:44 -04:00
CHIEFSOFT\ameye 8c50e5a98f loand ids 2025-05-30 20:27:05 -04:00
CHIEFSOFT\ameye 03a30577d7 collect call 2025-05-30 18:15:31 -04:00
CHIEFSOFT\ameye c8685b7b8c Repay data 2025-05-30 18:07:15 -04:00
CHIEFSOFT\ameye fb28fd9bbf Loan data logs 2025-05-30 17:58:18 -04:00
CHIEFSOFT\ameye 240f9c3d32 Url config 2025-05-30 17:50:08 -04:00
CHIEFSOFT\ameye 0591cd6073 Api url 2025-05-30 17:37:57 -04:00
CHIEFSOFT\ameye fad8315d5e Updated the loan service end 2025-05-30 17:26:00 -04:00
ameye 38c7bf16d4 Merge branch 'EVN-Dsibursement-data-clean-up-' of DigiFi/digifi-EventManager into master 2025-04-26 23:48:59 +00:00
ameye 8760b9ba93 Started cleaning disbursement data 2025-04-26 19:47:22 -04:00
ameye a6054e5a26 Merge branch 'oluyemi' of DigiFi/digifi-EventManager into master 2025-04-17 21:07:23 +00:00
ameye 6a68041b08 Merge branch 'oluyemi' of DigiFi/digifi-EventManager into master 2025-04-16 20:05:34 +00:00
ameye eca34b77ab Merge branch 'oluyemi' of DigiFi/digifi-EventManager into master 2025-04-16 09:42:30 +00:00
ameye c39d68a28f Merge branch 'oluyemi' of DigiFi/digifi-EventManager into master 2025-04-15 20:44:47 +00:00
ameye 5a5b418354 Merge branch 'oluyemi' of DigiFi/digifi-EventManager into master 2025-04-15 16:06:06 +00:00
18 changed files with 1136 additions and 75 deletions
+7 -3
View File
@@ -6,12 +6,11 @@ class Config:
"""Base configuration for Flask app""" """Base configuration for Flask app"""
SECRET_KEY = os.getenv("SECRET_KEY", "supersecretkey") SECRET_KEY = os.getenv("SECRET_KEY", "supersecretkey")
BANK_CALL_BASE_URL = "https://bank-emulator.dev.simbrellang.net"
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your_jwt_secret") JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your_jwt_secret")
DEBUG = True DEBUG = True
KAFKA_BROKER = os.getenv("KAFKA_BROKER", "dev-events.simbrellang.net:9085") KAFKA_BROKER = os.getenv("KAFKA_BROKER", "dev-events.simbrellang.net:9085")
KAFKA_TOPICS = [topic.strip() for topic in os.getenv("KAFKA_TOPICS", "").split(",") if topic.strip()] KAFKA_TOPICS = [topic.strip() for topic in os.getenv("KAFKA_TOPICS", "PROCESS_PAYMENT,LOAN_REPAYMENT").split(",") if topic.strip()]
KAFKA_TIMEOUT = float( os.getenv("KAFKA_TIMEOUT", 1000.0) ) KAFKA_TIMEOUT = float( os.getenv("KAFKA_TIMEOUT", 1000.0) )
JWT_ACCESS_TOKEN_EXPIRES = os.getenv("JWT_ACCESS_TOKEN_EXPIRES", timedelta(hours=1)) JWT_ACCESS_TOKEN_EXPIRES = os.getenv("JWT_ACCESS_TOKEN_EXPIRES", timedelta(hours=1))
@@ -37,6 +36,11 @@ class Config:
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}" SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
BANK_CALL_BASE_URL = os.getenv("BANK_CALL_BASE_URL", "https://bank-emulator.dev.simbrellang.net")
BANK_CALL_SMS_BASE_URL= os.getenv("BANK_CALL_SMS_BASE_URL","https://first-advance-middleware-develop.fbn-devops-dev-asenv.appserviceenvironment.net/SMS")
BANK_CALL_DISBURSE_LOAN_ENDPOINT = os.getenv("BANK_CALL_DISBURSE_LOAN_ENDPOINT","/DisburseLoan")
BANK_CALL_COLLECT_LOAN_ENDPOINT = os.getenv("BANK_CALL_COLLECT_LOAN_ENDPOINT","/CollectLoan")
BANK_CALL_TRANSACTION_VERIFY = os.getenv("BANK_CALL_TRANSACTION_VERIFY", "/TransactionVerify")
TEST_NO = os.getenv("TEST_NO", "2347038224367")
settings = Config() settings = Config()
+7 -2
View File
@@ -127,10 +127,15 @@ class KafkaIntegration:
logger.info(f"Calling disbursement service with message: {message}") logger.info(f"Calling disbursement service with message: {message}")
try: try:
response = SimbrellaClient.disbursement(message) response = SimbrellaClient.disburse_loan(message)
logger.info( logger.info(
f"Successfully sent message to disbursement service: {response}" f"Successfully sent message to disbursement service: {response}"
) )
# LoanService.set_disbursement_date(loan_id=loan_data['debtId'],
# customer_id=customerId) # must mark it on way out
#
except Exception as e: except Exception as e:
logger.info(f"Failed to call disbursement service: {e}") logger.info(f"Failed to call disbursement service: {e}")
#raise #raise
@@ -141,7 +146,7 @@ class KafkaIntegration:
logger.info(f"Calling collect_loan service with message: {message}") logger.info(f"Calling collect_loan service with message: {message}")
try: try:
response = SimbrellaClient.collect_loan(message) response = SimbrellaClient.collect_loan_user_initiated(message)
logger.info( logger.info(
f"Successfully sent message to collect_loan service: {response}" f"Successfully sent message to collect_loan service: {response}"
) )
+208 -53
View File
@@ -8,21 +8,29 @@ 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
from app.services.repayment import RepaymentService from app.services.repayment import RepaymentService
from app.extensions import db
from app.services.repayments_data import RepaymentsData
from app.services.salary import SalaryService
class SimbrellaClient: class SimbrellaClient:
BANK_CALL_BASE_URL = settings.BANK_CALL_BASE_URL BANK_CALL_BASE_URL = settings.BANK_CALL_BASE_URL
BANK_CALL_SMS_BASE_URL = settings.BANK_CALL_SMS_BASE_URL
BANK_CALL_DISBURSE_LOAN_ENDPOINT = settings.BANK_CALL_DISBURSE_LOAN_ENDPOINT
BANK_CALL_COLLECT_LOAN_ENDPOINT = settings.BANK_CALL_COLLECT_LOAN_ENDPOINT
BANK_CALL_TRANSACTION_VERIFY = settings.BANK_CALL_TRANSACTION_VERIFY
@staticmethod @staticmethod
def disbursement(data): def disburse_loan(data):
api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}/Disbursement" api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}/{SimbrellaClient.BANK_CALL_DISBURSE_LOAN_ENDPOINT}"
logger.info(f"Calling Disbursement endpoint with data: {data}") logger.info(f"Calling DisburseLoan api_url==> : {api_url}")
logger.info(f"Calling DisburseLoan endpoint with data: {data}")
# Check if the transaction exists # Check if the transaction exists
logger.info(f"Checking if transaction exists") logger.info(f"Checking if transaction exists")
transaction = TransactionService.get_transaction_by_transaction_id(transaction_id=data['transactionId']) transaction = TransactionService.get_transaction_by_transaction_id(transaction_id=data['transactionId'])
logger.info(f"Response from database: {transaction}") logger.info(f"Loan Response From Database ** : {transaction}")
# If transaction is not found # If transaction is not found
if not transaction: if not transaction:
@@ -42,24 +50,36 @@ class SimbrellaClient:
loan_data = loan.to_dict() loan_data = loan.to_dict()
logger.info(f"Here is your loan data: {loan_data}") logger.info(f"Here is your loan data: {loan_data}")
if loan_data['disburseDate'] is not None:
logger.info(
f"Please call verify loan : {data['transactionId']} loan send for processing at {loan_data['disburseDate']}")
return 0
# let us set disbursement date
LoanService.set_disbursement_date(loan_data['debtId'],loan_data['customerId']) # toda this must return something
logger.info(f"Here is your loan data after setting disbursement date: {loan_data}")
loan_charges = preprocess_loan_charges_data([loan_charge.to_dict() for loan_charge in loan.loan_charges]) 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}") logger.info(f"Here are your loan_charges: {loan_charges}")
mgt_fee = loan_charges.get("MGTFEE")['amount'] mgt_fee = loan_charges.get("MGTFEE")['amount']
vat_fee = loan_charges.get("VAT")['amount'] vat_fee = loan_charges.get("VAT")['amount']
interest_fee = loan_charges.get("INTEREST")['amount']
insurance_fee = loan_charges.get("INSURANCE")['amount']
disbursement_data ={ debtId = str(loan_data.get('debtId', "")).strip().zfill(6)
"requestId": data['requestId'],
"transactionId": data['transactionId'], disbursement_data = {
"debtId": loan_data['debtId'], "transactionId": loan_data.get('transactionId'),
"customerId": data['customerId'], "FbnTransactionId": loan_data.get('transactionId'),
"accountId": data['accountId'], "debtId": debtId,
"productId": loan_data['productId'], "customerId": loan_data.get('customerId'),
"provideAmount": loan_data['currentLoanAmount'], "accountId": loan_data.get('accountId'),
"collectAmountInterest": 5000, "productId": str(loan_data.get('productId', "")),
"provideAmount": loan_data.get('currentLoanAmount'),
"collectAmountInterest": interest_fee,
"collectAmountMgtFee": mgt_fee, "collectAmountMgtFee": mgt_fee,
"collectAmountInsurance": 1000, "collectAmountInsurance": insurance_fee,
"collectAmountVAT": vat_fee, "collectAmountVAT": vat_fee,
"countryId": "01", "countryId": "01",
"comment": "Loan Disbursement", "comment": "Loan Disbursement",
@@ -69,7 +89,9 @@ class SimbrellaClient:
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())
logger.info(f"Disbursement response: {response.json()}") logger.info(f"Disbursement response: {response.json()}")
result = response.json()
LoanService.set_disbursement_result(loan_data['debtId'],result.get('responseCode', ''), result.get('responseMessage', ''))
return ResponseHelper.success(response.json(), "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}")
return 0 return 0
@@ -77,59 +99,192 @@ class SimbrellaClient:
return 1 return 1
@staticmethod @staticmethod
def collect_loan(data): def verify_transaction(data):
api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}/CollectLoan" api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}{SimbrellaClient.BANK_CALL_TRANSACTION_VERIFY}"
sms_url = f"{SimbrellaClient.BANK_CALL_SMS_BASE_URL}/singleSMS"
logger.info(f"Calling TransactionVerify api_url==> : {api_url}")
# Check if the transaction exists
logger.info(f"Checking if transaction exists")
transaction = TransactionService.get_transaction_by_transaction_id(transaction_id=data['transactionId'])
transaction_data = transaction.to_dict()
logger.info(f"Loan Response From Database ** : {transaction}")
# If transaction is not found
if not transaction:
logger.info(f"Transaction id: {data['transactionId']}, was not found")
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}")
if loan_data['disburseDate'] is not None and loan_data['disburseVerify'] is None :
LoanService.set_disburse_verify_date(loan_data['debtId'],loan_data['customerId'])
loan_data = loan.to_dict()
logger.info(f"Here is your loan data after setting verify date: {loan_data}")
logger.info(f"Good to Verify transaction id: {data['transactionId']}")
else:
logger.info(
f"Please call disburse loan : {data['transactionId']} loan send for processing first")
return 0
verify_data = {
"customerId": loan_data.get('customerId'),
"accountId": loan_data.get('accountId'),
"transactionId": loan_data.get('transactionId'),
"transactionType": "provide",
"fbnTransactionId": loan_data.get('transactionId'),
"countryId": "NG",
"requestId": loan_data.get('transactionId')
}
try:
logger.info(f"Here is your TransactionVerify Request data ****** : {verify_data}")
response = requests.post(api_url, json=verify_data, timeout=10, headers=get_headers())
result = response.json()
logger.info(f"this is verify result, {result}")
LoanService.set_disburse_verify_result(loan_data['debtId'],result.get('responseCode', ''), result.get('responseMessage', ''))
sms_data = {
"dest": transaction_data.get('phone_number') or settings.TEST_NO,
"text": f"Transaction {loan_data.get('transactionId')} verified successfully",
"unicode": True
}
try:
sms_response = requests.post(sms_url, json=sms_data, timeout=10, headers=get_headers())
sms_response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
result = sms_response.json()
logger.info(f"SMS Response JSON: {result}")
if result.get('isSuccess'):
logger.info(f"sms sent successfully")
return ResponseHelper.success(response.json(), "Successful")
logger.info(f"sms failed!")
return 1
except requests.RequestException as e:
# Handle the exception
logger.error(f"Failed to send SMS: {e}")
return 0
except Exception as e:
logger.info(f"Failed to call TransactionVerify endpoint: {e}")
return 0
@staticmethod
def collect_loan_user_initiated(data):
# InitiatedBy = USER_INITIATED
logger.info(f"Calling CollectLoan collect_loan_user_initiated ******* endpoint with data: {data}")
return SimbrellaClient._collect_loan(data,"1")
@staticmethod
def collect_loan_user_salary_detect(data):
try:
return SimbrellaClient._collect_loan(data, "2")
except Exception as e:
logger.error(f"Error in collect_loan_user_salary_detect: {e}")
return ResponseHelper.error(
message="Failed to collect loan for salary detection",
status_code=500,
error=str(e)
)
@staticmethod
def collect_loan_user_due_payment(data):
# InitiatedBy = REPAYMENT_DUE
return SimbrellaClient._collect_loan(data,"3")
@staticmethod
def _collect_loan(data, collectionMethod: str):
api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}{SimbrellaClient.BANK_CALL_COLLECT_LOAN_ENDPOINT}"
logger.info(f"Calling CollectLoan api_url==> : {api_url}")
logger.info(f"Calling CollectLoan endpoint with data: {data}") logger.info(f"Calling CollectLoan endpoint with data: {data}")
# Check if the repayment exists # Check if the repayment exists
logger.info(f"Checking if repayment exists") logger.info(f"Checking if repayment exists")
repayment = RepaymentService.get_repayment_by_transaction_id(transaction_id=data['transactionId']) repayment = RepaymentService.get_repayment_by_id(id=data['Id'])
logger.info(f"Response from database: {repayment}") logger.info(f"Repayment Response From Database ** : {repayment}")
# If repayment is not found
if not repayment: if not repayment:
logger.info(f"Repayment id: {data['transactionId']}, was not found") logger.info(f"Repayment with id: {data['Id']}, was not found")
return 0 return ResponseHelper.error("Repayment not found")
logger.info(f"Repayment Response From Database ** : {repayment.to_dict()}")
repayment_data = repayment.to_dict()
loan = LoanService.get_loan_by_loan_id(loan_id=int(repayment_data['loanId']))
# If loan is not found
if loan is None:
logger.info(f"Loan with debtId: {repayment_data['loanId']}, was not found")
return ResponseHelper.error("Loan not found")
loan_data = loan.to_dict()
logger.info(f"loan dict : {loan_data}")
if repayment_data['repayDate'] is not None:
logger.info(
f"Please call verify collection : {data['transactionId']} repayment send for processing at {repayment_data['repayDate']}")
return ResponseHelper.error("Repayment already processed")
# let us set repay date
RepaymentService.set_repay_date(repayment_data['Id'], repayment_data['customerId'])
repayment = RepaymentService.get_repayment_by_transaction_id(transaction_id=data['transactionId'])
repayment_data = repayment.to_dict()
logger.info(f"Here is your repayment data after setting repay date: {repayment_data}")
debtId = str(loan_data.get('debtId', "")).strip().zfill(6)
collect_loan_data = { collect_loan_data = {
"transactionId": "T002", "transactionId": repayment_data['transactionId'],
"fbnTransactionId": "FBN20231123", "fbnTransactionId": loan_data['transactionId'],
"debtId": data['debtId'], "debtId": debtId,
"customerId": data['customerId'], "customerId": repayment_data['customerId'],
"accountId": "2017821799", "accountId": loan_data['accountId'],
"productId": data['productId'], "productId": repayment_data['productId'],
"collectAmount": 80000, "collectAmount": loan_data['repaymentAmount'] or 0,
"penalCharge": 0, "penalCharge": 5,
"collectionMethod": 1, "channel": "USSD",
"lienAmount": 80000, "collectionMethod": collectionMethod,
"countryId": "01", "lienAmount": 0,
"comment": "Testing CollectionLoanRequest" "countryId": "NG",
"comment": "COLLECT LOAN"
} }
try: try:
logger.info(f"Here is your CollectLoan Request data ***** : {collect_loan_data}") logger.info(f"Here is your CollectLoan Request data ***** : {collect_loan_data}")
response = requests.post(api_url, json=collect_loan_data, headers=get_headers()) response = requests.post(api_url, json=collect_loan_data,timeout=30, headers=get_headers())
logger.info(f"CollectLoan response: {response.json()}") logger.info(f"CollectLoan response: {response.json()}")
RepaymentService.set_repay_result(repayment_data['Id'], response.json().get('responseCode', ''), response.json().get('responseMessage', ''))
result = response.json()
logger.info(f"this is the result {result}")
data_to_add = {
"transactionId": result.get('transactionId') or collect_loan_data.get('transactionId'),
"fbnTransactionId": result.get('fbnTransactionId') or collect_loan_data.get('fbnTransactionId'),
"accountId": result.get('accountId') or collect_loan_data.get('accountId'),
"customerId": result.get('customerId') or collect_loan_data.get('customerId'),
"amountCollected": result.get('amountCollected'),
"repaymentAmount": collect_loan_data.get('collectAmount'),
"responseCode": result.get('responseCode'),
"responseDescr": result.get('responseMessage'),
"balance":result.get('lienAmount')
}
new_repayment_data = RepaymentsData.add_repayment_data(data_to_add)
logger.info(f"Repayment data added successfully: {new_repayment_data.to_dict()}")
if not new_repayment_data:
logger.info(f"Failed to add repayment data")
return ResponseHelper.success(response.json(), "Successful")
except Exception as e: except Exception as e:
logger.info(f"Failed to call CollectLoan endpoint: {e}") logger.info(f"Failed to call CollectLoan endpoint: {e}")
return 0 return ResponseHelper.error("Failed to call CollectLoan endpoint")
return 1
@staticmethod
def verify_transaction():
try:
data = {
"status": "00",
"message": "Transaction verified"
}
return ResponseHelper.success(data, "Successful")
except Exception as e:
logger.info(f"Failed to call TransactionVerify endpoint: {e}")
raise
@staticmethod @staticmethod
def refresh_disbursement(data): def refresh_disbursement(data):
+3 -1
View File
@@ -4,5 +4,7 @@ from .loan import Loan
from .loan_charge import LoanCharge from .loan_charge import LoanCharge
from .customer import Customer from .customer import Customer
from .account import Account from .account import Account
from .repayments_data import RepaymentsData
from .salary import Salary
__all__ = ['Transaction', 'Repayment', 'Loan', 'LoanCharge', 'Customer', 'Account'] __all__ = ['Transaction', 'Repayment', 'Loan', 'LoanCharge', 'Customer', 'Account', 'RepaymentsData','Salary']
+180 -4
View File
@@ -1,6 +1,13 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from app.extensions import db from app.extensions import db
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from dateutil.relativedelta import relativedelta
from datetime import timedelta
import logging
from sqlalchemy import and_, or_, not_
from sqlalchemy.sql import func
from app.utils.logger import logger
from app.extensions import db
class Loan(db.Model): class Loan(db.Model):
__tablename__ = "loans" __tablename__ = "loans"
@@ -12,6 +19,7 @@ class Loan(db.Model):
) )
customer_id = db.Column(db.String(50), nullable=False) customer_id = db.Column(db.String(50), nullable=False)
transaction_id = db.Column(db.String(50), nullable=True) transaction_id = db.Column(db.String(50), nullable=True)
original_transaction = db.Column(db.String(50), nullable=True)
account_id = db.Column(db.String(50), nullable=False) account_id = db.Column(db.String(50), nullable=False)
offer_id = db.Column(db.String(20), nullable=False) offer_id = db.Column(db.String(20), nullable=False)
product_id = db.Column(db.String(20), nullable=True) product_id = db.Column(db.String(20), nullable=True)
@@ -20,10 +28,22 @@ class Loan(db.Model):
initial_loan_amount = db.Column(db.Float, nullable=False) initial_loan_amount = db.Column(db.Float, nullable=False)
default_penalty_fee = db.Column(db.Float, default=0) default_penalty_fee = db.Column(db.Float, default=0)
continuous_fee = db.Column(db.Float, default=0) continuous_fee = db.Column(db.Float, default=0)
upfront_fee = db.Column(db.Float, nullable=True, default=0.0)
repayment_amount = db.Column(db.Float, nullable=True, default=0.0)
installment_amount = db.Column(db.Float, nullable=True, default=0.0)
status = db.Column(db.String(20), default='pending') status = db.Column(db.String(20), default='pending')
tenor = db.Column(db.Integer, nullable=True)
due_date = db.Column(db.DateTime, nullable=True) due_date = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
eligible_amount = db.Column(db.Float, nullable=True, default=0.0)
disburse_date = db.Column(db.DateTime, nullable=True)
disburse_verify = db.Column(db.DateTime, nullable=True)
disburse_result = db.Column(db.String(10), nullable=True)
disburse_description = db.Column(db.String(100), nullable=True)
verify_result = db.Column(db.String(10), nullable=True)
verify_description = db.Column(db.String(100), nullable=True)
reference = db.Column(db.String(50), nullable=True)
customer = relationship( customer = relationship(
"Customer", "Customer",
@@ -48,17 +68,173 @@ class Loan(db.Model):
""" """
return { return {
'debtId': self.id, 'debtId': self.id,
"customerId": self.customer_id,
'initialLoanAmount': self.initial_loan_amount, 'initialLoanAmount': self.initial_loan_amount,
'currentLoanAmount': self.current_loan_amount, 'currentLoanAmount': self.current_loan_amount,
'defaultPenaltyFee': self.default_penalty_fee, 'defaultPenaltyFee': self.default_penalty_fee,
'continuousFee': self.continuous_fee, 'continuousFee': self.continuous_fee,
'collectionType': self.collection_type, 'collectionType': self.collection_type,
'repaymentAmount':self.repayment_amount,
'status': self.status, 'status': self.status,
'productId': self.product_id, 'productId': self.product_id,
'disburseResult': self.disburse_result,
'disburseDescription': self.disburse_description,
'verifyResult': self.verify_result,
'verifyDescription': self.verify_description,
'transactionId': self.transaction_id,
'accountId':self.account_id,
'dueDate': self.due_date.isoformat() if self.due_date else None, 'dueDate': self.due_date.isoformat() if self.due_date else None,
'loanDate': self.created_at.isoformat if self.created_at else None 'loanDate': self.created_at.isoformat() if self.created_at else None,
'disburseDate': self.disburse_date.isoformat() if self.disburse_date else None,
'disburseVerify': self.disburse_verify.isoformat() if self.disburse_verify else None,
'reference': self.reference
} }
@classmethod @classmethod
def get_loan_by_transaction_id(cls, transaction_id): def get_loan_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()
@classmethod
def get_loan_by_loan_id(cls, loan_id):
return cls.query.filter_by(id=loan_id).first()
@classmethod
def set_disbursement_date(cls, loan_id, customer_id):
"""
Update the disburse date of the loan with the given loan_id.
"""
# Retrieve loan
loan = cls.query.get(loan_id)
if not loan:
raise ValueError(f"Loan with ID {loan_id} does not exist.")
# Check if customer_id matches
if loan.customer_id != customer_id:
raise ValueError(f"Customer ID {customer_id} does not match the loan's customer ID.")
current_time = datetime.now()
logger.info(f"What is now ======= ==== ==> : {current_time}")
# Update loan disburse_date
loan.disburse_date = current_time
# Commit changes to database
try:
logger.info(f"Updating disburse date for loan ID {loan_id} to {current_time}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update disburse date: {e}")
raise
@classmethod
def set_disburse_verify_date(cls, loan_id, customer_id):
"""
Update the disburse verify date of the loan with the given loan_id.
"""
# Retrieve loan
loan = cls.query.get(loan_id)
if not loan:
raise ValueError(f"Loan with ID {loan_id} does not exist.")
# Check if customer_id matches
if loan.customer_id != customer_id:
raise ValueError(f"Customer ID {customer_id} does not match the loan's customer ID.")
current_time = datetime.now()
logger.info(f"What is now ======= ==== ==> : {current_time}")
# Update loan verify_date
loan.disburse_verify = current_time
# Commit changes to database
try:
logger.info(f"Updating disburse verify date for loan ID {loan_id} to {current_time}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update disburse verify date: {e}")
raise
@classmethod
def set_disbursement_result(cls, loan_id, result, description):
"""
Update the disburse result and description of the loan with the given loan_id.
"""
# Retrieve loan
loan = cls.query.get(loan_id)
if not loan:
raise ValueError(f"Loan with ID {loan_id} does not exist.")
# Update disburse result and description
loan.disburse_result = result
loan.disburse_description = description
# Commit changes to database
try:
logger.info(f"Updating disburse result for loan ID {loan_id} to {result} with description {description}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update disbursement result: {e}")
raise
@classmethod
def set_disburse_verify_result(cls, loan_id, result, description):
"""
Update the verify result and description of the loan with the given loan_id.
"""
# Retrieve loan
loan = cls.query.get(loan_id)
if not loan:
raise ValueError(f"Loan with ID {loan_id} does not exist.")
# Update disburse result and description
loan.verify_result = result
loan.verify_description = description
# Commit changes to database
try:
logger.info(f"Updating verify result for loan ID {loan_id} to {result} with description {description}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update verify result: {e}")
raise
@classmethod
def get_latest_loan_without_disburse_date(cls):
"""
Get the latest loan without a disbursement date.
"""
return cls.query.filter(
cls.disburse_date.is_(None)
).order_by(cls.created_at.desc()).first()
@classmethod
def get_latest_loan_with_disburse_date(cls):
"""
Get the latest loan with a disbursement date and no verification date.
"""
return cls.query.filter(
cls.disburse_date.isnot(None),
cls.disburse_verify.is_(None)
).order_by(cls.created_at.desc()).first()
@classmethod
def get_customer_loans(cls, customer_id):
"""
Get customer's active loans and sum by customer_id.
"""
customer_loans = cls.query.filter_by( customer_id = customer_id).all()
if not customer_loans:
raise ValueError(f"Customer with Id {customer_id} does not have any loan.")
total_amount = (
db.session.query(func.coalesce(func.sum(cls.current_loan_amount), 0.0))
.filter_by(customer_id=customer_id)
.scalar()
)
logger.info(f"Found {len(customer_loans)} loans for customer ID: {customer_id} with total amount: {total_amount}")
return customer_loans, total_amount
+207 -2
View File
@@ -1,5 +1,6 @@
from app.extensions import db from app.extensions import db
from datetime import datetime, timezone from datetime import datetime, timezone
from app.utils.logger import logger
class Repayment(db.Model): class Repayment(db.Model):
__tablename__ = "repayments" __tablename__ = "repayments"
@@ -13,12 +14,216 @@ class Repayment(db.Model):
customer_id = db.Column(db.String(50), nullable=False) customer_id = db.Column(db.String(50), nullable=False)
product_id = db.Column(db.String(20), nullable=True) product_id = db.Column(db.String(20), nullable=True)
transaction_id = db.Column(db.String(50), nullable=False) transaction_id = db.Column(db.String(50), nullable=False)
initiated_by = db.Column(db.String(50), nullable=True)
salary_amount = db.Column(db.Float, nullable=True, default=0.0)
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) 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)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
repay_date = db.Column(db.DateTime, nullable=True)
verify_date = db.Column(db.DateTime, nullable=True)
repay_result = db.Column(db.String(10), nullable=True)
repay_description = db.Column(db.String(100), nullable=True)
verify_result = db.Column(db.String(10), nullable=True)
verify_description = db.Column(db.String(100), nullable=True)
def __repr__(self): def __repr__(self):
return f'<Repayment {self.id}>' return f'<Repayment {self.id}>'
def to_dict(self):
"""
Convert the Repayment object to a dictionary format for JSON serialization.
"""
return {
'Id': self.id,
"customerId": self.customer_id,
'loanId': self.loan_id,
'productId': self.product_id,
'repayResult': self.repay_result,
'repayDescription': self.repay_description,
'verifyResult': self.verify_result,
'verifyDescription': self.verify_description,
'transactionId': self.transaction_id,
'initiatedBy':self.initiated_by,
'salaryAmount':self.salary_amount,
'repayDate': self.repay_date.isoformat() if self.repay_date else None,
'VerifyDate': self.verify_date.isoformat() if self.verify_date else None,
}
@classmethod
def add_repayment(cls, data: dict):
"""
Create and persist a new repayment record.
"""
logger.info(f"Received repayment data: {data}")
try:
new_repayment = cls(
loan_id=data["loanId"],
customer_id=data["customerId"],
product_id=data.get("productId"),
transaction_id=data["transactionId"],
initiated_by=data.get("initiatedBy"),
salary_amount=float(data.get("salaryAmount", 0.0)),
repay_date=(
datetime.strptime(data["repayDate"], "%Y-%m-%d")
.replace(tzinfo=timezone.utc)
if data.get("repayDate")
else None
),
repay_result=data.get("repayResult"),
repay_description=data.get("repayDescription"),
verify_result=data.get("verifyResult"),
verify_description=data.get("verifyDescription"),
verify_date=(
datetime.strptime(data["verifyDate"], "%Y-%m-%d")
.replace(tzinfo=timezone.utc)
if data.get("verifyDate")
else None
),
)
db.session.add(new_repayment)
db.session.commit()
logger.info("Repayment record committed.")
return new_repayment
except Exception as e:
db.session.rollback()
logger.error(f"Error adding repayment data: {e}")
raise
@classmethod @classmethod
def get_repayment_by_transaction_id(cls, transaction_id): def get_repayment_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()
@classmethod
def get_repayment_by_id(cls, id):
return cls.query.filter_by(id=id).first()
@classmethod
def set_repay_date(cls, repayment_id, customer_id):
"""
Update the repay date of the loan with the given loan_id.
"""
# Retrieve repayment
repayment = cls.query.get(repayment_id)
if not repayment:
raise ValueError(f"repayment with ID {repayment_id} does not exist.")
# Check if customer_id matches
if repayment.customer_id != customer_id:
raise ValueError(f"Customer ID {customer_id} does not match the repayment's customer ID.")
current_time = datetime.now()
logger.info(f"What is now ======= ==== ==> : {current_time}")
# Update repayment date
repayment.repay_date = current_time
# Commit changes to database
try:
logger.info(f"Updating repay date for repayment ID {repayment_id} to {current_time}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update repay date: {e}")
raise
@classmethod
def set_repay_verify_date(cls, repayment_id, customer_id):
"""
Update the repayment verify date of the loan with the given repayment_id.
"""
# Retrieve repayment
repayment = cls.query.get(repayment_id)
if not repayment:
raise ValueError(f"repayment with ID {repayment_id} does not exist.")
# Check if customer_id matches
if repayment.customer_id != customer_id:
raise ValueError(f"Customer ID {customer_id} does not match the repayment's customer ID.")
current_time = datetime.now()
logger.info(f"What is now ======= ==== ==> : {current_time}")
# Update repayment verify_date
repayment.verify_date = current_time
# Commit changes to database
try:
logger.info(f"Updating repay verify date for repayment ID {repayment_id} to {current_time}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update repay verify date: {e}")
raise
@classmethod
def set_repay_result(cls, repayment_id, result, description):
"""
Update the repayment result and description of the repayment with the given repayment_id.
"""
# Retrieve loan
repayment = cls.query.get(repayment_id)
if not repayment:
raise ValueError(f"repayment with ID {repayment_id} does not exist.")
# Update repayment result and description
repayment.repay_result = result
repayment.repay_description = description
# Commit changes to database
try:
logger.info(f"Updating repayment result for repayment ID {repayment_id} to {result} with description {description}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update repayment result: {e}")
raise
@classmethod
def set_verify_date_result(cls, repayment_id, result, description):
"""
Update the verify result and description of the repayment with the given repayment_id.
"""
# Retrieve repayment
repayment = cls.query.get(repayment_id)
if not repayment:
raise ValueError(f"repayment with ID {repayment_id} does not exist.")
# Update disburse result and description
repayment.verify_result = result
repayment.verify_description = description
# Commit changes to database
try:
logger.info(f"Updating verify result for repayment ID {repayment_id} to {result} with description {description}")
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Failed to update verify result: {e}")
raise
@classmethod
def get_latest_repayment_without_repay_date(cls):
"""
Get the latest repayment without a repay date.
"""
return cls.query.filter(
cls.repay_date.is_(None)
).order_by(cls.created_at.desc()).first()
@classmethod
def get_latest_repayment_with_loanId(cls, loan_id):
"""
Get the latest repayment with loan Id.
"""
return cls.query.filter(
cls.loan_id == loan_id
).order_by(cls.created_at.desc()).first()
@classmethod
def get_latest_loan_with_repay_date(cls):
"""
Get the latest repayment with a repay date and no verification date.
"""
return cls.query.filter(
cls.repay_date.isnot(None),
cls.verify_date.is_(None)
).order_by(cls.created_at.desc()).first()
+61
View File
@@ -0,0 +1,61 @@
from datetime import datetime, timezone
from app.extensions import db
from app.utils.logger import logger
class RepaymentsData(db.Model):
__tablename__ = 'repayments_data'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
transaction_id = db.Column(db.String(50), nullable=False)
added_date = db.Column(db.DateTime(timezone=True), default=datetime.now(timezone.utc), nullable=False)
response_code = db.Column(db.String(10), nullable=True)
response_descr = db.Column(db.String(255), nullable=True)
fbn_transaction_id = db.Column(db.String(255),nullable=True)
account_id = db.Column(db.String(50), nullable=True)
customer_id = db.Column(db.String(50), nullable=True)
repayment_amount = db.Column(db.Float, nullable=True)
amount_collected = db.Column(db.Float, nullable=True)
balance = db.Column(db.Float, nullable=True, default=0.0)
def to_dict(self):
return {
"id": self.id,
"transaction_id": self.transaction_id,
"added_date": self.added_date.isoformat() if self.added_date else None,
"response_code": self.response_code,
"response_descr": self.response_descr,
"customerId": self.customer_id,
"accountId": self.customer_id,
"fbnTransactionId": self.fbn_transaction_id,
"repaymentAmount": self.repayment_amount,
"amountCollected": self.amount_collected,
"balance": self.balance
}
def __repr__(self):
return f"<RepaymentsData id={self.id}, transaction_id={self.transaction_id}>"
@classmethod
def add_repayment_data(cls, data):
"""
Add a new repayment data entry.
"""
try:
new_data = cls(
transaction_id=data.get('transactionId'),
response_code=data.get('responseCode'),
response_descr=data.get('responseDescr'),
fbn_transaction_id=data.get('fbnTransactionId'),
account_id=data.get('accountId'),
customer_id=data.get('customerId'),
amount_collected=data.get('amountCollected'),
repayment_amount=data.get('repaymentAmount'),
)
db.session.add(new_data)
db.session.commit()
logger.info(f"data has been commited ")
return new_data
except Exception as e:
db.session.rollback()
raise Exception(f"Error adding repayment data: {str(e)}")
+98
View File
@@ -0,0 +1,98 @@
from app.extensions import db
from datetime import datetime, timezone
from app.utils.logger import logger
class Salary(db.Model):
__tablename__ = "salaries"
id = db.Column(
db.Integer,
primary_key=True,
autoincrement=True,
)
customer_id = db.Column(db.String(50), nullable=False)
account_id = db.Column(db.String(50), nullable=False)
amount = db.Column(db.Float, nullable=True, default=0.0)
status = db.Column(db.String(20), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.now)
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
salary_date = db.Column(db.DateTime, nullable=True)
def __repr__(self):
return f'<Salary {self.id}>'
def to_dict(self):
"""
Convert the Salary object to a dictionary format for JSON serialization.
"""
return {
'id': self.id,
'customerId': self.customer_id,
'accountId' : self.account_id,
'salaryAmount': self.amount,
'status': self.status,
'createdAt': self.created_at.isoformat() if self.created_at else None,
'updatedAt': self.updated_at.isoformat() if self.updated_at else None,
'salaryDate': self.salary_date.isoformat() if self.salary_date else None,
}
@classmethod
def add_salary_data(cls, data):
"""
Add a new salary data entry.
"""
logger.info(f"receieved data:{data}")
try:
new_data = cls(
customer_id=data.get('customerId'),
amount=data.get('salaryAmount', 0.0),
status='START',
salary_date = datetime.strptime(data.get('salaryDate'), "%Y-%m-%d").date() if data.get('salaryDate') else None,
account_id=data.get('accountId')
)
db.session.add(new_data)
db.session.commit()
logger.info("Salary data has been committed.")
return new_data
except Exception as e:
db.session.rollback()
logger.info(f"error : {str(e)}")
raise Exception(f"Error adding salary data: {str(e)}")
@classmethod
def get_pending_salaries(cls):
"""
Retrieve all salary entries with status 'START', ordered by ID ascending.
"""
try:
return cls.query.filter_by(status='START').order_by(cls.id.asc()).all()
except Exception as e:
logger.error(f"Error fetching pending salaries: {str(e)}")
return []
@classmethod
def update_status(cls, salary_id, status):
"""
Update the status of the salary record with the given salary_id.
"""
try:
# Retrieve salary record
salary = cls.query.get(salary_id)
if not salary:
raise ValueError(f"Salary with ID {salary_id} does not exist.")
if salary.status == status:
return salary.to_dict() # Still return the current state if no change
# Update status and timestamp
salary.status = status
salary.updated_at = datetime.now(timezone.utc) # Manually update timestamp if not auto-updating
db.session.commit()
logger.info("Salary status updated and committed.")
return salary.to_dict()
except Exception as e:
db.session.rollback()
logger.error(f"Error updating salary status: {e}")
raise Exception(f"Error updating salary status: {str(e)}")
+2
View File
@@ -14,6 +14,7 @@ class Transaction(db.Model):
customer_id = db.Column(db.String(50), nullable=True) customer_id = db.Column(db.String(50), nullable=True)
type = db.Column(db.String(50), nullable=False) type = db.Column(db.String(50), nullable=False)
channel = db.Column(db.String(50), nullable=False) channel = db.Column(db.String(50), nullable=False)
phone_number = db.Column(db.String(50), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) 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)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
@@ -29,6 +30,7 @@ class Transaction(db.Model):
'transaction_id': self.transaction_id, 'transaction_id': self.transaction_id,
'account_id': self.account_id, 'account_id': self.account_id,
'customer_id': self.customer_id, 'customer_id': self.customer_id,
'phone_number':self.phone_number,
'type': self.type, 'type': self.type,
'channel': self.channel, 'channel': self.channel,
} }
+2
View File
@@ -2,6 +2,7 @@ from flask import Blueprint, request, jsonify, current_app
import requests import requests
from app.utils.auth import get_headers from app.utils.auth import get_headers
from app.config import settings from app.config import settings
from app.utils.logger import logger
auth_bp = Blueprint("auth", __name__) auth_bp = Blueprint("auth", __name__)
@@ -10,6 +11,7 @@ BASE_URL = settings.BANK_CALL_BASE_URL
@auth_bp.route("/health", methods=["GET"]) @auth_bp.route("/health", methods=["GET"])
def health(): def health():
logger.info("Health check endpoint called")
return jsonify({"status": "Up"}) return jsonify({"status": "Up"})
+154 -6
View File
@@ -5,27 +5,94 @@ 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
from app.services.loan import LoanService
from app.services.repayment import RepaymentService
from app.services.salary import SalaryService
autocall_bp = Blueprint("autocall", __name__) autocall_bp = Blueprint("autocall", __name__)
@autocall_bp.route("/refresh-verify-disbursement", methods=["GET"]) @autocall_bp.route("/refresh-verify-disbursement", methods=["GET"])
def verify_transaction(): def verify_transaction():
# data = request.json()
logger.info(f"Calling VerifyTransaction Components") logger.info(f"Calling VerifyTransaction Components")
response = SimbrellaClient.verify_transaction() loan = LoanService.get_latest_loan_with_disburse_date()
if not loan:
logger.info(f"No loan found without disbursement date")
return 0
logger.info(f"Calling VerifyTransaction endpoint with data: {loan}")
loan_data = loan.to_dict()
data = {
"transactionId": loan_data.get('transactionId'),
"FbnTransactionId": loan_data.get('transactionId'),
"debtId": str(loan_data.get('debtId')),
"customerId": loan_data.get('customerId'),
"accountId": loan_data.get('accountId'),
"productId": str(loan_data.get('productId', "")),
"provideAmount": loan_data.get('currentLoanAmount'),
}
response = SimbrellaClient.verify_transaction(data)
return response return response
@autocall_bp.route("/refresh-disbursement", methods=["GET"]) @autocall_bp.route("/refresh-disbursement", methods=["GET"])
def disbursement(): def disbursement():
# data = request.json() # data = request.json()
logger.info(f"Calling Disbursement Components") logger.info(f"Calling Disbursement Components")
loan = LoanService.get_latest_loan_without_disburse_date()
if not loan:
logger.info(f"No loan found without disbursement date")
return 0
logger.info(f"Calling DisburseLoan endpoint with data: {loan}")
loan_data = loan.to_dict()
response = SimbrellaClient.verify_transaction() data = {
"transactionId": loan_data.get('transactionId'),
"FbnTransactionId": loan_data.get('transactionId'),
"debtId": str(loan_data.get('debtId')),
"customerId": loan_data.get('customerId'),
"accountId": loan_data.get('accountId'),
"productId": str(loan_data.get('productId', "")),
"provideAmount": loan_data.get('currentLoanAmount'),
}
response = SimbrellaClient.disburse_loan(data)
return response
@autocall_bp.route("/refresh-verify-collection", methods=["GET"])
def refresh_verify_collection():
data = request.get_json()
logger.info(f"Calling Verify Collection")
response = SimbrellaClient.collect_loan(data)
return response return response
@autocall_bp.route("/refresh-collection", methods=["GET"])
def refresh_collection():
#data = request.get_json()
logger.info(f"Calling Collection ")
#grab the last repayments with repay date is none
repayment = RepaymentService.get_latest_repayment_without_repay_date()
#repayment = RepaymentService.get_latest_repayment_with_loanId(13735)
if not repayment:
logger.info(f"No repayment found without repay date")
return 0
logger.info(f"Calling repay loan endpoint with data: {repayment}")
repayment_data = repayment.to_dict()
logger.info(f"here is the dict form of repayment {repayment_data}")
data = {
"transactionId": repayment_data['transactionId'],
"debtId": repayment_data['loanId'],
"customerId": repayment_data['customerId'],
"productId": repayment_data['productId'],
"Id":repayment_data['Id']
}
logger.info(f"Data being sent to Simbrella: {data}")
logger.info(f"calling simbrella")
response = SimbrellaClient.collect_loan_user_initiated(data)
return response
@autocall_bp.route("/payment-callback", methods=["POST"]) @autocall_bp.route("/payment-callback", methods=["POST"])
def payment_callback(): def payment_callback():
@@ -39,8 +106,89 @@ def payment_callback():
@autocall_bp.route("/penal-charge", methods=["POST"]) @autocall_bp.route("/penal-charge", methods=["POST"])
def penal_charge(): def penal_charge():
data = request.get_json() data = request.get_json()
logger.info(f"Calling Penal Charge Endpoints") logger.info(f"Calling Penal Charge Endpoint")
response = SimbrellaClient.penal_charge(data[0]) try:
response = SimbrellaClient.penal_charge(data[0])
return response
except Exception as e:
logger.error(f"Error in Penal Charge: {e}")
return ResponseHelper.error("Penal charge failed")
return response
@autocall_bp.route("/analytic-salary-detect", methods=["POST"])
def salary_detect():
payload = request.get_json()
logger.info("Calling Salary Detect endpoint")
if payload is None:
logger.warning("No payload received in request")
return ResponseHelper.error("Missing request payload", status_code=400)
# Step 1: Try to add new salary data
try:
new_salary = SalaryService.add_salary_data(payload)
if new_salary:
logger.info(f"Salary added: {new_salary.id}")
except Exception as e:
logger.error(f"Failed to save salary: {e}")
# Step 2: Get all pending salaries
pending_salaries = SalaryService.get_pending_salaries()
if not pending_salaries:
logger.info("No pending salaries found")
return ResponseHelper.success([], "No pending salaries")
logger.info(f"Found {len(pending_salaries)} pending salaries to process")
# Step 3: Process each salary
for pending_salary in pending_salaries:
logger.info(f"Processing salary ID: {pending_salary.id}")
# Step 3.1: Update status to PROCESSING
try:
SalaryService.update_status(pending_salary.id, "PROCESSING")
except Exception as e:
logger.warning(f"Could not update status for salary ID {pending_salary.id}: {e}")
continue
# Step 3.2: Get loans
try:
loans, total_amount = LoanService.get_customer_loans(pending_salary.customer_id)
if not loans:
logger.warning(f"No loans found for customer ID: {pending_salary.customer_id}")
continue
except Exception as e:
logger.error(f"Error fetching loans for customer ID {pending_salary.customer_id}: {e}")
continue
# Step 3.3: Create repayments
for loan in loans:
try:
loan_dict = loan.to_dict()
repayment_data = {
"customerId": pending_salary.customer_id,
"loanId": loan_dict["debtId"],
"productId": loan_dict["productId"],
"transactionId": loan_dict["transactionId"],
"initiatedBy": "SALARY_DETECT",
"salaryAmount": pending_salary.amount,
}
logger.info(f"Creating repayment for loan ID {loan_dict['debtId']}")
repayment = RepaymentService.add_repayment(repayment_data)
logger.info(f"Created repayment ID: {repayment.id}")
except Exception as e:
logger.error(f"Error creating repayment for loan ID {loan.id}: {e}")
continue
# Step 4: Simbrella integration call after all processing
try:
SimbrellaClient.collect_loan_user_salary_detect(repayment.to_dict())
except Exception as e:
logger.error(f"Failed to call Simbrella client: {e}")
logger.info(f"Finished processing salary ID: {pending_salary.id}")
return ResponseHelper.success([], "AutoCall Successful")
+64 -1
View File
@@ -8,10 +8,73 @@ class LoanService:
Get the loan by transaction ID Get the loan by transaction ID
""" """
return Loan.get_loan_by_transaction_id(transaction_id) return Loan.get_loan_by_transaction_id(transaction_id)
@classmethod
def get_loan_by_loan_id(cls, loan_id):
"""
Get the loan by ID
"""
return Loan.get_loan_by_loan_id(loan_id)
@classmethod
def get_loan_by_debt_id(cls, debt_id):
"""
Get the loan by transaction ID
"""
return Loan.get_loan_by_debt_id(debt_id)
@classmethod @classmethod
def get_loan_charge_by_debt_id(cls, debt_id): def get_loan_charge_by_debt_id(cls, debt_id):
""" """
Get the loan charge by debt ID Get the loan charge by debt ID
""" """
return LoanCharge.get_loan_charge_by_debt_id(debt_id) return LoanCharge.get_loan_charge_by_debt_id(debt_id)
@classmethod
def set_disbursement_date(cls, loan_id, customer_id):
"""
Update the disbursement status of the loan with the given loan_id.
"""
return Loan.set_disbursement_date(loan_id, customer_id)
@classmethod
def set_disburse_verify_date(cls, loan_id, customer_id):
"""
Update the disburse verify date of the loan with the given loan_id.
"""
return Loan.set_disburse_verify_date(loan_id, customer_id)
@classmethod
def set_disbursement_result(cls, loan_id, result, description):
"""
Update the disbursement result of the loan with the given loan_id.
"""
return Loan.set_disbursement_result(loan_id, result, description)
@classmethod
def set_disburse_verify_result(cls, loan_id, result, description):
"""
Update the disburse verify result of the loan with the given loan_id.
"""
return Loan.set_disburse_verify_result(loan_id, result, description)
@classmethod
def get_latest_loan_without_disburse_date(cls):
"""
Get the latest loan without a disbursement date.
"""
return Loan.get_latest_loan_without_disburse_date()
@classmethod
def get_latest_loan_with_disburse_date(cls):
"""
Get the latest loan without a disbursement date.
"""
return Loan.get_latest_loan_with_disburse_date()
@classmethod
def get_customer_loans(cls, customer_id):
"""
Get customer's active loans by customer_id.
"""
return Loan.get_customer_loans(customer_id=customer_id)
+61 -1
View File
@@ -7,4 +7,64 @@ class RepaymentService:
""" """
Get the repayment by transaction ID Get the repayment by transaction ID
""" """
return Repayment.get_repayment_by_transaction_id(transaction_id) return Repayment.get_repayment_by_transaction_id(transaction_id)
@staticmethod
def get_repayment_by_id(id):
"""
Get the repayment by ID
"""
return Repayment.get_repayment_by_id(id)
@classmethod
def set_repay_date(cls, repayment_id, customer_id):
"""
Update the repay status of the repayment with the given repayment_id.
"""
return Repayment.set_repay_date(repayment_id, customer_id)
@classmethod
def set_repay_verify_date(cls, repayment_id, customer_id):
"""
Update the verify date of the repayment with the given repayment_id.
"""
return Repayment.set_repay_verify_date(repayment_id, customer_id)
@classmethod
def set_repay_result(cls, repayment_id, result, description):
"""
Update the repay result of the repayment with the given repayment_id.
"""
return Repayment.set_repay_result(repayment_id, result, description)
@classmethod
def set_verify_date_result(cls, repayment_id, result, description):
"""
Update the verify result of the repayment with the given repayment_id.
"""
return Repayment.set_verify_date_result(repayment_id, result, description)
@classmethod
def get_latest_repayment_without_repay_date(cls):
"""
Get the latest repayment without a repay date.
"""
return Repayment.get_latest_repayment_without_repay_date()
@classmethod
def get_latest_repayment_with_loanId(cls,loan_id):
"""
Get the latest repayment with loan id.
"""
return Repayment.get_latest_repayment_with_loanId(loan_id)
@classmethod
def get_latest_loan_with_repay_date(cls):
"""
Get the latest repayment with a repay date and no verification date.
"""
return Repayment.get_latest_loan_with_repay_date()
@classmethod
def add_repayment(cls, data):
"""
Add a new repayment entry.
"""
return Repayment.add_repayment(data)
+11
View File
@@ -0,0 +1,11 @@
from app.models import RepaymentsData
class RepaymentService:
@classmethod
def add_repayment_data(cls,data):
"""
Add a new repayment data entry.
"""
return RepaymentsData.add_repayment_data(data)
+24
View File
@@ -0,0 +1,24 @@
from app.models import Salary
class SalaryService:
@classmethod
def add_salary_data(cls,data):
"""
Add a new salary data entry.
"""
return Salary.add_salary_data(data)
@classmethod
def get_pending_salaries(cls):
"""
Get the pending salary for a given customer.
"""
return Salary.get_pending_salaries()
@classmethod
def update_status(cls, salary_id, status):
"""
Update the status of the salary with the given salary_id.
"""
return Salary.update_status(salary_id, status)
+2 -1
View File
@@ -7,4 +7,5 @@ class TransactionService:
""" """
Get the transaction by ID Get the transaction by ID
""" """
return Transaction.get_transaction_by_transaction_id(transaction_id) return Transaction.get_transaction_by_transaction_id(transaction_id)
+43
View File
@@ -13,6 +13,10 @@ info:
servers: servers:
- url: http://localhost:5000 - url: http://localhost:5000
description: Local development server description: Local development server
- url: http://www.simbrellang.net:5000
description: Remote Temporary development server
- url: https://event-core.simbrellang.net
description: Remote development server
paths: paths:
/health: /health:
@@ -110,6 +114,18 @@ paths:
responses: responses:
200: 200:
description: A successful response description: A successful response
/autocall/refresh-verify-collection:
get:
summary: Refresh the disbursement to verify
responses:
200:
description: A successful response
/autocall/refresh-collection:
get:
summary: Refresh the disbursement
responses:
200:
description: A successful response
/autocall/payment-callback: /autocall/payment-callback:
get: get:
summary: The Payment callback summary: The Payment callback
@@ -155,6 +171,33 @@ paths:
comment: comment:
type: string type: string
example: "Testing PenalCharge" example: "Testing PenalCharge"
responses:
200:
description: A successful response
/autocall/analytic-salary-detect:
post:
summary: Salary Detect Endpoint
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
customerId:
type: string
example: "CN621868"
accountId:
type: string
example: "OP621868"
status:
type: string
salaryAmount:
type: number
example: 200000
salaryDate:
type: string
example: "2025-01-01"
responses: responses:
200: 200:
description: A successful response description: A successful response
+2 -1
View File
@@ -8,4 +8,5 @@ requests
confluent-kafka==1.9.2 confluent-kafka==1.9.2
flask-sqlalchemy flask-sqlalchemy
psycopg2-binary psycopg2-binary
alembic alembic
python-dateutil