mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
feat(Connector): [Paypal] add support for dispute webhooks for paypal connector (#2353)
This commit is contained in:
@ -8,7 +8,7 @@ use error_stack::{IntoReport, ResultExt};
|
|||||||
use masking::PeekInterface;
|
use masking::PeekInterface;
|
||||||
use transformers as paypal;
|
use transformers as paypal;
|
||||||
|
|
||||||
use self::transformers::{PaypalAuthResponse, PaypalMeta};
|
use self::transformers::{PaypalAuthResponse, PaypalMeta, PaypalWebhookEventType};
|
||||||
use super::utils::PaymentsCompleteAuthorizeRequestData;
|
use super::utils::PaymentsCompleteAuthorizeRequestData;
|
||||||
use crate::{
|
use crate::{
|
||||||
configs::settings,
|
configs::settings,
|
||||||
@ -30,6 +30,7 @@ use crate::{
|
|||||||
types::{
|
types::{
|
||||||
self,
|
self,
|
||||||
api::{self, CompleteAuthorize, ConnectorCommon, ConnectorCommonExt, VerifyWebhookSource},
|
api::{self, CompleteAuthorize, ConnectorCommon, ConnectorCommonExt, VerifyWebhookSource},
|
||||||
|
transformers::ForeignFrom,
|
||||||
ErrorResponse, Response,
|
ErrorResponse, Response,
|
||||||
},
|
},
|
||||||
utils::{self, BytesExt},
|
utils::{self, BytesExt},
|
||||||
@ -1113,6 +1114,17 @@ impl api::IncomingWebhook for Paypal {
|
|||||||
api_models::webhooks::RefundIdType::ConnectorRefundId(resource.id),
|
api_models::webhooks::RefundIdType::ConnectorRefundId(resource.id),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
paypal::PaypalResource::PaypalDisputeWebhooks(resource) => {
|
||||||
|
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
|
||||||
|
api_models::payments::PaymentIdType::PaymentAttemptId(
|
||||||
|
resource
|
||||||
|
.dispute_transactions
|
||||||
|
.first()
|
||||||
|
.map(|transaction| transaction.reference_id.clone())
|
||||||
|
.ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1124,7 +1136,33 @@ impl api::IncomingWebhook for Paypal {
|
|||||||
.body
|
.body
|
||||||
.parse_struct("PaypalWebooksEventType")
|
.parse_struct("PaypalWebooksEventType")
|
||||||
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
|
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
|
||||||
Ok(api::IncomingWebhookEvent::from(payload.event_type))
|
let outcome = match payload.event_type {
|
||||||
|
PaypalWebhookEventType::CustomerDisputeCreated
|
||||||
|
| PaypalWebhookEventType::CustomerDisputeResolved
|
||||||
|
| PaypalWebhookEventType::CustomerDisputedUpdated
|
||||||
|
| PaypalWebhookEventType::RiskDisputeCreated => Some(
|
||||||
|
request
|
||||||
|
.body
|
||||||
|
.parse_struct::<paypal::DisputeOutcome>("PaypalWebooksEventType")
|
||||||
|
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?
|
||||||
|
.outcome_code,
|
||||||
|
),
|
||||||
|
PaypalWebhookEventType::PaymentAuthorizationCreated
|
||||||
|
| PaypalWebhookEventType::PaymentAuthorizationVoided
|
||||||
|
| PaypalWebhookEventType::PaymentCaptureDeclined
|
||||||
|
| PaypalWebhookEventType::PaymentCaptureCompleted
|
||||||
|
| PaypalWebhookEventType::PaymentCapturePending
|
||||||
|
| PaypalWebhookEventType::PaymentCaptureRefunded
|
||||||
|
| PaypalWebhookEventType::CheckoutOrderApproved
|
||||||
|
| PaypalWebhookEventType::CheckoutOrderCompleted
|
||||||
|
| PaypalWebhookEventType::CheckoutOrderProcessed
|
||||||
|
| PaypalWebhookEventType::Unknown => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(api::IncomingWebhookEvent::foreign_from((
|
||||||
|
payload.event_type,
|
||||||
|
outcome,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_webhook_resource_object(
|
fn get_webhook_resource_object(
|
||||||
@ -1152,9 +1190,39 @@ impl api::IncomingWebhook for Paypal {
|
|||||||
)
|
)
|
||||||
.into_report()
|
.into_report()
|
||||||
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?,
|
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?,
|
||||||
|
paypal::PaypalResource::PaypalDisputeWebhooks(_) => serde_json::to_value(details)
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?,
|
||||||
};
|
};
|
||||||
Ok(sync_payload)
|
Ok(sync_payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_dispute_details(
|
||||||
|
&self,
|
||||||
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
|
) -> CustomResult<api::disputes::DisputePayload, errors::ConnectorError> {
|
||||||
|
let payload: paypal::PaypalDisputeWebhooks = request
|
||||||
|
.body
|
||||||
|
.parse_struct("PaypalDisputeWebhooks")
|
||||||
|
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
||||||
|
Ok(api::disputes::DisputePayload {
|
||||||
|
amount: connector_utils::to_currency_lower_unit(
|
||||||
|
payload.dispute_amount.value,
|
||||||
|
payload.dispute_amount.currency_code,
|
||||||
|
)?,
|
||||||
|
currency: payload.dispute_amount.currency_code.to_string(),
|
||||||
|
dispute_stage: api_models::enums::DisputeStage::from(
|
||||||
|
payload.dispute_life_cycle_stage.clone(),
|
||||||
|
),
|
||||||
|
connector_status: payload.status.to_string(),
|
||||||
|
connector_dispute_id: payload.dispute_id,
|
||||||
|
connector_reason: payload.reason,
|
||||||
|
connector_reason_code: payload.external_reason_code,
|
||||||
|
challenge_required_by: payload.seller_response_due_date,
|
||||||
|
created_at: payload.create_time,
|
||||||
|
updated_at: payload.update_time,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl services::ConnectorRedirectResponse for Paypal {
|
impl services::ConnectorRedirectResponse for Paypal {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
use api_models::payments::BankRedirectData;
|
use api_models::{enums, payments::BankRedirectData};
|
||||||
use common_utils::errors::CustomResult;
|
use common_utils::errors::CustomResult;
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use masking::Secret;
|
use masking::Secret;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::PrimitiveDateTime;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -67,8 +68,8 @@ pub enum PaypalPaymentIntent {
|
|||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Eq, PartialEq, Deserialize)]
|
#[derive(Default, Debug, Clone, Serialize, Eq, PartialEq, Deserialize)]
|
||||||
pub struct OrderAmount {
|
pub struct OrderAmount {
|
||||||
currency_code: storage_enums::Currency,
|
pub currency_code: storage_enums::Currency,
|
||||||
value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||||
@ -1403,7 +1404,7 @@ pub struct PaypalWebhooksBody {
|
|||||||
pub resource: PaypalResource,
|
pub resource: PaypalResource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Serialize)]
|
#[derive(Clone, Deserialize, Debug, strum::Display, Serialize)]
|
||||||
pub enum PaypalWebhookEventType {
|
pub enum PaypalWebhookEventType {
|
||||||
#[serde(rename = "PAYMENT.AUTHORIZATION.CREATED")]
|
#[serde(rename = "PAYMENT.AUTHORIZATION.CREATED")]
|
||||||
PaymentAuthorizationCreated,
|
PaymentAuthorizationCreated,
|
||||||
@ -1423,6 +1424,14 @@ pub enum PaypalWebhookEventType {
|
|||||||
CheckoutOrderCompleted,
|
CheckoutOrderCompleted,
|
||||||
#[serde(rename = "CHECKOUT.ORDER.PROCESSED")]
|
#[serde(rename = "CHECKOUT.ORDER.PROCESSED")]
|
||||||
CheckoutOrderProcessed,
|
CheckoutOrderProcessed,
|
||||||
|
#[serde(rename = "CUSTOMER.DISPUTE.CREATED")]
|
||||||
|
CustomerDisputeCreated,
|
||||||
|
#[serde(rename = "CUSTOMER.DISPUTE.RESOLVED")]
|
||||||
|
CustomerDisputeResolved,
|
||||||
|
#[serde(rename = "CUSTOMER.DISPUTE.UPDATED")]
|
||||||
|
CustomerDisputedUpdated,
|
||||||
|
#[serde(rename = "RISK.DISPUTE.CREATED")]
|
||||||
|
RiskDisputeCreated,
|
||||||
#[serde(other)]
|
#[serde(other)]
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
@ -1433,6 +1442,64 @@ pub enum PaypalResource {
|
|||||||
PaypalCardWebhooks(Box<PaypalCardWebhooks>),
|
PaypalCardWebhooks(Box<PaypalCardWebhooks>),
|
||||||
PaypalRedirectsWebhooks(Box<PaypalRedirectsWebhooks>),
|
PaypalRedirectsWebhooks(Box<PaypalRedirectsWebhooks>),
|
||||||
PaypalRefundWebhooks(Box<PaypalRefundWebhooks>),
|
PaypalRefundWebhooks(Box<PaypalRefundWebhooks>),
|
||||||
|
PaypalDisputeWebhooks(Box<PaypalDisputeWebhooks>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Serialize)]
|
||||||
|
pub struct PaypalDisputeWebhooks {
|
||||||
|
pub dispute_id: String,
|
||||||
|
pub dispute_transactions: Vec<DisputeTransaction>,
|
||||||
|
pub dispute_amount: OrderAmount,
|
||||||
|
pub dispute_outcome: DisputeOutcome,
|
||||||
|
pub dispute_life_cycle_stage: DisputeLifeCycleStage,
|
||||||
|
pub status: DisputeStatus,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub external_reason_code: Option<String>,
|
||||||
|
pub seller_response_due_date: Option<PrimitiveDateTime>,
|
||||||
|
pub update_time: Option<PrimitiveDateTime>,
|
||||||
|
pub create_time: Option<PrimitiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Serialize)]
|
||||||
|
pub struct DisputeTransaction {
|
||||||
|
pub reference_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Debug, strum::Display, Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum DisputeLifeCycleStage {
|
||||||
|
Inquiry,
|
||||||
|
Chargeback,
|
||||||
|
PreArbitration,
|
||||||
|
Arbitration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, strum::Display, Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum DisputeStatus {
|
||||||
|
Open,
|
||||||
|
WaitingForBuyerResponse,
|
||||||
|
WaitingForSellerResponse,
|
||||||
|
UnderReview,
|
||||||
|
Resolved,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Serialize)]
|
||||||
|
pub struct DisputeOutcome {
|
||||||
|
pub outcome_code: OutcomeCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum OutcomeCode {
|
||||||
|
ResolvedBuyerFavour,
|
||||||
|
ResolvedSellerFavour,
|
||||||
|
ResolvedWithPayout,
|
||||||
|
CanceledByBuyer,
|
||||||
|
ACCEPTED,
|
||||||
|
DENIED,
|
||||||
|
NONE,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Serialize)]
|
#[derive(Deserialize, Debug, Serialize)]
|
||||||
@ -1482,8 +1549,8 @@ pub struct PaypalWebooksEventType {
|
|||||||
pub event_type: PaypalWebhookEventType,
|
pub event_type: PaypalWebhookEventType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PaypalWebhookEventType> for api::IncomingWebhookEvent {
|
impl ForeignFrom<(PaypalWebhookEventType, Option<OutcomeCode>)> for api::IncomingWebhookEvent {
|
||||||
fn from(event: PaypalWebhookEventType) -> Self {
|
fn foreign_from((event, outcome): (PaypalWebhookEventType, Option<OutcomeCode>)) -> Self {
|
||||||
match event {
|
match event {
|
||||||
PaypalWebhookEventType::PaymentCaptureCompleted
|
PaypalWebhookEventType::PaymentCaptureCompleted
|
||||||
| PaypalWebhookEventType::CheckoutOrderCompleted => Self::PaymentIntentSuccess,
|
| PaypalWebhookEventType::CheckoutOrderCompleted => Self::PaymentIntentSuccess,
|
||||||
@ -1491,14 +1558,49 @@ impl From<PaypalWebhookEventType> for api::IncomingWebhookEvent {
|
|||||||
| PaypalWebhookEventType::CheckoutOrderProcessed => Self::PaymentIntentProcessing,
|
| PaypalWebhookEventType::CheckoutOrderProcessed => Self::PaymentIntentProcessing,
|
||||||
PaypalWebhookEventType::PaymentCaptureDeclined => Self::PaymentIntentFailure,
|
PaypalWebhookEventType::PaymentCaptureDeclined => Self::PaymentIntentFailure,
|
||||||
PaypalWebhookEventType::PaymentCaptureRefunded => Self::RefundSuccess,
|
PaypalWebhookEventType::PaymentCaptureRefunded => Self::RefundSuccess,
|
||||||
|
PaypalWebhookEventType::CustomerDisputeCreated => Self::DisputeOpened,
|
||||||
|
PaypalWebhookEventType::RiskDisputeCreated => Self::DisputeAccepted,
|
||||||
|
PaypalWebhookEventType::CustomerDisputeResolved => {
|
||||||
|
if let Some(outcome_code) = outcome {
|
||||||
|
Self::from(outcome_code)
|
||||||
|
} else {
|
||||||
|
Self::EventNotSupported
|
||||||
|
}
|
||||||
|
}
|
||||||
PaypalWebhookEventType::PaymentAuthorizationCreated
|
PaypalWebhookEventType::PaymentAuthorizationCreated
|
||||||
| PaypalWebhookEventType::PaymentAuthorizationVoided
|
| PaypalWebhookEventType::PaymentAuthorizationVoided
|
||||||
| PaypalWebhookEventType::CheckoutOrderApproved
|
| PaypalWebhookEventType::CheckoutOrderApproved
|
||||||
|
| PaypalWebhookEventType::CustomerDisputedUpdated
|
||||||
| PaypalWebhookEventType::Unknown => Self::EventNotSupported,
|
| PaypalWebhookEventType::Unknown => Self::EventNotSupported,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<OutcomeCode> for api::IncomingWebhookEvent {
|
||||||
|
fn from(outcome_code: OutcomeCode) -> Self {
|
||||||
|
match outcome_code {
|
||||||
|
OutcomeCode::ResolvedBuyerFavour => Self::DisputeLost,
|
||||||
|
OutcomeCode::ResolvedSellerFavour => Self::DisputeWon,
|
||||||
|
OutcomeCode::CanceledByBuyer => Self::DisputeCancelled,
|
||||||
|
OutcomeCode::ACCEPTED => Self::DisputeAccepted,
|
||||||
|
OutcomeCode::DENIED => Self::DisputeCancelled,
|
||||||
|
OutcomeCode::NONE => Self::DisputeCancelled,
|
||||||
|
OutcomeCode::ResolvedWithPayout => Self::EventNotSupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DisputeLifeCycleStage> for enums::DisputeStage {
|
||||||
|
fn from(dispute_life_cycle_stage: DisputeLifeCycleStage) -> Self {
|
||||||
|
match dispute_life_cycle_stage {
|
||||||
|
DisputeLifeCycleStage::Inquiry => Self::PreDispute,
|
||||||
|
DisputeLifeCycleStage::Chargeback => Self::Dispute,
|
||||||
|
DisputeLifeCycleStage::PreArbitration => Self::PreArbitration,
|
||||||
|
DisputeLifeCycleStage::Arbitration => Self::PreArbitration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
pub struct PaypalSourceVerificationRequest {
|
pub struct PaypalSourceVerificationRequest {
|
||||||
pub transmission_id: String,
|
pub transmission_id: String,
|
||||||
@ -1617,7 +1719,11 @@ impl TryFrom<PaypalWebhookEventType> for PaypalPaymentStatus {
|
|||||||
| PaypalWebhookEventType::CheckoutOrderProcessed => Ok(Self::Pending),
|
| PaypalWebhookEventType::CheckoutOrderProcessed => Ok(Self::Pending),
|
||||||
PaypalWebhookEventType::PaymentAuthorizationCreated => Ok(Self::Created),
|
PaypalWebhookEventType::PaymentAuthorizationCreated => Ok(Self::Created),
|
||||||
PaypalWebhookEventType::PaymentCaptureRefunded => Ok(Self::Refunded),
|
PaypalWebhookEventType::PaymentCaptureRefunded => Ok(Self::Refunded),
|
||||||
PaypalWebhookEventType::Unknown => {
|
PaypalWebhookEventType::CustomerDisputeCreated
|
||||||
|
| PaypalWebhookEventType::CustomerDisputeResolved
|
||||||
|
| PaypalWebhookEventType::CustomerDisputedUpdated
|
||||||
|
| PaypalWebhookEventType::RiskDisputeCreated
|
||||||
|
| PaypalWebhookEventType::Unknown => {
|
||||||
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
|
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1637,6 +1743,10 @@ impl TryFrom<PaypalWebhookEventType> for RefundStatus {
|
|||||||
| PaypalWebhookEventType::CheckoutOrderApproved
|
| PaypalWebhookEventType::CheckoutOrderApproved
|
||||||
| PaypalWebhookEventType::CheckoutOrderCompleted
|
| PaypalWebhookEventType::CheckoutOrderCompleted
|
||||||
| PaypalWebhookEventType::CheckoutOrderProcessed
|
| PaypalWebhookEventType::CheckoutOrderProcessed
|
||||||
|
| PaypalWebhookEventType::CustomerDisputeCreated
|
||||||
|
| PaypalWebhookEventType::CustomerDisputeResolved
|
||||||
|
| PaypalWebhookEventType::CustomerDisputedUpdated
|
||||||
|
| PaypalWebhookEventType::RiskDisputeCreated
|
||||||
| PaypalWebhookEventType::Unknown => {
|
| PaypalWebhookEventType::Unknown => {
|
||||||
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
|
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
|
||||||
}
|
}
|
||||||
@ -1657,6 +1767,10 @@ impl TryFrom<PaypalWebhookEventType> for PaypalOrderStatus {
|
|||||||
PaypalWebhookEventType::CheckoutOrderApproved
|
PaypalWebhookEventType::CheckoutOrderApproved
|
||||||
| PaypalWebhookEventType::PaymentCaptureDeclined
|
| PaypalWebhookEventType::PaymentCaptureDeclined
|
||||||
| PaypalWebhookEventType::PaymentCaptureRefunded
|
| PaypalWebhookEventType::PaymentCaptureRefunded
|
||||||
|
| PaypalWebhookEventType::CustomerDisputeCreated
|
||||||
|
| PaypalWebhookEventType::CustomerDisputeResolved
|
||||||
|
| PaypalWebhookEventType::CustomerDisputedUpdated
|
||||||
|
| PaypalWebhookEventType::RiskDisputeCreated
|
||||||
| PaypalWebhookEventType::Unknown => {
|
| PaypalWebhookEventType::Unknown => {
|
||||||
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
|
Err(errors::ConnectorError::WebhookEventTypeNotFound.into())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user