Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 2m55s
263 lines
9.7 KiB
Python
Executable File
263 lines
9.7 KiB
Python
Executable File
#!/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/<accountnumber>', 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, "<unknown>"),
|
|
"bankNameShort": BANK_BIC_SHORT.get(bank_bic, "<unknown>")
|
|
})
|
|
|
|
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)
|