Skip to content

Commit 575fac6

Browse files
feat(payment_methods): Filter payment methods based on pm client secret (#4249)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
1 parent 89e5884 commit 575fac6

File tree

11 files changed

+132
-11
lines changed

11 files changed

+132
-11
lines changed

config/config.example.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,3 +613,6 @@ payment_attempts = "hyperswitch-payment-attempt-events"
613613
payment_intents = "hyperswitch-payment-intent-events"
614614
refunds = "hyperswitch-refund-events"
615615
disputes = "hyperswitch-dispute-events"
616+
617+
[saved_payment_methods]
618+
sdk_eligible_payment_methods = ["card"]

config/deployments/integration_test.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,6 @@ connectors_with_webhook_source_verification_call = "paypal" # List of co
319319

320320
[unmasked_headers]
321321
keys = "user-agent"
322+
323+
[saved_payment_methods]
324+
sdk_eligible_payment_methods = ["card"]

config/deployments/production.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,6 @@ connectors_with_webhook_source_verification_call = "paypal" # List of connec
330330

331331
[unmasked_headers]
332332
keys = "user-agent"
333+
334+
[saved_payment_methods]
335+
sdk_eligible_payment_methods = ["card"]

config/deployments/sandbox.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,6 @@ connectors_with_webhook_source_verification_call = "paypal" # List of con
334334

335335
[unmasked_headers]
336336
keys = "user-agent"
337+
338+
[saved_payment_methods]
339+
sdk_eligible_payment_methods = ["card"]

config/development.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,3 +615,6 @@ payment_attempts = "hyperswitch-payment-attempt-events"
615615
payment_intents = "hyperswitch-payment-intent-events"
616616
refunds = "hyperswitch-refund-events"
617617
disputes = "hyperswitch-dispute-events"
618+
619+
[saved_payment_methods]
620+
sdk_eligible_payment_methods = ["card"]

config/docker_compose.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,6 @@ payment_attempts = "hyperswitch-payment-attempt-events"
474474
payment_intents = "hyperswitch-payment-intent-events"
475475
refunds = "hyperswitch-refund-events"
476476
disputes = "hyperswitch-dispute-events"
477+
478+
[saved_payment_methods]
479+
sdk_eligible_payment_methods = ["card"]

crates/diesel_models/src/payment_method.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ pub enum PaymentMethodUpdate {
143143
status: Option<storage_enums::PaymentMethodStatus>,
144144
locker_id: Option<String>,
145145
payment_method: Option<storage_enums::PaymentMethod>,
146+
payment_method_type: Option<storage_enums::PaymentMethodType>,
147+
payment_method_issuer: Option<String>,
146148
},
147149
ConnectorMandateDetailsUpdate {
148150
connector_mandate_details: Option<serde_json::Value>,
@@ -162,6 +164,8 @@ pub struct PaymentMethodUpdateInternal {
162164
locker_id: Option<String>,
163165
payment_method: Option<storage_enums::PaymentMethod>,
164166
connector_mandate_details: Option<serde_json::Value>,
167+
payment_method_type: Option<storage_enums::PaymentMethodType>,
168+
payment_method_issuer: Option<String>,
165169
}
166170

167171
impl PaymentMethodUpdateInternal {
@@ -208,6 +212,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
208212
locker_id: None,
209213
payment_method: None,
210214
connector_mandate_details: None,
215+
payment_method_issuer: None,
216+
payment_method_type: None,
211217
},
212218
PaymentMethodUpdate::PaymentMethodDataUpdate {
213219
payment_method_data,
@@ -220,6 +226,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
220226
locker_id: None,
221227
payment_method: None,
222228
connector_mandate_details: None,
229+
payment_method_issuer: None,
230+
payment_method_type: None,
223231
},
224232
PaymentMethodUpdate::LastUsedUpdate { last_used_at } => Self {
225233
metadata: None,
@@ -230,6 +238,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
230238
locker_id: None,
231239
payment_method: None,
232240
connector_mandate_details: None,
241+
payment_method_issuer: None,
242+
payment_method_type: None,
233243
},
234244
PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
235245
network_transaction_id,
@@ -243,6 +253,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
243253
locker_id: None,
244254
payment_method: None,
245255
connector_mandate_details: None,
256+
payment_method_issuer: None,
257+
payment_method_type: None,
246258
},
247259
PaymentMethodUpdate::StatusUpdate { status } => Self {
248260
metadata: None,
@@ -253,12 +265,16 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
253265
locker_id: None,
254266
payment_method: None,
255267
connector_mandate_details: None,
268+
payment_method_issuer: None,
269+
payment_method_type: None,
256270
},
257271
PaymentMethodUpdate::AdditionalDataUpdate {
258272
payment_method_data,
259273
status,
260274
locker_id,
261275
payment_method,
276+
payment_method_type,
277+
payment_method_issuer,
262278
} => Self {
263279
metadata: None,
264280
payment_method_data,
@@ -268,6 +284,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
268284
locker_id,
269285
payment_method,
270286
connector_mandate_details: None,
287+
payment_method_issuer,
288+
payment_method_type,
271289
},
272290
PaymentMethodUpdate::ConnectorMandateDetailsUpdate {
273291
connector_mandate_details,
@@ -280,6 +298,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
280298
payment_method: None,
281299
connector_mandate_details,
282300
network_transaction_id: None,
301+
payment_method_issuer: None,
302+
payment_method_type: None,
283303
},
284304
}
285305
}

crates/router/src/configs/secrets_transformers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,5 +365,6 @@ pub(crate) async fn fetch_raw_secrets(
365365
connector_onboarding,
366366
cors: conf.cors,
367367
unmasked_headers: conf.unmasked_headers,
368+
saved_payment_methods: conf.saved_payment_methods,
368369
}
369370
}

crates/router/src/configs/settings.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ pub struct Settings<S: SecretState> {
119119
#[cfg(feature = "olap")]
120120
pub connector_onboarding: SecretStateContainer<ConnectorOnboarding, S>,
121121
pub unmasked_headers: UnmaskedHeaders,
122+
pub saved_payment_methods: EligiblePaymentMethods,
122123
}
123124

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

169+
#[derive(Debug, Deserialize, Clone, Default)]
170+
pub struct EligiblePaymentMethods {
171+
pub sdk_eligible_payment_methods: HashSet<String>,
172+
}
173+
168174
#[derive(Debug, Deserialize, Clone, Default)]
169175
pub struct DefaultExchangeRates {
170176
pub base_currency: String,

crates/router/src/core/payment_methods/cards.rs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ pub async fn add_payment_method_data(
441441
status: Some(enums::PaymentMethodStatus::Active),
442442
locker_id: Some(locker_id),
443443
payment_method: req.payment_method,
444+
payment_method_issuer: req.payment_method_issuer,
445+
payment_method_type: req.payment_method_type,
444446
};
445447

446448
db.update_payment_method(
@@ -555,7 +557,7 @@ pub async fn add_payment_method(
555557
match duplication_check {
556558
Some(duplication_check) => match duplication_check {
557559
payment_methods::DataDuplicationCheck::Duplicated => {
558-
get_or_insert_payment_method(
560+
let existing_pm = get_or_insert_payment_method(
559561
db,
560562
req.clone(),
561563
&mut resp,
@@ -564,6 +566,8 @@ pub async fn add_payment_method(
564566
key_store,
565567
)
566568
.await?;
569+
570+
resp.client_secret = existing_pm.client_secret;
567571
}
568572
payment_methods::DataDuplicationCheck::MetaDataChanged => {
569573
if let Some(card) = req.card.clone() {
@@ -577,6 +581,8 @@ pub async fn add_payment_method(
577581
)
578582
.await?;
579583

584+
let client_secret = existing_pm.client_secret.clone();
585+
580586
delete_card_from_locker(
581587
&state,
582588
&customer_id,
@@ -653,6 +659,8 @@ pub async fn add_payment_method(
653659
.await
654660
.change_context(errors::ApiErrorResponse::InternalServerError)
655661
.attach_printable("Failed to add payment method in db")?;
662+
663+
resp.client_secret = client_secret;
656664
}
657665
}
658666
},
@@ -667,7 +675,7 @@ pub async fn add_payment_method(
667675
None
668676
};
669677
resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
670-
insert_payment_method(
678+
let pm = insert_payment_method(
671679
db,
672680
&resp,
673681
req,
@@ -682,6 +690,8 @@ pub async fn add_payment_method(
682690
merchant_account.storage_scheme,
683691
)
684692
.await?;
693+
694+
resp.client_secret = pm.client_secret;
685695
}
686696
}
687697

@@ -744,6 +754,18 @@ pub async fn update_customer_payment_method(
744754
.await
745755
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
746756

757+
if pm.status == enums::PaymentMethodStatus::AwaitingData {
758+
return Err(report!(errors::ApiErrorResponse::NotSupported {
759+
message: "Payment method is awaiting data so it cannot be updated".into()
760+
}));
761+
}
762+
763+
if pm.payment_method_data.is_none() {
764+
return Err(report!(errors::ApiErrorResponse::GenericNotFoundError {
765+
message: "payment_method_data not found".to_string()
766+
}));
767+
}
768+
747769
// Fetch the existing payment method data from db
748770
let existing_card_data = decrypt::<serde_json::Value, masking::WithType>(
749771
pm.payment_method_data.clone(),
@@ -812,7 +834,7 @@ pub async fn update_customer_payment_method(
812834
wallet: req.wallet,
813835
metadata: req.metadata,
814836
customer_id: Some(pm.customer_id.clone()),
815-
client_secret: None,
837+
client_secret: pm.client_secret.clone(),
816838
payment_method_data: None,
817839
card_network: req
818840
.card_network
@@ -901,7 +923,7 @@ pub async fn update_customer_payment_method(
901923
installment_payment_enabled: false,
902924
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
903925
last_used_at: Some(common_utils::date_time::now()),
904-
client_secret: None,
926+
client_secret: pm.client_secret.clone(),
905927
}
906928
};
907929

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

1694-
let payment_intent = helpers::verify_payment_intent_time_and_client_secret(
1695-
db,
1696-
&merchant_account,
1697-
req.client_secret.clone(),
1698-
)
1699-
.await?;
1716+
let payment_intent = if let Some(cs) = &req.client_secret {
1717+
if cs.starts_with("pm_") {
1718+
validate_payment_method_and_client_secret(cs, db, &merchant_account).await?;
1719+
None
1720+
} else {
1721+
helpers::verify_payment_intent_time_and_client_secret(
1722+
db,
1723+
&merchant_account,
1724+
req.client_secret.clone(),
1725+
)
1726+
.await?
1727+
}
1728+
} else {
1729+
None
1730+
};
17001731

17011732
let shipping_address = payment_intent
17021733
.as_ref()
@@ -1839,6 +1870,7 @@ pub async fn list_payment_methods(
18391870
pm_config_mapping,
18401871
&state.conf.mandates.supported_payment_methods,
18411872
&state.conf.mandates.update_mandate_supported,
1873+
&state.conf.saved_payment_methods,
18421874
)
18431875
.await?;
18441876
}
@@ -2535,6 +2567,34 @@ pub async fn list_payment_methods(
25352567
))
25362568
}
25372569

2570+
async fn validate_payment_method_and_client_secret(
2571+
cs: &String,
2572+
db: &dyn db::StorageInterface,
2573+
merchant_account: &domain::MerchantAccount,
2574+
) -> Result<(), error_stack::Report<errors::ApiErrorResponse>> {
2575+
let pm_vec = cs.split("_secret").collect::<Vec<&str>>();
2576+
let pm_id = pm_vec
2577+
.first()
2578+
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
2579+
field_name: "client_secret",
2580+
})?;
2581+
2582+
let payment_method = db
2583+
.find_payment_method(pm_id, merchant_account.storage_scheme)
2584+
.await
2585+
.change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
2586+
.attach_printable("Unable to find payment method")?;
2587+
2588+
let client_secret_expired =
2589+
authenticate_pm_client_secret_and_check_expiry(cs, &payment_method)?;
2590+
if client_secret_expired {
2591+
return Err::<(), error_stack::Report<errors::ApiErrorResponse>>(
2592+
(errors::ApiErrorResponse::ClientSecretExpired).into(),
2593+
);
2594+
}
2595+
Ok(())
2596+
}
2597+
25382598
pub async fn call_surcharge_decision_management(
25392599
state: routes::AppState,
25402600
merchant_account: &domain::MerchantAccount,
@@ -2644,6 +2704,7 @@ pub async fn filter_payment_methods(
26442704
config: &settings::ConnectorFilters,
26452705
supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate,
26462706
supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate,
2707+
saved_payment_methods: &settings::EligiblePaymentMethods,
26472708
) -> errors::CustomResult<(), errors::ApiErrorResponse> {
26482709
for payment_method in payment_methods.into_iter() {
26492710
let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method);
@@ -2761,6 +2822,20 @@ pub async fn filter_payment_methods(
27612822
})
27622823
.unwrap_or(true);
27632824

2825+
let filter9 = req
2826+
.client_secret
2827+
.as_ref()
2828+
.map(|cs| {
2829+
if cs.starts_with("pm_") {
2830+
saved_payment_methods
2831+
.sdk_eligible_payment_methods
2832+
.contains(payment_method.to_string().as_str())
2833+
} else {
2834+
true
2835+
}
2836+
})
2837+
.unwrap_or(true);
2838+
27642839
let connector = connector.clone();
27652840

27662841
let response_pm_type = ResponsePaymentMethodIntermediate::new(
@@ -2777,6 +2852,7 @@ pub async fn filter_payment_methods(
27772852
&& filter6
27782853
&& filter7
27792854
&& filter8
2855+
&& filter9
27802856
{
27812857
resp.push(response_pm_type);
27822858
}

0 commit comments

Comments
 (0)