feat: add base verification engine, Codeplus vendor adapter, settings, and tests
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 30s

This commit is contained in:
vickytechkey 2026-10-15 19:59:25 +05:30
parent c6b177ac55
commit 5e11485ba8
4 changed files with 1457 additions and 192 deletions

View file

@ -116,7 +116,8 @@ REST_FRAMEWORK = {
# Vendor API Configurations
# ──────────────────────────────────────────────────────────────────────────────
# Codeplus API Credentials
CODEPLUS_BASE_URL = os.environ.get("CODEPLUS_BASE_URL", "https://api.codeplus.in")
CODEPLUS_API_KEY = os.environ.get("CODEPLUS_API_KEY", "")
CODEPLUS_CLIENT_ID = os.environ.get("CODEPLUS_CLIENT_ID", "")
CODEPLUS_CLIENT_SECRET = os.environ.get("CODEPLUS_CLIENT_SECRET", "")
CODEPLUS_BASE_URL = os.environ.get("CODEPLUS_BASE_URL", "https://apihub.services")
CODEPLUS_CLIENT_API_ID = os.environ.get("CODEPLUS_CLIENT_API_ID", "CAPI-YYR0YJIE0VMTDENY")
CODEPLUS_CLIENT_API_SECRET = os.environ.get("CODEPLUS_CLIENT_API_SECRET", "s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs")

View file

@ -6,15 +6,27 @@ from typing import Any, Dict, List, Optional
class DocumentType(str, Enum):
PAN = "pan"
PAN_V5 = "pan_v5"
PAN_VERIFY = "pan_verify"
PAN_ADVANCED = "pan_advanced"
PAN_DETAILED = "pan_detailed"
AADHAAR = "aadhaar"
DRIVING_LICENSE = "driving_license"
VOTER_ID = "voter_id"
PASSPORT = "passport"
BANK_ACCOUNT = "bank_account"
GSTIN = "gstin"
GST = "gst"
GST_VERIFY = "gst_verify"
GST_BY_PAN = "gst_by_pan"
FETCH_GST = "fetch_gst"
MSME = "msme"
MSME_BY_PAN = "msme_by_pan"
UDYAM = "udyam"
CUSTOM = "custom"
class VerificationStatus(str, Enum):
SUCCESS = "SUCCESS"
FAILED = "FAILED"

View file

@ -104,7 +104,13 @@ class CodeplusVendorTests(TestCase):
"""Tests Codeplus vendor adapter configuration and handling."""
def setUp(self):
self.codeplus = CodeplusVendor(config={"base_url": "https://api.codeplus.in", "api_key": "test_key"})
self.codeplus = CodeplusVendor(
config={
"base_url": "https://apihub.services",
"client_api_id": "CAPI-YYR0YJIE0VMTDENY",
"client_api_secret": "test_secret_123",
}
)
def test_codeplus_metadata(self):
self.assertEqual(self.codeplus.name, "codeplus")
@ -114,8 +120,397 @@ class CodeplusVendorTests(TestCase):
def test_codeplus_headers(self):
headers = self.codeplus.get_headers()
self.assertEqual(headers["Authorization"], "Bearer test_key")
self.assertEqual(headers["x-api-key"], "test_key")
self.assertEqual(headers["X-Client-Api-ID"], "CAPI-YYR0YJIE0VMTDENY")
self.assertEqual(headers["X-Client-Api-Secret"], "test_secret_123")
self.assertEqual(headers["Content-Type"], "application/json")
def test_codeplus_pan_v5_success(self):
"""Test parsing valid PAN response via v5 Recommended API."""
mock_response_payload = {
"success": True,
"message": "PAN verification completed",
"data": {
"transactionId": "TXN_1787136465045_demo12345",
"service": "PAN_VERIFICATION",
"success": True,
"resultCode": 101,
"status": "SUCCESS",
"message": "PAN verified successfully",
"providerTransactionId": "TXN_1787136465045_demo12345",
"pan": "ABCDE1234F",
"panType": "Individual",
"fullName": "RAHUL KUMAR",
"firstName": "RAHUL",
"middleName": None,
"lastName": "KUMAR",
"gender": "male",
"dob": "15/06/1995",
"aadhaarNumber": "XXXXXXXX1234",
"aadhaarLinked": True,
"addressDetails": {
"building_name": "12/45",
"locality": "Central Area",
"street_name": "MG Road",
"pincode": "641001",
"city": "COIMBATORE",
"state": "Tamil Nadu",
"country": "India",
},
"clientRefNum": "REQ_1787136465034_demo12345",
},
"requestId": "REQ_1787136465034_demo12345",
"timestamp": "2026-08-19T10:47:45.735Z",
"statusCode": 200,
"status": "SUCCESS",
}
class MockResponse:
status_code = 200
def json(self):
return mock_response_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("pan", {"pan": "ABCDE1234F"})
self.assertTrue(result.is_success)
self.assertEqual(result.status, VerificationStatus.SUCCESS)
self.assertEqual(result.data["full_name"], "RAHUL KUMAR")
self.assertEqual(result.data["dob"], "15/06/1995")
self.assertEqual(result.data["pan_type"], "Individual")
self.assertEqual(result.data["address_details"]["city"], "COIMBATORE")
self.assertTrue(result.data["aadhaar_linked"])
def test_codeplus_pan_advanced_success(self):
"""Test parsing valid PAN response via v4 Advanced API (HTTP 200, code 1000)."""
mock_response_payload = {
"request_id": "8a42f1c7-63d9-4e15-b820-7c91a5d34628",
"transaction_id": "d5b73e91-2a46-4c08-9f61-83e2b7a51439",
"status": 200,
"data": {
"code": "1000",
"message": "Valid PAN.",
"pan_data": {
"document_type": "PAN",
"document_id": "ABCDE1234F",
"name": "RAHUL KUMAR",
"first_name": "RAHUL",
"last_name": "KUMAR",
"category": "P",
"category_type": "Individual",
"is_individual": True,
"individual_tax_compliance_status": "Operative",
"aadhaar_linked": True,
},
},
"timestamp": 1787031203055,
"path": "/pan-api/fetch-advanced",
}
class MockResponse:
status_code = 200
def json(self):
return mock_response_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("pan_advanced", {"pan": "ABCDE1234F"})
self.assertTrue(result.is_success)
self.assertEqual(result.status, VerificationStatus.SUCCESS)
self.assertEqual(result.data["name"], "RAHUL KUMAR")
self.assertEqual(result.data["document_id"], "ABCDE1234F")
self.assertTrue(result.data["aadhaar_linked"])
def test_codeplus_pan_advanced_error_nonexistent(self):
"""Test parsing non-existent PAN (code 1004)."""
mock_error_payload = {
"request_id": "23bc5fa5-a02e-4246-aefc-3c455bcd1853",
"transaction_id": "542f956c-db4f-48f5-8d6b-d1877a0b930a",
"status": 200,
"data": {
"code": "1004",
"message": "PAN does not exist.",
},
"timestamp": 1787031223795,
"path": "/pan-api/fetch-advanced",
}
class MockResponse:
status_code = 400
def json(self):
return mock_error_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("pan_advanced", {"pan": "INVALID999Z"})
self.assertFalse(result.is_success)
self.assertEqual(result.error_code, "1004")
self.assertEqual(result.error_message, "PAN does not exist.")
def test_codeplus_pan_detailed_success(self):
"""Test parsing valid Detailed PAN response (HTTP 200, code 1000 with address/email/phone)."""
mock_response_payload = {
"request_id": "7f3a1c92-5b84-4d21-a7e6-91c2f0d84b56",
"transaction_id": "b6e8d4a1-2c73-49f5-9a10-5f7c3e821d64",
"status": 200,
"data": {
"code": "1000",
"message": "Valid PAN.",
"pan_data": {
"document_type": "PAN",
"document_id": "ABCDE1234F",
"name": "RAHUL KUMAR",
"first_name": "RAHUL",
"last_name": "KUMAR",
"category": "P",
"date_of_birth": "1995-06-15",
"masked_aadhaar_number": "XXXXXXXX1234",
"address_data": {
"line_1": "12/45",
"city": "COIMBATORE",
"state": "TAMIL NADU",
"pincode": "641001",
},
"email": "ra******@example.com",
"phone": "98XXXXXX10",
"gender": "MALE",
"aadhaar_linked": True,
"taxpayer_profile": True,
},
},
"timestamp": 1787028660489,
"path": "/pan-api/fetch-detailed",
}
class MockResponse:
status_code = 200
def json(self):
return mock_response_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("pan_detailed", {"pan_number": "ABCDE1234F"})
self.assertTrue(result.is_success)
self.assertEqual(result.status, VerificationStatus.SUCCESS)
self.assertEqual(result.data["name"], "RAHUL KUMAR")
self.assertEqual(result.data["email"], "ra******@example.com")
self.assertEqual(result.data["phone"], "98XXXXXX10")
self.assertEqual(result.data["date_of_birth"], "1995-06-15")
self.assertEqual(result.data["address_data"]["city"], "COIMBATORE")
def test_codeplus_pan_detailed_error(self):
"""Test parsing invalid Detailed PAN error (HTTP 400 with error block)."""
mock_error_payload = {
"request_id": "b89afa37-8282-4cf7-827f-985700f84ab8",
"transaction_id": "d796efe7-26e2-41c0-a1e7-2f7387b2d5aa",
"status": 400,
"error": {
"code": "INVALID_PAN",
"message": "Invalid PAN number.",
},
"timestamp": 1787028631589,
"path": "/pan-api/fetch-detailed",
}
class MockResponse:
status_code = 400
def json(self):
return mock_error_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("pan_detailed", {"pan_number": "INVALID999Z"})
self.assertFalse(result.is_success)
self.assertEqual(result.error_code, "INVALID_PAN")
self.assertEqual(result.error_message, "Invalid PAN number.")
def test_codeplus_bank_verify_success(self):
"""Test successful Bank Account verification."""
mock_response_payload = {
"request_id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890",
"transaction_id": "tx-bank-998877",
"status": 200,
"data": {
"code": "1000",
"message": "Bank account verified successfully.",
"bank_data": {
"account_number": "123456789012",
"ifsc": "HDFC0001234",
"name_at_bank": "PRIYA SHARMA",
"micr": "641240002",
"bank_name": "HDFC BANK",
"branch": "MAIN BRANCH",
"city": "COIMBATORE",
"state": "TAMIL NADU",
"pincode": "641001",
"account_exists": True,
},
},
}
class MockResponse:
status_code = 200
def json(self):
return mock_response_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify(
"bank_account",
{"account_number": "123456789012", "ifsc": "HDFC0001234"},
)
self.assertTrue(result.is_success)
self.assertEqual(result.status, VerificationStatus.SUCCESS)
self.assertEqual(result.data["beneficiary_name"], "PRIYA SHARMA")
self.assertEqual(result.data["bank_name"], "HDFC BANK")
self.assertEqual(result.data["micr"], "641240002")
self.assertEqual(result.data["city"], "COIMBATORE")
def test_codeplus_bank_verify_missing_fields(self):
"""Test bank verification validation for missing account or IFSC."""
result_missing_acc = self.codeplus.verify("bank_account", {"ifsc": "HDFC0001234"})
self.assertFalse(result_missing_acc.is_success)
self.assertEqual(result_missing_acc.error_code, "MISSING_ACCOUNT_NUMBER")
result_missing_ifsc = self.codeplus.verify("bank_account", {"account_number": "123456"})
self.assertFalse(result_missing_ifsc.is_success)
self.assertEqual(result_missing_ifsc.error_code, "MISSING_IFSC")
def test_codeplus_fetch_gst_success(self):
"""Test successful fetch GST by PAN."""
mock_response_payload = {
"request_id": "b27c9087-f061-4d38-b7aa-96498f8e667e",
"status": 200,
"data": {
"code": "1002",
"message": "Found 3 records",
"results": [
{
"document_type": "GSTIN",
"document_id": "20AXXXXXXXXXXX",
"status": "Active",
"state": "Jharkhand",
"state_code": "20",
},
{
"document_type": "GSTIN",
"document_id": "27AXXXXXXXXXXX",
"status": "Active",
"state": "Maharashtra",
"state_code": "27",
},
{
"document_type": "GSTIN",
"document_id": "29AXXXXXXXXXXX",
"status": "Active",
"state": "Karnataka",
"state_code": "29",
},
],
},
}
class MockResponse:
status_code = 200
def json(self):
return mock_response_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("gst_by_pan", {"pan": "ABCDE1234F"})
self.assertTrue(result.is_success)
self.assertEqual(result.status, VerificationStatus.SUCCESS)
self.assertEqual(result.data["records_count"], 3)
self.assertEqual(len(result.data["results"]), 3)
self.assertEqual(result.data["results"][0]["document_id"], "20AXXXXXXXXXXX")
def test_codeplus_fetch_gst_missing_pan(self):
"""Test validation for missing PAN in GST by PAN."""
result = self.codeplus.verify("gst_by_pan", {})
self.assertFalse(result.is_success)
self.assertEqual(result.error_code, "MISSING_PAN")
def test_codeplus_fetch_msme_no_record(self):
"""Test MSME by PAN returning code 1011 (No record found)."""
mock_response_payload = {
"request_id": "cd7a7566-3122-4942-a110-d2b0ab81224d",
"transaction_id": "87186c96-f51d-436b-9fce-3a9d1ab8c44c",
"status": 200,
"data": {
"code": "1011",
"message": "No record found.",
},
"timestamp": 1741349527562,
"path": "/msme-api/udyam/fetch-by-pan",
}
class MockResponse:
status_code = 200
def json(self):
return mock_response_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("msme", {"pan": "ABCDE1234F"})
self.assertTrue(result.is_success)
self.assertEqual(result.status, VerificationStatus.SUCCESS)
self.assertFalse(result.data["has_msme"])
self.assertEqual(result.data["code"], "1011")
self.assertEqual(result.data["message"], "No record found.")
def test_codeplus_fetch_msme_missing_pan(self):
"""Test validation for missing PAN in MSME."""
result = self.codeplus.verify("msme", {})
self.assertFalse(result.is_success)
self.assertEqual(result.error_code, "MISSING_PAN")
def test_codeplus_gst_verify_v5_success(self):
"""Test successful GST verification v5."""
mock_response_payload = {
"success": True,
"message": "GST verification completed",
"data": {
"service": "GST_VERIFICATION",
"gstNumber": "27ABCDE1234F1Z5",
"legalName": "ABCDE PRIVATE LIMITED",
"registrationStatus": "ACTIVE",
},
"statusCode": 200,
}
class MockResponse:
status_code = 200
def json(self):
return mock_response_payload
from unittest.mock import patch
with patch.object(self.codeplus.session, "post", return_value=MockResponse()):
result = self.codeplus.verify("gstin", {"gst_number": "27ABCDE1234F1Z5"})
self.assertTrue(result.is_success)
self.assertEqual(result.status, VerificationStatus.SUCCESS)
self.assertEqual(result.data["gst_number"], "27ABCDE1234F1Z5")
self.assertEqual(result.data["legal_name"], "ABCDE PRIVATE LIMITED")
self.assertEqual(result.data["registration_status"], "ACTIVE")
def test_codeplus_gst_verify_v5_missing_gst(self):
"""Test validation for missing GST number."""
result = self.codeplus.verify("gstin", {})
self.assertFalse(result.is_success)
self.assertEqual(result.error_code, "MISSING_GST_NUMBER")
class VerificationAPITests(TestCase):

File diff suppressed because it is too large Load diff