mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-31 01:57:45 +08:00
feat(router): add external authentication webhooks flow (#4339)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d4dbaadb06
commit
00cd96d097
@ -38,6 +38,7 @@ pub enum IncomingWebhookEvent {
|
|||||||
MandateActive,
|
MandateActive,
|
||||||
MandateRevoked,
|
MandateRevoked,
|
||||||
EndpointVerification,
|
EndpointVerification,
|
||||||
|
ExternalAuthenticationARes,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum WebhookFlow {
|
pub enum WebhookFlow {
|
||||||
@ -48,6 +49,7 @@ pub enum WebhookFlow {
|
|||||||
ReturnResponse,
|
ReturnResponse,
|
||||||
BankTransfer,
|
BankTransfer,
|
||||||
Mandate,
|
Mandate,
|
||||||
|
ExternalAuthentication,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
@ -116,6 +118,7 @@ impl From<IncomingWebhookEvent> for WebhookFlow {
|
|||||||
IncomingWebhookEvent::EndpointVerification => Self::ReturnResponse,
|
IncomingWebhookEvent::EndpointVerification => Self::ReturnResponse,
|
||||||
IncomingWebhookEvent::SourceChargeable
|
IncomingWebhookEvent::SourceChargeable
|
||||||
| IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer,
|
| IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer,
|
||||||
|
IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,11 +137,18 @@ pub enum MandateIdType {
|
|||||||
ConnectorMandateId(String),
|
ConnectorMandateId(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum AuthenticationIdType {
|
||||||
|
AuthenticationId(String),
|
||||||
|
ConnectorAuthenticationId(String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ObjectReferenceId {
|
pub enum ObjectReferenceId {
|
||||||
PaymentId(payments::PaymentIdType),
|
PaymentId(payments::PaymentIdType),
|
||||||
RefundId(RefundIdType),
|
RefundId(RefundIdType),
|
||||||
MandateId(MandateIdType),
|
MandateId(MandateIdType),
|
||||||
|
ExternalAuthenticationID(AuthenticationIdType),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IncomingWebhookDetails {
|
pub struct IncomingWebhookDetails {
|
||||||
|
|||||||
@ -40,6 +40,9 @@ pub struct Authentication {
|
|||||||
pub acs_trans_id: Option<String>,
|
pub acs_trans_id: Option<String>,
|
||||||
pub three_ds_server_trans_id: Option<String>,
|
pub three_ds_server_trans_id: Option<String>,
|
||||||
pub acs_signed_content: Option<String>,
|
pub acs_signed_content: Option<String>,
|
||||||
|
pub profile_id: String,
|
||||||
|
pub payment_id: Option<String>,
|
||||||
|
pub merchant_connector_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Authentication {
|
impl Authentication {
|
||||||
@ -82,6 +85,9 @@ pub struct AuthenticationNew {
|
|||||||
pub acs_trans_id: Option<String>,
|
pub acs_trans_id: Option<String>,
|
||||||
pub three_dsserver_trans_id: Option<String>,
|
pub three_dsserver_trans_id: Option<String>,
|
||||||
pub acs_signed_content: Option<String>,
|
pub acs_signed_content: Option<String>,
|
||||||
|
pub profile_id: String,
|
||||||
|
pub payment_id: Option<String>,
|
||||||
|
pub merchant_connector_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@ -69,4 +69,18 @@ impl Authentication {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
merchant_id: &str,
|
||||||
|
connector_authentication_id: &str,
|
||||||
|
) -> StorageResult<Self> {
|
||||||
|
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
|
||||||
|
conn,
|
||||||
|
dsl::merchant_id
|
||||||
|
.eq(merchant_id.to_owned())
|
||||||
|
.and(dsl::connector_authentication_id.eq(connector_authentication_id.to_owned())),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,6 +111,12 @@ diesel::table! {
|
|||||||
acs_trans_id -> Nullable<Varchar>,
|
acs_trans_id -> Nullable<Varchar>,
|
||||||
three_dsserver_trans_id -> Nullable<Varchar>,
|
three_dsserver_trans_id -> Nullable<Varchar>,
|
||||||
acs_signed_content -> Nullable<Varchar>,
|
acs_signed_content -> Nullable<Varchar>,
|
||||||
|
#[max_length = 64]
|
||||||
|
profile_id -> Varchar,
|
||||||
|
#[max_length = 255]
|
||||||
|
payment_id -> Nullable<Varchar>,
|
||||||
|
#[max_length = 128]
|
||||||
|
merchant_connector_id -> Varchar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -591,6 +591,10 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
|
|||||||
object: "dispute".to_owned(),
|
object: "dispute".to_owned(),
|
||||||
id: dispute_id,
|
id: dispute_id,
|
||||||
},
|
},
|
||||||
|
errors::ApiErrorResponse::AuthenticationNotFound { id } => Self::ResourceMissing {
|
||||||
|
object: "authentication".to_owned(),
|
||||||
|
id,
|
||||||
|
},
|
||||||
errors::ApiErrorResponse::BusinessProfileNotFound { id } => Self::ResourceMissing {
|
errors::ApiErrorResponse::BusinessProfileNotFound { id } => Self::ResourceMissing {
|
||||||
object: "business_profile".to_owned(),
|
object: "business_profile".to_owned(),
|
||||||
id,
|
id,
|
||||||
|
|||||||
@ -155,6 +155,17 @@ pub async fn perform_post_authentication<F: Clone + Send>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_payment_id_from_pre_authentication_flow_input<F: Clone + Send>(
|
||||||
|
pre_authentication_flow_input: &types::PreAuthenthenticationFlowInput<'_, F>,
|
||||||
|
) -> Option<String> {
|
||||||
|
match pre_authentication_flow_input {
|
||||||
|
types::PreAuthenthenticationFlowInput::PaymentAuthNFlow { payment_data, .. } => {
|
||||||
|
Some(payment_data.payment_intent.payment_id.clone())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn perform_pre_authentication<F: Clone + Send>(
|
pub async fn perform_pre_authentication<F: Clone + Send>(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
authentication_connector_name: String,
|
authentication_connector_name: String,
|
||||||
@ -163,10 +174,17 @@ pub async fn perform_pre_authentication<F: Clone + Send>(
|
|||||||
three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType,
|
three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType,
|
||||||
payment_connector_account: payments_core::helpers::MerchantConnectorAccountType,
|
payment_connector_account: payments_core::helpers::MerchantConnectorAccountType,
|
||||||
) -> CustomResult<(), ApiErrorResponse> {
|
) -> CustomResult<(), ApiErrorResponse> {
|
||||||
|
let payment_id = get_payment_id_from_pre_authentication_flow_input(&authentication_flow_input);
|
||||||
let authentication = utils::create_new_authentication(
|
let authentication = utils::create_new_authentication(
|
||||||
state,
|
state,
|
||||||
business_profile.merchant_id.clone(),
|
business_profile.merchant_id.clone(),
|
||||||
authentication_connector_name.clone(),
|
authentication_connector_name.clone(),
|
||||||
|
business_profile.profile_id.clone(),
|
||||||
|
payment_id,
|
||||||
|
three_ds_connector_account
|
||||||
|
.get_mca_id()
|
||||||
|
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Error while finding mca_id from merchant_connector_account")?,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
match authentication_flow_input {
|
match authentication_flow_input {
|
||||||
|
|||||||
@ -74,3 +74,8 @@ pub struct AcquirerDetails {
|
|||||||
pub acquirer_bin: String,
|
pub acquirer_bin: String,
|
||||||
pub acquirer_merchant_id: String,
|
pub acquirer_merchant_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct ExternalThreeDSConnectorMetadata {
|
||||||
|
pub pull_mechanism_for_external_3ds_enabled: Option<bool>,
|
||||||
|
}
|
||||||
|
|||||||
@ -144,6 +144,9 @@ pub async fn create_new_authentication(
|
|||||||
state: &AppState,
|
state: &AppState,
|
||||||
merchant_id: String,
|
merchant_id: String,
|
||||||
authentication_connector: String,
|
authentication_connector: String,
|
||||||
|
profile_id: String,
|
||||||
|
payment_id: Option<String>,
|
||||||
|
merchant_connector_id: String,
|
||||||
) -> RouterResult<storage::Authentication> {
|
) -> RouterResult<storage::Authentication> {
|
||||||
let authentication_id =
|
let authentication_id =
|
||||||
common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX);
|
common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX);
|
||||||
@ -176,6 +179,9 @@ pub async fn create_new_authentication(
|
|||||||
acs_trans_id: None,
|
acs_trans_id: None,
|
||||||
three_dsserver_trans_id: None,
|
three_dsserver_trans_id: None,
|
||||||
acs_signed_content: None,
|
acs_signed_content: None,
|
||||||
|
profile_id,
|
||||||
|
payment_id,
|
||||||
|
merchant_connector_id,
|
||||||
};
|
};
|
||||||
state
|
state
|
||||||
.store
|
.store
|
||||||
|
|||||||
@ -170,6 +170,8 @@ pub enum ApiErrorResponse {
|
|||||||
ResourceIdNotFound,
|
ResourceIdNotFound,
|
||||||
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")]
|
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")]
|
||||||
MandateNotFound,
|
MandateNotFound,
|
||||||
|
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Authentication does not exist in our records")]
|
||||||
|
AuthenticationNotFound { id: String },
|
||||||
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")]
|
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")]
|
||||||
MandateUpdateFailed,
|
MandateUpdateFailed,
|
||||||
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")]
|
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")]
|
||||||
|
|||||||
@ -229,6 +229,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
|
|||||||
Self::DisputeNotFound { .. } => {
|
Self::DisputeNotFound { .. } => {
|
||||||
AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None))
|
AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None))
|
||||||
},
|
},
|
||||||
|
Self::AuthenticationNotFound { .. } => {
|
||||||
|
AER::NotFound(ApiError::new("HE", 2, "Authentication does not exist in our records", None))
|
||||||
|
},
|
||||||
Self::BusinessProfileNotFound { id } => {
|
Self::BusinessProfileNotFound { id } => {
|
||||||
AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None))
|
AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ use crate::{
|
|||||||
api::{self, mandates::MandateResponseExt},
|
api::{self, mandates::MandateResponseExt},
|
||||||
domain::{self, types as domain_types},
|
domain::{self, types as domain_types},
|
||||||
storage::{self, enums},
|
storage::{self, enums},
|
||||||
transformers::{ForeignInto, ForeignTryFrom},
|
transformers::{ForeignFrom, ForeignInto, ForeignTryFrom},
|
||||||
},
|
},
|
||||||
utils::{self as helper_utils, generate_id, OptionExt, ValueExt},
|
utils::{self as helper_utils, generate_id, OptionExt, ValueExt},
|
||||||
workflows::outgoing_webhook_retry,
|
workflows::outgoing_webhook_retry,
|
||||||
@ -416,6 +416,162 @@ pub async fn get_or_update_dispute_object(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub async fn external_authentication_incoming_webhook_flow<Ctx: PaymentMethodRetrieve>(
|
||||||
|
state: AppState,
|
||||||
|
req_state: ReqState,
|
||||||
|
merchant_account: domain::MerchantAccount,
|
||||||
|
key_store: domain::MerchantKeyStore,
|
||||||
|
source_verified: bool,
|
||||||
|
event_type: api_models::webhooks::IncomingWebhookEvent,
|
||||||
|
request_details: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
|
connector: &(dyn api::Connector + Sync),
|
||||||
|
object_ref_id: api::ObjectReferenceId,
|
||||||
|
business_profile: diesel_models::business_profile::BusinessProfile,
|
||||||
|
merchant_connector_account: domain::MerchantConnectorAccount,
|
||||||
|
) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
|
||||||
|
if source_verified {
|
||||||
|
let authentication_details = connector
|
||||||
|
.get_external_authentication_details(request_details)
|
||||||
|
.switch()?;
|
||||||
|
let trans_status = authentication_details.trans_status;
|
||||||
|
let authentication_update = storage::AuthenticationUpdate::PostAuthenticationUpdate {
|
||||||
|
authentication_status: common_enums::AuthenticationStatus::foreign_from(
|
||||||
|
trans_status.clone(),
|
||||||
|
),
|
||||||
|
trans_status,
|
||||||
|
authentication_value: authentication_details.authentication_value,
|
||||||
|
eci: authentication_details.eci,
|
||||||
|
};
|
||||||
|
let authentication =
|
||||||
|
if let webhooks::ObjectReferenceId::ExternalAuthenticationID(authentication_id_type) =
|
||||||
|
object_ref_id
|
||||||
|
{
|
||||||
|
match authentication_id_type {
|
||||||
|
webhooks::AuthenticationIdType::AuthenticationId(authentication_id) => state
|
||||||
|
.store
|
||||||
|
.find_authentication_by_merchant_id_authentication_id(
|
||||||
|
merchant_account.merchant_id.clone(),
|
||||||
|
authentication_id.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::AuthenticationNotFound {
|
||||||
|
id: authentication_id,
|
||||||
|
})
|
||||||
|
.attach_printable("Error while fetching authentication record"),
|
||||||
|
webhooks::AuthenticationIdType::ConnectorAuthenticationId(
|
||||||
|
connector_authentication_id,
|
||||||
|
) => state
|
||||||
|
.store
|
||||||
|
.find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
merchant_account.merchant_id.clone(),
|
||||||
|
connector_authentication_id.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::AuthenticationNotFound {
|
||||||
|
id: connector_authentication_id,
|
||||||
|
})
|
||||||
|
.attach_printable("Error while fetching authentication record"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable(
|
||||||
|
"received a non-external-authentication id for retrieving authentication",
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
let updated_authentication = state
|
||||||
|
.store
|
||||||
|
.update_authentication_by_merchant_id_authentication_id(
|
||||||
|
authentication,
|
||||||
|
authentication_update,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Error while updating authentication")?;
|
||||||
|
// Check if it's a payment authentication flow, payment_id would be there only for payment authentication flows
|
||||||
|
if let Some(payment_id) = updated_authentication.payment_id {
|
||||||
|
let is_pull_mechanism_enabled = helper_utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(merchant_connector_account.metadata.map(|metadata| metadata.expose()));
|
||||||
|
// Merchant doesn't have pull mechanism enabled, so we have to authorize whenever we receive a ARes webhook
|
||||||
|
if !is_pull_mechanism_enabled
|
||||||
|
&& event_type == webhooks::IncomingWebhookEvent::ExternalAuthenticationARes
|
||||||
|
{
|
||||||
|
let payment_confirm_req = api::PaymentsRequest {
|
||||||
|
payment_id: Some(api_models::payments::PaymentIdType::PaymentIntentId(
|
||||||
|
payment_id,
|
||||||
|
)),
|
||||||
|
merchant_id: Some(merchant_account.merchant_id.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let payments_response = Box::pin(payments::payments_core::<
|
||||||
|
api::Authorize,
|
||||||
|
api::PaymentsResponse,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
Ctx,
|
||||||
|
>(
|
||||||
|
state.clone(),
|
||||||
|
req_state,
|
||||||
|
merchant_account.clone(),
|
||||||
|
key_store.clone(),
|
||||||
|
payments::PaymentConfirm,
|
||||||
|
payment_confirm_req,
|
||||||
|
services::api::AuthFlow::Merchant,
|
||||||
|
payments::CallConnectorAction::Trigger,
|
||||||
|
None,
|
||||||
|
HeaderPayload::with_source(enums::PaymentSource::ExternalAuthenticator),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
match payments_response {
|
||||||
|
services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => {
|
||||||
|
let payment_id = payments_response
|
||||||
|
.payment_id
|
||||||
|
.clone()
|
||||||
|
.get_required_value("payment_id")
|
||||||
|
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||||
|
.attach_printable("payment id not received from payments core")?;
|
||||||
|
let status = payments_response.status;
|
||||||
|
let event_type: Option<enums::EventType> =
|
||||||
|
payments_response.status.foreign_into();
|
||||||
|
// If event is NOT an UnsupportedEvent, trigger Outgoing Webhook
|
||||||
|
if let Some(outgoing_event_type) = event_type {
|
||||||
|
let primary_object_created_at = payments_response.created;
|
||||||
|
create_event_and_trigger_outgoing_webhook(
|
||||||
|
state,
|
||||||
|
merchant_account,
|
||||||
|
business_profile,
|
||||||
|
&key_store,
|
||||||
|
outgoing_event_type,
|
||||||
|
enums::EventClass::Payments,
|
||||||
|
payment_id.clone(),
|
||||||
|
enums::EventObjectType::PaymentDetails,
|
||||||
|
api::OutgoingWebhookContent::PaymentDetails(payments_response),
|
||||||
|
primary_object_created_at,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
};
|
||||||
|
let response = WebhookResponseTracker::Payment { payment_id, status };
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
_ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable(
|
||||||
|
"Did not get payment id as object reference id in webhook payments flow",
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(WebhookResponseTracker::NoEffect)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(WebhookResponseTracker::NoEffect)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger::error!(
|
||||||
|
"Webhook source verification failed for external authentication webhook flow"
|
||||||
|
);
|
||||||
|
Err(report!(
|
||||||
|
errors::ApiErrorResponse::WebhookAuthenticationFailed
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn mandates_incoming_webhook_flow(
|
pub async fn mandates_incoming_webhook_flow(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
merchant_account: domain::MerchantAccount,
|
merchant_account: domain::MerchantAccount,
|
||||||
@ -1365,7 +1521,7 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr
|
|||||||
// Fetch the merchant connector account to get the webhooks source secret
|
// Fetch the merchant connector account to get the webhooks source secret
|
||||||
// `webhooks source secret` is a secret shared between the merchant and connector
|
// `webhooks source secret` is a secret shared between the merchant and connector
|
||||||
// This is used for source verification and webhooks integrity
|
// This is used for source verification and webhooks integrity
|
||||||
let (merchant_connector_account, connector) = fetch_optional_mca_and_connector(
|
let (merchant_connector_account, connector, connector_name) = fetch_optional_mca_and_connector(
|
||||||
&state,
|
&state,
|
||||||
&merchant_account,
|
&merchant_account,
|
||||||
connector_name_or_mca_id,
|
connector_name_or_mca_id,
|
||||||
@ -1373,10 +1529,6 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let connector_name = connector.connector_name.to_string();
|
|
||||||
|
|
||||||
let connector = connector.connector;
|
|
||||||
|
|
||||||
let decoded_body = connector
|
let decoded_body = connector
|
||||||
.decode_webhook_body(
|
.decode_webhook_body(
|
||||||
&*state.clone().store,
|
&*state.clone().store,
|
||||||
@ -1548,7 +1700,7 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr
|
|||||||
.attach_printable("Could not find resource object in incoming webhook body")?;
|
.attach_printable("Could not find resource object in incoming webhook body")?;
|
||||||
|
|
||||||
let webhook_details = api::IncomingWebhookDetails {
|
let webhook_details = api::IncomingWebhookDetails {
|
||||||
object_reference_id: object_ref_id,
|
object_reference_id: object_ref_id.clone(),
|
||||||
resource_object: serde_json::to_vec(&event_object)
|
resource_object: serde_json::to_vec(&event_object)
|
||||||
.change_context(errors::ParsingError::EncodeError("byte-vec"))
|
.change_context(errors::ParsingError::EncodeError("byte-vec"))
|
||||||
.attach_printable("Unable to convert webhook payload to a value")
|
.attach_printable("Unable to convert webhook payload to a value")
|
||||||
@ -1606,7 +1758,7 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr
|
|||||||
key_store,
|
key_store,
|
||||||
webhook_details,
|
webhook_details,
|
||||||
source_verified,
|
source_verified,
|
||||||
*connector,
|
connector,
|
||||||
&request_details,
|
&request_details,
|
||||||
event_type,
|
event_type,
|
||||||
))
|
))
|
||||||
@ -1639,6 +1791,24 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr
|
|||||||
.await
|
.await
|
||||||
.attach_printable("Incoming webhook flow for mandates failed")?,
|
.attach_printable("Incoming webhook flow for mandates failed")?,
|
||||||
|
|
||||||
|
api::WebhookFlow::ExternalAuthentication => {
|
||||||
|
Box::pin(external_authentication_incoming_webhook_flow::<Ctx>(
|
||||||
|
state.clone(),
|
||||||
|
req_state,
|
||||||
|
merchant_account,
|
||||||
|
key_store,
|
||||||
|
source_verified,
|
||||||
|
event_type,
|
||||||
|
&request_details,
|
||||||
|
connector,
|
||||||
|
object_ref_id,
|
||||||
|
business_profile,
|
||||||
|
merchant_connector_account,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.attach_printable("Incoming webhook flow for external authentication failed")?
|
||||||
|
}
|
||||||
|
|
||||||
_ => Err(errors::ApiErrorResponse::InternalServerError)
|
_ => Err(errors::ApiErrorResponse::InternalServerError)
|
||||||
.attach_printable("Unsupported Flow Type received in incoming webhooks")?,
|
.attach_printable("Unsupported Flow Type received in incoming webhooks")?,
|
||||||
}
|
}
|
||||||
@ -1704,6 +1874,39 @@ pub async fn get_payment_id(
|
|||||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
|
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_connector_by_connector_name(
|
||||||
|
state: &AppState,
|
||||||
|
connector_name: &str,
|
||||||
|
merchant_connector_id: Option<String>,
|
||||||
|
) -> CustomResult<(&'static (dyn api::Connector + Sync), String), errors::ApiErrorResponse> {
|
||||||
|
let authentication_connector =
|
||||||
|
api_models::enums::convert_authentication_connector(connector_name);
|
||||||
|
let (connector, connector_name) = if authentication_connector.is_some() {
|
||||||
|
let authentication_connector_data =
|
||||||
|
api::AuthenticationConnectorData::get_connector_by_name(connector_name)?;
|
||||||
|
(
|
||||||
|
authentication_connector_data.connector,
|
||||||
|
authentication_connector_data.connector_name.to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||||
|
&state.conf.connectors,
|
||||||
|
connector_name,
|
||||||
|
api::GetToken::Connector,
|
||||||
|
merchant_connector_id,
|
||||||
|
)
|
||||||
|
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||||
|
message: "invalid connector name received".to_string(),
|
||||||
|
})
|
||||||
|
.attach_printable("Failed construction of ConnectorData")?;
|
||||||
|
(
|
||||||
|
connector_data.connector,
|
||||||
|
connector_data.connector_name.to_string(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Ok((*connector, connector_name))
|
||||||
|
}
|
||||||
|
|
||||||
/// This function fetches the merchant connector account ( if the url used is /{merchant_connector_id})
|
/// This function fetches the merchant connector account ( if the url used is /{merchant_connector_id})
|
||||||
/// if merchant connector id is not passed in the request, then this will return None for mca
|
/// if merchant connector id is not passed in the request, then this will return None for mca
|
||||||
async fn fetch_optional_mca_and_connector(
|
async fn fetch_optional_mca_and_connector(
|
||||||
@ -1712,7 +1915,11 @@ async fn fetch_optional_mca_and_connector(
|
|||||||
connector_name_or_mca_id: &str,
|
connector_name_or_mca_id: &str,
|
||||||
key_store: &domain::MerchantKeyStore,
|
key_store: &domain::MerchantKeyStore,
|
||||||
) -> CustomResult<
|
) -> CustomResult<
|
||||||
(Option<domain::MerchantConnectorAccount>, api::ConnectorData),
|
(
|
||||||
|
Option<domain::MerchantConnectorAccount>,
|
||||||
|
&'static (dyn api::Connector + Sync),
|
||||||
|
String,
|
||||||
|
),
|
||||||
errors::ApiErrorResponse,
|
errors::ApiErrorResponse,
|
||||||
> {
|
> {
|
||||||
let db = &state.store;
|
let db = &state.store;
|
||||||
@ -1730,33 +1937,18 @@ async fn fetch_optional_mca_and_connector(
|
|||||||
.attach_printable(
|
.attach_printable(
|
||||||
"error while fetching merchant_connector_account from connector_id",
|
"error while fetching merchant_connector_account from connector_id",
|
||||||
)?;
|
)?;
|
||||||
|
let (connector, connector_name) = get_connector_by_connector_name(
|
||||||
let connector = api::ConnectorData::get_connector_by_name(
|
state,
|
||||||
&state.conf.connectors,
|
|
||||||
&mca.connector_name,
|
&mca.connector_name,
|
||||||
api::GetToken::Connector,
|
|
||||||
Some(mca.merchant_connector_id.clone()),
|
Some(mca.merchant_connector_id.clone()),
|
||||||
)
|
)?;
|
||||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
|
||||||
message: "invalid connector name received".to_string(),
|
|
||||||
})
|
|
||||||
.attach_printable("Failed construction of ConnectorData")?;
|
|
||||||
|
|
||||||
Ok((Some(mca), connector))
|
Ok((Some(mca), connector, connector_name))
|
||||||
} else {
|
} else {
|
||||||
// Merchant connector account is already being queried, it is safe to set connector id as None
|
// Merchant connector account is already being queried, it is safe to set connector id as None
|
||||||
let connector = api::ConnectorData::get_connector_by_name(
|
let (connector, connector_name) =
|
||||||
&state.conf.connectors,
|
get_connector_by_connector_name(state, connector_name_or_mca_id, None)?;
|
||||||
connector_name_or_mca_id,
|
Ok((None, connector, connector_name))
|
||||||
api::GetToken::Connector,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
|
||||||
message: "invalid connector name received".to_string(),
|
|
||||||
})
|
|
||||||
.attach_printable("Failed construction of ConnectorData")?;
|
|
||||||
|
|
||||||
Ok((None, connector))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,12 @@ pub trait AuthenticationInterface {
|
|||||||
authentication_id: String,
|
authentication_id: String,
|
||||||
) -> CustomResult<storage::Authentication, errors::StorageError>;
|
) -> CustomResult<storage::Authentication, errors::StorageError>;
|
||||||
|
|
||||||
|
async fn find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
&self,
|
||||||
|
merchant_id: String,
|
||||||
|
connector_authentication_id: String,
|
||||||
|
) -> CustomResult<storage::Authentication, errors::StorageError>;
|
||||||
|
|
||||||
async fn update_authentication_by_merchant_id_authentication_id(
|
async fn update_authentication_by_merchant_id_authentication_id(
|
||||||
&self,
|
&self,
|
||||||
previous_state: storage::Authentication,
|
previous_state: storage::Authentication,
|
||||||
@ -59,6 +65,21 @@ impl AuthenticationInterface for Store {
|
|||||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
&self,
|
||||||
|
merchant_id: String,
|
||||||
|
connector_authentication_id: String,
|
||||||
|
) -> CustomResult<storage::Authentication, errors::StorageError> {
|
||||||
|
let conn = connection::pg_connection_read(self).await?;
|
||||||
|
storage::Authentication::find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
&conn,
|
||||||
|
&merchant_id,
|
||||||
|
&connector_authentication_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn update_authentication_by_merchant_id_authentication_id(
|
async fn update_authentication_by_merchant_id_authentication_id(
|
||||||
&self,
|
&self,
|
||||||
@ -124,6 +145,9 @@ impl AuthenticationInterface for MockDb {
|
|||||||
acs_trans_id: authentication.acs_trans_id,
|
acs_trans_id: authentication.acs_trans_id,
|
||||||
three_ds_server_trans_id: authentication.three_dsserver_trans_id,
|
three_ds_server_trans_id: authentication.three_dsserver_trans_id,
|
||||||
acs_signed_content: authentication.acs_signed_content,
|
acs_signed_content: authentication.acs_signed_content,
|
||||||
|
profile_id: authentication.profile_id,
|
||||||
|
payment_id: authentication.payment_id,
|
||||||
|
merchant_connector_id: authentication.merchant_connector_id,
|
||||||
};
|
};
|
||||||
authentications.push(authentication.clone());
|
authentications.push(authentication.clone());
|
||||||
Ok(authentication)
|
Ok(authentication)
|
||||||
@ -145,6 +169,14 @@ impl AuthenticationInterface for MockDb {
|
|||||||
).cloned()
|
).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
&self,
|
||||||
|
_merchant_id: String,
|
||||||
|
_connector_authentication_id: String,
|
||||||
|
) -> CustomResult<storage::Authentication, errors::StorageError> {
|
||||||
|
Err(errors::StorageError::MockDbError)?
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_authentication_by_merchant_id_authentication_id(
|
async fn update_authentication_by_merchant_id_authentication_id(
|
||||||
&self,
|
&self,
|
||||||
previous_state: storage::Authentication,
|
previous_state: storage::Authentication,
|
||||||
|
|||||||
@ -2645,6 +2645,19 @@ impl AuthenticationInterface for KafkaStore {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
&self,
|
||||||
|
merchant_id: String,
|
||||||
|
connector_authentication_id: String,
|
||||||
|
) -> CustomResult<storage::Authentication, errors::StorageError> {
|
||||||
|
self.diesel_store
|
||||||
|
.find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
merchant_id,
|
||||||
|
connector_authentication_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_authentication_by_merchant_id_authentication_id(
|
async fn update_authentication_by_merchant_id_authentication_id(
|
||||||
&self,
|
&self,
|
||||||
previous_state: storage::Authentication,
|
previous_state: storage::Authentication,
|
||||||
|
|||||||
@ -47,6 +47,13 @@ pub enum MessageCategory {
|
|||||||
NonPayment,
|
NonPayment,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)]
|
||||||
|
pub struct ExternalAuthenticationPayload {
|
||||||
|
pub trans_status: common_enums::TransactionStatus,
|
||||||
|
pub authentication_value: Option<String>,
|
||||||
|
pub eci: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ConnectorAuthentication:
|
pub trait ConnectorAuthentication:
|
||||||
services::ConnectorIntegration<
|
services::ConnectorIntegration<
|
||||||
Authentication,
|
Authentication,
|
||||||
|
|||||||
@ -270,4 +270,14 @@ pub trait IncomingWebhook: ConnectorCommon + Sync {
|
|||||||
) -> CustomResult<super::disputes::DisputePayload, errors::ConnectorError> {
|
) -> CustomResult<super::disputes::DisputePayload, errors::ConnectorError> {
|
||||||
Err(errors::ConnectorError::NotImplemented("get_dispute_details method".to_string()).into())
|
Err(errors::ConnectorError::NotImplemented("get_dispute_details method".to_string()).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_external_authentication_details(
|
||||||
|
&self,
|
||||||
|
_request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
) -> CustomResult<super::ExternalAuthenticationPayload, errors::ConnectorError> {
|
||||||
|
Err(errors::ConnectorError::NotImplemented(
|
||||||
|
"get_external_authentication_details method".to_string(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,7 @@ pub use self::ext_traits::{OptionExt, ValidateCall};
|
|||||||
use crate::{
|
use crate::{
|
||||||
consts,
|
consts,
|
||||||
core::{
|
core::{
|
||||||
|
authentication::types::ExternalThreeDSConnectorMetadata,
|
||||||
errors::{self, CustomResult, RouterResult, StorageErrorExt},
|
errors::{self, CustomResult, RouterResult, StorageErrorExt},
|
||||||
utils, webhooks as webhooks_core,
|
utils, webhooks as webhooks_core,
|
||||||
},
|
},
|
||||||
@ -323,6 +324,98 @@ pub async fn find_payment_intent_from_mandate_id_type(
|
|||||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
|
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_mca_from_authentication_id_type(
|
||||||
|
db: &dyn StorageInterface,
|
||||||
|
authentication_id_type: webhooks::AuthenticationIdType,
|
||||||
|
merchant_account: &domain::MerchantAccount,
|
||||||
|
key_store: &domain::MerchantKeyStore,
|
||||||
|
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
|
||||||
|
let authentication = match authentication_id_type {
|
||||||
|
webhooks::AuthenticationIdType::AuthenticationId(authentication_id) => db
|
||||||
|
.find_authentication_by_merchant_id_authentication_id(
|
||||||
|
merchant_account.merchant_id.clone(),
|
||||||
|
authentication_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?,
|
||||||
|
webhooks::AuthenticationIdType::ConnectorAuthenticationId(connector_authentication_id) => {
|
||||||
|
db.find_authentication_by_merchant_id_connector_authentication_id(
|
||||||
|
merchant_account.merchant_id.clone(),
|
||||||
|
connector_authentication_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
db.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
||||||
|
&merchant_account.merchant_id,
|
||||||
|
&authentication.merchant_connector_id,
|
||||||
|
key_store,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
|
id: authentication.merchant_connector_id.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_mca_from_payment_intent(
|
||||||
|
db: &dyn StorageInterface,
|
||||||
|
merchant_account: &domain::MerchantAccount,
|
||||||
|
payment_intent: PaymentIntent,
|
||||||
|
key_store: &domain::MerchantKeyStore,
|
||||||
|
connector_name: &str,
|
||||||
|
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
|
||||||
|
let payment_attempt = db
|
||||||
|
.find_payment_attempt_by_attempt_id_merchant_id(
|
||||||
|
&payment_intent.active_attempt.get_id(),
|
||||||
|
&merchant_account.merchant_id,
|
||||||
|
merchant_account.storage_scheme,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||||
|
|
||||||
|
match payment_attempt.merchant_connector_id {
|
||||||
|
Some(merchant_connector_id) => db
|
||||||
|
.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
||||||
|
&merchant_account.merchant_id,
|
||||||
|
&merchant_connector_id,
|
||||||
|
key_store,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
|
id: merchant_connector_id,
|
||||||
|
}),
|
||||||
|
None => {
|
||||||
|
let profile_id = match payment_intent.profile_id {
|
||||||
|
Some(profile_id) => profile_id,
|
||||||
|
None => utils::get_profile_id_from_business_details(
|
||||||
|
payment_intent.business_country,
|
||||||
|
payment_intent.business_label.as_ref(),
|
||||||
|
merchant_account,
|
||||||
|
payment_intent.profile_id.as_ref(),
|
||||||
|
db,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("profile_id is not set in payment_intent")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
db.find_merchant_connector_account_by_profile_id_connector_name(
|
||||||
|
&profile_id,
|
||||||
|
connector_name,
|
||||||
|
key_store,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(
|
||||||
|
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
|
id: format!("profile_id {profile_id} and connector_name {connector_name}"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_mca_from_object_reference_id(
|
pub async fn get_mca_from_object_reference_id(
|
||||||
db: &dyn StorageInterface,
|
db: &dyn StorageInterface,
|
||||||
object_reference_id: webhooks::ObjectReferenceId,
|
object_reference_id: webhooks::ObjectReferenceId,
|
||||||
@ -330,8 +423,6 @@ pub async fn get_mca_from_object_reference_id(
|
|||||||
connector_name: &str,
|
connector_name: &str,
|
||||||
key_store: &domain::MerchantKeyStore,
|
key_store: &domain::MerchantKeyStore,
|
||||||
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
|
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
|
||||||
let merchant_id = merchant_account.merchant_id.clone();
|
|
||||||
|
|
||||||
match merchant_account.default_profile.as_ref() {
|
match merchant_account.default_profile.as_ref() {
|
||||||
Some(profile_id) => db
|
Some(profile_id) => db
|
||||||
.find_merchant_connector_account_by_profile_id_connector_name(
|
.find_merchant_connector_account_by_profile_id_connector_name(
|
||||||
@ -343,78 +434,55 @@ pub async fn get_mca_from_object_reference_id(
|
|||||||
.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
id: format!("profile_id {profile_id} and connector_name {connector_name}"),
|
id: format!("profile_id {profile_id} and connector_name {connector_name}"),
|
||||||
}),
|
}),
|
||||||
_ => {
|
_ => match object_reference_id {
|
||||||
let payment_intent = match object_reference_id {
|
webhooks::ObjectReferenceId::PaymentId(payment_id_type) => {
|
||||||
webhooks::ObjectReferenceId::PaymentId(payment_id_type) => {
|
get_mca_from_payment_intent(
|
||||||
|
db,
|
||||||
|
merchant_account,
|
||||||
find_payment_intent_from_payment_id_type(db, payment_id_type, merchant_account)
|
find_payment_intent_from_payment_id_type(db, payment_id_type, merchant_account)
|
||||||
.await?
|
.await?,
|
||||||
}
|
key_store,
|
||||||
webhooks::ObjectReferenceId::RefundId(refund_id_type) => {
|
connector_name,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
webhooks::ObjectReferenceId::RefundId(refund_id_type) => {
|
||||||
|
get_mca_from_payment_intent(
|
||||||
|
db,
|
||||||
|
merchant_account,
|
||||||
find_payment_intent_from_refund_id_type(
|
find_payment_intent_from_refund_id_type(
|
||||||
db,
|
db,
|
||||||
refund_id_type,
|
refund_id_type,
|
||||||
merchant_account,
|
merchant_account,
|
||||||
connector_name,
|
connector_name,
|
||||||
)
|
)
|
||||||
.await?
|
.await?,
|
||||||
}
|
key_store,
|
||||||
webhooks::ObjectReferenceId::MandateId(mandate_id_type) => {
|
connector_name,
|
||||||
find_payment_intent_from_mandate_id_type(db, mandate_id_type, merchant_account)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let payment_attempt = db
|
|
||||||
.find_payment_attempt_by_attempt_id_merchant_id(
|
|
||||||
&payment_intent.active_attempt.get_id(),
|
|
||||||
&merchant_id,
|
|
||||||
merchant_account.storage_scheme,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
|
||||||
|
|
||||||
match payment_attempt.merchant_connector_id {
|
|
||||||
Some(merchant_connector_id) => db
|
|
||||||
.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
|
||||||
&merchant_id,
|
|
||||||
&merchant_connector_id,
|
|
||||||
key_store,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.to_not_found_response(
|
|
||||||
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
|
||||||
id: merchant_connector_id,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
None => {
|
|
||||||
let profile_id = utils::get_profile_id_from_business_details(
|
|
||||||
payment_intent.business_country,
|
|
||||||
payment_intent.business_label.as_ref(),
|
|
||||||
merchant_account,
|
|
||||||
payment_intent.profile_id.as_ref(),
|
|
||||||
db,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
|
||||||
.attach_printable("profile_id is not set in payment_intent")?;
|
|
||||||
|
|
||||||
db.find_merchant_connector_account_by_profile_id_connector_name(
|
|
||||||
&profile_id,
|
|
||||||
connector_name,
|
|
||||||
key_store,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.to_not_found_response(
|
|
||||||
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
|
||||||
id: format!(
|
|
||||||
"profile_id {profile_id} and connector_name {connector_name}"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
webhooks::ObjectReferenceId::MandateId(mandate_id_type) => {
|
||||||
|
get_mca_from_payment_intent(
|
||||||
|
db,
|
||||||
|
merchant_account,
|
||||||
|
find_payment_intent_from_mandate_id_type(db, mandate_id_type, merchant_account)
|
||||||
|
.await?,
|
||||||
|
key_store,
|
||||||
|
connector_name,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
webhooks::ObjectReferenceId::ExternalAuthenticationID(authentication_id_type) => {
|
||||||
|
find_mca_from_authentication_id_type(
|
||||||
|
db,
|
||||||
|
authentication_id_type,
|
||||||
|
merchant_account,
|
||||||
|
key_store,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,6 +805,18 @@ pub fn add_apple_pay_payment_status_metrics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(
|
||||||
|
metadata: Option<Value>,
|
||||||
|
) -> bool {
|
||||||
|
let external_three_ds_connector_metadata: Option<ExternalThreeDSConnectorMetadata> = metadata
|
||||||
|
.parse_value("ExternalThreeDSConnectorMetadata")
|
||||||
|
.map_err(|err| logger::warn!(parsing_error=?err,"Error while parsing ExternalThreeDSConnectorMetadata"))
|
||||||
|
.ok();
|
||||||
|
external_three_ds_connector_metadata
|
||||||
|
.and_then(|metadata| metadata.pull_mechanism_for_external_3ds_enabled)
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn trigger_payments_webhook<F, Op>(
|
pub async fn trigger_payments_webhook<F, Op>(
|
||||||
merchant_account: domain::MerchantAccount,
|
merchant_account: domain::MerchantAccount,
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE authentication DROP COLUMN IF EXISTS profile_id;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE authentication ADD COLUMN profile_id VARCHAR(64) NOT NULL;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE authentication DROP COLUMN IF EXISTS payment_id;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE authentication ADD COLUMN payment_id VARCHAR(255);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE authentication DROP COLUMN IF EXISTS merchant_connector_id;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE authentication ADD COLUMN merchant_connector_id VARCHAR(128) NOT NULL;
|
||||||
Reference in New Issue
Block a user