Bug: [FEAT] Implement Refund for JPMorgan



this connector lacks basic feature i.e., refunds.

relevant docs:

- https://developer.payments.jpmorgan.com/api/commerce/online-payments/overview
- https://developer.payments.jpmorgan.com/api/commerce/online-payments/online-payments/online-payments#/operations/V2RefundPost
diff --git a/crates/hyperswitch_connectors/src/connectors/jpmorgan.rs b/crates/hyperswitch_connectors/src/connectors/jpmorgan.rs
index 9783324c042..043f140a174 100644
--- a/crates/hyperswitch_connectors/src/connectors/jpmorgan.rs
+++ b/crates/hyperswitch_connectors/src/connectors/jpmorgan.rs
@@ -665,9 +665,9 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Jpmorga
     fn get_url(
         &self,
         _req: &RefundsRouterData<Execute>,
-        _connectors: &Connectors,
+        connectors: &Connectors,
     ) -> CustomResult<String, errors::ConnectorError> {
-        Err(errors::ConnectorError::NotImplemented("Refunds".to_string()).into())
+        Ok(format!("{}/refunds", self.base_url(connectors)))
     }
 
     fn get_request_body(
diff --git a/crates/hyperswitch_connectors/src/connectors/jpmorgan/transformers.rs b/crates/hyperswitch_connectors/src/connectors/jpmorgan/transformers.rs
index 9f253c0ffb7..0c2f6110f30 100644
--- a/crates/hyperswitch_connectors/src/connectors/jpmorgan/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/jpmorgan/transformers.rs
@@ -272,18 +272,10 @@ pub struct JpmorganPaymentsResponse {
 #[serde(rename_all = "camelCase")]
 pub struct Merchant {
     merchant_id: Option<String>,
-    merchant_software: MerchantSoftware,
+    merchant_software: JpmorganMerchantSoftware,
     merchant_category_code: Option<String>,
 }
 
-#[derive(Default, Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct MerchantSoftware {
-    company_name: Secret<String>,
-    product_name: Secret<String>,
-    version: Option<Secret<String>>,
-}
-
 #[derive(Default, Debug, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct PaymentMethodType {
@@ -522,18 +514,31 @@ pub struct TransactionData {
 pub struct JpmorganRefundRequest {
     pub merchant: MerchantRefundReq,
     pub amount: MinorUnit,
+    pub currency: common_enums::Currency,
 }
 
 #[derive(Default, Debug, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct MerchantRefundReq {
-    pub merchant_software: MerchantSoftware,
+    pub merchant_software: JpmorganMerchantSoftware,
 }
 
 impl<F> TryFrom<&JpmorganRouterData<&RefundsRouterData<F>>> for JpmorganRefundRequest {
     type Error = error_stack::Report<errors::ConnectorError>;
-    fn try_from(_item: &JpmorganRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
-        Err(errors::ConnectorError::NotImplemented("Refunds".to_string()).into())
+    fn try_from(item: &JpmorganRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
+        let merchant_software = JpmorganMerchantSoftware {
+            company_name: String::from("JPMC").into(),
+            product_name: String::from("Hyperswitch").into(),
+        };
+        let merchant = MerchantRefundReq { merchant_software };
+        let amount = item.amount;
+        let currency = item.router_data.request.currency;
+
+        Ok(Self {
+            merchant,
+            amount,
+            currency,
+        })
     }
 }
 
@@ -552,7 +557,6 @@ pub struct JpmorganRefundResponse {
     pub remaining_refundable_amount: Option<i64>,
 }
 
-#[allow(dead_code)]
 #[derive(Debug, Serialize, Default, Deserialize, Clone)]
 pub enum RefundStatus {
     Succeeded,
@@ -571,24 +575,23 @@ impl From<RefundStatus> for common_enums::RefundStatus {
     }
 }
 
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct RefundResponse {
-    id: String,
-    status: RefundStatus,
-}
-
-pub fn refund_status_from_transaction_state(
-    transaction_state: JpmorganTransactionState,
-) -> common_enums::RefundStatus {
-    match transaction_state {
-        JpmorganTransactionState::Voided | JpmorganTransactionState::Closed => {
-            common_enums::RefundStatus::Success
-        }
-        JpmorganTransactionState::Declined | JpmorganTransactionState::Error => {
-            common_enums::RefundStatus::Failure
-        }
-        JpmorganTransactionState::Pending | JpmorganTransactionState::Authorized => {
-            common_enums::RefundStatus::Pending
+impl From<(JpmorganResponseStatus, JpmorganTransactionState)> for RefundStatus {
+    fn from(
+        (response_status, transaction_state): (JpmorganResponseStatus, JpmorganTransactionState),
+    ) -> Self {
+        match response_status {
+            JpmorganResponseStatus::Success => match transaction_state {
+                JpmorganTransactionState::Voided | JpmorganTransactionState::Closed => {
+                    Self::Succeeded
+                }
+                JpmorganTransactionState::Declined | JpmorganTransactionState::Error => {
+                    Self::Failed
+                }
+                JpmorganTransactionState::Pending | JpmorganTransactionState::Authorized => {
+                    Self::Processing
+                }
+            },
+            JpmorganResponseStatus::Denied | JpmorganResponseStatus::Error => Self::Failed,
         }
     }
 }
@@ -607,9 +610,11 @@ impl TryFrom<RefundsResponseRouterData<Execute, JpmorganRefundResponse>>
                     .transaction_id
                     .clone()
                     .ok_or(errors::ConnectorError::ResponseHandlingFailed)?,
-                refund_status: refund_status_from_transaction_state(
+                refund_status: RefundStatus::from((
+                    item.response.response_status,
                     item.response.transaction_state,
-                ),
+                ))
+                .into(),
             }),
             ..item.data
         })
@@ -638,9 +643,11 @@ impl TryFrom<RefundsResponseRouterData<RSync, JpmorganRefundSyncResponse>>
         Ok(Self {
             response: Ok(RefundsResponseData {
                 connector_refund_id: item.response.transaction_id.clone(),
-                refund_status: refund_status_from_transaction_state(
+                refund_status: RefundStatus::from((
+                    item.response.response_status,
                     item.response.transaction_state,
-                ),
+                ))
+                .into(),
             }),
             ..item.data
         })

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

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

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

This PR implements refund flow for JPMorgan connector. Connector returns `Authorized` state (`Pending`). Post RSync call, the status gets updated to `Success`.

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

The connector lacked this basic feature until now. Closes #8435 

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

Updated Cypress tests:

<img width="558" alt="image" src="https://github.com/user-attachments/assets/cb18d34e-d6d2-4145-8e94-773ab6638389" />

There exist an issue from the connector where it returns same `transaction_id` (`connector_transaction_id`) every single time which we have no control. Because of this, refunds tend to exceed the maximum refundable amount resulting in 4xx as shown below:

<img width="557" alt="image" src="https://github.com/user-attachments/assets/549c517b-cc54-4114-b034-5df64783bd68" />

<img width="477" alt="image" src="https://github.com/user-attachments/assets/47018470-5798-4a07-b480-1f0694540d68" />


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

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

<!-- @coderabbitai ignore -->