mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(connector): add dispute webhooks for Stripe (#918)
This commit is contained in:
@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
4
migrations/2023-04-26-062424_alter_dispute_table/up.sql
Normal file
4
migrations/2023-04-26-062424_alter_dispute_table/up.sql
Normal 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
|
||||||
Reference in New Issue
Block a user