From bb85c8f166a8f443ff07cd3862c7e18942f87f2a Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:14:59 +0100 Subject: [PATCH 1/3] [add]: bank call auth endpoint --- app/api/integrations/simbrella.py | 64 ++++++++++++++++++++++++++++++- app/api/routes/routes.py | 18 ++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/app/api/integrations/simbrella.py b/app/api/integrations/simbrella.py index 3455aef..cf67099 100644 --- a/app/api/integrations/simbrella.py +++ b/app/api/integrations/simbrella.py @@ -8,6 +8,45 @@ import logging class SimbrellaIntegration: BASE_URL = settings.SIMBRELLA_BASE_URL ENDPOINT_RAC_CHECKS = settings.SIMBRELLA_ENDPOINT_RAC_CHECKS + HEALTH_ENDPOINT = settings.SIMBRELLA_HEALTH + AUTH_ENDPOINT = settings.BANK_CALL_AUTH_ENDPOINT + + _access_token = None # cache token in memory + + @staticmethod + def generate_token(): + """ + Generate a new access token using the username and password from settings. + """ + url = f"{SimbrellaIntegration.BASE_URL}{SimbrellaIntegration.AUTH_ENDPOINT}" + + payload = { + "username": settings.BANK_CALL_USERNAME, + "password": settings.BANK_CALL_PASSWORD, + "grant_type": "password" + } + + headers = {"Content-Type": "application/json"} + + try: + logger.info(f"Requesting token from {url}") + + response = httpx.post(url, json=payload, headers=headers, timeout=10.0) + response.raise_for_status() + + data = response.json() + SimbrellaIntegration._access_token = data.get("access_token") + + if not SimbrellaIntegration._access_token: + raise Exception("Access token not found in response") + + logger.info("Successfully retrieved access token") + return SimbrellaIntegration._access_token + + except Exception as e: + logger.error(f"Token generation failed: {str(e)}", exc_info=True) + raise Exception(f"Token generation failed: {str(e)}") + @staticmethod def rac_check(customer_id, account_id, transaction_id): @@ -28,8 +67,7 @@ class SimbrellaIntegration: headers = { "Content-Type": "application/json", - "x-api-key": f"{settings.VALID_API_KEY}", - "App-Id": f"{settings.VALID_APP_ID}", + "Authorization": f"Bearer {SimbrellaIntegration._access_token}" } try: @@ -42,4 +80,26 @@ class SimbrellaIntegration: except Exception as e: logger.error(f"RACCheck API call failed: {str(e)}", exc_info=True) raise Exception(f"RACCheck API call failed: {str(e)}") + + @staticmethod + def health_check(): + """ + Health check for Simbrella Service + """ + + url = f"{SimbrellaIntegration.BASE_URL}/{SimbrellaIntegration.HEALTH_ENDPOINT}" + logger.info(f"Simbrella Health Check URL: {url}") + + try: + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {SimbrellaIntegration._access_token}" + } + + response = httpx.get(url, headers=headers, timeout=10.0) + logger.info(f"Simbrella Health Check Response: {response.text}") + return response + except Exception as e: + logger.error(f"Simbrella Health Check API call failed: {str(e)}", exc_info=True) + raise Exception(f"Simbrella Health Check API call failed: {str(e)}") diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index b1ecc67..231a426 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -1,5 +1,5 @@ -from sqlite3 import DatabaseError from app.api.integrations.events_service import EventServiceIntegration +from app.api.integrations.simbrella import SimbrellaIntegration from flask import Blueprint, request, jsonify, send_from_directory from app.api.services import ( EligibilityCheckService, @@ -125,6 +125,7 @@ def health_check(): response = {} db_status = "Connection Successful" events_service_status = "Connection Successful" + bank_status = "Connection Successful" errors = [] status = "ok" @@ -162,11 +163,26 @@ def health_check(): status = "failed" errors.append(f"Events Service connection failed: {str(e)}") + # Check Emulator health + try: + emulator_response = SimbrellaIntegration.health_check() + + if emulator_response.status_code != 200: + bank_status = "Connection Failed" + status = "failed" + errors.append(f"Emulator response: {emulator_response.text}") + + except Exception as e: + bank_status = "Connection Failed" + status = "failed" + errors.append(f"Emulator connection failed: {str(e)}") + response = { "status": status, "db_status": db_status, "events_service_status": events_service_status, + "bank_status": bank_status, "db_uri": db_uri, "errors": errors or None } From 54e52c639bdb72972e73418c561d788d8013351a Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:24:08 +0100 Subject: [PATCH 2/3] [add]: token expiry --- app/api/integrations/simbrella.py | 19 +++++++++++++++---- app/config.py | 7 +++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/api/integrations/simbrella.py b/app/api/integrations/simbrella.py index cf67099..bab1fb4 100644 --- a/app/api/integrations/simbrella.py +++ b/app/api/integrations/simbrella.py @@ -1,8 +1,7 @@ import httpx -import json +import time from app.utils.logger import logger from app.config import settings -import logging class SimbrellaIntegration: @@ -12,6 +11,7 @@ class SimbrellaIntegration: AUTH_ENDPOINT = settings.BANK_CALL_AUTH_ENDPOINT _access_token = None # cache token in memory + _token_expiry = 0 @staticmethod def generate_token(): @@ -35,7 +35,10 @@ class SimbrellaIntegration: response.raise_for_status() data = response.json() + expires_in = data.get("expires_in", 1800) + SimbrellaIntegration._access_token = data.get("access_token") + SimbrellaIntegration._token_expiry = time.time() + expires_in - 60 if not SimbrellaIntegration._access_token: raise Exception("Access token not found in response") @@ -47,6 +50,14 @@ class SimbrellaIntegration: logger.error(f"Token generation failed: {str(e)}", exc_info=True) raise Exception(f"Token generation failed: {str(e)}") + @staticmethod + def _get_token(): + """ + Return a valid token, refreshing if expired or missing + """ + if not SimbrellaIntegration._access_token or time.time() >= SimbrellaIntegration._token_expiry: + return SimbrellaIntegration.generate_token() + return SimbrellaIntegration._access_token @staticmethod def rac_check(customer_id, account_id, transaction_id): @@ -67,7 +78,7 @@ class SimbrellaIntegration: headers = { "Content-Type": "application/json", - "Authorization": f"Bearer {SimbrellaIntegration._access_token}" + "Authorization": f"Bearer {SimbrellaIntegration._get_token()}" } try: @@ -93,7 +104,7 @@ class SimbrellaIntegration: try: headers = { "Content-Type": "application/json", - "Authorization": f"Bearer {SimbrellaIntegration._access_token}" + "Authorization": f"Bearer {SimbrellaIntegration._get_token()}" } response = httpx.get(url, headers=headers, timeout=10.0) diff --git a/app/config.py b/app/config.py index 594be39..8cf31c2 100644 --- a/app/config.py +++ b/app/config.py @@ -46,6 +46,13 @@ class Config: VALID_API_KEY = os.getenv("SIMBRELLA_API_KEY", "test-api-key-12345") SIMBRELLA_BASE_URL = os.getenv("SIMBRELLA_BASE_URL", "http://127.0.0.1:6337") SIMBRELLA_ENDPOINT_RAC_CHECKS = os.getenv("SIMBRELLA_ENDPOINT_RAC_CHECKS","api/rac-check") + SIMBRELLA_HEALTH = os.getenv("SIMBRELLA_ENDPOINT_RAC_CHECKS","api/system-health-check") + BANK_CALL_AUTH_ENDPOINT = os.getenv("BANK_CALL_AUTH_ENDPOINT", "/api/Auth/generate-token") + BANK_CALL_USERNAME = os.getenv("BANK_CALL_USERNAME", "simbrella") + BANK_CALL_PASSWORD = os.getenv("BANK_CALL_PASSWORD", "G7$k9@pL2!qR") + + + EVENTS_SERVICE_BASE_URL = os.getenv("EVENTS_SERVICE_BASE_URL","https://event-core.simbrellang.net") ENDPOINT_DIRECT_LOAN = os.getenv("ENDPOINT_DIRECT_LOAN","/autocall/direct/loan") ENDPOINT_DIRECT_REPAYMENT = os.getenv("ENDPOINT_DIRECT_REPAYMENT","/autocall/direct/repayment") From d99640345a5427445e1896436d85758b476381a4 Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:42:23 +0100 Subject: [PATCH 3/3] [fix]: swagger and health check response --- app/api/integrations/simbrella.py | 32 +++++++++++++++++-------------- app/api/routes/routes.py | 6 +++--- app/swagger/digifi_swagger.json | 4 ++-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/app/api/integrations/simbrella.py b/app/api/integrations/simbrella.py index bab1fb4..58171f4 100644 --- a/app/api/integrations/simbrella.py +++ b/app/api/integrations/simbrella.py @@ -1,3 +1,4 @@ +from os import access import httpx import time from app.utils.logger import logger @@ -29,7 +30,7 @@ class SimbrellaIntegration: headers = {"Content-Type": "application/json"} try: - logger.info(f"Requesting token from {url}") + logger.info(f"Requesting Bank token from {url}") response = httpx.post(url, json=payload, headers=headers, timeout=10.0) response.raise_for_status() @@ -41,9 +42,9 @@ class SimbrellaIntegration: SimbrellaIntegration._token_expiry = time.time() + expires_in - 60 if not SimbrellaIntegration._access_token: - raise Exception("Access token not found in response") + raise Exception("Access token not found in Bank Authorization response") - logger.info("Successfully retrieved access token") + logger.info("Successfully retrieved Bank access token") return SimbrellaIntegration._access_token except Exception as e: @@ -76,12 +77,14 @@ class SimbrellaIntegration: "channel": "USSD" } - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {SimbrellaIntegration._get_token()}" - } - try: + access_token = SimbrellaIntegration._get_token() + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}" + } + + response = httpx.post(url, json=payload, headers=headers, timeout=10.0) logger.info(f"This is Response: {str(response)}", exc_info=True) @@ -95,22 +98,23 @@ class SimbrellaIntegration: @staticmethod def health_check(): """ - Health check for Simbrella Service + Health check for Bank Service """ url = f"{SimbrellaIntegration.BASE_URL}/{SimbrellaIntegration.HEALTH_ENDPOINT}" - logger.info(f"Simbrella Health Check URL: {url}") + logger.info(f"Bank Health Check URL: {url}") try: + access_token = SimbrellaIntegration._get_token() headers = { "Content-Type": "application/json", - "Authorization": f"Bearer {SimbrellaIntegration._get_token()}" + "Authorization": f"Bearer {access_token}" } response = httpx.get(url, headers=headers, timeout=10.0) - logger.info(f"Simbrella Health Check Response: {response.text}") + logger.info(f"Bank Health Check Response: {response.text}") return response except Exception as e: - logger.error(f"Simbrella Health Check API call failed: {str(e)}", exc_info=True) - raise Exception(f"Simbrella Health Check API call failed: {str(e)}") + logger.error(f"Bank Health Check API call failed: {str(e)}", exc_info=True) + raise Exception(f"Bank Health Check API call failed: {str(e)}") diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index 231a426..6e63009 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -163,19 +163,19 @@ def health_check(): status = "failed" errors.append(f"Events Service connection failed: {str(e)}") - # Check Emulator health + # Check Bank health try: emulator_response = SimbrellaIntegration.health_check() if emulator_response.status_code != 200: bank_status = "Connection Failed" status = "failed" - errors.append(f"Emulator response: {emulator_response.text}") + errors.append(f"Bank Connection response: {emulator_response.text}") except Exception as e: bank_status = "Connection Failed" status = "failed" - errors.append(f"Emulator connection failed: {str(e)}") + errors.append(f"Connection to Bank failed: {str(e)}") response = { diff --git a/app/swagger/digifi_swagger.json b/app/swagger/digifi_swagger.json index ab4fa0b..423ba5b 100644 --- a/app/swagger/digifi_swagger.json +++ b/app/swagger/digifi_swagger.json @@ -104,7 +104,7 @@ "status": "ok", "db_status": "Connection Successful", "events_service_status":"Connection Successful", - "emulator_status":"Connection Successful", + "bank_status":"Connection Successful", "db_uri": "postgresql://user:****@localhost:5432/digifi_db", "error": [] } @@ -119,7 +119,7 @@ "status": "failed", "db_status": "Connection Failed", "events_service_status":"Connection Failed", - "emulator_status":"Connection Failed", + "bank_status":"Connection Failed", "db_uri": "Unavailable", "error":["could not connect to server: Connection refused"] }