refactor(mandates): add validations for recurring mandates using payment_method_id (#4263)

This commit is contained in:
Chethan Rao
2024-04-03 20:11:59 +05:30
committed by GitHub
parent 4445a86207
commit 49cfe72cd2
6 changed files with 105 additions and 5 deletions

View File

@ -282,6 +282,28 @@ fn saved_in_locker_default() -> bool {
true
}
impl From<CardDetailFromLocker> for payments::AdditionalCardInfo {
fn from(item: CardDetailFromLocker) -> Self {
Self {
card_issuer: item.card_issuer,
card_network: item.card_network,
card_type: item.card_type,
card_issuing_country: item.issuer_country,
bank_code: None,
last4: item.last4_digits,
card_isin: item.card_isin,
card_extended_bin: item
.card_number
.map(|card_number| card_number.get_card_extended_bin()),
card_exp_month: item.expiry_month,
card_exp_year: item.expiry_year,
card_holder_name: item.card_holder_name,
payment_checks: None,
authentication_data: None,
}
}
}
impl From<CardDetailsPaymentMethod> for CardDetailFromLocker {
fn from(item: CardDetailsPaymentMethod) -> Self {
Self {

View File

@ -469,6 +469,18 @@ pub async fn get_token_pm_type_mandate_details(
errors::ApiErrorResponse::PaymentMethodNotFound,
)?;
let customer_id = request
.customer_id
.clone()
.get_required_value("customer_id")?;
verify_mandate_details_for_recurring_payments(
&payment_method_info.merchant_id,
&merchant_account.merchant_id,
&payment_method_info.customer_id,
&customer_id,
)?;
(
None,
Some(payment_method_info.payment_method),
@ -869,6 +881,7 @@ pub fn validate_mandate(
pub fn validate_recurring_details_and_token(
recurring_details: &Option<RecurringDetails>,
payment_token: &Option<String>,
mandate_id: &Option<String>,
) -> CustomResult<(), errors::ApiErrorResponse> {
utils::when(
recurring_details.is_some() && payment_token.is_some(),
@ -880,6 +893,12 @@ pub fn validate_recurring_details_and_token(
},
)?;
utils::when(recurring_details.is_some() && mandate_id.is_some(), || {
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
message: "Expected one out of recurring_details and mandate_id but got both".into()
}))
})?;
Ok(())
}
@ -1080,6 +1099,24 @@ pub fn verify_mandate_details(
)
}
pub fn verify_mandate_details_for_recurring_payments(
mandate_merchant_id: &str,
merchant_id: &str,
mandate_customer_id: &str,
customer_id: &str,
) -> RouterResult<()> {
if mandate_merchant_id != merchant_id {
Err(report!(errors::ApiErrorResponse::MandateNotFound))?
}
if mandate_customer_id != customer_id {
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
message: "customer_id must match mandate customer_id".into()
}))?
}
Ok(())
}
#[instrument(skip_all)]
pub fn payment_attempt_status_fsm(
payment_method_data: &Option<api::payments::PaymentMethodDataRequest>,

View File

@ -461,6 +461,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
helpers::validate_recurring_details_and_token(
&request.recurring_details,
&request.payment_token,
&request.mandate_id,
)?;
Ok((

View File

@ -1205,6 +1205,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
helpers::validate_recurring_details_and_token(
&request.recurring_details,
&request.payment_token,
&request.mandate_id,
)?;
let payment_id = request

View File

@ -1,15 +1,17 @@
use std::marker::PhantomData;
use api_models::{enums::FrmSuggestion, mandates::RecurringDetails};
use api_models::{
enums::FrmSuggestion, mandates::RecurringDetails, payment_methods::PaymentMethodsData,
};
use async_trait::async_trait;
use common_utils::ext_traits::{AsyncExt, Encode, ValueExt};
use data_models::{
mandates::{MandateData, MandateDetails},
payments::payment_attempt::PaymentAttempt,
};
use diesel_models::ephemeral_key;
use diesel_models::{ephemeral_key, PaymentMethod};
use error_stack::{self, ResultExt};
use masking::PeekInterface;
use masking::{ExposeInterface, PeekInterface};
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
use time::PrimitiveDateTime;
@ -259,6 +261,8 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
payment_method_billing_address
.as_ref()
.map(|address| address.address_id.clone()),
&payment_method_info,
merchant_key_store,
)
.await?;
@ -691,6 +695,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
helpers::validate_recurring_details_and_token(
&request.recurring_details,
&request.payment_token,
&request.mandate_id,
)?;
if request.confirm.unwrap_or(false) {
@ -744,6 +749,8 @@ impl PaymentCreate {
browser_info: Option<serde_json::Value>,
state: &AppState,
payment_method_billing_address_id: Option<String>,
payment_method_info: &Option<PaymentMethod>,
key_store: &domain::MerchantKeyStore,
) -> RouterResult<(
storage::PaymentAttemptNew,
Option<api_models::payments::AdditionalPaymentData>,
@ -753,7 +760,7 @@ impl PaymentCreate {
helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm);
let (amount, currency) = (money.0, Some(money.1));
let additional_pm_data = request
let mut additional_pm_data = request
.payment_method_data
.as_ref()
.async_map(|payment_method_data| async {
@ -764,6 +771,35 @@ impl PaymentCreate {
.await
})
.await;
if additional_pm_data.is_none() {
// If recurring payment is made using payment_method_id, then fetch payment_method_data from retrieved payment_method object
additional_pm_data = payment_method_info
.as_ref()
.async_map(|pm_info| async {
domain::types::decrypt::<serde_json::Value, masking::WithType>(
pm_info.payment_method_data.clone(),
key_store.key.get_inner().peek(),
)
.await
.change_context(errors::StorageError::DecryptionError)
.attach_printable("unable to decrypt card details")
.ok()
.flatten()
.map(|x| x.into_inner().expose())
.and_then(|v| serde_json::from_value::<PaymentMethodsData>(v).ok())
.and_then(|pmd| match pmd {
PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)),
_ => None,
})
})
.await
.flatten()
.map(|card| {
api_models::payments::AdditionalPaymentData::Card(Box::new(card.into()))
})
};
let additional_pm_data_value = additional_pm_data
.as_ref()
.map(Encode::encode_to_value)
@ -842,7 +878,9 @@ impl PaymentCreate {
connector: None,
error_message: None,
offer_amount: None,
payment_method_id: None,
payment_method_id: payment_method_info
.as_ref()
.map(|pm_info| pm_info.payment_method_id.clone()),
cancellation_reason: None,
error_code: None,
connector_metadata: None,

View File

@ -754,6 +754,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
helpers::validate_recurring_details_and_token(
&request.recurring_details,
&request.payment_token,
&request.mandate_id,
)?;
Ok((