Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(router): add mandates incoming webhooks flow #2464

Merged
merged 3 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/api_models/src/mandates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct MandateRevokedResponse {
pub status: api_enums::MandateStatus,
}

#[derive(Default, Debug, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Deserialize, Serialize, ToSchema, Clone)]
pub struct MandateResponse {
/// The identifier for mandate
pub mandate_id: String,
Expand All @@ -37,7 +37,7 @@ pub struct MandateResponse {
pub customer_acceptance: Option<payments::CustomerAcceptance>,
}

#[derive(Default, Debug, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Deserialize, Serialize, ToSchema, Clone)]
pub struct MandateCardDetails {
/// The last 4 digits of card
pub last4_digits: Option<String>,
Expand Down
23 changes: 21 additions & 2 deletions crates/api_models/src/webhooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;

use crate::{disputes, enums as api_enums, payments, refunds};
use crate::{disputes, enums as api_enums, mandates, payments, refunds};

#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy)]
#[serde(rename_all = "snake_case")]
Expand All @@ -27,6 +27,8 @@ pub enum IncomingWebhookEvent {
DisputeWon,
// dispute has been unsuccessfully challenged
DisputeLost,
MandateActive,
MandateRevoked,
EndpointVerification,
}

Expand All @@ -37,6 +39,7 @@ pub enum WebhookFlow {
Subscription,
ReturnResponse,
BankTransfer,
Mandate,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
Expand All @@ -56,6 +59,10 @@ pub enum WebhookResponseTracker {
payment_id: String,
status: common_enums::DisputeStatus,
},
Mandate {
mandate_id: String,
status: common_enums::MandateStatus,
},
NoEffect,
}

Expand All @@ -65,7 +72,7 @@ impl WebhookResponseTracker {
Self::Payment { payment_id, .. }
| Self::Refund { payment_id, .. }
| Self::Dispute { payment_id, .. } => Some(payment_id.to_string()),
Self::NoEffect => None,
Self::NoEffect | Self::Mandate { .. } => None,
}
}
}
Expand All @@ -82,6 +89,9 @@ impl From<IncomingWebhookEvent> for WebhookFlow {
IncomingWebhookEvent::RefundSuccess | IncomingWebhookEvent::RefundFailure => {
Self::Refund
}
IncomingWebhookEvent::MandateActive | IncomingWebhookEvent::MandateRevoked => {
Self::Mandate
}
IncomingWebhookEvent::DisputeOpened
| IncomingWebhookEvent::DisputeAccepted
| IncomingWebhookEvent::DisputeExpired
Expand All @@ -104,10 +114,17 @@ pub enum RefundIdType {
ConnectorRefundId(String),
}

#[derive(Clone)]
pub enum MandateIdType {
MandateId(String),
ConnectorMandateId(String),
}

#[derive(Clone)]
pub enum ObjectReferenceId {
PaymentId(payments::PaymentIdType),
RefundId(RefundIdType),
MandateId(MandateIdType),
}

pub struct IncomingWebhookDetails {
Expand Down Expand Up @@ -144,6 +161,8 @@ pub enum OutgoingWebhookContent {
RefundDetails(refunds::RefundResponse),
#[schema(value_type = DisputeResponse)]
DisputeDetails(Box<disputes::DisputeResponse>),
#[schema(value_type = MandateResponse)]
MandateDetails(Box<mandates::MandateResponse>),
}

#[derive(Debug, Clone, Serialize)]
Expand Down
2 changes: 2 additions & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,8 @@ pub enum EventType {
DisputeChallenged,
DisputeWon,
DisputeLost,
MandateActive,
MandateRevoked,
}

#[derive(
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub enum EventClass {
Payments,
Refunds,
Disputes,
Mandates,
}

#[derive(
Expand All @@ -59,6 +60,7 @@ pub enum EventObjectType {
PaymentDetails,
RefundDetails,
DisputeDetails,
MandateDetails,
}

#[derive(
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/mandate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct Mandate {
pub end_date: Option<PrimitiveDateTime>,
pub metadata: Option<pii::SecretSerdeValue>,
pub connector_mandate_ids: Option<pii::SecretSerdeValue>,
pub original_payment_id: Option<String>,
}

#[derive(
Expand Down Expand Up @@ -58,6 +59,7 @@ pub struct MandateNew {
pub end_date: Option<PrimitiveDateTime>,
pub metadata: Option<pii::SecretSerdeValue>,
pub connector_mandate_ids: Option<pii::SecretSerdeValue>,
pub original_payment_id: Option<String>,
}

#[derive(Debug)]
Expand Down
14 changes: 14 additions & 0 deletions crates/diesel_models/src/query/mandate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ impl Mandate {
.await
}

pub async fn find_by_merchant_id_connector_mandate_id(
conn: &PgPooledConn,
merchant_id: &str,
connector_mandate_id: &str,
) -> StorageResult<Self> {
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
conn,
dsl::merchant_id
.eq(merchant_id.to_owned())
.and(dsl::connector_mandate_id.eq(connector_mandate_id.to_owned())),
)
.await
}

pub async fn find_by_merchant_id_customer_id(
conn: &PgPooledConn,
merchant_id: &str,
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ diesel::table! {
end_date -> Nullable<Timestamp>,
metadata -> Nullable<Jsonb>,
connector_mandate_ids -> Nullable<Jsonb>,
#[max_length = 64]
original_payment_id -> Nullable<Varchar>,
}
}

Expand Down
45 changes: 44 additions & 1 deletion crates/router/src/compatibility/stripe/webhooks.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use api_models::{
enums::DisputeStatus,
enums::{DisputeStatus, MandateStatus},
webhooks::{self as api},
};
use common_utils::{crypto::SignMessage, date_time, ext_traits};
Expand Down Expand Up @@ -73,6 +73,7 @@ pub enum StripeWebhookObject {
PaymentIntent(StripePaymentIntentResponse),
Refund(StripeRefundResponse),
Dispute(StripeDisputeResponse),
Mandate(StripeMandateResponse),
}

#[derive(Serialize, Debug)]
Expand All @@ -85,6 +86,22 @@ pub struct StripeDisputeResponse {
pub status: StripeDisputeStatus,
}

#[derive(Serialize, Debug)]
pub struct StripeMandateResponse {
pub mandate_id: String,
pub status: StripeMandateStatus,
pub payment_method_id: String,
pub payment_method: String,
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripeMandateStatus {
Active,
Inactive,
Pending,
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripeDisputeStatus {
Expand All @@ -111,6 +128,27 @@ impl From<api_models::disputes::DisputeResponse> for StripeDisputeResponse {
}
}

impl From<api_models::mandates::MandateResponse> for StripeMandateResponse {
fn from(res: api_models::mandates::MandateResponse) -> Self {
Self {
mandate_id: res.mandate_id,
payment_method: res.payment_method,
payment_method_id: res.payment_method_id,
status: StripeMandateStatus::from(res.status),
}
}
}

impl From<MandateStatus> for StripeMandateStatus {
fn from(status: MandateStatus) -> Self {
match status {
MandateStatus::Active => Self::Active,
MandateStatus::Inactive | MandateStatus::Revoked => Self::Inactive,
MandateStatus::Pending => Self::Pending,
}
}
}

impl From<DisputeStatus> for StripeDisputeStatus {
fn from(status: DisputeStatus) -> Self {
match status {
Expand Down Expand Up @@ -142,6 +180,8 @@ fn get_stripe_event_type(event_type: api_models::enums::EventType) -> &'static s
api_models::enums::EventType::DisputeChallenged => "dispute.challenged",
api_models::enums::EventType::DisputeWon => "dispute.won",
api_models::enums::EventType::DisputeLost => "dispute.lost",
api_models::enums::EventType::MandateActive => "mandate.active",
api_models::enums::EventType::MandateRevoked => "mandate.revoked",
}
}

Expand Down Expand Up @@ -179,6 +219,9 @@ impl From<api::OutgoingWebhookContent> for StripeWebhookObject {
api::OutgoingWebhookContent::DisputeDetails(dispute) => {
Self::Dispute((*dispute).into())
}
api::OutgoingWebhookContent::MandateDetails(mandate) => {
Self::Mandate((*mandate).into())
}
}
}
}
1 change: 1 addition & 0 deletions crates/router/src/core/mandate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ where

if let Some(new_mandate_data) = helpers::generate_mandate(
resp.merchant_id.clone(),
resp.payment_id.clone(),
resp.connector.clone(),
resp.request.get_setup_mandate_details().map(Clone::clone),
maybe_customer,
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/core/payments/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2115,6 +2115,7 @@ pub fn check_if_operation_confirm<Op: std::fmt::Debug>(operations: Op) -> bool {
#[allow(clippy::too_many_arguments)]
pub fn generate_mandate(
merchant_id: String,
payment_id: String,
connector: String,
setup_mandate_details: Option<MandateData>,
customer: &Option<domain::Customer>,
Expand All @@ -2137,6 +2138,7 @@ pub fn generate_mandate(
.set_mandate_id(mandate_id)
.set_customer_id(cus.customer_id.clone())
.set_merchant_id(merchant_id)
.set_original_payment_id(Some(payment_id))
.set_payment_method_id(payment_method_id)
.set_connector(connector)
.set_mandate_status(storage_enums::MandateStatus::Active)
Expand Down
Loading