From 0416302b0fd7fbc1838d6af43ddf83de8d8fd10a Mon Sep 17 00:00:00 2001 From: hrithikeshvm Date: Wed, 27 Dec 2023 16:45:16 +0530 Subject: [PATCH 1/7] feat(core): add new payments webhook events --- crates/api_models/src/webhooks.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index bc8e75f6d47..7b3564732bf 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -8,11 +8,18 @@ use crate::{disputes, enums as api_enums, mandates, payments, refunds}; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy)] #[serde(rename_all = "snake_case")] pub enum IncomingWebhookEvent { + /// Authorization + Capture success PaymentIntentFailure, + /// Authorization + Capture failure PaymentIntentSuccess, PaymentIntentProcessing, PaymentIntentPartiallyFunded, PaymentIntentCancelled, + PaymentIntentCancelFailure, + PaymentIntentAuthorizationSuccess, + PaymentIntentAuthorizationFailure, + PaymentIntentCaptureSuccess, + PaymentIntentCaptureFailure, PaymentActionRequired, EventNotSupported, SourceChargeable, @@ -86,7 +93,12 @@ impl From for WebhookFlow { | IncomingWebhookEvent::PaymentIntentProcessing | IncomingWebhookEvent::PaymentActionRequired | IncomingWebhookEvent::PaymentIntentPartiallyFunded - | IncomingWebhookEvent::PaymentIntentCancelled => Self::Payment, + | IncomingWebhookEvent::PaymentIntentCancelled + | IncomingWebhookEvent::PaymentIntentCancelFailure + | IncomingWebhookEvent::PaymentIntentAuthorizationSuccess + | IncomingWebhookEvent::PaymentIntentAuthorizationFailure + | IncomingWebhookEvent::PaymentIntentCaptureSuccess + | IncomingWebhookEvent::PaymentIntentCaptureFailure => Self::Payment, IncomingWebhookEvent::EventNotSupported => Self::ReturnResponse, IncomingWebhookEvent::RefundSuccess | IncomingWebhookEvent::RefundFailure => { Self::Refund From 936e18d655b0496e4f616346f54ffca338d87a11 Mon Sep 17 00:00:00 2001 From: hrithikeshvm Date: Thu, 28 Dec 2023 12:02:58 +0530 Subject: [PATCH 2/7] add new outgoing webhook event type --- crates/common_enums/src/enums.rs | 4 ++++ crates/router/src/compatibility/stripe/webhooks.rs | 7 +++++++ crates/router/src/types/transformers.rs | 12 ++++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 0c4b9720cab..3af1c0e826b 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -921,10 +921,14 @@ impl Currency { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum EventType { + /// Authorize + Capture success PaymentSucceeded, + /// Authorize + Capture failed PaymentFailed, PaymentProcessing, PaymentCancelled, + PaymentAuthorized, + PaymentCaptured, ActionRequired, RefundSucceeded, RefundFailed, diff --git a/crates/router/src/compatibility/stripe/webhooks.rs b/crates/router/src/compatibility/stripe/webhooks.rs index c44e265a965..807278e0aff 100644 --- a/crates/router/src/compatibility/stripe/webhooks.rs +++ b/crates/router/src/compatibility/stripe/webhooks.rs @@ -183,6 +183,13 @@ fn get_stripe_event_type(event_type: api_models::enums::EventType) -> &'static s api_models::enums::EventType::DisputeLost => "dispute.lost", api_models::enums::EventType::MandateActive => "mandate.active", api_models::enums::EventType::MandateRevoked => "mandate.revoked", + + // as per this doc https://stripe.com/docs/api/events/types#event_types-payment_intent.amount_capturable_updated + api_models::enums::EventType::PaymentAuthorized => { + "payment_intent.amount_capturable_updated" + } + // stripe treats partially captured payments as succeeded. + api_models::enums::EventType::PaymentCaptured => "payment_intent.succeeded", } } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 5bd03fe833a..abc05cc9818 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -352,11 +352,15 @@ impl ForeignFrom for Option { Some(storage_enums::EventType::ActionRequired) } api_enums::IntentStatus::Cancelled => Some(storage_enums::EventType::PaymentCancelled), + api_enums::IntentStatus::PartiallyCaptured + | api_enums::IntentStatus::PartiallyCapturedAndCapturable => { + Some(storage_enums::EventType::PaymentCaptured) + } + api_enums::IntentStatus::RequiresCapture => { + Some(storage_enums::EventType::PaymentAuthorized) + } api_enums::IntentStatus::RequiresPaymentMethod - | api_enums::IntentStatus::RequiresConfirmation - | api_enums::IntentStatus::RequiresCapture - | api_enums::IntentStatus::PartiallyCaptured - | api_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + | api_enums::IntentStatus::RequiresConfirmation => None, } } } From a84834dee6c0f56438966b0dafe92676cb5a1f63 Mon Sep 17 00:00:00 2001 From: hrithikeshvm Date: Thu, 28 Dec 2023 12:37:48 +0530 Subject: [PATCH 3/7] add new postgres enum types to EventType --- .../2023-12-28-063619_add_enum_types_to_EventType/down.sql | 2 ++ .../2023-12-28-063619_add_enum_types_to_EventType/up.sql | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 migrations/2023-12-28-063619_add_enum_types_to_EventType/down.sql create mode 100644 migrations/2023-12-28-063619_add_enum_types_to_EventType/up.sql diff --git a/migrations/2023-12-28-063619_add_enum_types_to_EventType/down.sql b/migrations/2023-12-28-063619_add_enum_types_to_EventType/down.sql new file mode 100644 index 00000000000..c7c9cbeb401 --- /dev/null +++ b/migrations/2023-12-28-063619_add_enum_types_to_EventType/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/migrations/2023-12-28-063619_add_enum_types_to_EventType/up.sql b/migrations/2023-12-28-063619_add_enum_types_to_EventType/up.sql new file mode 100644 index 00000000000..74b87199c2f --- /dev/null +++ b/migrations/2023-12-28-063619_add_enum_types_to_EventType/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TYPE "EventType" ADD VALUE IF NOT EXISTS 'payment_authorized'; +ALTER TYPE "EventType" ADD VALUE IF NOT EXISTS 'payment_captured'; From 5d5933db23288cba342d5aebae9c589d2f96ea8b Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 07:16:23 +0000 Subject: [PATCH 4/7] docs(openapi): re-generate OpenAPI specification --- openapi/openapi_spec.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 3ffb98e56b9..fa237a30a9d 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -5532,6 +5532,8 @@ "payment_failed", "payment_processing", "payment_cancelled", + "payment_authorized", + "payment_captured", "action_required", "refund_succeeded", "refund_failed", From 7457f74ca18b633c5e6656c8a981b92396400643 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Tue, 9 Jan 2024 20:22:00 +0530 Subject: [PATCH 5/7] feat(connector): expand webhook event coverage stripe nmi (#3298) Co-authored-by: hrithikeshvm --- crates/router/src/connector/nmi.rs | 15 +++++++++++++++ crates/router/src/connector/nmi/transformers.rs | 10 +++++----- crates/router/src/connector/stripe.rs | 4 +++- .../router/src/connector/stripe/transformers.rs | 2 +- .../core/payments/operations/payment_response.rs | 11 +---------- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/crates/router/src/connector/nmi.rs b/crates/router/src/connector/nmi.rs index 0c01c752039..312c174756a 100644 --- a/crates/router/src/connector/nmi.rs +++ b/crates/router/src/connector/nmi.rs @@ -871,6 +871,21 @@ impl api::IncomingWebhook for Nmi { reference_body.event_body.order_id, ), ), + nmi::NmiActionType::Auth => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + reference_body.event_body.order_id, + ), + ), + nmi::NmiActionType::Capture => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + reference_body.event_body.order_id, + ), + ), + nmi::NmiActionType::Void => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + reference_body.event_body.order_id, + ), + ), nmi::NmiActionType::Refund => api_models::webhooks::ObjectReferenceId::RefundId( api_models::webhooks::RefundIdType::RefundId(reference_body.event_body.order_id), ), diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 931dac5a966..254eccf3381 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -1167,15 +1167,15 @@ impl ForeignFrom for webhooks::IncomingWebhookEvent { NmiWebhookEventType::RefundSuccess => Self::RefundSuccess, NmiWebhookEventType::RefundFailure => Self::RefundFailure, NmiWebhookEventType::VoidSuccess => Self::PaymentIntentCancelled, + NmiWebhookEventType::AuthSuccess => Self::PaymentIntentAuthorizationSuccess, + NmiWebhookEventType::CaptureSuccess => Self::PaymentIntentCaptureSuccess, + NmiWebhookEventType::AuthFailure => Self::PaymentIntentAuthorizationFailure, + NmiWebhookEventType::CaptureFailure => Self::PaymentIntentCaptureFailure, + NmiWebhookEventType::VoidFailure => Self::PaymentIntentCancelFailure, NmiWebhookEventType::SaleUnknown | NmiWebhookEventType::RefundUnknown - | NmiWebhookEventType::AuthSuccess - | NmiWebhookEventType::AuthFailure | NmiWebhookEventType::AuthUnknown - | NmiWebhookEventType::VoidFailure | NmiWebhookEventType::VoidUnknown - | NmiWebhookEventType::CaptureSuccess - | NmiWebhookEventType::CaptureFailure | NmiWebhookEventType::CaptureUnknown => Self::EventNotSupported, } } diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 65fd652629f..a574d0e7daf 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -1956,6 +1956,9 @@ impl api::IncomingWebhook for Stripe { stripe::WebhookEventType::PaymentIntentCanceled => { api::IncomingWebhookEvent::PaymentIntentCancelled } + stripe::WebhookEventType::PaymentIntentAmountCapturableUpdated => { + api::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess + } stripe::WebhookEventType::ChargeSucceeded => { if let Some(stripe::WebhookPaymentMethodDetails { payment_method: @@ -2012,7 +2015,6 @@ impl api::IncomingWebhook for Stripe { | stripe::WebhookEventType::ChargeRefunded | stripe::WebhookEventType::PaymentIntentCreated | stripe::WebhookEventType::PaymentIntentProcessing - | stripe::WebhookEventType::PaymentIntentAmountCapturableUpdated | stripe::WebhookEventType::SourceTransactionCreated => { api::IncomingWebhookEvent::EventNotSupported } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 3a28f777907..57dcd0cd9dd 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -3268,7 +3268,7 @@ pub enum WebhookEventType { PaymentIntentProcessing, #[serde(rename = "payment_intent.requires_action")] PaymentIntentRequiresAction, - #[serde(rename = "amount_capturable_updated")] + #[serde(rename = "payment_intent.amount_capturable_updated")] PaymentIntentAmountCapturableUpdated, #[serde(rename = "source.chargeable")] SourceChargeable, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index adecf1b78eb..10d985b0a04 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -24,7 +24,7 @@ use crate::{ services::RedirectForm, types::{ self, api, - storage::{self, enums, payment_attempt::AttemptStatusExt}, + storage::{self, enums}, transformers::{ForeignFrom, ForeignTryFrom}, CaptureSyncResponse, }, @@ -632,15 +632,6 @@ async fn payment_response_update_tracker( unified_code: error_status.clone(), unified_message: error_status, connector_response_reference_id, - amount_capturable: if router_data.status.is_terminal_status() - || router_data - .status - .maps_to_intent_status(enums::IntentStatus::Processing) - { - Some(0) - } else { - None - }, updated_by: storage_scheme.to_string(), authentication_data, encoded_data, From f71bcc5a883fcd7c15477c64397fc3f7121b27a5 Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Wed, 10 Jan 2024 00:06:48 +0530 Subject: [PATCH 6/7] address compilation failure --- .../src/core/payments/operations/payment_response.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 10d985b0a04..adecf1b78eb 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -24,7 +24,7 @@ use crate::{ services::RedirectForm, types::{ self, api, - storage::{self, enums}, + storage::{self, enums, payment_attempt::AttemptStatusExt}, transformers::{ForeignFrom, ForeignTryFrom}, CaptureSyncResponse, }, @@ -632,6 +632,15 @@ async fn payment_response_update_tracker( unified_code: error_status.clone(), unified_message: error_status, connector_response_reference_id, + amount_capturable: if router_data.status.is_terminal_status() + || router_data + .status + .maps_to_intent_status(enums::IntentStatus::Processing) + { + Some(0) + } else { + None + }, updated_by: storage_scheme.to_string(), authentication_data, encoded_data, From 6b02d3854f1f7258a05eff4aa80225d4db6aaf07 Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Wed, 10 Jan 2024 14:11:47 +0530 Subject: [PATCH 7/7] address compilation failure --- crates/router/src/connector/nmi/transformers.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 4fc56c2b2d2..fcf35bfbe37 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -1166,12 +1166,8 @@ pub enum NmiWebhookEventType { impl ForeignFrom for webhooks::IncomingWebhookEvent { fn foreign_from(status: NmiWebhookEventType) -> Self { match status { - NmiWebhookEventType::SaleSuccess | NmiWebhookEventType::CaptureSuccess => { - Self::PaymentIntentSuccess - } - NmiWebhookEventType::SaleFailure | NmiWebhookEventType::CaptureFailure => { - Self::PaymentIntentFailure - } + NmiWebhookEventType::SaleSuccess => Self::PaymentIntentSuccess, + NmiWebhookEventType::SaleFailure => Self::PaymentIntentFailure, NmiWebhookEventType::RefundSuccess => Self::RefundSuccess, NmiWebhookEventType::RefundFailure => Self::RefundFailure, NmiWebhookEventType::VoidSuccess => Self::PaymentIntentCancelled,