From b5bc8c4e7cfdde8251ed0e2e3835ed5e3f1435c4 Mon Sep 17 00:00:00 2001 From: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:15:04 +0530 Subject: [PATCH] feat(connector): [noon] add revoke mandate (#3487) --- crates/router/src/connector/noon.rs | 76 +++++++++++++++ .../router/src/connector/noon/transformers.rs | 95 ++++++++++++++++++- crates/router/src/core/payments/flows.rs | 1 - 3 files changed, 167 insertions(+), 5 deletions(-) diff --git a/crates/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs index 180b4b1485fb..6c98a3076375 100644 --- a/crates/router/src/connector/noon.rs +++ b/crates/router/src/connector/noon.rs @@ -46,6 +46,7 @@ impl api::Refund for Noon {} impl api::RefundExecute for Noon {} impl api::RefundSync for Noon {} impl api::PaymentToken for Noon {} +impl api::ConnectorMandateRevoke for Noon {} impl ConnectorIntegration< @@ -492,6 +493,81 @@ impl ConnectorIntegration for Noon +{ + fn get_headers( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}payment/v1/order", self.base_url(connectors))) + } + fn build_request( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::MandateRevokeType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::MandateRevokeType::get_headers( + self, req, connectors, + )?) + .set_body(types::MandateRevokeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + fn get_request_body( + &self, + req: &types::MandateRevokeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = noon::NoonRevokeMandateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn handle_response( + &self, + data: &types::MandateRevokeRouterData, + res: Response, + ) -> CustomResult { + let response: noon::NoonRevokeMandateResponse = res + .response + .parse_struct("Noon NoonRevokeMandateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + impl ConnectorIntegration for Noon { fn get_headers( &self, diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index bbf284848b59..ee06cd064bed 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ - self as conn_utils, CardData, PaymentsAuthorizeRequestData, RouterData, WalletData, + self as conn_utils, CardData, PaymentsAuthorizeRequestData, RevokeMandateRequestData, + RouterData, WalletData, }, core::errors, services, @@ -30,11 +31,13 @@ pub enum NoonSubscriptionType { } #[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct NoonSubscriptionData { #[serde(rename = "type")] subscription_type: NoonSubscriptionType, //Short description about the subscription. name: String, + max_amount: Option, } #[derive(Debug, Serialize)] @@ -168,12 +171,13 @@ pub enum NoonPaymentData { } #[derive(Debug, Serialize)] -#[serde(rename_all = "UPPERCASE")] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum NoonApiOperations { Initiate, Capture, Reverse, Refund, + CancelSubscription, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -335,6 +339,21 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { NoonSubscriptionData { subscription_type: NoonSubscriptionType::Unscheduled, name: name.clone(), + max_amount: item + .request + .setup_mandate_details + .clone() + .and_then(|mandate_details| match mandate_details.mandate_type { + Some(data_models::mandates::MandateDataType::SingleUse(mandate)) + | Some(data_models::mandates::MandateDataType::MultiUse(Some( + mandate, + ))) => Some( + conn_utils::to_currency_base_unit(mandate.amount, mandate.currency) + .ok(), + ), + _ => None, + }) + .flatten(), }, true, )) { @@ -450,7 +469,7 @@ impl ForeignFrom<(NoonPaymentStatus, Self)> for enums::AttemptStatus { } #[derive(Debug, Serialize, Deserialize)] -pub struct NoonSubscriptionResponse { +pub struct NoonSubscriptionObject { identifier: String, } @@ -475,7 +494,7 @@ pub struct NoonCheckoutData { pub struct NoonPaymentsResponseResult { order: NoonPaymentsOrderResponse, checkout_data: Option, - subscription: Option, + subscription: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -603,6 +622,25 @@ impl TryFrom<&types::PaymentsCancelRouterData> for NoonPaymentsCancelRequest { } } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NoonRevokeMandateRequest { + api_operation: NoonApiOperations, + subscription: NoonSubscriptionObject, +} + +impl TryFrom<&types::MandateRevokeRouterData> for NoonRevokeMandateRequest { + type Error = error_stack::Report; + fn try_from(item: &types::MandateRevokeRouterData) -> Result { + Ok(Self { + api_operation: NoonApiOperations::CancelSubscription, + subscription: NoonSubscriptionObject { + identifier: item.request.get_connector_mandate_id()?, + }, + }) + } +} + impl TryFrom<&types::RefundsRouterData> for NoonPaymentsActionRequest { type Error = error_stack::Report; fn try_from(item: &types::RefundsRouterData) -> Result { @@ -624,6 +662,55 @@ impl TryFrom<&types::RefundsRouterData> for NoonPaymentsActionRequest { }) } } +#[derive(Debug, Deserialize)] +pub enum NoonRevokeStatus { + Cancelled, +} + +#[derive(Debug, Deserialize)] +pub struct NoonCancelSubscriptionObject { + status: NoonRevokeStatus, +} + +#[derive(Debug, Deserialize)] +pub struct NoonRevokeMandateResult { + subscription: NoonCancelSubscriptionObject, +} + +#[derive(Debug, Deserialize)] +pub struct NoonRevokeMandateResponse { + result: NoonRevokeMandateResult, +} + +impl + TryFrom< + types::ResponseRouterData< + F, + NoonRevokeMandateResponse, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + NoonRevokeMandateResponse, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, + >, + ) -> Result { + match item.response.result.subscription.status { + NoonRevokeStatus::Cancelled => Ok(Self { + response: Ok(types::MandateRevokeResponseData { + mandate_status: common_enums::MandateStatus::Revoked, + }), + ..item.data + }), + } + } +} #[derive(Debug, Default, Deserialize, Clone)] #[serde(rename_all = "UPPERCASE")] diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index c9f9d6d87f5c..ebc0cf3664af 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -2247,7 +2247,6 @@ default_imp_for_revoking_mandates!( connector::Multisafepay, connector::Nexinets, connector::Nmi, - connector::Noon, connector::Nuvei, connector::Opayo, connector::Opennode,