feat(router): add payment incoming webhooks support for v2 (#6551)

Co-authored-by: Narayan Bhat <narayan.bhat@juspay.in>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: hrithikesh026 <hrithikesh.vm@juspay.in>
This commit is contained in:
Sai Harsha Vardhan
2024-11-19 13:13:58 +05:30
committed by GitHub
parent 65bf75a75e
commit 8e9c3ec893
25 changed files with 1271 additions and 9 deletions

View File

@ -3691,6 +3691,7 @@ pub struct PaymentMethodDataResponseWithBilling {
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
#[cfg(feature = "v1")]
pub enum PaymentIdType {
/// The identifier for payment intent
PaymentIntentId(id_type::PaymentId),
@ -3702,6 +3703,20 @@ pub enum PaymentIdType {
PreprocessingId(String),
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
#[cfg(feature = "v2")]
pub enum PaymentIdType {
/// The identifier for payment intent
PaymentIntentId(id_type::GlobalPaymentId),
/// The identifier for connector transaction
ConnectorTransactionId(String),
/// The identifier for payment attempt
PaymentAttemptId(String),
/// The identifier for preprocessing step
PreprocessingId(String),
}
#[cfg(feature = "v1")]
impl fmt::Display for PaymentIdType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@ -3726,6 +3741,7 @@ impl fmt::Display for PaymentIdType {
}
}
#[cfg(feature = "v1")]
impl Default for PaymentIdType {
fn default() -> Self {
Self::PaymentIntentId(Default::default())
@ -4566,7 +4582,7 @@ pub struct PaymentsRetrieveRequest {
/// Error details for the payment
#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, ToSchema)]
#[derive(Debug, serde::Serialize, Clone, ToSchema)]
pub struct ErrorDetails {
/// The error code
pub code: String,
@ -4651,7 +4667,7 @@ pub struct PaymentsConfirmIntentResponse {
// TODO: have a separate response for detailed, summarized
/// Response for Payment Intent Confirm
#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, ToSchema)]
#[derive(Debug, serde::Serialize, Clone, ToSchema)]
pub struct PaymentsRetrieveResponse {
/// Unique identifier for the payment. This ensures idempotency for multiple payments
/// that have been done by a single merchant.
@ -6307,6 +6323,85 @@ pub struct FrmMessage {
pub frm_error: Option<String>,
}
#[cfg(feature = "v2")]
mod payment_id_type {
use std::{borrow::Cow, fmt};
use serde::{
de::{self, Visitor},
Deserializer,
};
use super::PaymentIdType;
struct PaymentIdVisitor;
struct OptionalPaymentIdVisitor;
impl<'de> Visitor<'de> for PaymentIdVisitor {
type Value = PaymentIdType;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("payment id")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
common_utils::id_type::GlobalPaymentId::try_from(Cow::Owned(value.to_string()))
.map_err(de::Error::custom)
.map(PaymentIdType::PaymentIntentId)
}
}
impl<'de> Visitor<'de> for OptionalPaymentIdVisitor {
type Value = Option<PaymentIdType>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("payment id")
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(PaymentIdVisitor).map(Some)
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
}
#[allow(dead_code)]
pub(crate) fn deserialize<'a, D>(deserializer: D) -> Result<PaymentIdType, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_any(PaymentIdVisitor)
}
pub(crate) fn deserialize_option<'a, D>(
deserializer: D,
) -> Result<Option<PaymentIdType>, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_option(OptionalPaymentIdVisitor)
}
}
#[cfg(feature = "v1")]
mod payment_id_type {
use std::{borrow::Cow, fmt};

View File

@ -76,25 +76,45 @@ pub enum WebhookFlow {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
/// This enum tells about the affect a webhook had on an object
pub enum WebhookResponseTracker {
#[cfg(feature = "v1")]
Payment {
payment_id: common_utils::id_type::PaymentId,
status: common_enums::IntentStatus,
},
#[cfg(feature = "v2")]
Payment {
payment_id: common_utils::id_type::GlobalPaymentId,
status: common_enums::IntentStatus,
},
#[cfg(feature = "payouts")]
Payout {
payout_id: String,
status: common_enums::PayoutStatus,
},
#[cfg(feature = "v1")]
Refund {
payment_id: common_utils::id_type::PaymentId,
refund_id: String,
status: common_enums::RefundStatus,
},
#[cfg(feature = "v2")]
Refund {
payment_id: common_utils::id_type::GlobalPaymentId,
refund_id: String,
status: common_enums::RefundStatus,
},
#[cfg(feature = "v1")]
Dispute {
dispute_id: String,
payment_id: common_utils::id_type::PaymentId,
status: common_enums::DisputeStatus,
},
#[cfg(feature = "v2")]
Dispute {
dispute_id: String,
payment_id: common_utils::id_type::GlobalPaymentId,
status: common_enums::DisputeStatus,
},
Mandate {
mandate_id: String,
status: common_enums::MandateStatus,
@ -103,6 +123,7 @@ pub enum WebhookResponseTracker {
}
impl WebhookResponseTracker {
#[cfg(feature = "v1")]
pub fn get_payment_id(&self) -> Option<common_utils::id_type::PaymentId> {
match self {
Self::Payment { payment_id, .. }
@ -113,6 +134,18 @@ impl WebhookResponseTracker {
Self::Payout { .. } => None,
}
}
#[cfg(feature = "v2")]
pub fn get_payment_id(&self) -> Option<common_utils::id_type::GlobalPaymentId> {
match self {
Self::Payment { payment_id, .. }
| Self::Refund { payment_id, .. }
| Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()),
Self::NoEffect | Self::Mandate { .. } => None,
#[cfg(feature = "payouts")]
Self::Payout { .. } => None,
}
}
}
impl From<IncomingWebhookEvent> for WebhookFlow {
@ -227,6 +260,7 @@ pub struct OutgoingWebhook {
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
#[cfg(feature = "v1")]
pub enum OutgoingWebhookContent {
#[schema(value_type = PaymentsResponse, title = "PaymentsResponse")]
PaymentDetails(Box<payments::PaymentsResponse>),
@ -241,6 +275,23 @@ pub enum OutgoingWebhookContent {
PayoutDetails(Box<payouts::PayoutCreateResponse>),
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
#[cfg(feature = "v2")]
pub enum OutgoingWebhookContent {
#[schema(value_type = PaymentsResponse, title = "PaymentsResponse")]
PaymentDetails(Box<payments::PaymentsRetrieveResponse>),
#[schema(value_type = RefundResponse, title = "RefundResponse")]
RefundDetails(Box<refunds::RefundResponse>),
#[schema(value_type = DisputeResponse, title = "DisputeResponse")]
DisputeDetails(Box<disputes::DisputeResponse>),
#[schema(value_type = MandateResponse, title = "MandateResponse")]
MandateDetails(Box<mandates::MandateResponse>),
#[cfg(feature = "payouts")]
#[schema(value_type = PayoutCreateResponse, title = "PayoutCreateResponse")]
PayoutDetails(Box<payouts::PayoutCreateResponse>),
}
#[derive(Debug, Clone, Serialize)]
pub struct ConnectorWebhookSecrets {
pub secret: Vec<u8>,