feat: add Bank Verification Full Details (v4) and fetch bank_name and branch
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s
This commit is contained in:
parent
71058e84c9
commit
6521343ea1
3 changed files with 95 additions and 46 deletions
|
|
@ -18,6 +18,8 @@ class DocumentType(str, Enum):
|
|||
BANK_VERIFY_PD = "bank_verify_pd"
|
||||
BANK_VERIFY_V5 = "bank_verify_v5"
|
||||
BANK_PD = "bank_pd"
|
||||
BANK_VERIFY_FULL = "bank_verify_full"
|
||||
BANK_VERIFY_V4 = "bank_verify_v4"
|
||||
GSTIN = "gstin"
|
||||
GST = "gst"
|
||||
GST_VERIFY = "gst_verify"
|
||||
|
|
|
|||
137
verifier/vendors/codeplus.py
vendored
137
verifier/vendors/codeplus.py
vendored
|
|
@ -3,6 +3,7 @@ import os
|
|||
from typing import Any, Dict, Optional
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from verifier.engine.base import (
|
||||
BaseVendor,
|
||||
|
|
@ -36,6 +37,8 @@ class CodeplusVendor(BaseVendor):
|
|||
DocumentType.VOTER_ID.value,
|
||||
DocumentType.PASSPORT.value,
|
||||
DocumentType.BANK_ACCOUNT.value,
|
||||
DocumentType.BANK_VERIFY_FULL.value,
|
||||
DocumentType.BANK_VERIFY_V4.value,
|
||||
DocumentType.BANK_VERIFY_PD.value,
|
||||
DocumentType.BANK_VERIFY_V5.value,
|
||||
DocumentType.BANK_PD.value,
|
||||
|
|
@ -116,6 +119,8 @@ class CodeplusVendor(BaseVendor):
|
|||
DocumentType.VOTER_ID.value: self.verify_voter_id,
|
||||
DocumentType.PASSPORT.value: self.verify_passport,
|
||||
DocumentType.BANK_ACCOUNT.value: self.verify_bank_account,
|
||||
DocumentType.BANK_VERIFY_FULL.value: self.verify_bank_account_v4,
|
||||
DocumentType.BANK_VERIFY_V4.value: self.verify_bank_account_v4,
|
||||
DocumentType.BANK_VERIFY_PD.value: self.verify_bank_verify_pd_v5,
|
||||
DocumentType.BANK_VERIFY_V5.value: self.verify_bank_verify_pd_v5,
|
||||
DocumentType.BANK_PD.value: self.verify_bank_verify_pd_v5,
|
||||
|
|
@ -579,30 +584,29 @@ class CodeplusVendor(BaseVendor):
|
|||
def verify_bank_account(self, data: Dict[str, Any], files: Optional[Dict[str, Any]] = None) -> VendorResult:
|
||||
"""
|
||||
Bank Account Verification Dispatcher:
|
||||
- Defaults to Bank Verify (Penny Drop - v5) Recommended API (`verify_bank_verify_pd_v5`)
|
||||
- If mode or version is 'v4', 'detailed', or 'full', routes to `verify_bank_account_v4`
|
||||
- If v5 encounters an upstream retryable failure (5xx or timeout), falls back to v4
|
||||
- Prioritizes Bank Verification (Full Details - v4) (`verify_bank_account_v4`)
|
||||
which retrieves beneficiary name, bank_name, branch, and city via penny drop routing
|
||||
- If mode or version is 'v5', routes to `verify_bank_verify_pd_v5`
|
||||
- Seamless failover across v4 and v5
|
||||
"""
|
||||
mode = str(data.get("mode", "")).lower()
|
||||
version = str(data.get("version", "")).lower()
|
||||
|
||||
if version == "v4" or mode in ("v4", "detailed", "full"):
|
||||
return self.verify_bank_account_v4(data, files)
|
||||
if version in ("v5", "pd") or mode in ("v5", "penny_drop", "pd"):
|
||||
return self.verify_bank_verify_pd_v5(data, files)
|
||||
|
||||
# Primary execution: Bank Verify (Penny Drop - v5)
|
||||
v5_result = self.verify_bank_verify_pd_v5(data, files)
|
||||
if v5_result.is_success:
|
||||
return v5_result
|
||||
# Primary execution: Bank Verification (Full Details - v4) to fetch bank_name and branch
|
||||
v4_result = self.verify_bank_account_v4(data, files)
|
||||
if v4_result.is_success:
|
||||
return v4_result
|
||||
|
||||
# Fallback locally to v4 if upstream server error / timeout or if upstream returns v4 response structure
|
||||
is_v4_payload = isinstance(v5_result.raw_response, dict) and "bank_data" in v5_result.raw_response.get("data", {})
|
||||
if v5_result.retryable or is_v4_payload:
|
||||
logger.info("Codeplus Bank Verify fallback to v4 triggered (retryable=%s, is_v4=%s)...", v5_result.retryable, is_v4_payload)
|
||||
v4_result = self.verify_bank_account_v4(data, files)
|
||||
if v4_result.is_success:
|
||||
return v4_result
|
||||
# Fallback to v5 Penny Drop if v4 failed with retryable/network error
|
||||
if v4_result.retryable:
|
||||
v5_result = self.verify_bank_verify_pd_v5(data, files)
|
||||
if v5_result.is_success:
|
||||
return v5_result
|
||||
|
||||
return v5_result
|
||||
return v4_result
|
||||
|
||||
def verify_bank_verify_pd_v5(self, data: Dict[str, Any], files: Optional[Dict[str, Any]] = None) -> VendorResult:
|
||||
"""
|
||||
|
|
@ -941,35 +945,55 @@ class CodeplusVendor(BaseVendor):
|
|||
error_block = raw_json.get("error") if isinstance(raw_json.get("error"), dict) else {}
|
||||
code = str(data_block.get("code") or "")
|
||||
|
||||
bank_data = data_block.get("bank_account_data") or data_block.get("bank_data") or data_block or raw_json
|
||||
beneficiary_name = (
|
||||
bank_data.get("name")
|
||||
or bank_data.get("name_at_bank")
|
||||
or bank_data.get("beneficiary_name")
|
||||
or bank_data.get("account_holder_name")
|
||||
or data_block.get("name")
|
||||
)
|
||||
bank_name = bank_data.get("bank_name") or bank_data.get("bank")
|
||||
branch = bank_data.get("branch") or bank_data.get("bank_branch")
|
||||
city = bank_data.get("city")
|
||||
utr = bank_data.get("utr")
|
||||
account_status = bank_data.get("account_status")
|
||||
|
||||
# Success condition: HTTP 200 and either code 1000 or valid bank verification payload
|
||||
is_code_success = code in ("1000", "SUCCESS", "200")
|
||||
is_body_success = response.status_code == 200 and not error_block and ("data" in raw_json or "account_exists" in raw_json)
|
||||
|
||||
if is_code_success or is_body_success:
|
||||
bank_data = data_block.get("bank_data") or data_block or raw_json
|
||||
beneficiary_name = (
|
||||
bank_data.get("name_at_bank")
|
||||
or bank_data.get("beneficiary_name")
|
||||
or bank_data.get("account_holder_name")
|
||||
or bank_data.get("name")
|
||||
or data_block.get("name")
|
||||
)
|
||||
is_code_success = code in ("1000", "SUCCESS", "200") or bool(beneficiary_name and response.status_code == 200 and not error_block)
|
||||
|
||||
if is_code_success and not error_block:
|
||||
masked_acc = f"********{account_number[-4:]}" if len(account_number) >= 4 else account_number
|
||||
normalized_result = {
|
||||
"document_type": "BANK_ACCOUNT",
|
||||
"verification_mode": "full_details_v4",
|
||||
"account_number": account_number,
|
||||
"masked_account_number": masked_acc,
|
||||
"ifsc": ifsc,
|
||||
"account_ifsc": ifsc,
|
||||
"beneficiary_name": beneficiary_name,
|
||||
"micr": bank_data.get("micr") or bank_data.get("micr_code"),
|
||||
"bank_name": bank_data.get("bank_name") or bank_data.get("bank"),
|
||||
"branch": bank_data.get("branch"),
|
||||
"city": bank_data.get("city"),
|
||||
"account_holder_name": beneficiary_name,
|
||||
"bank_name": bank_name,
|
||||
"branch": branch,
|
||||
"bank_branch": branch,
|
||||
"city": city,
|
||||
"state": bank_data.get("state"),
|
||||
"pincode": bank_data.get("pincode") or bank_data.get("pin_code"),
|
||||
"account_exists": bank_data.get("account_exists", True),
|
||||
"micr": bank_data.get("micr") or bank_data.get("micr_code"),
|
||||
"utr": utr,
|
||||
"account_status": account_status,
|
||||
"npci_response_code": bank_data.get("npci_response_code"),
|
||||
"npci_description": bank_data.get("npci_description"),
|
||||
"is_valid": True,
|
||||
"account_exists": True,
|
||||
"failure_reason": None,
|
||||
"npci_error_code": None,
|
||||
"user_consent": str(data.get("user_consent") or "true"),
|
||||
"bank_details": bank_data,
|
||||
"request_id": raw_json.get("request_id"),
|
||||
"transaction_id": raw_json.get("transaction_id"),
|
||||
"tranx_id": raw_json.get("transaction_id") or raw_json.get("request_id"),
|
||||
"timestamp": str(raw_json.get("timestamp") or timezone.now().isoformat()),
|
||||
}
|
||||
|
||||
return VendorResult(
|
||||
|
|
@ -980,20 +1004,43 @@ class CodeplusVendor(BaseVendor):
|
|||
raw_response=raw_json,
|
||||
)
|
||||
|
||||
# Failure / Error handling
|
||||
# Failure / Error handling (e.g. code 1001 "Provided invalid Account Number." or 400 "Invalid IFSC Code")
|
||||
is_server_error = response.status_code >= 500
|
||||
if error_block:
|
||||
err_code = str(error_block.get("code") or f"HTTP_{response.status_code}")
|
||||
err_message = error_block.get("message") or "Bank verification failed"
|
||||
elif data_block:
|
||||
err_code = str(data_block.get("code") or f"HTTP_{response.status_code}")
|
||||
err_message = data_block.get("message") or "Bank verification failed"
|
||||
else:
|
||||
err_code = f"HTTP_{response.status_code}"
|
||||
err_code = str(code or (error_block.get("code") if error_block else f"HTTP_{response.status_code}"))
|
||||
err_message = data_block.get("message") or ""
|
||||
if not err_message and error_block:
|
||||
fields = error_block.get("metadata", {}).get("fields", [])
|
||||
if fields and isinstance(fields, list):
|
||||
field_msgs = [f.get("message") for f in fields if f.get("message")]
|
||||
if field_msgs:
|
||||
err_message = "; ".join(field_msgs)
|
||||
if not err_message:
|
||||
err_message = error_block.get("message") or "Bank verification failed"
|
||||
if not err_message:
|
||||
err_message = raw_json.get("message") or "Bank verification failed"
|
||||
|
||||
normalized_result = {
|
||||
"document_type": "BANK_ACCOUNT",
|
||||
"verification_mode": "full_details_v4",
|
||||
"account_number": account_number,
|
||||
"ifsc": ifsc,
|
||||
"account_ifsc": ifsc,
|
||||
"beneficiary_name": None,
|
||||
"account_holder_name": None,
|
||||
"bank_name": None,
|
||||
"branch": None,
|
||||
"bank_branch": None,
|
||||
"is_valid": False,
|
||||
"account_exists": False,
|
||||
"failure_reason": err_message,
|
||||
"npci_error_code": err_code,
|
||||
"bank_details": data_block,
|
||||
"request_id": raw_json.get("request_id"),
|
||||
"transaction_id": raw_json.get("transaction_id"),
|
||||
}
|
||||
|
||||
logger.warning(
|
||||
"Codeplus Bank verification failed: status_code=%d, code=%s, message=%s",
|
||||
"Codeplus Bank verification (v4 Full Details) failed: status_code=%d, code=%s, message=%s",
|
||||
response.status_code,
|
||||
err_code,
|
||||
err_message,
|
||||
|
|
@ -1003,7 +1050,7 @@ class CodeplusVendor(BaseVendor):
|
|||
vendor_name=self.name,
|
||||
is_success=False,
|
||||
status=VerificationStatus.ERROR if is_server_error else VerificationStatus.FAILED,
|
||||
data=data_block,
|
||||
data=normalized_result,
|
||||
raw_response=raw_json,
|
||||
error_code=err_code,
|
||||
error_message=err_message,
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ class DocumentVerifyView(APIView):
|
|||
|
||||
# If bank verification was performed and seller_id is supplied, persist directly to seller_bank_verification table
|
||||
seller_id = input_data.get("seller_id") or request.data.get("seller_id")
|
||||
if doc_type in ["bank_account", "bank_verify_pd", "bank_verify_v5", "bank_pd"] and seller_id:
|
||||
if doc_type in ["bank_account", "bank_verify_pd", "bank_verify_v5", "bank_pd", "bank_verify_full", "bank_verify_v4"] and seller_id:
|
||||
try:
|
||||
b_data = engine_result.data or {}
|
||||
last_err_code = engine_result.attempts[-1].get("error_code") if engine_result.attempts else None
|
||||
|
|
|
|||
Loading…
Reference in a new issue