Bug: [REFACTOR]: [CYBERSOURCE] Add amount conversion framework to Cybersource



### :memo: Feature Description

Currently, amounts are represented as `i64` values throughout the application. We want to introduce a `Unit` struct that explicitly states the denomination. A new type, `MinorUnit`, has been added to standardize the flow of amounts across the application. This type will now be used by all the connector flows.

Rather than handling conversions in each connector, we will centralize the conversion logic in one place within the core of the application.

### :hammer: Possible Implementation

- For each connector, we need to create an amount conversion function. Connectors will specify the format they require, and the core framework will handle the conversion accordingly.
- Connectors should invoke the `convert` function to receive the amount in their required format.
- Refer to the [connector documentation](https://developer.cybersource.com/docs/cybs/en-us/api-fields/reference/all/so/api-fields/original-transaction-amount.html) to determine the required amount format for each connector.
- You can refer [this PR](https://github.com/juspay/hyperswitch/pull/4825) for more context.


🔖 Note: All the changes needed should be contained within `hyperswitch/crates/router/src/connector/` , `crates/router/src/types/api.rs` , `crates/router/tests/connectors/`


### :package: Have you spent some time checking if this feature request has been raised before?

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

### :package: Have you read the Contributing Guidelines?

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

### :package: Submission Process:
- Ask the maintainers for assignment of the issue, you can request for assignment by commenting on the issue itself.
- Once assigned, submit a pull request (PR).
- Maintainers will review and provide feedback, if any. 
- Maintainers can unassign issues due to inactivity, [read more here](https://github.com/juspay/hyperswitch/wiki/Hacktoberfest-Contribution-Rules).

Refer [here](https://github.com/juspay/hyperswitch/blob/main/docs/TERMS_OF_CONTEST.md) for Terms and conditions for the contest.

###  :sparkles: Are you willing to submit a PR?
diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs
index 2a271acb62b..433a3139d52 100644
--- a/crates/common_utils/src/types.rs
+++ b/crates/common_utils/src/types.rs
@@ -603,7 +603,6 @@ impl StringMajorUnit {
     pub fn zero() -> Self {
         Self("0".to_string())
     }
-
     /// Get string amount from struct to be removed in future
     pub fn get_amount_as_string(&self) -> String {
         self.0.clone()
diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs
index 806cf67b2da..06f448e075a 100644
--- a/crates/router/src/connector/cybersource.rs
+++ b/crates/router/src/connector/cybersource.rs
@@ -1,9 +1,10 @@
 pub mod transformers;
 
-use std::fmt::Debug;
-
 use base64::Engine;
-use common_utils::request::RequestContent;
+use common_utils::{
+    request::RequestContent,
+    types::{AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector},
+};
 use diesel_models::enums;
 use error_stack::{report, Report, ResultExt};
 use masking::{ExposeInterface, PeekInterface};
@@ -12,7 +13,7 @@ use time::OffsetDateTime;
 use transformers as cybersource;
 use url::Url;
 
-use super::utils::{PaymentsAuthorizeRequestData, RouterData};
+use super::utils::{convert_amount, PaymentsAuthorizeRequestData, RouterData};
 use crate::{
     configs::settings,
     connector::{
@@ -36,9 +37,18 @@ use crate::{
     utils::BytesExt,
 };
 
-#[derive(Debug, Clone)]
-pub struct Cybersource;
+#[derive(Clone)]
+pub struct Cybersource {
+    amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
+}
 
+impl Cybersource {
+    pub fn new() -> &'static Self {
+        &Self {
+            amount_converter: &StringMajorUnitForConnector,
+        }
+    }
+}
 impl Cybersource {
     pub fn generate_digest(&self, payload: &[u8]) -> String {
         let payload_digest = digest::digest(&digest::SHA256, payload);
@@ -617,20 +627,20 @@ impl
         req: &types::PaymentsPreProcessingRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let minor_amount =
             req.request
-                .currency
+                .minor_amount
                 .ok_or(errors::ConnectorError::MissingRequiredField {
-                    field_name: "currency",
-                })?,
+                    field_name: "minor_amount",
+                })?;
+        let currency =
             req.request
-                .amount
+                .currency
                 .ok_or(errors::ConnectorError::MissingRequiredField {
-                    field_name: "amount",
-                })?,
-            req,
-        ))?;
+                    field_name: "currency",
+                })?;
+        let amount = convert_amount(self.amount_converter, minor_amount, currency)?;
+        let connector_router_data = cybersource::CybersourceRouterData::from((amount, req));
         let connector_req =
             cybersource::CybersourcePreProcessingRequest::try_from(&connector_router_data)?;
         Ok(RequestContent::Json(Box::new(connector_req)))
@@ -718,12 +728,12 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
         req: &types::PaymentsCaptureRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let amount = convert_amount(
+            self.amount_converter,
+            req.request.minor_amount_to_capture,
             req.request.currency,
-            req.request.amount_to_capture,
-            req,
-        ))?;
+        )?;
+        let connector_router_data = cybersource::CybersourceRouterData::from((amount, req));
         let connector_req =
             cybersource::CybersourcePaymentsCaptureRequest::try_from(&connector_router_data)?;
         Ok(RequestContent::Json(Box::new(connector_req)))
@@ -922,12 +932,12 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
         req: &types::PaymentsAuthorizeRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let amount = convert_amount(
+            self.amount_converter,
+            req.request.minor_amount,
             req.request.currency,
-            req.request.amount,
-            req,
-        ))?;
+        )?;
+        let connector_router_data = cybersource::CybersourceRouterData::from((amount, req));
         if req.is_three_ds()
             && req.request.is_card()
             && (req.request.connector_mandate_id().is_none()
@@ -1068,12 +1078,12 @@ impl ConnectorIntegration<api::PoFulfill, types::PayoutsData, types::PayoutsResp
         req: &types::PayoutsRouterData<api::PoFulfill>,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let amount = convert_amount(
+            self.amount_converter,
+            req.request.minor_amount,
             req.request.destination_currency,
-            req.request.amount,
-            req,
-        ))?;
+        )?;
+        let connector_router_data = cybersource::CybersourceRouterData::from((amount, req));
         let connector_req =
             cybersource::CybersourcePayoutFulfillRequest::try_from(&connector_router_data)?;
         Ok(RequestContent::Json(Box::new(connector_req)))
@@ -1193,12 +1203,12 @@ impl
         req: &types::PaymentsCompleteAuthorizeRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let amount = convert_amount(
+            self.amount_converter,
+            req.request.minor_amount,
             req.request.currency,
-            req.request.amount,
-            req,
-        ))?;
+        )?;
+        let connector_router_data = cybersource::CybersourceRouterData::from((amount, req));
         let connector_req =
             cybersource::CybersourcePaymentsRequest::try_from(&connector_router_data)?;
         Ok(RequestContent::Json(Box::new(connector_req)))
@@ -1317,20 +1327,21 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
         req: &types::PaymentsCancelRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let minor_amount =
             req.request
-                .currency
+                .minor_amount
                 .ok_or(errors::ConnectorError::MissingRequiredField {
-                    field_name: "Currency",
-                })?,
+                    field_name: "Amount",
+                })?;
+        let currency =
             req.request
-                .amount
+                .currency
                 .ok_or(errors::ConnectorError::MissingRequiredField {
-                    field_name: "Amount",
-                })?,
-            req,
-        ))?;
+                    field_name: "Currency",
+                })?;
+        let amount = convert_amount(self.amount_converter, minor_amount, currency)?;
+        let connector_router_data = cybersource::CybersourceRouterData::from((amount, req));
+
         let connector_req = cybersource::CybersourceVoidRequest::try_from(&connector_router_data)?;
 
         Ok(RequestContent::Json(Box::new(connector_req)))
@@ -1443,12 +1454,12 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
         req: &types::RefundExecuteRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let refund_amount = convert_amount(
+            self.amount_converter,
+            req.request.minor_refund_amount,
             req.request.currency,
-            req.request.refund_amount,
-            req,
-        ))?;
+        )?;
+        let connector_router_data = cybersource::CybersourceRouterData::from((refund_amount, req));
         let connector_req =
             cybersource::CybersourceRefundRequest::try_from(&connector_router_data)?;
         Ok(RequestContent::Json(Box::new(connector_req)))
@@ -1609,12 +1620,14 @@ impl
         req: &types::PaymentsIncrementalAuthorizationRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let connector_router_data = cybersource::CybersourceRouterData::try_from((
-            &self.get_currency_unit(),
+        let minor_additional_amount = MinorUnit::new(req.request.additional_amount);
+        let additional_amount = convert_amount(
+            self.amount_converter,
+            minor_additional_amount,
             req.request.currency,
-            req.request.additional_amount,
-            req,
-        ))?;
+        )?;
+        let connector_router_data =
+            cybersource::CybersourceRouterData::from((additional_amount, req));
         let connector_request =
             cybersource::CybersourcePaymentsIncrementalAuthorizationRequest::try_from(
                 &connector_router_data,
diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs
index 2ea36e3e35e..2174eb3bc50 100644
--- a/crates/router/src/connector/cybersource/transformers.rs
+++ b/crates/router/src/connector/cybersource/transformers.rs
@@ -9,7 +9,7 @@ use common_enums::FutureUsage;
 use common_utils::{
     ext_traits::{OptionExt, ValueExt},
     pii,
-    types::SemanticVersion,
+    types::{SemanticVersion, StringMajorUnit},
 };
 use error_stack::ResultExt;
 use masking::{ExposeInterface, PeekInterface, Secret};
@@ -41,21 +41,16 @@ use crate::{
 
 #[derive(Debug, Serialize)]
 pub struct CybersourceRouterData<T> {
-    pub amount: String,
+    pub amount: StringMajorUnit,
     pub router_data: T,
 }
 
-impl<T> TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for CybersourceRouterData<T> {
-    type Error = error_stack::Report<errors::ConnectorError>;
-    fn try_from(
-        (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T),
-    ) -> Result<Self, Self::Error> {
-        // This conversion function is used at different places in the file, if updating this, keep a check for those
-        let amount = utils::get_amount_as_string(currency_unit, amount, currency)?;
-        Ok(Self {
+impl<T> From<(StringMajorUnit, T)> for CybersourceRouterData<T> {
+    fn from((amount, router_data): (StringMajorUnit, T)) -> Self {
+        Self {
             amount,
-            router_data: item,
-        })
+            router_data,
+        }
     }
 }
 
@@ -93,7 +88,7 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest {
 
         let order_information = OrderInformationWithBill {
             amount_details: Amount {
-                total_amount: "0".to_string(),
+                total_amount: StringMajorUnit::zero(),
                 currency: item.request.currency,
             },
             bill_to: Some(bill_to),
@@ -526,14 +521,14 @@ pub struct OrderInformation {
 #[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct Amount {
-    total_amount: String,
+    total_amount: StringMajorUnit,
     currency: api_models::enums::Currency,
 }
 
 #[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct AdditionalAmount {
-    additional_amount: String,
+    additional_amount: StringMajorUnit,
     currency: String,
 }
 
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index f5fa2d9a37e..975b219a26c 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -367,7 +367,7 @@ impl ConnectorData {
                     Ok(ConnectorEnum::Old(Box::new(connector::Cryptopay::new())))
                 }
                 enums::Connector::Cybersource => {
-                    Ok(ConnectorEnum::Old(Box::new(&connector::Cybersource)))
+                    Ok(ConnectorEnum::Old(Box::new(connector::Cybersource::new())))
                 }
                 enums::Connector::Datatrans => {
                     Ok(ConnectorEnum::Old(Box::new(connector::Datatrans::new())))
diff --git a/crates/router/tests/connectors/cybersource.rs b/crates/router/tests/connectors/cybersource.rs
index 0116b52f4ed..1accece7fa6 100644
--- a/crates/router/tests/connectors/cybersource.rs
+++ b/crates/router/tests/connectors/cybersource.rs
@@ -14,7 +14,7 @@ impl utils::Connector for Cybersource {
     fn get_data(&self) -> api::ConnectorData {
         use router::connector::Cybersource;
         utils::construct_connector_data_old(
-            Box::new(&Cybersource),
+            Box::new(Cybersource::new()),
             types::Connector::Cybersource,
             api::GetToken::Connector,
             None,

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

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

## Description
<!-- Describe your changes in detail -->
added StringMajorUnit for amount conversion

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

## 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)?
-->
Tested through postman:

- Create a MCA for cybersource:
- Create a Payment with Cybersource:
```
{
    "amount": 499,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "customer_id": "test_rec7",
    "email": "guest@example.com",
    "request_external_three_ds_authentication": true,
    "customer_acceptance": {
        "acceptance_type": "online"
    },
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "Test Holder",
            "card_cvc": "737"
        }
    },
    "billing": {
        "address": {
            "city": "test",
            "country": "US",
            "line1": "here",
            "line2": "there",
            "line3": "anywhere",
            "zip": "560095",
            "state": "Washington",
            "first_name": "One",
            "last_name": "Two"
        },
        "phone": {
            "number": "1234567890",
            "country_code": "+1"
        },
        "email": "guest@example.com"
    },
    "authentication_type": "no_three_ds"
}
```
- The payment should be succeeded 
```
{
    "payment_id": "pay_LGt7Tbil8tLko2OpVHqJ",
    "merchant_id": "merchant_1730121122",
    "status": "succeeded",
    "amount": 499,
    "net_amount": 499,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 499,
    "connector": "cybersource",
    "client_secret": "pay_LGt7Tbil8tLko2OpVHqJ_secret_dycae051HVaAJYXSCdCH",
    "created": "2024-10-28T13:12:22.609Z",
    "currency": "USD",
    "customer_id": "test_rec7",
    "customer": {
        "id": "test_rec7",
        "name": null,
        "email": "guest@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": "4242",
            "card_type": null,
            "card_network": "Visa",
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": null,
            "payment_checks": {
                "avs_response": {
                    "code": "Y",
                    "codeRaw": "Y"
                },
                "card_verification": {
                    "resultCode": "M",
                    "resultCodeRaw": "M"
                }
            },
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": "token_LyziBYSQnkubuQbE91YF",
    "shipping": null,
    "billing": {
        "address": {
            "city": "test",
            "country": "US",
            "line1": "here",
            "line2": "there",
            "line3": "anywhere",
            "zip": "560095",
            "state": "Washington",
            "first_name": "One",
            "last_name": "Two"
        },
        "phone": {
            "number": "1234567890",
            "country_code": "+1"
        },
        "email": "guest@example.com"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": null,
    "phone": null,
    "return_url": null,
    "authentication_type": "no_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": "credit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": {
        "customer_id": "test_rec7",
        "created_at": 1730121142,
        "expires": 1730124742,
        "secret": "epk_bb3a50134caa4214ad2ea7b534184d26"
    },
    "manual_retry_allowed": false,
    "connector_transaction_id": "7301211437366791304951",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_LGt7Tbil8tLko2OpVHqJ_1",
    "payment_link": null,
    "profile_id": "pro_2pDEf41cwojHtrM9xItk",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_AFVVYXejTDoWa4IgaPnw",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2024-10-28T13:27:22.609Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2024-10-28T13:12:24.692Z",
    "charges": null,
    "frm_metadata": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null
}
```
- Check cybersource dashboard with respective connector_transaction_id to get the transaction data
- the amount in dashboard should match with the amount passed in request.
![image](https://github.com/user-attachments/assets/c9644519-1638-4a09-a6b0-3c56c1adabd0)

## 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`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
