diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index 662129204c29..ce61d30d36f5 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -21,6 +21,7 @@ mime = "0.3.17" reqwest = { version = "0.11.18", optional = true } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" +serde_with = "3.0.0" 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"] } diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index caff4e1780c7..29ad9be051b6 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -16,6 +16,5 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod refunds; -pub mod types; pub mod verifications; pub mod webhooks; diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index eb15f0e3bc3a..9d3983e73a1f 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -3,8 +3,10 @@ use std::collections::HashMap; use cards::CardNumber; use common_utils::{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, pii, + types::Percentage, }; use serde::de; +use serde_with::serde_as; use utoipa::ToSchema; #[cfg(feature = "payouts")] @@ -12,7 +14,6 @@ use crate::payouts; use crate::{ admin, enums as api_enums, payments::{self, BankCodeResponse}, - types::Percentage, }; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -251,12 +252,28 @@ pub struct PaymentExperienceTypes { pub eligible_connectors: Vec, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)] +#[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)] pub struct CardNetworkTypes { /// The card network enabled #[schema(value_type = Option, example = "Visa")] pub card_network: api_enums::CardNetwork, + /// surcharge details for this card network + #[schema(example = r#" + { + "surcharge": { + "type": "rate", + "value": { + "percentage": 2.5 + } + }, + "tax_on_surcharge": { + "percentage": 1.5 + } + } + "#)] + pub surcharge_details: Option, + /// The list of eligible connectors for a given card network #[schema(example = json!(["stripe", "adyen"]))] pub eligible_connectors: Vec, @@ -304,18 +321,31 @@ pub struct ResponsePaymentMethodTypes { } } "#)] - pub surcharge_details: Option, + pub surcharge_details: Option, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] #[serde(rename_all = "snake_case")] -pub struct SurchargeDetails { +pub struct SurchargeDetailsResponse { /// surcharge value - surcharge: Surcharge, + pub surcharge: Surcharge, /// tax on surcharge value - tax_on_surcharge: Option>, + pub tax_on_surcharge: Option>, + /// surcharge amount for this payment + pub surcharge_amount: i64, + /// tax on surcharge amount for this payment + pub tax_on_surcharge_amount: i64, + /// sum of original amount, + pub final_amount: i64, +} + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct SurchargeMetadata { + #[serde_as(as = "HashMap<_, _>")] + pub surcharge_results: HashMap, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] #[serde(rename_all = "snake_case", tag = "type", content = "value")] pub enum Surcharge { /// Fixed Surcharge value diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 01edef87a67a..08646a4a0b3c 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -795,6 +795,168 @@ pub enum PaymentMethodData { GiftCard(Box), } +pub trait GetPaymentMethodType { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType; +} + +impl GetPaymentMethodType for CardRedirectData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::Knet {} => api_enums::PaymentMethodType::Knet, + Self::Benefit {} => api_enums::PaymentMethodType::Benefit, + Self::MomoAtm {} => api_enums::PaymentMethodType::MomoAtm, + } + } +} + +impl GetPaymentMethodType for WalletData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::AliPayQr(_) | Self::AliPayRedirect(_) => api_enums::PaymentMethodType::AliPay, + Self::AliPayHkRedirect(_) => api_enums::PaymentMethodType::AliPayHk, + Self::MomoRedirect(_) => api_enums::PaymentMethodType::Momo, + Self::KakaoPayRedirect(_) => api_enums::PaymentMethodType::KakaoPay, + Self::GoPayRedirect(_) => api_enums::PaymentMethodType::GoPay, + Self::GcashRedirect(_) => api_enums::PaymentMethodType::Gcash, + Self::ApplePay(_) | Self::ApplePayRedirect(_) | Self::ApplePayThirdPartySdk(_) => { + api_enums::PaymentMethodType::ApplePay + } + Self::DanaRedirect {} => api_enums::PaymentMethodType::Dana, + Self::GooglePay(_) | Self::GooglePayRedirect(_) | Self::GooglePayThirdPartySdk(_) => { + api_enums::PaymentMethodType::GooglePay + } + Self::MbWayRedirect(_) => api_enums::PaymentMethodType::MbWay, + Self::MobilePayRedirect(_) => api_enums::PaymentMethodType::MobilePay, + Self::PaypalRedirect(_) | Self::PaypalSdk(_) => api_enums::PaymentMethodType::Paypal, + Self::SamsungPay(_) => api_enums::PaymentMethodType::SamsungPay, + Self::TwintRedirect {} => api_enums::PaymentMethodType::Twint, + Self::VippsRedirect {} => api_enums::PaymentMethodType::Vipps, + Self::TouchNGoRedirect(_) => api_enums::PaymentMethodType::TouchNGo, + Self::WeChatPayRedirect(_) | Self::WeChatPayQr(_) => { + api_enums::PaymentMethodType::WeChatPay + } + Self::CashappQr(_) => api_enums::PaymentMethodType::Cashapp, + Self::SwishQr(_) => api_enums::PaymentMethodType::Swish, + } + } +} + +impl GetPaymentMethodType for PayLaterData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna, + Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna, + Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm, + Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay, + Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright, + Self::WalleyRedirect {} => api_enums::PaymentMethodType::Walley, + Self::AlmaRedirect {} => api_enums::PaymentMethodType::Alma, + Self::AtomeRedirect {} => api_enums::PaymentMethodType::Atome, + } + } +} + +impl GetPaymentMethodType for BankRedirectData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::BancontactCard { .. } => api_enums::PaymentMethodType::BancontactCard, + Self::Bizum {} => api_enums::PaymentMethodType::Bizum, + Self::Blik { .. } => api_enums::PaymentMethodType::Blik, + Self::Eps { .. } => api_enums::PaymentMethodType::Eps, + Self::Giropay { .. } => api_enums::PaymentMethodType::Giropay, + Self::Ideal { .. } => api_enums::PaymentMethodType::Ideal, + Self::Interac { .. } => api_enums::PaymentMethodType::Interac, + Self::OnlineBankingCzechRepublic { .. } => { + api_enums::PaymentMethodType::OnlineBankingCzechRepublic + } + Self::OnlineBankingFinland { .. } => api_enums::PaymentMethodType::OnlineBankingFinland, + Self::OnlineBankingPoland { .. } => api_enums::PaymentMethodType::OnlineBankingPoland, + Self::OnlineBankingSlovakia { .. } => { + api_enums::PaymentMethodType::OnlineBankingSlovakia + } + Self::OpenBankingUk { .. } => api_enums::PaymentMethodType::OpenBankingUk, + Self::Przelewy24 { .. } => api_enums::PaymentMethodType::Przelewy24, + Self::Sofort { .. } => api_enums::PaymentMethodType::Sofort, + Self::Trustly { .. } => api_enums::PaymentMethodType::Trustly, + Self::OnlineBankingFpx { .. } => api_enums::PaymentMethodType::OnlineBankingFpx, + Self::OnlineBankingThailand { .. } => { + api_enums::PaymentMethodType::OnlineBankingThailand + } + } + } +} + +impl GetPaymentMethodType for BankDebitData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::AchBankDebit { .. } => api_enums::PaymentMethodType::Ach, + Self::SepaBankDebit { .. } => api_enums::PaymentMethodType::Sepa, + Self::BecsBankDebit { .. } => api_enums::PaymentMethodType::Becs, + Self::BacsBankDebit { .. } => api_enums::PaymentMethodType::Bacs, + } + } +} + +impl GetPaymentMethodType for BankTransferData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::AchBankTransfer { .. } => api_enums::PaymentMethodType::Ach, + Self::SepaBankTransfer { .. } => api_enums::PaymentMethodType::Sepa, + Self::BacsBankTransfer { .. } => api_enums::PaymentMethodType::Bacs, + Self::MultibancoBankTransfer { .. } => api_enums::PaymentMethodType::Multibanco, + Self::PermataBankTransfer { .. } => api_enums::PaymentMethodType::PermataBankTransfer, + Self::BcaBankTransfer { .. } => api_enums::PaymentMethodType::BcaBankTransfer, + Self::BniVaBankTransfer { .. } => api_enums::PaymentMethodType::BniVa, + Self::BriVaBankTransfer { .. } => api_enums::PaymentMethodType::BriVa, + Self::CimbVaBankTransfer { .. } => api_enums::PaymentMethodType::CimbVa, + Self::DanamonVaBankTransfer { .. } => api_enums::PaymentMethodType::DanamonVa, + Self::MandiriVaBankTransfer { .. } => api_enums::PaymentMethodType::MandiriVa, + Self::Pix {} => api_enums::PaymentMethodType::Pix, + Self::Pse {} => api_enums::PaymentMethodType::Pse, + } + } +} + +impl GetPaymentMethodType for CryptoData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + api_enums::PaymentMethodType::CryptoCurrency + } +} + +impl GetPaymentMethodType for UpiData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + api_enums::PaymentMethodType::UpiCollect + } +} +impl GetPaymentMethodType for VoucherData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::Boleto(_) => api_enums::PaymentMethodType::Boleto, + Self::Efecty => api_enums::PaymentMethodType::Efecty, + Self::PagoEfectivo => api_enums::PaymentMethodType::PagoEfectivo, + Self::RedCompra => api_enums::PaymentMethodType::RedCompra, + Self::RedPagos => api_enums::PaymentMethodType::RedPagos, + Self::Alfamart(_) => api_enums::PaymentMethodType::Alfamart, + Self::Indomaret(_) => api_enums::PaymentMethodType::Indomaret, + Self::Oxxo => api_enums::PaymentMethodType::Oxxo, + Self::SevenEleven(_) => api_enums::PaymentMethodType::SevenEleven, + Self::Lawson(_) => api_enums::PaymentMethodType::Lawson, + Self::MiniStop(_) => api_enums::PaymentMethodType::MiniStop, + Self::FamilyMart(_) => api_enums::PaymentMethodType::FamilyMart, + Self::Seicomart(_) => api_enums::PaymentMethodType::Seicomart, + Self::PayEasy(_) => api_enums::PaymentMethodType::PayEasy, + } + } +} +impl GetPaymentMethodType for GiftCardData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::Givex(_) => api_enums::PaymentMethodType::Givex, + Self::PaySafeCard {} => api_enums::PaymentMethodType::PaySafeCard, + } + } +} + #[derive(serde::Deserialize, serde::Serialize, Debug, Clone, ToSchema, Eq, PartialEq)] #[serde(rename_all = "snake_case")] pub enum GiftCardData { diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index 01c9c80fceca..ca6bba480063 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -13,6 +13,7 @@ pub mod pii; pub mod request; #[cfg(feature = "signals")] pub mod signals; +pub mod types; pub mod validation; /// Date-time utilities. diff --git a/crates/api_models/src/types.rs b/crates/common_utils/src/types.rs similarity index 52% rename from crates/api_models/src/types.rs rename to crates/common_utils/src/types.rs index bd594ba62766..d745334a21ea 100644 --- a/crates/api_models/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -1,46 +1,68 @@ -use common_utils::errors::{ApiModelsError, CustomResult}; -use error_stack::ResultExt; +//! Types that can be used in other crates +use error_stack::{IntoReport, ResultExt}; use serde::{de::Visitor, Deserialize, Deserializer}; -use utoipa::ToSchema; -#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] +use crate::errors::{ApiModelsError, CustomResult}; + +/// Represents Percentage Value between 0 and 100 both inclusive +#[derive(Clone, Default, Debug, PartialEq, serde::Serialize)] pub struct Percentage { // 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", + "value should be a float between 0 to 100 and precise to only upto {} decimal digits", precision ) } impl Percentage { - pub fn from_float(value: f32) -> CustomResult { - if Self::is_valid_value(value) { - Ok(Self { percentage: value }) + /// construct percentage using a string representation of float value + pub fn from_string(value: String) -> CustomResult { + if Self::is_valid_string_value(&value)? { + Ok(Self { + percentage: value + .parse() + .into_report() + .change_context(ApiModelsError::InvalidPercentageValue)?, + }) } else { Err(ApiModelsError::InvalidPercentageValue.into()) .attach_printable(get_invalid_percentage_error_message(PRECISION)) } } + /// function to get percentage value 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_string_value(value: &str) -> CustomResult { + let float_value = Self::is_valid_float_string(value)?; + Ok(Self::is_valid_range(float_value) && Self::is_valid_precision_length(value)) + } + fn is_valid_float_string(value: &str) -> CustomResult { + value + .parse() + .into_report() + .change_context(ApiModelsError::InvalidPercentageValue) } 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 + fn is_valid_precision_length(value: &str) -> bool { + if value.contains('.') { + // if string has '.' then take the decimal part and verify precision length + match value.split('.').last() { + Some(decimal_part) => decimal_part.trim_end_matches('0').len() <= PRECISION.into(), + // will never be None + None => false, + } + } else { + // if there is no '.' then it is a whole number with no decimal part. So return true + true + } } } @@ -62,17 +84,17 @@ impl<'de, const PRECISION: u8> Visitor<'de> for PercentageVisitor { if percentage_value.is_some() { return Err(serde::de::Error::duplicate_field("percentage")); } - percentage_value = Some(map.next_value::()?); + percentage_value = Some(map.next_value::()?); } 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(|_| { + let string_value = value.to_string(); + Ok(Percentage::from_string(string_value.clone()).map_err(|_| { serde::de::Error::invalid_value( - serde::de::Unexpected::Other(&format!("percentage value `{}`", str_value)), + serde::de::Unexpected::Other(&format!("percentage value {}", string_value)), &&*get_invalid_percentage_error_message(PRECISION), ) })?) diff --git a/crates/api_models/tests/percentage.rs b/crates/common_utils/tests/percentage.rs similarity index 60% rename from crates/api_models/tests/percentage.rs rename to crates/common_utils/tests/percentage.rs index 3e137a2592d1..95c112523376 100644 --- a/crates/api_models/tests/percentage.rs +++ b/crates/common_utils/tests/percentage.rs @@ -1,12 +1,11 @@ #![allow(clippy::panic_in_result_fn)] -use api_models::types::Percentage; -use common_utils::errors::ApiModelsError; +use common_utils::{errors::ApiModelsError, types::Percentage}; const PRECISION_2: u8 = 2; const PRECISION_0: u8 = 0; #[test] fn invalid_range_more_than_100() -> Result<(), Box> { - let percentage = Percentage::::from_float(100.01); + let percentage = Percentage::::from_string("100.01".to_string()); assert!(percentage.is_err()); if let Err(err) = percentage { assert_eq!( @@ -18,7 +17,7 @@ fn invalid_range_more_than_100() -> Result<(), Box Result<(), Box> { - let percentage = Percentage::::from_float(-0.01); + let percentage = Percentage::::from_string("-0.01".to_string()); assert!(percentage.is_err()); if let Err(err) = percentage { assert_eq!( @@ -28,21 +27,35 @@ fn invalid_range_less_than_0() -> Result<(), Box Result<(), Box> { + let percentage = Percentage::::from_string("-0.01ed".to_string()); + assert!(percentage.is_err()); + if let Err(err) = percentage { + assert_eq!( + *err.current_context(), + ApiModelsError::InvalidPercentageValue + ) + } + Ok(()) +} + #[test] fn valid_range() -> Result<(), Box> { - let percentage = Percentage::::from_float(2.22); + let percentage = Percentage::::from_string("2.22".to_string()); assert!(percentage.is_ok()); if let Ok(percentage) = percentage { assert_eq!(percentage.get_percentage(), 2.22) } - let percentage = Percentage::::from_float(0.0); + let percentage = Percentage::::from_string("0.05".to_string()); assert!(percentage.is_ok()); if let Ok(percentage) = percentage { - assert_eq!(percentage.get_percentage(), 0.0) + assert_eq!(percentage.get_percentage(), 0.05) } - let percentage = Percentage::::from_float(100.0); + let percentage = Percentage::::from_string("100.0".to_string()); assert!(percentage.is_ok()); if let Ok(percentage) = percentage { assert_eq!(percentage.get_percentage(), 100.0) @@ -51,19 +64,19 @@ fn valid_range() -> Result<(), Box> { } #[test] fn valid_precision() -> Result<(), Box> { - let percentage = Percentage::::from_float(2.2); + let percentage = Percentage::::from_string("2.2".to_string()); assert!(percentage.is_ok()); if let Ok(percentage) = percentage { assert_eq!(percentage.get_percentage(), 2.2) } - let percentage = Percentage::::from_float(2.20000); + let percentage = Percentage::::from_string("2.20000".to_string()); assert!(percentage.is_ok()); if let Ok(percentage) = percentage { assert_eq!(percentage.get_percentage(), 2.2) } - let percentage = Percentage::::from_float(2.0); + let percentage = Percentage::::from_string("2.0".to_string()); assert!(percentage.is_ok()); if let Ok(percentage) = percentage { assert_eq!(percentage.get_percentage(), 2.0) @@ -74,7 +87,7 @@ fn valid_precision() -> Result<(), Box> { #[test] fn invalid_precision() -> Result<(), Box> { - let percentage = Percentage::::from_float(2.221); + let percentage = Percentage::::from_string("2.221".to_string()); assert!(percentage.is_err()); if let Err(err) = percentage { assert_eq!( @@ -87,15 +100,47 @@ fn invalid_precision() -> Result<(), Box> { #[test] fn deserialization_test_ok() -> Result<(), Box> { + let mut decimal = 0; + let mut integer = 0; + // check for all percentage values from 0 to 100 + while integer <= 100 { + let json_string = format!( + r#" + {{ + "percentage" : {}.{} + }} + "#, + integer, decimal + ); + let percentage = serde_json::from_str::>(&json_string); + assert!(percentage.is_ok()); + if let Ok(percentage) = percentage { + assert_eq!( + percentage.get_percentage(), + format!("{}.{}", integer, decimal) + .parse::() + .unwrap_or_default() + ) + } + if integer == 100 { + break; + } + decimal += 1; + if decimal == 100 { + decimal = 0; + integer += 1; + } + } + let json_string = r#" { - "percentage" : 12.4 + "percentage" : 18.7 } "#; let percentage = serde_json::from_str::>(json_string); assert!(percentage.is_ok()); if let Ok(percentage) = percentage { - assert_eq!(percentage.get_percentage(), 12.4) + assert_eq!(percentage.get_percentage(), 18.7) } let json_string = r#" @@ -122,7 +167,7 @@ fn deserialization_test_err() -> Result<(), Box>(json_string); assert!(percentage.is_err()); if let Err(err) = percentage { - assert_eq!(err.to_string(), "invalid value: percentage value `12.4`, expected value should be between 0 to 100 and precise to only upto 0 decimal digits at line 4 column 9".to_string()) + assert_eq!(err.to_string(), "invalid value: percentage value 12.4, expected value should be a float between 0 to 100 and precise to only upto 0 decimal digits at line 4 column 9".to_string()) } // invalid percentage value @@ -134,7 +179,7 @@ fn deserialization_test_err() -> Result<(), Box>(json_string); assert!(percentage.is_err()); if let Err(err) = percentage { - assert_eq!(err.to_string(), "invalid value: percentage value `123.42`, expected value should be between 0 to 100 and precise to only upto 2 decimal digits at line 4 column 9".to_string()) + assert_eq!(err.to_string(), "invalid value: percentage value 123.42, expected value should be a float between 0 to 100 and precise to only upto 2 decimal digits at line 4 column 9".to_string()) } // missing percentage field @@ -146,7 +191,6 @@ fn deserialization_test_err() -> Result<(), Box>(json_string); assert!(percentage.is_err()); if let Err(err) = percentage { - dbg!(err.to_string()); assert_eq!( err.to_string(), "missing field `percentage` at line 4 column 9".to_string() diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index a372a77f0643..29a83a309973 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -294,6 +294,10 @@ pub enum PaymentAttemptUpdate { MultipleCaptureCountUpdate { multiple_capture_count: i16, }, + SurchargeAmountUpdate { + surcharge_amount: Option, + tax_amount: Option, + }, AmountToCaptureUpdate { status: storage_enums::AttemptStatus, amount_capturable: i64, diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index ebda08d759eb..97f45a868307 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -214,6 +214,10 @@ pub enum PaymentAttemptUpdate { status: storage_enums::AttemptStatus, amount_capturable: i64, }, + SurchargeAmountUpdate { + surcharge_amount: Option, + tax_amount: Option, + }, PreprocessingUpdate { status: storage_enums::AttemptStatus, payment_method_id: Option>, @@ -257,6 +261,8 @@ pub struct PaymentAttemptUpdateInternal { capture_method: Option, connector_response_reference_id: Option, multiple_capture_count: Option, + surcharge_amount: Option, + tax_amount: Option, amount_capturable: Option, surcharge_metadata: Option, } @@ -507,6 +513,14 @@ impl From for PaymentAttemptUpdateInternal { surcharge_metadata, ..Default::default() }, + PaymentAttemptUpdate::SurchargeAmountUpdate { + surcharge_amount, + tax_amount, + } => Self { + surcharge_amount, + tax_amount, + ..Default::default() + }, } } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index deccf98d83e5..88ea12388db5 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1172,6 +1172,7 @@ pub async fn list_payment_methods( card_network_types.push(CardNetworkTypes { card_network: card_network_type.0.clone(), eligible_connectors: card_network_type.1.clone(), + surcharge_details: None, }) } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index d65e53c95ba9..14b97fec42e4 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -9,7 +9,11 @@ pub mod types; use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant}; -use api_models::{enums, payments::HeaderPayload}; +use api_models::{ + enums, + payment_methods::{SurchargeDetailsResponse, SurchargeMetadata}, + payments::HeaderPayload, +}; use common_utils::{ext_traits::AsyncExt, pii}; use data_models::mandates::MandateData; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; @@ -814,6 +818,18 @@ where { let call_connectors_start_time = Instant::now(); let mut join_handlers = Vec::with_capacity(connectors.len()); + let surcharge_metadata = payment_data + .payment_attempt + .surcharge_metadata + .as_ref() + .map(|surcharge_metadata_value| { + surcharge_metadata_value + .clone() + .parse_value::("SurchargeMetadata") + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to Deserialize SurchargeMetadata")?; for session_connector_data in connectors.iter() { let connector_id = session_connector_data.connector.connector.id(); @@ -827,6 +843,14 @@ where false, ) .await?; + payment_data.surcharge_details = + surcharge_metadata.as_ref().and_then(|surcharge_metadata| { + let payment_method_type = session_connector_data.payment_method_type; + surcharge_metadata + .surcharge_results + .get(&payment_method_type.to_string()) + .cloned() + }); let router_data = payment_data .construct_router_data( @@ -1460,6 +1484,7 @@ where pub recurring_mandate_payment_data: Option, pub ephemeral_key: Option, pub redirect_response: Option, + pub surcharge_details: Option, pub frm_message: Option, } diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index d7b3d743b959..edb6478efec0 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -251,6 +251,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response, + surcharge_details: None, frm_message: frm_response.ok(), }, Some(CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 72006946c207..d92d86e79239 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -171,6 +171,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: None, }, None, diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index bd64ebac6322..eb88265bfc88 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -229,6 +229,7 @@ impl ephemeral_key: None, multiple_capture_data, redirect_response: None, + surcharge_details: None, frm_message: None, }, None, diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 506a9b4421cd..029652902248 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -246,6 +246,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response, + surcharge_details: None, frm_message: None, }, Some(CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 761264e4798e..a40de370b368 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -359,6 +359,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: None, }, Some(customer_details), diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 479b0e2cceea..0be92418183f 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -294,6 +294,7 @@ impl ephemeral_key, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: None, }, Some(customer_details), diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index 0ff49279f3c8..1b33f2dddb81 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -195,6 +195,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: None, }, Some(payments::CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index c9a24b8fb840..2e9bd3eb9ed3 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -157,6 +157,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: frm_response.ok(), }, None, diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 261275296f14..cc91482440ba 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -191,6 +191,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: None, }, Some(customer_details), diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 07015810039d..4979b7902c85 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -166,6 +166,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: None, }, Some(customer_details), diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 6e0ef2f4bac7..c2762a966631 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -402,6 +402,7 @@ async fn get_tracker_for_sync< ephemeral_key: None, multiple_capture_data, redirect_response: None, + surcharge_details: None, frm_message: frm_response.ok(), }, None, diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 9e0ef76d3e7f..1c7b432c3276 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -345,6 +345,7 @@ impl ephemeral_key: None, multiple_capture_data: None, redirect_response: None, + surcharge_details: None, frm_message: None, }, Some(customer_details), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 8526f48a590c..d19460127b17 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1024,6 +1024,7 @@ impl TryFrom> for types::PaymentsAuthoriz webhook_url, complete_authorize_url, customer_id: None, + surcharge_details: payment_data.surcharge_details, }) } } @@ -1185,6 +1186,7 @@ impl TryFrom> for types::PaymentsSessionD billing_address.address.and_then(|address| address.country) }), order_details, + surcharge_details: payment_data.surcharge_details, }) } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index cb8fe8fa7979..faa393fd7513 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -379,6 +379,7 @@ pub struct PaymentsAuthorizeData { pub related_transaction_id: Option, pub payment_experience: Option, pub payment_method_type: Option, + pub surcharge_details: Option, pub customer_id: Option, } @@ -514,6 +515,7 @@ pub struct PaymentsSessionData { pub amount: i64, pub currency: storage_enums::Currency, pub country: Option, + pub surcharge_details: Option, pub order_details: Option>, } @@ -1075,6 +1077,7 @@ impl From<&SetupMandateRouterData> for PaymentsAuthorizeData { payment_experience: None, payment_method_type: None, customer_id: None, + surcharge_details: None, } } } diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 63d2ee364fa6..cba0640e79a0 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -68,6 +68,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { webhook_url: None, complete_authorize_url: None, customer_id: None, + surcharge_details: None, }, response: Err(types::ErrorResponse::default()), payment_method_id: None, diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index 06f048bb7461..2ee6c4912e7c 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -153,6 +153,7 @@ impl AdyenTest { webhook_url: None, complete_authorize_url: None, customer_id: None, + surcharge_details: None, }) } } diff --git a/crates/router/tests/connectors/bitpay.rs b/crates/router/tests/connectors/bitpay.rs index 1eaf1580f0a7..2f6db7a9e850 100644 --- a/crates/router/tests/connectors/bitpay.rs +++ b/crates/router/tests/connectors/bitpay.rs @@ -90,6 +90,7 @@ fn payment_method_details() -> Option { complete_authorize_url: None, capture_method: None, customer_id: None, + surcharge_details: None, }) } diff --git a/crates/router/tests/connectors/cashtocode.rs b/crates/router/tests/connectors/cashtocode.rs index 141aee4ff49b..fdb1b94a7149 100644 --- a/crates/router/tests/connectors/cashtocode.rs +++ b/crates/router/tests/connectors/cashtocode.rs @@ -65,6 +65,7 @@ impl CashtocodeTest { webhook_url: None, complete_authorize_url: None, customer_id: Some("John Doe".to_owned()), + surcharge_details: None, }) } diff --git a/crates/router/tests/connectors/coinbase.rs b/crates/router/tests/connectors/coinbase.rs index dc677c6e31a6..cc8cc774a144 100644 --- a/crates/router/tests/connectors/coinbase.rs +++ b/crates/router/tests/connectors/coinbase.rs @@ -92,6 +92,7 @@ fn payment_method_details() -> Option { complete_authorize_url: None, capture_method: None, customer_id: None, + surcharge_details: None, }) } diff --git a/crates/router/tests/connectors/cryptopay.rs b/crates/router/tests/connectors/cryptopay.rs index d86284159954..48313e5d1a12 100644 --- a/crates/router/tests/connectors/cryptopay.rs +++ b/crates/router/tests/connectors/cryptopay.rs @@ -90,6 +90,7 @@ fn payment_method_details() -> Option { complete_authorize_url: None, capture_method: None, customer_id: None, + surcharge_details: None, }) } diff --git a/crates/router/tests/connectors/opennode.rs b/crates/router/tests/connectors/opennode.rs index 79fadf3e130d..a46fc20604a5 100644 --- a/crates/router/tests/connectors/opennode.rs +++ b/crates/router/tests/connectors/opennode.rs @@ -91,6 +91,7 @@ fn payment_method_details() -> Option { complete_authorize_url: None, capture_method: None, customer_id: None, + surcharge_details: None, }) } diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 81f4f6bef29d..7cd2e16fd361 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -886,6 +886,7 @@ impl Default for PaymentAuthorizeType { complete_authorize_url: None, webhook_url: None, customer_id: None, + surcharge_details: None, }; Self(data) } diff --git a/crates/router/tests/connectors/worldline.rs b/crates/router/tests/connectors/worldline.rs index 11de3be8a2c6..b9ea1364fdb1 100644 --- a/crates/router/tests/connectors/worldline.rs +++ b/crates/router/tests/connectors/worldline.rs @@ -100,6 +100,7 @@ impl WorldlineTest { webhook_url: None, complete_authorize_url: None, customer_id: None, + surcharge_details: None, }) } } diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 4764ce68a314..b9b850a0aaa5 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1307,6 +1307,13 @@ impl DataModelExt for PaymentAttemptUpdate { Self::SurchargeMetadataUpdate { surcharge_metadata } => { DieselPaymentAttemptUpdate::SurchargeMetadataUpdate { surcharge_metadata } } + Self::SurchargeAmountUpdate { + surcharge_amount, + tax_amount, + } => DieselPaymentAttemptUpdate::SurchargeAmountUpdate { + surcharge_amount, + tax_amount, + }, } } @@ -1500,6 +1507,13 @@ impl DataModelExt for PaymentAttemptUpdate { DieselPaymentAttemptUpdate::SurchargeMetadataUpdate { surcharge_metadata } => { Self::SurchargeMetadataUpdate { surcharge_metadata } } + DieselPaymentAttemptUpdate::SurchargeAmountUpdate { + surcharge_amount, + tax_amount, + } => Self::SurchargeAmountUpdate { + surcharge_amount, + tax_amount, + }, } } }