Skip to content

Commit

Permalink
feat(payment_methods): Filter payment methods based on pm client secr…
Browse files Browse the repository at this point in the history
…et (#4249)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
Sarthak1799 and hyperswitch-bot[bot] authored May 6, 2024
1 parent 89e5884 commit 575fac6
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 11 deletions.
3 changes: 3 additions & 0 deletions config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -613,3 +613,6 @@ payment_attempts = "hyperswitch-payment-attempt-events"
payment_intents = "hyperswitch-payment-intent-events"
refunds = "hyperswitch-refund-events"
disputes = "hyperswitch-dispute-events"

[saved_payment_methods]
sdk_eligible_payment_methods = ["card"]
3 changes: 3 additions & 0 deletions config/deployments/integration_test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,6 @@ connectors_with_webhook_source_verification_call = "paypal" # List of co

[unmasked_headers]
keys = "user-agent"

[saved_payment_methods]
sdk_eligible_payment_methods = ["card"]
3 changes: 3 additions & 0 deletions config/deployments/production.toml
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,6 @@ connectors_with_webhook_source_verification_call = "paypal" # List of connec

[unmasked_headers]
keys = "user-agent"

[saved_payment_methods]
sdk_eligible_payment_methods = ["card"]
3 changes: 3 additions & 0 deletions config/deployments/sandbox.toml
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,6 @@ connectors_with_webhook_source_verification_call = "paypal" # List of con

[unmasked_headers]
keys = "user-agent"

[saved_payment_methods]
sdk_eligible_payment_methods = ["card"]
3 changes: 3 additions & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -615,3 +615,6 @@ payment_attempts = "hyperswitch-payment-attempt-events"
payment_intents = "hyperswitch-payment-intent-events"
refunds = "hyperswitch-refund-events"
disputes = "hyperswitch-dispute-events"

[saved_payment_methods]
sdk_eligible_payment_methods = ["card"]
3 changes: 3 additions & 0 deletions config/docker_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,6 @@ payment_attempts = "hyperswitch-payment-attempt-events"
payment_intents = "hyperswitch-payment-intent-events"
refunds = "hyperswitch-refund-events"
disputes = "hyperswitch-dispute-events"

[saved_payment_methods]
sdk_eligible_payment_methods = ["card"]
20 changes: 20 additions & 0 deletions crates/diesel_models/src/payment_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ pub enum PaymentMethodUpdate {
status: Option<storage_enums::PaymentMethodStatus>,
locker_id: Option<String>,
payment_method: Option<storage_enums::PaymentMethod>,
payment_method_type: Option<storage_enums::PaymentMethodType>,
payment_method_issuer: Option<String>,
},
ConnectorMandateDetailsUpdate {
connector_mandate_details: Option<serde_json::Value>,
Expand All @@ -162,6 +164,8 @@ pub struct PaymentMethodUpdateInternal {
locker_id: Option<String>,
payment_method: Option<storage_enums::PaymentMethod>,
connector_mandate_details: Option<serde_json::Value>,
payment_method_type: Option<storage_enums::PaymentMethodType>,
payment_method_issuer: Option<String>,
}

impl PaymentMethodUpdateInternal {
Expand Down Expand Up @@ -208,6 +212,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
locker_id: None,
payment_method: None,
connector_mandate_details: None,
payment_method_issuer: None,
payment_method_type: None,
},
PaymentMethodUpdate::PaymentMethodDataUpdate {
payment_method_data,
Expand All @@ -220,6 +226,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
locker_id: None,
payment_method: None,
connector_mandate_details: None,
payment_method_issuer: None,
payment_method_type: None,
},
PaymentMethodUpdate::LastUsedUpdate { last_used_at } => Self {
metadata: None,
Expand All @@ -230,6 +238,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
locker_id: None,
payment_method: None,
connector_mandate_details: None,
payment_method_issuer: None,
payment_method_type: None,
},
PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
network_transaction_id,
Expand All @@ -243,6 +253,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
locker_id: None,
payment_method: None,
connector_mandate_details: None,
payment_method_issuer: None,
payment_method_type: None,
},
PaymentMethodUpdate::StatusUpdate { status } => Self {
metadata: None,
Expand All @@ -253,12 +265,16 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
locker_id: None,
payment_method: None,
connector_mandate_details: None,
payment_method_issuer: None,
payment_method_type: None,
},
PaymentMethodUpdate::AdditionalDataUpdate {
payment_method_data,
status,
locker_id,
payment_method,
payment_method_type,
payment_method_issuer,
} => Self {
metadata: None,
payment_method_data,
Expand All @@ -268,6 +284,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
locker_id,
payment_method,
connector_mandate_details: None,
payment_method_issuer,
payment_method_type,
},
PaymentMethodUpdate::ConnectorMandateDetailsUpdate {
connector_mandate_details,
Expand All @@ -280,6 +298,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
payment_method: None,
connector_mandate_details,
network_transaction_id: None,
payment_method_issuer: None,
payment_method_type: None,
},
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/configs/secrets_transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,5 +365,6 @@ pub(crate) async fn fetch_raw_secrets(
connector_onboarding,
cors: conf.cors,
unmasked_headers: conf.unmasked_headers,
saved_payment_methods: conf.saved_payment_methods,
}
}
6 changes: 6 additions & 0 deletions crates/router/src/configs/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ pub struct Settings<S: SecretState> {
#[cfg(feature = "olap")]
pub connector_onboarding: SecretStateContainer<ConnectorOnboarding, S>,
pub unmasked_headers: UnmaskedHeaders,
pub saved_payment_methods: EligiblePaymentMethods,
}

#[derive(Debug, Deserialize, Clone, Default)]
Expand Down Expand Up @@ -165,6 +166,11 @@ pub struct PaymentMethodAuth {
pub pm_auth_key: Secret<String>,
}

#[derive(Debug, Deserialize, Clone, Default)]
pub struct EligiblePaymentMethods {
pub sdk_eligible_payment_methods: HashSet<String>,
}

#[derive(Debug, Deserialize, Clone, Default)]
pub struct DefaultExchangeRates {
pub base_currency: String,
Expand Down
96 changes: 86 additions & 10 deletions crates/router/src/core/payment_methods/cards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ pub async fn add_payment_method_data(
status: Some(enums::PaymentMethodStatus::Active),
locker_id: Some(locker_id),
payment_method: req.payment_method,
payment_method_issuer: req.payment_method_issuer,
payment_method_type: req.payment_method_type,
};

db.update_payment_method(
Expand Down Expand Up @@ -555,7 +557,7 @@ pub async fn add_payment_method(
match duplication_check {
Some(duplication_check) => match duplication_check {
payment_methods::DataDuplicationCheck::Duplicated => {
get_or_insert_payment_method(
let existing_pm = get_or_insert_payment_method(
db,
req.clone(),
&mut resp,
Expand All @@ -564,6 +566,8 @@ pub async fn add_payment_method(
key_store,
)
.await?;

resp.client_secret = existing_pm.client_secret;
}
payment_methods::DataDuplicationCheck::MetaDataChanged => {
if let Some(card) = req.card.clone() {
Expand All @@ -577,6 +581,8 @@ pub async fn add_payment_method(
)
.await?;

let client_secret = existing_pm.client_secret.clone();

delete_card_from_locker(
&state,
&customer_id,
Expand Down Expand Up @@ -653,6 +659,8 @@ pub async fn add_payment_method(
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to add payment method in db")?;

resp.client_secret = client_secret;
}
}
},
Expand All @@ -667,7 +675,7 @@ pub async fn add_payment_method(
None
};
resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
insert_payment_method(
let pm = insert_payment_method(
db,
&resp,
req,
Expand All @@ -682,6 +690,8 @@ pub async fn add_payment_method(
merchant_account.storage_scheme,
)
.await?;

resp.client_secret = pm.client_secret;
}
}

Expand Down Expand Up @@ -744,6 +754,18 @@ pub async fn update_customer_payment_method(
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;

if pm.status == enums::PaymentMethodStatus::AwaitingData {
return Err(report!(errors::ApiErrorResponse::NotSupported {
message: "Payment method is awaiting data so it cannot be updated".into()
}));
}

if pm.payment_method_data.is_none() {
return Err(report!(errors::ApiErrorResponse::GenericNotFoundError {
message: "payment_method_data not found".to_string()
}));
}

// Fetch the existing payment method data from db
let existing_card_data = decrypt::<serde_json::Value, masking::WithType>(
pm.payment_method_data.clone(),
Expand Down Expand Up @@ -812,7 +834,7 @@ pub async fn update_customer_payment_method(
wallet: req.wallet,
metadata: req.metadata,
customer_id: Some(pm.customer_id.clone()),
client_secret: None,
client_secret: pm.client_secret.clone(),
payment_method_data: None,
card_network: req
.card_network
Expand Down Expand Up @@ -901,7 +923,7 @@ pub async fn update_customer_payment_method(
installment_payment_enabled: false,
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
last_used_at: Some(common_utils::date_time::now()),
client_secret: None,
client_secret: pm.client_secret.clone(),
}
};

Expand Down Expand Up @@ -1691,12 +1713,21 @@ pub async fn list_payment_methods(
let db = &*state.store;
let pm_config_mapping = &state.conf.pm_filters;

let payment_intent = helpers::verify_payment_intent_time_and_client_secret(
db,
&merchant_account,
req.client_secret.clone(),
)
.await?;
let payment_intent = if let Some(cs) = &req.client_secret {
if cs.starts_with("pm_") {
validate_payment_method_and_client_secret(cs, db, &merchant_account).await?;
None
} else {
helpers::verify_payment_intent_time_and_client_secret(
db,
&merchant_account,
req.client_secret.clone(),
)
.await?
}
} else {
None
};

let shipping_address = payment_intent
.as_ref()
Expand Down Expand Up @@ -1839,6 +1870,7 @@ pub async fn list_payment_methods(
pm_config_mapping,
&state.conf.mandates.supported_payment_methods,
&state.conf.mandates.update_mandate_supported,
&state.conf.saved_payment_methods,
)
.await?;
}
Expand Down Expand Up @@ -2535,6 +2567,34 @@ pub async fn list_payment_methods(
))
}

async fn validate_payment_method_and_client_secret(
cs: &String,
db: &dyn db::StorageInterface,
merchant_account: &domain::MerchantAccount,
) -> Result<(), error_stack::Report<errors::ApiErrorResponse>> {
let pm_vec = cs.split("_secret").collect::<Vec<&str>>();
let pm_id = pm_vec
.first()
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
field_name: "client_secret",
})?;

let payment_method = db
.find_payment_method(pm_id, merchant_account.storage_scheme)
.await
.change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
.attach_printable("Unable to find payment method")?;

let client_secret_expired =
authenticate_pm_client_secret_and_check_expiry(cs, &payment_method)?;
if client_secret_expired {
return Err::<(), error_stack::Report<errors::ApiErrorResponse>>(
(errors::ApiErrorResponse::ClientSecretExpired).into(),
);
}
Ok(())
}

pub async fn call_surcharge_decision_management(
state: routes::AppState,
merchant_account: &domain::MerchantAccount,
Expand Down Expand Up @@ -2644,6 +2704,7 @@ pub async fn filter_payment_methods(
config: &settings::ConnectorFilters,
supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate,
supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate,
saved_payment_methods: &settings::EligiblePaymentMethods,
) -> errors::CustomResult<(), errors::ApiErrorResponse> {
for payment_method in payment_methods.into_iter() {
let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method);
Expand Down Expand Up @@ -2761,6 +2822,20 @@ pub async fn filter_payment_methods(
})
.unwrap_or(true);

let filter9 = req
.client_secret
.as_ref()
.map(|cs| {
if cs.starts_with("pm_") {
saved_payment_methods
.sdk_eligible_payment_methods
.contains(payment_method.to_string().as_str())
} else {
true
}
})
.unwrap_or(true);

let connector = connector.clone();

let response_pm_type = ResponsePaymentMethodIntermediate::new(
Expand All @@ -2777,6 +2852,7 @@ pub async fn filter_payment_methods(
&& filter6
&& filter7
&& filter8
&& filter9
{
resp.push(response_pm_type);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/core/payment_methods/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ pub fn mk_add_card_response_hs(
installment_payment_enabled: false, // #[#256]
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
last_used_at: Some(common_utils::date_time::now()), // [#256]
client_secret: None,
client_secret: req.client_secret,
}
}

Expand Down

0 comments on commit 575fac6

Please sign in to comment.