Bug: [REFACTOR]: [RISKIFIED] Add amount conversion framework to Riskified



### :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://support.riskified.com/hc/en-us/articles/360012160393-API-Integration-Guide) 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?

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

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

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

###  :sparkles: Are you willing to submit a PR?

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

diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs
index 0f8ebb46134..7d9feec717a 100644
--- a/crates/router/src/connector/riskified.rs
+++ b/crates/router/src/connector/riskified.rs
@@ -1,8 +1,10 @@
 pub mod transformers;
-use std::fmt::Debug;
 
 #[cfg(feature = "frm")]
 use base64::Engine;
+use common_utils::types::{
+    AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector,
+};
 #[cfg(feature = "frm")]
 use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent};
 #[cfg(feature = "frm")]
@@ -14,6 +16,7 @@ use ring::hmac;
 #[cfg(feature = "frm")]
 use transformers as riskified;
 
+use super::utils::convert_amount;
 #[cfg(feature = "frm")]
 use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest};
 use crate::{
@@ -35,10 +38,18 @@ use crate::{
     utils::BytesExt,
 };
 
-#[derive(Debug, Clone)]
-pub struct Riskified;
+#[derive(Clone)]
+pub struct Riskified {
+    amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
+}
 
 impl Riskified {
+    pub fn new() -> &'static Self {
+        &Self {
+            amount_converter: &StringMajorUnitForConnector,
+        }
+    }
+
     #[cfg(feature = "frm")]
     pub fn generate_authorization_signature(
         &self,
@@ -173,7 +184,17 @@ impl
         req: &frm_types::FrmCheckoutRouterData,
         _connectors: &settings::Connectors,
     ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(req)?;
+        let amount = convert_amount(
+            self.amount_converter,
+            MinorUnit::new(req.request.amount),
+            req.request
+                .currency
+                .ok_or(errors::ConnectorError::MissingRequiredField {
+                    field_name: "currency",
+                })?,
+        )?;
+        let req_data = riskified::RiskifiedRouterData::from((amount, req));
+        let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(&req_data)?;
         Ok(RequestContent::Json(Box::new(req_obj)))
     }
 
@@ -293,7 +314,17 @@ impl
                 Ok(RequestContent::Json(Box::new(req_obj)))
             }
             _ => {
-                let req_obj = riskified::TransactionSuccessRequest::try_from(req)?;
+                let amount = convert_amount(
+                    self.amount_converter,
+                    MinorUnit::new(req.request.amount),
+                    req.request
+                        .currency
+                        .ok_or(errors::ConnectorError::MissingRequiredField {
+                            field_name: "currency",
+                        })?,
+                )?;
+                let req_data = riskified::RiskifiedRouterData::from((amount, req));
+                let req_obj = riskified::TransactionSuccessRequest::try_from(&req_data)?;
                 Ok(RequestContent::Json(Box::new(req_obj)))
             }
         }
diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs
index d2d38855daf..bbeb045f692 100644
--- a/crates/router/src/connector/riskified/transformers/api.rs
+++ b/crates/router/src/connector/riskified/transformers/api.rs
@@ -1,5 +1,10 @@
 use api_models::payments::AdditionalPaymentData;
-use common_utils::{ext_traits::ValueExt, id_type, pii::Email};
+use common_utils::{
+    ext_traits::ValueExt,
+    id_type,
+    pii::Email,
+    types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector},
+};
 use error_stack::{self, ResultExt};
 use masking::Secret;
 use serde::{Deserialize, Serialize};
@@ -7,17 +12,37 @@ use time::PrimitiveDateTime;
 
 use crate::{
     connector::utils::{
-        AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckTransactionRequest, RouterData,
+        convert_amount, AddressDetailsData, FraudCheckCheckoutRequest,
+        FraudCheckTransactionRequest, RouterData,
     },
     core::{errors, fraud_check::types as core_types},
     types::{
-        self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums,
+        self,
+        api::{self, Fulfillment},
+        fraud_check as frm_types,
+        storage::enums as storage_enums,
         ResponseId, ResponseRouterData,
     },
 };
 
 type Error = error_stack::Report<errors::ConnectorError>;
 
+pub struct RiskifiedRouterData<T> {
+    pub amount: StringMajorUnit,
+    pub router_data: T,
+    amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
+}
+
+impl<T> From<(StringMajorUnit, T)> for RiskifiedRouterData<T> {
+    fn from((amount, router_data): (StringMajorUnit, T)) -> Self {
+        Self {
+            amount,
+            router_data,
+            amount_converter: &StringMajorUnitForConnector,
+        }
+    }
+}
+
 #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
 pub struct RiskifiedPaymentsCheckoutRequest {
     order: CheckoutRequest,
@@ -35,7 +60,7 @@ pub struct CheckoutRequest {
     updated_at: PrimitiveDateTime,
     gateway: Option<String>,
     browser_ip: Option<std::net::IpAddr>,
-    total_price: i64,
+    total_price: StringMajorUnit,
     total_discounts: i64,
     cart_token: String,
     referring_site: String,
@@ -60,13 +85,13 @@ pub struct PaymentDetails {
 
 #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
 pub struct ShippingLines {
-    price: i64,
+    price: StringMajorUnit,
     title: Option<String>,
 }
 
 #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
 pub struct DiscountCodes {
-    amount: i64,
+    amount: StringMajorUnit,
     code: Option<String>,
 }
 
@@ -110,7 +135,7 @@ pub struct OrderAddress {
 
 #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
 pub struct LineItem {
-    price: i64,
+    price: StringMajorUnit,
     quantity: i32,
     title: String,
     product_type: Option<api_models::payments::ProductType>,
@@ -132,9 +157,14 @@ pub struct RiskifiedMetadata {
     shipping_lines: Vec<ShippingLines>,
 }
 
-impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutRequest {
+impl TryFrom<&RiskifiedRouterData<&frm_types::FrmCheckoutRouterData>>
+    for RiskifiedPaymentsCheckoutRequest
+{
     type Error = Error;
-    fn try_from(payment_data: &frm_types::FrmCheckoutRouterData) -> Result<Self, Self::Error> {
+    fn try_from(
+        payment: &RiskifiedRouterData<&frm_types::FrmCheckoutRouterData>,
+    ) -> Result<Self, Self::Error> {
+        let payment_data = payment.router_data.clone();
         let metadata: RiskifiedMetadata = payment_data
             .frm_metadata
             .clone()
@@ -148,6 +178,33 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq
         let billing_address = payment_data.get_billing()?;
         let shipping_address = payment_data.get_shipping_address_with_phone_number()?;
         let address = payment_data.get_billing_address()?;
+        let line_items = payment_data
+            .request
+            .get_order_details()?
+            .iter()
+            .map(|order_detail| {
+                let price = convert_amount(
+                    payment.amount_converter,
+                    order_detail.amount,
+                    payment_data.request.currency.ok_or_else(|| {
+                        errors::ConnectorError::MissingRequiredField {
+                            field_name: "currency",
+                        }
+                    })?,
+                )?;
+
+                Ok(LineItem {
+                    price,
+                    quantity: i32::from(order_detail.quantity),
+                    title: order_detail.product_name.clone(),
+                    product_type: order_detail.product_type.clone(),
+                    requires_shipping: order_detail.requires_shipping,
+                    product_id: order_detail.product_id.clone(),
+                    category: order_detail.category.clone(),
+                    brand: order_detail.brand.clone(),
+                })
+            })
+            .collect::<Result<Vec<_>, Self::Error>>()?;
 
         Ok(Self {
             order: CheckoutRequest {
@@ -156,23 +213,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq
                 created_at: common_utils::date_time::now(),
                 updated_at: common_utils::date_time::now(),
                 gateway: payment_data.request.gateway.clone(),
-                total_price: payment_data.request.amount,
+                total_price: payment.amount.clone(),
                 cart_token: payment_data.attempt_id.clone(),
-                line_items: payment_data
-                    .request
-                    .get_order_details()?
-                    .iter()
-                    .map(|order_detail| LineItem {
-                        price: order_detail.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future.
-                        quantity: i32::from(order_detail.quantity),
-                        title: order_detail.product_name.clone(),
-                        product_type: order_detail.product_type.clone(),
-                        requires_shipping: order_detail.requires_shipping,
-                        product_id: order_detail.product_id.clone(),
-                        category: order_detail.category.clone(),
-                        brand: order_detail.brand.clone(),
-                    })
-                    .collect::<Vec<_>>(),
+                line_items,
                 source: Source::DesktopWeb,
                 billing_address: OrderAddress::try_from(billing_address).ok(),
                 shipping_address: OrderAddress::try_from(shipping_address).ok(),
@@ -411,7 +454,7 @@ pub struct SuccessfulTransactionData {
 pub struct TransactionDecisionData {
     external_status: TransactionStatus,
     reason: Option<String>,
-    amount: i64,
+    amount: StringMajorUnit,
     currency: storage_enums::Currency,
     #[serde(with = "common_utils::custom_serde::iso8601")]
     decided_at: PrimitiveDateTime,
@@ -429,16 +472,21 @@ pub enum TransactionStatus {
     Approved,
 }
 
-impl TryFrom<&frm_types::FrmTransactionRouterData> for TransactionSuccessRequest {
+impl TryFrom<&RiskifiedRouterData<&frm_types::FrmTransactionRouterData>>
+    for TransactionSuccessRequest
+{
     type Error = Error;
-    fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result<Self, Self::Error> {
+    fn try_from(
+        item_data: &RiskifiedRouterData<&frm_types::FrmTransactionRouterData>,
+    ) -> Result<Self, Self::Error> {
+        let item = item_data.router_data.clone();
         Ok(Self {
             order: SuccessfulTransactionData {
                 id: item.attempt_id.clone(),
                 decision: TransactionDecisionData {
                     external_status: TransactionStatus::Approved,
                     reason: None,
-                    amount: item.request.amount,
+                    amount: item_data.amount.clone(),
                     currency: item.request.get_currency()?,
                     decided_at: common_utils::date_time::now(),
                     payment_details: [TransactionPaymentDetails {
diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs
index 2d1a42092f4..213aef9cf03 100644
--- a/crates/router/src/types/api/fraud_check.rs
+++ b/crates/router/src/types/api/fraud_check.rs
@@ -51,7 +51,7 @@ impl FraudCheckConnectorData {
                 Ok(ConnectorEnum::Old(Box::new(&connector::Signifyd)))
             }
             enums::FrmConnectors::Riskified => {
-                Ok(ConnectorEnum::Old(Box::new(&connector::Riskified)))
+                Ok(ConnectorEnum::Old(Box::new(connector::Riskified::new())))
             }
         }
     }

## 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 -->
This PR does amount conversion changes for Riskified Connector and it accepts the amount in MinorUnit


### 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 https://github.com/juspay/hyperswitch/issues/6139

## Test Case
1. Create an FRM payment with Riskified
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_zDKkW8FG9RWqLNhMLVekbZbKOO1AYL8AEOQMKBQx0e0MbbmvKbGF32hdEnyA4JuS' \
--data-raw '{
    "amount": 1000,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 1000,
    "customer_id": "StripeCustomer2",
    "email": "guest@example.com",
    "name": "Bob Smith",
    "phone": "999999999",
    "phone_country_code": "+91",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "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": "IN",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "31 Sherwood Gardens",
            "line3": "31 Sherwood Gardens",
            "city": "London",
            "state": "Manchester",
            "zip": "E14 9wn",
            "country": "GB",
            "first_name": "Bob",
            "last_name": "Smith"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "frm_metadata": { 
        "vendor_name": "srujan",
        "shipping_lines": [
            {
                "price": 240,
                "title": "customer"
            }
        ]
    },
    "order_details": [
        {
            "product_name": "gillete creme",
            "quantity": 2,
            "amount": 600
        },
        {
            "product_name": "gillete razor",
            "quantity": 1,
            "amount": 300
        }
    ]
}'
```
Response
```
{
    "payment_id": "pay_p2PnkzcC76EiUfkMHoS3",
    "merchant_id": "merchant_1732534203",
    "status": "succeeded",
    "amount": 1000,
    "net_amount": 1000,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 1000,
    "connector": "stripe",
    "client_secret": "pay_p2PnkzcC76EiUfkMHoS3_secret_aJ2PoxDPJ177jxjuLJtI",
    "created": "2024-11-25T12:18:24.319Z",
    "currency": "USD",
    "customer_id": "StripeCustomer2",
    "customer": {
        "id": "StripeCustomer2",
        "name": "Bob Smith",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+91"
    },
    "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": "automatic",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "4242",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": null,
            "payment_checks": {
                "cvc_check": "pass",
                "address_line1_check": "pass",
                "address_postal_code_check": "pass"
            },
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "London",
            "country": "GB",
            "line1": "1467",
            "line2": "31 Sherwood Gardens",
            "line3": "31 Sherwood Gardens",
            "zip": "E14 9wn",
            "state": "Manchester",
            "first_name": "Bob",
            "last_name": "Smith"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "IN",
            "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": null
    },
    "order_details": [
        {
            "brand": null,
            "amount": 600,
            "category": null,
            "quantity": 2,
            "product_id": null,
            "product_name": "gillete creme",
            "product_type": null,
            "sub_category": null,
            "product_img_link": null,
            "product_tax_code": null,
            "requires_shipping": null
        },
        {
            "brand": null,
            "amount": 300,
            "category": null,
            "quantity": 1,
            "product_id": null,
            "product_name": "gillete razor",
            "product_type": null,
            "sub_category": null,
            "product_img_link": null,
            "product_tax_code": null,
            "requires_shipping": null
        }
    ],
    "email": "guest@example.com",
    "name": "Bob Smith",
    "phone": "999999999",
    "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": 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": "StripeCustomer2",
        "created_at": 1732537104,
        "expires": 1732540704,
        "secret": "epk_af52a2410328468da0efcceab4721688"
    },
    "manual_retry_allowed": false,
    "connector_transaction_id": "pi_3QP1FGD5R7gDAGff0hjO4CcX",
    "frm_message": {
        "frm_name": "riskified",
        "frm_transaction_id": "pay_p2PnkzcC76EiUfkMHoS3_1",
        "frm_transaction_type": "pre_frm",
        "frm_status": "legit",
        "frm_score": null,
        "frm_reason": "Reviewed and approved by Riskified",
        "frm_error": null
    },
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pi_3QP1FGD5R7gDAGff0hjO4CcX",
    "payment_link": null,
    "profile_id": "pro_zRAcxSJqv3OTKIPGkuxI",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_TSlADmpiVd230wMhKj67",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2024-11-25T12:33:24.319Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2024-11-25T12:18:27.847Z",
    "charges": null,
    "frm_metadata": {
        "vendor_name": "srujan",
        "shipping_lines": [
            {
                "price": 240,
                "title": "customer"
            }
        ]
    },
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": 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`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
