Bug: [BUG] Fiuu connector does not return error_code and error_message for failed FPX transactions



### Bug Description

Currently, Fiuu connector is not returning error_code and error_message for failed FPX transactions. This is returned during redirection back to HS but not during PSync. However, this is being not being respected.

### Expected Behavior

In case error_code and error_message is present, it should be populated in the API and webhooks response.

### Actual Behavior

The error code and message returned during redirection back to HS is not being respected.

### Steps To Reproduce

1. Create fpx transaction
2. Proceed with failure
3. Retrieve payments in HS - should see empty error_code and error_message in the payments API response

### Context For The Bug

_No response_

### Environment

Are you using hyperswitch hosted version? Yes/No
If yes, please provide the value of the `x-request-id` response header to help us debug your issue.

If not (or if building/running locally), please provide the following details:
1. Operating System or Linux distribution:
2. Rust version (output of `rustc --version`): ``
3. App version (output of `cargo r --features vergen -- --version`): ``


### Have you spent some time checking if this bug has been raised before?

- [x] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [x] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs
index 367f800a1d4..d05654265fd 100644
--- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs
@@ -1282,6 +1282,65 @@ impl TryFrom<&PaymentsSyncRouterData> for FiuuPaymentSyncRequest {
     }
 }
 
+struct ErrorInputs {
+    encoded_data: Option<String>,
+    response_error_code: Option<String>,
+    response_error_desc: Option<String>,
+}
+
+struct ErrorDetails {
+    pub code: String,
+    pub message: String,
+    pub reason: Option<String>,
+}
+
+impl TryFrom<ErrorInputs> for ErrorDetails {
+    type Error = Report<errors::ConnectorError>;
+    fn try_from(value: ErrorInputs) -> Result<Self, Self::Error> {
+        let query_params = value
+            .encoded_data
+            .as_ref()
+            .map(|encoded_data| {
+                serde_urlencoded::from_str::<FiuuPaymentRedirectResponse>(encoded_data)
+            })
+            .transpose()
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)
+            .attach_printable("Failed to deserialize FiuuPaymentRedirectResponse")?;
+        let error_message = value
+            .response_error_desc
+            .as_ref()
+            .filter(|s| !s.is_empty())
+            .cloned()
+            .or_else(|| {
+                query_params
+                    .as_ref()
+                    .and_then(|qp| qp.error_desc.as_ref())
+                    .filter(|s| !s.is_empty())
+                    .cloned()
+            });
+        let error_code = value
+            .response_error_code
+            .as_ref()
+            .filter(|s| !s.is_empty())
+            .cloned()
+            .or_else(|| {
+                query_params
+                    .as_ref()
+                    .and_then(|qp| qp.error_code.as_ref())
+                    .filter(|s| !s.is_empty())
+                    .cloned()
+            })
+            .unwrap_or_else(|| consts::NO_ERROR_CODE.to_owned());
+        Ok(Self {
+            code: error_code,
+            message: error_message
+                .clone()
+                .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_owned()),
+            reason: error_message,
+        })
+    }
+}
+
 impl TryFrom<PaymentsSyncResponseRouterData<FiuuPaymentResponse>> for PaymentsSyncRouterData {
     type Error = Report<errors::ConnectorError>;
     fn try_from(
@@ -1297,16 +1356,16 @@ impl TryFrom<PaymentsSyncResponseRouterData<FiuuPaymentResponse>> for PaymentsSy
                     stat_code,
                 })?;
                 let error_response = if status == enums::AttemptStatus::Failure {
+                    let error_details = ErrorDetails::try_from(ErrorInputs {
+                        encoded_data: item.data.request.encoded_data.clone(),
+                        response_error_code: response.error_code.clone(),
+                        response_error_desc: response.error_desc.clone(),
+                    })?;
                     Some(ErrorResponse {
                         status_code: item.http_code,
-                        code: response
-                            .error_code
-                            .unwrap_or(consts::NO_ERROR_CODE.to_owned()),
-                        message: response
-                            .error_desc
-                            .clone()
-                            .unwrap_or(consts::NO_ERROR_MESSAGE.to_owned()),
-                        reason: response.error_desc,
+                        code: error_details.code,
+                        message: error_details.message,
+                        reason: error_details.reason,
                         attempt_status: Some(enums::AttemptStatus::Failure),
                         connector_transaction_id: Some(txn_id.clone()),
                         network_advice_code: None,
@@ -1364,17 +1423,16 @@ impl TryFrom<PaymentsSyncResponseRouterData<FiuuPaymentResponse>> for PaymentsSy
                     }
                 });
                 let error_response = if status == enums::AttemptStatus::Failure {
+                    let error_details = ErrorDetails::try_from(ErrorInputs {
+                        encoded_data: item.data.request.encoded_data.clone(),
+                        response_error_code: response.error_code.clone(),
+                        response_error_desc: response.error_desc.clone(),
+                    })?;
                     Some(ErrorResponse {
                         status_code: item.http_code,
-                        code: response
-                            .error_code
-                            .clone()
-                            .unwrap_or(consts::NO_ERROR_CODE.to_owned()),
-                        message: response
-                            .error_desc
-                            .clone()
-                            .unwrap_or(consts::NO_ERROR_MESSAGE.to_owned()),
-                        reason: response.error_desc.clone(),
+                        code: error_details.code,
+                        message: error_details.message,
+                        reason: error_details.reason,
                         attempt_status: Some(enums::AttemptStatus::Failure),
                         connector_transaction_id: Some(txn_id.clone()),
                         network_advice_code: None,
@@ -1861,6 +1919,17 @@ pub struct FiuuWebhooksPaymentResponse {
     pub extra_parameters: Option<Secret<String>>,
 }
 
+#[derive(Debug, Deserialize, Serialize, Clone)]
+pub struct FiuuPaymentRedirectResponse {
+    pub skey: Secret<String>,
+    #[serde(rename = "tranID")]
+    pub tran_id: String,
+    pub status: FiuuPaymentWebhookStatus,
+    pub appcode: Option<String>,
+    pub error_code: Option<String>,
+    pub error_desc: Option<String>,
+}
+
 #[derive(Debug, Deserialize, Serialize, Clone)]
 #[serde(rename_all = "PascalCase")]
 pub struct FiuuWebhooksRefundResponse {

## Type of Change
<!-- Put an `x` in the boxes that apply -->
- [x] Bugfix
- [ ] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->

Fixed Fiuu connector to properly handle and return `error_code` and `error_message` for failed FPX transactions. The connector was previously receiving error information during redirection back to Hyperswitch but was not populating these fields in API responses after redirection back to Hyperswitch during Payment Sync (PSync) operations.

**Changes made:**
- Updated Fiuu connector's FPX transaction handling to capture and store error_code and error_message from redirection responses
- Modified PSync logic to properly propagate error information to payment API response

### 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
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.
If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->

This change addresses a critical gap in error handling for FPX transactions through the Fiuu connector. Currently, when FPX transactions fail, the error details provided during redirection are not being captured and made available through the Hyperswitch API or webhooks. This makes it difficult for merchants to understand why transactions failed and provide appropriate feedback to their customers.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

Steps for testing -

<details>
    <summary>1. Add Fiuu connector and enable FPX</summary>

cURL

    curl --location --request POST 'http://localhost:8080/account/merchant_1747931797/connectors' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'x-feature: integ-custom' \
        --header 'api-key: dev_8GkaY93JFWEP8zYOBlUmFePPfckcnGW5qdoLLXxb3jSqYOwt0ZhTNTpIFoLVLfo4' \
        --data '{"connector_type":"payment_processor","connector_name":"fiuu","connector_label":"fiuu_payments","connector_account_details":{"auth_type":"SignatureKey","api_key":"A","key1":"B","api_secret":"C"},"test_mode":true,"disabled":false,"payment_methods_enabled":[{"payment_method":"card","payment_method_types":[{"payment_method_type":"debit","payment_experience":null,"card_networks":["Visa"],"accepted_currencies":null,"accepted_countries":null,"minimum_amount":1,"maximum_amount":68607706,"recurring_enabled":true,"installment_payment_enabled":true},{"payment_method_type":"credit","payment_experience":null,"card_networks":["Visa"],"accepted_currencies":null,"accepted_countries":null,"minimum_amount":1,"maximum_amount":68607706,"recurring_enabled":true,"installment_payment_enabled":true}]},{"payment_method":"bank_redirect","payment_method_types":[{"payment_method_type":"online_banking_fpx","payment_experience":null,"card_networks":null,"accepted_currencies":null,"accepted_countries":null,"minimum_amount":1,"maximum_amount":68607706,"recurring_enabled":true,"installment_payment_enabled":true}]}]}'

</details>

<details>
    <summary>2. Create a payment link with MYR currency</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payments' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_8GkaY93JFWEP8zYOBlUmFePPfckcnGW5qdoLLXxb3jSqYOwt0ZhTNTpIFoLVLfo4' \
        --data-raw '{"authentication_type":"three_ds","customer_id":"cus_Wz8ND2QMGUTQCZBgO4Jx","profile_id":"pro_POJq22KThTEaouJM0sv7","customer_acceptance":{"acceptance_type":"online","accepted_at":"1963-05-03T04:07:52.723Z","online":{"ip_address":"127.0.0.1","user_agent":"amet irure esse"}},"amount":1000,"currency":"MYR","confirm":false,"payment_link":true,"setup_future_usage":"off_session","billing":{"address":{},"email":"random@juspay.in"},"return_url":"https://www.example.com","payment_link_config":{"background_image":{"url":"https://img.freepik.com/free-photo/abstract-blue-geometric-shapes-background_24972-1841.jpg","position":"bottom","size":"cover"}}}'

Response

    {"payment_id":"pay_yPdYbjvEUbc7FFYKG2QF","merchant_id":"merchant_1747931797","status":"requires_payment_method","amount":1000,"net_amount":1000,"shipping_cost":null,"amount_capturable":0,"amount_received":null,"connector":null,"client_secret":"pay_yPdYbjvEUbc7FFYKG2QF_secret_VHqHmGCJjjQpSeyewAxN","created":"2025-05-23T10:36:41.060Z","currency":"MYR","customer_id":"cus_Wz8ND2QMGUTQCZBgO4Jx","customer":{"id":"cus_Wz8ND2QMGUTQCZBgO4Jx","name":null,"email":null,"phone":null,"phone_country_code":null},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":"off_session","off_session":null,"capture_on":null,"capture_method":null,"payment_method":null,"payment_method_data":null,"payment_token":null,"shipping":null,"billing":{"address":null,"phone":null,"email":"random@juspay.in"},"order_details":null,"email":null,"name":null,"phone":null,"return_url":"https://www.example.com/","authentication_type":"three_ds","statement_descriptor_name":null,"statement_descriptor_suffix":null,"next_action":null,"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":{"customer_id":"cus_Wz8ND2QMGUTQCZBgO4Jx","created_at":1747996601,"expires":1748000201,"secret":"epk_a10b3bda47314b72ab41296146b73e6a"},"manual_retry_allowed":null,"connector_transaction_id":null,"frm_message":null,"metadata":null,"connector_metadata":null,"feature_metadata":null,"reference_id":null,"payment_link":{"link":"http://localhost:8080/payment_link/merchant_1747931797/pay_yPdYbjvEUbc7FFYKG2QF?locale=es-ES","secure_link":null,"payment_link_id":"plink_ACAHjaujoqHWhJQrulhC"},"profile_id":"pro_POJq22KThTEaouJM0sv7","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":null,"incremental_authorization_allowed":null,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2025-05-23T10:51:41.055Z","fingerprint":null,"browser_info":null,"payment_method_id":null,"payment_method_status":null,"updated":"2025-05-23T10:36:41.080Z","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":null,"force_3ds_challenge":false,"force_3ds_challenge_trigger":false,"issuer_error_code":null,"issuer_error_message":null,"is_iframe_redirection_enabled":null,"whole_connector_response":null}

</details>

3. Proceed with the FPX payment and mark it as a failure

<details>
    <summary>4. Retrieve the payment - Response to have `error_code` and `error_message`</summary>

cURL

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

Response

    {"payment_id":"pay_yPdYbjvEUbc7FFYKG2QF","merchant_id":"merchant_1747931797","status":"failed","amount":1000,"net_amount":1000,"shipping_cost":null,"amount_capturable":0,"amount_received":null,"connector":"fiuu","client_secret":"pay_yPdYbjvEUbc7FFYKG2QF_secret_VHqHmGCJjjQpSeyewAxN","created":"2025-05-23T10:36:41.060Z","currency":"MYR","customer_id":"cus_Wz8ND2QMGUTQCZBgO4Jx","customer":{"id":"cus_Wz8ND2QMGUTQCZBgO4Jx","name":null,"email":null,"phone":null,"phone_country_code":null},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":"on_session","off_session":null,"capture_on":null,"capture_method":null,"payment_method":"bank_redirect","payment_method_data":{"bank_redirect":{"type":"BankRedirectResponse","bank_name":"affin_bank"},"billing":null},"payment_token":null,"shipping":null,"billing":{"address":null,"phone":null,"email":"random@juspay.in"},"order_details":null,"email":null,"name":null,"phone":null,"return_url":"https://www.example.com/","authentication_type":"three_ds","statement_descriptor_name":null,"statement_descriptor_suffix":null,"next_action":null,"cancellation_reason":null,"error_code":"FPX_1B","error_message":"Buyer Failed To Provide The Necessary Info To Login IB Login Page","unified_code":"UE_9000","unified_message":"Something went wrong","payment_experience":null,"payment_method_type":"online_banking_fpx","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":"30994873","frm_message":null,"metadata":null,"connector_metadata":null,"feature_metadata":null,"reference_id":null,"payment_link":{"link":"http://localhost:8080/payment_link/merchant_1747931797/pay_yPdYbjvEUbc7FFYKG2QF?locale=es-ES","secure_link":null,"payment_link_id":"plink_ACAHjaujoqHWhJQrulhC"},"profile_id":"pro_POJq22KThTEaouJM0sv7","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":"mca_D52A4XabkFvmpjJMgiUd","incremental_authorization_allowed":false,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2025-05-23T10:51:41.055Z","fingerprint":null,"browser_info":{"os_type":"macOS","language":"en-US","time_zone":-330,"ip_address":"::1","os_version":"10.15.7","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15","color_depth":24,"device_model":"Macintosh","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,"accept_language":"en-US,en;q=0.9","java_script_enabled":true},"payment_method_id":"pm_RhSibHsj5rRz9sav6phs","payment_method_status":"inactive","updated":"2025-05-23T10:46:22.127Z","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":null,"force_3ds_challenge":false,"force_3ds_challenge_trigger":false,"issuer_error_code":null,"issuer_error_message":null,"is_iframe_redirection_enabled":null,"whole_connector_response":null}

</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