Bug: Fix bugs in peachpayments and add Cypress test for Peachpayments



Fix bugs in peachpayments and add Cypress test for Peachpayments
diff --git a/crates/hyperswitch_connectors/src/connectors/peachpayments/transformers.rs b/crates/hyperswitch_connectors/src/connectors/peachpayments/transformers.rs
index a5e34b416ac..9b1776bf056 100644
--- a/crates/hyperswitch_connectors/src/connectors/peachpayments/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/peachpayments/transformers.rs
@@ -16,7 +16,6 @@ use hyperswitch_interfaces::{
 };
 use masking::Secret;
 use serde::{Deserialize, Serialize};
-use strum::Display;
 use time::{format_description::well_known::Rfc3339, OffsetDateTime};
 
 use crate::{
@@ -70,68 +69,78 @@ pub struct EcommerceCardPaymentOnlyTransactionData {
     pub routing: Routing,
     pub card: CardDetails,
     pub amount: AmountDetails,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub three_ds_data: Option<ThreeDSData>,
 }
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
 #[serde[rename_all = "camelCase"]]
 pub struct MerchantInformation {
-    pub client_merchant_reference_id: String,
-    pub name: String,
-    pub mcc: String,
+    pub client_merchant_reference_id: Secret<String>,
+    pub name: Secret<String>,
+    pub mcc: Secret<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub phone: Option<String>,
+    pub phone: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub email: Option<String>,
+    pub email: Option<pii::Email>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub mobile: Option<String>,
+    pub mobile: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub address: Option<String>,
+    pub address: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub city: Option<String>,
+    pub city: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub postal_code: Option<String>,
+    pub postal_code: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub region_code: Option<String>,
+    pub region_code: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub merchant_type: Option<String>,
+    pub merchant_type: Option<MerchantType>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub website_url: Option<String>,
+    pub website_url: Option<url::Url>,
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+#[serde[rename_all = "lowercase"]]
+pub enum MerchantType {
+    Standard,
+    Sub,
+    Iso,
 }
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
 #[serde[rename_all = "camelCase"]]
 pub struct Routing {
-    pub route: String,
-    pub mid: String,
-    pub tid: String,
+    pub route: Route,
+    pub mid: Secret<String>,
+    pub tid: Secret<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub visa_payment_facilitator_id: Option<String>,
+    pub visa_payment_facilitator_id: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub master_card_payment_facilitator_id: Option<String>,
+    pub master_card_payment_facilitator_id: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub sub_mid: Option<String>,
+    pub sub_mid: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub amex_id: Option<String>,
+    pub amex_id: Option<Secret<String>>,
 }
 
-#[derive(Debug, Serialize, PartialEq)]
-#[serde[rename_all = "camelCase"]]
-pub struct CardDetails {
-    pub pan: CardNumber,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub cardholder_name: Option<Secret<String>>,
-    pub expiry_year: Secret<String>,
-    pub expiry_month: Secret<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub cvv: Option<Secret<String>>,
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+#[serde[rename_all = "snake_case"]]
+pub enum Route {
+    ExipayEmulator,
+    AbsaBase24,
+    NedbankPostbridge,
+    AbsaPostbridgeEcentric,
+    PostbridgeDirecttransact,
+    PostbridgeEfficacy,
+    FiservLloyds,
+    NfsIzwe,
+    AbsaHpsZambia,
+    EcentricEcommerce,
+    UnitTestEmptyConfig,
 }
 
 #[derive(Debug, Serialize, PartialEq)]
 #[serde[rename_all = "camelCase"]]
-pub struct RefundCardDetails {
-    pub pan: Secret<String>,
+pub struct CardDetails {
+    pub pan: CardNumber,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub cardholder_name: Option<Secret<String>>,
     pub expiry_year: Secret<String>,
@@ -145,21 +154,8 @@ pub struct RefundCardDetails {
 pub struct AmountDetails {
     pub amount: MinorUnit,
     pub currency_code: String,
-}
-
-#[derive(Debug, Serialize, PartialEq)]
-#[serde[rename_all = "camelCase"]]
-pub struct ThreeDSData {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub cavv: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub tavv: Option<Secret<String>>,
-    pub eci: String,
-    pub ds_trans_id: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub xid: Option<String>,
-    pub authentication_status: AuthenticationStatus,
-    pub three_ds_version: String,
+    pub display_amount: Option<String>,
 }
 
 // Confirm Transaction Request (for capture)
@@ -199,6 +195,7 @@ impl TryFrom<&PeachpaymentsRouterData<&PaymentsCaptureRouterData>> for Peachpaym
         let amount = AmountDetails {
             amount: amount_in_cents,
             currency_code: item.router_data.request.currency.to_string(),
+            display_amount: None,
         };
 
         let confirmation_data = EcommerceCardPaymentOnlyConfirmationData { amount };
@@ -271,57 +268,40 @@ impl TryFrom<&PaymentsCancelRouterData> for PeachpaymentsVoidRequest {
     }
 }
 
-#[derive(Debug, Serialize, PartialEq)]
-pub enum AuthenticationStatus {
-    Y, // Fully authenticated
-    A, // Attempted authentication / liability shift
-    N, // Not authenticated / failed
-}
-
-impl From<&str> for AuthenticationStatus {
-    fn from(eci: &str) -> Self {
-        match eci {
-            "05" | "06" => Self::Y,
-            "07" => Self::A,
-            _ => Self::N,
-        }
-    }
-}
-
 #[derive(Debug, Serialize, Deserialize)]
 pub struct PeachPaymentsConnectorMetadataObject {
-    pub client_merchant_reference_id: String,
-    pub name: String,
-    pub mcc: String,
+    pub client_merchant_reference_id: Secret<String>,
+    pub name: Secret<String>,
+    pub mcc: Secret<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub phone: Option<String>,
+    pub phone: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub email: Option<String>,
+    pub email: Option<pii::Email>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub mobile: Option<String>,
+    pub mobile: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub address: Option<String>,
+    pub address: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub city: Option<String>,
+    pub city: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub postal_code: Option<String>,
+    pub postal_code: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub region_code: Option<String>,
+    pub region_code: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub merchant_type: Option<String>,
+    pub merchant_type: Option<MerchantType>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub website_url: Option<String>,
-    pub route: String,
-    pub mid: String,
-    pub tid: String,
+    pub website_url: Option<url::Url>,
+    pub route: Route,
+    pub mid: Secret<String>,
+    pub tid: Secret<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub visa_payment_facilitator_id: Option<String>,
+    pub visa_payment_facilitator_id: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub master_card_payment_facilitator_id: Option<String>,
+    pub master_card_payment_facilitator_id: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub sub_mid: Option<String>,
+    pub sub_mid: Option<Secret<String>>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub amex_id: Option<String>,
+    pub amex_id: Option<Secret<String>>,
 }
 
 impl TryFrom<&PeachpaymentsRouterData<&PaymentsAuthorizeRouterData>>
@@ -379,55 +359,14 @@ impl TryFrom<&PeachpaymentsRouterData<&PaymentsAuthorizeRouterData>>
                 let amount = AmountDetails {
                     amount: amount_in_cents,
                     currency_code: item.router_data.request.currency.to_string(),
+                    display_amount: None,
                 };
 
-                // Extract 3DS data if available
-                let three_ds_data = item
-                    .router_data
-                    .request
-                    .authentication_data
-                    .as_ref()
-                    .and_then(|auth_data| {
-                        // Only include 3DS data if we have essential fields (ECI is most critical)
-                        if let Some(eci) = &auth_data.eci {
-                            let ds_trans_id = auth_data
-                                .ds_trans_id
-                                .clone()
-                                .or_else(|| auth_data.threeds_server_transaction_id.clone())?;
-
-                            // Determine authentication status based on ECI value
-                            let authentication_status = eci.as_str().into();
-
-                            // Convert message version to string, handling None case
-                            let three_ds_version = auth_data.message_version.as_ref().map(|v| {
-                                let version_str = v.to_string();
-                                let mut parts = version_str.split('.');
-                                match (parts.next(), parts.next()) {
-                                    (Some(major), Some(minor)) => format!("{}.{}", major, minor),
-                                    _ => v.to_string(), // fallback if format unexpected
-                                }
-                            })?;
-
-                            Some(ThreeDSData {
-                                cavv: Some(auth_data.cavv.clone()),
-                                tavv: None, // Network token field - not available in Hyperswitch AuthenticationData
-                                eci: eci.clone(),
-                                ds_trans_id,
-                                xid: None, // Legacy 3DS 1.x/network token field - not available in Hyperswitch AuthenticationData
-                                authentication_status,
-                                three_ds_version,
-                            })
-                        } else {
-                            None
-                        }
-                    });
-
                 let ecommerce_data = EcommerceCardPaymentOnlyTransactionData {
                     merchant_information,
                     routing,
                     card,
                     amount,
-                    three_ds_data,
                 };
 
                 // Generate current timestamp for sendDateTime (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ)
@@ -469,18 +408,16 @@ impl TryFrom<&ConnectorAuthType> for PeachpaymentsAuthType {
 }
 // Card Gateway API Response
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "lowercase")]
+#[serde(rename_all = "snake_case")]
 pub enum PeachpaymentsPaymentStatus {
     Successful,
     Pending,
     Authorized,
     Approved,
-    #[serde(rename = "approved_confirmed")]
     ApprovedConfirmed,
     Declined,
     Failed,
     Reversed,
-    #[serde(rename = "threeds_required")]
     ThreedsRequired,
     Voided,
 }
@@ -509,9 +446,8 @@ impl From<PeachpaymentsPaymentStatus> for common_enums::AttemptStatus {
 #[serde[rename_all = "camelCase"]]
 pub struct PeachpaymentsPaymentsResponse {
     pub transaction_id: String,
-    pub response_code: ResponseCode,
+    pub response_code: Option<ResponseCode>,
     pub transaction_result: PeachpaymentsPaymentStatus,
-    #[serde(skip_serializing_if = "Option::is_none")]
     pub ecommerce_card_payment_only_transaction_data: Option<EcommerceCardPaymentOnlyResponseData>,
 }
 
@@ -520,24 +456,26 @@ pub struct PeachpaymentsPaymentsResponse {
 #[serde[rename_all = "camelCase"]]
 pub struct PeachpaymentsConfirmResponse {
     pub transaction_id: String,
-    pub response_code: ResponseCode,
+    pub response_code: Option<ResponseCode>,
     pub transaction_result: PeachpaymentsPaymentStatus,
-    #[serde(skip_serializing_if = "Option::is_none")]
     pub authorization_code: Option<String>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde[rename_all = "camelCase"]]
 #[serde(untagged)]
 pub enum ResponseCode {
     Text(String),
     Structured {
-        value: ResponseCodeValue,
+        value: String,
         description: String,
+        terminal_outcome_string: Option<String>,
+        receipt_string: Option<String>,
     },
 }
 
 impl ResponseCode {
-    pub fn value(&self) -> Option<&ResponseCodeValue> {
+    pub fn value(&self) -> Option<&String> {
         match self {
             Self::Structured { value, .. } => Some(value),
             _ => None,
@@ -559,33 +497,48 @@ impl ResponseCode {
     }
 }
 
-#[derive(Debug, Display, Clone, Serialize, Deserialize, PartialEq)]
-pub enum ResponseCodeValue {
-    #[serde(rename = "00", alias = "08")]
-    Success,
-}
-
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde[rename_all = "camelCase"]]
 pub struct EcommerceCardPaymentOnlyResponseData {
-    pub card: Option<CardResponseData>,
     pub amount: Option<AmountDetails>,
-    pub stan: Option<String>,
-    pub rrn: Option<String>,
-    #[serde(rename = "approvalCode")]
+    pub stan: Option<Secret<String>>,
+    pub rrn: Option<Secret<String>>,
     pub approval_code: Option<String>,
-    #[serde(rename = "merchantAdviceCode")]
     pub merchant_advice_code: Option<String>,
     pub description: Option<String>,
+    pub trace_id: Option<String>,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde[rename_all = "camelCase"]]
-pub struct CardResponseData {
-    pub bin_number: Option<String>,
-    pub masked_pan: Option<String>,
-    pub cardholder_name: Option<String>,
-    pub expiry_month: Option<String>,
-    pub expiry_year: Option<String>,
+fn is_payment_success(value: Option<&String>) -> bool {
+    if let Some(val) = value {
+        val == "00" || val == "08" || val == "X94"
+    } else {
+        false
+    }
+}
+
+fn get_error_code(response_code: Option<&ResponseCode>) -> String {
+    response_code
+        .and_then(|code| code.value())
+        .map(|val| val.to_string())
+        .unwrap_or(
+            response_code
+                .and_then(|code| code.as_text())
+                .map(|text| text.to_string())
+                .unwrap_or(NO_ERROR_CODE.to_string()),
+        )
+}
+
+fn get_error_message(response_code: Option<&ResponseCode>) -> String {
+    response_code
+        .and_then(|code| code.description())
+        .map(|desc| desc.to_string())
+        .unwrap_or(
+            response_code
+                .and_then(|code| code.as_text())
+                .map(|text| text.to_string())
+                .unwrap_or(NO_ERROR_MESSAGE.to_string()),
+        )
 }
 
 impl<F, T> TryFrom<ResponseRouterData<F, PeachpaymentsPaymentsResponse, T, PaymentsResponseData>>
@@ -598,20 +551,15 @@ impl<F, T> TryFrom<ResponseRouterData<F, PeachpaymentsPaymentsResponse, T, Payme
         let status = common_enums::AttemptStatus::from(item.response.transaction_result);
 
         // Check if it's an error response
-        let response = if item.response.response_code.value() != Some(&ResponseCodeValue::Success) {
+        let response = if !is_payment_success(
+            item.response
+                .response_code
+                .as_ref()
+                .and_then(|code| code.value()),
+        ) {
             Err(ErrorResponse {
-                code: item
-                    .response
-                    .response_code
-                    .value()
-                    .map(|val| val.to_string())
-                    .unwrap_or(NO_ERROR_CODE.to_string()),
-                message: item
-                    .response
-                    .response_code
-                    .description()
-                    .map(|desc| desc.to_string())
-                    .unwrap_or(NO_ERROR_MESSAGE.to_string()),
+                code: get_error_code(item.response.response_code.as_ref()),
+                message: get_error_message(item.response.response_code.as_ref()),
                 reason: item
                     .response
                     .ecommerce_card_payment_only_transaction_data
@@ -658,20 +606,15 @@ impl<F, T> TryFrom<ResponseRouterData<F, PeachpaymentsConfirmResponse, T, Paymen
         let status = common_enums::AttemptStatus::from(item.response.transaction_result);
 
         // Check if it's an error response
-        let response = if item.response.response_code.value() != Some(&ResponseCodeValue::Success) {
+        let response = if !is_payment_success(
+            item.response
+                .response_code
+                .as_ref()
+                .and_then(|code| code.value()),
+        ) {
             Err(ErrorResponse {
-                code: item
-                    .response
-                    .response_code
-                    .value()
-                    .map(|val| val.to_string())
-                    .unwrap_or(NO_ERROR_CODE.to_string()),
-                message: item
-                    .response
-                    .response_code
-                    .description()
-                    .map(|desc| desc.to_string())
-                    .unwrap_or(NO_ERROR_MESSAGE.to_string()),
+                code: get_error_code(item.response.response_code.as_ref()),
+                message: get_error_message(item.response.response_code.as_ref()),
                 reason: None,
                 status_code: item.http_code,
                 attempt_status: Some(status),
@@ -720,6 +663,7 @@ impl TryFrom<&PeachpaymentsRouterData<&PaymentsAuthorizeRouterData>>
         let amount = AmountDetails {
             amount: amount_in_cents,
             currency_code: item.router_data.request.currency.to_string(),
+            display_amount: None,
         };
 
         let confirmation_data = EcommerceCardPaymentOnlyConfirmationData { amount };
diff --git a/crates/payment_methods/src/configs/payment_connector_required_fields.rs b/crates/payment_methods/src/configs/payment_connector_required_fields.rs
index 32a1c175ea0..f61c789fe2f 100644
--- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs
+++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs
@@ -1538,6 +1538,10 @@ fn get_cards_required_fields() -> HashMap<Connector, RequiredFieldFinal> {
         ),
         (Connector::Paypal, fields(vec![], card_basic(), vec![])),
         (Connector::Payu, fields(vec![], card_basic(), vec![])),
+        (
+            Connector::Peachpayments,
+            fields(vec![], vec![], card_with_name()),
+        ),
         (
             Connector::Powertranz,
             fields(vec![], card_with_name(), vec![]),

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

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

Closes this [issue](https://github.com/juspay/hyperswitch/issues/9392)

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

Fixed bugs and added cypress test

1. Made sensitive informations as Secret.
2. Resolved Deserialization error in Payments Response
3. Removed unnecessary structs

### 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`
5. `crates/router/src/configs`
6. `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).
-->


## 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)?
-->

Postman Test

1. Previously error response from connector end was getting deserialization error but now it's getting deserialized

Payments - Create:

Request:

```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_4XwGvSX1WUNBozVTSoR0qBIPamQg9lArgmef44Z4m5onvvCWRQwGtm7vlkzprgCe' \
--data-raw '{
    "amount": 4510,
    "currency": "USD",
    "confirm": true,
    "capture_method": "manual",
    "authentication_type": "no_three_ds",
    "customer": {
        "id": "customer123",
        "name": "John Doe",
        "email": "customer@gmail.com",
        "phone": "9999999999",
        "phone_country_code": "+1"
    },
    "customer_id": "customer123",  
    "description": "Its my first payment request",
    "return_url": "https://google.com",
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_data": { 
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "card_cvc": "123"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "guest@example.com"
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "guest@example.com"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS"  
}'
```

Response:

```
{
    "payment_id": "pay_nXdonksTZgNzS3TukSiC",
    "merchant_id": "merchant_1757951892",
    "status": "failed",
    "amount": 4510,
    "net_amount": 4510,
    "shipping_cost": null,
    "amount_capturable": 4510,
    "amount_received": null,
    "connector": "peachpayments",
    "client_secret": "pay_nXdonksTZgNzS3TukSiC_secret_SorQgwmawwbNSaAxbZHB",
    "created": "2025-09-15T17:02:32.989Z",
    "currency": "USD",
    "customer_id": "customer123",
    "customer": {
        "id": "customer123",
        "name": "John Doe",
        "email": "customer@gmail.com",
        "phone": "9999999999",
        "phone_country_code": "+1"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "4242",
            "card_type": "CREDIT",
            "card_network": "Visa",
            "card_issuer": "STRIPE PAYMENTS UK LIMITED",
            "card_issuing_country": "UNITEDKINGDOM",
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "guest@example.com"
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "guest@example.com"
    },
    "order_details": null,
    "email": "customer@gmail.com",
    "name": "John Doe",
    "phone": "9999999999",
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": "10",
    "error_message": "Approved for partial amount",
    "unified_code": "UE_9000",
    "unified_message": "Something went wrong",
    "payment_experience": null,
    "payment_method_type": "credit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": {
        "customer_id": "customer123",
        "created_at": 1757955752,
        "expires": 1757959352,
        "secret": "epk_aeab9e4bf1b14c6da933533a5f1b0d51"
    },
    "manual_retry_allowed": null,
    "connector_transaction_id": "0b8298ce-34a6-45e4-baf2-1e44edd98076",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": null,
    "payment_link": null,
    "profile_id": "pro_ddJMerdREreozAswfmdi",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_i4R4BSvIXZXfZv4ieVIH",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-15T17:17:32.989Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-09-15T17:02:34.974Z",
    "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,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null,
    "enable_partial_authorization": null,
    "enable_overcapture": null,
    "is_overcapture_enabled": null,
    "network_details": {
        "network_advice_code": null
    }
}
```

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