diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b10bc98
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Python Virtual Environments
+venv/
+env/
+ENV/
+.venv/
+
+# Django files
+db.sqlite3
+db.sqlite3-journal
+*.log
+local_settings.py
+
+# Environment variables & secrets
+.env
+.env.*
+
+# IDE / Editor folders
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# OS generated files
+.DS_Store
+Thumbs.db
diff --git a/docverify/__init__.py b/docverify/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/docverify/asgi.py b/docverify/asgi.py
new file mode 100644
index 0000000..ad3364a
--- /dev/null
+++ b/docverify/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for docverify project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'docverify.settings')
+
+application = get_asgi_application()
diff --git a/docverify/settings.py b/docverify/settings.py
new file mode 100644
index 0000000..3846d9e
--- /dev/null
+++ b/docverify/settings.py
@@ -0,0 +1,108 @@
+import os
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-docverify-cashfree-secure-id-verification-suite-key'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+# Application definition
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+
+ # Third party apps
+ 'rest_framework',
+
+ # Local apps
+ 'verifier',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'docverify.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'docverify.wsgi.application'
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+LANGUAGE_CODE = 'en-us'
+TIME_ZONE = 'Asia/Kolkata'
+USE_I18N = True
+USE_TZ = True
+
+STATIC_URL = 'static/'
+STATICFILES_DIRS = [
+ BASE_DIR / 'verifier' / 'static',
+]
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': [
+ 'rest_framework.permissions.AllowAny',
+ ],
+ 'DEFAULT_RENDERER_CLASSES': [
+ 'rest_framework.renderers.JSONRenderer',
+ 'rest_framework.renderers.BrowsableAPIRenderer',
+ ]
+}
+
+# Cashfree Default Environment Config
+CASHFREE_CLIENT_ID = os.environ.get('CASHFREE_CLIENT_ID', '')
+CASHFREE_CLIENT_SECRET = os.environ.get('CASHFREE_CLIENT_SECRET', '')
+CASHFREE_ENV = os.environ.get('CASHFREE_ENV', 'TEST')
diff --git a/docverify/urls.py b/docverify/urls.py
new file mode 100644
index 0000000..d265f7b
--- /dev/null
+++ b/docverify/urls.py
@@ -0,0 +1,7 @@
+from django.contrib import admin
+from django.urls import path, include
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path('', include('verifier.urls')),
+]
diff --git a/docverify/wsgi.py b/docverify/wsgi.py
new file mode 100644
index 0000000..17bd92f
--- /dev/null
+++ b/docverify/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for docverify project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'docverify.settings')
+
+application = get_wsgi_application()
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..afbb9db
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'docverify.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/verifier/__init__.py b/verifier/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/verifier/admin.py b/verifier/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/verifier/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/verifier/apps.py b/verifier/apps.py
new file mode 100644
index 0000000..06d3767
--- /dev/null
+++ b/verifier/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class VerifierConfig(AppConfig):
+ name = 'verifier'
diff --git a/verifier/migrations/__init__.py b/verifier/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/verifier/models.py b/verifier/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/verifier/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/verifier/services/cashfree_client.py b/verifier/services/cashfree_client.py
new file mode 100644
index 0000000..736a9f5
--- /dev/null
+++ b/verifier/services/cashfree_client.py
@@ -0,0 +1,508 @@
+import os
+import json
+import logging
+import requests
+from django.conf import settings
+
+logger = logging.getLogger(__name__)
+
+class CashfreeSecureIDClient:
+ """
+ Service client for Cashfree Secure ID (Verification Suite) APIs.
+ Supports both Sandbox (TEST) and Production (PROD) environments.
+ Includes built-in test-data fallback for immediate interactive testing.
+ """
+
+ SANDBOX_BASE_URL = "https://sandbox.cashfree.com/verification"
+ PRODUCTION_BASE_URL = "https://api.cashfree.com/verification"
+
+ def __init__(self, client_id=None, client_secret=None, environment=None):
+ self.client_id = client_id or getattr(settings, 'CASHFREE_CLIENT_ID', '') or os.environ.get('CASHFREE_CLIENT_ID', '')
+ self.client_secret = client_secret or getattr(settings, 'CASHFREE_CLIENT_SECRET', '') or os.environ.get('CASHFREE_CLIENT_SECRET', '')
+ self.environment = (environment or getattr(settings, 'CASHFREE_ENV', '') or os.environ.get('CASHFREE_ENV', 'TEST')).upper()
+
+ if self.environment == "PROD":
+ self.base_url = self.PRODUCTION_BASE_URL
+ else:
+ self.base_url = self.SANDBOX_BASE_URL
+
+ def _get_headers(self):
+ return {
+ "x-client-id": self.client_id,
+ "x-client-secret": self.client_secret,
+ "x-api-version": "2024-01-01",
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ }
+
+ def has_credentials(self):
+ return bool(self.client_id and self.client_secret)
+
+ def verify_credentials(self):
+ """
+ Verify if provided Cashfree credentials are valid.
+ """
+ if not self.has_credentials():
+ return {
+ "success": False,
+ "status_code": 400,
+ "message": "Cashfree Client ID or Secret Key missing.",
+ "data": None
+ }
+
+ url = f"{self.base_url}/api/v1/credentials/verify"
+ try:
+ res = requests.post(url, headers=self._get_headers(), timeout=10)
+ return {
+ "success": res.status_code == 200,
+ "status_code": res.status_code,
+ "data": res.json() if res.headers.get("content-type", "").startswith("application/json") else res.text,
+ "curl": f"curl -X POST '{url}' -H 'x-client-id: {self.client_id}' -H 'x-client-secret: [REDACTED]'"
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "status_code": 500,
+ "message": str(e),
+ "data": None
+ }
+
+ # ==================== PAN VERIFICATION ====================
+ def verify_pan(self, pan, name="", pan_type="INDIVIDUAL"):
+ pan = pan.strip().upper()
+ payload = {"pan": pan}
+ if name:
+ payload["name"] = name.strip()
+
+ url = f"{self.base_url}/pan"
+ curl_cmd = f"curl -X POST '{url}' \\\n -H 'x-client-id: {self.client_id or 'YOUR_CLIENT_ID'}' \\\n -H 'x-client-secret: [REDACTED]' \\\n -H 'Content-Type: application/json' \\\n -d '{json.dumps(payload)}'"
+
+ if self.has_credentials():
+ try:
+ res = requests.post(url, headers=self._get_headers(), json=payload, timeout=12)
+ return {
+ "source": "live_api",
+ "status_code": res.status_code,
+ "payload_sent": payload,
+ "response": res.json() if res.headers.get("content-type", "").startswith("application/json") else res.text,
+ "curl": curl_cmd
+ }
+ except Exception as e:
+ logger.error(f"Error calling Cashfree PAN API: {e}")
+
+ # Fallback simulation using official Cashfree Sandbox test data
+ valid_pans = {
+ "ABCPV1234D": {"name": "JOHN DOE", "type": "INDIVIDUAL"},
+ "XYZPP4321W": {"name": "ALICE SMITH", "type": "INDIVIDUAL"},
+ "AZJPG7110R": {"name": "RAHUL SHARMA", "type": "INDIVIDUAL"},
+ "ABCCD8000T": {"name": "ACME SOLUTIONS PVT LTD", "type": "BUSINESS"},
+ "XYZHP2000L": {"name": "TECH CORP INDIA", "type": "BUSINESS"},
+ "AAAHU4383C": {"name": "GLOBAL ENTERPRISES", "type": "BUSINESS"},
+ "AMJCL2021N": {"name": "CASHFREE INDIA PRIVATE LTD", "type": "BUSINESS"},
+ }
+ invalid_pans = ["DEFPV0126D", "TUVPP5678W", "LMNCD8010T", "EFGHH2020L"]
+
+ if pan in valid_pans:
+ reg_name = valid_pans[pan]["name"]
+ score = 100.0 if not name or name.upper() == reg_name else (85.0 if name.upper() in reg_name or reg_name in name.upper() else 70.0)
+ result = "DIRECT_MATCH" if score == 100.0 else "GOOD_PARTIAL_MATCH"
+ mock_res = {
+ "reference_id": 982341,
+ "pan": pan,
+ "registered_name": reg_name,
+ "type": valid_pans[pan]["type"],
+ "valid": True,
+ "message": "PAN verification successful",
+ "name_match_score": score,
+ "name_match_result": result,
+ "status": "SUCCESS"
+ }
+ status_code = 200
+ elif pan in invalid_pans:
+ mock_res = {
+ "reference_id": 982342,
+ "pan": pan,
+ "valid": False,
+ "message": "PAN number does not exist or is invalid in income tax database",
+ "status": "INVALID"
+ }
+ status_code = 400
+ else:
+ # Default response for custom input
+ mock_res = {
+ "reference_id": 982343,
+ "pan": pan,
+ "registered_name": name.upper() or "VERIFIED NAME",
+ "type": pan_type,
+ "valid": True,
+ "message": "PAN details fetched successfully (Sandbox Mode)",
+ "name_match_score": 100.0 if name else 0.0,
+ "name_match_result": "DIRECT_MATCH" if name else "NOT_APPLICABLE",
+ "status": "SUCCESS"
+ }
+ status_code = 200
+
+ return {
+ "source": "sandbox_simulated" if not self.has_credentials() else "fallback_sandbox",
+ "status_code": status_code,
+ "payload_sent": payload,
+ "response": mock_res,
+ "curl": curl_cmd
+ }
+
+ # ==================== AADHAAR VERIFICATION & MASKING ====================
+ def verify_aadhaar(self, aadhaar_number, action="verify"):
+ aadhaar_number = aadhaar_number.replace(" ", "").strip()
+ payload = {"aadhaar_number": aadhaar_number}
+
+ url = f"{self.base_url}/aadhaar" if action == "verify" else f"{self.base_url}/aadhaar/masking"
+ curl_cmd = f"curl -X POST '{url}' \\\n -H 'x-client-id: {self.client_id or 'YOUR_CLIENT_ID'}' \\\n -H 'x-client-secret: [REDACTED]' \\\n -H 'Content-Type: application/json' \\\n -d '{json.dumps(payload)}'"
+
+ if self.has_credentials():
+ try:
+ res = requests.post(url, headers=self._get_headers(), json=payload, timeout=12)
+ return {
+ "source": "live_api",
+ "status_code": res.status_code,
+ "payload_sent": payload,
+ "response": res.json() if res.headers.get("content-type", "").startswith("application/json") else res.text,
+ "curl": curl_cmd
+ }
+ except Exception as e:
+ logger.error(f"Error calling Cashfree Aadhaar API: {e}")
+
+ # Fallback simulation
+ valid_aadhaars = ["655675523712", "655675523711"]
+ invalid_aadhaars = ["655675523710", "655675523709"]
+
+ if action == "mask":
+ masked = "XXXX-XXXX-" + (aadhaar_number[-4:] if len(aadhaar_number) >= 4 else "3712")
+ mock_res = {
+ "reference_id": 554312,
+ "aadhaar_number": masked,
+ "status": "SUCCESS",
+ "message": "Aadhaar masking performed successfully per UIDAI regulations",
+ "qr_code_masked": True,
+ "first_8_digits_obscured": True
+ }
+ status_code = 200
+ else:
+ if aadhaar_number in valid_aadhaars or len(aadhaar_number) == 12:
+ mock_res = {
+ "reference_id": 554310,
+ "aadhaar_number": "XXXX-XXXX-" + (aadhaar_number[-4:] if len(aadhaar_number) >= 4 else "3712"),
+ "valid": True,
+ "status": "SUCCESS",
+ "gender": "MALE",
+ "state": "Karnataka",
+ "age_band": "20-30",
+ "mobile_number_masked": "XXXXXX9912",
+ "message": "Aadhaar verification successful"
+ }
+ status_code = 200
+ elif aadhaar_number in invalid_aadhaars:
+ mock_res = {
+ "reference_id": 554311,
+ "aadhaar_number": aadhaar_number,
+ "valid": False,
+ "status": "INVALID",
+ "message": "Invalid Aadhaar number or not found in UIDAI database"
+ }
+ status_code = 400
+ else:
+ mock_res = {
+ "reference_id": 554313,
+ "valid": True,
+ "status": "SUCCESS",
+ "gender": "FEMALE",
+ "state": "Maharashtra",
+ "age_band": "25-35",
+ "mobile_number_masked": "XXXXXX8877",
+ "message": "Aadhaar details retrieved (Sandbox Mode)"
+ }
+ status_code = 200
+
+ return {
+ "source": "sandbox_simulated" if not self.has_credentials() else "fallback_sandbox",
+ "status_code": status_code,
+ "payload_sent": payload,
+ "response": mock_res,
+ "curl": curl_cmd
+ }
+
+ # ==================== GSTIN VERIFICATION ====================
+ def verify_gstin(self, gstin):
+ gstin = gstin.strip().upper()
+ payload = {"gstin": gstin}
+ url = f"{self.base_url}/gstin"
+ curl_cmd = f"curl -X POST '{url}' \\\n -H 'x-client-id: {self.client_id or 'YOUR_CLIENT_ID'}' \\\n -H 'x-client-secret: [REDACTED]' \\\n -H 'Content-Type: application/json' \\\n -d '{json.dumps(payload)}'"
+
+ if self.has_credentials():
+ try:
+ res = requests.post(url, headers=self._get_headers(), json=payload, timeout=12)
+ return {
+ "source": "live_api",
+ "status_code": res.status_code,
+ "payload_sent": payload,
+ "response": res.json() if res.headers.get("content-type", "").startswith("application/json") else res.text,
+ "curl": curl_cmd
+ }
+ except Exception as e:
+ logger.error(f"Error calling Cashfree GSTIN API: {e}")
+
+ # Fallback simulation
+ valid_gstins = {
+ "29AAICP2912R1ZR": {
+ "legal_name": "CASHFREE PAYMENTS INDIA PRIVATE LIMITED",
+ "trade_name": "Cashfree Payments",
+ "state_jurisdiction": "Karnataka",
+ "principal_address": "Koramangala 5th Block, Bengaluru, Karnataka 560095",
+ "taxpayer_type": "Regular",
+ "registration_date": "2015-09-01"
+ },
+ "27ABCFGH5678D1Z": {
+ "legal_name": "APEX ENTERPRISES INDIA PVT LTD",
+ "trade_name": "Apex Digital",
+ "state_jurisdiction": "Maharashtra",
+ "principal_address": "Bandra West, Mumbai, Maharashtra 400050",
+ "taxpayer_type": "Regular",
+ "registration_date": "2018-04-15"
+ },
+ "36JKLPQR9012E2Y": {
+ "legal_name": "HYDERABAD TECHSOLUTIONS LLP",
+ "trade_name": "TechSolutions",
+ "state_jurisdiction": "Telangana",
+ "principal_address": "HITEC City, Hyderabad, Telangana 500081",
+ "taxpayer_type": "Regular",
+ "registration_date": "2020-01-10"
+ }
+ }
+ invalid_gstins = ["29AAIZP2912R1ZR", "05BBICA2912R1ZR"]
+
+ if gstin in valid_gstins:
+ data = valid_gstins[gstin]
+ mock_res = {
+ "reference_id": 771239,
+ "gstin": gstin,
+ "gstin_status": "ACTIVE",
+ "legal_name_of_business": data["legal_name"],
+ "trade_name": data["trade_name"],
+ "taxpayer_type": data["taxpayer_type"],
+ "registration_date": data["registration_date"],
+ "state_jurisdiction": data["state_jurisdiction"],
+ "principal_place_of_business_address": data["principal_address"],
+ "valid": True,
+ "status": "SUCCESS"
+ }
+ status_code = 200
+ elif gstin in invalid_gstins:
+ mock_res = {
+ "reference_id": 771240,
+ "gstin": gstin,
+ "valid": False,
+ "status": "INVALID",
+ "message": "GSTIN number is invalid or not registered in GST portal"
+ }
+ status_code = 400
+ else:
+ mock_res = {
+ "reference_id": 771241,
+ "gstin": gstin,
+ "gstin_status": "ACTIVE",
+ "legal_name_of_business": "VERIFIED BUSINESS ENTITY",
+ "trade_name": "Verified Brand",
+ "taxpayer_type": "Regular",
+ "registration_date": "2021-06-01",
+ "state_jurisdiction": "India",
+ "principal_place_of_business_address": "Main Business Street, Industrial Area, India",
+ "valid": True,
+ "status": "SUCCESS"
+ }
+ status_code = 200
+
+ return {
+ "source": "sandbox_simulated" if not self.has_credentials() else "fallback_sandbox",
+ "status_code": status_code,
+ "payload_sent": payload,
+ "response": mock_res,
+ "curl": curl_cmd
+ }
+
+ # ==================== IFSC VERIFICATION & LOOKUP ====================
+ def verify_ifsc(self, ifsc):
+ ifsc = ifsc.strip().upper()
+ payload = {"ifsc": ifsc}
+ url = f"{self.base_url}/ifsc"
+ curl_cmd = f"curl -X POST '{url}' \\\n -H 'x-client-id: {self.client_id or 'YOUR_CLIENT_ID'}' \\\n -H 'x-client-secret: [REDACTED]' \\\n -H 'Content-Type: application/json' \\\n -d '{json.dumps(payload)}'"
+
+ if self.has_credentials():
+ try:
+ res = requests.post(url, headers=self._get_headers(), json=payload, timeout=12)
+ return {
+ "source": "live_api",
+ "status_code": res.status_code,
+ "payload_sent": payload,
+ "response": res.json() if res.headers.get("content-type", "").startswith("application/json") else res.text,
+ "curl": curl_cmd
+ }
+ except Exception as e:
+ logger.error(f"Error calling Cashfree IFSC API: {e}")
+
+ # Fallback simulation
+ ifsc_database = {
+ "YESB0000262": {
+ "bank": "YES BANK",
+ "branch": "KORAMANGALA",
+ "address": "GROUND FLOOR, NO 43, 80 FEET ROAD, KORAMANGALA 4TH BLOCK, BENGALURU 560034",
+ "city": "BENGALURU",
+ "state": "KARNATAKA",
+ "micr": "560532002",
+ "modes": ["IMPS", "NEFT", "RTGS", "UPI"]
+ },
+ "HDFC0000001": {
+ "bank": "HDFC BANK",
+ "branch": "MUMBAI - FORT",
+ "address": "MANEKJI WADIA BLDG, GROUND FLOOR, NANIK MOTWANI MARG, FORT, MUMBAI 400023",
+ "city": "MUMBAI",
+ "state": "MAHARASHTRA",
+ "micr": "400240002",
+ "modes": ["IMPS", "NEFT", "RTGS", "UPI"]
+ },
+ "SBIN0008752": {
+ "bank": "STATE BANK OF INDIA",
+ "branch": "INDIRANAGAR",
+ "address": "100 FEET ROAD, INDIRANAGAR 1ST STAGE, BENGALURU 560038",
+ "city": "BENGALURU",
+ "state": "KARNATAKA",
+ "micr": "560002045",
+ "modes": ["IMPS", "NEFT", "RTGS", "UPI"]
+ },
+ "SCBL0036078": {
+ "bank": "STANDARD CHARTERED BANK",
+ "branch": "MG ROAD BENGALURU",
+ "address": "26-27 RAHEJA TOWERS, MG ROAD, BENGALURU 560001",
+ "city": "BENGALURU",
+ "state": "KARNATAKA",
+ "micr": "560036002",
+ "modes": ["IMPS", "NEFT", "RTGS", "UPI"]
+ }
+ }
+ invalid_ifscs = ["CNRR0002640", "INVALID001"]
+
+ if ifsc in ifsc_database:
+ b_info = ifsc_database[ifsc]
+ mock_res = {
+ "reference_id": 331892,
+ "ifsc": ifsc,
+ "valid": True,
+ "bank": b_info["bank"],
+ "branch": b_info["branch"],
+ "address": b_info["address"],
+ "city": b_info["city"],
+ "state": b_info["state"],
+ "micr": b_info["micr"],
+ "supported_modes": b_info["modes"],
+ "status": "SUCCESS"
+ }
+ status_code = 200
+ elif ifsc in invalid_ifscs:
+ mock_res = {
+ "reference_id": 331893,
+ "ifsc": ifsc,
+ "valid": False,
+ "status": "INVALID",
+ "message": "IFSC code is invalid or branch does not exist in RBI registry"
+ }
+ status_code = 400
+ else:
+ mock_res = {
+ "reference_id": 331894,
+ "ifsc": ifsc,
+ "valid": True,
+ "bank": "INDIAN NATIONAL BANK",
+ "branch": "MAIN BRANCH",
+ "address": "COMMERCIAL PLAZA, METRO STATION ROAD, INDIA",
+ "city": "METRO CITY",
+ "state": "INDIA",
+ "micr": "110002999",
+ "supported_modes": ["IMPS", "NEFT", "RTGS", "UPI"],
+ "status": "SUCCESS"
+ }
+ status_code = 200
+
+ return {
+ "source": "sandbox_simulated" if not self.has_credentials() else "fallback_sandbox",
+ "status_code": status_code,
+ "payload_sent": payload,
+ "response": mock_res,
+ "curl": curl_cmd
+ }
+
+ # ==================== BANK ACCOUNT VERIFICATION (BAV SYNC) ====================
+ def verify_bank_account(self, bank_account, ifsc, name="", phone=""):
+ bank_account = bank_account.strip()
+ ifsc = ifsc.strip().upper()
+ payload = {
+ "bank_account": bank_account,
+ "ifsc": ifsc
+ }
+ if name:
+ payload["name"] = name.strip()
+ if phone:
+ payload["phone"] = phone.strip()
+
+ url = f"{self.base_url}/bank-account/sync"
+ curl_cmd = f"curl -X POST '{url}' \\\n -H 'x-client-id: {self.client_id or 'YOUR_CLIENT_ID'}' \\\n -H 'x-client-secret: [REDACTED]' \\\n -H 'Content-Type: application/json' \\\n -d '{json.dumps(payload)}'"
+
+ if self.has_credentials():
+ try:
+ res = requests.post(url, headers=self._get_headers(), json=payload, timeout=15)
+ return {
+ "source": "live_api",
+ "status_code": res.status_code,
+ "payload_sent": payload,
+ "response": res.json() if res.headers.get("content-type", "").startswith("application/json") else res.text,
+ "curl": curl_cmd
+ }
+ except Exception as e:
+ logger.error(f"Error calling Cashfree Bank Account API: {e}")
+
+ # Fallback simulation
+ valid_accounts = ["026291800001191", "00011020001772", "000890289871772", "1233943142", "388108022658"]
+ failed_accounts = ["000100289877623", "026291800001190"]
+
+ if bank_account in valid_accounts or (len(bank_account) >= 9 and bank_account not in failed_accounts):
+ registered_name = name.upper() if name else "JOHN DOE"
+ mock_res = {
+ "reference_id": 441091,
+ "bank_account": bank_account,
+ "ifsc": ifsc,
+ "account_status": "VALID",
+ "account_name": registered_name,
+ "name_match_score": 100.0 if name else 0.0,
+ "name_match_result": "DIRECT_MATCH" if name else "NOT_APPLICABLE",
+ "utr": "427189012345",
+ "bank_name": "YES BANK" if "YESB" in ifsc else "HDFC BANK",
+ "city": "BENGALURU",
+ "message": "Bank account is active and verified via penny drop",
+ "status": "SUCCESS"
+ }
+ status_code = 200
+ else:
+ mock_res = {
+ "reference_id": 441092,
+ "bank_account": bank_account,
+ "ifsc": ifsc,
+ "account_status": "INVALID",
+ "message": "Bank account number is invalid or rejected by destination bank",
+ "status": "FAILED"
+ }
+ status_code = 400
+
+ return {
+ "source": "sandbox_simulated" if not self.has_credentials() else "fallback_sandbox",
+ "status_code": status_code,
+ "payload_sent": payload,
+ "response": mock_res,
+ "curl": curl_cmd
+ }
diff --git a/verifier/static/verifier/app.js b/verifier/static/verifier/app.js
new file mode 100644
index 0000000..a0e3797
--- /dev/null
+++ b/verifier/static/verifier/app.js
@@ -0,0 +1,241 @@
+// Interactive Client JavaScript for DocVerify AI
+
+document.addEventListener('DOMContentLoaded', () => {
+ initNavigation();
+ initForms();
+ initCopyButton();
+});
+
+// Tab Navigation Config
+const tabMeta = {
+ 'tab-pan': {
+ title: 'PAN Verification Service',
+ desc: 'Validate individual or business Permanent Account Numbers (PAN) with name match algorithms.',
+ badge: 'POST /api/verify/pan/'
+ },
+ 'tab-aadhaar': {
+ title: 'Aadhaar Verification & Masking',
+ desc: 'Verify Aadhaar number validity or execute UIDAI compliant Aadhaar card masking.',
+ badge: 'POST /api/verify/aadhaar/'
+ },
+ 'tab-gstin': {
+ title: 'GSTIN / Business Verification',
+ desc: 'Verify Goods and Services Tax Identification Number (GSTIN) and legal business details.',
+ badge: 'POST /api/verify/gstin/'
+ },
+ 'tab-ifsc': {
+ title: 'IFSC Code Lookup',
+ desc: 'Fetch bank name, branch address, MICR code, and supported payment rails by IFSC.',
+ badge: 'POST /api/verify/ifsc/'
+ },
+ 'tab-bav': {
+ title: 'Bank Account Verification (BAV)',
+ desc: 'Real-time synchronous bank account validation via IMPS/NPCI penny drop simulation.',
+ badge: 'POST /api/verify/bank-account/'
+ },
+ 'tab-config': {
+ title: 'API Credentials & Settings',
+ desc: 'Configure Cashfree App ID, Secret Key, and switch between Sandbox and Production modes.',
+ badge: 'POST /api/config/'
+ }
+};
+
+function initNavigation() {
+ const navButtons = document.querySelectorAll('.nav-btn');
+ const tabPanes = document.querySelectorAll('.tab-pane');
+
+ navButtons.forEach(btn => {
+ btn.addEventListener('click', () => {
+ const targetTab = btn.getAttribute('data-tab');
+
+ // Update nav buttons
+ navButtons.forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+
+ // Update panes
+ tabPanes.forEach(pane => {
+ if (pane.id === targetTab) {
+ pane.classList.add('active');
+ } else {
+ pane.classList.remove('active');
+ }
+ });
+
+ // Update header info
+ if (tabMeta[targetTab]) {
+ document.getElementById('page-title').innerText = tabMeta[targetTab].title;
+ document.getElementById('page-desc').innerText = tabMeta[targetTab].desc;
+ document.getElementById('api-endpoint-badge').innerHTML = ` ${tabMeta[targetTab].badge}`;
+ }
+ });
+ });
+}
+
+function initForms() {
+ // Form 1: PAN Verification
+ document.getElementById('form-pan').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const pan = document.getElementById('pan-input').value.trim();
+ const name = document.getElementById('pan-name-input').value.trim();
+ const type = document.getElementById('pan-type-select').value;
+ await executeVerification('/api/verify/pan/', { pan, name, type });
+ });
+
+ // Form 2: Aadhaar Suite
+ document.getElementById('form-aadhaar').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const aadhaar_number = document.getElementById('aadhaar-input').value.trim();
+ const action = document.querySelector('input[name="aadhaar-action"]:checked').value;
+ await executeVerification('/api/verify/aadhaar/', { aadhaar_number, action });
+ });
+
+ // Form 3: GSTIN Verification
+ document.getElementById('form-gstin').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const gstin = document.getElementById('gstin-input').value.trim();
+ await executeVerification('/api/verify/gstin/', { gstin });
+ });
+
+ // Form 4: IFSC Lookup
+ document.getElementById('form-ifsc').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const ifsc = document.getElementById('ifsc-input').value.trim();
+ await executeVerification('/api/verify/ifsc/', { ifsc });
+ });
+
+ // Form 5: Bank Account Verification
+ document.getElementById('form-bav').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const bank_account = document.getElementById('bav-acc-input').value.trim();
+ const ifsc = document.getElementById('bav-ifsc-input').value.trim();
+ const name = document.getElementById('bav-name-input').value.trim();
+ await executeVerification('/api/verify/bank-account/', { bank_account, ifsc, name });
+ });
+
+ // Form 6: Configuration
+ document.getElementById('form-config').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const client_id = document.getElementById('cfg-client-id').value.trim();
+ const client_secret = document.getElementById('cfg-client-secret').value.trim();
+ const environment = document.getElementById('cfg-env').value;
+
+ const startTime = performance.now();
+ try {
+ const res = await fetch('/api/config/', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ client_id, client_secret, environment })
+ });
+ const latency = Math.round(performance.now() - startTime);
+ const json = await res.json();
+
+ updateInspector({
+ status_code: res.status,
+ latency: latency,
+ source: 'configuration_update',
+ curl: `curl -X POST '${window.location.origin}/api/config/' \\\n -H 'Content-Type: application/json' \\\n -d '{"environment":"${environment}"}'`,
+ response: json
+ });
+
+ // Update UI status
+ document.getElementById('env-name-display').innerText = `${json.environment} MODE`;
+ document.getElementById('cred-status-msg').innerHTML = json.has_credentials ?
+ ' Custom API Credentials Active' :
+ ' Using Cashfree Sandbox Simulation';
+
+ } catch (err) {
+ alert('Error updating configuration: ' + err.message);
+ }
+ });
+}
+
+async function executeVerification(endpoint, payload) {
+ const statusBadge = document.getElementById('res-status-badge');
+ statusBadge.innerText = 'PROCESSING...';
+ statusBadge.className = 'badge';
+
+ const startTime = performance.now();
+ try {
+ const res = await fetch(endpoint, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+ const latency = Math.round(performance.now() - startTime);
+ const data = await res.json();
+
+ updateInspector({
+ status_code: data.status_code || res.status,
+ latency: latency,
+ source: data.source || 'server_api',
+ curl: data.curl || `curl -X POST '${window.location.origin}${endpoint}' -d '${JSON.stringify(payload)}'`,
+ response: data.response || data
+ });
+ } catch (err) {
+ const latency = Math.round(performance.now() - startTime);
+ updateInspector({
+ status_code: 500,
+ latency: latency,
+ source: 'client_error',
+ curl: `curl -X POST '${window.location.origin}${endpoint}' -d '${JSON.stringify(payload)}'`,
+ response: { error: 'Failed to connect to verification server', message: err.message }
+ });
+ }
+}
+
+function updateInspector({ status_code, latency, source, curl, response }) {
+ // Status badge
+ const statusBadge = document.getElementById('res-status-badge');
+ statusBadge.innerText = `HTTP ${status_code}`;
+ if (status_code >= 200 && status_code < 300) {
+ statusBadge.className = 'badge badge-success';
+ } else {
+ statusBadge.className = 'badge badge-error';
+ }
+
+ // Latency & Source
+ document.getElementById('res-latency-text').innerHTML = ` ${latency} ms`;
+ document.getElementById('res-source-tag').innerText = source.replace('_', ' ').toUpperCase();
+
+ // cURL & Response JSON
+ document.getElementById('curl-display').innerText = curl;
+ document.getElementById('json-response-display').innerText = JSON.stringify(response, null, 2);
+}
+
+// Quick Sample Loaders
+function loadSamplePAN(pan, name, type) {
+ document.getElementById('pan-input').value = pan;
+ document.getElementById('pan-name-input').value = name;
+ document.getElementById('pan-type-select').value = type;
+}
+
+function loadSampleAadhaar(aadhaar) {
+ document.getElementById('aadhaar-input').value = aadhaar;
+}
+
+function loadSampleGSTIN(gstin) {
+ document.getElementById('gstin-input').value = gstin;
+}
+
+function loadSampleIFSC(ifsc) {
+ document.getElementById('ifsc-input').value = ifsc;
+}
+
+function loadSampleBAV(acc, ifsc, name) {
+ document.getElementById('bav-acc-input').value = acc;
+ document.getElementById('bav-ifsc-input').value = ifsc;
+ document.getElementById('bav-name-input').value = name;
+}
+
+function initCopyButton() {
+ document.getElementById('btn-copy-curl').addEventListener('click', () => {
+ const curlText = document.getElementById('curl-display').innerText;
+ navigator.clipboard.writeText(curlText).then(() => {
+ const btn = document.getElementById('btn-copy-curl');
+ btn.innerHTML = ' Copied!';
+ setTimeout(() => {
+ btn.innerHTML = ' Copy cURL';
+ }, 2000);
+ });
+ });
+}
diff --git a/verifier/static/verifier/style.css b/verifier/static/verifier/style.css
new file mode 100644
index 0000000..85f3aaa
--- /dev/null
+++ b/verifier/static/verifier/style.css
@@ -0,0 +1,525 @@
+/* Modern Dark Mode & Glassmorphic CSS System for DocVerify AI */
+:root {
+ --bg-dark: #090d16;
+ --bg-card: rgba(17, 24, 39, 0.7);
+ --bg-card-border: rgba(255, 255, 255, 0.08);
+ --bg-input: #1e293b;
+ --text-main: #f8fafc;
+ --text-muted: #94a3b8;
+ --primary: #6366f1;
+ --primary-hover: #4f46e5;
+ --emerald: #10b981;
+ --emerald-hover: #059669;
+ --amber: #f59e0b;
+ --rose: #f43f5e;
+ --font-inter: 'Inter', sans-serif;
+ --font-outfit: 'Outfit', sans-serif;
+ --font-code: 'Fira Code', monospace;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--bg-dark);
+ color: var(--text-main);
+ font-family: var(--font-inter);
+ min-height: 100vh;
+ overflow-x: hidden;
+}
+
+.app-container {
+ display: flex;
+ min-height: 100vh;
+}
+
+/* Sidebar Styling */
+.sidebar {
+ width: 280px;
+ background: rgba(15, 23, 42, 0.85);
+ backdrop-filter: blur(12px);
+ border-right: 1px solid var(--bg-card-border);
+ padding: 1.5rem 1rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ shrink: 0;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding-bottom: 1.5rem;
+ border-bottom: 1px solid var(--bg-card-border);
+}
+
+.logo-icon {
+ width: 42px;
+ height: 42px;
+ background: linear-gradient(135deg, var(--primary), #818cf8);
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+ color: #fff;
+ box-shadow: 0 4px 14px rgba(99, 102, 241, 0.4);
+}
+
+.brand-text h2 {
+ font-family: var(--font-outfit);
+ font-size: 1.2rem;
+ font-weight: 700;
+ line-height: 1.2;
+}
+
+.badge-tag {
+ font-size: 0.7rem;
+ background: rgba(99, 102, 241, 0.2);
+ color: var(--primary);
+ padding: 2px 6px;
+ border-radius: 6px;
+ border: 1px solid rgba(99, 102, 241, 0.4);
+}
+
+.brand-sub {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+/* Navigation Menu */
+.nav-menu {
+ margin-top: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+}
+
+.menu-label {
+ font-size: 0.68rem;
+ font-weight: 700;
+ letter-spacing: 0.05em;
+ color: #64748b;
+ padding: 0.5rem 0.75rem 0.25rem 0.75rem;
+}
+
+.nav-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.75rem 1rem;
+ border: none;
+ background: transparent;
+ color: var(--text-muted);
+ font-family: var(--font-inter);
+ font-size: 0.9rem;
+ font-weight: 500;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-align: left;
+}
+
+.nav-btn i {
+ font-size: 1.1rem;
+ width: 20px;
+}
+
+.nav-btn:hover {
+ background: rgba(255, 255, 255, 0.04);
+ color: var(--text-main);
+}
+
+.nav-btn.active {
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.15), rgba(99, 102, 241, 0.05));
+ color: #818cf8;
+ border: 1px solid rgba(99, 102, 241, 0.3);
+ font-weight: 600;
+}
+
+/* Env Card */
+.env-status-card {
+ background: rgba(30, 41, 59, 0.6);
+ border: 1px solid var(--bg-card-border);
+ border-radius: 12px;
+ padding: 0.85rem;
+ margin-top: auto;
+}
+
+.env-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-family: var(--font-outfit);
+ font-size: 0.8rem;
+ font-weight: 700;
+ letter-spacing: 0.05em;
+ color: #cbd5e1;
+}
+
+.pulse-dot {
+ width: 8px;
+ height: 8px;
+ background-color: var(--emerald);
+ border-radius: 50%;
+ box-shadow: 0 0 8px var(--emerald);
+}
+
+.env-details {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ margin-top: 0.4rem;
+}
+
+/* Main Content Area */
+.main-content {
+ flex: 1;
+ padding: 2rem;
+ overflow-y: auto;
+}
+
+.top-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 2rem;
+ padding-bottom: 1.25rem;
+ border-bottom: 1px solid var(--bg-card-border);
+}
+
+.header-title h1 {
+ font-family: var(--font-outfit);
+ font-size: 1.8rem;
+ font-weight: 700;
+}
+
+.header-desc {
+ font-size: 0.9rem;
+ color: var(--text-muted);
+ margin-top: 0.25rem;
+}
+
+.api-badge {
+ background: rgba(30, 41, 59, 0.8);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ color: #a5b4fc;
+ font-family: var(--font-code);
+ font-size: 0.8rem;
+ padding: 0.4rem 0.8rem;
+ border-radius: 8px;
+}
+
+/* Grid Layout */
+.grid-layout {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.5rem;
+}
+
+@media (max-width: 1024px) {
+ .grid-layout {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Glass Card */
+.glass-card {
+ background: var(--bg-card);
+ backdrop-filter: blur(16px);
+ border: 1px solid var(--bg-card-border);
+ border-radius: 16px;
+ padding: 1.5rem;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.37);
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ margin-bottom: 1.25rem;
+ padding-bottom: 0.75rem;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.card-header i {
+ font-size: 1.25rem;
+}
+
+.card-header h3 {
+ font-family: var(--font-outfit);
+ font-size: 1.2rem;
+ font-weight: 600;
+}
+
+/* Forms */
+.tab-pane {
+ display: none;
+}
+
+.tab-pane.active {
+ display: block;
+}
+
+.form-group {
+ margin-bottom: 1.2rem;
+}
+
+.form-group label {
+ display: block;
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: #cbd5e1;
+ margin-bottom: 0.4rem;
+}
+
+.required {
+ color: var(--rose);
+}
+
+.form-control {
+ width: 100%;
+ padding: 0.75rem 1rem;
+ background-color: var(--bg-input);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 10px;
+ color: var(--text-main);
+ font-family: var(--font-inter);
+ font-size: 0.95rem;
+ outline: none;
+ transition: all 0.2s ease;
+}
+
+.form-control:focus {
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
+}
+
+.text-uppercase {
+ text-transform: uppercase;
+}
+
+.radio-group {
+ display: flex;
+ gap: 1.5rem;
+ padding: 0.4rem 0;
+}
+
+.radio-label {
+ font-size: 0.85rem;
+ color: #cbd5e1;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+}
+
+/* Sample Pills */
+.sample-pills-container {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.5rem;
+ margin-top: 1rem;
+ padding: 0.75rem;
+ background: rgba(255, 255, 255, 0.02);
+ border-radius: 10px;
+ border: 1px dashed rgba(255, 255, 255, 0.08);
+}
+
+.sample-label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ font-weight: 600;
+}
+
+.pill-btn {
+ background: rgba(99, 102, 241, 0.15);
+ border: 1px solid rgba(99, 102, 241, 0.3);
+ color: #a5b4fc;
+ font-size: 0.75rem;
+ font-weight: 500;
+ padding: 4px 10px;
+ border-radius: 20px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.pill-btn:hover {
+ background: var(--primary);
+ color: #fff;
+}
+
+.pill-btn-danger {
+ background: rgba(244, 63, 94, 0.15);
+ border-color: rgba(244, 63, 94, 0.3);
+ color: #fda4af;
+}
+
+.pill-btn-danger:hover {
+ background: var(--rose);
+ color: #fff;
+}
+
+/* Buttons */
+.btn {
+ padding: 0.8rem 1.25rem;
+ font-family: var(--font-inter);
+ font-size: 0.95rem;
+ font-weight: 600;
+ border-radius: 10px;
+ border: none;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, var(--primary), var(--primary-hover));
+ color: #fff;
+ box-shadow: 0 4px 14px rgba(99, 102, 241, 0.35);
+}
+
+.btn-primary:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 20px rgba(99, 102, 241, 0.5);
+}
+
+.btn-emerald {
+ background: linear-gradient(135deg, var(--emerald), var(--emerald-hover));
+ color: #fff;
+ box-shadow: 0 4px 14px rgba(16, 185, 129, 0.35);
+}
+
+.btn-emerald:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 20px rgba(16, 185, 129, 0.5);
+}
+
+.w-100 {
+ width: 100%;
+}
+
+.mt-3 {
+ margin-top: 1rem;
+}
+
+.mt-4 {
+ margin-top: 1.5rem;
+}
+
+/* Inspector Column */
+.inspector-card {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.inspector-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+ padding-bottom: 0.75rem;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.status-indicator {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.badge {
+ font-size: 0.75rem;
+ font-weight: 700;
+ padding: 4px 10px;
+ border-radius: 6px;
+ background: rgba(148, 163, 184, 0.2);
+ color: var(--text-muted);
+}
+
+.badge-success {
+ background: rgba(16, 185, 129, 0.2);
+ color: var(--emerald);
+ border: 1px solid rgba(16, 185, 129, 0.4);
+}
+
+.badge-error {
+ background: rgba(244, 63, 94, 0.2);
+ color: var(--rose);
+ border: 1px solid rgba(244, 63, 94, 0.4);
+}
+
+.latency-text {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+.source-tag {
+ font-size: 0.7rem;
+ padding: 2px 8px;
+ border-radius: 4px;
+ background: rgba(255, 255, 255, 0.05);
+ color: #a5b4fc;
+}
+
+.copy-btn {
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ color: var(--text-muted);
+ padding: 4px 10px;
+ border-radius: 6px;
+ font-size: 0.75rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.copy-btn:hover {
+ background: rgba(255, 255, 255, 0.1);
+ color: var(--text-main);
+}
+
+.code-box-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.code-box-title {
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: #64748b;
+ margin-bottom: 0.4rem;
+}
+
+.code-box {
+ background: #020617;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 10px;
+ padding: 1rem;
+ font-family: var(--font-code);
+ font-size: 0.82rem;
+ color: #38bdf8;
+ white-space: pre-wrap;
+ word-break: break-all;
+ overflow-x: auto;
+ max-height: 200px;
+}
+
+.json-box {
+ flex: 1;
+ max-height: 420px;
+ color: #e2e8f0;
+}
+
+/* Utilities */
+.text-indigo { color: var(--primary); }
+.text-emerald { color: var(--emerald); }
+.text-amber { color: var(--amber); }
diff --git a/verifier/templates/verifier/index.html b/verifier/templates/verifier/index.html
new file mode 100644
index 0000000..7b1674f
--- /dev/null
+++ b/verifier/templates/verifier/index.html
@@ -0,0 +1,339 @@
+{% load static %}
+
+
+
+
+
+ DocVerify AI - Cashfree Secure ID Verification Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
HTTP Request (cURL)
+
# Execute a verification call from the form on the left to see live request & response details
+
+
+
+
+
JSON Response Body
+
{
+ "status": "IDLE",
+ "message": "Select a verification service on the left to test the API."
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/verifier/tests.py b/verifier/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/verifier/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/verifier/urls.py b/verifier/urls.py
new file mode 100644
index 0000000..85c4ec1
--- /dev/null
+++ b/verifier/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path
+from . import views
+
+urlpatterns = [
+ path('', views.index_view, name='index'),
+ path('api/config/', views.config_api_view, name='api_config'),
+ path('api/verify/pan/', views.verify_pan_api, name='api_verify_pan'),
+ path('api/verify/aadhaar/', views.verify_aadhaar_api, name='api_verify_aadhaar'),
+ path('api/verify/gstin/', views.verify_gstin_api, name='api_verify_gstin'),
+ path('api/verify/ifsc/', views.verify_ifsc_api, name='api_verify_ifsc'),
+ path('api/verify/bank-account/', views.verify_bank_account_api, name='api_verify_bank_account'),
+]
diff --git a/verifier/views.py b/verifier/views.py
new file mode 100644
index 0000000..38c76d6
--- /dev/null
+++ b/verifier/views.py
@@ -0,0 +1,171 @@
+import json
+from django.shortcuts import render
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework import status
+
+from .services.cashfree_client import CashfreeSecureIDClient
+
+def get_cashfree_client(request):
+ """
+ Instantiate client using session-stored override credentials if available,
+ otherwise fallback to environment variables.
+ """
+ client_id = request.session.get('CASHFREE_CLIENT_ID')
+ client_secret = request.session.get('CASHFREE_CLIENT_SECRET')
+ environment = request.session.get('CASHFREE_ENV', 'TEST')
+
+ return CashfreeSecureIDClient(
+ client_id=client_id,
+ client_secret=client_secret,
+ environment=environment
+ )
+
+def index_view(request):
+ """
+ Main Web Application Dashboard UI.
+ """
+ client = get_cashfree_client(request)
+ context = {
+ "client_id": client.client_id,
+ "environment": client.environment,
+ "has_credentials": client.has_credentials(),
+ }
+ return render(request, "verifier/index.html", context)
+
+@api_view(['GET', 'POST'])
+def config_api_view(request):
+ """
+ API to fetch or update active Cashfree credentials and environment configuration.
+ """
+ if request.method == 'GET':
+ client = get_cashfree_client(request)
+ return Response({
+ "client_id": client.client_id,
+ "has_credentials": client.has_credentials(),
+ "environment": client.environment,
+ "base_url": client.base_url
+ })
+ elif request.method == 'POST':
+ data = request.data or {}
+ client_id = data.get('client_id', '').strip()
+ client_secret = data.get('client_secret', '').strip()
+ environment = data.get('environment', 'TEST').upper()
+
+ request.session['CASHFREE_CLIENT_ID'] = client_id
+ request.session['CASHFREE_CLIENT_SECRET'] = client_secret
+ request.session['CASHFREE_ENV'] = environment
+
+ client = CashfreeSecureIDClient(client_id=client_id, client_secret=client_secret, environment=environment)
+ verify_res = client.verify_credentials() if client.has_credentials() else {"success": False, "message": "No credentials provided"}
+
+ return Response({
+ "message": "Configuration updated successfully",
+ "client_id": client.client_id,
+ "environment": client.environment,
+ "has_credentials": client.has_credentials(),
+ "credential_check": verify_res
+ })
+
+@api_view(['POST'])
+def verify_pan_api(request):
+ """
+ POST /api/verify/pan/
+ Verify PAN details (Individual / Business).
+ """
+ data = request.data or {}
+ pan = data.get('pan', '')
+ name = data.get('name', '')
+ pan_type = data.get('type', 'INDIVIDUAL')
+
+ if not pan:
+ return Response({
+ "error": "PAN parameter is required.",
+ "field": "pan"
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ client = get_cashfree_client(request)
+ result = client.verify_pan(pan=pan, name=name, pan_type=pan_type)
+ return Response(result)
+
+@api_view(['POST'])
+def verify_aadhaar_api(request):
+ """
+ POST /api/verify/aadhaar/
+ Verify Aadhaar details or execute Aadhaar masking.
+ """
+ data = request.data or {}
+ aadhaar_number = data.get('aadhaar_number', '')
+ action = data.get('action', 'verify') # 'verify' or 'mask'
+
+ if not aadhaar_number:
+ return Response({
+ "error": "Aadhaar number parameter is required.",
+ "field": "aadhaar_number"
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ client = get_cashfree_client(request)
+ result = client.verify_aadhaar(aadhaar_number=aadhaar_number, action=action)
+ return Response(result)
+
+@api_view(['POST'])
+def verify_gstin_api(request):
+ """
+ POST /api/verify/gstin/
+ Verify Goods and Services Tax Identification Number (GSTIN).
+ """
+ data = request.data or {}
+ gstin = data.get('gstin', '')
+
+ if not gstin:
+ return Response({
+ "error": "GSTIN parameter is required.",
+ "field": "gstin"
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ client = get_cashfree_client(request)
+ result = client.verify_gstin(gstin=gstin)
+ return Response(result)
+
+@api_view(['POST'])
+def verify_ifsc_api(request):
+ """
+ POST /api/verify/ifsc/
+ Lookup bank details, branch address, and supported payment modes using IFSC code.
+ """
+ data = request.data or {}
+ ifsc = data.get('ifsc', '')
+
+ if not ifsc:
+ return Response({
+ "error": "IFSC code parameter is required.",
+ "field": "ifsc"
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ client = get_cashfree_client(request)
+ result = client.verify_ifsc(ifsc=ifsc)
+ return Response(result)
+
+@api_view(['POST'])
+def verify_bank_account_api(request):
+ """
+ POST /api/verify/bank-account/
+ Synchronous Bank Account Verification (BAV Sync).
+ """
+ data = request.data or {}
+ bank_account = data.get('bank_account', '')
+ ifsc = data.get('ifsc', '')
+ name = data.get('name', '')
+ phone = data.get('phone', '')
+
+ if not bank_account or not ifsc:
+ return Response({
+ "error": "Both bank_account and ifsc parameters are required.",
+ "fields": ["bank_account", "ifsc"]
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ client = get_cashfree_client(request)
+ result = client.verify_bank_account(bank_account=bank_account, ifsc=ifsc, name=name, phone=phone)
+ return Response(result)