Skip to content

Commit

Permalink
feat(core): add surcharge_details field to ResponsePaymentMethodTypes…
Browse files Browse the repository at this point in the history
… struct (#2435)
  • Loading branch information
hrithikesh026 authored Oct 5, 2023
1 parent 51d9f8d commit 3f0d927
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 12 deletions.
109 changes: 101 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/api_models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ strum = { version = "0.24.1", features = ["derive"] }
time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] }
url = { version = "2.4.0", features = ["serde"] }
utoipa = { version = "3.3.0", features = ["preserve_order"] }
thiserror = "1.0.40"

# First party crates
cards = { version = "0.1.0", path = "../cards" }
Expand Down
1 change: 1 addition & 0 deletions crates/api_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
46 changes: 43 additions & 3 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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)]
Expand Down Expand Up @@ -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")]
Expand All @@ -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
Expand All @@ -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)]
Expand Down Expand Up @@ -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>,
}
Expand Down
92 changes: 92 additions & 0 deletions crates/api_models/src/types.rs
Original file line number Diff line number Diff line change
@@ -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> {})
}
}
Loading

0 comments on commit 3f0d927

Please sign in to comment.