feat: initialize Django document verification application and Cashfree Secure ID integration
This commit is contained in:
parent
8359fa88dd
commit
abad73be0d
19 changed files with 2009 additions and 0 deletions
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
|
|
@ -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
|
||||
0
docverify/__init__.py
Normal file
0
docverify/__init__.py
Normal file
16
docverify/asgi.py
Normal file
16
docverify/asgi.py
Normal file
|
|
@ -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()
|
||||
108
docverify/settings.py
Normal file
108
docverify/settings.py
Normal file
|
|
@ -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')
|
||||
7
docverify/urls.py
Normal file
7
docverify/urls.py
Normal file
|
|
@ -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')),
|
||||
]
|
||||
16
docverify/wsgi.py
Normal file
16
docverify/wsgi.py
Normal file
|
|
@ -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()
|
||||
22
manage.py
Executable file
22
manage.py
Executable file
|
|
@ -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()
|
||||
0
verifier/__init__.py
Normal file
0
verifier/__init__.py
Normal file
3
verifier/admin.py
Normal file
3
verifier/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
verifier/apps.py
Normal file
5
verifier/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class VerifierConfig(AppConfig):
|
||||
name = 'verifier'
|
||||
0
verifier/migrations/__init__.py
Normal file
0
verifier/migrations/__init__.py
Normal file
3
verifier/models.py
Normal file
3
verifier/models.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
508
verifier/services/cashfree_client.py
Normal file
508
verifier/services/cashfree_client.py
Normal file
|
|
@ -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
|
||||
}
|
||||
241
verifier/static/verifier/app.js
Normal file
241
verifier/static/verifier/app.js
Normal file
|
|
@ -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 = `<i class="fa-solid fa-code"></i> ${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 ?
|
||||
'<i class="fa-solid fa-circle-check text-emerald"></i> Custom API Credentials Active' :
|
||||
'<i class="fa-solid fa-flask text-amber"></i> 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 = `<i class="fa-regular fa-clock"></i> ${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 = '<i class="fa-solid fa-check text-emerald"></i> Copied!';
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = '<i class="fa-regular fa-copy"></i> Copy cURL';
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
}
|
||||
525
verifier/static/verifier/style.css
Normal file
525
verifier/static/verifier/style.css
Normal file
|
|
@ -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); }
|
||||
339
verifier/templates/verifier/index.html
Normal file
339
verifier/templates/verifier/index.html
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DocVerify AI - Cashfree Secure ID Verification Suite</title>
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@500;600;700;800&display=swap" rel="stylesheet">
|
||||
<!-- Font Awesome Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="{% static 'verifier/style.css' %}">
|
||||
</head>
|
||||
<body class="bg-dark text-light font-inter">
|
||||
|
||||
<div class="app-container">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="logo-icon">
|
||||
<i class="fa-solid fa-shield-halved"></i>
|
||||
</div>
|
||||
<div class="brand-text">
|
||||
<h2>DocVerify <span class="badge-tag">AI</span></h2>
|
||||
<p class="brand-sub">Powered by Cashfree Secure ID</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<div class="menu-label">VERIFICATION SERVICES</div>
|
||||
<button class="nav-btn active" data-tab="tab-pan">
|
||||
<i class="fa-solid fa-id-card"></i>
|
||||
<span>PAN Verification</span>
|
||||
</button>
|
||||
<button class="nav-btn" data-tab="tab-aadhaar">
|
||||
<i class="fa-solid fa-fingerprint"></i>
|
||||
<span>Aadhaar Suite</span>
|
||||
</button>
|
||||
<button class="nav-btn" data-tab="tab-gstin">
|
||||
<i class="fa-solid fa-building"></i>
|
||||
<span>GSTIN / Business</span>
|
||||
</button>
|
||||
<button class="nav-btn" data-tab="tab-ifsc">
|
||||
<i class="fa-solid fa-building-columns"></i>
|
||||
<span>IFSC Lookup</span>
|
||||
</button>
|
||||
<button class="nav-btn" data-tab="tab-bav">
|
||||
<i class="fa-solid fa-money-check-dollar"></i>
|
||||
<span>Bank Account (BAV)</span>
|
||||
</button>
|
||||
|
||||
<div class="menu-label mt-4">SETTINGS & API</div>
|
||||
<button class="nav-btn" data-tab="tab-config">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
<span>API Credentials</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="env-status-card">
|
||||
<div class="env-header">
|
||||
<span class="pulse-dot" id="env-pulse"></span>
|
||||
<span id="env-name-display">{{ environment }} MODE</span>
|
||||
</div>
|
||||
<div class="env-details" id="cred-status-msg">
|
||||
{% if has_credentials %}
|
||||
<i class="fa-solid fa-circle-check text-emerald"></i> Custom API Credentials Active
|
||||
{% else %}
|
||||
<i class="fa-solid fa-flask text-amber"></i> Using Cashfree Sandbox Simulation
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Workspace -->
|
||||
<main class="main-content">
|
||||
<!-- Top Header -->
|
||||
<header class="top-header">
|
||||
<div class="header-title">
|
||||
<h1 id="page-title">PAN Verification Service</h1>
|
||||
<p class="header-desc" id="page-desc">Validate individual or business Permanent Account Numbers (PAN) with name match algorithms.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<span class="api-badge" id="api-endpoint-badge"><i class="fa-solid fa-code"></i> POST /api/verify/pan/</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Dashboard Content Grid -->
|
||||
<div class="grid-layout">
|
||||
<!-- Service Form Column -->
|
||||
<div class="form-column">
|
||||
<!-- Tab 1: PAN Verification -->
|
||||
<div class="tab-pane active" id="tab-pan">
|
||||
<div class="glass-card">
|
||||
<div class="card-header">
|
||||
<i class="fa-solid fa-id-card text-indigo"></i>
|
||||
<h3>PAN Verification Form</h3>
|
||||
</div>
|
||||
|
||||
<form id="form-pan">
|
||||
<div class="form-group">
|
||||
<label for="pan-input">PAN Number <span class="required">*</span></label>
|
||||
<input type="text" id="pan-input" class="form-control text-uppercase" placeholder="e.g. ABCPV1234D" maxlength="10" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pan-name-input">Full Name (Optional for Name Match)</label>
|
||||
<input type="text" id="pan-name-input" class="form-control" placeholder="e.g. JOHN DOE">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pan-type-select">Entity Type</label>
|
||||
<select id="pan-type-select" class="form-control">
|
||||
<option value="INDIVIDUAL">Individual</option>
|
||||
<option value="BUSINESS">Company / Business</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="sample-pills-container">
|
||||
<span class="sample-label">Quick Test Data:</span>
|
||||
<button type="button" class="pill-btn" onclick="loadSamplePAN('ABCPV1234D', 'JOHN DOE', 'INDIVIDUAL')">Valid Individual</button>
|
||||
<button type="button" class="pill-btn" onclick="loadSamplePAN('ABCCD8000T', 'ACME SOLUTIONS PVT LTD', 'BUSINESS')">Valid Business</button>
|
||||
<button type="button" class="pill-btn pill-btn-danger" onclick="loadSamplePAN('DEFPV0126D', '', 'INDIVIDUAL')">Invalid PAN</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 mt-3">
|
||||
<i class="fa-solid fa-magnifying-glass"></i> Verify PAN Now
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Aadhaar Suite -->
|
||||
<div class="tab-pane" id="tab-aadhaar">
|
||||
<div class="glass-card">
|
||||
<div class="card-header">
|
||||
<i class="fa-solid fa-fingerprint text-indigo"></i>
|
||||
<h3>Aadhaar Verification & Masking</h3>
|
||||
</div>
|
||||
|
||||
<form id="form-aadhaar">
|
||||
<div class="form-group">
|
||||
<label for="aadhaar-input">Aadhaar Number (12 Digits) <span class="required">*</span></label>
|
||||
<input type="text" id="aadhaar-input" class="form-control" placeholder="e.g. 655675523712" maxlength="14" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Action</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="aadhaar-action" value="verify" checked> Verify Status
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="aadhaar-action" value="mask"> Execute Aadhaar Masking
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sample-pills-container">
|
||||
<span class="sample-label">Quick Test Data:</span>
|
||||
<button type="button" class="pill-btn" onclick="loadSampleAadhaar('655675523712')">Valid Aadhaar</button>
|
||||
<button type="button" class="pill-btn pill-btn-danger" onclick="loadSampleAadhaar('655675523710')">Invalid Aadhaar</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 mt-3">
|
||||
<i class="fa-solid fa-shield-check"></i> Process Aadhaar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 3: GSTIN Verification -->
|
||||
<div class="tab-pane" id="tab-gstin">
|
||||
<div class="glass-card">
|
||||
<div class="card-header">
|
||||
<i class="fa-solid fa-building text-indigo"></i>
|
||||
<h3>GSTIN / Business Entity Verification</h3>
|
||||
</div>
|
||||
|
||||
<form id="form-gstin">
|
||||
<div class="form-group">
|
||||
<label for="gstin-input">GSTIN Number (15 Alphanumeric) <span class="required">*</span></label>
|
||||
<input type="text" id="gstin-input" class="form-control text-uppercase" placeholder="e.g. 29AAICP2912R1ZR" maxlength="15" required>
|
||||
</div>
|
||||
|
||||
<div class="sample-pills-container">
|
||||
<span class="sample-label">Quick Test Data:</span>
|
||||
<button type="button" class="pill-btn" onclick="loadSampleGSTIN('29AAICP2912R1ZR')">Valid Cashfree GSTIN</button>
|
||||
<button type="button" class="pill-btn" onclick="loadSampleGSTIN('27ABCFGH5678D1Z')">Valid Maharashtra GSTIN</button>
|
||||
<button type="button" class="pill-btn pill-btn-danger" onclick="loadSampleGSTIN('29AAIZP2912R1ZR')">Invalid GSTIN</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 mt-3">
|
||||
<i class="fa-solid fa-magnifying-glass-chart"></i> Verify Business GSTIN
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 4: IFSC Lookup -->
|
||||
<div class="tab-pane" id="tab-ifsc">
|
||||
<div class="glass-card">
|
||||
<div class="card-header">
|
||||
<i class="fa-solid fa-building-columns text-indigo"></i>
|
||||
<h3>IFSC Code Lookup & Branch Info</h3>
|
||||
</div>
|
||||
|
||||
<form id="form-ifsc">
|
||||
<div class="form-group">
|
||||
<label for="ifsc-input">IFSC Code <span class="required">*</span></label>
|
||||
<input type="text" id="ifsc-input" class="form-control text-uppercase" placeholder="e.g. YESB0000262" maxlength="11" required>
|
||||
</div>
|
||||
|
||||
<div class="sample-pills-container">
|
||||
<span class="sample-label">Quick Test Data:</span>
|
||||
<button type="button" class="pill-btn" onclick="loadSampleIFSC('YESB0000262')">Yes Bank Koramangala</button>
|
||||
<button type="button" class="pill-btn" onclick="loadSampleIFSC('HDFC0000001')">HDFC Fort Mumbai</button>
|
||||
<button type="button" class="pill-btn pill-btn-danger" onclick="loadSampleIFSC('CNRR0002640')">Invalid IFSC</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 mt-3">
|
||||
<i class="fa-solid fa-search-dollar"></i> Lookup IFSC Code
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 5: Bank Account Verification (BAV) -->
|
||||
<div class="tab-pane" id="tab-bav">
|
||||
<div class="glass-card">
|
||||
<div class="card-header">
|
||||
<i class="fa-solid fa-money-check-dollar text-indigo"></i>
|
||||
<h3>Bank Account Verification (BAV Sync)</h3>
|
||||
</div>
|
||||
|
||||
<form id="form-bav">
|
||||
<div class="form-group">
|
||||
<label for="bav-acc-input">Bank Account Number <span class="required">*</span></label>
|
||||
<input type="text" id="bav-acc-input" class="form-control" placeholder="e.g. 026291800001191" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="bav-ifsc-input">IFSC Code <span class="required">*</span></label>
|
||||
<input type="text" id="bav-ifsc-input" class="form-control text-uppercase" placeholder="e.g. YESB0000262" maxlength="11" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="bav-name-input">Account Holder Name (Optional for Name Match)</label>
|
||||
<input type="text" id="bav-name-input" class="form-control" placeholder="e.g. JOHN DOE">
|
||||
</div>
|
||||
|
||||
<div class="sample-pills-container">
|
||||
<span class="sample-label">Quick Test Data:</span>
|
||||
<button type="button" class="pill-btn" onclick="loadSampleBAV('026291800001191', 'YESB0000262', 'JOHN DOE')">Valid Account</button>
|
||||
<button type="button" class="pill-btn pill-btn-danger" onclick="loadSampleBAV('000100289877623', 'SBIN0008752', '')">Failed at Bank</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 mt-3">
|
||||
<i class="fa-solid fa-check-double"></i> Verify Bank Account
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 6: API Configuration -->
|
||||
<div class="tab-pane" id="tab-config">
|
||||
<div class="glass-card">
|
||||
<div class="card-header">
|
||||
<i class="fa-solid fa-sliders text-indigo"></i>
|
||||
<h3>Cashfree Secure ID API Credentials</h3>
|
||||
</div>
|
||||
|
||||
<form id="form-config">
|
||||
<div class="form-group">
|
||||
<label for="cfg-client-id">Cashfree Client ID (App ID)</label>
|
||||
<input type="text" id="cfg-client-id" class="form-control" placeholder="Enter Cashfree App ID" value="{{ client_id }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="cfg-client-secret">Cashfree Client Secret Key</label>
|
||||
<input type="password" id="cfg-client-secret" class="form-control" placeholder="Enter Cashfree Secret Key">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="cfg-env">Environment</label>
|
||||
<select id="cfg-env" class="form-control">
|
||||
<option value="TEST" {% if environment == 'TEST' %}selected{% endif %}>Sandbox (TEST - sandbox.cashfree.com)</option>
|
||||
<option value="PROD" {% if environment == 'PROD' %}selected{% endif %}>Production (PROD - api.cashfree.com)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-emerald w-100 mt-3">
|
||||
<i class="fa-solid fa-floppy-disk"></i> Save & Test Credentials
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Response & Inspection Column -->
|
||||
<div class="inspector-column">
|
||||
<div class="glass-card inspector-card">
|
||||
<div class="inspector-header">
|
||||
<div class="status-indicator">
|
||||
<span class="badge" id="res-status-badge">READY</span>
|
||||
<span class="latency-text" id="res-latency-text"><i class="fa-regular fa-clock"></i> 0 ms</span>
|
||||
<span class="source-tag" id="res-source-tag">Idle</span>
|
||||
</div>
|
||||
<button class="copy-btn" id="btn-copy-curl" title="Copy cURL Command">
|
||||
<i class="fa-regular fa-copy"></i> Copy cURL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- cURL Command Box -->
|
||||
<div class="code-box-container">
|
||||
<div class="code-box-title">HTTP Request (cURL)</div>
|
||||
<pre class="code-box" id="curl-display"># Execute a verification call from the form on the left to see live request & response details</pre>
|
||||
</div>
|
||||
|
||||
<!-- JSON Response Box -->
|
||||
<div class="code-box-container mt-3">
|
||||
<div class="code-box-title">JSON Response Body</div>
|
||||
<pre class="code-box json-box" id="json-response-display">{
|
||||
"status": "IDLE",
|
||||
"message": "Select a verification service on the left to test the API."
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="{% static 'verifier/app.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
verifier/tests.py
Normal file
3
verifier/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
12
verifier/urls.py
Normal file
12
verifier/urls.py
Normal file
|
|
@ -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'),
|
||||
]
|
||||
171
verifier/views.py
Normal file
171
verifier/views.py
Normal file
|
|
@ -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)
|
||||
Loading…
Reference in a new issue