From 1ff0bd9f93202998b3ad7f4d8850dd2dbe56932b Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Thu, 15 Oct 2026 20:03:19 +0530 Subject: [PATCH] feat: add verification models, serializers, views, and core engine tests for multi-vendor document validation --- CODEPLUS_API_DOCUMENTATION.md | 522 ++++++++++++++++++ verifier/admin.py | 1 + ...erificationtransaction_response_payload.py | 18 + verifier/models.py | 1 + verifier/serializers.py | 1 + verifier/tests.py | 67 +++ verifier/views.py | 4 + 7 files changed, 614 insertions(+) create mode 100644 CODEPLUS_API_DOCUMENTATION.md create mode 100644 verifier/migrations/0002_verificationtransaction_response_payload.py diff --git a/CODEPLUS_API_DOCUMENTATION.md b/CODEPLUS_API_DOCUMENTATION.md new file mode 100644 index 0000000..593f373 --- /dev/null +++ b/CODEPLUS_API_DOCUMENTATION.md @@ -0,0 +1,522 @@ +# Codeplus (apihub.services) Document Verification Engine Documentation + +This document covers all active verification functions implemented in the **Multi-Vendor Verification Engine** using the **Codeplus** (`apihub.services`) adapter. + +--- + +## 1. Architecture & Failover Waterfall + +The verification engine provides seamless waterfall cascading across onboarded vendors: +- Every request is dispatched to the highest-priority active vendor. +- If the vendor encounters an error (e.g. HTTP 5xx, timeout, network failure, or API failure status), the engine logs the failure and automatically cascades to the next vendor. +- **Every attempt, raw vendor response, final normalized data, latency, and client response are permanently recorded in PostgreSQL.** + +### Database Storage Architecture + +1. **`VerificationTransaction` (`verifier_verificationtransaction`):** + - `reference_id`: Unique UUID generated for each client request. + - `document_type`: Type of document requested (`pan`, `bank_account`, `gstin`, etc.). + - `status`: Final outcome (`SUCCESS`, `FAILED`, `ERROR`). + - `successful_vendor`: Name of the vendor that resolved the verification (e.g. `codeplus`). + - `input_payload`: Masked, audit-safe input data. + - `final_data`: Normalized result payload. + - `response_payload`: Complete response payload returned to the client. + - `attempts_count`: Total number of vendors attempted in the waterfall. + - `total_latency_ms`: Total execution time in milliseconds. + - `created_at` / `updated_at`: Timestamps. + +2. **`VendorAttemptLog` (`verifier_vendorattemptlog`):** + - `transaction`: Foreign key to `VerificationTransaction`. + - `attempt_number`: Sequential attempt sequence (`1`, `2`, ...). + - `vendor_name`: Vendor adapter invoked (`codeplus`). + - `status`: Vendor status (`SUCCESS`, `FAILED`, `ERROR`, `SKIPPED`). + - `is_success`: Boolean indicator. + - `data`: Extracted vendor data block. + - `raw_response`: Full unaltered JSON/HTTP response returned by the upstream API. + - `error_code` & `error_message`: Upstream failure codes and diagnostics. + - `latency_ms`: Execution latency for this specific vendor hit. + +--- + +## 2. Global Authentication & Configuration + +The Codeplus adapter uses the following headers for all upstream calls: +```http +X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY +X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs +Content-Type: application/json +``` + +Environment variables configured in `docverify/settings.py` / `.env`: +- `CODEPLUS_BASE_URL`: `https://apihub.services` +- `CODEPLUS_CLIENT_API_ID`: `CAPI-YYR0YJIE0VMTDENY` +- `CODEPLUS_CLIENT_API_SECRET`: `s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs` + +--- + +## 3. Summary of Available Functions + +| Function Name | Upstream Endpoint | Internal `document_type` | Description | +| :--- | :--- | :--- | :--- | +| **Pan Verification (v5 Recommended)** | `POST /v5/pan-verify` | `pan`, `pan_v5`, `pan_verify` | Full PAN holder details with name, DOB, gender, masked Aadhaar & address. | +| **Detailed PAN API** | `POST /v4/pan-detailed` | `pan_detailed` | Comprehensive PAN data including address, contact info, and profile. | +| **PAN Advanced API** | `POST /v4/pan-advanced` | `pan_advanced` | Standard government database verification for PAN validity and taxpayer status. | +| **Bank Verification (Full Details)** | `POST /v4/bank-verify` | `bank_account` | Penny drop routing for account holder name, MICR, IFSC, and branch details. | +| **Fetch GST By PAN** | `POST /v4/fetch-gst` | `gst_by_pan`, `fetch_gst` | Retrieves all registered GSTIN entities associated with a business PAN. | +| **Fetch MSME By PAN** | `POST /v4/msme-pan` | `msme`, `msme_by_pan`, `udyam` | Retrieves Udyam registration number and enterprise details linked to a PAN. | +| **GST Verification (v5)** | `POST /v5/gst-verify` | `gstin`, `gst`, `gst_verify` | Verifies single GSTIN and returns legal business name and active status. | + +--- + +## 4. API Reference & Sample Requests + +### Function 1: Pan Verification (v5 Recommended) + +- **Engine Request:** +```bash +curl -X POST http://localhost:8092/api/verify/ \ + -H "Content-Type: application/json" \ + -d '{ + "document_type": "pan", + "data": { + "pan": "ABCDE1234F" + } + }' +``` + +- **Direct Upstream cURL:** +```bash +curl --location 'https://apihub.services/v5/pan-verify' \ + --header 'X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY' \ + --header 'X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs' \ + --header 'Content-Type: application/json' \ + --data '{"pan": "ABCDE1234F"}' +``` + +- **Sample Engine Response (HTTP 200):** +```json +{ + "reference_id": "8a7c29e1-2244-48f1-9c6a-4933a1e94b21", + "is_success": true, + "status": "SUCCESS", + "document_type": "pan", + "successful_vendor": "codeplus", + "data": { + "document_type": "PAN", + "pan": "ABCDE1234F", + "pan_type": "Individual", + "full_name": "RAHUL KUMAR", + "first_name": "RAHUL", + "middle_name": null, + "last_name": "KUMAR", + "gender": "male", + "dob": "15/06/1995", + "aadhaar_number": "XXXXXXXX1234", + "aadhaar_linked": true, + "address_details": { + "building_name": "12/45", + "locality": "Central Area", + "street_name": "MG Road", + "pincode": "641001", + "city": "COIMBATORE", + "state": "Tamil Nadu", + "country": "India" + }, + "service": "PAN_VERIFICATION", + "result_code": 101, + "client_ref_num": "REQ_1787136465034_demo12345", + "provider_transaction_id": "TXN_1787136465045_demo12345", + "transaction_id": "TXN_1787136465045_demo12345", + "request_id": "REQ_1787136465034_demo12345", + "timestamp": "2026-08-19T10:47:45.735Z" + }, + "attempts_count": 1, + "attempts": [ + { + "attempt_number": 1, + "vendor_name": "codeplus", + "is_success": true, + "status": "SUCCESS", + "latency_ms": 310.2 + } + ], + "error_message": null, + "total_latency_ms": 315.4 +} +``` + +--- + +### Function 2: Detailed PAN API + +- **Engine Request:** +```bash +curl -X POST http://localhost:8092/api/verify/ \ + -H "Content-Type: application/json" \ + -d '{ + "document_type": "pan_detailed", + "data": { + "pan_number": "ABCDE1234F" + } + }' +``` + +- **Direct Upstream cURL:** +```bash +curl --location 'https://apihub.services/v4/pan-detailed' \ + --header 'X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY' \ + --header 'X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs' \ + --header 'Content-Type: application/json' \ + --data '{"pan_number": "ABCDE1234F"}' +``` + +- **Sample Upstream Response:** +```json +{ + "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", + "line_2": "MG Road", + "street": "Central Area", + "city": "COIMBATORE", + "state": "TAMIL NADU", + "pincode": "641001" + }, + "email": "ra******@example.com", + "phone": "98XXXXXX10", + "gender": "MALE", + "aadhaar_linked": true, + "taxpayer_profile": true + } + } +} +``` + +--- + +### Function 3: PAN Advanced API + +- **Engine Request:** +```bash +curl -X POST http://localhost:8092/api/verify/ \ + -H "Content-Type: application/json" \ + -d '{ + "document_type": "pan_advanced", + "data": { + "pan": "ABCDE1234F" + } + }' +``` + +- **Direct Upstream cURL:** +```bash +curl --location 'https://apihub.services/v4/pan-advanced' \ + --header 'X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY' \ + --header 'X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs' \ + --header 'Content-Type: application/json' \ + --data '{"pan": "ABCDE1234F"}' +``` + +- **Sample Upstream Response:** +```json +{ + "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 + } + } +} +``` + +--- + +### Function 4: Bank Verification (Full Details) + +- **Engine Request:** +```bash +curl -X POST http://localhost:8092/api/verify/ \ + -H "Content-Type: application/json" \ + -d '{ + "document_type": "bank_account", + "data": { + "account_number": "123456789012", + "ifsc": "HDFC0001234" + } + }' +``` + +- **Direct Upstream cURL:** +```bash +curl --location 'https://apihub.services/v4/bank-verify' \ + --header 'X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY' \ + --header 'X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs' \ + --header 'Content-Type: application/json' \ + --data '{ + "account_number": "123456789012", + "ifsc": "HDFC0001234" + }' +``` + +- **Sample Engine Response (HTTP 200):** +```json +{ + "reference_id": "4b5d21a9-9941-4198-89c0-e74bf1a998bc", + "is_success": true, + "status": "SUCCESS", + "document_type": "bank_account", + "successful_vendor": "codeplus", + "data": { + "document_type": "BANK_ACCOUNT", + "account_number": "123456789012", + "ifsc": "HDFC0001234", + "beneficiary_name": "PRIYA SHARMA", + "micr": "641240002", + "bank_name": "HDFC BANK", + "branch": "MAIN BRANCH", + "city": "COIMBATORE", + "state": "TAMIL NADU", + "pincode": "641001", + "account_exists": true + }, + "attempts_count": 1, + "attempts": [ + { + "attempt_number": 1, + "vendor_name": "codeplus", + "is_success": true, + "status": "SUCCESS", + "latency_ms": 420.5 + } + ] +} +``` + +--- + +### Function 5: Fetch GST By PAN + +- **Engine Request:** +```bash +curl -X POST http://localhost:8092/api/verify/ \ + -H "Content-Type: application/json" \ + -d '{ + "document_type": "gst_by_pan", + "data": { + "pan": "ABCDE1234F" + } + }' +``` + +- **Direct Upstream cURL:** +```bash +curl --location 'https://apihub.services/v4/fetch-gst' \ + --header 'X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY' \ + --header 'X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs' \ + --header 'Content-Type: application/json' \ + --data '{"pan": "ABCDE1234F"}' +``` + +- **Sample Upstream Response:** +```json +{ + "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" + } + ] + } +} +``` + +--- + +### Function 6: Fetch MSME By PAN + +- **Engine Request:** +```bash +curl -X POST http://localhost:8092/api/verify/ \ + -H "Content-Type: application/json" \ + -d '{ + "document_type": "msme", + "data": { + "pan": "ABCDE1234F", + "detailed_response": true + } + }' +``` + +- **Direct Upstream cURL:** +```bash +curl --location 'https://apihub.services/v4/msme-pan' \ + --header 'X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY' \ + --header 'X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs' \ + --header 'Content-Type: application/json' \ + --data '{"pan": "ABCDE1234F", "detailed_response": true}' +``` + +- **Sample Engine Response (No Record Found):** +```json +{ + "reference_id": "c19b38fa-0123-4567-89ab-cdef01234567", + "is_success": true, + "status": "SUCCESS", + "document_type": "msme", + "successful_vendor": "codeplus", + "data": { + "document_type": "MSME", + "pan": "ABCDE1234F", + "has_msme": false, + "code": "1011", + "message": "No record found.", + "udyam_data": { + "code": "1011", + "message": "No record found." + } + } +} +``` + +--- + +### Function 7: GST Verification (v5) + +- **Engine Request:** +```bash +curl -X POST http://localhost:8092/api/verify/ \ + -H "Content-Type: application/json" \ + -d '{ + "document_type": "gstin", + "data": { + "gst_number": "27ABCDE1234F1Z5" + } + }' +``` + +- **Direct Upstream cURL:** +```bash +curl --location 'https://apihub.services/v5/gst-verify' \ + --header 'X-Client-Api-ID: CAPI-YYR0YJIE0VMTDENY' \ + --header 'X-Client-Api-Secret: s24Ts9wFsgl64mFzA61OqG0RDLRs2HFCpmOZBmqs' \ + --header 'Content-Type: application/json' \ + --data '{"gst_number": "27ABCDE1234F1Z5"}' +``` + +- **Sample Engine Response (HTTP 200):** +```json +{ + "reference_id": "5f6e7d8c-9b0a-1122-3344-556677889900", + "is_success": true, + "status": "SUCCESS", + "document_type": "gstin", + "successful_vendor": "codeplus", + "data": { + "document_type": "GSTIN", + "gst_number": "27ABCDE1234F1Z5", + "legal_name": "ABCDE PRIVATE LIMITED", + "registration_status": "ACTIVE", + "service": "GST_VERIFICATION", + "message": "GST verification completed" + }, + "attempts_count": 1, + "attempts": [ + { + "attempt_number": 1, + "vendor_name": "codeplus", + "is_success": true, + "status": "SUCCESS", + "latency_ms": 285.6 + } + ] +} +``` + +--- + +## 5. Transaction Audit API + +Retrieve the complete historical audit log, raw responses, and latency for any transaction: + +- **Endpoint:** `GET /api/transactions//` + +- **cURL:** +```bash +curl -X GET http://localhost:8092/api/transactions/8a7c29e1-2244-48f1-9c6a-4933a1e94b21/ +``` + +- **Response:** +```json +{ + "reference_id": "8a7c29e1-2244-48f1-9c6a-4933a1e94b21", + "document_type": "pan", + "status": "SUCCESS", + "successful_vendor": "codeplus", + "final_data": { ... }, + "response_payload": { ... }, + "attempts_count": 1, + "total_latency_ms": 315.4, + "created_at": "2026-10-15T19:58:00Z", + "attempts": [ + { + "attempt_number": 1, + "vendor_name": "codeplus", + "status": "SUCCESS", + "is_success": true, + "error_code": null, + "error_message": null, + "latency_ms": 310.2, + "created_at": "2026-10-15T19:58:00Z" + } + ] +} +``` diff --git a/verifier/admin.py b/verifier/admin.py index dbdd50d..40d5050 100644 --- a/verifier/admin.py +++ b/verifier/admin.py @@ -46,6 +46,7 @@ class VerificationTransactionAdmin(admin.ModelAdmin): "successful_vendor", "input_payload", "final_data", + "response_payload", "attempts_count", "total_latency_ms", "created_at", diff --git a/verifier/migrations/0002_verificationtransaction_response_payload.py b/verifier/migrations/0002_verificationtransaction_response_payload.py new file mode 100644 index 0000000..07dbe1f --- /dev/null +++ b/verifier/migrations/0002_verificationtransaction_response_payload.py @@ -0,0 +1,18 @@ +# Generated by Django 6.1 on 2026-10-15 14:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('verifier', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='verificationtransaction', + name='response_payload', + field=models.JSONField(blank=True, default=dict), + ), + ] diff --git a/verifier/models.py b/verifier/models.py index 7648339..bd275ec 100644 --- a/verifier/models.py +++ b/verifier/models.py @@ -50,6 +50,7 @@ class VerificationTransaction(models.Model): successful_vendor = models.CharField(max_length=50, blank=True, null=True) input_payload = models.JSONField(default=dict, blank=True) final_data = models.JSONField(default=dict, blank=True) + response_payload = models.JSONField(default=dict, blank=True) attempts_count = models.PositiveIntegerField(default=0) total_latency_ms = models.FloatField(default=0.0) created_at = models.DateTimeField(auto_now_add=True, db_index=True) diff --git a/verifier/serializers.py b/verifier/serializers.py index 90662cd..abac477 100644 --- a/verifier/serializers.py +++ b/verifier/serializers.py @@ -46,6 +46,7 @@ class VerificationTransactionSerializer(serializers.ModelSerializer): "status", "successful_vendor", "final_data", + "response_payload", "attempts_count", "total_latency_ms", "created_at", diff --git a/verifier/tests.py b/verifier/tests.py index 4a41ef5..73a4e46 100644 --- a/verifier/tests.py +++ b/verifier/tests.py @@ -532,3 +532,70 @@ class VerificationAPITests(TestCase): response = self.client.post(url, {}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertFalse(response.data["is_success"]) + + def test_verify_api_database_persistence(self): + """Ensure full transaction and attempt logs (including raw response and client response) are saved in DB.""" + from unittest.mock import patch + from verifier.vendors import codeplus_vendor + + mock_v5_response = { + "success": True, + "message": "PAN verification completed", + "data": { + "transactionId": "TXN_TEST_DB_123", + "service": "PAN_VERIFICATION", + "success": True, + "resultCode": 101, + "status": "SUCCESS", + "pan": "ABCDE1234F", + "fullName": "DATABASE PERSISTENCE USER", + "dob": "01/01/1990", + "aadhaarLinked": True, + }, + "requestId": "REQ_TEST_DB_123", + "statusCode": 200, + } + + class MockResponse: + status_code = 200 + def json(self): + return mock_v5_response + + url = reverse("verifier:verify_document") + with patch.object(codeplus_vendor.session, "post", return_value=MockResponse()): + res = self.client.post( + url, + { + "document_type": "pan", + "data": {"pan": "ABCDE1234F"}, + }, + format="json", + ) + + self.assertEqual(res.status_code, status.HTTP_200_OK) + ref_id = res.data["reference_id"] + + # Verify transaction in PostgreSQL DB + tx = VerificationTransaction.objects.get(reference_id=ref_id) + self.assertEqual(tx.document_type, "pan") + self.assertEqual(tx.status, "SUCCESS") + self.assertEqual(tx.successful_vendor, "codeplus") + self.assertEqual(tx.final_data["full_name"], "DATABASE PERSISTENCE USER") + self.assertEqual(tx.response_payload["reference_id"], str(ref_id)) + self.assertEqual(tx.attempts_count, 1) + + # Verify attempt log in DB + attempts = tx.attempts.all() + self.assertEqual(attempts.count(), 1) + attempt_1 = attempts.first() + self.assertEqual(attempt_1.vendor_name, "codeplus") + self.assertTrue(attempt_1.is_success) + self.assertEqual(attempt_1.raw_response["statusCode"], 200) + + # Verify GET /api/transactions// + detail_url = reverse("verifier:transaction_detail", kwargs={"reference_id": ref_id}) + detail_res = self.client.get(detail_url) + self.assertEqual(detail_res.status_code, status.HTTP_200_OK) + self.assertEqual(detail_res.data["reference_id"], str(ref_id)) + self.assertEqual(len(detail_res.data["attempts"]), 1) + diff --git a/verifier/views.py b/verifier/views.py index b02c097..b2fdc30 100644 --- a/verifier/views.py +++ b/verifier/views.py @@ -98,6 +98,10 @@ class DocumentVerifyView(APIView): "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)