feat: add base verification engine, Codeplus vendor adapter, settings, and tests
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 30s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 30s
This commit is contained in:
parent
c6b177ac55
commit
5e11485ba8
4 changed files with 1457 additions and 192 deletions
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
1227
verifier/vendors/codeplus.py
vendored
1227
verifier/vendors/codeplus.py
vendored
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue