fix(router): outgoing webhook api call (#1193)

This commit is contained in:
Sai Harsha Vardhan
2023-05-18 13:47:06 +05:30
committed by GitHub
parent 1f52a66452
commit 31a52d8058
7 changed files with 51 additions and 36 deletions

View File

@ -91,5 +91,8 @@ pub enum OutgoingWebhookContent {
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 {}

View File

@ -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 number: cards::CardNumber,
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")]
pub enum StripePaymentStatus {
Succeeded,
@ -288,7 +288,7 @@ pub struct StripeCaptureRequest {
pub amount_to_capture: Option<i64>,
}
#[derive(Default, Eq, PartialEq, Serialize)]
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct StripePaymentIntentResponse {
pub id: Option<String>,
pub object: &'static str,
@ -328,7 +328,7 @@ pub struct StripePaymentIntentResponse {
pub last_payment_error: Option<LastPaymentError>,
}
#[derive(Default, Eq, PartialEq, Serialize)]
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct LastPaymentError {
charge: 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 {
#[serde(rename = "id")]
payment_method_id: String,
@ -414,7 +414,7 @@ pub struct StripePaymentMethod {
livemode: bool,
}
#[derive(Default, Eq, PartialEq, Serialize)]
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct Charges {
object: &'static str,
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 return_url: Option<String>,
pub url: Option<String>,
}
#[derive(Eq, PartialEq, Serialize)]
#[derive(Eq, PartialEq, Serialize, Debug)]
pub struct StripeNextAction {
#[serde(rename = "type")]
stype: payments::NextActionType,

View File

@ -20,7 +20,7 @@ pub struct StripeUpdateRefundRequest {
pub metadata: Option<pii::SecretSerdeValue>,
}
#[derive(Clone, Serialize, PartialEq, Eq)]
#[derive(Clone, Serialize, PartialEq, Eq, Debug)]
pub struct StripeRefundResponse {
pub id: String,
pub amount: i64,
@ -31,7 +31,7 @@ pub struct StripeRefundResponse {
pub metadata: pii::SecretSerdeValue,
}
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripeRefundStatus {
Succeeded,

View File

@ -8,7 +8,7 @@ use super::{
payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse,
};
#[derive(Serialize)]
#[derive(Serialize, Debug)]
pub struct StripeOutgoingWebhook {
id: Option<String>,
#[serde(rename = "type")]
@ -18,7 +18,7 @@ pub struct StripeOutgoingWebhook {
impl api::OutgoingWebhookType for StripeOutgoingWebhook {}
#[derive(Serialize)]
#[derive(Serialize, Debug)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
pub enum StripeWebhookObject {
PaymentIntent(StripePaymentIntentResponse),
@ -26,7 +26,7 @@ pub enum StripeWebhookObject {
Dispute(StripeDisputeResponse),
}
#[derive(Serialize)]
#[derive(Serialize, Debug)]
pub struct StripeDisputeResponse {
pub id: String,
pub amount: String,
@ -36,7 +36,7 @@ pub struct StripeDisputeResponse {
pub status: StripeDisputeStatus,
}
#[derive(Serialize)]
#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripeDisputeStatus {
WarningNeedsResponse,

View File

@ -460,6 +460,8 @@ pub enum WebhooksFlowError {
NotImplemented,
#[error("Dispute webhook status validation failed")]
DisputeWebhookValidationFailed,
#[error("Outgoing webhook body encoding failed")]
OutgoingWebhookEncodingFailed,
}
#[cfg(feature = "detailed_errors")]

View File

@ -12,7 +12,6 @@ use crate::{
errors::{self, CustomResult, RouterResponse},
payments, refunds,
},
db::StorageInterface,
logger,
routes::AppState,
services,
@ -24,7 +23,7 @@ use crate::{
utils::{generate_id, Encode, OptionExt, ValueExt},
};
const OUTGOING_WEBHOOK_TIMEOUT_MS: u64 = 5000;
const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5;
#[instrument(skip_all)]
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 {
let result =
trigger_webhook_to_merchant::<W>(merchant_account, outgoing_webhook, state.store)
.await;
trigger_webhook_to_merchant::<W>(merchant_account, outgoing_webhook, &state).await;
if let Err(e) = result {
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>(
merchant_account: storage::MerchantAccount,
webhook: api::OutgoingWebhook,
db: Box<dyn StorageInterface>,
state: &AppState,
) -> CustomResult<(), errors::WebhooksFlowError> {
let webhook_details_json = merchant_account
.webhook_details
@ -440,29 +438,38 @@ async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>(
let transformed_outgoing_webhook = W::from(webhook);
let response = reqwest::Client::new()
.post(&webhook_url)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.json(&transformed_outgoing_webhook)
.timeout(core::time::Duration::from_millis(
OUTGOING_WEBHOOK_TIMEOUT_MS,
))
.send()
.await;
let transformed_outgoing_webhook_string =
Encode::<serde_json::Value>::encode_to_string_of_json(&transformed_outgoing_webhook)
.change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
.attach_printable("There was an issue when encoding the outgoing webhook body")?;
let request = services::RequestBuilder::new()
.method(services::Method::Post)
.url(&webhook_url)
.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 {
Err(e) => {
// [#217]: Schedule webhook for retry.
Err(e)
.into_report()
.change_context(errors::WebhooksFlowError::CallToMerchantFailed)?;
Err(e).change_context(errors::WebhooksFlowError::CallToMerchantFailed)?;
}
Ok(res) => {
if res.status().is_success() {
let update_event = storage::EventUpdate::UpdateWebhookNotified {
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
.change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)?;
} else {

View File

@ -287,7 +287,7 @@ pub async fn call_connector_api(
) -> CustomResult<Result<types::Response, types::Response>, errors::ApiClientError> {
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();
logger::info!(request_time=?elapsed_time);
@ -296,9 +296,10 @@ pub async fn call_connector_api(
}
#[instrument(skip_all)]
async fn send_request(
pub async fn send_request(
state: &AppState,
request: Request,
option_timeout_secs: Option<u64>,
) -> CustomResult<reqwest::Response, errors::ApiClientError> {
logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request);
let url = &request.url;
@ -356,7 +357,9 @@ async fn send_request(
Method::Delete => client.delete(url),
}
.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()
.await
.map_err(|error| match error {