diff --git a/verifier/engine/orchestrator.py b/verifier/engine/orchestrator.py index d57bd06..8c3cda5 100644 --- a/verifier/engine/orchestrator.py +++ b/verifier/engine/orchestrator.py @@ -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, ) diff --git a/verifier/vendors/codeplus.py b/verifier/vendors/codeplus.py index 9f7a10f..e903e88 100644 --- a/verifier/vendors/codeplus.py +++ b/verifier/vendors/codeplus.py @@ -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), )