Skip to content

Commit

Permalink
Inespay webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
ImSagnik007 committed Jan 8, 2025
1 parent 183c79a commit 9b55b86
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 57 deletions.
135 changes: 119 additions & 16 deletions crates/hyperswitch_connectors/src/connectors/inespay.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
pub mod transformers;

use common_utils::{
crypto,
errors::CustomResult,
ext_traits::BytesExt,
ext_traits::{ByteSliceExt, BytesExt},
request::{Method, Request, RequestBuilder, RequestContent},
types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector},
};
use error_stack::{report, ResultExt};
use base64::Engine;
use common_utils::consts::BASE64_ENGINE;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
router_flow_types::{
Expand Down Expand Up @@ -36,8 +39,9 @@ use hyperswitch_interfaces::{
types::{self, Response},
webhooks,
};
use masking::{ExposeInterface, Mask};
use masking::{ExposeInterface, Mask, Secret};
use transformers as inespay;
use ring::hmac;

use crate::{constants::headers, types::ResponseRouterData, utils};

Expand Down Expand Up @@ -99,9 +103,6 @@ impl ConnectorCommon for Inespay {

fn get_currency_unit(&self) -> api::CurrencyUnit {
api::CurrencyUnit::Minor
// TODO! Check connector documentation, on which unit they are processing the currency.
// If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor,
// if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base
}

fn common_get_content_type(&self) -> &'static str {
Expand Down Expand Up @@ -505,10 +506,20 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Inespay {

fn get_url(
&self,
_req: &RefundSyncRouterData,
_connectors: &Connectors,
req: &RefundSyncRouterData,
connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let connector_refund_id = req
.request
.connector_refund_id
.clone()
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?;
Ok(format!(
"{}{}{}",
self.base_url(connectors),
"/refunds/",
connector_refund_id,
))
}

fn build_request(
Expand All @@ -535,7 +546,7 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Inespay {
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<RefundSyncRouterData, errors::ConnectorError> {
let response: inespay::RefundResponse = res
let response: inespay::InespayRSyncResponse = res
.response
.parse_struct("inespay RefundSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Expand All @@ -557,27 +568,119 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Inespay {
}
}

fn get_webhook_body(
body: &[u8],
) -> CustomResult<inespay::InespayWebhookEventData, errors::ConnectorError> {
let notif_item:inespay::InespayWebhookEvent = serde_urlencoded::from_bytes::<inespay::InespayWebhookEvent>(body)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let encoded_data_return = notif_item.data_return;
let decoded_data_return = BASE64_ENGINE.decode(encoded_data_return)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let data_return:inespay::InespayWebhookEventData = decoded_data_return.parse_struct("inespay InespayWebhookEventData")
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(data_return)
}

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Inespay {
fn get_webhook_object_reference_id(
fn get_webhook_source_verification_algorithm(
&self,
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
Ok(Box::new(crypto::HmacSha256))
}

fn get_webhook_source_verification_signature(
&self,
request: &webhooks::IncomingWebhookRequestDetails<'_>,
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let notif_item = serde_urlencoded::from_bytes::<inespay::InespayWebhookEvent>(request.body)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

Ok(notif_item.signature_data_return.as_bytes().to_owned())
}

fn get_webhook_source_verification_message(
&self,
request: &webhooks::IncomingWebhookRequestDetails<'_>,
_merchant_id: &common_utils::id_type::MerchantId,
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let notif_item = serde_urlencoded::from_bytes::<inespay::InespayWebhookEvent>(request.body)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

Ok(notif_item.data_return.into_bytes())
}

async fn verify_webhook_source(
&self,
request: &webhooks::IncomingWebhookRequestDetails<'_>,
merchant_id: &common_utils::id_type::MerchantId,
connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
connector_label: &str,
) -> CustomResult<bool, errors::ConnectorError> {
let connector_webhook_secrets = self
.get_webhook_source_verification_merchant_secret(
merchant_id,
connector_label,
connector_webhook_details,
)
.await?;
let signature =
self.get_webhook_source_verification_signature(request, &connector_webhook_secrets)?;

let message = self.get_webhook_source_verification_message(
request,
merchant_id,
&connector_webhook_secrets,
)?;
let secret = connector_webhook_secrets.secret;

let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &secret);
let signed_message = hmac::sign(&signing_key, &message);
let computed_signature = hex::encode(signed_message.as_ref());
let payload_sign = BASE64_ENGINE.encode(computed_signature);
Ok(payload_sign.as_bytes().eq(&signature))
}

fn get_webhook_object_reference_id(
&self,
request: &webhooks::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
let data_return = get_webhook_body(request.body)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
match data_return {
inespay::InespayWebhookEventData::Payment(data)=> Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::ConnectorTransactionId(data.single_payin_id),
)),
inespay::InespayWebhookEventData::Refund(data)=> Ok(api_models::webhooks::ObjectReferenceId::RefundId(
api_models::webhooks::RefundIdType::ConnectorRefundId(data.refund_id),
))
}
}

fn get_webhook_event_type(
&self,
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
request: &webhooks::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
let data_return = get_webhook_body(request.body)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(api_models::webhooks::IncomingWebhookEvent::from(data_return))
}

fn get_webhook_resource_object(
&self,
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
request: &webhooks::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
let data_return = get_webhook_body(request.body)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(match data_return {
inespay::InespayWebhookEventData::Payment(payment_webhook_data)=> Box::new(payment_webhook_data),
inespay::InespayWebhookEventData::Refund(refund_webhook_data)=> Box::new(refund_webhook_data),
})

}
}

Expand Down
Loading

0 comments on commit 9b55b86

Please sign in to comment.