mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(core): add surcharge_details field to ResponsePaymentMethodTypes struct (#2435)
This commit is contained in:
@ -16,5 +16,6 @@ pub mod payments;
|
||||
#[cfg(feature = "payouts")]
|
||||
pub mod payouts;
|
||||
pub mod refunds;
|
||||
pub mod types;
|
||||
pub mod verifications;
|
||||
pub mod webhooks;
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use cards::CardNumber;
|
||||
use common_utils::{crypto::OptionalEncryptableName, pii};
|
||||
use common_utils::{
|
||||
consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, pii,
|
||||
};
|
||||
use serde::de;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
@ -10,6 +12,7 @@ use crate::payouts;
|
||||
use crate::{
|
||||
admin, enums as api_enums,
|
||||
payments::{self, BankCodeResponse},
|
||||
types::Percentage,
|
||||
};
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
@ -263,7 +266,7 @@ pub struct BankDebitTypes {
|
||||
pub eligible_connectors: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)]
|
||||
pub struct ResponsePaymentMethodTypes {
|
||||
/// The payment method type enabled
|
||||
#[schema(example = "klarna")]
|
||||
@ -285,6 +288,39 @@ pub struct ResponsePaymentMethodTypes {
|
||||
|
||||
/// Required fields for the payment_method_type.
|
||||
pub required_fields: Option<HashMap<String, RequiredFieldInfo>>,
|
||||
|
||||
/// surcharge details for this payment method type if exists
|
||||
#[schema(example = r#"
|
||||
{
|
||||
"surcharge": {
|
||||
"type": "rate",
|
||||
"value": {
|
||||
"percentage": 2.5
|
||||
}
|
||||
},
|
||||
"tax_on_surcharge": {
|
||||
"percentage": 1.5
|
||||
}
|
||||
}
|
||||
"#)]
|
||||
pub surcharge_details: Option<SurchargeDetails>,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct SurchargeDetails {
|
||||
/// surcharge value
|
||||
surcharge: Surcharge,
|
||||
/// tax on surcharge value
|
||||
tax_on_surcharge: Option<Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
|
||||
pub enum Surcharge {
|
||||
/// Fixed Surcharge value
|
||||
Fixed(i64),
|
||||
/// Surcharge percentage
|
||||
Rate(Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>),
|
||||
}
|
||||
|
||||
/// Required fields info used while listing the payment_method_data
|
||||
@ -303,7 +339,7 @@ pub struct RequiredFieldInfo {
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
|
||||
pub struct ResponsePaymentMethodsEnabled {
|
||||
/// The payment method enabled
|
||||
#[schema(value_type = PaymentMethod)]
|
||||
@ -539,6 +575,10 @@ pub struct PaymentMethodListResponse {
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub merchant_name: OptionalEncryptableName,
|
||||
|
||||
/// flag to indicate if surcharge and tax breakup screen should be shown or not
|
||||
#[schema(value_type = bool)]
|
||||
pub show_surcharge_breakup_screen: bool,
|
||||
|
||||
#[schema(value_type = Option<PaymentType>)]
|
||||
pub payment_type: Option<api_enums::PaymentType>,
|
||||
}
|
||||
|
||||
92
crates/api_models/src/types.rs
Normal file
92
crates/api_models/src/types.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use common_utils::errors::{ApiModelsError, CustomResult};
|
||||
use error_stack::ResultExt;
|
||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)]
|
||||
pub struct Percentage<const PRECISION: u8> {
|
||||
// this value will range from 0 to 100, decimal length defined by precision macro
|
||||
/// Percentage value ranging between 0 and 100
|
||||
#[schema(example = 2.5)]
|
||||
percentage: f32,
|
||||
}
|
||||
|
||||
fn get_invalid_percentage_error_message(precision: u8) -> String {
|
||||
format!(
|
||||
"value should be between 0 to 100 and precise to only upto {} decimal digits",
|
||||
precision
|
||||
)
|
||||
}
|
||||
|
||||
impl<const PRECISION: u8> Percentage<PRECISION> {
|
||||
pub fn from_float(value: f32) -> CustomResult<Self, ApiModelsError> {
|
||||
if Self::is_valid_value(value) {
|
||||
Ok(Self { percentage: value })
|
||||
} else {
|
||||
Err(ApiModelsError::InvalidPercentageValue.into())
|
||||
.attach_printable(get_invalid_percentage_error_message(PRECISION))
|
||||
}
|
||||
}
|
||||
pub fn get_percentage(&self) -> f32 {
|
||||
self.percentage
|
||||
}
|
||||
fn is_valid_value(value: f32) -> bool {
|
||||
Self::is_valid_range(value) && Self::is_valid_precision_length(value)
|
||||
}
|
||||
fn is_valid_range(value: f32) -> bool {
|
||||
(0.0..=100.0).contains(&value)
|
||||
}
|
||||
fn is_valid_precision_length(value: f32) -> bool {
|
||||
let multiplier = f32::powf(10.0, PRECISION.into());
|
||||
let multiplied_value = value * multiplier;
|
||||
// if fraction part is 0, then the percentage value is valid
|
||||
multiplied_value.fract() == 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// custom serde deserialization function
|
||||
struct PercentageVisitor<const PRECISION: u8> {}
|
||||
impl<'de, const PRECISION: u8> Visitor<'de> for PercentageVisitor<PRECISION> {
|
||||
type Value = Percentage<PRECISION>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("Percentage object")
|
||||
}
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let mut percentage_value = None;
|
||||
while let Some(key) = map.next_key::<String>()? {
|
||||
if key.eq("percentage") {
|
||||
if percentage_value.is_some() {
|
||||
return Err(serde::de::Error::duplicate_field("percentage"));
|
||||
}
|
||||
percentage_value = Some(map.next_value::<f32>()?);
|
||||
} else {
|
||||
// Ignore unknown fields
|
||||
let _: serde::de::IgnoredAny = map.next_value()?;
|
||||
}
|
||||
}
|
||||
if let Some(value) = percentage_value {
|
||||
let str_value = value.to_string();
|
||||
Ok(Percentage::from_float(value).map_err(|_| {
|
||||
serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Other(&format!("percentage value `{}`", str_value)),
|
||||
&&*get_invalid_percentage_error_message(PRECISION),
|
||||
)
|
||||
})?)
|
||||
} else {
|
||||
Err(serde::de::Error::missing_field("percentage"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const PRECISION: u8> Deserialize<'de> for Percentage<PRECISION> {
|
||||
fn deserialize<D>(data: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
data.deserialize_map(PercentageVisitor::<PRECISION> {})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user