import asyncio import base64 import json import os import re import cv2 import ddddocr import numpy as np from asgiref.sync import async_to_sync from django.contrib.auth.decorators import login_required, permission_required from django.http import JsonResponse from playwright.async_api import async_playwright from ._helpers import logger, _safe_int # ===================================================================== # توابع کمکی و منطق استعلام چک (برگرفته از اسکریپت‌های شما) # ===================================================================== def solve_base64_captcha(base64_string: str) -> str: try: if ',' in base64_string: base64_string = base64_string.split(',')[1] image_bytes = base64.b64decode(base64_string) np_arr = np.frombuffer(image_bytes, np.uint8) img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR) if img is None: return "" gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur = cv2.medianBlur(gray, 3) _, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) kernel = np.ones((2, 2), np.uint8) cleaned_img = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1) final_img = cv2.bitwise_not(cleaned_img) success, encoded_image = cv2.imencode('.png', final_img) if not success: return "" processed_bytes = encoded_image.tobytes() ocr = ddddocr.DdddOcr(show_ad=False) extracted_text = ocr.classification(processed_bytes) numbers = re.findall(r'\d+', extracted_text) if len(numbers) >= 2: num1, num2 = int(numbers[0]), int(numbers[1]) if '+' in extracted_text: ans = num1 + num2 elif '-' in extracted_text: ans = num1 - num2 elif '*' in extracted_text or 'x' in extracted_text.lower(): ans = num1 * num2 else: ans = num1 + num2 return str(ans) return "" except Exception: return "" def parse_cheque_result_html(html_content: str) -> dict: sayad_id = re.search(r'شناسه\s*?\s*(\d+)\s*?', html_content) owner_name = re.search(r'متعلق به\s*?\s*(.*?)\s*?\s*می باشد', html_content) validity_date = re.search(r'تا تاریخ\s*?\s*([\d/]+)\s*?', html_content) status = re.search(r'در وضعیت\s*?\s*(.*?)\s*?\s*در پایگاه', html_content) clean_full_text = re.sub(r'<[^>]+>', '', html_content).strip() return { "success": True, "data": { "sayad_id": sayad_id.group(1) if sayad_id else "", "owner_name": owner_name.group(1).strip() if owner_name else "", "validity_date": validity_date.group(1) if validity_date else "", "status": status.group(1).strip() if status else "", "raw_message": clean_full_text }, "error": None } async def run_cbi_sayad_inquiry_logic(sayad_id: str, max_retries: int = 7) -> str: lightpanda_url = os.environ.get("LIGHTPANDA_URL") or "ws://127.0.0.1:9222" async with async_playwright() as p: try: browser = await p.chromium.connect_over_cdp(lightpanda_url) except Exception as e: return json.dumps({"success": False, "data": None, "error": f"عدم اتصال به Lightpanda: {str(e)}"}) for attempt in range(1, max_retries + 1): context = await browser.new_context(ignore_https_errors=True, bypass_csp=True) page = await context.new_page() try: url = "https://cbi.ir/EstelamSayad/19689.aspx" await page.goto(url, wait_until="domcontentloaded", timeout=20000) captcha_selector = "#ctl00_ucBody_ucContent_ctl00_imgcpatcha" await page.wait_for_selector(captcha_selector, state="visible", timeout=15000) await asyncio.sleep(1) base64_src = await page.locator(captcha_selector).get_attribute("src") viewstate = await page.locator("#__VIEWSTATE").get_attribute("value") or "" viewstate_gen = await page.locator("#__VIEWSTATEGENERATOR").get_attribute("value") or "453B48D8" if not base64_src or not viewstate: await context.close() continue captcha_answer = solve_base64_captcha(base64_src) if not captcha_answer: await context.close() continue ajax_raw_response = await page.evaluate(""" async ({ sayadId, captchaAns, viewState, viewStateGen }) => { try { const formData = new URLSearchParams(); formData.append('ctl00$ucBody$ucContent$ctl00$ScriptManager1', 'ctl00$ucBody$ucContent$ctl00$UpdatePanel1|ctl00$ucBody$ucContent$ctl00$btnSearch'); formData.append('__EVENTTARGET', ''); formData.append('__EVENTARGUMENT', ''); formData.append('__VIEWSTATE', viewState); formData.append('__VIEWSTATEGENERATOR', viewStateGen); formData.append('__VIEWSTATEENCRYPTED', ''); formData.append('ctl00$ucHeader$txtQuickSearch', ''); formData.append('ctl00$ucBody$ucContent$ctl00$txtChequeNumber', sayadId); formData.append('ctl00$ucBody$ucContent$ctl00$txtCaptchaInput', captchaAns); formData.append('__ASYNCPOST', 'true'); formData.append('ctl00$ucBody$ucContent$ctl00$btnSearch', 'استعلام چک صیادی'); const response = await fetch(window.location.href, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Cache-Control': 'no-cache' }, body: formData.toString() }); return await response.text(); } catch (err) { return 'FETCH_ERROR: ' + err.message; } } """, {'sayadId': sayad_id, 'captchaAns': captcha_answer, 'viewState': viewstate, 'viewStateGen': viewstate_gen}) if "FETCH_ERROR:" in ajax_raw_response: await context.close() await asyncio.sleep(1) continue error_match = re.search(r'id="ctl00_ucBody_ucContent_ctl00_lblError"[^>]*>(.*?)', ajax_raw_response, re.DOTALL) if error_match and error_match.group(1).strip(): clean_error = re.sub(r'<[^>]+>', '', error_match.group(1)).strip() if "عبارت محاسباتی" in clean_error: await context.close() continue else: await context.close() await browser.close() return json.dumps({"success": False, "data": None, "error": clean_error}) result_match = re.search(r'id="ctl00_ucBody_ucContent_ctl00_lblChequeNumberResult"[^>]*>(.*?)', ajax_raw_response, re.DOTALL) if result_match: raw_html = result_match.group(1) parsed_result = parse_cheque_result_html(raw_html) await context.close() await browser.close() return json.dumps(parsed_result) await context.close() except Exception: await context.close() await asyncio.sleep(1) await browser.close() return json.dumps({"success": False, "data": None, "error": "استعلام ناموفق بود (خطای شبکه یا تشخیص کپچا)"}) async def run_bpi_cheque_inquiry_logic(national_id: str, sayad_id: str, id_type: int = 1): lightpanda_url = os.environ.get("LIGHTPANDA_URL") or "ws://127.0.0.1:9222" async with async_playwright() as p: try: browser = await p.chromium.connect_over_cdp(lightpanda_url) except Exception as e: return {"success": False, "error": f"عدم اتصال به مرورگر Lightpanda: {str(e)}"} context = await browser.new_context() page = await context.new_page() try: await page.goto("https://vbank.bpi.ir/public/inquiries/inquiry-bounced-cheques", wait_until="domcontentloaded", timeout=20000) api_url = f"https://sec.bpi.ir/prls/api/v1/inquiry/chequeStatus?IdCode={national_id}&IdType={id_type}&SayadId={sayad_id}" response = await context.request.get( api_url, headers={ "Referer": "https://vbank.bpi.ir/public/inquiries/inquiry-bounced-cheques", "Accept": "application/json, text/plain, */*" } ) if response.ok: data = await response.json() return {"success": True, "data": data} else: error_text = await response.text() return {"success": False, "error": f"سرور خطا برگرداند (کد {response.status}): {error_text}"} except Exception as e: return {"success": False, "error": f"خطایی در فرآیند استعلام رخ داد: {str(e)}"} finally: await context.close() await browser.close() # ===================================================================== # View های مربوط به استعلام (API) # ===================================================================== @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def api_check_status_inquiry(request): """ استعلام وضعیت چک از بانک مرکزی پارامتر ورودی: sayad_id (GET) """ sayad_id = request.GET.get('sayad_id', '').strip() if not sayad_id or not sayad_id.isdigit() or len(sayad_id) != 16: return JsonResponse({"success": False, "error": "شناسه صیادی باید دقیقاً ۱۶ رقم باشد."}) try: # اجرای تابع Async به صورت Sync result_json_str = async_to_sync(run_cbi_sayad_inquiry_logic)(sayad_id) # تبدیل رشته جیسون به دیکشنری برای ارسال مجدد با JsonResponse result_data = json.loads(result_json_str) return JsonResponse(result_data) except Exception as e: logger.error(f"Error in CBI Check Status Inquiry: {str(e)}", exc_info=True) return JsonResponse({"success": False, "error": "خطای سرور در برقراری ارتباط با بانک مرکزی."}) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def api_check_sabt_inquiry(request): """ استعلام ثبت چک از بانک پاسارگاد پارامترهای ورودی: sayad_id, national_id, id_type (GET) """ sayad_id = request.GET.get('sayad_id', '').strip() national_id = request.GET.get('national_id', '').strip() id_type = _safe_int(request.GET.get('id_type', 1)) # عدد 1 حقیقی، 2 حقوقی # اگر کدملی از فرانت‌اند خالی ارسال شد، از یک کدملی پیش‌فرض استفاده می‌کنیم if not national_id: national_id = "1219968218" if not sayad_id: return JsonResponse({"success": False, "error": "شناسه صیادی برای استعلام ثبت الزامی است."}) try: # اجرای تابع Async به صورت Sync result_data = async_to_sync(run_bpi_cheque_inquiry_logic)(national_id, sayad_id, id_type) return JsonResponse(result_data) except Exception as e: logger.error(f"Error in BPI Check Registration Inquiry: {str(e)}", exc_info=True) return JsonResponse({"success": False, "error": "خطای سرور در برقراری ارتباط با سامانه بانک."})