mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
fix(router/webhooks): use api error response for returning errors from webhooks core (#1305)
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -3058,7 +3058,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "opentelemetry"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
dependencies = [
|
||||
"opentelemetry_api",
|
||||
"opentelemetry_sdk",
|
||||
@ -3067,7 +3067,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "opentelemetry-otlp"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
@ -3084,7 +3084,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-util",
|
||||
@ -3096,7 +3096,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "opentelemetry_api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"futures-channel",
|
||||
@ -3111,7 +3111,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "opentelemetry_sdk"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"crossbeam-channel",
|
||||
|
||||
@ -199,6 +199,8 @@ pub enum StripeErrorCode {
|
||||
FileNotFound,
|
||||
#[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "File not available")]
|
||||
FileNotAvailable,
|
||||
#[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "There was an issue with processing webhooks")]
|
||||
WebhookProcessingError,
|
||||
// [#216]: https://github.com/juspay/hyperswitch/issues/216
|
||||
// Implement the remaining stripe error codes
|
||||
|
||||
@ -498,6 +500,10 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
|
||||
Self::MerchantConnectorAccountDisabled
|
||||
}
|
||||
errors::ApiErrorResponse::NotSupported { .. } => Self::InternalServerError,
|
||||
errors::ApiErrorResponse::WebhookBadRequest
|
||||
| errors::ApiErrorResponse::WebhookResourceNotFound
|
||||
| errors::ApiErrorResponse::WebhookProcessingFailure
|
||||
| errors::ApiErrorResponse::WebhookAuthenticationFailed => Self::WebhookProcessingError,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -557,7 +563,8 @@ impl actix_web::ResponseError for StripeErrorCode {
|
||||
Self::RefundFailed
|
||||
| Self::InternalServerError
|
||||
| Self::MandateActive
|
||||
| Self::CustomerRedacted => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
| Self::CustomerRedacted
|
||||
| Self::WebhookProcessingError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE,
|
||||
Self::ExternalConnectorError { status_code, .. } => {
|
||||
StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
|
||||
@ -678,34 +678,59 @@ impl From<CheckoutRedirectResponseStatus> for enums::AttemptStatus {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_refund_event(event_code: &CheckoutTransactionType) -> bool {
|
||||
pub fn is_refund_event(event_code: &CheckoutWebhookEventType) -> bool {
|
||||
matches!(
|
||||
event_code,
|
||||
CheckoutTransactionType::PaymentRefunded | CheckoutTransactionType::PaymentRefundDeclined
|
||||
CheckoutWebhookEventType::PaymentRefunded | CheckoutWebhookEventType::PaymentRefundDeclined
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_chargeback_event(event_code: &CheckoutTransactionType) -> bool {
|
||||
pub fn is_chargeback_event(event_code: &CheckoutWebhookEventType) -> bool {
|
||||
matches!(
|
||||
event_code,
|
||||
CheckoutTransactionType::DisputeReceived
|
||||
| CheckoutTransactionType::DisputeExpired
|
||||
| CheckoutTransactionType::DisputeAccepted
|
||||
| CheckoutTransactionType::DisputeCanceled
|
||||
| CheckoutTransactionType::DisputeEvidenceSubmitted
|
||||
| CheckoutTransactionType::DisputeEvidenceAcknowledgedByScheme
|
||||
| CheckoutTransactionType::DisputeEvidenceRequired
|
||||
| CheckoutTransactionType::DisputeArbitrationLost
|
||||
| CheckoutTransactionType::DisputeArbitrationWon
|
||||
| CheckoutTransactionType::DisputeWon
|
||||
| CheckoutTransactionType::DisputeLost
|
||||
CheckoutWebhookEventType::DisputeReceived
|
||||
| CheckoutWebhookEventType::DisputeExpired
|
||||
| CheckoutWebhookEventType::DisputeAccepted
|
||||
| CheckoutWebhookEventType::DisputeCanceled
|
||||
| CheckoutWebhookEventType::DisputeEvidenceSubmitted
|
||||
| CheckoutWebhookEventType::DisputeEvidenceAcknowledgedByScheme
|
||||
| CheckoutWebhookEventType::DisputeEvidenceRequired
|
||||
| CheckoutWebhookEventType::DisputeArbitrationLost
|
||||
| CheckoutWebhookEventType::DisputeArbitrationWon
|
||||
| CheckoutWebhookEventType::DisputeWon
|
||||
| CheckoutWebhookEventType::DisputeLost
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, strum::Display, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CheckoutWebhookEventType {
|
||||
AuthenticationStarted,
|
||||
AuthenticationApproved,
|
||||
PaymentApproved,
|
||||
PaymentCaptured,
|
||||
PaymentDeclined,
|
||||
PaymentRefunded,
|
||||
PaymentRefundDeclined,
|
||||
DisputeReceived,
|
||||
DisputeExpired,
|
||||
DisputeAccepted,
|
||||
DisputeCanceled,
|
||||
DisputeEvidenceSubmitted,
|
||||
DisputeEvidenceAcknowledgedByScheme,
|
||||
DisputeEvidenceRequired,
|
||||
DisputeArbitrationLost,
|
||||
DisputeArbitrationWon,
|
||||
DisputeWon,
|
||||
DisputeLost,
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CheckoutWebhookEventTypeBody {
|
||||
#[serde(rename = "type")]
|
||||
pub transaction_type: CheckoutTransactionType,
|
||||
pub transaction_type: CheckoutWebhookEventType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -720,7 +745,7 @@ pub struct CheckoutWebhookData {
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CheckoutWebhookBody {
|
||||
#[serde(rename = "type")]
|
||||
pub transaction_type: CheckoutTransactionType,
|
||||
pub transaction_type: CheckoutWebhookEventType,
|
||||
pub data: CheckoutWebhookData,
|
||||
}
|
||||
|
||||
@ -768,29 +793,30 @@ pub enum CheckoutTransactionType {
|
||||
DisputeLost,
|
||||
}
|
||||
|
||||
impl From<CheckoutTransactionType> for api::IncomingWebhookEvent {
|
||||
fn from(transaction_type: CheckoutTransactionType) -> Self {
|
||||
impl From<CheckoutWebhookEventType> for api::IncomingWebhookEvent {
|
||||
fn from(transaction_type: CheckoutWebhookEventType) -> Self {
|
||||
match transaction_type {
|
||||
CheckoutTransactionType::AuthenticationStarted => Self::EventNotSupported,
|
||||
CheckoutTransactionType::AuthenticationApproved => Self::EventNotSupported,
|
||||
CheckoutTransactionType::PaymentApproved => Self::EventNotSupported,
|
||||
CheckoutTransactionType::PaymentCaptured => Self::PaymentIntentSuccess,
|
||||
CheckoutTransactionType::PaymentDeclined => Self::PaymentIntentFailure,
|
||||
CheckoutTransactionType::PaymentRefunded => Self::RefundSuccess,
|
||||
CheckoutTransactionType::PaymentRefundDeclined => Self::RefundFailure,
|
||||
CheckoutTransactionType::DisputeReceived
|
||||
| CheckoutTransactionType::DisputeEvidenceRequired => Self::DisputeOpened,
|
||||
CheckoutTransactionType::DisputeExpired => Self::DisputeExpired,
|
||||
CheckoutTransactionType::DisputeAccepted => Self::DisputeAccepted,
|
||||
CheckoutTransactionType::DisputeCanceled => Self::DisputeCancelled,
|
||||
CheckoutTransactionType::DisputeEvidenceSubmitted
|
||||
| CheckoutTransactionType::DisputeEvidenceAcknowledgedByScheme => {
|
||||
CheckoutWebhookEventType::AuthenticationStarted => Self::EventNotSupported,
|
||||
CheckoutWebhookEventType::AuthenticationApproved => Self::EventNotSupported,
|
||||
CheckoutWebhookEventType::PaymentApproved => Self::EventNotSupported,
|
||||
CheckoutWebhookEventType::PaymentCaptured => Self::PaymentIntentSuccess,
|
||||
CheckoutWebhookEventType::PaymentDeclined => Self::PaymentIntentFailure,
|
||||
CheckoutWebhookEventType::PaymentRefunded => Self::RefundSuccess,
|
||||
CheckoutWebhookEventType::PaymentRefundDeclined => Self::RefundFailure,
|
||||
CheckoutWebhookEventType::DisputeReceived
|
||||
| CheckoutWebhookEventType::DisputeEvidenceRequired => Self::DisputeOpened,
|
||||
CheckoutWebhookEventType::DisputeExpired => Self::DisputeExpired,
|
||||
CheckoutWebhookEventType::DisputeAccepted => Self::DisputeAccepted,
|
||||
CheckoutWebhookEventType::DisputeCanceled => Self::DisputeCancelled,
|
||||
CheckoutWebhookEventType::DisputeEvidenceSubmitted
|
||||
| CheckoutWebhookEventType::DisputeEvidenceAcknowledgedByScheme => {
|
||||
Self::DisputeChallenged
|
||||
}
|
||||
CheckoutTransactionType::DisputeWon
|
||||
| CheckoutTransactionType::DisputeArbitrationWon => Self::DisputeWon,
|
||||
CheckoutTransactionType::DisputeLost
|
||||
| CheckoutTransactionType::DisputeArbitrationLost => Self::DisputeLost,
|
||||
CheckoutWebhookEventType::DisputeWon
|
||||
| CheckoutWebhookEventType::DisputeArbitrationWon => Self::DisputeWon,
|
||||
CheckoutWebhookEventType::DisputeLost
|
||||
| CheckoutWebhookEventType::DisputeArbitrationLost => Self::DisputeLost,
|
||||
CheckoutWebhookEventType::Unknown => Self::EventNotSupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,6 +184,14 @@ pub enum ApiErrorResponse {
|
||||
MissingFilePurpose,
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File content type not found / valid")]
|
||||
MissingFileContentType,
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")]
|
||||
WebhookAuthenticationFailed,
|
||||
#[error(error_type = ErrorType::ObjectNotFound, code = "WE_04", message = "Webhook resource not found")]
|
||||
WebhookResourceNotFound,
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")]
|
||||
WebhookBadRequest,
|
||||
#[error(error_type = ErrorType::RouterError, code = "WE_03", message = "There was some issue processing the webhook")]
|
||||
WebhookProcessingFailure,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -222,11 +230,12 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
||||
Self::Unauthorized
|
||||
| Self::InvalidEphemeralKey
|
||||
| Self::InvalidJwtToken
|
||||
| Self::GenericUnauthorized { .. } => StatusCode::UNAUTHORIZED, // 401
|
||||
| Self::GenericUnauthorized { .. }
|
||||
| Self::WebhookAuthenticationFailed => StatusCode::UNAUTHORIZED, // 401
|
||||
Self::ExternalConnectorError { status_code, .. } => {
|
||||
StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
Self::InvalidRequestUrl => StatusCode::NOT_FOUND, // 404
|
||||
Self::InvalidRequestUrl | Self::WebhookResourceNotFound => StatusCode::NOT_FOUND, // 404
|
||||
Self::InvalidHttpMethod => StatusCode::METHOD_NOT_ALLOWED, // 405
|
||||
Self::MissingRequiredField { .. }
|
||||
| Self::InvalidDataValue { .. }
|
||||
@ -250,9 +259,9 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
||||
| Self::PaymentUnexpectedState { .. }
|
||||
| Self::MandateValidationFailed { .. } => StatusCode::BAD_REQUEST, // 400
|
||||
|
||||
Self::MandateUpdateFailed | Self::InternalServerError => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
} // 500
|
||||
Self::MandateUpdateFailed
|
||||
| Self::InternalServerError
|
||||
| Self::WebhookProcessingFailure => StatusCode::INTERNAL_SERVER_ERROR, // 500
|
||||
Self::DuplicateRefundRequest | Self::DuplicatePayment { .. } => StatusCode::BAD_REQUEST, // 400
|
||||
Self::RefundNotFound
|
||||
| Self::CustomerNotFound
|
||||
@ -275,7 +284,8 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
||||
| Self::NotSupported { .. }
|
||||
| Self::FlowNotSupported { .. }
|
||||
| Self::ApiKeyNotFound
|
||||
| Self::DisputeStatusValidationFailed { .. } => StatusCode::BAD_REQUEST, // 400
|
||||
| Self::DisputeStatusValidationFailed { .. }
|
||||
| Self::WebhookBadRequest => StatusCode::BAD_REQUEST, // 400
|
||||
Self::DuplicateMerchantAccount
|
||||
| Self::DuplicateMerchantConnectorAccount { .. }
|
||||
| Self::DuplicatePaymentMethod
|
||||
@ -510,6 +520,18 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::MissingDisputeId => {
|
||||
AER::BadRequest(ApiError::new("HE", 2, "Dispute id not found in the request", None))
|
||||
}
|
||||
Self::WebhookAuthenticationFailed => {
|
||||
AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None))
|
||||
}
|
||||
Self::WebhookResourceNotFound => {
|
||||
AER::NotFound(ApiError::new("WE", 4, "Webhook resource was not found", None))
|
||||
}
|
||||
Self::WebhookBadRequest => {
|
||||
AER::BadRequest(ApiError::new("WE", 2, "Bad request body received", None))
|
||||
}
|
||||
Self::WebhookProcessingFailure => {
|
||||
AER::InternalServerError(ApiError::new("WE", 3, "There was an issue processing the webhook", None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,9 @@ use common_utils::{crypto::SignMessage, ext_traits};
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
use masking::ExposeInterface;
|
||||
use router_env::{instrument, tracing};
|
||||
use utils::WebhookApiErrorSwitch;
|
||||
|
||||
use super::metrics;
|
||||
use super::{errors::StorageErrorExt, metrics};
|
||||
use crate::{
|
||||
consts,
|
||||
core::{
|
||||
@ -32,7 +33,7 @@ pub async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
merchant_account: storage::MerchantAccount,
|
||||
webhook_details: api::IncomingWebhookDetails,
|
||||
source_verified: bool,
|
||||
) -> CustomResult<(), errors::WebhooksFlowError> {
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
let consume_or_trigger_flow = if source_verified {
|
||||
payments::CallConnectorAction::HandleResponse(webhook_details.resource_object)
|
||||
} else {
|
||||
@ -56,10 +57,13 @@ pub async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
services::AuthFlow::Merchant,
|
||||
consume_or_trigger_flow,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::PaymentsCoreFailed)?
|
||||
.await?
|
||||
}
|
||||
_ => Err(errors::WebhooksFlowError::PaymentsCoreFailed).into_report()?,
|
||||
_ => Err(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.into_report()
|
||||
.attach_printable(
|
||||
"Did not get payment id as object reference id in webhook payments flow",
|
||||
)?,
|
||||
};
|
||||
|
||||
match payments_response {
|
||||
@ -68,13 +72,15 @@ pub async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
.payment_id
|
||||
.clone()
|
||||
.get_required_value("payment_id")
|
||||
.change_context(errors::WebhooksFlowError::PaymentsCoreFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("payment id not received from payments core")?;
|
||||
|
||||
let event_type: enums::EventType = payments_response
|
||||
.status
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::PaymentsCoreFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("payment event type mapping failed")?;
|
||||
|
||||
create_event_and_trigger_outgoing_webhook::<W>(
|
||||
state,
|
||||
@ -89,7 +95,9 @@ pub async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
.await?;
|
||||
}
|
||||
|
||||
_ => Err(errors::WebhooksFlowError::PaymentsCoreFailed).into_report()?,
|
||||
_ => Err(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.into_report()
|
||||
.attach_printable("received non-json response from payments core")?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -103,7 +111,7 @@ pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
connector_name: &str,
|
||||
source_verified: bool,
|
||||
event_type: api_models::webhooks::IncomingWebhookEvent,
|
||||
) -> CustomResult<(), errors::WebhooksFlowError> {
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
let db = &*state.store;
|
||||
//find refund by connector refund id
|
||||
let refund = match webhook_details.object_reference_id {
|
||||
@ -117,7 +125,7 @@ pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound)
|
||||
.change_context(errors::ApiErrorResponse::WebhookResourceNotFound)
|
||||
.attach_printable_lazy(|| "Failed fetching the refund")?,
|
||||
api_models::webhooks::ObjectReferenceId::RefundId(
|
||||
api_models::webhooks::RefundIdType::RefundId(id),
|
||||
@ -128,9 +136,11 @@ pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound)
|
||||
.change_context(errors::ApiErrorResponse::WebhookResourceNotFound)
|
||||
.attach_printable_lazy(|| "Failed fetching the refund")?,
|
||||
_ => Err(errors::WebhooksFlowError::RefundsCoreFailed).into_report()?,
|
||||
_ => Err(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.into_report()
|
||||
.attach_printable("received a non-refund id when processing refund webhooks")?,
|
||||
};
|
||||
let refund_id = refund.refund_id.to_owned();
|
||||
//if source verified then update refund status else trigger refund sync
|
||||
@ -141,7 +151,8 @@ pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
refund_status: event_type
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::RefundsCoreFailed)?,
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("failed refund status mapping from event type")?,
|
||||
};
|
||||
state
|
||||
.store
|
||||
@ -151,7 +162,7 @@ pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::RefundsCoreFailed)
|
||||
.to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound)
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Failed while updating refund: refund_id: {}",
|
||||
@ -169,7 +180,6 @@ pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
},
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::RefundsCoreFailed)
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Failed while updating refund: refund_id: {}",
|
||||
@ -181,7 +191,8 @@ pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
.refund_status
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::RefundsCoreFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("refund status to event type mapping failed")?;
|
||||
let refund_response: api_models::refunds::RefundResponse = updated_refund.foreign_into();
|
||||
create_event_and_trigger_outgoing_webhook::<W>(
|
||||
state,
|
||||
@ -201,7 +212,7 @@ pub async fn get_payment_attempt_from_object_reference_id(
|
||||
state: &AppState,
|
||||
object_reference_id: api_models::webhooks::ObjectReferenceId,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
) -> CustomResult<storage_models::payment_attempt::PaymentAttempt, errors::WebhooksFlowError> {
|
||||
) -> CustomResult<storage_models::payment_attempt::PaymentAttempt, errors::ApiErrorResponse> {
|
||||
let db = &*state.store;
|
||||
match object_reference_id {
|
||||
api::ObjectReferenceId::PaymentId(api::PaymentIdType::ConnectorTransactionId(ref id)) => db
|
||||
@ -211,7 +222,7 @@ pub async fn get_payment_attempt_from_object_reference_id(
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound),
|
||||
.to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound),
|
||||
api::ObjectReferenceId::PaymentId(api::PaymentIdType::PaymentAttemptId(ref id)) => db
|
||||
.find_payment_attempt_by_attempt_id_merchant_id(
|
||||
id,
|
||||
@ -219,7 +230,7 @@ pub async fn get_payment_attempt_from_object_reference_id(
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound),
|
||||
.to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound),
|
||||
api::ObjectReferenceId::PaymentId(api::PaymentIdType::PreprocessingId(ref id)) => db
|
||||
.find_payment_attempt_by_preprocessing_id_merchant_id(
|
||||
id,
|
||||
@ -227,8 +238,10 @@ pub async fn get_payment_attempt_from_object_reference_id(
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound),
|
||||
_ => Err(errors::WebhooksFlowError::ResourceNotFound).into_report(),
|
||||
.to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound),
|
||||
_ => Err(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.into_report()
|
||||
.attach_printable("received a non-payment id for retrieving payment")?,
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +253,7 @@ pub async fn get_or_update_dispute_object(
|
||||
payment_attempt: &storage_models::payment_attempt::PaymentAttempt,
|
||||
event_type: api_models::webhooks::IncomingWebhookEvent,
|
||||
connector_name: &str,
|
||||
) -> CustomResult<storage_models::dispute::Dispute, errors::WebhooksFlowError> {
|
||||
) -> CustomResult<storage_models::dispute::Dispute, errors::ApiErrorResponse> {
|
||||
let db = &*state.store;
|
||||
match option_dispute {
|
||||
None => {
|
||||
@ -254,7 +267,8 @@ pub async fn get_or_update_dispute_object(
|
||||
dispute_status: event_type
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::DisputeCoreFailed)?,
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("event type to dispute status mapping failed")?,
|
||||
payment_id: payment_attempt.payment_id.to_owned(),
|
||||
connector: connector_name.to_owned(),
|
||||
attempt_id: payment_attempt.attempt_id.to_owned(),
|
||||
@ -272,7 +286,7 @@ pub async fn get_or_update_dispute_object(
|
||||
.store
|
||||
.insert_dispute(new_dispute.clone())
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::WebhookEventCreationFailed)
|
||||
.to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound)
|
||||
}
|
||||
Some(dispute) => {
|
||||
logger::info!("Dispute Already exists, Updating the dispute details");
|
||||
@ -280,13 +294,16 @@ pub async fn get_or_update_dispute_object(
|
||||
let dispute_status: storage_models::enums::DisputeStatus = event_type
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::DisputeCoreFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("event type to dispute state conversion failure")?;
|
||||
crate::core::utils::validate_dispute_stage_and_dispute_status(
|
||||
dispute.dispute_stage.foreign_into(),
|
||||
dispute.dispute_status.foreign_into(),
|
||||
dispute_details.dispute_stage.clone(),
|
||||
dispute_status.foreign_into(),
|
||||
)?;
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("dispute stage and status validation failed")?;
|
||||
let update_dispute = storage_models::dispute::DisputeUpdate::Update {
|
||||
dispute_stage: dispute_details.dispute_stage.foreign_into(),
|
||||
dispute_status,
|
||||
@ -298,7 +315,7 @@ pub async fn get_or_update_dispute_object(
|
||||
};
|
||||
db.update_dispute(dispute, update_dispute)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound)
|
||||
.to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,13 +329,11 @@ pub async fn disputes_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
connector: &(dyn api::Connector + Sync),
|
||||
request_details: &api::IncomingWebhookRequestDetails<'_>,
|
||||
event_type: api_models::webhooks::IncomingWebhookEvent,
|
||||
) -> CustomResult<(), errors::WebhooksFlowError> {
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
metrics::INCOMING_DISPUTE_WEBHOOK_METRIC.add(&metrics::CONTEXT, 1, &[]);
|
||||
if source_verified {
|
||||
let db = &*state.store;
|
||||
let dispute_details = connector
|
||||
.get_dispute_details(request_details)
|
||||
.change_context(errors::WebhooksFlowError::WebhookEventObjectCreationFailed)?;
|
||||
let dispute_details = connector.get_dispute_details(request_details).switch()?;
|
||||
let payment_attempt = get_payment_attempt_from_object_reference_id(
|
||||
&state,
|
||||
webhook_details.object_reference_id,
|
||||
@ -332,7 +347,7 @@ pub async fn disputes_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
&dispute_details.connector_dispute_id,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::ResourceNotFound)?;
|
||||
.to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound)?;
|
||||
let dispute_object = get_or_update_dispute_object(
|
||||
state.clone(),
|
||||
option_dispute,
|
||||
@ -348,7 +363,8 @@ pub async fn disputes_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
.dispute_status
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::DisputeCoreFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("failed to map dispute status to event type")?;
|
||||
create_event_and_trigger_outgoing_webhook::<W>(
|
||||
state,
|
||||
merchant_account,
|
||||
@ -364,7 +380,7 @@ pub async fn disputes_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
Ok(())
|
||||
} else {
|
||||
metrics::INCOMING_DISPUTE_WEBHOOK_SIGNATURE_FAILURE_METRIC.add(&metrics::CONTEXT, 1, &[]);
|
||||
Err(errors::WebhooksFlowError::WebhookSourceVerificationFailed).into_report()
|
||||
Err(errors::ApiErrorResponse::WebhookAuthenticationFailed).into_report()
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,7 +389,7 @@ async fn bank_transfer_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
merchant_account: storage::MerchantAccount,
|
||||
webhook_details: api::IncomingWebhookDetails,
|
||||
source_verified: bool,
|
||||
) -> CustomResult<(), errors::WebhooksFlowError> {
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
let response = if source_verified {
|
||||
let payment_attempt = get_payment_attempt_from_object_reference_id(
|
||||
&state,
|
||||
@ -398,10 +414,9 @@ async fn bank_transfer_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
payments::CallConnectorAction::Trigger,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::PaymentsCoreFailed)
|
||||
} else {
|
||||
Err(report!(
|
||||
errors::WebhooksFlowError::WebhookSourceVerificationFailed
|
||||
errors::ApiErrorResponse::WebhookAuthenticationFailed
|
||||
))
|
||||
};
|
||||
|
||||
@ -411,13 +426,15 @@ async fn bank_transfer_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
.payment_id
|
||||
.clone()
|
||||
.get_required_value("payment_id")
|
||||
.change_context(errors::WebhooksFlowError::PaymentsCoreFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("did not receive payment id from payments core response")?;
|
||||
|
||||
let event_type: enums::EventType = payments_response
|
||||
.status
|
||||
.foreign_try_into()
|
||||
.into_report()
|
||||
.change_context(errors::WebhooksFlowError::PaymentsCoreFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("error mapping payments response status to event type")?;
|
||||
|
||||
create_event_and_trigger_outgoing_webhook::<W>(
|
||||
state,
|
||||
@ -432,7 +449,9 @@ async fn bank_transfer_webhook_flow<W: api::OutgoingWebhookType>(
|
||||
.await?;
|
||||
}
|
||||
|
||||
_ => Err(errors::WebhooksFlowError::PaymentsCoreFailed).into_report()?,
|
||||
_ => Err(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.into_report()
|
||||
.attach_printable("received non-json response from payments core")?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -449,7 +468,7 @@ pub async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookTy
|
||||
primary_object_id: String,
|
||||
primary_object_type: enums::EventObjectType,
|
||||
content: api::OutgoingWebhookContent,
|
||||
) -> CustomResult<(), errors::WebhooksFlowError> {
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
let new_event = storage::EventNew {
|
||||
event_id: generate_id(consts::ID_LENGTH, "evt"),
|
||||
event_type,
|
||||
@ -464,12 +483,14 @@ pub async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookTy
|
||||
.store
|
||||
.insert_event(new_event)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::WebhookEventCreationFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("event insertion failure")?;
|
||||
|
||||
if state.conf.webhooks.outgoing_enabled {
|
||||
let arbiter = actix::Arbiter::try_current()
|
||||
.ok_or(errors::WebhooksFlowError::ForkFlowFailed)
|
||||
.into_report()?;
|
||||
.ok_or(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.into_report()
|
||||
.attach_printable("arbiter retrieval failure")?;
|
||||
|
||||
let outgoing_webhook = api::OutgoingWebhook {
|
||||
merchant_id: merchant_account.merchant_id.clone(),
|
||||
@ -481,7 +502,8 @@ pub async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookTy
|
||||
|
||||
let webhook_signature_payload =
|
||||
ext_traits::Encode::<serde_json::Value>::encode_to_string_of_json(&outgoing_webhook)
|
||||
.change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)?;
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("failed encoding outgoing webhook payload")?;
|
||||
|
||||
let outgoing_webhooks_signature = merchant_account
|
||||
.payment_response_hash_key
|
||||
@ -494,7 +516,7 @@ pub async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookTy
|
||||
)
|
||||
})
|
||||
.transpose()
|
||||
.change_context(errors::WebhooksFlowError::OutgoingWebhookSigningFailed)
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("Failed to sign the message")?
|
||||
.map(hex::encode);
|
||||
|
||||
@ -625,7 +647,7 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
&merchant_account.merchant_id,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.switch()
|
||||
.attach_printable("There was an error in incoming webhook body decoding")?;
|
||||
|
||||
request_details.body = &decoded_body;
|
||||
@ -646,7 +668,8 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
logger::info!(process_webhook=?process_webhook_further);
|
||||
logger::info!(event_type=?event_type);
|
||||
|
||||
if process_webhook_further {
|
||||
let flow_type: api::WebhookFlow = event_type.to_owned().into();
|
||||
if process_webhook_further && !matches!(flow_type, api::WebhookFlow::ReturnResponse) {
|
||||
let source_verified = connector
|
||||
.verify_webhook_source(
|
||||
&*state.store,
|
||||
@ -654,17 +677,17 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
&merchant_account.merchant_id,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.switch()
|
||||
.attach_printable("There was an issue in incoming webhook source verification")?;
|
||||
|
||||
let object_ref_id = connector
|
||||
.get_webhook_object_reference_id(&request_details)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.switch()
|
||||
.attach_printable("Could not find object reference id in incoming webhook body")?;
|
||||
|
||||
let event_object = connector
|
||||
.get_webhook_resource_object(&request_details)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.switch()
|
||||
.attach_printable("Could not find resource object in incoming webhook body")?;
|
||||
|
||||
let webhook_details = api::IncomingWebhookDetails {
|
||||
@ -676,7 +699,6 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
)?,
|
||||
};
|
||||
|
||||
let flow_type: api::WebhookFlow = event_type.to_owned().into();
|
||||
match flow_type {
|
||||
api::WebhookFlow::Payment => payments_incoming_webhook_flow::<W>(
|
||||
state.clone(),
|
||||
@ -685,7 +707,6 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
source_verified,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Incoming webhook flow for payments failed")?,
|
||||
|
||||
api::WebhookFlow::Refund => refunds_incoming_webhook_flow::<W>(
|
||||
@ -697,7 +718,6 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
event_type,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Incoming webhook flow for refunds failed")?,
|
||||
|
||||
api::WebhookFlow::Dispute => disputes_incoming_webhook_flow::<W>(
|
||||
@ -710,7 +730,6 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
event_type,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Incoming webhook flow for disputes failed")?,
|
||||
|
||||
api::WebhookFlow::BankTransfer => bank_transfer_webhook_flow::<W>(
|
||||
@ -720,7 +739,6 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
source_verified,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Incoming bank-transfer webhook flow failed")?,
|
||||
|
||||
api::WebhookFlow::ReturnResponse => {}
|
||||
@ -733,7 +751,7 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
|
||||
|
||||
let response = connector
|
||||
.get_webhook_api_response(&request_details)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.switch()
|
||||
.attach_printable("Could not get incoming webhook api response from connector")?;
|
||||
|
||||
Ok(response)
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use crate::{
|
||||
core::errors,
|
||||
db::{get_and_deserialize_key, StorageInterface},
|
||||
types::api,
|
||||
};
|
||||
@ -45,3 +48,30 @@ pub async fn lookup_webhook_event(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WebhookApiErrorSwitch<T> {
|
||||
fn switch(self) -> errors::RouterResult<T>;
|
||||
}
|
||||
|
||||
impl<T> WebhookApiErrorSwitch<T> for errors::CustomResult<T, errors::ConnectorError> {
|
||||
fn switch(self) -> errors::RouterResult<T> {
|
||||
match self {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => match e.current_context() {
|
||||
errors::ConnectorError::WebhookSourceVerificationFailed => {
|
||||
Err(e).change_context(errors::ApiErrorResponse::WebhookAuthenticationFailed)
|
||||
}
|
||||
|
||||
errors::ConnectorError::WebhookSignatureNotFound
|
||||
| errors::ConnectorError::WebhookReferenceIdNotFound
|
||||
| errors::ConnectorError::WebhookEventTypeNotFound
|
||||
| errors::ConnectorError::WebhookResourceObjectNotFound
|
||||
| errors::ConnectorError::WebhookBodyDecodingFailed => {
|
||||
Err(e).change_context(errors::ApiErrorResponse::WebhookBadRequest)
|
||||
}
|
||||
|
||||
_ => Err(e).change_context(errors::ApiErrorResponse::InternalServerError),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user