Skip to content

Commit

Permalink
feat: calculate surcharge for customer saved card list (#3039)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
hrithikesh026 and hyperswitch-bot[bot] authored Dec 4, 2023
1 parent 6c7d3a2 commit daf0f09
Show file tree
Hide file tree
Showing 15 changed files with 477 additions and 287 deletions.
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.

68 changes: 38 additions & 30 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use common_utils::{
types::{Percentage, Surcharge},
};
use serde::de;
use utoipa::ToSchema;
use utoipa::{schema, ToSchema};

#[cfg(feature = "payouts")]
use crate::payouts;
Expand Down Expand Up @@ -264,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 @@ -313,31 +300,19 @@ 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 display_surcharge_amount: f64,
/// tax on surcharge amount for this payment
Expand All @@ -348,6 +323,36 @@ pub struct SurchargeDetailsResponse {
pub display_final_amount: f64,
}

#[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),
}

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, 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 @@ -716,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
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
108 changes: 93 additions & 15 deletions crates/router/src/core/payment_methods/cards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ use error_stack::{report, IntoReport, ResultExt};
use masking::Secret;
use router_env::{instrument, tracing};

use super::surcharge_decision_configs::perform_surcharge_decision_management_for_payment_method_list;
use super::surcharge_decision_configs::{
perform_surcharge_decision_management_for_payment_method_list,
perform_surcharge_decision_management_for_saved_cards,
};
use crate::{
configs::settings,
core::{
Expand All @@ -38,7 +41,6 @@ use crate::{
helpers,
routing::{self, SessionFlowRoutingInput},
},
utils::persist_individual_surcharge_details_in_redis,
},
db, logger,
pii::prelude::*,
Expand Down Expand Up @@ -1687,12 +1689,9 @@ pub async fn call_surcharge_decision_management(
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("error performing surcharge decision operation")?;
if !surcharge_results.is_empty_result() {
persist_individual_surcharge_details_in_redis(
&state,
merchant_account,
&surcharge_results,
)
.await?;
surcharge_results
.persist_individual_surcharge_details_in_redis(&state, merchant_account)
.await?;
let _ = state
.store
.update_payment_intent(
Expand All @@ -1711,6 +1710,56 @@ pub async fn call_surcharge_decision_management(
}
}

pub async fn call_surcharge_decision_management_for_saved_card(
state: &routes::AppState,
merchant_account: &domain::MerchantAccount,
payment_attempt: &storage::PaymentAttempt,
payment_intent: storage::PaymentIntent,
customer_payment_method_response: &mut api::CustomerPaymentMethodsListResponse,
) -> errors::RouterResult<()> {
if payment_attempt.surcharge_amount.is_some() {
Ok(())
} else {
let algorithm_ref: routing_types::RoutingAlgorithmRef = merchant_account
.routing_algorithm
.clone()
.map(|val| val.parse_value("routing algorithm"))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Could not decode the routing algorithm")?
.unwrap_or_default();
let surcharge_results = perform_surcharge_decision_management_for_saved_cards(
state,
algorithm_ref,
payment_attempt,
&payment_intent,
&mut customer_payment_method_response.customer_payment_methods,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("error performing surcharge decision operation")?;
if !surcharge_results.is_empty_result() {
surcharge_results
.persist_individual_surcharge_details_in_redis(state, merchant_account)
.await?;
let _ = state
.store
.update_payment_intent(
payment_intent,
storage::PaymentIntentUpdate::SurchargeApplicableUpdate {
surcharge_applicable: true,
updated_by: merchant_account.storage_scheme.to_string(),
},
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
.attach_printable("Failed to update surcharge_applicable in Payment Intent");
}
Ok(())
}
}

#[allow(clippy::too_many_arguments)]
pub async fn filter_payment_methods(
payment_methods: Vec<serde_json::Value>,
Expand Down Expand Up @@ -2195,12 +2244,13 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed(
.await
} else {
let cloned_secret = req.and_then(|r| r.client_secret.as_ref().cloned());
let payment_intent = helpers::verify_payment_intent_time_and_client_secret(
db,
&merchant_account,
cloned_secret,
)
.await?;
let payment_intent: Option<data_models::payments::PaymentIntent> =
helpers::verify_payment_intent_time_and_client_secret(
db,
&merchant_account,
cloned_secret,
)
.await?;
let customer_id = payment_intent
.as_ref()
.and_then(|intent| intent.customer_id.to_owned())
Expand Down Expand Up @@ -2326,6 +2376,7 @@ pub async fn list_customer_payment_method(
created: Some(pm.created_at),
bank_transfer: pmd,
bank: bank_details,
surcharge_details: None,
requires_cvv,
};
customer_pms.push(pma.to_owned());
Expand Down Expand Up @@ -2377,9 +2428,36 @@ pub async fn list_customer_payment_method(
}
}

let response = api::CustomerPaymentMethodsListResponse {
let mut response = api::CustomerPaymentMethodsListResponse {
customer_payment_methods: customer_pms,
};
let payment_attempt = payment_intent
.as_ref()
.async_map(|payment_intent| async {
state
.store
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
&payment_intent.payment_id,
&merchant_account.merchant_id,
&payment_intent.active_attempt.get_id(),
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
})
.await
.transpose()?;

if let Some((payment_attempt, payment_intent)) = payment_attempt.zip(payment_intent) {
call_surcharge_decision_management_for_saved_card(
state,
&merchant_account,
&payment_attempt,
payment_intent,
&mut response,
)
.await?;
}

Ok(services::ApplicationResponse::Json(response))
}
Expand Down
Loading

0 comments on commit daf0f09

Please sign in to comment.