mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(core): [Payouts] Add billing address to payout list (#5004)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -6,6 +6,8 @@ pub mod transformers;
|
||||
pub mod validator;
|
||||
use std::{collections::HashSet, vec::IntoIter};
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
use api_models::payments as payment_enums;
|
||||
use api_models::{self, enums as api_enums, payouts::PayoutLinkResponse};
|
||||
#[cfg(feature = "payout_retry")]
|
||||
use common_enums::PayoutRetryType;
|
||||
@ -33,6 +35,8 @@ use time::Duration;
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
use crate::types::domain::behaviour::Conversion;
|
||||
#[cfg(feature = "olap")]
|
||||
use crate::types::PayoutActionData;
|
||||
use crate::{
|
||||
core::{
|
||||
errors::{
|
||||
@ -770,7 +774,9 @@ pub async fn payouts_list_core(
|
||||
.to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
|
||||
let payouts = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, payouts);
|
||||
|
||||
let collected_futures = payouts.into_iter().map(|payout| async {
|
||||
let mut pi_pa_tuple_vec = PayoutActionData::new();
|
||||
|
||||
for payout in payouts {
|
||||
match db
|
||||
.find_payout_attempt_by_merchant_id_payout_attempt_id(
|
||||
merchant_id,
|
||||
@ -778,73 +784,80 @@ pub async fn payouts_list_core(
|
||||
storage_enums::MerchantStorageScheme::PostgresOnly,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
{
|
||||
Ok(ref payout_attempt) => match payout.customer_id.clone() {
|
||||
Some(ref customer_id) => {
|
||||
Ok(payout_attempt) => {
|
||||
let domain_customer = match payout.customer_id.clone() {
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
||||
match db
|
||||
Some(customer_id) => db
|
||||
.find_customer_by_customer_id_merchant_id(
|
||||
&(&state).into(),
|
||||
customer_id,
|
||||
&customer_id,
|
||||
merchant_id,
|
||||
&key_store,
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(customer) => Ok((payout, payout_attempt.to_owned(), Some(customer))),
|
||||
Err(err) => {
|
||||
.map_err(|err| {
|
||||
let err_msg = format!(
|
||||
"failed while fetching customer for customer_id - {:?}",
|
||||
customer_id
|
||||
);
|
||||
logger::warn!(?err, err_msg);
|
||||
if err.current_context().is_db_not_found() {
|
||||
Ok((payout, payout_attempt.to_owned(), None))
|
||||
} else {
|
||||
Err(err
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable(err_msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Ok((payout.to_owned(), payout_attempt.to_owned(), None)),
|
||||
},
|
||||
})
|
||||
.ok(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let payout_id_as_payment_id_type =
|
||||
common_utils::id_type::PaymentId::wrap(payout.payout_id.clone())
|
||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "payout_id contains invalid data".to_string(),
|
||||
})
|
||||
.attach_printable("Error converting payout_id to PaymentId type")?;
|
||||
|
||||
let payment_addr = payment_helpers::create_or_find_address_for_payment_by_request(
|
||||
&state,
|
||||
None,
|
||||
payout.address_id.as_deref(),
|
||||
merchant_id,
|
||||
payout.customer_id.as_ref(),
|
||||
&key_store,
|
||||
&payout_id_as_payment_id_type,
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.transpose()
|
||||
.and_then(|addr| {
|
||||
addr.map_err(|err| {
|
||||
let err_msg = format!(
|
||||
"billing_address missing for address_id : {:?}",
|
||||
payout.address_id
|
||||
);
|
||||
logger::warn!(?err, err_msg);
|
||||
})
|
||||
.ok()
|
||||
.map(payment_enums::Address::foreign_from)
|
||||
});
|
||||
|
||||
pi_pa_tuple_vec.push((
|
||||
payout.to_owned(),
|
||||
payout_attempt.to_owned(),
|
||||
domain_customer,
|
||||
payment_addr,
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
let err_msg = format!(
|
||||
"failed while fetching payout_attempt for payout_id - {}",
|
||||
payout.payout_id.clone(),
|
||||
"failed while fetching payout_attempt for payout_id - {:?}",
|
||||
payout.payout_id
|
||||
);
|
||||
logger::warn!(?err, err_msg);
|
||||
Err(err
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable(err_msg))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let pi_pa_tuple_vec: Result<
|
||||
Vec<(
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<domain::Customer>,
|
||||
)>,
|
||||
_,
|
||||
> = join_all(collected_futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<
|
||||
Vec<(
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<domain::Customer>,
|
||||
)>,
|
||||
_,
|
||||
>>();
|
||||
}
|
||||
|
||||
let data: Vec<api::PayoutCreateResponse> = pi_pa_tuple_vec
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.into_iter()
|
||||
.map(ForeignFrom::foreign_from)
|
||||
.collect();
|
||||
@ -874,6 +887,7 @@ pub async fn payouts_filtered_list_core(
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<diesel_models::Customer>,
|
||||
Option<diesel_models::Address>,
|
||||
)> = db
|
||||
.filter_payouts_and_attempts(
|
||||
merchant_account.get_id(),
|
||||
@ -883,30 +897,50 @@ pub async fn payouts_filtered_list_core(
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
|
||||
let list = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, list);
|
||||
let data: Vec<api::PayoutCreateResponse> = join_all(list.into_iter().map(|(p, pa, c)| async {
|
||||
let domain_cust = c
|
||||
.async_and_then(|cust| async {
|
||||
domain::Customer::convert_back(
|
||||
&(&state).into(),
|
||||
cust,
|
||||
&key_store.key,
|
||||
key_store.merchant_id.clone().into(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
let msg = format!("failed to convert customer for id: {:?}", p.customer_id);
|
||||
logger::warn!(?err, msg);
|
||||
let data: Vec<api::PayoutCreateResponse> =
|
||||
join_all(list.into_iter().map(|(p, pa, customer, address)| async {
|
||||
let customer: Option<domain::Customer> = customer
|
||||
.async_and_then(|cust| async {
|
||||
domain::Customer::convert_back(
|
||||
&(&state).into(),
|
||||
cust,
|
||||
&key_store.key,
|
||||
key_store.merchant_id.clone().into(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
let msg = format!("failed to convert customer for id: {:?}", p.customer_id);
|
||||
logger::warn!(?err, msg);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.await;
|
||||
Some((p, pa, domain_cust))
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(ForeignFrom::foreign_from)
|
||||
.collect();
|
||||
.await;
|
||||
|
||||
let payout_addr: Option<payment_enums::Address> = address
|
||||
.async_and_then(|addr| async {
|
||||
domain::Address::convert_back(
|
||||
&(&state).into(),
|
||||
addr,
|
||||
&key_store.key,
|
||||
key_store.merchant_id.clone().into(),
|
||||
)
|
||||
.await
|
||||
.map(ForeignFrom::foreign_from)
|
||||
.map_err(|err| {
|
||||
let msg = format!("failed to convert address for id: {:?}", p.address_id);
|
||||
logger::warn!(?err, msg);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.await;
|
||||
|
||||
Some((p, pa, customer, payout_addr))
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(ForeignFrom::foreign_from)
|
||||
.collect();
|
||||
|
||||
let active_payout_ids = db
|
||||
.filter_active_payout_ids_by_constraints(merchant_account.get_id(), &constraints)
|
||||
|
||||
@ -9,7 +9,7 @@ use common_utils::link_utils::EnabledPaymentMethod;
|
||||
))]
|
||||
use crate::types::transformers::ForeignInto;
|
||||
#[cfg(feature = "olap")]
|
||||
use crate::types::{domain, storage};
|
||||
use crate::types::{api::payments, domain, storage};
|
||||
use crate::{
|
||||
settings::PayoutRequiredFields,
|
||||
types::{api, transformers::ForeignFrom},
|
||||
@ -21,6 +21,7 @@ impl
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<domain::Customer>,
|
||||
Option<payments::Address>,
|
||||
)> for api::PayoutCreateResponse
|
||||
{
|
||||
fn foreign_from(
|
||||
@ -28,6 +29,7 @@ impl
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<domain::Customer>,
|
||||
Option<payments::Address>,
|
||||
),
|
||||
) -> Self {
|
||||
todo!()
|
||||
@ -44,6 +46,7 @@ impl
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<domain::Customer>,
|
||||
Option<payments::Address>,
|
||||
)> for api::PayoutCreateResponse
|
||||
{
|
||||
fn foreign_from(
|
||||
@ -51,9 +54,10 @@ impl
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<domain::Customer>,
|
||||
Option<payments::Address>,
|
||||
),
|
||||
) -> Self {
|
||||
let (payout, payout_attempt, customer) = item;
|
||||
let (payout, payout_attempt, customer, address) = item;
|
||||
let attempt = api::PayoutAttemptResponse {
|
||||
attempt_id: payout_attempt.payout_attempt_id,
|
||||
status: payout_attempt.status,
|
||||
@ -95,7 +99,7 @@ impl
|
||||
connector_transaction_id: attempt.connector_transaction_id.clone(),
|
||||
priority: payout.priority,
|
||||
attempts: Some(vec![attempt]),
|
||||
billing: None,
|
||||
billing: address,
|
||||
client_secret: None,
|
||||
payout_link: None,
|
||||
email: customer
|
||||
|
||||
@ -1521,7 +1521,7 @@ impl GetProfileId for storage::Payouts {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "payouts")]
|
||||
impl<T, F> GetProfileId for (storage::Payouts, T, F) {
|
||||
impl<T, F, R> GetProfileId for (storage::Payouts, T, F, R) {
|
||||
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
|
||||
self.0.get_profile_id()
|
||||
}
|
||||
|
||||
@ -2073,6 +2073,7 @@ impl PayoutsInterface for KafkaStore {
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<diesel_models::Customer>,
|
||||
Option<diesel_models::Address>,
|
||||
)>,
|
||||
errors::DataStorageError,
|
||||
> {
|
||||
|
||||
@ -207,6 +207,14 @@ pub type PayoutsRouterData<F> = RouterData<F, PayoutsData, PayoutsResponseData>;
|
||||
pub type PayoutsResponseRouterData<F, R> =
|
||||
ResponseRouterData<F, R, PayoutsData, PayoutsResponseData>;
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
pub type PayoutActionData = Vec<(
|
||||
storage::Payouts,
|
||||
storage::PayoutAttempt,
|
||||
Option<domain::Customer>,
|
||||
Option<api_models::payments::Address>,
|
||||
)>;
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
pub trait PayoutIndividualDetailsExt {
|
||||
type Error;
|
||||
|
||||
@ -789,6 +789,52 @@ impl<'a> From<&'a domain::Address> for api_types::Address {
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignFrom<domain::Address> for api_types::Address {
|
||||
fn foreign_from(address: domain::Address) -> Self {
|
||||
// If all the fields of address are none, then pass the address as None
|
||||
let address_details = if address.city.is_none()
|
||||
&& address.line1.is_none()
|
||||
&& address.line2.is_none()
|
||||
&& address.line3.is_none()
|
||||
&& address.state.is_none()
|
||||
&& address.country.is_none()
|
||||
&& address.zip.is_none()
|
||||
&& address.first_name.is_none()
|
||||
&& address.last_name.is_none()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(api_types::AddressDetails {
|
||||
city: address.city.clone(),
|
||||
country: address.country,
|
||||
line1: address.line1.clone().map(Encryptable::into_inner),
|
||||
line2: address.line2.clone().map(Encryptable::into_inner),
|
||||
line3: address.line3.clone().map(Encryptable::into_inner),
|
||||
state: address.state.clone().map(Encryptable::into_inner),
|
||||
zip: address.zip.clone().map(Encryptable::into_inner),
|
||||
first_name: address.first_name.clone().map(Encryptable::into_inner),
|
||||
last_name: address.last_name.clone().map(Encryptable::into_inner),
|
||||
})
|
||||
};
|
||||
|
||||
// If all the fields of phone are none, then pass the phone as None
|
||||
let phone_details = if address.phone_number.is_none() && address.country_code.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(api_types::PhoneDetails {
|
||||
number: address.phone_number.clone().map(Encryptable::into_inner),
|
||||
country_code: address.country_code.clone(),
|
||||
})
|
||||
};
|
||||
|
||||
Self {
|
||||
address: address_details,
|
||||
phone: phone_details,
|
||||
email: address.email.clone().map(pii::Email::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
ForeignFrom<(
|
||||
diesel_models::api_keys::ApiKey,
|
||||
|
||||
Reference in New Issue
Block a user