mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
refactor(mandates): add validations for recurring mandates using payment_method_id (#4263)
This commit is contained in:
@ -282,6 +282,28 @@ fn saved_in_locker_default() -> bool {
|
|||||||
true
|
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 {
|
impl From<CardDetailsPaymentMethod> for CardDetailFromLocker {
|
||||||
fn from(item: CardDetailsPaymentMethod) -> Self {
|
fn from(item: CardDetailsPaymentMethod) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@ -469,6 +469,18 @@ pub async fn get_token_pm_type_mandate_details(
|
|||||||
errors::ApiErrorResponse::PaymentMethodNotFound,
|
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,
|
None,
|
||||||
Some(payment_method_info.payment_method),
|
Some(payment_method_info.payment_method),
|
||||||
@ -869,6 +881,7 @@ pub fn validate_mandate(
|
|||||||
pub fn validate_recurring_details_and_token(
|
pub fn validate_recurring_details_and_token(
|
||||||
recurring_details: &Option<RecurringDetails>,
|
recurring_details: &Option<RecurringDetails>,
|
||||||
payment_token: &Option<String>,
|
payment_token: &Option<String>,
|
||||||
|
mandate_id: &Option<String>,
|
||||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||||
utils::when(
|
utils::when(
|
||||||
recurring_details.is_some() && payment_token.is_some(),
|
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(())
|
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)]
|
#[instrument(skip_all)]
|
||||||
pub fn payment_attempt_status_fsm(
|
pub fn payment_attempt_status_fsm(
|
||||||
payment_method_data: &Option<api::payments::PaymentMethodDataRequest>,
|
payment_method_data: &Option<api::payments::PaymentMethodDataRequest>,
|
||||||
|
|||||||
@ -461,6 +461,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
|
|||||||
helpers::validate_recurring_details_and_token(
|
helpers::validate_recurring_details_and_token(
|
||||||
&request.recurring_details,
|
&request.recurring_details,
|
||||||
&request.payment_token,
|
&request.payment_token,
|
||||||
|
&request.mandate_id,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
|||||||
@ -1205,6 +1205,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
|
|||||||
helpers::validate_recurring_details_and_token(
|
helpers::validate_recurring_details_and_token(
|
||||||
&request.recurring_details,
|
&request.recurring_details,
|
||||||
&request.payment_token,
|
&request.payment_token,
|
||||||
|
&request.mandate_id,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let payment_id = request
|
let payment_id = request
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
use std::marker::PhantomData;
|
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 async_trait::async_trait;
|
||||||
use common_utils::ext_traits::{AsyncExt, Encode, ValueExt};
|
use common_utils::ext_traits::{AsyncExt, Encode, ValueExt};
|
||||||
use data_models::{
|
use data_models::{
|
||||||
mandates::{MandateData, MandateDetails},
|
mandates::{MandateData, MandateDetails},
|
||||||
payments::payment_attempt::PaymentAttempt,
|
payments::payment_attempt::PaymentAttempt,
|
||||||
};
|
};
|
||||||
use diesel_models::ephemeral_key;
|
use diesel_models::{ephemeral_key, PaymentMethod};
|
||||||
use error_stack::{self, ResultExt};
|
use error_stack::{self, ResultExt};
|
||||||
use masking::PeekInterface;
|
use masking::{ExposeInterface, PeekInterface};
|
||||||
use router_derive::PaymentOperation;
|
use router_derive::PaymentOperation;
|
||||||
use router_env::{instrument, tracing};
|
use router_env::{instrument, tracing};
|
||||||
use time::PrimitiveDateTime;
|
use time::PrimitiveDateTime;
|
||||||
@ -259,6 +261,8 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
|||||||
payment_method_billing_address
|
payment_method_billing_address
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|address| address.address_id.clone()),
|
.map(|address| address.address_id.clone()),
|
||||||
|
&payment_method_info,
|
||||||
|
merchant_key_store,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -691,6 +695,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
|
|||||||
helpers::validate_recurring_details_and_token(
|
helpers::validate_recurring_details_and_token(
|
||||||
&request.recurring_details,
|
&request.recurring_details,
|
||||||
&request.payment_token,
|
&request.payment_token,
|
||||||
|
&request.mandate_id,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if request.confirm.unwrap_or(false) {
|
if request.confirm.unwrap_or(false) {
|
||||||
@ -744,6 +749,8 @@ impl PaymentCreate {
|
|||||||
browser_info: Option<serde_json::Value>,
|
browser_info: Option<serde_json::Value>,
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
payment_method_billing_address_id: Option<String>,
|
payment_method_billing_address_id: Option<String>,
|
||||||
|
payment_method_info: &Option<PaymentMethod>,
|
||||||
|
key_store: &domain::MerchantKeyStore,
|
||||||
) -> RouterResult<(
|
) -> RouterResult<(
|
||||||
storage::PaymentAttemptNew,
|
storage::PaymentAttemptNew,
|
||||||
Option<api_models::payments::AdditionalPaymentData>,
|
Option<api_models::payments::AdditionalPaymentData>,
|
||||||
@ -753,7 +760,7 @@ impl PaymentCreate {
|
|||||||
helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm);
|
helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm);
|
||||||
let (amount, currency) = (money.0, Some(money.1));
|
let (amount, currency) = (money.0, Some(money.1));
|
||||||
|
|
||||||
let additional_pm_data = request
|
let mut additional_pm_data = request
|
||||||
.payment_method_data
|
.payment_method_data
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.async_map(|payment_method_data| async {
|
.async_map(|payment_method_data| async {
|
||||||
@ -764,6 +771,35 @@ impl PaymentCreate {
|
|||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
.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
|
let additional_pm_data_value = additional_pm_data
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(Encode::encode_to_value)
|
.map(Encode::encode_to_value)
|
||||||
@ -842,7 +878,9 @@ impl PaymentCreate {
|
|||||||
connector: None,
|
connector: None,
|
||||||
error_message: None,
|
error_message: None,
|
||||||
offer_amount: 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,
|
cancellation_reason: None,
|
||||||
error_code: None,
|
error_code: None,
|
||||||
connector_metadata: None,
|
connector_metadata: None,
|
||||||
|
|||||||
@ -754,6 +754,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
|
|||||||
helpers::validate_recurring_details_and_token(
|
helpers::validate_recurring_details_and_token(
|
||||||
&request.recurring_details,
|
&request.recurring_details,
|
||||||
&request.payment_token,
|
&request.payment_token,
|
||||||
|
&request.mandate_id,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
|||||||
Reference in New Issue
Block a user