All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 29s
146 lines
5.5 KiB
Python
146 lines
5.5 KiB
Python
import logging
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
# Import engine and vendors to ensure default registration
|
|
import verifier.vendors # noqa: F401
|
|
from verifier.engine import engine, registry
|
|
from .models import VerificationTransaction, VendorAttemptLog
|
|
from .serializers import (
|
|
VerifyDocumentRequestSerializer,
|
|
VerificationTransactionSerializer,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def mask_sensitive_payload(payload: dict) -> dict:
|
|
"""Mask sensitive inputs like full aadhaar numbers or passwords for audit safety."""
|
|
masked = dict(payload)
|
|
sensitive_keys = ["aadhaar_number", "aadhaar", "otp", "password", "client_secret"]
|
|
for key in sensitive_keys:
|
|
if key in masked and isinstance(masked[key], str):
|
|
val = masked[key]
|
|
if len(val) > 4:
|
|
masked[key] = f"{val[:2]}****{val[-2:]}"
|
|
else:
|
|
masked[key] = "****"
|
|
return masked
|
|
|
|
|
|
class DocumentVerifyView(APIView):
|
|
"""
|
|
POST /api/verify/
|
|
Verifies a document using the vendor waterfall failover engine.
|
|
If the first vendor encounters an error/failure, the engine automatically
|
|
cascades to the next available vendor.
|
|
"""
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
serializer = VerifyDocumentRequestSerializer(data=request.data)
|
|
if not serializer.is_valid():
|
|
return Response(
|
|
{"is_success": False, "errors": serializer.errors},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
doc_type = serializer.validated_data["document_type"]
|
|
input_data = serializer.validated_data["data"]
|
|
preferred_vendor = serializer.validated_data.get("preferred_vendor")
|
|
files = request.FILES if hasattr(request, "FILES") else None
|
|
|
|
# Execute waterfall verification
|
|
engine_result = engine.verify(
|
|
document_type=doc_type,
|
|
data=input_data,
|
|
files=files,
|
|
preferred_vendor=preferred_vendor,
|
|
)
|
|
|
|
# Persist transaction and audit attempts in DB
|
|
safe_input = mask_sensitive_payload(input_data)
|
|
transaction = VerificationTransaction.objects.create(
|
|
document_type=doc_type,
|
|
status=engine_result.status.value,
|
|
successful_vendor=engine_result.successful_vendor,
|
|
input_payload=safe_input,
|
|
final_data=engine_result.data if engine_result.is_success else {},
|
|
attempts_count=len(engine_result.attempts),
|
|
total_latency_ms=engine_result.total_latency_ms,
|
|
)
|
|
|
|
# Persist attempt logs
|
|
for attempt in engine_result.attempts:
|
|
VendorAttemptLog.objects.create(
|
|
transaction=transaction,
|
|
attempt_number=attempt.get("attempt_number", 1),
|
|
vendor_name=attempt.get("vendor_name", "unknown"),
|
|
status=attempt.get("status", "UNKNOWN"),
|
|
is_success=attempt.get("is_success", False),
|
|
data=attempt.get("data", {}),
|
|
raw_response=attempt.get("raw_response"),
|
|
error_code=attempt.get("error_code"),
|
|
error_message=attempt.get("error_message"),
|
|
latency_ms=attempt.get("latency_ms", 0.0),
|
|
)
|
|
|
|
response_payload = {
|
|
"reference_id": str(transaction.reference_id),
|
|
"is_success": engine_result.is_success,
|
|
"status": engine_result.status.value,
|
|
"document_type": doc_type,
|
|
"successful_vendor": engine_result.successful_vendor,
|
|
"data": engine_result.data,
|
|
"attempts_count": len(engine_result.attempts),
|
|
"attempts": engine_result.attempts,
|
|
"error_message": engine_result.error_message,
|
|
"total_latency_ms": round(engine_result.total_latency_ms, 2),
|
|
}
|
|
|
|
# Store complete client response payload in database record
|
|
transaction.response_payload = response_payload
|
|
transaction.save(update_fields=["response_payload"])
|
|
|
|
http_status = status.HTTP_200_OK if engine_result.is_success else status.HTTP_422_UNPROCESSABLE_ENTITY
|
|
return Response(response_payload, status=http_status)
|
|
|
|
|
|
class VendorListView(APIView):
|
|
"""
|
|
GET /api/vendors/
|
|
Lists all active verification vendors registered with the engine in priority order.
|
|
"""
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
vendors = registry.get_all_vendors()
|
|
vendor_data = [
|
|
{
|
|
"name": v.name,
|
|
"display_name": getattr(v, "display_name", v.name),
|
|
"priority": v.priority,
|
|
"is_active": v.is_active,
|
|
"supported_documents": v.supported_documents,
|
|
}
|
|
for v in vendors
|
|
]
|
|
return Response({"count": len(vendor_data), "vendors": vendor_data})
|
|
|
|
|
|
class TransactionDetailView(APIView):
|
|
"""
|
|
GET /api/transactions/<uuid:reference_id>/
|
|
Fetch complete audit trail and fallback logs for a transaction.
|
|
"""
|
|
|
|
def get(self, request, reference_id, *args, **kwargs):
|
|
try:
|
|
tx = VerificationTransaction.objects.prefetch_related("attempts").get(reference_id=reference_id)
|
|
except VerificationTransaction.DoesNotExist:
|
|
return Response(
|
|
{"error": f"Transaction '{reference_id}' not found"},
|
|
status=status.HTTP_404_NOT_FOUND,
|
|
)
|
|
|
|
serializer = VerificationTransactionSerializer(tx)
|
|
return Response(serializer.data)
|