mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 13:30:39 +08:00
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:
@ -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),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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())))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user