This commit is contained in:
VivianDee
2025-05-10 08:53:09 +01:00
11 changed files with 207 additions and 32 deletions
+2 -2
View File
@@ -35,7 +35,7 @@ class SimbrellaIntegration:
], ],
} }
logger.error(f"This is PayLoad: {str(payload)}", exc_info=True) logger.info(f"This is PayLoad: {str(payload)}", exc_info=True)
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -46,7 +46,7 @@ class SimbrellaIntegration:
try: try:
response = httpx.post(url, json=payload, headers=headers, timeout=10.0) response = httpx.post(url, json=payload, headers=headers, timeout=10.0)
logger.error(f"This is Response: {str(response)}", exc_info=True) logger.info(f"This is Response: {str(response)}", exc_info=True)
return response return response
+1
View File
@@ -6,3 +6,4 @@ from app.api.services.repayment import RepaymentService
from app.api.services.customer_consent import CustomerConsentService from app.api.services.customer_consent import CustomerConsentService
from app.api.services.notification_callback import NotificationCallbackService from app.api.services.notification_callback import NotificationCallbackService
from app.api.services.authorization import AuthorizationService from app.api.services.authorization import AuthorizationService
from app.api.services.offer_analysis import OfferAnalysis
+1
View File
@@ -123,6 +123,7 @@ class BaseService:
return { return {
"interest": interest, "interest": interest,
"interest_amount": interest_amount,
"management": management, "management": management,
"insurance": insurance, "insurance": insurance,
"vat": vat, "vat": vat,
+17 -2
View File
@@ -7,7 +7,7 @@ from marshmallow import ValidationError
from app.api.enums import TransactionType from app.api.enums import TransactionType
from app.api.integrations import SimbrellaIntegration from app.api.integrations import SimbrellaIntegration
from app.extensions import db from app.extensions import db
from app.models import Offer from app.models import Offer, RACCheck
import random import random
@@ -57,12 +57,27 @@ class EligibilityCheckService(BaseService):
response = SimbrellaIntegration.rac_check( response = SimbrellaIntegration.rac_check(
customer_id = customer_id, customer_id = customer_id,
account_id = account_id, account_id = account_id,
transaction_id = transaction.id, transaction_id = transaction.transaction_id,
) )
# this chck for error is not valid # this chck for error is not valid
if response.status_code != 200: if response.status_code != 200:
return jsonify({"message": "RACCheck failed"}), 400 return jsonify({"message": "RACCheck failed"}), 400
response = response.json()
rac_check = RACCheck.add_rac_check(
customer_id = customer_id,
account_id = account_id,
transaction_id = transaction.transaction_id,
data = response['RACResponse']
)
if not rac_check:
logger.error(f"Failed to save RACCheck")
return jsonify({
"message": "Failed to save RACCheck."
}), 400
offers = Offer.get_all_offers() offers = Offer.get_all_offers()
+37
View File
@@ -0,0 +1,37 @@
from app.models import Offer, TransactionOffer
from app.models.loan import Loan
import logging
logger = logging.getLogger(__name__)
class OfferAnalysis:
@staticmethod
def get_offer(transaction_id, rac_response, validated_data):
customer_id = validated_data.get("customerId")
product_id = validated_data.get("productId")
offer_id = validated_data.get("offerId")
transaction_offer_id = int(offer_id[5:]) # The last part is int
logger.info(f"customer_id == *************** : {customer_id}")
logger.info(f"product_id == *************** : {product_id}")
logger.info(f"offer_id == *************** : {offer_id}")
logger.info(f"transaction_offer_id == *************** : {transaction_offer_id}")
transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id, customer_id, product_id)
if not transaction_offer:
raise ValueError("Invalid Transaction Offer.")
eligible_amount = transaction_offer.eligible_amount
offer = Offer.is_valid_offer( transaction_offer.offer_id)
if not offer:
raise ValueError("Invalid Offer.")
original_transaction = transaction_id
# we can now find the origin transactions
customer_loan = Loan.get_customer_current_active_loan(customer_id)
return transaction_offer, offer, eligible_amount, original_transaction
+32 -16
View File
@@ -8,13 +8,13 @@ from app.models.loan_charge import LoanCharge
from app.utils.logger import logger from app.utils.logger import logger
from app.api.schemas.provide_loan import ProvideLoanSchema from app.api.schemas.provide_loan import ProvideLoanSchema
from threading import Thread from threading import Thread
from app.models import Loan, Offer, Charge , TransactionOffer from app.models import Loan, Offer, Charge , TransactionOffer, RACCheck
from app.api.enums import LoanStatus from app.api.enums import LoanStatus
from app.extensions import db from app.extensions import db
from datetime import datetime, timezone from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from app.models import LoanRepaymentSchedule from app.models import LoanRepaymentSchedule
from app.api.services.offer_analysis import OfferAnalysis
class ProvideLoanService(BaseService): class ProvideLoanService(BaseService):
TRANSACTION_TYPE = TransactionType.PROVIDE_LOAN TRANSACTION_TYPE = TransactionType.PROVIDE_LOAN
@@ -45,25 +45,40 @@ class ProvideLoanService(BaseService):
customer = Customer.is_valid_customer(customer_id) customer = Customer.is_valid_customer(customer_id)
if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
transaction_offer_id = int(offer_id[5:]) # The last part is int rac_response = RACCheck.get_rac_check(customer_id = customer_id, account_id = account_id)
transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id) try:
if not transaction_offer: transaction_offer, offer, eligible_amount, original_transaction = OfferAnalysis.get_offer(
logger.error(f"Invalid Transaction Offer") transaction_id=transaction_id,
rac_response=rac_response,
validated_data=validated_data
)
except ValueError as ve:
logger.error(str(ve))
return jsonify({ return jsonify({
"message": "Invalid Transaction Offer." "message": str(ve)
}), 400 }), 400
# transaction_offer_id = int(offer_id[5:]) # The last part is int
eligible_amount = transaction_offer.eligible_amount # transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id)
offer = Offer.is_valid_offer( transaction_offer.offer_id)
# if not transaction_offer:
# logger.error(f"Invalid Transaction Offer")
# return jsonify({
# "message": "Invalid Transaction Offer."
# }), 400
if not offer: # eligible_amount = transaction_offer.eligible_amount
logger.error(f"Invalid Offer") # offer = Offer.is_valid_offer( transaction_offer.offer_id)
return jsonify({
"message": "Invalid Offer." # if not offer:
}), 400 # logger.error(f"Invalid Offer")
# return jsonify({
# "message": "Invalid Offer."
# }), 400
# Log Transaction # Log Transaction
@@ -102,6 +117,7 @@ class ProvideLoanService(BaseService):
product_id = offer.product_id, product_id = offer.product_id,
collection_type = collection_type, collection_type = collection_type,
transaction_id = validated_data.get('transactionId'), transaction_id = validated_data.get('transactionId'),
original_transaction = validated_data.get('transactionId'),
initial_loan_amount = validated_data.get('requestedAmount'), initial_loan_amount = validated_data.get('requestedAmount'),
upfront_fee = upfront_fee, upfront_fee = upfront_fee,
repayment_amount = repayment_amount, repayment_amount = repayment_amount,
@@ -131,7 +147,7 @@ class ProvideLoanService(BaseService):
# charges = Charge.get_offer_charges(offer.id) # charges = Charge.get_offer_charges(offer.id)
logger.error(f"{charges}") logger.info(f"{charges}")
loan_id = loan.id loan_id = loan.id
+8 -2
View File
@@ -32,10 +32,14 @@ class SelectOfferService(BaseService):
customer_id = validated_data.get("customerId") customer_id = validated_data.get("customerId")
amount = validated_data.get("requestedAmount") amount = validated_data.get("requestedAmount")
product_id = validated_data.get("productId") product_id = validated_data.get("productId")
offer_id = validated_data.get("offerId") transaction_offer_id = validated_data.get("offerId")
transaction_id = validated_data.get("transactionId") transaction_id = validated_data.get("transactionId")
request_id = validated_data.get("requestId") request_id = validated_data.get("requestId")
offer_id = int(transaction_offer_id[5:]) # The last part is int
#"offerId": "SAL30001129",
if SelectOfferService.validate_account_ownership( if SelectOfferService.validate_account_ownership(
account_id=account_id, customer_id=customer_id account_id=account_id, customer_id=customer_id
): ):
@@ -63,6 +67,7 @@ class SelectOfferService(BaseService):
insurance = charges["insurance"] insurance = charges["insurance"]
vat = charges["vat"] vat = charges["vat"]
repayment_amount = charges["repayment_amount"] repayment_amount = charges["repayment_amount"]
interest_amount = charges["interest_amount"]
# Calculate the repayment dates # Calculate the repayment dates
@@ -81,11 +86,12 @@ class SelectOfferService(BaseService):
offers = [ offers = [
{ {
"offerId": offer.id, "offerId": transaction_offer_id,
"productId": product_id, "productId": product_id,
"amount": amount, "amount": amount,
"upfrontPayment": upfront_payment, "upfrontPayment": upfront_payment,
"interestRate": offer.interest_rate, "interestRate": offer.interest_rate,
"interestAmount": interest_amount,
"managementRate": offer.management_rate, "managementRate": offer.management_rate,
"managementFee": management["fee"], "managementFee": management["fee"],
"insuranceRate": offer.insurance_rate, "insuranceRate": offer.insurance_rate,
+26 -4
View File
@@ -5,6 +5,7 @@ from app.models.account import Account
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from datetime import timedelta
class Loan(db.Model): class Loan(db.Model):
@@ -68,6 +69,7 @@ class Loan(db.Model):
initial_loan_amount, initial_loan_amount,
collection_type, collection_type,
transaction_id, transaction_id,
original_transaction,
upfront_fee, upfront_fee,
repayment_amount, repayment_amount,
installment_amount, installment_amount,
@@ -81,6 +83,7 @@ class Loan(db.Model):
raise ValueError("Customer does not exist") raise ValueError("Customer does not exist")
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
due_date = now + timedelta(days=tenor)
# Create and save the loan # Create and save the loan
loan = cls( loan = cls(
@@ -90,13 +93,13 @@ class Loan(db.Model):
product_id = product_id, product_id = product_id,
collection_type = collection_type, collection_type = collection_type,
transaction_id = transaction_id, transaction_id = transaction_id,
original_transaction = transaction_id, original_transaction = original_transaction,
initial_loan_amount = initial_loan_amount, initial_loan_amount = initial_loan_amount,
current_loan_amount = initial_loan_amount, current_loan_amount = initial_loan_amount,
upfront_fee = upfront_fee, upfront_fee = upfront_fee,
repayment_amount = repayment_amount, repayment_amount = repayment_amount,
installment_amount = installment_amount, installment_amount = installment_amount,
due_date=now, due_date=due_date,
tenor = tenor, tenor = tenor,
status = status, status = status,
eligible_amount =eligible_amount eligible_amount =eligible_amount
@@ -123,13 +126,32 @@ class Loan(db.Model):
@classmethod @classmethod
def get_customer_loan(cls, loan_id, customer_id): def get_customer_loan(cls, loan_id, customer_id):
""" """
Get customer's active loans. Get customer's active loans by loan_id.
""" """
loan = cls.query.filter_by(id = loan_id, customer_id = customer_id).first() loan = cls.query.filter_by(id = loan_id, customer_id = customer_id).first()
if not loan: if not loan:
raise ValueError(f"Loan with ID {loan_id} does not exist or does not belong to customer {customer_id}.") raise ValueError(f"Loan with ID {loan_id} does not exist or does not belong to customer {customer_id}.")
return loan return loan
@classmethod
def get_customer_current_active_loan(cls, customer_id):
"""
Get customer's active loans.
"""
loan = cls.query.filter_by( customer_id = customer_id).first()
if not loan:
loan = {
"eligible_amount": 0,
"loan_amount": 0,
"customer_id": customer_id,
"transaction_id": "",
"resultDescription": "No Active Loan"
}
logger.info(f" Active Loan ==>>>> {loan}")
return loan
@classmethod @classmethod
def update_status(cls, loan_id, status): def update_status(cls, loan_id, status):
""" """
+26 -6
View File
@@ -1,14 +1,14 @@
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 sqlalchemy.dialects.postgresql import UUID from sqlalchemy.exc import IntegrityError
from uuid import uuid4 from uuid import uuid4
from sqlalchemy.types import JSON from sqlalchemy.types import JSON
class RACCheck(db.Model): class RACCheck(db.Model):
__tablename__ = 'rac_checks' __tablename__ = 'rac_checks'
id = db.Column(db.String, primary_key=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
transaction_id = db.Column(db.String(50), nullable=False) transaction_id = db.Column(db.String(50), nullable=False)
customer_id = db.Column(db.String, nullable=False) customer_id = db.Column(db.String, nullable=False)
account_id = db.Column(db.String, nullable=False) account_id = db.Column(db.String, nullable=False)
@@ -16,6 +16,25 @@ class RACCheck(db.Model):
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))
@classmethod
def add_rac_check(cls, customer_id, account_id, transaction_id, data = None):
# Save the response
rac_check = cls(
customer_id = customer_id,
account_id = account_id,
transaction_id = transaction_id,
rac_response = data
)
try:
db.session.add(rac_check)
except IntegrityError as err:
raise ValueError(f"Database integrity error: {err}")
return rac_check
@classmethod @classmethod
def get_all_rac_checks(cls): def get_all_rac_checks(cls):
""" """
@@ -24,18 +43,19 @@ class RACCheck(db.Model):
rac_checks = cls.query.all() rac_checks = cls.query.all()
if not rac_checks: if not rac_checks:
raise ValueError("No available RAC checks") return None
return rac_checks return rac_checks
@classmethod @classmethod
def get_rac_check_by_id(cls, check_id): def get_rac_check(cls, customer_id, account_id):
""" """
Return a RAC check by its ID. Return a RAC check by its ID.
""" """
rac_check = cls.query.filter_by(id=check_id).first() rac_check = cls.query.filter_by( customer_id = customer_id,
account_id = account_id,).first()
if not rac_check: if not rac_check:
raise ValueError(f"RAC Check with ID {check_id} not found") raise ValueError(f"RAC Check for customer not found")
return rac_check return rac_check
def to_dict(self): def to_dict(self):
@@ -49,6 +49,11 @@
"format": "float", "format": "float",
"example": 3.0 "example": 3.0
}, },
"interestAmount": {
"type": "number",
"format": "float",
"example": 3000.00
},
"ManagementRate": { "ManagementRate": {
"type": "number", "type": "number",
"format": "float", "format": "float",
+52
View File
@@ -0,0 +1,52 @@
"""empty message
Revision ID: 3105abd795d4
Revises: 95a52be203c4
Create Date: 2025-05-07 11:44:18.483694
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '3105abd795d4'
down_revision = '95a52be203c4'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('rac_checks', schema=None) as batch_op:
# Step 1: Drop the default value
batch_op.alter_column('id',
server_default=None,
existing_type=sa.VARCHAR(),
existing_nullable=False
)
with op.batch_alter_table('rac_checks', schema=None) as batch_op:
# Step 2: Change the column type
batch_op.alter_column('id',
existing_type=sa.VARCHAR(),
type_=sa.Integer(),
existing_nullable=False,
autoincrement=True,
postgresql_using='id::integer'
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('rac_checks', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.Integer(),
type_=sa.VARCHAR(),
existing_nullable=False,
autoincrement=True,
existing_server_default=sa.text("''::character varying"))
# ### end Alembic commands ###