Bug: feat(connector) : [Authorizedotnet] Webhooks support for Authorizedotnet



payment and refund webhook support for authorizedotnet
diff --git a/crates/router/src/connector/authorizedotnet.rs b/crates/router/src/connector/authorizedotnet.rs
index 49869f0137f..83e96138833 100644
--- a/crates/router/src/connector/authorizedotnet.rs
+++ b/crates/router/src/connector/authorizedotnet.rs
@@ -3,6 +3,7 @@ mod transformers;
 
 use std::fmt::Debug;
 
+use common_utils::{crypto, ext_traits::ByteSliceExt};
 use error_stack::{IntoReport, ResultExt};
 use transformers as authorizedotnet;
 
@@ -10,6 +11,7 @@ use crate::{
     configs::settings,
     consts,
     core::errors::{self, CustomResult},
+    db::StorageInterface,
     headers,
     services::{self, ConnectorIntegration},
     types::{
@@ -611,25 +613,109 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
 
 #[async_trait::async_trait]
 impl api::IncomingWebhook for Authorizedotnet {
-    fn get_webhook_object_reference_id(
+    fn get_webhook_source_verification_algorithm(
         &self,
         _request: &api::IncomingWebhookRequestDetails<'_>,
+    ) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
+        Ok(Box::new(crypto::HmacSha512))
+    }
+
+    fn get_webhook_source_verification_signature(
+        &self,
+        request: &api::IncomingWebhookRequestDetails<'_>,
+    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
+        let security_header = request
+            .headers
+            .get("X-ANET-Signature")
+            .map(|header_value| {
+                header_value
+                    .to_str()
+                    .map(String::from)
+                    .map_err(|_| errors::ConnectorError::WebhookSignatureNotFound)
+                    .into_report()
+            })
+            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)
+            .into_report()??
+            .to_lowercase();
+        let (_, sig_value) = security_header
+            .split_once('=')
+            .ok_or(errors::ConnectorError::WebhookSourceVerificationFailed)
+            .into_report()?;
+        hex::decode(sig_value)
+            .into_report()
+            .change_context(errors::ConnectorError::WebhookSignatureNotFound)
+    }
+
+    fn get_webhook_source_verification_message(
+        &self,
+        request: &api::IncomingWebhookRequestDetails<'_>,
+        _merchant_id: &str,
+        _secret: &[u8],
+    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
+        Ok(request.body.to_vec())
+    }
+
+    fn get_webhook_object_reference_id(
+        &self,
+        request: &api::IncomingWebhookRequestDetails<'_>,
     ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
-        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+        let details: authorizedotnet::AuthorizedotnetWebhookObjectId = request
+            .body
+            .parse_struct("AuthorizedotnetWebhookObjectId")
+            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
+        match details.event_type {
+            authorizedotnet::AuthorizedotnetWebhookEvent::RefundCreated => {
+                Ok(api_models::webhooks::ObjectReferenceId::RefundId(
+                    api_models::webhooks::RefundIdType::ConnectorRefundId(
+                        authorizedotnet::get_trans_id(details)?,
+                    ),
+                ))
+            }
+            _ => Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
+                api_models::payments::PaymentIdType::ConnectorTransactionId(
+                    authorizedotnet::get_trans_id(details)?,
+                ),
+            )),
+        }
+    }
+
+    async fn get_webhook_source_verification_merchant_secret(
+        &self,
+        db: &dyn StorageInterface,
+        merchant_id: &str,
+    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
+        let key = format!("whsec_verification_{}_{}", self.id(), merchant_id);
+        let secret = match db.find_config_by_key(&key).await {
+            Ok(config) => Some(config),
+            Err(e) => {
+                crate::logger::warn!("Unable to fetch merchant webhook secret from DB: {:#?}", e);
+                None
+            }
+        };
+        Ok(secret
+            .map(|conf| conf.config.into_bytes())
+            .unwrap_or_default())
     }
 
     fn get_webhook_event_type(
         &self,
-        _request: &api::IncomingWebhookRequestDetails<'_>,
+        request: &api::IncomingWebhookRequestDetails<'_>,
     ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
-        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+        let details: authorizedotnet::AuthorizedotnetWebhookEventType = request
+            .body
+            .parse_struct("AuthorizedotnetWebhookEventType")
+            .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
+        Ok(api::IncomingWebhookEvent::from(details.event_type))
     }
 
     fn get_webhook_resource_object(
         &self,
-        _request: &api::IncomingWebhookRequestDetails<'_>,
+        request: &api::IncomingWebhookRequestDetails<'_>,
     ) -> CustomResult<serde_json::Value, errors::ConnectorError> {
-        Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
+        let payload = serde_json::to_value(request.body)
+            .into_report()
+            .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
+        Ok(payload)
     }
 }
 
diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs
index 77de1a870a7..0ff92ca196a 100644
--- a/crates/router/src/connector/authorizedotnet/transformers.rs
+++ b/crates/router/src/connector/authorizedotnet/transformers.rs
@@ -881,3 +881,60 @@ fn get_err_response(status_code: u16, message: ResponseMessages) -> types::Error
         status_code,
     }
 }
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AuthorizedotnetWebhookObjectId {
+    pub webhook_id: String,
+    pub event_type: AuthorizedotnetWebhookEvent,
+    pub payload: AuthorizedotnetWebhookPayload,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct AuthorizedotnetWebhookPayload {
+    pub id: Option<String>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AuthorizedotnetWebhookEventType {
+    pub event_type: AuthorizedotnetWebhookEvent,
+}
+
+#[derive(Debug, Deserialize)]
+pub enum AuthorizedotnetWebhookEvent {
+    #[serde(rename = "net.authorize.payment.authorization.created")]
+    AuthorizationCreated,
+    #[serde(rename = "net.authorize.payment.priorAuthCapture.created")]
+    PriorAuthCapture,
+    #[serde(rename = "net.authorize.payment.authcapture.created")]
+    AuthCapCreated,
+    #[serde(rename = "net.authorize.payment.capture.created")]
+    CaptureCreated,
+    #[serde(rename = "net.authorize.payment.void.created")]
+    VoidCreated,
+    #[serde(rename = "net.authorize.payment.refund.created")]
+    RefundCreated,
+}
+
+impl From<AuthorizedotnetWebhookEvent> for api::IncomingWebhookEvent {
+    fn from(event_type: AuthorizedotnetWebhookEvent) -> Self {
+        match event_type {
+            AuthorizedotnetWebhookEvent::AuthorizationCreated
+            | AuthorizedotnetWebhookEvent::PriorAuthCapture
+            | AuthorizedotnetWebhookEvent::AuthCapCreated
+            | AuthorizedotnetWebhookEvent::CaptureCreated
+            | AuthorizedotnetWebhookEvent::VoidCreated => Self::PaymentIntentSuccess,
+            AuthorizedotnetWebhookEvent::RefundCreated => Self::RefundSuccess,
+        }
+    }
+}
+
+pub fn get_trans_id(
+    details: AuthorizedotnetWebhookObjectId,
+) -> Result<String, errors::ConnectorError> {
+    details
+        .payload
+        .id
+        .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)
+}

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Payments and refund Webhook Flow for Authorizedotnet

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!-- 
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless its an obvious bug or documentation fix
that will have little conversation).
-->
Payments and refund webhook flow Authorizedotnet

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Outgoing payment webhook 
<img width="1169" alt="Screenshot 2023-05-16 at 7 55 40 PM" src="https://github.com/juspay/hyperswitch/assets/121822803/0a35ba1a-9c4a-4cd3-9863-4ec22fd5029f">

Payment retrieve for same payment
<img width="1280" alt="Screenshot 2023-05-16 at 7 56 17 PM" src="https://github.com/juspay/hyperswitch/assets/121822803/697ad716-1224-4f63-a46a-ce4f6117331f">



## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
