Bug: [ENHANCEMENT] update payments API response



This issue is around two specific fields in the payments API 

1. Payments response -`error_code` and `error_message`

HyperSwitch should not propagate the intermediary dummy error codes and error messages to the API response. Currently, `ErrorResponse` in connector integration mandates setting an error_code and error_message. But some connectors might not have this available during transaction failures. For this reason, current HyperSwitch integration uses NO_ERROR_CODE for `error_code` and NO_ERROR_MESSAGE for `error_message`. These are constant strings which are propagated from connector integration to core layer and later sent back in the response. It doesn't make sense to have these in the API response as HyperSwitch should not 'invent' new codes and messages. This should be removed from the API response in such cases.

2. Payments request - `return_url`

HyperSwitch should always validate the presence of `return_url` during payment link creation as it is a required field in payment link flows.
diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs
index f7167a607a2..7ce8ed6d710 100644
--- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs
@@ -892,11 +892,11 @@ impl<F>
                             code: non_threeds_data
                                 .error_code
                                 .clone()
-                                .unwrap_or_else(|| "NO_ERROR_CODE".to_string()),
+                                .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
                             message: non_threeds_data
                                 .error_desc
                                 .clone()
-                                .unwrap_or_else(|| "NO_ERROR_MESSAGE".to_string()),
+                                .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
                             reason: non_threeds_data.error_desc.clone(),
                             status_code: item.http_code,
                             attempt_status: None,
@@ -941,11 +941,11 @@ impl<F>
                                 code: recurring_response
                                     .reason
                                     .clone()
-                                    .unwrap_or_else(|| "NO_ERROR_CODE".to_string()),
+                                    .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
                                 message: recurring_response
                                     .reason
                                     .clone()
-                                    .unwrap_or_else(|| "NO_ERROR_MESSAGE".to_string()),
+                                    .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
                                 reason: recurring_response.reason.clone(),
                                 status_code: item.http_code,
                                 attempt_status: None,
@@ -1480,19 +1480,17 @@ impl TryFrom<PaymentsCaptureResponseRouterData<PaymentCaptureResponse>>
         }?;
         let capture_message_status = capture_status_codes();
         let error_response = if status == enums::AttemptStatus::Failure {
+            let optional_message = capture_message_status
+                .get(status_code.as_str())
+                .copied()
+                .map(String::from);
             Some(ErrorResponse {
                 status_code: item.http_code,
                 code: status_code.to_owned(),
-                message: capture_message_status
-                    .get(status_code.as_str())
-                    .unwrap_or(&"NO_ERROR_MESSAGE")
-                    .to_string(),
-                reason: Some(
-                    capture_message_status
-                        .get(status_code.as_str())
-                        .unwrap_or(&"NO_ERROR_REASON")
-                        .to_string(),
-                ),
+                message: optional_message
+                    .clone()
+                    .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
+                reason: optional_message,
                 attempt_status: None,
                 connector_transaction_id: Some(item.response.tran_id.clone()),
                 network_advice_code: None,
@@ -1596,19 +1594,18 @@ impl TryFrom<PaymentsCancelResponseRouterData<FiuuPaymentCancelResponse>>
         }?;
         let void_message_status = void_status_codes();
         let error_response = if status == enums::AttemptStatus::VoidFailed {
+            let optional_message = void_message_status
+                .get(status_code.as_str())
+                .copied()
+                .map(String::from);
+
             Some(ErrorResponse {
                 status_code: item.http_code,
                 code: status_code.to_owned(),
-                message: void_message_status
-                    .get(status_code.as_str())
-                    .unwrap_or(&"NO_ERROR_MESSAGE")
-                    .to_string(),
-                reason: Some(
-                    void_message_status
-                        .get(status_code.as_str())
-                        .unwrap_or(&"NO_ERROR_REASON")
-                        .to_string(),
-                ),
+                message: optional_message
+                    .clone()
+                    .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
+                reason: optional_message,
                 attempt_status: None,
                 connector_transaction_id: Some(item.response.tran_id.clone()),
                 network_advice_code: None,
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 077a288c167..3001776cbfd 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -2075,6 +2075,8 @@ where
 {
     use std::ops::Not;
 
+    use hyperswitch_interfaces::consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE};
+
     let payment_attempt = payment_data.get_payment_attempt().clone();
     let payment_intent = payment_data.get_payment_intent().clone();
     let payment_link_data = payment_data.get_payment_link_data();
@@ -2561,10 +2563,13 @@ where
             statement_descriptor_suffix: payment_intent.statement_descriptor_suffix,
             next_action: next_action_response,
             cancellation_reason: payment_attempt.cancellation_reason,
-            error_code: payment_attempt.error_code,
+            error_code: payment_attempt
+                .error_code
+                .filter(|code| code != NO_ERROR_CODE),
             error_message: payment_attempt
                 .error_reason
-                .or(payment_attempt.error_message),
+                .or(payment_attempt.error_message)
+                .filter(|message| message != NO_ERROR_MESSAGE),
             unified_code: payment_attempt.unified_code,
             unified_message: payment_attempt.unified_message,
             payment_experience: payment_attempt.payment_experience,

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [x] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
This PR filters the `error_code` and `error_message` in Payments API response to ensure HyperSwitch does not send back any 'invented' codes or messages. This is achieved by filtering such error codes and error messages are not sent back in the API response.

Details in ticket - https://github.com/juspay/hyperswitch/issues/7794 (point # 1)


### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
This PR ensures HyperSwitch does not invent any new error codes or messages and send it back in the Payments API response. In case error codes and messages are not present, it will send back null in the response


## How did you test it?
<details>
    <summary>1. Create card payment via Nexixpay (failed card txn)</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payments' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_ddmeaxUlgRPHkcqtmZZm5nGMRhejMxXijYCnsp070Oqn2y4lrfUNBCb9UmedwgMt' \
        --data-raw '{"amount":3000,"currency":"EUR","confirm":true,"profile_id":"pro_510ieWUUX4pc0bUWmBW3","capture_method":"automatic","capture_on":"2022-09-10T10:11:12Z","authentication_type":"three_ds","connector":["nexixpay"],"email":"abc@example.com","return_url":"https://google.com","payment_method":"card","payment_method_data":{"card":{"card_number":"4349940199997007","card_exp_month":"12","card_exp_year":"28","card_holder_name":"joseph Doe","card_cvc":"829"}},"billing":{"address":{"line1":"1467","line2":"Harrison Street","line3":"Harrison Street","city":"San Fransico","state":"California","zip":"94122","country":"IT","first_name":"joseph","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":"guest@example.com"},"browser_info":{"user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","language":"en-US","color_depth":30,"screen_height":1117,"screen_width":1728,"time_zone":-330,"java_enabled":true,"java_script_enabled":true,"ip_address":"127.0.0.1"},"connector_metadata":{"noon":{"order_category":"pay"}},"session_expiry":60}'

Response

    {"payment_id":"pay_CLMzDJcQecFlM3bTdeZQ","merchant_id":"merchant_1744368926","status":"requires_customer_action","amount":3000,"net_amount":3000,"shipping_cost":null,"amount_capturable":3000,"amount_received":null,"connector":"nexixpay","client_secret":"pay_CLMzDJcQecFlM3bTdeZQ_secret_WtxTUTQUXoYjF4Gt6f7a","created":"2025-04-11T14:28:21.682Z","currency":"EUR","customer_id":null,"customer":{"id":null,"name":null,"email":"abc@example.com","phone":null,"phone_country_code":null},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":null,"off_session":null,"capture_on":null,"capture_method":"automatic","payment_method":"card","payment_method_data":{"card":{"last4":"7007","card_type":null,"card_network":null,"card_issuer":null,"card_issuing_country":null,"card_isin":"434994","card_extended_bin":null,"card_exp_month":"12","card_exp_year":"28","card_holder_name":"joseph Doe","payment_checks":null,"authentication_data":null},"billing":null},"payment_token":"token_KyWCvOf88BnnPydHsewF","shipping":null,"billing":{"address":{"city":"San Fransico","country":"IT","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"California","first_name":"joseph","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":"guest@example.com"},"order_details":null,"email":null,"name":null,"phone":null,"return_url":"https://google.com/","authentication_type":"three_ds","statement_descriptor_name":null,"statement_descriptor_suffix":null,"next_action":{"type":"redirect_to_url","redirect_to_url":"https://tame-bird-39.telebit.io/payments/redirect/pay_CLMzDJcQecFlM3bTdeZQ/merchant_1744368926/pay_CLMzDJcQecFlM3bTdeZQ_1"},"cancellation_reason":null,"error_code":null,"error_message":null,"unified_code":null,"unified_message":null,"payment_experience":null,"payment_method_type":null,"connector_label":null,"business_country":null,"business_label":"default","business_sub_label":null,"allowed_payment_method_types":null,"ephemeral_key":null,"manual_retry_allowed":null,"connector_transaction_id":"pay_CLMzDJcQecFlM3bTdeZQ_1","frm_message":null,"metadata":null,"connector_metadata":{"apple_pay":null,"airwallex":null,"noon":{"order_category":"pay"},"braintree":null},"feature_metadata":null,"reference_id":"pay_CLMzDJcQecFlM3bTdeZQ_1","payment_link":null,"profile_id":"pro_510ieWUUX4pc0bUWmBW3","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":"mca_d4eEG4Vgv2liRzTtsWKM","incremental_authorization_allowed":null,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2025-04-11T14:29:21.682Z","fingerprint":null,"browser_info":{"language":"en-US","time_zone":-330,"ip_address":"127.0.0.1","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","color_depth":30,"java_enabled":true,"screen_width":1728,"accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","screen_height":1117,"java_script_enabled":true},"payment_method_id":null,"payment_method_status":null,"updated":"2025-04-11T14:28:22.739Z","split_payments":null,"frm_metadata":null,"extended_authorization_applied":null,"capture_before":null,"merchant_order_reference_id":null,"order_tax_amount":null,"connector_mandate_id":null,"card_discovery":"manual","force_3ds_challenge":false,"force_3ds_challenge_trigger":false,"issuer_error_code":null,"issuer_error_message":null}

Open the link and complete the payment

</details>

<details>
    <summary>2. Retrieve the payment</summary>

cURL

    curl --location --request GET 'http://localhost:8080/payments/pay_CLMzDJcQecFlM3bTdeZQ?force_sync=true' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_ddmeaxUlgRPHkcqtmZZm5nGMRhejMxXijYCnsp070Oqn2y4lrfUNBCb9UmedwgMt'

Response

    {"payment_id":"pay_CLMzDJcQecFlM3bTdeZQ","merchant_id":"merchant_1744368926","status":"failed","amount":3000,"net_amount":3000,"shipping_cost":null,"amount_capturable":0,"amount_received":null,"connector":"nexixpay","client_secret":"pay_CLMzDJcQecFlM3bTdeZQ_secret_WtxTUTQUXoYjF4Gt6f7a","created":"2025-04-11T14:28:21.682Z","currency":"EUR","customer_id":null,"customer":{"id":null,"name":null,"email":"abc@example.com","phone":null,"phone_country_code":null},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":null,"off_session":null,"capture_on":null,"capture_method":"automatic","payment_method":"card","payment_method_data":{"card":{"last4":"7007","card_type":null,"card_network":null,"card_issuer":null,"card_issuing_country":null,"card_isin":"434994","card_extended_bin":null,"card_exp_month":"12","card_exp_year":"28","card_holder_name":"joseph Doe","payment_checks":null,"authentication_data":null},"billing":null},"payment_token":"token_KyWCvOf88BnnPydHsewF","shipping":null,"billing":{"address":{"city":"San Fransico","country":"IT","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"California","first_name":"joseph","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":"guest@example.com"},"order_details":null,"email":null,"name":null,"phone":null,"return_url":"https://google.com/","authentication_type":"three_ds","statement_descriptor_name":null,"statement_descriptor_suffix":null,"next_action":null,"cancellation_reason":null,"error_code":null,"error_message":"Declined","unified_code":"UE_9000","unified_message":"Something went wrong","payment_experience":null,"payment_method_type":null,"connector_label":null,"business_country":null,"business_label":"default","business_sub_label":null,"allowed_payment_method_types":null,"ephemeral_key":null,"manual_retry_allowed":true,"connector_transaction_id":"pay_CLMzDJcQecFlM3bTdeZQ_1","frm_message":null,"metadata":null,"connector_metadata":{"apple_pay":null,"airwallex":null,"noon":{"order_category":"pay"},"braintree":null},"feature_metadata":null,"reference_id":"pay_CLMzDJcQecFlM3bTdeZQ_1","payment_link":null,"profile_id":"pro_510ieWUUX4pc0bUWmBW3","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":"mca_d4eEG4Vgv2liRzTtsWKM","incremental_authorization_allowed":false,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2025-04-11T14:29:21.682Z","fingerprint":null,"browser_info":{"language":"en-US","time_zone":-330,"ip_address":"127.0.0.1","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","color_depth":30,"java_enabled":true,"screen_width":1728,"accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","screen_height":1117,"java_script_enabled":true},"payment_method_id":null,"payment_method_status":null,"updated":"2025-04-11T14:28:32.770Z","split_payments":null,"frm_metadata":null,"extended_authorization_applied":null,"capture_before":null,"merchant_order_reference_id":null,"order_tax_amount":null,"connector_mandate_id":null,"card_discovery":"manual","force_3ds_challenge":false,"force_3ds_challenge_trigger":false,"issuer_error_code":null,"issuer_error_message":null}

Things to verify
1. Error code must be null for this scenario
2. Error code in DB must be `No error code`

<img width="413" alt="Screenshot 2025-04-11 at 9 52 43 PM" src="https://github.com/user-attachments/assets/f756f6e3-77f7-4e0a-9a78-c97f686b7b30" />

</details>


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
