feat(payout): add dynamic fields for payout links (#5764)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Kashif
2024-09-10 16:32:52 +05:30
committed by GitHub
parent 9b508a838d
commit f4ad6579cc
24 changed files with 1005 additions and 65 deletions

View File

@ -160,12 +160,13 @@ if (!isTestMode && !isFramed) {
theme: payoutDetails.theme,
collectorName: payoutDetails.merchant_name,
logo: payoutDetails.logo,
enabledPaymentMethods: payoutDetails.enabled_payment_methods,
enabledPaymentMethods: payoutDetails.enabled_payment_methods_with_required_fields,
returnUrl: payoutDetails.return_url,
sessionExpiry,
amount: payoutDetails.amount,
currency: payoutDetails.currency,
flow: "PayoutLinkInitiate",
formLayout: payoutDetails.form_layout,
};
payoutWidget = widgets.create("paymentMethodCollect", payoutOptions);

View File

@ -121,10 +121,10 @@ function renderStatusDetails(payoutDetails) {
"{{i18n_ref_id_text}}": payoutDetails.payout_id,
};
if (typeof payoutDetails.error_code === "string") {
resourceInfo["{{i18n_error_code_text}}"] = payoutDetails.error_code;
// resourceInfo["{{i18n_error_code_text}}"] = payoutDetails.error_code;
}
if (typeof payoutDetails.error_message === "string") {
resourceInfo["{{i18n_error_message}}"] = payoutDetails.error_message;
// resourceInfo["{{i18n_error_message}}"] = payoutDetails.error_message;
}
var resourceNode = document.createElement("div");
resourceNode.id = "resource-info-container";

View File

@ -80,7 +80,7 @@ body {
#resource-info-container {
width: 100%;
border-top: 1px solid rgb(231, 234, 241);
padding: 20px 0;
padding: 20px 10px;
}
#resource-info {
display: flex;

View File

@ -21,7 +21,7 @@ use crate::{
errors,
routes::{app::StorageInterface, SessionState},
services,
types::domain,
types::{api, domain, transformers::ForeignFrom},
};
#[cfg(all(feature = "v2", feature = "customer_v2"))]
@ -156,9 +156,31 @@ pub async fn initiate_payout_link(
.attach_printable_lazy(|| {
format!("customer [{}] not found", payout_link.primary_reference)
})?;
let address = payout
.address_id
.as_ref()
.async_map(|address_id| async {
db.find_address_by_address_id(&(&state).into(), address_id, &key_store)
.await
})
.await
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| {
format!(
"Failed while fetching address [id - {:?}] for payout [id - {}]",
payout.address_id, payout.payout_id
)
})?;
let enabled_payout_methods =
filter_payout_methods(&state, &merchant_account, &key_store, &payout).await?;
let enabled_payout_methods = filter_payout_methods(
&state,
&merchant_account,
&key_store,
&payout,
address.as_ref(),
)
.await?;
// Fetch default enabled_payout_methods
let mut default_enabled_payout_methods: Vec<link_utils::EnabledPaymentMethod> = vec![];
for (payment_method, payment_method_types) in
@ -188,6 +210,16 @@ pub async fn initiate_payout_link(
_ => Ordering::Equal,
});
let required_field_override = api::RequiredFieldsOverrideRequest {
billing: address.as_ref().map(From::from),
};
let enabled_payment_methods_with_required_fields = ForeignFrom::foreign_from((
&state.conf.payouts.required_fields,
enabled_payment_methods.clone(),
required_field_override,
));
let js_data = payouts::PayoutLinkDetails {
publishable_key: masking::Secret::new(merchant_account.publishable_key),
client_secret: link_data.client_secret.clone(),
@ -204,9 +236,11 @@ pub async fn initiate_payout_link(
.attach_printable("Failed to parse payout status link's return URL")?,
ui_config: ui_config_data,
enabled_payment_methods,
enabled_payment_methods_with_required_fields,
amount,
currency: payout.destination_currency,
locale: locale.clone(),
form_layout: link_data.form_layout,
test_mode: link_data.test_mode.unwrap_or(false),
};
@ -287,6 +321,7 @@ pub async fn filter_payout_methods(
merchant_account: &domain::MerchantAccount,
key_store: &domain::MerchantKeyStore,
payout: &hyperswitch_domain_models::payouts::payouts::Payouts,
address: Option<&domain::Address>,
) -> errors::RouterResult<Vec<link_utils::EnabledPaymentMethod>> {
use masking::ExposeInterface;
@ -308,22 +343,6 @@ pub async fn filter_payout_methods(
&payout.profile_id,
common_enums::ConnectorType::PayoutProcessor,
);
let address = payout
.address_id
.as_ref()
.async_map(|address_id| async {
db.find_address_by_address_id(key_manager_state, address_id, key_store)
.await
})
.await
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| {
format!(
"Failed while fetching address [id - {:?}] for payout [id - {}]",
payout.address_id, payout.payout_id
)
})?;
let mut response: Vec<link_utils::EnabledPaymentMethod> = vec![];
let mut payment_method_list_hm: HashMap<

View File

@ -11,11 +11,9 @@ use api_models::{self, enums as api_enums, payouts::PayoutLinkResponse};
use common_enums::PayoutRetryType;
use common_utils::{
consts,
crypto::Encryptable,
ext_traits::{AsyncExt, ValueExt},
id_type::CustomerId,
link_utils::{GenericLinkStatus, GenericLinkUiConfig, PayoutLinkData, PayoutLinkStatus},
pii,
types::MinorUnit,
};
use diesel_models::{
@ -2142,29 +2140,7 @@ pub async fn response_handler(
let billing_address = payout_data.billing_address.to_owned();
let customer_details = payout_data.customer_details.to_owned();
let customer_id = payouts.customer_id;
let address = billing_address.as_ref().map(|a| {
let phone_details = payment_api_types::PhoneDetails {
number: a.phone_number.to_owned().map(Encryptable::into_inner),
country_code: a.country_code.to_owned(),
};
let address_details = payment_api_types::AddressDetails {
city: a.city.to_owned(),
country: a.country.to_owned(),
line1: a.line1.to_owned().map(Encryptable::into_inner),
line2: a.line2.to_owned().map(Encryptable::into_inner),
line3: a.line3.to_owned().map(Encryptable::into_inner),
zip: a.zip.to_owned().map(Encryptable::into_inner),
first_name: a.first_name.to_owned().map(Encryptable::into_inner),
last_name: a.last_name.to_owned().map(Encryptable::into_inner),
state: a.state.to_owned().map(Encryptable::into_inner),
};
api::payments::Address {
phone: Some(phone_details),
address: Some(address_details),
email: a.email.to_owned().map(pii::Email::from),
}
});
let billing = billing_address.as_ref().map(From::from);
let response = api::PayoutCreateResponse {
payout_id: payouts.payout_id.to_owned(),
@ -2173,7 +2149,7 @@ pub async fn response_handler(
currency: payouts.destination_currency.to_owned(),
connector: payout_attempt.connector.to_owned(),
payout_type: payouts.payout_type.to_owned(),
billing: address,
billing,
auto_fulfill: payouts.auto_fulfill,
customer_id,
email: customer_details.as_ref().and_then(|c| c.email.clone()),
@ -2746,6 +2722,14 @@ pub async fn create_payout_link(
.and_then(|config| config.payout_link_id.clone()),
"payout_link",
)?;
let form_layout = payout_link_config_req
.as_ref()
.and_then(|config| config.form_layout.to_owned())
.or_else(|| {
profile_config
.as_ref()
.and_then(|config| config.form_layout.to_owned())
});
let data = PayoutLinkData {
payout_link_id: payout_link_id.clone(),
@ -2759,6 +2743,7 @@ pub async fn create_payout_link(
amount: MinorUnit::from(*amount),
currency: *currency,
allowed_domains,
form_layout,
test_mode: test_mode_in_config,
};

View File

@ -1,3 +1,7 @@
use std::collections::HashMap;
use common_utils::link_utils::EnabledPaymentMethod;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "customer_v2"),
@ -5,7 +9,11 @@
))]
use crate::types::transformers::ForeignInto;
#[cfg(feature = "olap")]
use crate::types::{api, domain, storage, transformers::ForeignFrom};
use crate::types::{domain, storage};
use crate::{
settings::PayoutRequiredFields,
types::{api, transformers::ForeignFrom},
};
#[cfg(all(feature = "v2", feature = "customer_v2", feature = "olap"))]
impl
@ -103,3 +111,72 @@ impl
}
}
}
impl
ForeignFrom<(
&PayoutRequiredFields,
Vec<EnabledPaymentMethod>,
api::RequiredFieldsOverrideRequest,
)> for Vec<api::PayoutEnabledPaymentMethodsInfo>
{
fn foreign_from(
(payout_required_fields, enabled_payout_methods, value_overrides): (
&PayoutRequiredFields,
Vec<EnabledPaymentMethod>,
api::RequiredFieldsOverrideRequest,
),
) -> Self {
let value_overrides = value_overrides.flat_struct();
enabled_payout_methods
.into_iter()
.map(|enabled_payout_method| {
let payment_method = enabled_payout_method.payment_method;
let payment_method_types_info = enabled_payout_method
.payment_method_types
.into_iter()
.filter_map(|pmt| {
payout_required_fields
.0
.get(&payment_method)
.and_then(|pmt_info| {
pmt_info.0.get(&pmt).map(|connector_fields| {
let mut required_fields = HashMap::new();
for required_field_final in connector_fields.fields.values() {
required_fields.extend(required_field_final.common.clone());
}
for (key, value) in &value_overrides {
required_fields.entry(key.to_string()).and_modify(
|required_field| {
required_field.value =
Some(masking::Secret::new(value.to_string()));
},
);
}
api::PaymentMethodTypeInfo {
payment_method_type: pmt,
required_fields: if required_fields.is_empty() {
None
} else {
Some(required_fields)
},
}
})
})
.or(Some(api::PaymentMethodTypeInfo {
payment_method_type: pmt,
required_fields: None,
}))
})
.collect();
api::PayoutEnabledPaymentMethodsInfo {
payment_method,
payment_method_types_info,
}
})
.collect()
}
}