refactor(connector): [FISERV, HELCIM] Add amount conversion framework to Fiserv, Helcim (#7336)

Co-authored-by: Harshvardhan Bahukhandi <harshvardhan.b@Harshvardhan-Bahukhandi-DLC97KJ2C1.local>
This commit is contained in:
Harshvardhan Bahukhandi
2025-03-12 12:40:53 +05:30
committed by GitHub
parent 833da1c3c5
commit 4352101555
8 changed files with 93 additions and 92 deletions

View File

@ -1,13 +1,12 @@
pub mod transformers; pub mod transformers;
use std::fmt::Debug;
use base64::Engine; use base64::Engine;
use common_enums::enums; use common_enums::enums;
use common_utils::{ use common_utils::{
errors::CustomResult, errors::CustomResult,
ext_traits::BytesExt, ext_traits::BytesExt,
request::{Method, Request, RequestBuilder, RequestContent}, request::{Method, Request, RequestBuilder, RequestContent},
types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector},
}; };
use error_stack::{report, ResultExt}; use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{ use hyperswitch_domain_models::{
@ -44,12 +43,23 @@ use time::OffsetDateTime;
use transformers as fiserv; use transformers as fiserv;
use uuid::Uuid; use uuid::Uuid;
use crate::{constants::headers, types::ResponseRouterData, utils}; use crate::{
constants::headers,
types::ResponseRouterData,
utils::{construct_not_implemented_error_report, convert_amount},
};
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct Fiserv; pub struct Fiserv {
amount_converter: &'static (dyn AmountConvertor<Output = FloatMajorUnit> + Sync),
}
impl Fiserv { impl Fiserv {
pub fn new() -> &'static Self {
&Self {
amount_converter: &FloatMajorUnitForConnector,
}
}
pub fn generate_authorization_signature( pub fn generate_authorization_signature(
&self, &self,
auth: fiserv::FiservAuthType, auth: fiserv::FiservAuthType,
@ -194,7 +204,7 @@ impl ConnectorValidation for Fiserv {
| enums::CaptureMethod::Manual | enums::CaptureMethod::Manual
| enums::CaptureMethod::SequentialAutomatic => Ok(()), | enums::CaptureMethod::SequentialAutomatic => Ok(()),
enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err(
utils::construct_not_implemented_error_report(capture_method, self.id()), construct_not_implemented_error_report(capture_method, self.id()),
), ),
} }
} }
@ -421,12 +431,12 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
req: &PaymentsCaptureRouterData, req: &PaymentsCaptureRouterData,
_connectors: &Connectors, _connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let router_obj = fiserv::FiservRouterData::try_from(( let amount_to_capture = convert_amount(
&self.get_currency_unit(), self.amount_converter,
req.request.minor_amount_to_capture,
req.request.currency, req.request.currency,
req.request.amount_to_capture, )?;
req, let router_obj = fiserv::FiservRouterData::try_from((amount_to_capture, req))?;
))?;
let connector_req = fiserv::FiservCaptureRequest::try_from(&router_obj)?; let connector_req = fiserv::FiservCaptureRequest::try_from(&router_obj)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }
@ -530,12 +540,12 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
req: &PaymentsAuthorizeRouterData, req: &PaymentsAuthorizeRouterData,
_connectors: &Connectors, _connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let router_obj = fiserv::FiservRouterData::try_from(( let amount = convert_amount(
&self.get_currency_unit(), self.amount_converter,
req.request.minor_amount,
req.request.currency, req.request.currency,
req.request.amount, )?;
req, let router_obj = fiserv::FiservRouterData::try_from((amount, req))?;
))?;
let connector_req = fiserv::FiservPaymentsRequest::try_from(&router_obj)?; let connector_req = fiserv::FiservPaymentsRequest::try_from(&router_obj)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }
@ -624,12 +634,12 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Fiserv
req: &RefundsRouterData<Execute>, req: &RefundsRouterData<Execute>,
_connectors: &Connectors, _connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let router_obj = fiserv::FiservRouterData::try_from(( let refund_amount = convert_amount(
&self.get_currency_unit(), self.amount_converter,
req.request.minor_refund_amount,
req.request.currency, req.request.currency,
req.request.refund_amount, )?;
req, let router_obj = fiserv::FiservRouterData::try_from((refund_amount, req))?;
))?;
let connector_req = fiserv::FiservRefundRequest::try_from(&router_obj)?; let connector_req = fiserv::FiservRefundRequest::try_from(&router_obj)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }

View File

@ -1,5 +1,5 @@
use common_enums::enums; use common_enums::enums;
use common_utils::{ext_traits::ValueExt, pii}; use common_utils::{ext_traits::ValueExt, pii, types::FloatMajorUnit};
use error_stack::ResultExt; use error_stack::ResultExt;
use hyperswitch_domain_models::{ use hyperswitch_domain_models::{
payment_method_data::PaymentMethodData, payment_method_data::PaymentMethodData,
@ -9,7 +9,7 @@ use hyperswitch_domain_models::{
router_response_types::{PaymentsResponseData, RefundsResponseData}, router_response_types::{PaymentsResponseData, RefundsResponseData},
types, types,
}; };
use hyperswitch_interfaces::{api, errors}; use hyperswitch_interfaces::errors;
use masking::Secret; use masking::Secret;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -23,22 +23,14 @@ use crate::{
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct FiservRouterData<T> { pub struct FiservRouterData<T> {
pub amount: String, pub amount: FloatMajorUnit,
pub router_data: T, pub router_data: T,
} }
impl<T> TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for FiservRouterData<T> { impl<T> TryFrom<(FloatMajorUnit, T)> for FiservRouterData<T> {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from( fn try_from((amount, router_data): (FloatMajorUnit, T)) -> Result<Self, Self::Error> {
(currency_unit, currency, amount, router_data): (
&api::CurrencyUnit,
enums::Currency,
i64,
T,
),
) -> Result<Self, Self::Error> {
let amount = utils::get_amount_as_string(currency_unit, amount, currency)?;
Ok(Self { Ok(Self {
amount, amount,
router_data, router_data,
@ -89,8 +81,7 @@ pub struct GooglePayToken {
#[derive(Default, Debug, Serialize)] #[derive(Default, Debug, Serialize)]
pub struct Amount { pub struct Amount {
#[serde(serialize_with = "utils::str_to_f32")] total: FloatMajorUnit,
total: String,
currency: String, currency: String,
} }
@ -143,7 +134,7 @@ impl TryFrom<&FiservRouterData<&types::PaymentsAuthorizeRouterData>> for FiservP
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let auth: FiservAuthType = FiservAuthType::try_from(&item.router_data.connector_auth_type)?; let auth: FiservAuthType = FiservAuthType::try_from(&item.router_data.connector_auth_type)?;
let amount = Amount { let amount = Amount {
total: item.amount.clone(), total: item.amount,
currency: item.router_data.request.currency.to_string(), currency: item.router_data.request.currency.to_string(),
}; };
let transaction_details = TransactionDetails { let transaction_details = TransactionDetails {
@ -484,7 +475,7 @@ impl TryFrom<&FiservRouterData<&types::PaymentsCaptureRouterData>> for FiservCap
})?; })?;
Ok(Self { Ok(Self {
amount: Amount { amount: Amount {
total: item.amount.clone(), total: item.amount,
currency: item.router_data.request.currency.to_string(), currency: item.router_data.request.currency.to_string(),
}, },
transaction_details: TransactionDetails { transaction_details: TransactionDetails {
@ -579,7 +570,7 @@ impl<F> TryFrom<&FiservRouterData<&types::RefundsRouterData<F>>> for FiservRefun
})?; })?;
Ok(Self { Ok(Self {
amount: Amount { amount: Amount {
total: item.amount.clone(), total: item.amount,
currency: item.router_data.request.currency.to_string(), currency: item.router_data.request.currency.to_string(),
}, },
merchant_details: MerchantDetails { merchant_details: MerchantDetails {

View File

@ -1,5 +1,4 @@
pub mod transformers; pub mod transformers;
use std::fmt::Debug;
use api_models::webhooks::IncomingWebhookEvent; use api_models::webhooks::IncomingWebhookEvent;
use common_enums::enums; use common_enums::enums;
@ -7,6 +6,7 @@ use common_utils::{
errors::CustomResult, errors::CustomResult,
ext_traits::BytesExt, ext_traits::BytesExt,
request::{Method, Request, RequestBuilder, RequestContent}, request::{Method, Request, RequestBuilder, RequestContent},
types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector},
}; };
use error_stack::{report, ResultExt}; use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{ use hyperswitch_domain_models::{
@ -49,11 +49,21 @@ use transformers as helcim;
use crate::{ use crate::{
constants::headers, constants::headers,
types::ResponseRouterData, types::ResponseRouterData,
utils::{to_connector_meta, PaymentsAuthorizeRequestData}, utils::{convert_amount, to_connector_meta, PaymentsAuthorizeRequestData},
}; };
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct Helcim; pub struct Helcim {
amount_convertor: &'static (dyn AmountConvertor<Output = FloatMajorUnit> + Sync),
}
impl Helcim {
pub fn new() -> &'static Self {
&Self {
amount_convertor: &FloatMajorUnitForConnector,
}
}
}
impl api::Payment for Helcim {} impl api::Payment for Helcim {}
impl api::PaymentSession for Helcim {} impl api::PaymentSession for Helcim {}
@ -305,13 +315,13 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
req: &PaymentsAuthorizeRouterData, req: &PaymentsAuthorizeRouterData,
_connectors: &Connectors, _connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = helcim::HelcimRouterData::try_from(( let connector_router_data = convert_amount(
&self.get_currency_unit(), self.amount_convertor,
req.request.minor_amount,
req.request.currency, req.request.currency,
req.request.amount, )?;
req, let router_obj = helcim::HelcimRouterData::try_from((connector_router_data, req))?;
))?; let connector_req = helcim::HelcimPaymentsRequest::try_from(&router_obj)?;
let connector_req = helcim::HelcimPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }
@ -484,13 +494,13 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
req: &PaymentsCaptureRouterData, req: &PaymentsCaptureRouterData,
_connectors: &Connectors, _connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = helcim::HelcimRouterData::try_from(( let connector_router_data = convert_amount(
&self.get_currency_unit(), self.amount_convertor,
req.request.minor_amount_to_capture,
req.request.currency, req.request.currency,
req.request.amount_to_capture, )?;
req, let router_obj = helcim::HelcimRouterData::try_from((connector_router_data, req))?;
))?; let connector_req = helcim::HelcimCaptureRequest::try_from(&router_obj)?;
let connector_req = helcim::HelcimCaptureRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }
@ -651,13 +661,13 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Helcim
req: &RefundsRouterData<Execute>, req: &RefundsRouterData<Execute>,
_connectors: &Connectors, _connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = helcim::HelcimRouterData::try_from(( let connector_router_data = convert_amount(
&self.get_currency_unit(), self.amount_convertor,
req.request.minor_refund_amount,
req.request.currency, req.request.currency,
req.request.refund_amount, )?;
req, let router_obj = helcim::HelcimRouterData::try_from((connector_router_data, req))?;
))?; let connector_req = helcim::HelcimRefundRequest::try_from(&router_obj)?;
let connector_req = helcim::HelcimRefundRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }

View File

@ -1,5 +1,8 @@
use common_enums::enums; use common_enums::enums;
use common_utils::pii::{Email, IpAddress}; use common_utils::{
pii::{Email, IpAddress},
types::FloatMajorUnit,
};
use error_stack::ResultExt; use error_stack::ResultExt;
use hyperswitch_domain_models::{ use hyperswitch_domain_models::{
payment_method_data::{Card, PaymentMethodData}, payment_method_data::{Card, PaymentMethodData},
@ -15,10 +18,7 @@ use hyperswitch_domain_models::{
RefundsRouterData, SetupMandateRouterData, RefundsRouterData, SetupMandateRouterData,
}, },
}; };
use hyperswitch_interfaces::{ use hyperswitch_interfaces::errors;
api::{self},
errors,
};
use masking::Secret; use masking::Secret;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -33,16 +33,13 @@ use crate::{
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct HelcimRouterData<T> { pub struct HelcimRouterData<T> {
pub amount: f64, pub amount: FloatMajorUnit,
pub router_data: T, pub router_data: T,
} }
impl<T> TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for HelcimRouterData<T> { impl<T> TryFrom<(FloatMajorUnit, T)> for HelcimRouterData<T> {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from( fn try_from((amount, item): (FloatMajorUnit, T)) -> Result<Self, Self::Error> {
(currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T),
) -> Result<Self, Self::Error> {
let amount = crate::utils::get_amount_as_f64(currency_unit, amount, currency)?;
Ok(Self { Ok(Self {
amount, amount,
router_data: item, router_data: item,
@ -77,7 +74,7 @@ pub struct HelcimVerifyRequest {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct HelcimPaymentsRequest { pub struct HelcimPaymentsRequest {
amount: f64, amount: FloatMajorUnit,
currency: enums::Currency, currency: enums::Currency,
ip_address: Secret<String, IpAddress>, ip_address: Secret<String, IpAddress>,
card_data: HelcimCard, card_data: HelcimCard,
@ -115,8 +112,8 @@ pub struct HelcimInvoice {
pub struct HelcimLineItems { pub struct HelcimLineItems {
description: String, description: String,
quantity: u8, quantity: u8,
price: f64, price: FloatMajorUnit,
total: f64, total: FloatMajorUnit,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@ -514,7 +511,7 @@ impl<F>
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct HelcimCaptureRequest { pub struct HelcimCaptureRequest {
pre_auth_transaction_id: u64, pre_auth_transaction_id: u64,
amount: f64, amount: FloatMajorUnit,
ip_address: Secret<String, IpAddress>, ip_address: Secret<String, IpAddress>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
ecommerce: Option<bool>, ecommerce: Option<bool>,
@ -637,7 +634,7 @@ impl<F>
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct HelcimRefundRequest { pub struct HelcimRefundRequest {
amount: f64, amount: FloatMajorUnit,
original_transaction_id: u64, original_transaction_id: u64,
ip_address: Secret<String, IpAddress>, ip_address: Secret<String, IpAddress>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View File

@ -57,7 +57,6 @@ use masking::{ExposeInterface, PeekInterface, Secret};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use router_env::logger; use router_env::logger;
use serde::Serializer;
use serde_json::Value; use serde_json::Value;
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
@ -344,16 +343,6 @@ pub(crate) fn construct_not_implemented_error_report(
.into() .into()
} }
pub(crate) fn str_to_f32<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let float_value = value.parse::<f64>().map_err(|_| {
serde::ser::Error::custom("Invalid string, cannot be converted to float value")
})?;
serializer.serialize_f64(float_value)
}
pub(crate) const SELECTED_PAYMENT_METHOD: &str = "Selected payment method"; pub(crate) const SELECTED_PAYMENT_METHOD: &str = "Selected payment method";
pub(crate) fn get_unimplemented_payment_method_error_message(connector: &str) -> String { pub(crate) fn get_unimplemented_payment_method_error_message(connector: &str) -> String {

View File

@ -432,7 +432,9 @@ impl ConnectorData {
enums::Connector::Elavon => { enums::Connector::Elavon => {
Ok(ConnectorEnum::Old(Box::new(connector::Elavon::new()))) Ok(ConnectorEnum::Old(Box::new(connector::Elavon::new())))
} }
enums::Connector::Fiserv => Ok(ConnectorEnum::Old(Box::new(&connector::Fiserv))), enums::Connector::Fiserv => {
Ok(ConnectorEnum::Old(Box::new(connector::Fiserv::new())))
}
enums::Connector::Fiservemea => { enums::Connector::Fiservemea => {
Ok(ConnectorEnum::Old(Box::new(connector::Fiservemea::new()))) Ok(ConnectorEnum::Old(Box::new(connector::Fiservemea::new())))
} }
@ -453,7 +455,9 @@ impl ConnectorData {
Ok(ConnectorEnum::Old(Box::new(&connector::Gocardless))) Ok(ConnectorEnum::Old(Box::new(&connector::Gocardless)))
} }
// enums::Connector::Hipay => Ok(ConnectorEnum::Old(Box::new(connector::Hipay))), // enums::Connector::Hipay => Ok(ConnectorEnum::Old(Box::new(connector::Hipay))),
enums::Connector::Helcim => Ok(ConnectorEnum::Old(Box::new(&connector::Helcim))), enums::Connector::Helcim => {
Ok(ConnectorEnum::Old(Box::new(connector::Helcim::new())))
}
enums::Connector::Iatapay => { enums::Connector::Iatapay => {
Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new()))) Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new())))
} }

View File

@ -16,7 +16,7 @@ impl utils::Connector for FiservTest {
fn get_data(&self) -> types::api::ConnectorData { fn get_data(&self) -> types::api::ConnectorData {
use router::connector::Fiserv; use router::connector::Fiserv;
utils::construct_connector_data_old( utils::construct_connector_data_old(
Box::new(&Fiserv), Box::new(Fiserv::new()),
types::Connector::Fiserv, types::Connector::Fiserv,
types::api::GetToken::Connector, types::api::GetToken::Connector,
None, None,

View File

@ -11,7 +11,7 @@ impl utils::Connector for HelcimTest {
fn get_data(&self) -> types::api::ConnectorData { fn get_data(&self) -> types::api::ConnectorData {
use router::connector::Helcim; use router::connector::Helcim;
utils::construct_connector_data_old( utils::construct_connector_data_old(
Box::new(&Helcim), Box::new(Helcim::new()),
types::Connector::Helcim, types::Connector::Helcim,
types::api::GetToken::Connector, types::api::GetToken::Connector,
None, None,