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>), 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 {}

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 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,

View File

@ -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,

View File

@ -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,

View File

@ -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")]

View File

@ -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 {

View File

@ -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 {