Skip to content

Commit

Permalink
refactor(connector): added amount conversion framework for klarna and…
Browse files Browse the repository at this point in the history
… change type of amount to MinorUnit for OrderDetailsWithAmount (#4979)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: swangi-kumari <[email protected]>
Co-authored-by: Swangi Kumari <[email protected]>
  • Loading branch information
4 people authored Oct 25, 2024
1 parent d58f706 commit 2807622
Show file tree
Hide file tree
Showing 21 changed files with 122 additions and 95 deletions.
4 changes: 1 addition & 3 deletions api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 1 addition & 3 deletions api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>,
/// The image URL of the product
Expand Down
17 changes: 16 additions & 1 deletion crates/common_utils/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -483,6 +484,20 @@ impl Sub for MinorUnit {
}
}

impl Mul<u16> for MinorUnit {
type Output = Self;

fn mul(self, a2: u16) -> Self::Output {
Self(self.0 * i64::from(a2))
}
}

impl Sum for MinorUnit {
fn sum<I: Iterator<Item = Self>>(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)]
Expand Down
15 changes: 13 additions & 2 deletions crates/hyperswitch_connectors/src/connectors/taxjar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,22 @@ impl ConnectorIntegration<CalculateTax, PaymentsTaxCalculationData, TaxCalculati

let shipping = utils::convert_amount(
self.amount_converter,
req.request.shipping_cost.unwrap_or(MinorUnit::new(0)),
req.request.shipping_cost.unwrap_or(MinorUnit::zero()),
req.request.currency,
)?;

let connector_router_data = taxjar::TaxjarRouterData::from((amount, shipping, req));
let order_amount = utils::convert_amount(
self.amount_converter,
req.request
.order_details
.as_ref()
.map(|details| details.iter().map(|item| item.amount).sum())
.unwrap_or(MinorUnit::zero()),
req.request.currency,
)?;

let connector_router_data =
taxjar::TaxjarRouterData::from((amount, order_amount, shipping, req));
let connector_req = taxjar::TaxjarPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use hyperswitch_domain_models::{
router_response_types::TaxCalculationResponseData,
types,
};
use hyperswitch_interfaces::{api, errors};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

Expand All @@ -18,14 +18,18 @@ use crate::{

pub struct TaxjarRouterData<T> {
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<T> From<(FloatMajorUnit, FloatMajorUnit, T)> for TaxjarRouterData<T> {
fn from((amount, shipping, item): (FloatMajorUnit, FloatMajorUnit, T)) -> Self {
impl<T> From<(FloatMajorUnit, FloatMajorUnit, FloatMajorUnit, T)> for TaxjarRouterData<T> {
fn from(
(amount, order_amount, shipping, item): (FloatMajorUnit, FloatMajorUnit, FloatMajorUnit, T),
) -> Self {
Self {
amount,
order_amount,
shipping,
router_data: item,
}
Expand All @@ -49,7 +53,7 @@ pub struct LineItem {
id: Option<String>,
quantity: Option<u16>,
product_tax_code: Option<String>,
unit_price: Option<f64>,
unit_price: Option<FloatMajorUnit>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
Expand All @@ -69,8 +73,6 @@ impl TryFrom<&TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>>
item: &TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>,
) -> Result<Self, Self::Error> {
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
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
})
Expand Down
4 changes: 2 additions & 2 deletions crates/router/src/connector/adyen/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
60 changes: 36 additions & 24 deletions crates/router/src/connector/klarna.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -29,8 +31,18 @@ use crate::{
utils::BytesExt,
};

#[derive(Debug, Clone)]
pub struct Klarna;
#[derive(Clone)]
pub struct Klarna {
amount_converter: &'static (dyn AmountConvertor<Output = MinorUnit> + Sync),
}

impl Klarna {
pub fn new() -> &'static Self {
&Self {
amount_converter: &MinorUnitForConnector,
}
}
}

impl ConnectorCommon for Klarna {
fn id(&self) -> &'static str {
Expand Down Expand Up @@ -215,12 +227,12 @@ impl
req: &types::PaymentsSessionRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
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.
Expand Down Expand Up @@ -342,12 +354,12 @@ impl
req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
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)))
}
Expand Down Expand Up @@ -670,12 +682,12 @@ impl
req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
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)))
Expand Down Expand Up @@ -847,12 +859,12 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
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)))
}
Expand Down
37 changes: 14 additions & 23 deletions crates/router/src/connector/klarna/transformers.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -15,25 +15,16 @@ use crate::{

#[derive(Debug, Serialize)]
pub struct KlarnaRouterData<T> {
amount: i64,
amount: MinorUnit,
router_data: T,
}

impl<T> TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for KlarnaRouterData<T> {
type Error = error_stack::Report<errors::ConnectorError>;

fn try_from(
(_currency_unit, _currency, amount, router_data): (
&api::CurrencyUnit,
enums::Currency,
i64,
T,
),
) -> Result<Self, Self::Error> {
Ok(Self {
impl<T> From<(MinorUnit, T)> for KlarnaRouterData<T> {
fn from((amount, router_data): (MinorUnit, T)) -> Self {
Self {
amount,
router_data,
})
}
}
}

Expand Down Expand Up @@ -74,7 +65,7 @@ impl TryFrom<&Option<pii::SecretSerdeValue>> for KlarnaConnectorMetadataObject {
pub struct KlarnaPaymentsRequest {
auto_capture: bool,
order_lines: Vec<OrderLines>,
order_amount: i64,
order_amount: MinorUnit,
purchase_country: enums::CountryAlpha2,
purchase_currency: enums::Currency,
merchant_reference1: Option<String>,
Expand Down Expand Up @@ -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<OrderLines>,
shipping_address: Option<KlarnaShippingAddress>,
}
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -294,8 +285,8 @@ impl TryFrom<types::PaymentsResponseRouterData<KlarnaPaymentsResponse>>
pub struct OrderLines {
name: String,
quantity: u16,
unit_price: i64,
total_amount: i64,
unit_price: MinorUnit,
total_amount: MinorUnit,
}

#[derive(Debug, Serialize)]
Expand Down Expand Up @@ -412,7 +403,7 @@ impl<F, T>

#[derive(Debug, Serialize)]
pub struct KlarnaCaptureRequest {
captured_amount: i64,
captured_amount: MinorUnit,
reference: Option<String>,
}

Expand Down Expand Up @@ -490,7 +481,7 @@ impl<F>

#[derive(Default, Debug, Serialize)]
pub struct KlarnaRefundRequest {
refunded_amount: i64,
refunded_amount: MinorUnit,
reference: Option<String>,
}

Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/connector/riskified/transformers/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading

0 comments on commit 2807622

Please sign in to comment.