mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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((
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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((
|
||||
|
||||
Reference in New Issue
Block a user