refactor(connector): added amount framework to paypal, payouts and routing (#4865)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com>
Co-authored-by: Narayan Bhat <narayan.bhat@juspay.in>
This commit is contained in:
Sahkal Poddar
2024-06-28 12:01:15 +05:30
committed by GitHub
parent 82a75da314
commit b08ce22108
34 changed files with 204 additions and 181 deletions

View File

@ -169,7 +169,7 @@ impl From<payout_models::PayoutCreateResponse> for StripePayoutResponse {
fn from(res: payout_models::PayoutCreateResponse) -> Self {
Self {
id: res.payout_id,
amount: res.amount,
amount: res.amount.get_amount_as_i64(),
currency: res.currency.to_string(),
payout_type: res.payout_type,
status: StripePayoutStatus::from(res.status),

View File

@ -1,8 +1,12 @@
pub mod transformers;
use std::fmt::{Debug, Write};
use std::fmt::Write;
use base64::Engine;
use common_utils::{ext_traits::ByteSliceExt, request::RequestContent};
use common_utils::{
ext_traits::ByteSliceExt,
request::RequestContent,
types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector},
};
use diesel_models::enums;
use error_stack::ResultExt;
use masking::{ExposeInterface, PeekInterface, Secret};
@ -40,8 +44,18 @@ use crate::{
utils::BytesExt,
};
#[derive(Debug, Clone)]
pub struct Paypal;
#[derive(Clone)]
pub struct Paypal {
amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
}
impl Paypal {
pub fn new() -> &'static Self {
&Self {
amount_converter: &StringMajorUnitForConnector,
}
}
}
impl api::Payment for Paypal {}
impl api::PaymentSession for Paypal {}
@ -103,7 +117,7 @@ impl Paypal {
let errors_list = response.details.unwrap_or_default();
let option_error_code_message =
connector_utils::get_error_code_error_message_based_on_priority(
Self.clone(),
self.clone(),
errors_list
.into_iter()
.map(|errors| errors.into())
@ -274,7 +288,7 @@ impl ConnectorCommon for Paypal {
let errors_list = response.details.unwrap_or_default();
let option_error_code_message =
connector_utils::get_error_code_error_message_based_on_priority(
Self.clone(),
self.clone(),
errors_list
.into_iter()
.map(|errors| errors.into())
@ -458,12 +472,12 @@ impl ConnectorIntegration<api::PoFulfill, types::PayoutsData, types::PayoutsResp
req: &types::PayoutsRouterData<api::PoFulfill>,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = paypal::PaypalRouterData::try_from((
&self.get_currency_unit(),
let amount = connector_utils::convert_amount(
self.amount_converter,
req.request.minor_amount,
req.request.destination_currency,
req.request.amount,
req,
))?;
)?;
let connector_router_data = paypal::PaypalRouterData::try_from((amount, req))?;
let connector_req = paypal::PaypalFulfillRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
@ -588,12 +602,12 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = paypal::PaypalRouterData::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 = paypal::PaypalRouterData::try_from((amount, req))?;
let connector_req = paypal::PaypalPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
@ -1093,12 +1107,12 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = paypal::PaypalRouterData::try_from((
&self.get_currency_unit(),
let amount_to_capture = 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 = paypal::PaypalRouterData::try_from((amount_to_capture, req))?;
let connector_req = paypal::PaypalPaymentsCaptureRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
@ -1260,12 +1274,12 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = paypal::PaypalRouterData::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 = paypal::PaypalRouterData::try_from((amount, req))?;
let connector_req = paypal::PaypalRefundRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
@ -1614,7 +1628,7 @@ impl api::IncomingWebhook for Paypal {
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(api::disputes::DisputePayload {
amount: connector_utils::to_currency_lower_unit(
payload.dispute_amount.value,
payload.dispute_amount.value.get_amount_as_string(),
payload.dispute_amount.currency_code,
)?,
currency: payload.dispute_amount.currency_code.to_string(),

View File

@ -1,8 +1,8 @@
use api_models::enums;
use base64::Engine;
use common_utils::errors::CustomResult;
#[cfg(feature = "payouts")]
use common_utils::pii::Email;
use common_utils::{errors::CustomResult, types::StringMajorUnit};
use error_stack::ResultExt;
use masking::{ExposeInterface, Secret};
use serde::{Deserialize, Serialize};
@ -27,23 +27,13 @@ use crate::{
#[derive(Debug, Serialize)]
pub struct PaypalRouterData<T> {
pub amount: String,
pub amount: StringMajorUnit,
pub router_data: T,
}
impl<T> TryFrom<(&api::CurrencyUnit, types::storage::enums::Currency, i64, T)>
for PaypalRouterData<T>
{
impl<T> TryFrom<(StringMajorUnit, T)> for PaypalRouterData<T> {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
(currency_unit, currency, amount, item): (
&api::CurrencyUnit,
types::storage::enums::Currency,
i64,
T,
),
) -> Result<Self, Self::Error> {
let amount = utils::get_amount_as_string(currency_unit, amount, currency)?;
fn try_from((amount, item): (StringMajorUnit, T)) -> Result<Self, Self::Error> {
Ok(Self {
amount,
router_data: item,
@ -76,13 +66,13 @@ pub enum PaypalPaymentIntent {
#[derive(Default, Debug, Clone, Serialize, Eq, PartialEq, Deserialize)]
pub struct OrderAmount {
pub currency_code: storage_enums::Currency,
pub value: String,
pub value: StringMajorUnit,
}
#[derive(Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct OrderRequestAmount {
pub currency_code: storage_enums::Currency,
pub value: String,
pub value: StringMajorUnit,
pub breakdown: AmountBreakdown,
}
@ -90,11 +80,11 @@ impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for OrderReque
fn from(item: &PaypalRouterData<&types::PaymentsAuthorizeRouterData>) -> Self {
Self {
currency_code: item.router_data.request.currency,
value: item.amount.to_owned(),
value: item.amount.clone(),
breakdown: AmountBreakdown {
item_total: OrderAmount {
currency_code: item.router_data.request.currency,
value: item.amount.to_owned(),
value: item.amount.clone(),
},
},
}
@ -140,7 +130,7 @@ impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ItemDetail
quantity: 1,
unit_amount: OrderAmount {
currency_code: item.router_data.request.currency,
value: item.amount.to_string(),
value: item.amount.clone(),
},
}
}
@ -1564,7 +1554,7 @@ pub enum PaypalPayoutDataType {
#[cfg(feature = "payouts")]
#[derive(Debug, Serialize)]
pub struct PayoutAmount {
value: String,
value: StringMajorUnit,
currency: storage_enums::Currency,
}
@ -1593,7 +1583,7 @@ impl TryFrom<&PaypalRouterData<&types::PayoutsRouterData<api::PoFulfill>>> for P
item: &PaypalRouterData<&types::PayoutsRouterData<api::PoFulfill>>,
) -> Result<Self, Self::Error> {
let amount = PayoutAmount {
value: item.amount.to_owned(),
value: item.amount.clone(),
currency: item.router_data.request.destination_currency,
};
@ -1726,7 +1716,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsCaptureRouterData>>
) -> Result<Self, Self::Error> {
let amount = OrderAmount {
currency_code: item.router_data.request.currency,
value: item.amount.to_owned(),
value: item.amount.clone(),
};
Ok(Self {
amount,
@ -1907,7 +1897,7 @@ impl<F> TryFrom<&PaypalRouterData<&types::RefundsRouterData<F>>> for PaypalRefun
Ok(Self {
amount: OrderAmount {
currency_code: item.router_data.request.currency,
value: item.amount.to_owned(),
value: item.amount.clone(),
},
})
}

View File

@ -24,6 +24,7 @@ use common_utils::{
consts,
ext_traits::{AsyncExt, Encode, StringExt, ValueExt},
generate_id, id_type,
types::MinorUnit,
};
use diesel_models::{
business_profile::BusinessProfile, encryption::Encryption, enums as storage_enums,
@ -2862,11 +2863,7 @@ pub async fn filter_payment_methods(
&payment_method_type_info,
req.installment_payment_enabled,
)
&& filter_amount_based(
&payment_method_type_info,
req.amount
.map(|minor_amount| minor_amount.get_amount_as_i64()),
)
&& filter_amount_based(&payment_method_type_info, req.amount)
{
let mut payment_method_object = payment_method_type_info;
@ -3108,6 +3105,19 @@ fn filter_pm_based_on_capture_method_used(
.unwrap_or(true)
}
fn filter_amount_based(
payment_method: &RequestPaymentMethodTypes,
amount: Option<MinorUnit>,
) -> bool {
let min_check = amount
.and_then(|amt| payment_method.minimum_amount.map(|min_amt| amt >= min_amt))
.unwrap_or(true);
let max_check = amount
.and_then(|amt| payment_method.maximum_amount.map(|max_amt| amt <= max_amt))
.unwrap_or(true);
(min_check && max_check) || amount == Some(MinorUnit::zero())
}
fn card_network_filter(
country: &Option<api_enums::CountryAlpha2>,
currency: Option<api_enums::Currency>,
@ -3279,32 +3289,6 @@ fn filter_disabled_enum_based<T: Eq + std::hash::Hash + Clone>(
}
}
fn filter_amount_based(payment_method: &RequestPaymentMethodTypes, amount: Option<i64>) -> bool {
let min_check = amount
.and_then(|amt| {
payment_method
.minimum_amount
.map(|min_amt| amt >= min_amt.into())
})
.unwrap_or(true);
let max_check = amount
.and_then(|amt| {
payment_method
.maximum_amount
.map(|max_amt| amt <= max_amt.into())
})
.unwrap_or(true);
// let min_check = match (amount, payment_method.minimum_amount) {
// (Some(amt), Some(min_amt)) => amt >= min_amt,
// (_, _) => true,
// };
// let max_check = match (amount, payment_method.maximum_amount) {
// (Some(amt), Some(max_amt)) => amt <= max_amt,
// (_, _) => true,
// };
(min_check && max_check) || amount == Some(0)
}
fn filter_pm_based_on_allowed_types(
allowed_types: Option<&Vec<api_enums::PaymentMethodType>>,
payment_method_type: &api_enums::PaymentMethodType,
@ -3360,10 +3344,10 @@ fn filter_payment_amount_based(
payment_intent: &storage::PaymentIntent,
pm: &RequestPaymentMethodTypes,
) -> bool {
let amount = payment_intent.amount.get_amount_as_i64();
(pm.maximum_amount.map_or(true, |amt| amount <= amt.into())
&& pm.minimum_amount.map_or(true, |amt| amount >= amt.into()))
|| payment_intent.amount.get_amount_as_i64() == 0
let amount = payment_intent.amount;
(pm.maximum_amount.map_or(true, |amt| amount <= amt)
&& pm.minimum_amount.map_or(true, |amt| amount >= amt))
|| payment_intent.amount == MinorUnit::zero()
}
async fn filter_payment_mandate_based(

View File

@ -211,7 +211,7 @@ where
};
let payment_input = dsl_inputs::PaymentInput {
amount: payment_data.payment_intent.amount.get_amount_as_i64(),
amount: payment_data.payment_intent.amount,
card_bin: payment_data
.payment_method_data
.as_ref()
@ -904,7 +904,7 @@ pub async fn perform_session_flow_routing(
};
let payment_input = dsl_inputs::PaymentInput {
amount: session_input.payment_intent.amount.get_amount_as_i64(),
amount: session_input.payment_intent.amount,
currency: session_input
.payment_intent
.currency
@ -1138,7 +1138,7 @@ pub fn make_dsl_input_for_surcharge(
payment_type: None,
};
let payment_input = dsl_inputs::PaymentInput {
amount: payment_attempt.amount.get_amount_as_i64(),
amount: payment_attempt.amount,
// currency is always populated in payment_attempt during payment create
currency: payment_attempt
.currency

View File

@ -4,6 +4,7 @@ use api_models::payouts;
use common_utils::{
ext_traits::{Encode, OptionExt},
link_utils,
types::{AmountConvertor, StringMajorUnitForConnector},
};
use diesel_models::PayoutLinkUpdate;
use error_stack::ResultExt;
@ -102,9 +103,9 @@ pub async fn initiate_payout_link(
// Initiate Payout link flow
(_, link_utils::PayoutLinkStatus::Initiated) => {
let customer_id = link_data.customer_id;
let amount = payout
.destination_currency
.to_currency_base_unit(payout.amount)
let required_amount_type = StringMajorUnitForConnector;
let amount = required_amount_type
.convert(payout.amount, payout.destination_currency)
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;
// Fetch customer
let customer = db

View File

@ -1919,7 +1919,7 @@ pub async fn response_handler(
let response = api::PayoutCreateResponse {
payout_id: payouts.payout_id.to_owned(),
merchant_id: merchant_account.merchant_id.to_owned(),
amount: payouts.amount.to_owned(),
amount: payouts.amount,
currency: payouts.destination_currency.to_owned(),
connector: payout_attempt.connector.to_owned(),
payout_type: payouts.payout_type.to_owned(),
@ -2045,7 +2045,7 @@ pub async fn payout_create_db_entries(
consts::ID_LENGTH,
format!("payout_{payout_id}_secret").as_str(),
);
let amount = MinorUnit::from(req.amount.unwrap_or(api::Amount::Zero)).get_amount_as_i64();
let amount = MinorUnit::from(req.amount.unwrap_or(api::Amount::Zero));
let payouts_req = storage::PayoutsNew {
payout_id: payout_id.to_string(),
merchant_id: merchant_id.to_string(),

View File

@ -959,8 +959,7 @@ pub async fn update_payouts_and_payout_attempt(
// Update DB with new data
let payouts = payout_data.payouts.to_owned();
let amount = MinorUnit::from(req.amount.unwrap_or(MinorUnit::new(payouts.amount).into()))
.get_amount_as_i64();
let amount = MinorUnit::from(req.amount.unwrap_or(payouts.amount.into()));
let updated_payouts = storage::PayoutsUpdate::Update {
amount,
destination_currency: req

View File

@ -170,7 +170,8 @@ pub async fn construct_payout_router_data<'a, F>(
payment_method_status: None,
request: types::PayoutsData {
payout_id: payouts.payout_id.to_owned(),
amount: payouts.amount,
amount: payouts.amount.get_amount_as_i64(),
minor_amount: payouts.amount,
connector_payout_id: payout_attempt.connector_payout_id.clone(),
destination_currency: payouts.destination_currency,
source_currency: payouts.source_currency,

View File

@ -1,4 +1,4 @@
use common_utils::{id_type, pii};
use common_utils::{id_type, pii, types::MinorUnit};
use diesel_models::enums as storage_enums;
use hyperswitch_domain_models::payouts::{payout_attempt::PayoutAttempt, payouts::Payouts};
use time::OffsetDateTime;
@ -13,7 +13,7 @@ pub struct KafkaPayout<'a> {
pub profile_id: &'a String,
pub payout_method_id: Option<&'a String>,
pub payout_type: Option<storage_enums::PayoutType>,
pub amount: i64,
pub amount: MinorUnit,
pub destination_currency: storage_enums::Currency,
pub source_currency: storage_enums::Currency,
pub description: Option<&'a String>,

View File

@ -477,7 +477,9 @@ impl ConnectorData {
enums::Connector::Nexinets => {
Ok(ConnectorEnum::Old(Box::new(&connector::Nexinets)))
}
enums::Connector::Paypal => Ok(ConnectorEnum::Old(Box::new(&connector::Paypal))),
enums::Connector::Paypal => {
Ok(ConnectorEnum::Old(Box::new(connector::Paypal::new())))
}
enums::Connector::Trustpay => {
Ok(ConnectorEnum::Old(Box::new(connector::Trustpay::new())))
}