feat(connector): add dispute webhooks for Stripe (#918)

This commit is contained in:
Shankar Singh C
2023-05-03 02:34:02 +05:30
committed by GitHub
parent c31b4b41c2
commit 0df2244794
14 changed files with 352 additions and 106 deletions

View File

@ -31,11 +31,14 @@ pub struct DisputeResponse {
/// Reason code of dispute sent by connector /// Reason code of dispute sent by connector
pub connector_reason_code: Option<String>, pub connector_reason_code: Option<String>,
/// Evidence deadline of dispute sent by connector /// Evidence deadline of dispute sent by connector
pub challenge_required_by: Option<String>, #[serde(with = "common_utils::custom_serde::iso8601::option")]
pub challenge_required_by: Option<PrimitiveDateTime>,
/// Dispute created time sent by connector /// Dispute created time sent by connector
pub created_at: Option<String>, #[serde(with = "common_utils::custom_serde::iso8601::option")]
pub created_at: Option<PrimitiveDateTime>,
/// Dispute updated time sent by connector /// Dispute updated time sent by connector
pub updated_at: Option<String>, #[serde(with = "common_utils::custom_serde::iso8601::option")]
pub updated_at: Option<PrimitiveDateTime>,
/// Time at which dispute is received /// Time at which dispute is received
pub received_at: String, pub received_at: String,
} }

View File

@ -880,6 +880,7 @@ impl From<AttemptStatus> for IntentStatus {
frunk::LabelledGeneric, frunk::LabelledGeneric,
ToSchema, ToSchema,
)] )]
#[serde(rename_all = "snake_case")]
pub enum DisputeStage { pub enum DisputeStage {
PreDispute, PreDispute,
#[default] #[default]
@ -901,6 +902,7 @@ pub enum DisputeStage {
frunk::LabelledGeneric, frunk::LabelledGeneric,
ToSchema, ToSchema,
)] )]
#[serde(rename_all = "snake_case")]
pub enum DisputeStatus { pub enum DisputeStatus {
#[default] #[default]
DisputeOpened, DisputeOpened,

View File

@ -86,6 +86,74 @@ pub mod iso8601 {
} }
} }
/// Use the UNIX timestamp when serializing and deserializing an
/// [`PrimitiveDateTime`][PrimitiveDateTime].
///
/// [PrimitiveDateTime]: ::time::PrimitiveDateTime
pub mod timestamp {
use serde::{Deserializer, Serialize, Serializer};
use time::{serde::timestamp, PrimitiveDateTime, UtcOffset};
/// Serialize a [`PrimitiveDateTime`] using UNIX timestamp.
pub fn serialize<S>(date_time: &PrimitiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
date_time
.assume_utc()
.unix_timestamp()
.serialize(serializer)
}
/// Deserialize an [`PrimitiveDateTime`] from UNIX timestamp.
pub fn deserialize<'a, D>(deserializer: D) -> Result<PrimitiveDateTime, D::Error>
where
D: Deserializer<'a>,
{
timestamp::deserialize(deserializer).map(|offset_date_time| {
let utc_date_time = offset_date_time.to_offset(UtcOffset::UTC);
PrimitiveDateTime::new(utc_date_time.date(), utc_date_time.time())
})
}
/// Use the UNIX timestamp when serializing and deserializing an
/// [`Option<PrimitiveDateTime>`][PrimitiveDateTime].
///
/// [PrimitiveDateTime]: ::time::PrimitiveDateTime
pub mod option {
use serde::Serialize;
use super::*;
/// Serialize an [`Option<PrimitiveDateTime>`] from UNIX timestamp.
pub fn serialize<S>(
date_time: &Option<PrimitiveDateTime>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
date_time
.map(|date_time| date_time.assume_utc().unix_timestamp())
.serialize(serializer)
}
/// Deserialize an [`Option<PrimitiveDateTime>`] from UNIX timestamp.
pub fn deserialize<'a, D>(deserializer: D) -> Result<Option<PrimitiveDateTime>, D::Error>
where
D: Deserializer<'a>,
{
timestamp::option::deserialize(deserializer).map(|option_offset_date_time| {
option_offset_date_time.map(|offset_date_time| {
let utc_date_time = offset_date_time.to_offset(UtcOffset::UTC);
PrimitiveDateTime::new(utc_date_time.date(), utc_date_time.time())
})
})
}
}
}
/// <https://github.com/serde-rs/serde/issues/994#issuecomment-316895860> /// <https://github.com/serde-rs/serde/issues/994#issuecomment-316895860>
pub mod json_string { pub mod json_string {

View File

@ -792,7 +792,7 @@ impl api::IncomingWebhook for Adyen {
connector_reason_code: notif.additional_data.chargeback_reason_code, connector_reason_code: notif.additional_data.chargeback_reason_code,
challenge_required_by: notif.additional_data.defense_period_ends_at, challenge_required_by: notif.additional_data.defense_period_ends_at,
connector_status: notif.event_code.to_string(), connector_status: notif.event_code.to_string(),
created_at: notif.event_date.clone(), created_at: notif.event_date,
updated_at: notif.event_date, updated_at: notif.event_date,
}) })
} }

View File

@ -2,6 +2,7 @@ use api_models::{enums::DisputeStage, webhooks::IncomingWebhookEvent};
use masking::PeekInterface; use masking::PeekInterface;
use reqwest::Url; use reqwest::Url;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{ use crate::{
connector::utils::PaymentsAuthorizeRequestData, connector::utils::PaymentsAuthorizeRequestData,
@ -1553,7 +1554,8 @@ pub struct AdyenAdditionalDataWH {
pub hmac_signature: String, pub hmac_signature: String,
pub dispute_status: Option<DisputeStatus>, pub dispute_status: Option<DisputeStatus>,
pub chargeback_reason_code: Option<String>, pub chargeback_reason_code: Option<String>,
pub defense_period_ends_at: Option<String>, #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
pub defense_period_ends_at: Option<PrimitiveDateTime>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -1653,7 +1655,8 @@ pub struct AdyenNotificationRequestItemWH {
pub merchant_reference: String, pub merchant_reference: String,
pub success: String, pub success: String,
pub reason: Option<String>, pub reason: Option<String>,
pub event_date: Option<String>, #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
pub event_date: Option<PrimitiveDateTime>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

View File

@ -1083,7 +1083,7 @@ impl api::IncomingWebhook for Checkout {
.parse_struct("CheckoutWebhookBody") .parse_struct("CheckoutWebhookBody")
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
if checkout::is_chargeback_event(&details.txn_type) { if checkout::is_chargeback_event(&details.transaction_type) {
return Ok(api_models::webhooks::ObjectReferenceId::PaymentId( return Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::ConnectorTransactionId( api_models::payments::PaymentIdType::ConnectorTransactionId(
details details
@ -1093,7 +1093,7 @@ impl api::IncomingWebhook for Checkout {
), ),
)); ));
} }
if checkout::is_refund_event(&details.txn_type) { if checkout::is_refund_event(&details.transaction_type) {
return Ok(api_models::webhooks::ObjectReferenceId::RefundId( return Ok(api_models::webhooks::ObjectReferenceId::RefundId(
api_models::webhooks::RefundIdType::ConnectorRefundId( api_models::webhooks::RefundIdType::ConnectorRefundId(
details details
@ -1117,7 +1117,7 @@ impl api::IncomingWebhook for Checkout {
.parse_struct("CheckoutWebhookBody") .parse_struct("CheckoutWebhookBody")
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
Ok(api::IncomingWebhookEvent::from(details.txn_type)) Ok(api::IncomingWebhookEvent::from(details.transaction_type))
} }
fn get_webhook_resource_object( fn get_webhook_resource_object(
@ -1143,12 +1143,14 @@ impl api::IncomingWebhook for Checkout {
Ok(api::disputes::DisputePayload { Ok(api::disputes::DisputePayload {
amount: dispute_details.data.amount.to_string(), amount: dispute_details.data.amount.to_string(),
currency: dispute_details.data.currency, currency: dispute_details.data.currency,
dispute_stage: api_models::enums::DisputeStage::from(dispute_details.txn_type.clone()), dispute_stage: api_models::enums::DisputeStage::from(
dispute_details.transaction_type.clone(),
),
connector_dispute_id: dispute_details.data.id, connector_dispute_id: dispute_details.data.id,
connector_reason: None, connector_reason: None,
connector_reason_code: dispute_details.data.reason_code, connector_reason_code: dispute_details.data.reason_code,
challenge_required_by: dispute_details.data.evidence_required_by, challenge_required_by: dispute_details.data.evidence_required_by,
connector_status: dispute_details.txn_type.to_string(), connector_status: dispute_details.transaction_type.to_string(),
created_at: dispute_details.created_on, created_at: dispute_details.created_on,
updated_at: dispute_details.data.date, updated_at: dispute_details.data.date,
}) })

View File

@ -1,6 +1,7 @@
use common_utils::errors::CustomResult; use common_utils::errors::CustomResult;
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use url::Url; use url::Url;
use crate::{ use crate::{
@ -674,27 +675,27 @@ impl From<CheckoutRedirectResponseStatus> for enums::AttemptStatus {
} }
} }
pub fn is_refund_event(event_code: &CheckoutTxnType) -> bool { pub fn is_refund_event(event_code: &CheckoutTransactionType) -> bool {
matches!( matches!(
event_code, event_code,
CheckoutTxnType::PaymentRefunded | CheckoutTxnType::PaymentRefundDeclined CheckoutTransactionType::PaymentRefunded | CheckoutTransactionType::PaymentRefundDeclined
) )
} }
pub fn is_chargeback_event(event_code: &CheckoutTxnType) -> bool { pub fn is_chargeback_event(event_code: &CheckoutTransactionType) -> bool {
matches!( matches!(
event_code, event_code,
CheckoutTxnType::DisputeReceived CheckoutTransactionType::DisputeReceived
| CheckoutTxnType::DisputeExpired | CheckoutTransactionType::DisputeExpired
| CheckoutTxnType::DisputeAccepted | CheckoutTransactionType::DisputeAccepted
| CheckoutTxnType::DisputeCanceled | CheckoutTransactionType::DisputeCanceled
| CheckoutTxnType::DisputeEvidenceSubmitted | CheckoutTransactionType::DisputeEvidenceSubmitted
| CheckoutTxnType::DisputeEvidenceAcknowledgedByScheme | CheckoutTransactionType::DisputeEvidenceAcknowledgedByScheme
| CheckoutTxnType::DisputeEvidenceRequired | CheckoutTransactionType::DisputeEvidenceRequired
| CheckoutTxnType::DisputeArbitrationLost | CheckoutTransactionType::DisputeArbitrationLost
| CheckoutTxnType::DisputeArbitrationWon | CheckoutTransactionType::DisputeArbitrationWon
| CheckoutTxnType::DisputeWon | CheckoutTransactionType::DisputeWon
| CheckoutTxnType::DisputeLost | CheckoutTransactionType::DisputeLost
) )
} }
@ -705,20 +706,20 @@ pub struct CheckoutWebhookData {
pub action_id: Option<String>, pub action_id: Option<String>,
pub amount: i32, pub amount: i32,
pub currency: String, pub currency: String,
pub evidence_required_by: Option<String>, pub evidence_required_by: Option<PrimitiveDateTime>,
pub reason_code: Option<String>, pub reason_code: Option<String>,
pub date: Option<String>, pub date: Option<PrimitiveDateTime>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct CheckoutWebhookBody { pub struct CheckoutWebhookBody {
#[serde(rename = "type")] #[serde(rename = "type")]
pub txn_type: CheckoutTxnType, pub transaction_type: CheckoutTransactionType,
pub data: CheckoutWebhookData, pub data: CheckoutWebhookData,
pub created_on: Option<String>, pub created_on: Option<PrimitiveDateTime>,
} }
#[derive(Debug, Deserialize, strum::Display, Clone)] #[derive(Debug, Deserialize, strum::Display, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum CheckoutTxnType { pub enum CheckoutTransactionType {
PaymentApproved, PaymentApproved,
PaymentDeclined, PaymentDeclined,
PaymentRefunded, PaymentRefunded,
@ -736,37 +737,35 @@ pub enum CheckoutTxnType {
DisputeLost, DisputeLost,
} }
impl From<CheckoutTxnType> for api::IncomingWebhookEvent { impl From<CheckoutTransactionType> for api::IncomingWebhookEvent {
fn from(txn_type: CheckoutTxnType) -> Self { fn from(transaction_type: CheckoutTransactionType) -> Self {
match txn_type { match transaction_type {
CheckoutTxnType::PaymentApproved => Self::PaymentIntentSuccess, CheckoutTransactionType::PaymentApproved => Self::PaymentIntentSuccess,
CheckoutTxnType::PaymentDeclined => Self::PaymentIntentSuccess, CheckoutTransactionType::PaymentDeclined => Self::PaymentIntentSuccess,
CheckoutTxnType::PaymentRefunded => Self::RefundSuccess, CheckoutTransactionType::PaymentRefunded => Self::RefundSuccess,
CheckoutTxnType::PaymentRefundDeclined => Self::RefundFailure, CheckoutTransactionType::PaymentRefundDeclined => Self::RefundFailure,
CheckoutTxnType::DisputeReceived | CheckoutTxnType::DisputeEvidenceRequired => { CheckoutTransactionType::DisputeReceived
Self::DisputeOpened | CheckoutTransactionType::DisputeEvidenceRequired => Self::DisputeOpened,
} CheckoutTransactionType::DisputeExpired => Self::DisputeExpired,
CheckoutTxnType::DisputeExpired => Self::DisputeExpired, CheckoutTransactionType::DisputeAccepted => Self::DisputeAccepted,
CheckoutTxnType::DisputeAccepted => Self::DisputeAccepted, CheckoutTransactionType::DisputeCanceled => Self::DisputeCancelled,
CheckoutTxnType::DisputeCanceled => Self::DisputeCancelled, CheckoutTransactionType::DisputeEvidenceSubmitted
CheckoutTxnType::DisputeEvidenceSubmitted | CheckoutTransactionType::DisputeEvidenceAcknowledgedByScheme => {
| CheckoutTxnType::DisputeEvidenceAcknowledgedByScheme => Self::DisputeChallenged, Self::DisputeChallenged
CheckoutTxnType::DisputeWon | CheckoutTxnType::DisputeArbitrationWon => {
Self::DisputeWon
}
CheckoutTxnType::DisputeLost | CheckoutTxnType::DisputeArbitrationLost => {
Self::DisputeLost
} }
CheckoutTransactionType::DisputeWon
| CheckoutTransactionType::DisputeArbitrationWon => Self::DisputeWon,
CheckoutTransactionType::DisputeLost
| CheckoutTransactionType::DisputeArbitrationLost => Self::DisputeLost,
} }
} }
} }
impl From<CheckoutTxnType> for api_models::enums::DisputeStage { impl From<CheckoutTransactionType> for api_models::enums::DisputeStage {
fn from(code: CheckoutTxnType) -> Self { fn from(code: CheckoutTransactionType) -> Self {
match code { match code {
CheckoutTxnType::DisputeArbitrationLost | CheckoutTxnType::DisputeArbitrationWon => { CheckoutTransactionType::DisputeArbitrationLost
Self::PreArbitration | CheckoutTransactionType::DisputeArbitrationWon => Self::PreArbitration,
}
_ => Self::Dispute, _ => Self::Dispute,
} }
} }

View File

@ -1284,28 +1284,58 @@ impl api::IncomingWebhook for Stripe {
&self, &self,
request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> { ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
let details: stripe::StripeWebhookObjectId = request let details: stripe::WebhookEvent = request
.body .body
.parse_struct("StripeWebhookObjectId") .parse_struct("WebhookEvent")
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
Ok(api_models::webhooks::ObjectReferenceId::PaymentId( Ok(match details.event_data.event_object.object {
api_models::payments::PaymentIdType::ConnectorTransactionId(details.data.object.id), stripe::WebhookEventObjectType::PaymentIntent => {
)) api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::ConnectorTransactionId(
details.event_data.event_object.id,
),
)
}
stripe::WebhookEventObjectType::Dispute => {
api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::ConnectorTransactionId(
details
.event_data
.event_object
.payment_intent
.ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
),
)
}
_ => Err(errors::ConnectorError::WebhookReferenceIdNotFound)?,
})
} }
fn get_webhook_event_type( fn get_webhook_event_type(
&self, &self,
request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> { ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
let details: stripe::StripeWebhookObjectEventType = request let details: stripe::WebhookEvent = request
.body .body
.parse_struct("StripeWebhookObjectEventType") .parse_struct("WebhookEvent")
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
Ok(match details.event_type {
Ok(match details.event_type.as_str() { stripe::WebhookEventType::PaymentIntentFailed => {
"payment_intent.payment_failed" => api::IncomingWebhookEvent::PaymentIntentFailure, api::IncomingWebhookEvent::PaymentIntentFailure
"payment_intent.succeeded" => api::IncomingWebhookEvent::PaymentIntentSuccess, }
stripe::WebhookEventType::PaymentIntentSucceed => {
api::IncomingWebhookEvent::PaymentIntentSuccess
}
stripe::WebhookEventType::DisputeCreated => api::IncomingWebhookEvent::DisputeOpened,
stripe::WebhookEventType::DisputeClosed => api::IncomingWebhookEvent::DisputeCancelled,
stripe::WebhookEventType::DisputeUpdated => api::IncomingWebhookEvent::try_from(
details
.event_data
.event_object
.status
.ok_or(errors::ConnectorError::WebhookEventTypeNotFound)?,
)?,
_ => Err(errors::ConnectorError::WebhookEventTypeNotFound).into_report()?, _ => Err(errors::ConnectorError::WebhookEventTypeNotFound).into_report()?,
}) })
} }
@ -1314,13 +1344,43 @@ impl api::IncomingWebhook for Stripe {
&self, &self,
request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<serde_json::Value, errors::ConnectorError> { ) -> CustomResult<serde_json::Value, errors::ConnectorError> {
let details: stripe::StripeWebhookObjectResource = request let details: stripe::WebhookEventObjectResource = request
.body .body
.parse_struct("StripeWebhookObjectResource") .parse_struct("WebhookEventObjectResource")
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
Ok(details.data.object) Ok(details.data.object)
} }
fn get_dispute_details(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::disputes::DisputePayload, errors::ConnectorError> {
let details: stripe::WebhookEvent = request
.body
.parse_struct("WebhookEvent")
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(api::disputes::DisputePayload {
amount: details.event_data.event_object.amount.to_string(),
currency: details.event_data.event_object.currency,
dispute_stage: api_models::enums::DisputeStage::Dispute,
connector_dispute_id: details.event_data.event_object.id,
connector_reason: details.event_data.event_object.reason,
connector_reason_code: None,
challenge_required_by: details
.event_data
.event_object
.evidence_details
.map(|payload| payload.due_by),
connector_status: details
.event_data
.event_object
.status
.ok_or(errors::ConnectorError::WebhookResourceObjectNotFound)?
.to_string(),
created_at: Some(details.event_data.event_object.created),
updated_at: None,
})
}
} }
impl services::ConnectorRedirectResponse for Stripe { impl services::ConnectorRedirectResponse for Stripe {

View File

@ -4,6 +4,7 @@ use common_utils::{errors::CustomResult, pii};
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use masking::{ExposeInterface, ExposeOptionInterface, Secret}; use masking::{ExposeInterface, ExposeOptionInterface, Secret};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
@ -357,6 +358,20 @@ pub enum StripeBankNames {
VanLanschot, VanLanschot,
} }
impl TryFrom<WebhookEventStatus> for api_models::webhooks::IncomingWebhookEvent {
type Error = errors::ConnectorError;
fn try_from(value: WebhookEventStatus) -> Result<Self, Self::Error> {
Ok(match value {
WebhookEventStatus::WarningNeedsResponse => Self::DisputeOpened,
WebhookEventStatus::WarningClosed => Self::DisputeCancelled,
WebhookEventStatus::WarningUnderReview => Self::DisputeChallenged,
WebhookEventStatus::Won => Self::DisputeWon,
WebhookEventStatus::Lost => Self::DisputeLost,
_ => Err(errors::ConnectorError::WebhookEventTypeNotFound)?,
})
}
}
impl TryFrom<&api_models::enums::BankNames> for StripeBankNames { impl TryFrom<&api_models::enums::BankNames> for StripeBankNames {
type Error = errors::ConnectorError; type Error = errors::ConnectorError;
fn try_from(bank: &api_models::enums::BankNames) -> Result<Self, Self::Error> { fn try_from(bank: &api_models::enums::BankNames) -> Result<Self, Self::Error> {
@ -1566,34 +1581,119 @@ impl<F, T>
// } // }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct StripeWebhookDataObjectId { pub struct WebhookEventDataResource {
pub id: String,
}
#[derive(Debug, Deserialize)]
pub struct StripeWebhookDataId {
pub object: StripeWebhookDataObjectId,
}
#[derive(Debug, Deserialize)]
pub struct StripeWebhookDataResource {
pub object: serde_json::Value, pub object: serde_json::Value,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct StripeWebhookObjectResource { pub struct WebhookEventObjectResource {
pub data: StripeWebhookDataResource, pub data: WebhookEventDataResource,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct StripeWebhookObjectEventType { pub struct WebhookEvent {
#[serde(rename = "type")] #[serde(rename = "type")]
pub event_type: String, pub event_type: WebhookEventType,
#[serde(rename = "data")]
pub event_data: WebhookEventData,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct StripeWebhookObjectId { pub struct WebhookEventData {
pub data: StripeWebhookDataId, #[serde(rename = "object")]
pub event_object: WebhookEventObjectData,
}
#[derive(Debug, Deserialize)]
pub struct WebhookEventObjectData {
pub id: String,
pub object: WebhookEventObjectType,
pub amount: i32,
pub currency: String,
pub payment_intent: Option<String>,
pub reason: Option<String>,
#[serde(with = "common_utils::custom_serde::timestamp")]
pub created: PrimitiveDateTime,
pub evidence_details: Option<EvidenceDetails>,
pub status: Option<WebhookEventStatus>,
}
#[derive(Debug, Deserialize, strum::Display)]
#[serde(rename_all = "snake_case")]
pub enum WebhookEventObjectType {
PaymentIntent,
Dispute,
Charge,
}
#[derive(Debug, Deserialize)]
pub enum WebhookEventType {
#[serde(rename = "payment_intent.payment_failed")]
PaymentIntentFailed,
#[serde(rename = "payment_intent.succeeded")]
PaymentIntentSucceed,
#[serde(rename = "charge.dispute.captured")]
ChargeDisputeCaptured,
#[serde(rename = "charge.dispute.created")]
DisputeCreated,
#[serde(rename = "charge.dispute.closed")]
DisputeClosed,
#[serde(rename = "charge.dispute.updated")]
DisputeUpdated,
#[serde(rename = "charge.dispute.funds_reinstated")]
ChargeDisputeFundsReinstated,
#[serde(rename = "charge.dispute.funds_withdrawn")]
ChargeDisputeFundsWithdrawn,
#[serde(rename = "charge.expired")]
ChargeExpired,
#[serde(rename = "charge.failed")]
ChargeFailed,
#[serde(rename = "charge.pending")]
ChargePending,
#[serde(rename = "charge.captured")]
ChargeCaptured,
#[serde(rename = "charge.succeeded")]
ChargeSucceeded,
#[serde(rename = "charge.updated")]
ChargeUpdated,
#[serde(rename = "charge.refunded")]
ChanrgeRefunded,
#[serde(rename = "payment_intent.canceled")]
PaymentIntentCanceled,
#[serde(rename = "payment_intent.created")]
PaymentIntentCreated,
#[serde(rename = "payment_intent.processing")]
PaymentIntentProcessing,
#[serde(rename = "payment_intent.requires_action")]
PaymentIntentRequiresAction,
#[serde(rename = "amount_capturable_updated")]
PaymentIntentAmountCapturableUpdated,
}
#[derive(Debug, Serialize, strum::Display, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum WebhookEventStatus {
WarningNeedsResponse,
WarningClosed,
WarningUnderReview,
Won,
Lost,
NeedsResponse,
UnderReview,
ChargeRefunded,
Succeeded,
RequiresPaymentMethod,
RequiresConfirmation,
RequiresAction,
Processing,
RequiresCapture,
Canceled,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct EvidenceDetails {
#[serde(with = "common_utils::custom_serde::timestamp")]
pub due_by: PrimitiveDateTime,
} }
impl impl

View File

@ -1,4 +1,5 @@
use masking::{Deserialize, Serialize}; use masking::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{services, types}; use crate::{services, types};
@ -7,7 +8,7 @@ pub struct DisputeId {
pub dispute_id: String, pub dispute_id: String,
} }
#[derive(Default, Debug, Deserialize)] #[derive(Default, Debug)]
pub struct DisputePayload { pub struct DisputePayload {
pub amount: String, pub amount: String,
pub currency: String, pub currency: String,
@ -16,9 +17,9 @@ pub struct DisputePayload {
pub connector_dispute_id: String, pub connector_dispute_id: String,
pub connector_reason: Option<String>, pub connector_reason: Option<String>,
pub connector_reason_code: Option<String>, pub connector_reason_code: Option<String>,
pub challenge_required_by: Option<String>, pub challenge_required_by: Option<PrimitiveDateTime>,
pub created_at: Option<String>, pub created_at: Option<PrimitiveDateTime>,
pub updated_at: Option<String>, pub updated_at: Option<PrimitiveDateTime>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -1,11 +1,11 @@
use common_utils::custom_serde; use common_utils::custom_serde;
use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable};
use serde::{Deserialize, Serialize}; use serde::Serialize;
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
use crate::{enums as storage_enums, schema::dispute}; use crate::{enums as storage_enums, schema::dispute};
#[derive(Clone, Debug, Deserialize, Insertable, Serialize, router_derive::DebugAsDisplay)] #[derive(Clone, Debug, Insertable, Serialize, router_derive::DebugAsDisplay)]
#[diesel(table_name = dispute)] #[diesel(table_name = dispute)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct DisputeNew { pub struct DisputeNew {
@ -21,13 +21,13 @@ pub struct DisputeNew {
pub connector_dispute_id: String, pub connector_dispute_id: String,
pub connector_reason: Option<String>, pub connector_reason: Option<String>,
pub connector_reason_code: Option<String>, pub connector_reason_code: Option<String>,
pub challenge_required_by: Option<String>, pub challenge_required_by: Option<PrimitiveDateTime>,
pub dispute_created_at: Option<String>, pub dispute_created_at: Option<PrimitiveDateTime>,
pub updated_at: Option<String>, pub updated_at: Option<PrimitiveDateTime>,
pub connector: String, pub connector: String,
} }
#[derive(Clone, Debug, Deserialize, Serialize, Identifiable, Queryable)] #[derive(Clone, Debug, Serialize, Identifiable, Queryable)]
#[diesel(table_name = dispute)] #[diesel(table_name = dispute)]
pub struct Dispute { pub struct Dispute {
#[serde(skip_serializing)] #[serde(skip_serializing)]
@ -44,9 +44,9 @@ pub struct Dispute {
pub connector_dispute_id: String, pub connector_dispute_id: String,
pub connector_reason: Option<String>, pub connector_reason: Option<String>,
pub connector_reason_code: Option<String>, pub connector_reason_code: Option<String>,
pub challenge_required_by: Option<String>, pub challenge_required_by: Option<PrimitiveDateTime>,
pub dispute_created_at: Option<String>, pub dispute_created_at: Option<PrimitiveDateTime>,
pub updated_at: Option<String>, pub updated_at: Option<PrimitiveDateTime>,
#[serde(with = "custom_serde::iso8601")] #[serde(with = "custom_serde::iso8601")]
pub created_at: PrimitiveDateTime, pub created_at: PrimitiveDateTime,
#[serde(with = "custom_serde::iso8601")] #[serde(with = "custom_serde::iso8601")]
@ -62,8 +62,8 @@ pub enum DisputeUpdate {
connector_status: String, connector_status: String,
connector_reason: Option<String>, connector_reason: Option<String>,
connector_reason_code: Option<String>, connector_reason_code: Option<String>,
challenge_required_by: Option<String>, challenge_required_by: Option<PrimitiveDateTime>,
updated_at: Option<String>, updated_at: Option<PrimitiveDateTime>,
}, },
StatusUpdate { StatusUpdate {
dispute_status: storage_enums::DisputeStatus, dispute_status: storage_enums::DisputeStatus,
@ -79,8 +79,8 @@ pub struct DisputeUpdateInternal {
connector_status: Option<String>, connector_status: Option<String>,
connector_reason: Option<String>, connector_reason: Option<String>,
connector_reason_code: Option<String>, connector_reason_code: Option<String>,
challenge_required_by: Option<String>, challenge_required_by: Option<PrimitiveDateTime>,
updated_at: Option<String>, updated_at: Option<PrimitiveDateTime>,
modified_at: Option<PrimitiveDateTime>, modified_at: Option<PrimitiveDateTime>,
} }

View File

@ -128,9 +128,9 @@ diesel::table! {
connector_dispute_id -> Varchar, connector_dispute_id -> Varchar,
connector_reason -> Nullable<Varchar>, connector_reason -> Nullable<Varchar>,
connector_reason_code -> Nullable<Varchar>, connector_reason_code -> Nullable<Varchar>,
challenge_required_by -> Nullable<Varchar>, challenge_required_by -> Nullable<Timestamp>,
dispute_created_at -> Nullable<Varchar>, dispute_created_at -> Nullable<Timestamp>,
updated_at -> Nullable<Varchar>, updated_at -> Nullable<Timestamp>,
created_at -> Timestamp, created_at -> Timestamp,
modified_at -> Timestamp, modified_at -> Timestamp,
connector -> Varchar, connector -> Varchar,

View File

@ -0,0 +1,4 @@
ALTER TABLE dispute
ALTER COLUMN challenge_required_by TYPE VARCHAR(255),
ALTER COLUMN dispute_created_at TYPE VARCHAR(255),
ALTER COLUMN updated_at TYPE VARCHAR(255)

View File

@ -0,0 +1,4 @@
ALTER TABLE dispute
ALTER COLUMN challenge_required_by TYPE TIMESTAMP USING dispute_created_at::TIMESTAMP,
ALTER COLUMN dispute_created_at TYPE TIMESTAMP USING dispute_created_at::TIMESTAMP,
ALTER COLUMN updated_at TYPE TIMESTAMP USING dispute_created_at::TIMESTAMP