feat(list_pm): allow client secret authentication for list payment method api (#126)

This commit is contained in:
Nishant Joshi
2022-12-13 14:03:33 +05:30
committed by GitHub
parent 3acde2603b
commit 6bf9904867
9 changed files with 268 additions and 121 deletions

View File

@ -1,11 +1,9 @@
use common_utils::pii; use common_utils::pii;
use masking::Secret; use serde::de;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::enums as api_enums; use crate::enums as api_enums;
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct CreatePaymentMethod { pub struct CreatePaymentMethod {
pub merchant_id: Option<String>, pub merchant_id: Option<String>,
@ -18,16 +16,16 @@ pub struct CreatePaymentMethod {
pub customer_id: Option<String>, pub customer_id: Option<String>,
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct CardDetail { pub struct CardDetail {
pub card_number: Secret<String, pii::CardNumber>, pub card_number: masking::Secret<String, pii::CardNumber>,
pub card_exp_month: Secret<String>, pub card_exp_month: masking::Secret<String>,
pub card_exp_year: Secret<String>, pub card_exp_year: masking::Secret<String>,
pub card_holder_name: Option<Secret<String>>, pub card_holder_name: Option<masking::Secret<String>>,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct PaymentMethodResponse { pub struct PaymentMethodResponse {
pub payment_method_id: String, pub payment_method_id: String,
pub payment_method: api_enums::PaymentMethodType, pub payment_method: api_enums::PaymentMethodType,
@ -45,27 +43,28 @@ pub struct PaymentMethodResponse {
pub payment_experience: Option<Vec<String>>, //TODO change it to enum pub payment_experience: Option<Vec<String>>, //TODO change it to enum
pub metadata: Option<serde_json::Value>, pub metadata: Option<serde_json::Value>,
#[serde(default, with = "common_utils::custom_serde::iso8601::option")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
pub created: Option<PrimitiveDateTime>, pub created: Option<time::PrimitiveDateTime>,
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CardDetailFromLocker { pub struct CardDetailFromLocker {
pub scheme: Option<String>, pub scheme: Option<String>,
pub issuer_country: Option<String>, pub issuer_country: Option<String>,
pub last4_digits: Option<String>, pub last4_digits: Option<String>,
#[serde(skip)] #[serde(skip)]
pub card_number: Option<Secret<String, pii::CardNumber>>, pub card_number: Option<masking::Secret<String, pii::CardNumber>>,
pub expiry_month: Option<Secret<String>>, pub expiry_month: Option<masking::Secret<String>>,
pub expiry_year: Option<Secret<String>>, pub expiry_year: Option<masking::Secret<String>>,
pub card_token: Option<Secret<String>>, pub card_token: Option<masking::Secret<String>>,
pub card_holder_name: Option<Secret<String>>, pub card_holder_name: Option<masking::Secret<String>>,
pub card_fingerprint: Option<Secret<String>>, pub card_fingerprint: Option<masking::Secret<String>>,
} }
//List Payment Method //List Payment Method
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, serde::Serialize, Default)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct ListPaymentMethodRequest { pub struct ListPaymentMethodRequest {
pub client_secret: Option<String>,
pub accepted_countries: Option<Vec<String>>, pub accepted_countries: Option<Vec<String>>,
pub accepted_currencies: Option<Vec<api_enums::Currency>>, pub accepted_currencies: Option<Vec<api_enums::Currency>>,
pub amount: Option<i32>, pub amount: Option<i32>,
@ -73,7 +72,65 @@ pub struct ListPaymentMethodRequest {
pub installment_payment_enabled: Option<bool>, pub installment_payment_enabled: Option<bool>,
} }
#[derive(Debug, Serialize, Deserialize)] impl<'de> serde::Deserialize<'de> for ListPaymentMethodRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> de::Visitor<'de> for FieldVisitor {
type Value = ListPaymentMethodRequest;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("Failed while deserializing as map")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut output = ListPaymentMethodRequest::default();
while let Some(key) = map.next_key()? {
match key {
"client_secret" => {
output.client_secret = Some(map.next_value()?);
}
"accepted_countries" => match output.accepted_countries.as_mut() {
Some(inner) => inner.push(map.next_value()?),
None => {
output.accepted_countries = Some(vec![map.next_value()?]);
}
},
"accepted_currencies" => match output.accepted_currencies.as_mut() {
Some(inner) => inner.push(map.next_value()?),
None => {
output.accepted_currencies = Some(vec![map.next_value()?]);
}
},
"amount" => {
output.amount = Some(map.next_value()?);
}
"recurring_enabled" => {
output.recurring_enabled = Some(map.next_value()?);
}
"installment_payment_enabled" => {
output.installment_payment_enabled = Some(map.next_value()?);
}
_ => {}
}
}
Ok(output)
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ListPaymentMethodResponse { pub struct ListPaymentMethodResponse {
pub payment_method: api_enums::PaymentMethodType, pub payment_method: api_enums::PaymentMethodType,
pub payment_method_types: Option<Vec<api_enums::PaymentMethodSubType>>, pub payment_method_types: Option<Vec<api_enums::PaymentMethodSubType>>,
@ -89,19 +146,19 @@ pub struct ListPaymentMethodResponse {
pub payment_experience: Option<Vec<String>>, //TODO change it to enum pub payment_experience: Option<Vec<String>>, //TODO change it to enum
} }
#[derive(Debug, Serialize)] #[derive(Debug, serde::Serialize)]
pub struct ListCustomerPaymentMethodsResponse { pub struct ListCustomerPaymentMethodsResponse {
pub enabled_payment_methods: Vec<ListPaymentMethodResponse>, pub enabled_payment_methods: Vec<ListPaymentMethodResponse>,
pub customer_payment_methods: Vec<CustomerPaymentMethod>, pub customer_payment_methods: Vec<CustomerPaymentMethod>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, serde::Serialize)]
pub struct DeletePaymentMethodResponse { pub struct DeletePaymentMethodResponse {
pub payment_method_id: String, pub payment_method_id: String,
pub deleted: bool, pub deleted: bool,
} }
#[derive(Debug, Serialize)] #[derive(Debug, serde::Serialize)]
pub struct CustomerPaymentMethod { pub struct CustomerPaymentMethod {
pub payment_token: String, pub payment_token: String,
pub customer_id: String, pub customer_id: String,
@ -120,23 +177,23 @@ pub struct CustomerPaymentMethod {
pub card: Option<CardDetailFromLocker>, pub card: Option<CardDetailFromLocker>,
pub metadata: Option<serde_json::Value>, pub metadata: Option<serde_json::Value>,
#[serde(default, with = "common_utils::custom_serde::iso8601::option")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
pub created: Option<PrimitiveDateTime>, pub created: Option<time::PrimitiveDateTime>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct PaymentMethodId { pub struct PaymentMethodId {
pub payment_method_id: String, pub payment_method_id: String,
} }
//------------------------------------------------TokenizeService------------------------------------------------ //------------------------------------------------TokenizeService------------------------------------------------
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizePayloadEncrypted { pub struct TokenizePayloadEncrypted {
pub payload: String, pub payload: String,
pub key_id: String, pub key_id: String,
pub version: Option<String>, pub version: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizePayloadRequest { pub struct TokenizePayloadRequest {
pub value1: String, pub value1: String,
pub value2: String, pub value2: String,
@ -144,30 +201,30 @@ pub struct TokenizePayloadRequest {
pub service_name: String, pub service_name: String,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct GetTokenizePayloadRequest { pub struct GetTokenizePayloadRequest {
pub lookup_key: String, pub lookup_key: String,
pub get_value2: bool, pub get_value2: bool,
} }
#[derive(Debug, Serialize)] #[derive(Debug, serde::Serialize)]
pub struct DeleteTokenizeByTokenRequest { pub struct DeleteTokenizeByTokenRequest {
pub lookup_key: String, pub lookup_key: String,
} }
#[derive(Debug, Serialize)] //FIXME yet to be implemented #[derive(Debug, serde::Serialize)] //FIXME yet to be implemented
pub struct DeleteTokenizeByDateRequest { pub struct DeleteTokenizeByDateRequest {
pub buffer_minutes: i32, pub buffer_minutes: i32,
pub service_name: String, pub service_name: String,
pub max_rows: i32, pub max_rows: i32,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, serde::Deserialize)]
pub struct GetTokenizePayloadResponse { pub struct GetTokenizePayloadResponse {
pub lookup_key: String, pub lookup_key: String,
pub get_value2: Option<bool>, pub get_value2: Option<bool>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TokenizedCardValue1 { pub struct TokenizedCardValue1 {
pub card_number: String, pub card_number: String,
@ -179,7 +236,7 @@ pub struct TokenizedCardValue1 {
pub card_token: Option<String>, pub card_token: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TokenizedCardValue2 { pub struct TokenizedCardValue2 {
pub card_security_code: Option<String>, pub card_security_code: Option<String>,

View File

@ -1,20 +1,18 @@
use std::{collections::HashSet, hash::Hash}; use std::collections;
use error_stack::{report, ResultExt}; use error_stack::{report, ResultExt};
use router_env::{tracing, tracing::instrument}; use router_env::{tracing, tracing::instrument};
use serde_json::Value;
use uuid::Uuid;
use crate::{ use crate::{
configs::settings::Keys, configs::settings,
core::{ core::{
errors::{self, CustomResult, RouterResponse, RouterResult, StorageErrorExt}, errors::{self, StorageErrorExt},
payment_methods::transformers as payment_methods, payment_methods::transformers as payment_methods,
payments::helpers,
}, },
db::StorageInterface, db,
pii::prelude::*, pii::prelude::*,
routes::AppState, routes, services,
services,
types::{ types::{
api::{self, CreatePaymentMethodExt}, api::{self, CreatePaymentMethodExt},
storage::{self, enums}, storage::{self, enums},
@ -25,12 +23,12 @@ use crate::{
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn create_payment_method( pub async fn create_payment_method(
db: &dyn StorageInterface, db: &dyn db::StorageInterface,
req: &api::CreatePaymentMethod, req: &api::CreatePaymentMethod,
customer_id: String, customer_id: String,
payment_method_id: String, payment_method_id: String,
merchant_id: &str, merchant_id: &str,
) -> CustomResult<storage::PaymentMethod, errors::StorageError> { ) -> errors::CustomResult<storage::PaymentMethod, errors::StorageError> {
let response = db let response = db
.insert_payment_method(storage::PaymentMethodNew { .insert_payment_method(storage::PaymentMethodNew {
customer_id, customer_id,
@ -48,10 +46,10 @@ pub async fn create_payment_method(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn add_payment_method( pub async fn add_payment_method(
state: &AppState, state: &routes::AppState,
req: api::CreatePaymentMethod, req: api::CreatePaymentMethod,
merchant_id: String, merchant_id: String,
) -> RouterResponse<api::PaymentMethodResponse> { ) -> errors::RouterResponse<api::PaymentMethodResponse> {
req.validate()?; req.validate()?;
let customer_id = req.customer_id.clone().get_required_value("customer_id")?; let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
@ -92,12 +90,12 @@ pub async fn add_payment_method(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn add_card( pub async fn add_card(
state: &AppState, state: &routes::AppState,
req: api::CreatePaymentMethod, req: api::CreatePaymentMethod,
card: api::CardDetail, card: api::CardDetail,
customer_id: String, customer_id: String,
merchant_id: &str, merchant_id: &str,
) -> CustomResult<api::PaymentMethodResponse, errors::CardVaultError> { ) -> errors::CustomResult<api::PaymentMethodResponse, errors::CardVaultError> {
let locker = &state.conf.locker; let locker = &state.conf.locker;
let db = &*state.store; let db = &*state.store;
let request = payment_methods::mk_add_card_request(locker, &card, &customer_id, &req)?; let request = payment_methods::mk_add_card_request(locker, &card, &customer_id, &req)?;
@ -118,7 +116,7 @@ pub async fn add_card(
}?; }?;
response response
} else { } else {
let card_id = Uuid::new_v4().to_string(); let card_id = uuid::Uuid::new_v4().to_string();
mock_add_card(db, &card_id, &card).await? mock_add_card(db, &card_id, &card).await?
}; };
@ -137,15 +135,15 @@ pub async fn add_card(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn mock_add_card( pub async fn mock_add_card(
db: &dyn StorageInterface, db: &dyn db::StorageInterface,
card_id: &str, card_id: &str,
card: &api::CardDetail, card: &api::CardDetail,
) -> CustomResult<payment_methods::AddCardResponse, errors::CardVaultError> { ) -> errors::CustomResult<payment_methods::AddCardResponse, errors::CardVaultError> {
let locker_mock_up = storage::LockerMockUpNew { let locker_mock_up = storage::LockerMockUpNew {
card_id: card_id.to_string(), card_id: card_id.to_string(),
external_id: Uuid::new_v4().to_string(), external_id: uuid::Uuid::new_v4().to_string(),
card_fingerprint: Uuid::new_v4().to_string(), card_fingerprint: uuid::Uuid::new_v4().to_string(),
card_global_fingerprint: Uuid::new_v4().to_string(), card_global_fingerprint: uuid::Uuid::new_v4().to_string(),
merchant_id: "mm01".to_string(), merchant_id: "mm01".to_string(),
card_number: card.card_number.peek().to_string(), card_number: card.card_number.peek().to_string(),
card_exp_year: card.card_exp_year.peek().to_string(), card_exp_year: card.card_exp_year.peek().to_string(),
@ -174,9 +172,9 @@ pub async fn mock_add_card(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn mock_get_card<'a>( pub async fn mock_get_card<'a>(
db: &dyn StorageInterface, db: &dyn db::StorageInterface,
card_id: &'a str, card_id: &'a str,
) -> CustomResult<payment_methods::GetCardResponse, errors::CardVaultError> { ) -> errors::CustomResult<payment_methods::GetCardResponse, errors::CardVaultError> {
let locker_mock_up = db let locker_mock_up = db
.find_locker_by_card_id(card_id) .find_locker_by_card_id(card_id)
.await .await
@ -202,10 +200,10 @@ pub async fn mock_get_card<'a>(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn get_card_from_legacy_locker<'a>( pub async fn get_card_from_legacy_locker<'a>(
state: &'a AppState, state: &'a routes::AppState,
merchant_id: &'a str, merchant_id: &'a str,
card_id: &'a str, card_id: &'a str,
) -> RouterResult<payment_methods::GetCardResponse> { ) -> errors::RouterResult<payment_methods::GetCardResponse> {
let locker = &state.conf.locker; let locker = &state.conf.locker;
let request = payment_methods::mk_get_card_request(locker, merchant_id, card_id) let request = payment_methods::mk_get_card_request(locker, merchant_id, card_id)
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
@ -236,10 +234,10 @@ pub async fn get_card_from_legacy_locker<'a>(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn delete_card<'a>( pub async fn delete_card<'a>(
state: &'a AppState, state: &'a routes::AppState,
merchant_id: &'a str, merchant_id: &'a str,
card_id: &'a str, card_id: &'a str,
) -> RouterResult<payment_methods::DeleteCardResponse> { ) -> errors::RouterResult<payment_methods::DeleteCardResponse> {
let request = payment_methods::mk_delete_card_request(&state.conf.locker, merchant_id, card_id) let request = payment_methods::mk_delete_card_request(&state.conf.locker, merchant_id, card_id)
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Making Delete card request Failed")?; .attach_printable("Making Delete card request Failed")?;
@ -255,10 +253,18 @@ pub async fn delete_card<'a>(
} }
pub async fn list_payment_methods( pub async fn list_payment_methods(
db: &dyn StorageInterface, db: &dyn db::StorageInterface,
merchant_account: storage::MerchantAccount, merchant_account: storage::MerchantAccount,
mut req: api::ListPaymentMethodRequest, mut req: api::ListPaymentMethodRequest,
) -> RouterResponse<Vec<api::ListPaymentMethodResponse>> { ) -> errors::RouterResponse<Vec<api::ListPaymentMethodResponse>> {
helpers::verify_client_secret(
db,
merchant_account.storage_scheme,
req.client_secret.clone(),
&merchant_account.merchant_id,
)
.await?;
let all_mcas = db let all_mcas = db
.find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id) .find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
.await .await
@ -283,7 +289,7 @@ pub async fn list_payment_methods(
} }
fn filter_payment_methods( fn filter_payment_methods(
payment_methods: Vec<Value>, payment_methods: Vec<serde_json::Value>,
req: &mut api::ListPaymentMethodRequest, req: &mut api::ListPaymentMethodRequest,
resp: &mut Vec<api::ListPaymentMethodResponse>, resp: &mut Vec<api::ListPaymentMethodResponse>,
) { ) {
@ -325,14 +331,14 @@ fn filter_payment_methods(
} }
} }
fn filter_accepted_enum_based<T: Eq + Hash + Clone>( fn filter_accepted_enum_based<T: Eq + std::hash::Hash + Clone>(
left: &Option<Vec<T>>, left: &Option<Vec<T>>,
right: &Option<Vec<T>>, right: &Option<Vec<T>>,
) -> (Option<Vec<T>>, Option<Vec<T>>, bool) { ) -> (Option<Vec<T>>, Option<Vec<T>>, bool) {
match (left, right) { match (left, right) {
(Some(ref l), Some(ref r)) => { (Some(ref l), Some(ref r)) => {
let a: HashSet<&T> = HashSet::from_iter(l.iter()); let a: collections::HashSet<&T> = collections::HashSet::from_iter(l.iter());
let b: HashSet<&T> = HashSet::from_iter(r.iter()); let b: collections::HashSet<&T> = collections::HashSet::from_iter(r.iter());
let y: Vec<T> = a.intersection(&b).map(|&i| i.to_owned()).collect(); let y: Vec<T> = a.intersection(&b).map(|&i| i.to_owned()).collect();
(Some(y), Some(r.to_vec()), true) (Some(y), Some(r.to_vec()), true)
@ -381,10 +387,10 @@ fn filter_installment_based(
} }
pub async fn list_customer_payment_method( pub async fn list_customer_payment_method(
state: &AppState, state: &routes::AppState,
merchant_account: storage::MerchantAccount, merchant_account: storage::MerchantAccount,
customer_id: &str, customer_id: &str,
) -> RouterResponse<api::ListCustomerPaymentMethodsResponse> { ) -> errors::RouterResponse<api::ListCustomerPaymentMethodsResponse> {
let db = &*state.store; let db = &*state.store;
let all_mcas = db let all_mcas = db
.find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id) .find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
@ -427,7 +433,7 @@ pub async fn list_customer_payment_method(
} }
let mut vec = Vec::new(); let mut vec = Vec::new();
for pm in resp.into_iter() { for pm in resp.into_iter() {
let payment_token = Uuid::new_v4().to_string(); let payment_token = uuid::Uuid::new_v4().to_string();
let card = if pm.payment_method == enums::PaymentMethodType::Card { let card = if pm.payment_method == enums::PaymentMethodType::Card {
Some(get_lookup_key_from_locker(state, &payment_token, &pm).await?) Some(get_lookup_key_from_locker(state, &payment_token, &pm).await?)
} else { } else {
@ -462,10 +468,10 @@ pub async fn list_customer_payment_method(
} }
pub async fn get_lookup_key_from_locker( pub async fn get_lookup_key_from_locker(
state: &AppState, state: &routes::AppState,
payment_token: &str, payment_token: &str,
pm: &storage::PaymentMethod, pm: &storage::PaymentMethod,
) -> RouterResult<api::CardDetailFromLocker> { ) -> errors::RouterResult<api::CardDetailFromLocker> {
let get_card_resp = get_card_from_legacy_locker( let get_card_resp = get_card_from_legacy_locker(
state, state,
pm.merchant_id.as_str(), pm.merchant_id.as_str(),
@ -487,10 +493,10 @@ pub struct BasiliskCardSupport;
#[cfg(not(feature = "basilisk"))] #[cfg(not(feature = "basilisk"))]
impl BasiliskCardSupport { impl BasiliskCardSupport {
async fn create_payment_method_data_in_locker( async fn create_payment_method_data_in_locker(
state: &AppState, state: &routes::AppState,
payment_token: &str, payment_token: &str,
card: api::CardDetailFromLocker, card: api::CardDetailFromLocker,
) -> RouterResult<api::CardDetailFromLocker> { ) -> errors::RouterResult<api::CardDetailFromLocker> {
let card_number = card let card_number = card
.card_number .card_number
.clone() .clone()
@ -530,10 +536,10 @@ impl BasiliskCardSupport {
impl BasiliskCardSupport { impl BasiliskCardSupport {
#[instrument(skip_all)] #[instrument(skip_all)]
async fn create_payment_method_data_in_locker( async fn create_payment_method_data_in_locker(
state: &AppState, state: &routes::AppState,
payment_token: &str, payment_token: &str,
card: api::CardDetailFromLocker, card: api::CardDetailFromLocker,
) -> RouterResult<api::CardDetailFromLocker> { ) -> errors::RouterResult<api::CardDetailFromLocker> {
let card_number = card let card_number = card
.card_number .card_number
.clone() .clone()
@ -579,9 +585,9 @@ impl BasiliskCardSupport {
} }
pub async fn get_card_info_value( pub async fn get_card_info_value(
keys: &Keys, keys: &settings::Keys,
card_info: String, card_info: String,
) -> RouterResult<serde_json::Value> { ) -> errors::RouterResult<serde_json::Value> {
let key = services::KeyHandler::get_encryption_key(keys) let key = services::KeyHandler::get_encryption_key(keys)
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError)?; .change_context(errors::ApiErrorResponse::InternalServerError)?;
@ -593,9 +599,9 @@ pub async fn get_card_info_value(
} }
pub async fn get_card_info_from_value( pub async fn get_card_info_from_value(
keys: &Keys, keys: &settings::Keys,
card_info: serde_json::Value, card_info: serde_json::Value,
) -> RouterResult<String> { ) -> errors::RouterResult<String> {
let key = services::KeyHandler::get_encryption_key(keys) let key = services::KeyHandler::get_encryption_key(keys)
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError)?; .change_context(errors::ApiErrorResponse::InternalServerError)?;
@ -608,9 +614,9 @@ pub async fn get_card_info_from_value(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn retrieve_payment_method( pub async fn retrieve_payment_method(
state: &AppState, state: &routes::AppState,
pm: api::PaymentMethodId, pm: api::PaymentMethodId,
) -> RouterResponse<api::PaymentMethodResponse> { ) -> errors::RouterResponse<api::PaymentMethodResponse> {
let db = &*state.store; let db = &*state.store;
let pm = db let pm = db
.find_payment_method(&pm.payment_method_id) .find_payment_method(&pm.payment_method_id)
@ -644,10 +650,10 @@ pub async fn retrieve_payment_method(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn delete_payment_method( pub async fn delete_payment_method(
state: &AppState, state: &routes::AppState,
merchant_account: storage::MerchantAccount, merchant_account: storage::MerchantAccount,
pm: api::PaymentMethodId, pm: api::PaymentMethodId,
) -> RouterResponse<api::DeletePaymentMethodResponse> { ) -> errors::RouterResponse<api::DeletePaymentMethodResponse> {
let pm = state let pm = state
.store .store
.delete_payment_method_by_merchant_id_payment_method_id( .delete_payment_method_by_merchant_id_payment_method_id(
@ -678,11 +684,11 @@ pub async fn delete_payment_method(
//------------------------------------------------TokenizeService------------------------------------------------ //------------------------------------------------TokenizeService------------------------------------------------
pub async fn create_tokenize( pub async fn create_tokenize(
state: &AppState, state: &routes::AppState,
value1: String, value1: String,
value2: Option<String>, value2: Option<String>,
lookup_key: String, lookup_key: String,
) -> RouterResult<String> { ) -> errors::RouterResult<String> {
let payload_to_be_encrypted = api::TokenizePayloadRequest { let payload_to_be_encrypted = api::TokenizePayloadRequest {
value1, value1,
value2: value2.unwrap_or_default(), value2: value2.unwrap_or_default(),
@ -738,10 +744,10 @@ pub async fn create_tokenize(
} }
pub async fn get_tokenized_data( pub async fn get_tokenized_data(
state: &AppState, state: &routes::AppState,
lookup_key: &str, lookup_key: &str,
should_get_value2: bool, should_get_value2: bool,
) -> RouterResult<api::TokenizePayloadRequest> { ) -> errors::RouterResult<api::TokenizePayloadRequest> {
let payload_to_be_encrypted = api::GetTokenizePayloadRequest { let payload_to_be_encrypted = api::GetTokenizePayloadRequest {
lookup_key: lookup_key.to_string(), lookup_key: lookup_key.to_string(),
get_value2: should_get_value2, get_value2: should_get_value2,
@ -792,7 +798,10 @@ pub async fn get_tokenized_data(
} }
} }
pub async fn delete_tokenized_data(state: &AppState, lookup_key: &str) -> RouterResult<String> { pub async fn delete_tokenized_data(
state: &routes::AppState,
lookup_key: &str,
) -> errors::RouterResult<String> {
let payload_to_be_encrypted = api::DeleteTokenizeByTokenRequest { let payload_to_be_encrypted = api::DeleteTokenizeByTokenRequest {
lookup_key: lookup_key.to_string(), lookup_key: lookup_key.to_string(),
}; };

View File

@ -16,7 +16,6 @@ pub use self::operations::{
}; };
use self::{ use self::{
flows::{ConstructFlowSpecificData, Feature}, flows::{ConstructFlowSpecificData, Feature},
helpers::{filter_by_constraints, validate_payment_list_request},
operations::{BoxedOperation, Operation}, operations::{BoxedOperation, Operation},
}; };
use super::errors::StorageErrorExt; use super::errors::StorageErrorExt;
@ -573,10 +572,10 @@ pub async fn list_payments(
merchant: storage::MerchantAccount, merchant: storage::MerchantAccount,
constraints: api::PaymentListConstraints, constraints: api::PaymentListConstraints,
) -> RouterResponse<api::PaymentListResponse> { ) -> RouterResponse<api::PaymentListResponse> {
validate_payment_list_request(&constraints)?; helpers::validate_payment_list_request(&constraints)?;
let merchant_id = &merchant.merchant_id; let merchant_id = &merchant.merchant_id;
let payment_intent = let payment_intent =
filter_by_constraints(db, &constraints, merchant_id, merchant.storage_scheme) helpers::filter_by_constraints(db, &constraints, merchant_id, merchant.storage_scheme)
.await .await
.map_err(|err| err.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound))?; .map_err(|err| err.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound))?;

View File

@ -531,14 +531,17 @@ pub(crate) async fn call_payment_method(
} }
} }
pub(crate) fn client_secret_auth( pub(crate) fn client_secret_auth<P>(
payload: api::PaymentsRequest, payload: P,
auth_type: &services::api::MerchantAuthentication, auth_type: &services::api::MerchantAuthentication,
) -> RouterResult<api::PaymentsRequest> { ) -> RouterResult<P>
where
P: services::Authenticate,
{
match auth_type { match auth_type {
services::MerchantAuthentication::PublishableKey => { services::MerchantAuthentication::PublishableKey => {
payload payload
.client_secret .get_client_secret()
.check_value_present("client_secret") .check_value_present("client_secret")
.change_context(errors::ApiErrorResponse::MissingRequiredField { .change_context(errors::ApiErrorResponse::MissingRequiredField {
field_name: "client_secret".to_owned(), field_name: "client_secret".to_owned(),
@ -546,7 +549,7 @@ pub(crate) fn client_secret_auth(
Ok(payload) Ok(payload)
} }
services::api::MerchantAuthentication::ApiKey => { services::api::MerchantAuthentication::ApiKey => {
if payload.client_secret.is_some() { if payload.get_client_secret().is_some() {
Err(report!(errors::ApiErrorResponse::InvalidRequestData { Err(report!(errors::ApiErrorResponse::InvalidRequestData {
message: "client_secret is not a valid parameter".to_owned(), message: "client_secret is not a valid parameter".to_owned(),
})) }))
@ -1178,3 +1181,56 @@ pub fn generate_mandate(
(_, _) => None, (_, _) => None,
} }
} }
// A function to manually authenticate the client secret
pub(crate) fn authenticate_client_secret(
request_client_secret: Option<&String>,
payment_intent_client_secret: Option<&String>,
) -> Result<(), errors::ApiErrorResponse> {
match (request_client_secret, payment_intent_client_secret) {
(Some(req_cs), Some(pi_cs)) => utils::when(
req_cs.ne(pi_cs),
Err(errors::ApiErrorResponse::ClientSecretInvalid),
),
_ => Ok(()),
}
}
// A function to perform database lookup and then verify the client secret
pub(crate) async fn verify_client_secret(
db: &dyn StorageInterface,
storage_scheme: storage_enums::MerchantStorageScheme,
client_secret: Option<String>,
merchant_id: &str,
) -> error_stack::Result<(), errors::ApiErrorResponse> {
match client_secret {
None => Ok(()),
Some(cs) => {
let payment_id = cs.split('_').take(2).collect::<Vec<&str>>().join("_");
let payment_intent = db
.find_payment_intent_by_payment_id_merchant_id(
&payment_id,
merchant_id,
storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::PaymentNotFound)?;
authenticate_client_secret(Some(&cs), payment_intent.client_secret.as_ref())
.map_err(|err| err.into())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_authenticate_client_secret() {
let req_cs = Some("1".to_string());
let pi_cs = Some("2".to_string());
assert!(authenticate_client_secret(req_cs.as_ref(), pi_cs.as_ref()).is_err())
}
}

View File

@ -61,13 +61,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
})?; })?;
if let Some(ref req_cs) = request.client_secret { helpers::authenticate_client_secret(
if let Some(ref pi_cs) = payment_intent.client_secret { request.client_secret.as_ref(),
if req_cs.ne(pi_cs) { payment_intent.client_secret.as_ref(),
return Err(report!(errors::ApiErrorResponse::ClientSecretInvalid)); )?;
}
}
}
let browser_info = request let browser_info = request
.browser_info .browser_info

View File

@ -1,7 +1,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use async_trait::async_trait; use async_trait::async_trait;
use error_stack::{report, ResultExt}; use error_stack::ResultExt;
use router_derive::PaymentOperation; use router_derive::PaymentOperation;
use router_env::{instrument, tracing}; use router_env::{instrument, tracing};
@ -73,11 +73,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
let amount = payment_intent.amount.into(); let amount = payment_intent.amount.into();
if let Some(ref payment_intent_client_secret) = payment_intent.client_secret { helpers::authenticate_client_secret(
if request.client_secret.ne(payment_intent_client_secret) { Some(&request.client_secret),
return Err(report!(errors::ApiErrorResponse::ClientSecretInvalid)); payment_intent.client_secret.as_ref(),
} )?;
}
let shipping_address = helpers::get_address_for_payment_request( let shipping_address = helpers::get_address_for_payment_request(
db, db,

View File

@ -85,13 +85,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
})?; })?;
if let Some(ref req_cs) = request.client_secret { helpers::authenticate_client_secret(
if let Some(ref pi_cs) = payment_intent.client_secret { request.client_secret.as_ref(),
if req_cs.ne(pi_cs) { payment_intent.client_secret.as_ref(),
return Err(report!(errors::ApiErrorResponse::ClientSecretInvalid)); )?;
}
}
}
let shipping_address = helpers::get_address_for_payment_request( let shipping_address = helpers::get_address_for_payment_request(
db, db,

View File

@ -10,6 +10,7 @@ use crate::{
errors::{http_not_implemented, BachResult}, errors::{http_not_implemented, BachResult},
payment_methods::cards, payment_methods::cards,
}, },
services,
services::api, services::api,
types::api::payment_methods::{self, PaymentMethodId}, types::api::payment_methods::{self, PaymentMethodId},
}; };
@ -44,14 +45,20 @@ pub async fn list_payment_method_api(
json_payload: web::Query<payment_methods::ListPaymentMethodRequest>, json_payload: web::Query<payment_methods::ListPaymentMethodRequest>,
) -> HttpResponse { ) -> HttpResponse {
//let merchant_id = merchant_id.into_inner(); //let merchant_id = merchant_id.into_inner();
let (payload, auth_type) =
match api::get_auth_type_and_check_client_secret(&req, json_payload.into_inner()) {
Ok(values) => values,
Err(err) => return api::log_and_return_error_response(err),
};
api::server_wrap( api::server_wrap(
&state, &state,
&req, &req,
json_payload.into_inner(), payload,
|state, merchant_account, req| { |state, merchant_account, req| {
cards::list_payment_methods(&*state.store, merchant_account, req) cards::list_payment_methods(&*state.store, merchant_account, req)
}, },
api::MerchantAuthentication::ApiKey, auth_type,
) )
.await .await
} }
@ -65,6 +72,13 @@ pub async fn list_customer_payment_method_api(
json_payload: web::Query<payment_methods::ListPaymentMethodRequest>, json_payload: web::Query<payment_methods::ListPaymentMethodRequest>,
) -> HttpResponse { ) -> HttpResponse {
let customer_id = customer_id.into_inner().0; let customer_id = customer_id.into_inner().0;
let auth_type =
match services::authenticate_eph_key(&req, &*state.store, customer_id.clone()).await {
Ok(auth_type) => auth_type,
Err(err) => return api::log_and_return_error_response(err),
};
api::server_wrap( api::server_wrap(
&state, &state,
&req, &req,
@ -72,7 +86,7 @@ pub async fn list_customer_payment_method_api(
|state, merchant_account, _| { |state, merchant_account, _| {
cards::list_customer_payment_method(state, merchant_account, &customer_id) cards::list_customer_payment_method(state, merchant_account, &customer_id)
}, },
api::MerchantAuthentication::ApiKey, auth_type,
) )
.await .await
} }

View File

@ -586,10 +586,13 @@ pub async fn authenticate_connector<'a>(
} }
} }
pub(crate) fn get_auth_type_and_check_client_secret( pub(crate) fn get_auth_type_and_check_client_secret<P>(
req: &actix_web::HttpRequest, req: &actix_web::HttpRequest,
payload: types::api::PaymentsRequest, payload: P,
) -> RouterResult<(types::api::PaymentsRequest, MerchantAuthentication)> { ) -> RouterResult<(P, MerchantAuthentication)>
where
P: Authenticate,
{
let auth_type = get_auth_type(req)?; let auth_type = get_auth_type(req)?;
Ok(( Ok((
payments::helpers::client_secret_auth(payload, &auth_type)?, payments::helpers::client_secret_auth(payload, &auth_type)?,
@ -701,6 +704,22 @@ pub trait ConnectorRedirectResponse {
} }
} }
pub trait Authenticate {
fn get_client_secret(&self) -> Option<&String>;
}
impl Authenticate for api_models::payments::PaymentsRequest {
fn get_client_secret(&self) -> Option<&String> {
self.client_secret.as_ref()
}
}
impl Authenticate for api_models::payment_methods::ListPaymentMethodRequest {
fn get_client_secret(&self) -> Option<&String> {
self.client_secret.as_ref()
}
}
pub fn build_redirection_form(form: &RedirectForm) -> maud::Markup { pub fn build_redirection_form(form: &RedirectForm) -> maud::Markup {
use maud::PreEscaped; use maud::PreEscaped;