mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
fix(router): outgoing webhook api call (#1193)
This commit is contained in:
committed by
GitHub
parent
1f52a66452
commit
31a52d8058
@ -91,5 +91,8 @@ pub enum OutgoingWebhookContent {
|
|||||||
DisputeDetails(Box<disputes::DisputeResponse>),
|
DisputeDetails(Box<disputes::DisputeResponse>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait OutgoingWebhookType: Serialize + From<OutgoingWebhook> + Sync + Send {}
|
pub trait OutgoingWebhookType:
|
||||||
|
Serialize + From<OutgoingWebhook> + Sync + Send + std::fmt::Debug
|
||||||
|
{
|
||||||
|
}
|
||||||
impl OutgoingWebhookType for OutgoingWebhook {}
|
impl OutgoingWebhookType for OutgoingWebhook {}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ impl From<StripeBillingDetails> for payments::Address {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
|
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
|
||||||
pub struct StripeCard {
|
pub struct StripeCard {
|
||||||
pub number: cards::CardNumber,
|
pub number: cards::CardNumber,
|
||||||
pub exp_month: pii::Secret<String>,
|
pub exp_month: pii::Secret<String>,
|
||||||
@ -221,7 +221,7 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Default, Eq, PartialEq, Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum StripePaymentStatus {
|
pub enum StripePaymentStatus {
|
||||||
Succeeded,
|
Succeeded,
|
||||||
@ -288,7 +288,7 @@ pub struct StripeCaptureRequest {
|
|||||||
pub amount_to_capture: Option<i64>,
|
pub amount_to_capture: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize)]
|
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
|
||||||
pub struct StripePaymentIntentResponse {
|
pub struct StripePaymentIntentResponse {
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
pub object: &'static str,
|
pub object: &'static str,
|
||||||
@ -328,7 +328,7 @@ pub struct StripePaymentIntentResponse {
|
|||||||
pub last_payment_error: Option<LastPaymentError>,
|
pub last_payment_error: Option<LastPaymentError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize)]
|
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
|
||||||
pub struct LastPaymentError {
|
pub struct LastPaymentError {
|
||||||
charge: Option<String>,
|
charge: Option<String>,
|
||||||
code: Option<String>,
|
code: Option<String>,
|
||||||
@ -402,7 +402,7 @@ impl From<payments::PaymentsResponse> for StripePaymentIntentResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize)]
|
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
|
||||||
pub struct StripePaymentMethod {
|
pub struct StripePaymentMethod {
|
||||||
#[serde(rename = "id")]
|
#[serde(rename = "id")]
|
||||||
payment_method_id: String,
|
payment_method_id: String,
|
||||||
@ -414,7 +414,7 @@ pub struct StripePaymentMethod {
|
|||||||
livemode: bool,
|
livemode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize)]
|
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
|
||||||
pub struct Charges {
|
pub struct Charges {
|
||||||
object: &'static str,
|
object: &'static str,
|
||||||
data: Vec<String>,
|
data: Vec<String>,
|
||||||
@ -604,13 +604,13 @@ impl ForeignFrom<Option<Request3DS>> for api_models::enums::AuthenticationType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize)]
|
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
|
||||||
pub struct RedirectUrl {
|
pub struct RedirectUrl {
|
||||||
pub return_url: Option<String>,
|
pub return_url: Option<String>,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Serialize)]
|
#[derive(Eq, PartialEq, Serialize, Debug)]
|
||||||
pub struct StripeNextAction {
|
pub struct StripeNextAction {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
stype: payments::NextActionType,
|
stype: payments::NextActionType,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ pub struct StripeUpdateRefundRequest {
|
|||||||
pub metadata: Option<pii::SecretSerdeValue>,
|
pub metadata: Option<pii::SecretSerdeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Serialize, PartialEq, Eq, Debug)]
|
||||||
pub struct StripeRefundResponse {
|
pub struct StripeRefundResponse {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub amount: i64,
|
pub amount: i64,
|
||||||
@ -31,7 +31,7 @@ pub struct StripeRefundResponse {
|
|||||||
pub metadata: pii::SecretSerdeValue,
|
pub metadata: pii::SecretSerdeValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum StripeRefundStatus {
|
pub enum StripeRefundStatus {
|
||||||
Succeeded,
|
Succeeded,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use super::{
|
|||||||
payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse,
|
payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct StripeOutgoingWebhook {
|
pub struct StripeOutgoingWebhook {
|
||||||
id: Option<String>,
|
id: Option<String>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
@ -18,7 +18,7 @@ pub struct StripeOutgoingWebhook {
|
|||||||
|
|
||||||
impl api::OutgoingWebhookType for StripeOutgoingWebhook {}
|
impl api::OutgoingWebhookType for StripeOutgoingWebhook {}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Debug)]
|
||||||
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
|
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
|
||||||
pub enum StripeWebhookObject {
|
pub enum StripeWebhookObject {
|
||||||
PaymentIntent(StripePaymentIntentResponse),
|
PaymentIntent(StripePaymentIntentResponse),
|
||||||
@ -26,7 +26,7 @@ pub enum StripeWebhookObject {
|
|||||||
Dispute(StripeDisputeResponse),
|
Dispute(StripeDisputeResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct StripeDisputeResponse {
|
pub struct StripeDisputeResponse {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub amount: String,
|
pub amount: String,
|
||||||
@ -36,7 +36,7 @@ pub struct StripeDisputeResponse {
|
|||||||
pub status: StripeDisputeStatus,
|
pub status: StripeDisputeStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum StripeDisputeStatus {
|
pub enum StripeDisputeStatus {
|
||||||
WarningNeedsResponse,
|
WarningNeedsResponse,
|
||||||
|
|||||||
@ -460,6 +460,8 @@ pub enum WebhooksFlowError {
|
|||||||
NotImplemented,
|
NotImplemented,
|
||||||
#[error("Dispute webhook status validation failed")]
|
#[error("Dispute webhook status validation failed")]
|
||||||
DisputeWebhookValidationFailed,
|
DisputeWebhookValidationFailed,
|
||||||
|
#[error("Outgoing webhook body encoding failed")]
|
||||||
|
OutgoingWebhookEncodingFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "detailed_errors")]
|
#[cfg(feature = "detailed_errors")]
|
||||||
|
|||||||
@ -12,7 +12,6 @@ use crate::{
|
|||||||
errors::{self, CustomResult, RouterResponse},
|
errors::{self, CustomResult, RouterResponse},
|
||||||
payments, refunds,
|
payments, refunds,
|
||||||
},
|
},
|
||||||
db::StorageInterface,
|
|
||||||
logger,
|
logger,
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
services,
|
services,
|
||||||
@ -24,7 +23,7 @@ use crate::{
|
|||||||
utils::{generate_id, Encode, OptionExt, ValueExt},
|
utils::{generate_id, Encode, OptionExt, ValueExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
const OUTGOING_WEBHOOK_TIMEOUT_MS: u64 = 5000;
|
const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5;
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>(
|
||||||
@ -403,8 +402,7 @@ async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookType>(
|
|||||||
|
|
||||||
arbiter.spawn(async move {
|
arbiter.spawn(async move {
|
||||||
let result =
|
let result =
|
||||||
trigger_webhook_to_merchant::<W>(merchant_account, outgoing_webhook, state.store)
|
trigger_webhook_to_merchant::<W>(merchant_account, outgoing_webhook, &state).await;
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
logger::error!(?e);
|
logger::error!(?e);
|
||||||
@ -418,7 +416,7 @@ async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookType>(
|
|||||||
async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>(
|
async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>(
|
||||||
merchant_account: storage::MerchantAccount,
|
merchant_account: storage::MerchantAccount,
|
||||||
webhook: api::OutgoingWebhook,
|
webhook: api::OutgoingWebhook,
|
||||||
db: Box<dyn StorageInterface>,
|
state: &AppState,
|
||||||
) -> CustomResult<(), errors::WebhooksFlowError> {
|
) -> CustomResult<(), errors::WebhooksFlowError> {
|
||||||
let webhook_details_json = merchant_account
|
let webhook_details_json = merchant_account
|
||||||
.webhook_details
|
.webhook_details
|
||||||
@ -440,29 +438,38 @@ async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>(
|
|||||||
|
|
||||||
let transformed_outgoing_webhook = W::from(webhook);
|
let transformed_outgoing_webhook = W::from(webhook);
|
||||||
|
|
||||||
let response = reqwest::Client::new()
|
let transformed_outgoing_webhook_string =
|
||||||
.post(&webhook_url)
|
Encode::<serde_json::Value>::encode_to_string_of_json(&transformed_outgoing_webhook)
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
.change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
|
||||||
.json(&transformed_outgoing_webhook)
|
.attach_printable("There was an issue when encoding the outgoing webhook body")?;
|
||||||
.timeout(core::time::Duration::from_millis(
|
|
||||||
OUTGOING_WEBHOOK_TIMEOUT_MS,
|
let request = services::RequestBuilder::new()
|
||||||
))
|
.method(services::Method::Post)
|
||||||
.send()
|
.url(&webhook_url)
|
||||||
.await;
|
.attach_default_headers()
|
||||||
|
.headers(vec![(
|
||||||
|
reqwest::header::CONTENT_TYPE.to_string(),
|
||||||
|
"application/json".into(),
|
||||||
|
)])
|
||||||
|
.body(Some(transformed_outgoing_webhook_string))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let response =
|
||||||
|
services::api::send_request(state, request, Some(OUTGOING_WEBHOOK_TIMEOUT_SECS)).await;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// [#217]: Schedule webhook for retry.
|
// [#217]: Schedule webhook for retry.
|
||||||
Err(e)
|
Err(e).change_context(errors::WebhooksFlowError::CallToMerchantFailed)?;
|
||||||
.into_report()
|
|
||||||
.change_context(errors::WebhooksFlowError::CallToMerchantFailed)?;
|
|
||||||
}
|
}
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
if res.status().is_success() {
|
if res.status().is_success() {
|
||||||
let update_event = storage::EventUpdate::UpdateWebhookNotified {
|
let update_event = storage::EventUpdate::UpdateWebhookNotified {
|
||||||
is_webhook_notified: Some(true),
|
is_webhook_notified: Some(true),
|
||||||
};
|
};
|
||||||
db.update_event(outgoing_webhook_event_id, update_event)
|
state
|
||||||
|
.store
|
||||||
|
.update_event(outgoing_webhook_event_id, update_event)
|
||||||
.await
|
.await
|
||||||
.change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)?;
|
.change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)?;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -287,7 +287,7 @@ pub async fn call_connector_api(
|
|||||||
) -> CustomResult<Result<types::Response, types::Response>, errors::ApiClientError> {
|
) -> CustomResult<Result<types::Response, types::Response>, errors::ApiClientError> {
|
||||||
let current_time = Instant::now();
|
let current_time = Instant::now();
|
||||||
|
|
||||||
let response = send_request(state, request).await;
|
let response = send_request(state, request, None).await;
|
||||||
|
|
||||||
let elapsed_time = current_time.elapsed();
|
let elapsed_time = current_time.elapsed();
|
||||||
logger::info!(request_time=?elapsed_time);
|
logger::info!(request_time=?elapsed_time);
|
||||||
@ -296,9 +296,10 @@ pub async fn call_connector_api(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn send_request(
|
pub async fn send_request(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: Request,
|
request: Request,
|
||||||
|
option_timeout_secs: Option<u64>,
|
||||||
) -> CustomResult<reqwest::Response, errors::ApiClientError> {
|
) -> CustomResult<reqwest::Response, errors::ApiClientError> {
|
||||||
logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request);
|
logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request);
|
||||||
let url = &request.url;
|
let url = &request.url;
|
||||||
@ -356,7 +357,9 @@ async fn send_request(
|
|||||||
Method::Delete => client.delete(url),
|
Method::Delete => client.delete(url),
|
||||||
}
|
}
|
||||||
.add_headers(headers)
|
.add_headers(headers)
|
||||||
.timeout(Duration::from_secs(crate::consts::REQUEST_TIME_OUT))
|
.timeout(Duration::from_secs(
|
||||||
|
option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT),
|
||||||
|
))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|error| match error {
|
.map_err(|error| match error {
|
||||||
|
|||||||
Reference in New Issue
Block a user