mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +08:00
feat(core): add config for update_mandate_flow (#3542)
This commit is contained in:
@ -391,6 +391,9 @@ bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"} # Mandate supp
|
||||
bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"}
|
||||
wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon" }
|
||||
|
||||
[mandates.update_mandate_supported]
|
||||
card.credit ={connector_list ="cybersource"} # Update Mandate supported payment method type and connector for card
|
||||
card.debit = {connector_list ="cybersource"} # Update Mandate supported payment method type and connector for card
|
||||
|
||||
# Required fields info used while listing the payment_method_data
|
||||
[required_fields.pay_later] # payment_method = "pay_later"
|
||||
|
||||
@ -120,6 +120,10 @@ wallet.paypal.connector_list = "adyen"
|
||||
bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"}
|
||||
bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"}
|
||||
|
||||
[mandates.update_mandate_supported]
|
||||
card.credit ={connector_list ="cybersource"}
|
||||
card.debit = {connector_list ="cybersource"}
|
||||
|
||||
[multiple_api_version_supported_connectors]
|
||||
supported_connectors = "braintree"
|
||||
|
||||
|
||||
@ -120,6 +120,10 @@ wallet.paypal.connector_list = "adyen"
|
||||
bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"}
|
||||
bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"}
|
||||
|
||||
[mandates.update_mandate_supported]
|
||||
card.credit ={connector_list ="cybersource"}
|
||||
card.debit = {connector_list ="cybersource"}
|
||||
|
||||
[multiple_api_version_supported_connectors]
|
||||
supported_connectors = "braintree"
|
||||
|
||||
|
||||
@ -120,6 +120,10 @@ wallet.paypal.connector_list = "adyen"
|
||||
bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"}
|
||||
bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"}
|
||||
|
||||
[mandates.update_mandate_supported]
|
||||
card.credit ={connector_list ="cybersource"}
|
||||
card.debit = {connector_list ="cybersource"}
|
||||
|
||||
[multiple_api_version_supported_connectors]
|
||||
supported_connectors = "braintree"
|
||||
|
||||
|
||||
@ -487,6 +487,10 @@ bank_debit.sepa = { connector_list = "gocardless"}
|
||||
bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"}
|
||||
bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"}
|
||||
|
||||
[mandates.update_mandate_supported]
|
||||
card.credit ={connector_list ="cybersource"}
|
||||
card.debit = {connector_list ="cybersource"}
|
||||
|
||||
[connector_request_reference_id_config]
|
||||
merchant_ids_send_payment_id_as_connector_request_id = []
|
||||
|
||||
|
||||
@ -1011,6 +1011,23 @@ impl PaymentMethodData {
|
||||
self.to_owned()
|
||||
}
|
||||
}
|
||||
pub fn get_payment_method(&self) -> Option<api_enums::PaymentMethod> {
|
||||
match self {
|
||||
Self::Card(_) => Some(api_enums::PaymentMethod::Card),
|
||||
Self::CardRedirect(_) => Some(api_enums::PaymentMethod::CardRedirect),
|
||||
Self::Wallet(_) => Some(api_enums::PaymentMethod::Wallet),
|
||||
Self::PayLater(_) => Some(api_enums::PaymentMethod::PayLater),
|
||||
Self::BankRedirect(_) => Some(api_enums::PaymentMethod::BankRedirect),
|
||||
Self::BankDebit(_) => Some(api_enums::PaymentMethod::BankDebit),
|
||||
Self::BankTransfer(_) => Some(api_enums::PaymentMethod::BankTransfer),
|
||||
Self::Crypto(_) => Some(api_enums::PaymentMethod::Crypto),
|
||||
Self::Reward => Some(api_enums::PaymentMethod::Reward),
|
||||
Self::Upi(_) => Some(api_enums::PaymentMethod::Upi),
|
||||
Self::Voucher(_) => Some(api_enums::PaymentMethod::Voucher),
|
||||
Self::GiftCard(_) => Some(api_enums::PaymentMethod::GiftCard),
|
||||
Self::CardToken(_) | Self::MandatePayment => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetPaymentMethodType {
|
||||
|
||||
@ -209,6 +209,7 @@ impl Default for Mandates {
|
||||
])),
|
||||
),
|
||||
])),
|
||||
update_mandate_supported: SupportedPaymentMethodsForMandate(HashMap::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +237,7 @@ pub struct DummyConnector {
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Mandates {
|
||||
pub supported_payment_methods: SupportedPaymentMethodsForMandate,
|
||||
pub update_mandate_supported: SupportedPaymentMethodsForMandate,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
||||
@ -59,7 +59,6 @@ pub async fn revoke_mandate(
|
||||
.find_mandate_by_merchant_id_mandate_id(&merchant_account.merchant_id, &req.mandate_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?;
|
||||
|
||||
let mandate_revoke_status = match mandate.mandate_status {
|
||||
common_enums::MandateStatus::Active
|
||||
| common_enums::MandateStatus::Inactive
|
||||
|
||||
@ -1219,6 +1219,7 @@ pub async fn list_payment_methods(
|
||||
mca.connector_name.clone(),
|
||||
pm_config_mapping,
|
||||
&state.conf.mandates.supported_payment_methods,
|
||||
&state.conf.mandates.update_mandate_supported,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@ -1985,6 +1986,7 @@ pub async fn filter_payment_methods(
|
||||
connector: String,
|
||||
config: &settings::ConnectorFilters,
|
||||
supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate,
|
||||
supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate,
|
||||
) -> errors::CustomResult<(), errors::ApiErrorResponse> {
|
||||
for payment_method in payment_methods.into_iter() {
|
||||
let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method);
|
||||
@ -2091,13 +2093,20 @@ pub async fn filter_payment_methods(
|
||||
),
|
||||
};
|
||||
|
||||
if mandate_type_present || update_mandate_id_present {
|
||||
if mandate_type_present {
|
||||
filter_pm_based_on_supported_payments_for_mandate(
|
||||
supported_payment_methods_for_mandate,
|
||||
&payment_method,
|
||||
&payment_method_object.payment_method_type,
|
||||
connector_variant,
|
||||
)
|
||||
} else if update_mandate_id_present {
|
||||
filter_pm_based_on_update_mandate_support_for_connector(
|
||||
supported_payment_methods_for_update_mandate,
|
||||
&payment_method,
|
||||
&payment_method_object.payment_method_type,
|
||||
connector_variant,
|
||||
)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@ -2121,6 +2130,19 @@ pub async fn filter_payment_methods(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn filter_pm_based_on_update_mandate_support_for_connector(
|
||||
supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate,
|
||||
payment_method: &api_enums::PaymentMethod,
|
||||
payment_method_type: &api_enums::PaymentMethodType,
|
||||
connector: api_enums::Connector,
|
||||
) -> bool {
|
||||
supported_payment_methods_for_mandate
|
||||
.0
|
||||
.get(payment_method)
|
||||
.and_then(|payment_method_type_hm| payment_method_type_hm.0.get(payment_method_type))
|
||||
.map(|supported_connectors| supported_connectors.connector_list.contains(&connector))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn filter_pm_based_on_supported_payments_for_mandate(
|
||||
supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate,
|
||||
|
||||
@ -3,9 +3,11 @@ use error_stack::{IntoReport, ResultExt};
|
||||
|
||||
use super::{ConstructFlowSpecificData, Feature};
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::{
|
||||
errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt},
|
||||
mandate,
|
||||
payment_methods::cards,
|
||||
payments::{
|
||||
self, access_token, customers, helpers, tokenization, transformers, PaymentData,
|
||||
},
|
||||
@ -60,6 +62,25 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
|
||||
connector_request: Option<services::Request>,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
if let Some(mandate_id) = self
|
||||
.request
|
||||
.setup_mandate_details
|
||||
.as_ref()
|
||||
.and_then(|mandate_data| mandate_data.update_mandate_id.clone())
|
||||
{
|
||||
self.update_mandate_flow(
|
||||
state,
|
||||
merchant_account,
|
||||
mandate_id,
|
||||
connector,
|
||||
key_store,
|
||||
call_connector_action,
|
||||
&state.conf.mandates.update_mandate_supported,
|
||||
connector_request,
|
||||
maybe_customer,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
api::SetupMandate,
|
||||
@ -76,9 +97,7 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
|
||||
)
|
||||
.await
|
||||
.to_setup_mandate_failed_response()?;
|
||||
|
||||
let is_mandate = resp.request.setup_mandate_details.is_some();
|
||||
|
||||
let pm_id = Box::pin(tokenization::save_payment_method(
|
||||
state,
|
||||
connector,
|
||||
@ -90,76 +109,6 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
|
||||
is_mandate,
|
||||
))
|
||||
.await?;
|
||||
|
||||
if let Some(mandate_id) = self
|
||||
.request
|
||||
.setup_mandate_details
|
||||
.as_ref()
|
||||
.and_then(|mandate_data| mandate_data.update_mandate_id.clone())
|
||||
{
|
||||
let mandate = state
|
||||
.store
|
||||
.find_mandate_by_merchant_id_mandate_id(&merchant_account.merchant_id, &mandate_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?;
|
||||
|
||||
let profile_id = mandate::helpers::get_profile_id_for_mandate(
|
||||
state,
|
||||
merchant_account,
|
||||
mandate.clone(),
|
||||
)
|
||||
.await?;
|
||||
match resp.response {
|
||||
Ok(types::PaymentsResponseData::TransactionResponse { .. }) => {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
types::api::MandateRevoke,
|
||||
types::MandateRevokeRequestData,
|
||||
types::MandateRevokeResponseData,
|
||||
> = connector.connector.get_connector_integration();
|
||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||
state,
|
||||
&merchant_account.merchant_id,
|
||||
None,
|
||||
key_store,
|
||||
&profile_id,
|
||||
&mandate.connector,
|
||||
mandate.merchant_connector_id.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let router_data = mandate::utils::construct_mandate_revoke_router_data(
|
||||
merchant_connector_account,
|
||||
merchant_account,
|
||||
mandate.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _response = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
&router_data,
|
||||
call_connector_action,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
// TODO:Add the revoke mandate task to process tracker
|
||||
mandate::update_mandate_procedure(
|
||||
state,
|
||||
resp,
|
||||
mandate,
|
||||
&merchant_account.merchant_id,
|
||||
pm_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Ok(_) => Err(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
.attach_printable("Unexpected response received")?,
|
||||
Err(_) => Ok(resp),
|
||||
}
|
||||
} else {
|
||||
mandate::mandate_procedure(
|
||||
state,
|
||||
resp,
|
||||
@ -310,6 +259,130 @@ impl types::SetupMandateRouterData {
|
||||
_ => Ok(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_mandate_flow(
|
||||
self,
|
||||
state: &AppState,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
mandate_id: String,
|
||||
connector: &api::ConnectorData,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
supported_connectors_for_update_mandate: &settings::SupportedPaymentMethodsForMandate,
|
||||
connector_request: Option<services::Request>,
|
||||
maybe_customer: &Option<domain::Customer>,
|
||||
) -> RouterResult<Self> {
|
||||
let payment_method_type = self.request.payment_method_type;
|
||||
|
||||
let payment_method = self.request.payment_method_data.get_payment_method();
|
||||
let supported_connectors_config =
|
||||
payment_method
|
||||
.zip(payment_method_type)
|
||||
.map_or(false, |(pm, pmt)| {
|
||||
cards::filter_pm_based_on_update_mandate_support_for_connector(
|
||||
supported_connectors_for_update_mandate,
|
||||
&pm,
|
||||
&pmt,
|
||||
connector.connector_name,
|
||||
)
|
||||
});
|
||||
if supported_connectors_config {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
api::SetupMandate,
|
||||
types::SetupMandateRequestData,
|
||||
types::PaymentsResponseData,
|
||||
> = connector.connector.get_connector_integration();
|
||||
|
||||
let resp = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
&self,
|
||||
call_connector_action.clone(),
|
||||
connector_request,
|
||||
)
|
||||
.await
|
||||
.to_setup_mandate_failed_response()?;
|
||||
let is_mandate = resp.request.setup_mandate_details.is_some();
|
||||
let pm_id = Box::pin(tokenization::save_payment_method(
|
||||
state,
|
||||
connector,
|
||||
resp.to_owned(),
|
||||
maybe_customer,
|
||||
merchant_account,
|
||||
self.request.payment_method_type,
|
||||
key_store,
|
||||
is_mandate,
|
||||
))
|
||||
.await?;
|
||||
let mandate = state
|
||||
.store
|
||||
.find_mandate_by_merchant_id_mandate_id(&merchant_account.merchant_id, &mandate_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?;
|
||||
|
||||
let profile_id = mandate::helpers::get_profile_id_for_mandate(
|
||||
state,
|
||||
merchant_account,
|
||||
mandate.clone(),
|
||||
)
|
||||
.await?;
|
||||
match resp.response {
|
||||
Ok(types::PaymentsResponseData::TransactionResponse { .. }) => {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
types::api::MandateRevoke,
|
||||
types::MandateRevokeRequestData,
|
||||
types::MandateRevokeResponseData,
|
||||
> = connector.connector.get_connector_integration();
|
||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||
state,
|
||||
&merchant_account.merchant_id,
|
||||
None,
|
||||
key_store,
|
||||
&profile_id,
|
||||
&mandate.connector,
|
||||
mandate.merchant_connector_id.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let router_data = mandate::utils::construct_mandate_revoke_router_data(
|
||||
merchant_connector_account,
|
||||
merchant_account,
|
||||
mandate.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _response = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
&router_data,
|
||||
call_connector_action,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
// TODO:Add the revoke mandate task to process tracker
|
||||
mandate::update_mandate_procedure(
|
||||
state,
|
||||
resp,
|
||||
mandate,
|
||||
&merchant_account.merchant_id,
|
||||
pm_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Ok(_) => Err(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
.attach_printable("Unexpected response received")?,
|
||||
Err(_) => Ok(resp),
|
||||
}
|
||||
} else {
|
||||
Err(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
.attach_printable("Update Mandate Flow not implemented for the connector ")?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mandate::MandateBehaviour for types::SetupMandateRequestData {
|
||||
|
||||
@ -1342,7 +1342,6 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsRejectDa
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSessionData {
|
||||
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||
|
||||
|
||||
@ -252,6 +252,13 @@ bank_debit.sepa = { connector_list = "gocardless"}
|
||||
bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"}
|
||||
bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"}
|
||||
|
||||
[mandates.update_mandate_supported]
|
||||
card.credit ={connector_list ="cybersource"}
|
||||
card.debit = {connector_list ="cybersource"}
|
||||
|
||||
[mandates.update_mandate_supported]
|
||||
connector_list = "cybersource"
|
||||
|
||||
[analytics]
|
||||
source = "sqlx"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user