diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 9a406e9c7330..8ee962b0bbad 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -10977,9 +10977,7 @@ "minimum": 0 }, "amount": { - "type": "integer", - "format": "int64", - "description": "the amount per quantity of product" + "$ref": "#/components/schemas/MinorUnit" }, "requires_shipping": { "type": "boolean", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 4b0f33da3e76..2f6f4fdf0bc7 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -14219,9 +14219,7 @@ "minimum": 0 }, "amount": { - "type": "integer", - "format": "int64", - "description": "the amount per quantity of product" + "$ref": "#/components/schemas/MinorUnit" }, "requires_shipping": { "type": "boolean", diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 61c7911170a3..4ab0158d01b9 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5080,7 +5080,7 @@ pub struct OrderDetailsWithAmount { #[schema(example = 1)] pub quantity: u16, /// the amount per quantity of product - pub amount: i64, + pub amount: MinorUnit, // Does the order includes shipping pub requires_shipping: Option, /// The image URL of the product diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 6a048e53147a..ce2be525989e 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -7,7 +7,8 @@ pub mod authentication; use std::{ borrow::Cow, fmt::Display, - ops::{Add, Sub}, + iter::Sum, + ops::{Add, Mul, Sub}, primitive::i64, str::FromStr, }; @@ -483,6 +484,20 @@ impl Sub for MinorUnit { } } +impl Mul for MinorUnit { + type Output = Self; + + fn mul(self, a2: u16) -> Self::Output { + Self(self.0 * i64::from(a2)) + } +} + +impl Sum for MinorUnit { + fn sum>(iter: I) -> Self { + iter.fold(Self(0), |a, b| a + b) + } +} + /// Connector specific types to send #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)] diff --git a/crates/hyperswitch_connectors/src/connectors/taxjar.rs b/crates/hyperswitch_connectors/src/connectors/taxjar.rs index 56559b75abc6..c2466799cdf9 100644 --- a/crates/hyperswitch_connectors/src/connectors/taxjar.rs +++ b/crates/hyperswitch_connectors/src/connectors/taxjar.rs @@ -190,11 +190,22 @@ impl ConnectorIntegration { pub amount: FloatMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub order_amount: FloatMajorUnit, pub shipping: FloatMajorUnit, pub router_data: T, } -impl From<(FloatMajorUnit, FloatMajorUnit, T)> for TaxjarRouterData { - fn from((amount, shipping, item): (FloatMajorUnit, FloatMajorUnit, T)) -> Self { +impl From<(FloatMajorUnit, FloatMajorUnit, FloatMajorUnit, T)> for TaxjarRouterData { + fn from( + (amount, order_amount, shipping, item): (FloatMajorUnit, FloatMajorUnit, FloatMajorUnit, T), + ) -> Self { Self { amount, + order_amount, shipping, router_data: item, } @@ -49,7 +53,7 @@ pub struct LineItem { id: Option, quantity: Option, product_tax_code: Option, - unit_price: Option, + unit_price: Option, } #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -69,8 +73,6 @@ impl TryFrom<&TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>> item: &TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>, ) -> Result { let request = &item.router_data.request; - let currency = item.router_data.request.currency; - let currency_unit = &api::CurrencyUnit::Base; let shipping = &item .router_data .request @@ -87,16 +89,11 @@ impl TryFrom<&TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>> order_details .iter() .map(|line_item| { - let unit_price = utils::get_amount_as_f64( - currency_unit, - line_item.amount, - currency, - )?; Ok(LineItem { id: line_item.product_id.clone(), quantity: Some(line_item.quantity), product_tax_code: line_item.product_tax_code.clone(), - unit_price: Some(unit_price), + unit_price: Some(item.order_amount), }) }) .collect(); diff --git a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index d4a18a15182c..166e71304835 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -624,11 +624,14 @@ fn get_item_object( name: data.product_name.clone(), quantity: data.quantity, price: utils::to_currency_base_unit_with_zero_decimal_check( - data.amount, + data.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. item.request.currency, )?, line_amount_total: (f64::from(data.quantity) - * utils::to_currency_base_unit_asf64(data.amount, item.request.currency)?) + * utils::to_currency_base_unit_asf64( + data.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. + item.request.currency, + )?) .to_string(), }) }) diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 21c9c34409a7..bc75fd29c02c 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -1770,8 +1770,8 @@ fn get_line_items(item: &AdyenRouterData<&types::PaymentsAuthorizeRouterData>) - .iter() .enumerate() .map(|(i, data)| LineItem { - amount_including_tax: Some(MinorUnit::new(data.amount)), - amount_excluding_tax: Some(MinorUnit::new(data.amount)), + amount_including_tax: Some(data.amount), + amount_excluding_tax: Some(data.amount), description: Some(data.product_name.clone()), id: Some(format!("Items #{i}")), tax_amount: None, diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index 0c362d26edfe..07bc6bd014c9 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -1,9 +1,11 @@ pub mod transformers; -use std::fmt::Debug; use api_models::enums; use base64::Engine; -use common_utils::request::RequestContent; +use common_utils::{ + request::RequestContent, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; use error_stack::{report, ResultExt}; use masking::PeekInterface; use router_env::logger; @@ -29,8 +31,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Klarna; +#[derive(Clone)] +pub struct Klarna { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Klarna { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl ConnectorCommon for Klarna { fn id(&self) -> &'static str { @@ -215,12 +227,12 @@ impl req: &types::PaymentsSessionRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaSessionRequest::try_from(&connector_router_data)?; // encode only for for urlencoded things. @@ -342,12 +354,12 @@ impl req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, req.request.currency, - req.request.amount_to_capture, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaCaptureRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -670,12 +682,12 @@ impl req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -847,12 +859,12 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, req.request.currency, - req.request.refund_amount, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/router/src/connector/klarna/transformers.rs index ea83e20d99d8..62a5cef0bcae 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/router/src/connector/klarna/transformers.rs @@ -1,5 +1,5 @@ use api_models::payments; -use common_utils::pii; +use common_utils::{pii, types::MinorUnit}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::router_data::KlarnaSdkResponse; use masking::{ExposeInterface, Secret}; @@ -15,25 +15,16 @@ use crate::{ #[derive(Debug, Serialize)] pub struct KlarnaRouterData { - amount: i64, + amount: MinorUnit, router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for KlarnaRouterData { - type Error = error_stack::Report; - - fn try_from( - (_currency_unit, _currency, amount, router_data): ( - &api::CurrencyUnit, - enums::Currency, - i64, - T, - ), - ) -> Result { - Ok(Self { +impl From<(MinorUnit, T)> for KlarnaRouterData { + fn from((amount, router_data): (MinorUnit, T)) -> Self { + Self { amount, router_data, - }) + } } } @@ -74,7 +65,7 @@ impl TryFrom<&Option> for KlarnaConnectorMetadataObject { pub struct KlarnaPaymentsRequest { auto_capture: bool, order_lines: Vec, - order_amount: i64, + order_amount: MinorUnit, purchase_country: enums::CountryAlpha2, purchase_currency: enums::Currency, merchant_reference1: Option, @@ -110,7 +101,7 @@ pub struct KlarnaSessionRequest { intent: KlarnaSessionIntent, purchase_country: enums::CountryAlpha2, purchase_currency: enums::Currency, - order_amount: i64, + order_amount: MinorUnit, order_lines: Vec, shipping_address: Option, } @@ -157,7 +148,7 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsSessionRouterData>> for KlarnaSes name: data.product_name.clone(), quantity: data.quantity, unit_price: data.amount, - total_amount: i64::from(data.quantity) * (data.amount), + total_amount: data.amount * data.quantity, }) .collect(), shipping_address: get_address_info(item.router_data.get_optional_shipping()) @@ -210,7 +201,7 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP name: data.product_name.clone(), quantity: data.quantity, unit_price: data.amount, - total_amount: i64::from(data.quantity) * (data.amount), + total_amount: data.amount * data.quantity, }) .collect(), merchant_reference1: Some(item.router_data.connector_request_reference_id.clone()), @@ -294,8 +285,8 @@ impl TryFrom> pub struct OrderLines { name: String, quantity: u16, - unit_price: i64, - total_amount: i64, + unit_price: MinorUnit, + total_amount: MinorUnit, } #[derive(Debug, Serialize)] @@ -412,7 +403,7 @@ impl #[derive(Debug, Serialize)] pub struct KlarnaCaptureRequest { - captured_amount: i64, + captured_amount: MinorUnit, reference: Option, } @@ -490,7 +481,7 @@ impl #[derive(Default, Debug, Serialize)] pub struct KlarnaRefundRequest { - refunded_amount: i64, + refunded_amount: MinorUnit, reference: Option, } diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs index 2e0ac3b00473..d2d38855dafc 100644 --- a/crates/router/src/connector/riskified/transformers/api.rs +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -163,7 +163,7 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq .get_order_details()? .iter() .map(|order_detail| LineItem { - price: order_detail.amount, + 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(), diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index 6aeda8f8d470..eed0e9937b25 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -145,7 +145,7 @@ impl TryFrom<&frm_types::FrmSaleRouterData> for SignifydPaymentsSaleRequest { .iter() .map(|order_detail| Products { item_name: order_detail.product_name.clone(), - item_price: order_detail.amount, + item_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. item_quantity: i32::from(order_detail.quantity), item_id: order_detail.product_id.clone(), item_category: order_detail.category.clone(), @@ -382,7 +382,7 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ .iter() .map(|order_detail| Products { item_name: order_detail.product_name.clone(), - item_price: order_detail.amount, + item_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. item_quantity: i32::from(order_detail.quantity), item_id: order_detail.product_id.clone(), item_category: order_detail.category.clone(), diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index ebc522b30ad9..09a2457197cb 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -11,7 +11,7 @@ use common_utils::{ DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY, }, ext_traits::{AsyncExt, OptionExt, ValueExt}, - types::{AmountConvertor, MinorUnit, StringMajorUnitForCore}, + types::{AmountConvertor, StringMajorUnitForCore}, }; use error_stack::{report, ResultExt}; use futures::future; @@ -547,7 +547,7 @@ fn validate_order_details( .clone_from(&order.product_img_link) }; order_details_amount_string.amount = required_conversion_type - .convert(MinorUnit::new(order.amount), currency) + .convert(order.amount, currency) .change_context(errors::ApiErrorResponse::AmountConversionFailed { amount_type: "StringMajorUnit", })?; diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 0de7683097b1..1f67e8066c9e 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -5445,13 +5445,13 @@ pub async fn get_unified_translation( } pub fn validate_order_details_amount( order_details: Vec, - amount: i64, + amount: MinorUnit, should_validate: bool, ) -> Result<(), errors::ApiErrorResponse> { if should_validate { - let total_order_details_amount: i64 = order_details + let total_order_details_amount: MinorUnit = order_details .iter() - .map(|order| order.amount * i64::from(order.quantity)) + .map(|order| order.amount * order.quantity) .sum(); if total_order_details_amount != amount { diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 69cf19b09bbe..3a66094081ef 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -98,7 +98,7 @@ impl GetTracker, api::PaymentsRequest> for Pa if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), - payment_intent.amount.get_amount_as_i64(), + payment_intent.amount, false, )?; } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 3bccce2783af..696bd1468e4b 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -338,7 +338,7 @@ impl GetTracker, api::PaymentsRequest> for Pa if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), - payment_intent.amount.get_amount_as_i64(), + payment_intent.amount, false, )?; } diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 757370f0c95c..0b530d4d0541 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -82,7 +82,7 @@ impl GetTracker, api::PaymentsRequest> for Pa if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), - payment_intent.amount.get_amount_as_i64(), + payment_intent.amount, false, )?; } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 0ba89053031e..fe217445eea3 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1974,7 +1974,7 @@ pub fn voucher_next_steps_check( } pub fn change_order_details_to_new_type( - order_amount: i64, + order_amount: MinorUnit, order_details: api_models::payments::OrderDetails, ) -> Option> { Some(vec![api_models::payments::OrderDetailsWithAmount { diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 198d400b8542..b85ae5bfab20 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -430,7 +430,9 @@ impl ConnectorData { enums::Connector::Itaubank => { Ok(ConnectorEnum::Old(Box::new(connector::Itaubank::new()))) } - enums::Connector::Klarna => Ok(ConnectorEnum::Old(Box::new(&connector::Klarna))), + enums::Connector::Klarna => { + Ok(ConnectorEnum::Old(Box::new(connector::Klarna::new()))) + } enums::Connector::Mollie => { Ok(ConnectorEnum::Old(Box::new(connector::Mollie::new()))) } diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index e85c57df622f..3b4cf5195f5e 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use api_models::payments::{Address, AddressDetails, OrderDetailsWithAmount}; -use common_utils::pii::Email; +use common_utils::{pii::Email, types::MinorUnit}; use masking::Secret; use router::types::{self, domain, storage::enums, PaymentAddress}; @@ -80,7 +80,7 @@ fn payment_method_details() -> Option { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -381,7 +381,7 @@ async fn should_fail_payment_for_incorrect_cvc() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 100, + amount: MinorUnit::new(100), product_img_link: None, requires_shipping: None, product_id: None, @@ -421,7 +421,7 @@ async fn should_fail_payment_for_invalid_exp_month() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 100, + amount: MinorUnit::new(100), product_img_link: None, requires_shipping: None, product_id: None, @@ -461,7 +461,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 100, + amount: MinorUnit::new(100), product_img_link: None, requires_shipping: None, product_id: None, diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index 20948a90c6d3..da83bdc7d415 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -325,7 +325,7 @@ async fn should_fail_payment_for_incorrect_card_number() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -368,7 +368,7 @@ async fn should_fail_payment_for_incorrect_cvc() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -411,7 +411,7 @@ async fn should_fail_payment_for_invalid_exp_month() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -454,7 +454,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None,