refactor(connector): add amount conversion framework to Shift4 (#6250)

This commit is contained in:
Anish Kacham
2024-10-24 17:07:04 +05:30
committed by GitHub
parent c7c1e1adab
commit fbe395198a
4 changed files with 103 additions and 43 deletions

View File

@ -1,8 +1,10 @@
pub mod transformers; pub mod transformers;
use std::fmt::Debug; use common_utils::{
ext_traits::ByteSliceExt,
use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; request::RequestContent,
types::{AmountConvertor, MinorUnit, MinorUnitForConnector},
};
use diesel_models::enums; use diesel_models::enums;
use error_stack::{report, ResultExt}; use error_stack::{report, ResultExt};
use transformers as shift4; use transformers as shift4;
@ -27,8 +29,18 @@ use crate::{
utils::BytesExt, utils::BytesExt,
}; };
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct Shift4; pub struct Shift4 {
amount_converter: &'static (dyn AmountConvertor<Output = MinorUnit> + Sync),
}
impl Shift4 {
pub fn new() -> &'static Self {
&Self {
amount_converter: &MinorUnitForConnector,
}
}
}
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Shift4 impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Shift4
where where
@ -206,7 +218,13 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req: &types::PaymentsAuthorizeRouterData, req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors, _connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; let amount = connector_utils::convert_amount(
self.amount_converter,
req.request.minor_amount,
req.request.currency,
)?;
let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?;
let connector_req = shift4::Shift4PaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }
@ -470,7 +488,21 @@ impl
req: &types::PaymentsPreProcessingRouterData, req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors, _connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; let amount = connector_utils::convert_amount(
self.amount_converter,
req.request.minor_amount.ok_or_else(|| {
errors::ConnectorError::MissingRequiredField {
field_name: "minor_amount",
}
})?,
req.request
.currency
.ok_or_else(|| errors::ConnectorError::MissingRequiredField {
field_name: "currency",
})?,
)?;
let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?;
let connector_req = shift4::Shift4PaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }
@ -559,7 +591,13 @@ impl
req: &types::PaymentsCompleteAuthorizeRouterData, req: &types::PaymentsCompleteAuthorizeRouterData,
_connectors: &settings::Connectors, _connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; let amount = connector_utils::convert_amount(
self.amount_converter,
req.request.minor_amount,
req.request.currency,
)?;
let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?;
let connector_req = shift4::Shift4PaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }
@ -641,7 +679,13 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
req: &types::RefundsRouterData<api::Execute>, req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors, _connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = shift4::Shift4RefundRequest::try_from(req)?; let amount = connector_utils::convert_amount(
self.amount_converter,
req.request.minor_refund_amount,
req.request.currency,
)?;
let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?;
let connector_req = shift4::Shift4RefundRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req))) Ok(RequestContent::Json(Box::new(connector_req)))
} }

View File

@ -1,6 +1,6 @@
use api_models::payments; use api_models::payments;
use cards::CardNumber; use cards::CardNumber;
use common_utils::pii::SecretSerdeValue; use common_utils::{pii::SecretSerdeValue, types::MinorUnit};
use error_stack::ResultExt; use error_stack::ResultExt;
use masking::Secret; use masking::Secret;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -23,11 +23,25 @@ trait Shift4AuthorizePreprocessingCommon {
fn get_router_return_url(&self) -> Option<String>; fn get_router_return_url(&self) -> Option<String>;
fn get_email_optional(&self) -> Option<pii::Email>; fn get_email_optional(&self) -> Option<pii::Email>;
fn get_complete_authorize_url(&self) -> Option<String>; fn get_complete_authorize_url(&self) -> Option<String>;
fn get_amount_required(&self) -> Result<i64, Error>;
fn get_currency_required(&self) -> Result<diesel_models::enums::Currency, Error>; fn get_currency_required(&self) -> Result<diesel_models::enums::Currency, Error>;
fn get_payment_method_data_required(&self) -> Result<domain::PaymentMethodData, Error>; fn get_payment_method_data_required(&self) -> Result<domain::PaymentMethodData, Error>;
} }
pub struct Shift4RouterData<T> {
pub amount: MinorUnit,
pub router_data: T,
}
impl<T> TryFrom<(MinorUnit, T)> for Shift4RouterData<T> {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from((amount, item): (MinorUnit, T)) -> Result<Self, Self::Error> {
Ok(Self {
amount,
router_data: item,
})
}
}
impl Shift4AuthorizePreprocessingCommon for types::PaymentsAuthorizeData { impl Shift4AuthorizePreprocessingCommon for types::PaymentsAuthorizeData {
fn get_email_optional(&self) -> Option<pii::Email> { fn get_email_optional(&self) -> Option<pii::Email> {
self.email.clone() self.email.clone()
@ -37,10 +51,6 @@ impl Shift4AuthorizePreprocessingCommon for types::PaymentsAuthorizeData {
self.complete_authorize_url.clone() self.complete_authorize_url.clone()
} }
fn get_amount_required(&self) -> Result<i64, error_stack::Report<errors::ConnectorError>> {
Ok(self.amount)
}
fn get_currency_required( fn get_currency_required(
&self, &self,
) -> Result<diesel_models::enums::Currency, error_stack::Report<errors::ConnectorError>> { ) -> Result<diesel_models::enums::Currency, error_stack::Report<errors::ConnectorError>> {
@ -70,10 +80,6 @@ impl Shift4AuthorizePreprocessingCommon for types::PaymentsPreProcessingData {
self.complete_authorize_url.clone() self.complete_authorize_url.clone()
} }
fn get_amount_required(&self) -> Result<i64, Error> {
self.get_amount()
}
fn get_currency_required(&self) -> Result<diesel_models::enums::Currency, Error> { fn get_currency_required(&self) -> Result<diesel_models::enums::Currency, Error> {
self.get_currency() self.get_currency()
} }
@ -95,7 +101,7 @@ impl Shift4AuthorizePreprocessingCommon for types::PaymentsPreProcessingData {
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct Shift4PaymentsRequest { pub struct Shift4PaymentsRequest {
amount: String, amount: MinorUnit,
currency: enums::Currency, currency: enums::Currency,
captured: bool, captured: bool,
#[serde(flatten)] #[serde(flatten)]
@ -195,19 +201,19 @@ pub enum CardPayment {
CardToken(Secret<String>), CardToken(Secret<String>),
} }
impl<T, Req> TryFrom<&types::RouterData<T, Req, types::PaymentsResponseData>> impl<T, Req> TryFrom<&Shift4RouterData<&types::RouterData<T, Req, types::PaymentsResponseData>>>
for Shift4PaymentsRequest for Shift4PaymentsRequest
where where
Req: Shift4AuthorizePreprocessingCommon, Req: Shift4AuthorizePreprocessingCommon,
{ {
type Error = Error; type Error = Error;
fn try_from( fn try_from(
item: &types::RouterData<T, Req, types::PaymentsResponseData>, item: &Shift4RouterData<&types::RouterData<T, Req, types::PaymentsResponseData>>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let submit_for_settlement = item.request.is_automatic_capture()?; let submit_for_settlement = item.router_data.request.is_automatic_capture()?;
let amount = item.request.get_amount_required()?.to_string(); let amount = item.amount.to_owned();
let currency = item.request.get_currency_required()?; let currency = item.router_data.request.get_currency_required()?;
let payment_method = Shift4PaymentMethod::try_from(item)?; let payment_method = Shift4PaymentMethod::try_from(item.router_data)?;
Ok(Self { Ok(Self {
amount, amount,
currency, currency,
@ -437,27 +443,33 @@ where
} }
} }
impl<T> TryFrom<&types::RouterData<T, types::CompleteAuthorizeData, types::PaymentsResponseData>> impl<T>
for Shift4PaymentsRequest TryFrom<
&Shift4RouterData<
&types::RouterData<T, types::CompleteAuthorizeData, types::PaymentsResponseData>,
>,
> for Shift4PaymentsRequest
{ {
type Error = Error; type Error = Error;
fn try_from( fn try_from(
item: &types::RouterData<T, types::CompleteAuthorizeData, types::PaymentsResponseData>, item: &Shift4RouterData<
&types::RouterData<T, types::CompleteAuthorizeData, types::PaymentsResponseData>,
>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
match &item.request.payment_method_data { match &item.router_data.request.payment_method_data {
Some(domain::PaymentMethodData::Card(_)) => { Some(domain::PaymentMethodData::Card(_)) => {
let card_token: Shift4CardToken = let card_token: Shift4CardToken =
to_connector_meta(item.request.connector_meta.clone())?; to_connector_meta(item.router_data.request.connector_meta.clone())?;
Ok(Self { Ok(Self {
amount: item.request.amount.to_string(), amount: item.amount.to_owned(),
currency: item.request.currency, currency: item.router_data.request.currency,
payment_method: Shift4PaymentMethod::CardsNon3DSRequest(Box::new( payment_method: Shift4PaymentMethod::CardsNon3DSRequest(Box::new(
CardsNon3DSRequest { CardsNon3DSRequest {
card: CardPayment::CardToken(card_token.id), card: CardPayment::CardToken(card_token.id),
description: item.description.clone(), description: item.router_data.description.clone(),
}, },
)), )),
captured: item.request.is_auto_capture()?, captured: item.router_data.request.is_auto_capture()?,
}) })
} }
Some(domain::PaymentMethodData::Wallet(_)) Some(domain::PaymentMethodData::Wallet(_))
@ -687,7 +699,7 @@ pub struct Token {
#[derive(Default, Debug, Deserialize, Serialize)] #[derive(Default, Debug, Deserialize, Serialize)]
pub struct ThreeDSecureInfo { pub struct ThreeDSecureInfo {
pub amount: i64, pub amount: MinorUnit,
pub currency: String, pub currency: String,
pub enrolled: bool, pub enrolled: bool,
#[serde(rename = "liabilityShift")] #[serde(rename = "liabilityShift")]
@ -814,15 +826,17 @@ impl<T, F>
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Shift4RefundRequest { pub struct Shift4RefundRequest {
charge_id: String, charge_id: String,
amount: i64, amount: MinorUnit,
} }
impl<F> TryFrom<&types::RefundsRouterData<F>> for Shift4RefundRequest { impl<F> TryFrom<&Shift4RouterData<&types::RefundsRouterData<F>>> for Shift4RefundRequest {
type Error = Error; type Error = Error;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { fn try_from(
item: &Shift4RouterData<&types::RefundsRouterData<F>>,
) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
charge_id: item.request.connector_transaction_id.clone(), charge_id: item.router_data.request.connector_transaction_id.clone(),
amount: item.request.refund_amount, amount: item.amount.to_owned(),
}) })
} }
} }

View File

@ -470,7 +470,9 @@ impl ConnectorData {
Ok(ConnectorEnum::Old(Box::new(connector::Razorpay::new()))) Ok(ConnectorEnum::Old(Box::new(connector::Razorpay::new())))
} }
enums::Connector::Rapyd => Ok(ConnectorEnum::Old(Box::new(&connector::Rapyd))), enums::Connector::Rapyd => Ok(ConnectorEnum::Old(Box::new(&connector::Rapyd))),
enums::Connector::Shift4 => Ok(ConnectorEnum::Old(Box::new(&connector::Shift4))), enums::Connector::Shift4 => {
Ok(ConnectorEnum::Old(Box::new(connector::Shift4::new())))
}
enums::Connector::Square => Ok(ConnectorEnum::Old(Box::new(&connector::Square))), enums::Connector::Square => Ok(ConnectorEnum::Old(Box::new(&connector::Square))),
enums::Connector::Stax => Ok(ConnectorEnum::Old(Box::new(&connector::Stax))), enums::Connector::Stax => Ok(ConnectorEnum::Old(Box::new(&connector::Stax))),
enums::Connector::Stripe => { enums::Connector::Stripe => {

View File

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