feat: add verification engine orchestrator and Codeplus vendor integration
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 37s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 37s
This commit is contained in:
parent
418c7e681d
commit
71058e84c9
2 changed files with 57 additions and 12 deletions
|
|
@ -125,14 +125,16 @@ class VerificationEngine:
|
|||
total_latency,
|
||||
)
|
||||
|
||||
last_data = attempts[-1].get("data") if attempts and isinstance(attempts[-1].get("data"), dict) else {}
|
||||
|
||||
return EngineResult(
|
||||
is_success=False,
|
||||
document_type=doc_type_clean,
|
||||
status=VerificationStatus.FAILED,
|
||||
successful_vendor=None,
|
||||
data={},
|
||||
data=last_data,
|
||||
attempts=attempts,
|
||||
error_message=f"All vendors exhausted. Last error: {last_error}",
|
||||
error_message=last_error,
|
||||
total_latency_ms=total_latency,
|
||||
)
|
||||
|
||||
|
|
|
|||
63
verifier/vendors/codeplus.py
vendored
63
verifier/vendors/codeplus.py
vendored
|
|
@ -704,6 +704,27 @@ class CodeplusVendor(BaseVendor):
|
|||
)
|
||||
account_ifsc = result_block.get("account_ifsc") or ifsc
|
||||
|
||||
recommended_action = result_block.get("recommended_action") or raw_json.get("recommended_action")
|
||||
|
||||
def resolve_bank_failure_reason(code: Optional[str], raw_reason: Optional[str]) -> str:
|
||||
if code in ("U01", "U1"):
|
||||
return "Beneficiary bank reported: Account does not exist or invalid account number at this branch."
|
||||
if code in ("U03", "U3"):
|
||||
return "Beneficiary bank reported: Net debit cap exceeded / transaction limit reached."
|
||||
if code in ("U09", "U9"):
|
||||
return "Beneficiary bank server timed out. Please try again in a few moments."
|
||||
if code in ("U13",):
|
||||
return "Beneficiary bank reported: Account is dormant, inactive, or frozen."
|
||||
if code in ("U28",):
|
||||
return "Beneficiary bank reported: Branch does not participate in IMPS penny drop verification."
|
||||
if code in ("U30",):
|
||||
return "Beneficiary bank reported: Verification transaction declined by beneficiary bank."
|
||||
if raw_reason and str(raw_reason).lower() not in ("bank verification failed", "failed", "invalid_bank_account"):
|
||||
return str(raw_reason)
|
||||
return "The destination bank could not verify this account. The account number or IFSC code is incorrect, inactive, or not accepting IMPS penny drop."
|
||||
|
||||
friendly_failure_reason = resolve_bank_failure_reason(npci_error_code, failure_reason or raw_json.get("message"))
|
||||
|
||||
if response.status_code == 200:
|
||||
# Success condition: status == 'success' and is_valid is True
|
||||
if api_status == "success" and (is_valid is True or (is_valid is None and account_holder_name and not failure_reason)):
|
||||
|
|
@ -720,6 +741,7 @@ class CodeplusVendor(BaseVendor):
|
|||
"account_exists": True,
|
||||
"failure_reason": None,
|
||||
"npci_error_code": None,
|
||||
"recommended_action": recommended_action,
|
||||
"user": result_block.get("user"),
|
||||
"tranx_id": raw_json.get("tranx_id"),
|
||||
"transaction_id": raw_json.get("tranx_id"),
|
||||
|
|
@ -739,7 +761,6 @@ class CodeplusVendor(BaseVendor):
|
|||
|
||||
# HTTP 200 with verification failure or account invalid
|
||||
err_code = str(npci_error_code or "INVALID_BANK_ACCOUNT")
|
||||
err_message = failure_reason or raw_json.get("message") or "Bank account verification failed"
|
||||
normalized_result = {
|
||||
"document_type": "BANK_ACCOUNT",
|
||||
"verification_mode": "penny_drop_v5",
|
||||
|
|
@ -751,8 +772,10 @@ class CodeplusVendor(BaseVendor):
|
|||
"account_holder_name": account_holder_name,
|
||||
"is_valid": False,
|
||||
"account_exists": False,
|
||||
"failure_reason": failure_reason,
|
||||
"failure_reason": friendly_failure_reason,
|
||||
"raw_failure_reason": failure_reason,
|
||||
"npci_error_code": npci_error_code,
|
||||
"recommended_action": recommended_action,
|
||||
"user": result_block.get("user"),
|
||||
"tranx_id": raw_json.get("tranx_id"),
|
||||
"transaction_id": raw_json.get("tranx_id"),
|
||||
|
|
@ -765,7 +788,7 @@ class CodeplusVendor(BaseVendor):
|
|||
logger.warning(
|
||||
"Codeplus Bank Verify PD v5 returned failure: npci_error=%s, failure_reason=%s",
|
||||
npci_error_code,
|
||||
failure_reason,
|
||||
friendly_failure_reason,
|
||||
)
|
||||
|
||||
return VendorResult(
|
||||
|
|
@ -775,32 +798,52 @@ class CodeplusVendor(BaseVendor):
|
|||
data=normalized_result,
|
||||
raw_response=raw_json,
|
||||
error_code=err_code,
|
||||
error_message=err_message,
|
||||
error_message=friendly_failure_reason,
|
||||
retryable=False,
|
||||
)
|
||||
|
||||
# Upstream HTTP error status (400, 500, etc.)
|
||||
is_server_error = response.status_code >= 500
|
||||
err_code = str(npci_error_code or f"HTTP_{response.status_code}")
|
||||
err_message = failure_reason or raw_json.get("message") or raw_json.get("detail") or raw_json.get("error") or "Bank verification failed"
|
||||
if isinstance(err_message, dict):
|
||||
err_message = str(err_message)
|
||||
normalized_result = {
|
||||
"document_type": "BANK_ACCOUNT",
|
||||
"verification_mode": "penny_drop_v5",
|
||||
"account_number": account_number,
|
||||
"masked_account_number": masked_acc,
|
||||
"ifsc": ifsc,
|
||||
"account_ifsc": account_ifsc,
|
||||
"beneficiary_name": account_holder_name,
|
||||
"account_holder_name": account_holder_name,
|
||||
"is_valid": False,
|
||||
"account_exists": False,
|
||||
"failure_reason": friendly_failure_reason,
|
||||
"raw_failure_reason": failure_reason or raw_json.get("message"),
|
||||
"npci_error_code": npci_error_code,
|
||||
"recommended_action": recommended_action,
|
||||
"user": result_block.get("user"),
|
||||
"tranx_id": raw_json.get("tranx_id"),
|
||||
"transaction_id": raw_json.get("tranx_id"),
|
||||
"timestamp": raw_json.get("timestamp"),
|
||||
"chargeble": raw_json.get("chargeble"),
|
||||
"user_consent": raw_json.get("user_consent"),
|
||||
"bank_details": result_block if result_block else raw_json,
|
||||
}
|
||||
|
||||
logger.warning(
|
||||
"Codeplus Bank Verify PD v5 failed: status_code=%d, code=%s, message=%s",
|
||||
response.status_code,
|
||||
err_code,
|
||||
err_message,
|
||||
friendly_failure_reason,
|
||||
)
|
||||
|
||||
return VendorResult(
|
||||
vendor_name=self.name,
|
||||
is_success=False,
|
||||
status=VerificationStatus.ERROR if is_server_error else VerificationStatus.FAILED,
|
||||
data=raw_json if isinstance(raw_json, dict) else {},
|
||||
data=normalized_result,
|
||||
raw_response=raw_json,
|
||||
error_code=err_code,
|
||||
error_message=str(err_message),
|
||||
error_message=friendly_failure_reason,
|
||||
retryable=is_server_error or response.status_code in (429, 408, 401, 403),
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue