Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: calculate surcharge for customer saved card list #3039

Merged
merged 23 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cd8d96a
refactor: create separate struct for surcharge details response
hrithikesh026 Nov 30, 2023
f829a8c
change function name
hrithikesh026 Nov 30, 2023
ccca67d
fix: use card bin to get additional card details
hrithikesh026 Dec 1, 2023
8043b99
revert few changes
hrithikesh026 Dec 2, 2023
ecc07d9
Merge branch 'use-card-bin' into surcharge-genration-for-saved-card
hrithikesh026 Dec 2, 2023
1684dae
chore: run formatter
hyperswitch-bot[bot] Dec 3, 2023
377fd63
calculate surcharge in saved card fetch and remove surcharge validati…
hrithikesh026 Dec 3, 2023
d86fae6
Merge branch 'main' into surcharge-api-model-changes
hrithikesh026 Dec 3, 2023
671db52
Merge branch 'main' into use-card-bin
hrithikesh026 Dec 3, 2023
6621225
Merge branch 'main' into surcharge-genration-for-saved-card
hrithikesh026 Dec 3, 2023
a3260f1
Merge branch 'surcharge-genration-for-saved-card' of github.com:juspa…
hrithikesh026 Dec 3, 2023
1eae927
Merge branch 'surcharge-api-model-changes' into use-card-bin
hrithikesh026 Dec 3, 2023
1133a53
chore: run formatter
hyperswitch-bot[bot] Dec 3, 2023
b4f74fe
generate openapi spec
hrithikesh026 Dec 3, 2023
dd4d582
chore: update Cargo.lock
hyperswitch-bot[bot] Dec 3, 2023
cc72c91
address comments
hrithikesh026 Dec 4, 2023
f939cda
Merge branch 'surcharge-api-model-changes' into surcharge-genration-f…
hrithikesh026 Dec 4, 2023
09adb02
cargo clippy
hrithikesh026 Dec 4, 2023
e428e8e
add display_total_surcharge_amount field in SurchargeDetailsResponse
hrithikesh026 Dec 4, 2023
9296073
Merge branch 'use-card-bin' into surcharge-genration-for-saved-card
hrithikesh026 Dec 4, 2023
5845bfa
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Dec 4, 2023
a80d968
address comments
hrithikesh026 Dec 4, 2023
255b107
Merge branch 'main' into surcharge-genration-for-saved-card
hrithikesh026 Dec 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

174 changes: 40 additions & 134 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ use std::collections::HashMap;

use cards::CardNumber;
use common_utils::{
consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, pii,
types::Percentage,
consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH,
crypto::OptionalEncryptableName,
pii,
types::{Percentage, Surcharge},
};
use serde::de;
use utoipa::ToSchema;
use utoipa::{schema, ToSchema};

#[cfg(feature = "payouts")]
use crate::payouts;
use crate::{
admin,
customers::CustomerId,
enums as api_enums,
payments::{self, BankCodeResponse, RequestSurchargeDetails},
payments::{self, BankCodeResponse},
};

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
Expand Down Expand Up @@ -262,19 +264,6 @@ pub struct CardNetworkTypes {
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<SurchargeDetailsResponse>,

/// The list of eligible connectors for a given card network
Expand Down Expand Up @@ -311,145 +300,59 @@ pub struct ResponsePaymentMethodTypes {
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<SurchargeDetailsResponse>,

/// auth service connector label for this payment method type, if exists
pub pm_auth_connector: Option<String>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]

#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct SurchargeDetailsResponse {
/// surcharge value
pub surcharge: Surcharge,
pub surcharge: SurchargeResponse,
/// tax on surcharge value
pub tax_on_surcharge: Option<Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>>,
pub tax_on_surcharge: Option<SurchargePercentage>,
/// surcharge amount for this payment
pub surcharge_amount: i64,
pub display_surcharge_amount: f64,
/// tax on surcharge amount for this payment
pub tax_on_surcharge_amount: i64,
pub display_tax_on_surcharge_amount: f64,
/// sum of display_surcharge_amount and display_tax_on_surcharge_amount
pub display_total_surcharge_amount: f64,
/// sum of original amount,
pub final_amount: i64,
pub display_final_amount: f64,
}

impl SurchargeDetailsResponse {
pub fn is_request_surcharge_matching(
&self,
request_surcharge_details: RequestSurchargeDetails,
) -> bool {
request_surcharge_details.surcharge_amount == self.surcharge_amount
&& request_surcharge_details.tax_amount.unwrap_or(0) == self.tax_on_surcharge_amount
}
pub fn get_total_surcharge_amount(&self) -> i64 {
self.surcharge_amount + self.tax_on_surcharge_amount
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
pub enum SurchargeResponse {
/// Fixed Surcharge value
Fixed(i64),
/// Surcharge percentage
Rate(SurchargePercentage),
}

#[derive(Clone, Debug)]
pub struct SurchargeMetadata {
surcharge_results: HashMap<
(
common_enums::PaymentMethod,
common_enums::PaymentMethodType,
Option<common_enums::CardNetwork>,
),
SurchargeDetailsResponse,
>,
pub payment_attempt_id: String,
}

impl SurchargeMetadata {
pub fn new(payment_attempt_id: String) -> Self {
Self {
surcharge_results: HashMap::new(),
payment_attempt_id,
}
}
pub fn is_empty_result(&self) -> bool {
self.surcharge_results.is_empty()
}
pub fn get_surcharge_results_size(&self) -> usize {
self.surcharge_results.len()
}
pub fn insert_surcharge_details(
&mut self,
payment_method: &common_enums::PaymentMethod,
payment_method_type: &common_enums::PaymentMethodType,
card_network: Option<&common_enums::CardNetwork>,
surcharge_details: SurchargeDetailsResponse,
) {
let key = (
payment_method.to_owned(),
payment_method_type.to_owned(),
card_network.cloned(),
);
self.surcharge_results.insert(key, surcharge_details);
}
pub fn get_surcharge_details(
&self,
payment_method: &common_enums::PaymentMethod,
payment_method_type: &common_enums::PaymentMethodType,
card_network: Option<&common_enums::CardNetwork>,
) -> Option<&SurchargeDetailsResponse> {
let key = &(
payment_method.to_owned(),
payment_method_type.to_owned(),
card_network.cloned(),
);
self.surcharge_results.get(key)
}
pub fn get_surcharge_metadata_redis_key(payment_attempt_id: &str) -> String {
format!("surcharge_metadata_{}", payment_attempt_id)
}
pub fn get_individual_surcharge_key_value_pairs(
&self,
) -> Vec<(String, SurchargeDetailsResponse)> {
self.surcharge_results
.iter()
.map(|((pm, pmt, card_network), surcharge_details)| {
let key =
Self::get_surcharge_details_redis_hashset_key(pm, pmt, card_network.as_ref());
(key, surcharge_details.to_owned())
})
.collect()
}
pub fn get_surcharge_details_redis_hashset_key(
payment_method: &common_enums::PaymentMethod,
payment_method_type: &common_enums::PaymentMethodType,
card_network: Option<&common_enums::CardNetwork>,
) -> String {
if let Some(card_network) = card_network {
format!(
"{}_{}_{}",
payment_method, payment_method_type, card_network
)
} else {
format!("{}_{}", payment_method, payment_method_type)
impl From<Surcharge> for SurchargeResponse {
fn from(value: Surcharge) -> Self {
match value {
Surcharge::Fixed(amount) => Self::Fixed(amount),
Surcharge::Rate(percentage) => Self::Rate(percentage.into()),
}
}
}

#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, 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>),
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)]
pub struct SurchargePercentage {
percentage: f32,
}

impl From<Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>> for SurchargePercentage {
fn from(value: Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>) -> Self {
Self {
percentage: value.get_percentage(),
}
}
}
/// Required fields info used while listing the payment_method_data
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, ToSchema, Hash)]
pub struct RequiredFieldInfo {
Expand Down Expand Up @@ -818,6 +721,9 @@ pub struct CustomerPaymentMethod {
#[schema(example = json!({"mask": "0000"}))]
pub bank: Option<MaskedBankDetails>,

/// Surcharge details for this saved card
pub surcharge_details: Option<SurchargeDetailsResponse>,

/// Whether this payment method requires CVV to be collected
#[schema(example = true)]
pub requires_cvv: bool,
Expand Down
54 changes: 42 additions & 12 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use crate::{
admin, disputes,
enums::{self as api_enums},
ephemeral_key::EphemeralKeyCreateResponse,
payment_methods::{Surcharge, SurchargeDetailsResponse},
refunds,
};

Expand Down Expand Up @@ -339,17 +338,6 @@ impl RequestSurchargeDetails {
pub fn is_surcharge_zero(&self) -> bool {
self.surcharge_amount == 0 && self.tax_amount.unwrap_or(0) == 0
}
pub fn get_surcharge_details_object(&self, original_amount: i64) -> SurchargeDetailsResponse {
let surcharge_amount = self.surcharge_amount;
let tax_on_surcharge_amount = self.tax_amount.unwrap_or(0);
SurchargeDetailsResponse {
surcharge: Surcharge::Fixed(self.surcharge_amount),
tax_on_surcharge: None,
surcharge_amount,
tax_on_surcharge_amount,
final_amount: original_amount + surcharge_amount + tax_on_surcharge_amount,
}
}
pub fn get_total_surcharge_amount(&self) -> i64 {
self.surcharge_amount + self.tax_amount.unwrap_or(0)
}
Expand Down Expand Up @@ -720,6 +708,33 @@ pub struct Card {
pub nick_name: Option<Secret<String>>,
}

impl Card {
fn apply_additional_card_info(&self, additional_card_info: AdditionalCardInfo) -> Self {
Self {
card_number: self.card_number.clone(),
card_exp_month: self.card_exp_month.clone(),
card_exp_year: self.card_exp_year.clone(),
card_holder_name: self.card_holder_name.clone(),
card_cvc: self.card_cvc.clone(),
card_issuer: self
.card_issuer
.clone()
.or(additional_card_info.card_issuer),
card_network: self
.card_network
.clone()
.or(additional_card_info.card_network),
card_type: self.card_type.clone().or(additional_card_info.card_type),
card_issuing_country: self
.card_issuing_country
.clone()
.or(additional_card_info.card_issuing_country),
bank_code: self.bank_code.clone().or(additional_card_info.bank_code),
nick_name: self.nick_name.clone(),
}
}
}

#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct CardToken {
Expand Down Expand Up @@ -889,6 +904,21 @@ impl PaymentMethodData {
| Self::CardToken(_) => None,
}
}
pub fn apply_additional_payment_data(
&self,
additional_payment_data: AdditionalPaymentData,
) -> Self {
if let AdditionalPaymentData::Card(additional_card_info) = additional_payment_data {
match self {
Self::Card(card) => {
Self::Card(card.apply_additional_card_info(*additional_card_info))
}
_ => self.to_owned(),
}
} else {
self.to_owned()
}
}
}

pub trait GetPaymentMethodType {
Expand Down
11 changes: 5 additions & 6 deletions crates/api_models/src/surcharge_decision_configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct SurchargeDetails {
pub surcharge: Surcharge,
pub struct SurchargeDetailsOutput {
pub surcharge: SurchargeOutput,
pub tax_on_surcharge: Option<Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
pub enum Surcharge {
Fixed(i64),
pub enum SurchargeOutput {
Fixed { amount: i64 },
Rate(Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>),
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct SurchargeDecisionConfigs {
pub surcharge_details: Option<SurchargeDetails>,
pub surcharge_details: Option<SurchargeDetailsOutput>,
}
impl EuclidDirFilter for SurchargeDecisionConfigs {
const ALLOWED: &'static [DirKeyKind] = &[
Expand All @@ -30,7 +30,6 @@ impl EuclidDirFilter for SurchargeDecisionConfigs {
DirKeyKind::PaymentAmount,
DirKeyKind::PaymentCurrency,
DirKeyKind::BillingCountry,
DirKeyKind::CardType,
DirKeyKind::CardNetwork,
DirKeyKind::PayLaterType,
DirKeyKind::WalletType,
Expand Down
1 change: 1 addition & 0 deletions crates/common_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ strum = { version = "0.24.1", features = ["derive"] }
thiserror = "1.0.40"
time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] }
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"], optional = true }
utoipa = { version = "3.3.0", features = ["preserve_order"] }

# First party crates
common_enums = { version = "0.1.0", path = "../common_enums" }
Expand Down
15 changes: 14 additions & 1 deletion crates/common_utils/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
use error_stack::{IntoReport, ResultExt};
use serde::{de::Visitor, Deserialize, Deserializer};

use crate::errors::{CustomResult, PercentageError};
use crate::{
consts,
errors::{CustomResult, PercentageError},
};

/// Represents Percentage Value between 0 and 100 both inclusive
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize)]
Expand Down Expand Up @@ -136,3 +139,13 @@ impl<'de, const PRECISION: u8> Deserialize<'de> for Percentage<PRECISION> {
data.deserialize_map(PercentageVisitor::<PRECISION> {})
}
}

/// represents surcharge type and value
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
pub enum Surcharge {
/// Fixed Surcharge value
Fixed(i64),
/// Surcharge percentage
Rate(Percentage<{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH }>),
}
1 change: 0 additions & 1 deletion crates/diesel_models/src/payment_intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ pub struct PaymentIntentNew {
pub merchant_decision: Option<String>,
pub payment_link_id: Option<String>,
pub payment_confirm_source: Option<storage_enums::PaymentSource>,

pub updated_by: String,
pub surcharge_applicable: Option<bool>,
pub request_incremental_authorization: RequestIncrementalAuthorization,
Expand Down
Loading
Loading