#!/usr/bin/env python3 from decimal import Decimal import os from dotenv import load_dotenv from flask import Flask, request, jsonify import subprocess import json import requests from datetime import datetime, timedelta load_dotenv() # This will load environment variables from a .env file if it exists TIME_DIFF_LIMIT = int(os.getenv('TIME_DIFF_LIMIT', 2)) # Default to 2mins if not set COOKIE_SERVER = os.getenv('COOKIE_SERVER', 'http://localhost:5000') # Bank BIC to name mapping BANK_BIC_NAMES = { "MADVMVMV": "Maldives Islamic Bank PLC", "MALBMVMV": "Bank of Maldives", } BANK_BIC_SHORT = { "MADVMVMV": "MIB", "MALBMVMV": "BML", } # Shared headers (same as getcookie.py) BASE_HEADERS = { "User-Agent": "Mozilla/5.0 (ismath-said-will) give-real-api/so-i-wont-have-to-do-this AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36", } app = Flask(__name__) def get_cookies(): """Fetch authentication cookies from the cookie server.""" try: response = requests.get(f"{COOKIE_SERVER}/getcookie", timeout=30) if response.status_code == 200: return response.json() except requests.RequestException as e: app.logger.error(f"Error fetching cookies: {str(e)}") return None def get_ips_account(account_number): """Look up account details via MIB IPS API.""" cookies = get_cookies() if not cookies: return None, "Failed to get authentication cookies" try: response = requests.post( 'https://faisanet.mib.com.mv/AjaxAlias/getIPSAccount', headers=BASE_HEADERS, cookies=cookies, data={'benefAccount': account_number}, timeout=30 ) return response.json(), None except requests.RequestException as e: return None, f"Request failed: {str(e)}" except json.JSONDecodeError as e: return None, f"Invalid JSON response: {str(e)}" def fetch_account_name(account_no): try: result = subprocess.run(['./fetchname.sh', account_no], capture_output=True, text=True, check=True) response = json.loads(result.stdout) if response.get('success'): return response.get('accountName') except (subprocess.CalledProcessError, json.JSONDecodeError) as e: app.logger.error(f"Error fetching account name: {str(e)}") return None def get_transaction_details(tx_data): """Extract reference and sourceBank based on transaction type""" descr1 = tx_data.get('descr1', '') if descr1 == "Favara Credit": reference = tx_data.get('descr2', '') # Extract sourceBank from descr3 (part before the first '-') descr3 = tx_data.get('descr3', '') sourceBank = descr3.split(' - ')[0] if ' - ' in descr3 else '' elif descr1 == "IB Acc to Acc": reference = tx_data.get('trxNumber', '') sourceBank = "MIB" else: reference = tx_data.get('trxNumber', '') sourceBank = "" return { "ref": reference, "trxDate": tx_data.get('trxDate', ''), "sourceBank": sourceBank } def verify_transaction(benef_name, abs_amount, request_time, tx_data_list): for tx_data in tx_data_list: required_keys = ['trxDate', 'benefName', 'absAmount'] if not all(key in tx_data for key in required_keys): continue # Skip this transaction if it's missing required keys try: tx_time = datetime.strptime(tx_data['trxDate'], "%Y-%m-%d %H:%M:%S") time_diff = abs(tx_time - request_time) tx_benef_name = tx_data['benefName'].strip().lower() if (tx_benef_name == benef_name.strip().lower() and compare_amounts(tx_data['absAmount'], abs_amount) and time_diff <= timedelta(minutes=TIME_DIFF_LIMIT)): return True, tx_data except ValueError as e: app.logger.error(f"Error processing transaction: {str(e)}") return False, None def compare_amounts(amount1, amount2): """Compare two amount strings as Decimal objects.""" return Decimal(amount1) == Decimal(amount2) @app.errorhandler(400) def bad_request(error): return jsonify({"success": False, "message": "Invalid request: Malformed JSON"}), 400 @app.errorhandler(500) def internal_error(error): return jsonify({"success": False, "message": "Internal server error"}), 500 @app.route('/verify-payment', methods=['POST']) def verify_payment(): try: if not request.is_json: return jsonify({"success": False, "message": "Request must be JSON"}), 400 data = request.get_json() if data is None: return jsonify({"success": False, "message": "Invalid JSON format"}), 400 benef_name = data.get('benefName', '').strip() if data.get('benefName') else '' account_no = data.get('accountNo') abs_amount = data.get('absAmount') time_str = data.get('time') if not all([abs_amount, time_str]): return jsonify({"success": False, "message": "Missing required parameters"}), 400 if not benef_name and not account_no: return jsonify({"success": False, "message": "Either benefName or accountNo must be provided"}), 400 try: request_time = datetime.strptime(time_str, "%Y-%m-%d %H:%M") except ValueError: return jsonify({"success": False, "message": "Invalid time format"}), 400 try: result = subprocess.run(['./tx.sh'], capture_output=True, text=True, check=True) tx_response = json.loads(result.stdout) app.logger.debug(f"tx_response: {json.dumps(tx_response, indent=2)}") if not tx_response.get('success'): return jsonify({"success": False, "message": f"Error from tx.sh: {tx_response.get('reasonText', 'Unknown error')}"}), 500 tx_data_list = tx_response.get('data', []) if not tx_data_list: return jsonify({"success": False, "message": "No transaction data found"}), 404 except subprocess.CalledProcessError as e: return jsonify({"success": False, "message": f"Error executing tx.sh: {str(e)}"}), 500 except json.JSONDecodeError as e: return jsonify({"success": False, "message": f"Error parsing tx.sh output: {str(e)}"}), 500 # First, try to verify with benefName if provided if benef_name: verified, tx_data = verify_transaction(benef_name, abs_amount, request_time, tx_data_list) if verified: return jsonify({ "success": True, "message": "Payment verified using beneficiary name", "transaction": get_transaction_details(tx_data) }) # If benefName verification failed or wasn't provided, try with accountNo if account_no: fetched_name = fetch_account_name(account_no) if fetched_name: verified, tx_data = verify_transaction(fetched_name, abs_amount, request_time, tx_data_list) if verified: return jsonify({ "success": True, "message": "Payment verified using account number", "transaction": get_transaction_details(tx_data) }) # If both verifications fail return jsonify({"success": False, "message": "Transaction not found, contact support"}) except Exception as e: app.logger.error(f"Unexpected error: {str(e)}") return jsonify({"success": False, "message": "Internal server error"}), 500 @app.route('/getaccount/', methods=['GET']) def get_account(accountnumber): try: result, error = get_ips_account(accountnumber) if error: return jsonify({ "success": False, "message": error, "accountName": None, "bankSwift": None, "currency": None, "bankName": None, "bankNameShort": None }), 500 if not result.get('success'): reason_text = result.get('reasonText', '') if reason_text == "Transaction forbidden on this type of account.": message = "Invalid currency" elif reason_text == "Account number is invalid or missing.": message = "Invalid account" else: message = reason_text or "Account not found" return jsonify({ "success": False, "message": message, "accountName": None, "bankSwift": None, "currency": None, "bankName": None, "bankNameShort": None }), 404 bank_bic = result.get('bankBic', '') return jsonify({ "success": True, "message": "Success", "accountName": result.get('accountName'), "bankSwift": bank_bic, "currency": "MVR", "bankName": BANK_BIC_NAMES.get(bank_bic, ""), "bankNameShort": BANK_BIC_SHORT.get(bank_bic, "") }) except Exception as e: app.logger.error(f"Error in get_account: {str(e)}") return jsonify({ "success": False, "message": "Internal server error", "accountName": None, "bankSwift": None, "currency": None, "bankName": None, "bankNameShort": None }), 500 if __name__ == '__main__': debug_mode = os.getenv('APP_DEBUG', 'False').lower() in ('true', '1', 't') port = int(os.getenv('PORT', 5000)) app.run(host='0.0.0.0', port=port, debug=debug_mode)