mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(connector): [Noon] Add Card Payments, Capture, Void and Refund (#1207)
This commit is contained in:
		| @ -2,12 +2,18 @@ mod transformers; | ||||
|  | ||||
| use std::fmt::Debug; | ||||
|  | ||||
| use base64::Engine; | ||||
| use error_stack::{IntoReport, ResultExt}; | ||||
| use transformers as noon; | ||||
|  | ||||
| use super::utils::PaymentsSyncRequestData; | ||||
| use crate::{ | ||||
|     configs::settings, | ||||
|     core::errors::{self, CustomResult}, | ||||
|     consts, | ||||
|     core::{ | ||||
|         errors::{self, CustomResult}, | ||||
|         payments, | ||||
|     }, | ||||
|     headers, | ||||
|     services::{self, ConnectorIntegration}, | ||||
|     types::{ | ||||
| @ -80,9 +86,16 @@ impl ConnectorCommon for Noon { | ||||
|         &self, | ||||
|         auth_type: &types::ConnectorAuthType, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         let auth = noon::NoonAuthType::try_from(auth_type) | ||||
|             .change_context(errors::ConnectorError::FailedToObtainAuthType)?; | ||||
|         Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)]) | ||||
|         let auth = noon::NoonAuthType::try_from(auth_type)?; | ||||
|  | ||||
|         let encoded_api_key = consts::BASE64_ENGINE.encode(format!( | ||||
|             "{}.{}:{}", | ||||
|             auth.business_identifier, auth.application_identifier, auth.api_key | ||||
|         )); | ||||
|         Ok(vec![( | ||||
|             headers::AUTHORIZATION.to_string(), | ||||
|             format!("Key_Test {encoded_api_key}"), | ||||
|         )]) | ||||
|     } | ||||
|  | ||||
|     fn build_error_response( | ||||
| @ -96,9 +109,9 @@ impl ConnectorCommon for Noon { | ||||
|  | ||||
|         Ok(ErrorResponse { | ||||
|             status_code: res.status_code, | ||||
|             code: response.code, | ||||
|             code: response.result_code.to_string(), | ||||
|             message: response.message, | ||||
|             reason: response.reason, | ||||
|             reason: Some(response.class_description), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @ -137,9 +150,9 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::PaymentsAuthorizeRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|         Ok(format!("{}payment/v1/order", self.base_url(connectors))) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
| @ -187,7 +200,6 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
| @ -215,10 +227,14 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::PaymentsSyncRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|         req: &types::PaymentsSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|         let connector_transaction_id = req.request.get_connector_transaction_id()?; | ||||
|         Ok(format!( | ||||
|             "{}payment/v1/order/{connector_transaction_id}", | ||||
|             self.base_url(connectors) | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
| @ -250,7 +266,6 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
| @ -279,16 +294,20 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::PaymentsCaptureRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|         Ok(format!("{}payment/v1/order", self.base_url(connectors))) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         _req: &types::PaymentsCaptureRouterData, | ||||
|         req: &types::PaymentsCaptureRouterData, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) | ||||
|         let req_obj = noon::NoonPaymentsActionRequest::try_from(req)?; | ||||
|         let noon_req = | ||||
|             utils::Encode::<noon::NoonPaymentsRequest>::encode_to_string_of_json(&req_obj) | ||||
|                 .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(noon_req)) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
| @ -323,7 +342,6 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
| @ -337,6 +355,75 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme | ||||
| impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData> | ||||
|     for Noon | ||||
| { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::PaymentsCancelRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, 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::PaymentsCancelRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Ok(format!("{}payment/v1/order", self.base_url(connectors))) | ||||
|     } | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         req: &types::PaymentsCancelRouterData, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         let connector_req = noon::NoonPaymentsCancelRequest::try_from(req)?; | ||||
|         let noon_req = utils::Encode::<noon::NoonPaymentsCancelRequest>::encode_to_string_of_json( | ||||
|             &connector_req, | ||||
|         ) | ||||
|         .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(noon_req)) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         req: &types::PaymentsCancelRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Ok(Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Post) | ||||
|                 .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) | ||||
|                 .body(types::PaymentsVoidType::get_request_body(self, req)?) | ||||
|                 .build(), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::PaymentsCancelRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> { | ||||
|         let response: noon::NoonPaymentsResponse = res | ||||
|             .response | ||||
|             .parse_struct("Noon PaymentsCancelResponse") | ||||
|             .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<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Noon { | ||||
| @ -355,18 +442,19 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::RefundsRouterData<api::Execute>, | ||||
|         _connectors: &settings::Connectors, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|         Ok(format!("{}payment/v1/order", self.base_url(connectors))) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         req: &types::RefundsRouterData<api::Execute>, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         let req_obj = noon::NoonRefundRequest::try_from(req)?; | ||||
|         let noon_req = utils::Encode::<noon::NoonRefundRequest>::encode_to_string_of_json(&req_obj) | ||||
|             .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         let req_obj = noon::NoonPaymentsActionRequest::try_from(req)?; | ||||
|         let noon_req = | ||||
|             utils::Encode::<noon::NoonPaymentsActionRequest>::encode_to_string_of_json(&req_obj) | ||||
|                 .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(noon_req)) | ||||
|     } | ||||
|  | ||||
| @ -401,7 +489,6 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
| @ -427,10 +514,14 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::RefundSyncRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|         req: &types::RefundSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|         Ok(format!( | ||||
|             "{}payment/v1/order/{}", | ||||
|             self.base_url(connectors), | ||||
|             req.request.connector_transaction_id | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
| @ -454,10 +545,11 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse | ||||
|         data: &types::RefundSyncRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> { | ||||
|         let response: noon::RefundResponse = | ||||
|             res.response | ||||
|                 .parse_struct("noon RefundSyncResponse") | ||||
|                 .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
|         let response: noon::RefundSyncResponse = res | ||||
|             .response | ||||
|             .parse_struct("noon RefundSyncResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
|  | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
|             data: data.clone(), | ||||
| @ -474,6 +566,17 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl services::ConnectorRedirectResponse for Noon { | ||||
|     fn get_flow_type( | ||||
|         &self, | ||||
|         _query_params: &str, | ||||
|         _json_payload: Option<serde_json::Value>, | ||||
|         _action: services::PaymentAction, | ||||
|     ) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> { | ||||
|         Ok(payments::CallConnectorAction::Trigger) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl api::IncomingWebhook for Noon { | ||||
|     fn get_webhook_object_reference_id( | ||||
|  | ||||
| @ -2,94 +2,192 @@ use masking::Secret; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
|     connector::utils::PaymentsAuthorizeRequestData, | ||||
|     connector::utils::{ | ||||
|         self as conn_utils, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData, | ||||
|     }, | ||||
|     core::errors, | ||||
|     services, | ||||
|     types::{self, api, storage::enums}, | ||||
| }; | ||||
|  | ||||
| //TODO: Fill the struct with respective fields | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| pub struct NoonPaymentsRequest { | ||||
|     amount: i64, | ||||
|     card: NoonCard, | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "UPPERCASE")] | ||||
| pub enum NoonChannels { | ||||
|     Web, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonOrder { | ||||
|     amount: String, | ||||
|     currency: storage_models::enums::Currency, | ||||
|     channel: NoonChannels, | ||||
|     category: String, | ||||
|     //Short description of the order. | ||||
|     name: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "UPPERCASE")] | ||||
| pub enum NoonPaymentActions { | ||||
|     Authorize, | ||||
|     Sale, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonConfiguration { | ||||
|     payment_action: NoonPaymentActions, | ||||
|     return_url: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonCard { | ||||
|     name: Secret<String>, | ||||
|     number: cards::CardNumber, | ||||
|     name_on_card: Secret<String>, | ||||
|     number_plain: cards::CardNumber, | ||||
|     expiry_month: Secret<String>, | ||||
|     expiry_year: Secret<String>, | ||||
|     cvc: Secret<String>, | ||||
|     complete: bool, | ||||
|     cvv: Secret<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(tag = "type", content = "data")] | ||||
| pub enum NoonPaymentData { | ||||
|     Card(NoonCard), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "UPPERCASE")] | ||||
| pub enum NoonApiOperations { | ||||
|     Initiate, | ||||
|     Capture, | ||||
|     Reverse, | ||||
|     Refund, | ||||
| } | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonPaymentsRequest { | ||||
|     api_operation: NoonApiOperations, | ||||
|     order: NoonOrder, | ||||
|     configuration: NoonConfiguration, | ||||
|     payment_data: NoonPaymentData, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { | ||||
|         match item.request.payment_method_data.clone() { | ||||
|             api::PaymentMethodData::Card(req_card) => { | ||||
|                 let card = NoonCard { | ||||
|                     name: req_card.card_holder_name, | ||||
|                     number: req_card.card_number, | ||||
|                     expiry_month: req_card.card_exp_month, | ||||
|                     expiry_year: req_card.card_exp_year, | ||||
|                     cvc: req_card.card_cvc, | ||||
|                     complete: item.request.is_auto_capture()?, | ||||
|                 }; | ||||
|                 Ok(Self { | ||||
|                     amount: item.request.amount, | ||||
|                     card, | ||||
|                 }) | ||||
|             } | ||||
|             _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), | ||||
|         } | ||||
|         let payment_data = match item.request.payment_method_data.clone() { | ||||
|             api::PaymentMethodData::Card(req_card) => Ok(NoonPaymentData::Card(NoonCard { | ||||
|                 name_on_card: req_card.card_holder_name, | ||||
|                 number_plain: req_card.card_number, | ||||
|                 expiry_month: req_card.card_exp_month, | ||||
|                 expiry_year: req_card.card_exp_year, | ||||
|                 cvv: req_card.card_cvc, | ||||
|             })), | ||||
|             _ => Err(errors::ConnectorError::NotImplemented( | ||||
|                 "Payment methods".to_string(), | ||||
|             )), | ||||
|         }?; | ||||
|  | ||||
|         let order = NoonOrder { | ||||
|             amount: conn_utils::to_currency_base_unit(item.request.amount, item.request.currency)?, | ||||
|             currency: item.request.currency, | ||||
|             channel: NoonChannels::Web, | ||||
|             category: "pay".to_string(), | ||||
|             name: item.get_description()?, | ||||
|         }; | ||||
|         let payment_action = if item.request.is_auto_capture()? { | ||||
|             NoonPaymentActions::Sale | ||||
|         } else { | ||||
|             NoonPaymentActions::Authorize | ||||
|         }; | ||||
|         Ok(Self { | ||||
|             api_operation: NoonApiOperations::Initiate, | ||||
|             order, | ||||
|             configuration: NoonConfiguration { | ||||
|                 payment_action, | ||||
|                 return_url: item.request.router_return_url.clone(), | ||||
|             }, | ||||
|             payment_data, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| //TODO: Fill the struct with respective fields | ||||
| // Auth Struct | ||||
| pub struct NoonAuthType { | ||||
|     pub(super) api_key: String, | ||||
|     pub(super) application_identifier: String, | ||||
|     pub(super) business_identifier: String, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::ConnectorAuthType> for NoonAuthType { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { | ||||
|         match auth_type { | ||||
|             types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { | ||||
|             types::ConnectorAuthType::SignatureKey { | ||||
|                 api_key, | ||||
|                 key1, | ||||
|                 api_secret, | ||||
|             } => Ok(Self { | ||||
|                 api_key: api_key.to_string(), | ||||
|                 application_identifier: api_secret.to_string(), | ||||
|                 business_identifier: key1.to_string(), | ||||
|             }), | ||||
|             _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // PaymentsResponse | ||||
| //TODO: Append the remaining status flags | ||||
| #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| #[serde(rename_all = "SCREAMING_SNAKE_CASE")] | ||||
| pub enum NoonPaymentStatus { | ||||
|     Succeeded, | ||||
|     Authorized, | ||||
|     Captured, | ||||
|     PartiallyCaptured, | ||||
|     Reversed, | ||||
|     #[serde(rename = "3DS_ENROLL_INITIATED")] | ||||
|     ThreeDsEnrollInitiated, | ||||
|     Failed, | ||||
|     #[default] | ||||
|     Processing, | ||||
|     Pending, | ||||
| } | ||||
|  | ||||
| impl From<NoonPaymentStatus> for enums::AttemptStatus { | ||||
|     fn from(item: NoonPaymentStatus) -> Self { | ||||
|         match item { | ||||
|             NoonPaymentStatus::Succeeded => Self::Charged, | ||||
|             NoonPaymentStatus::Authorized => Self::Authorized, | ||||
|             NoonPaymentStatus::Captured | NoonPaymentStatus::PartiallyCaptured => Self::Charged, | ||||
|             NoonPaymentStatus::Reversed => Self::Voided, | ||||
|             NoonPaymentStatus::ThreeDsEnrollInitiated => Self::AuthenticationPending, | ||||
|             NoonPaymentStatus::Failed => Self::Failure, | ||||
|             NoonPaymentStatus::Processing => Self::Authorizing, | ||||
|             NoonPaymentStatus::Pending => Self::Pending, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| //TODO: Fill the struct with respective fields | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||
| pub struct NoonPaymentsResponse { | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonPaymentsOrderResponse { | ||||
|     status: NoonPaymentStatus, | ||||
|     id: String, | ||||
|     id: u64, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonCheckoutData { | ||||
|     post_url: url::Url, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonPaymentsResponseResult { | ||||
|     order: NoonPaymentsOrderResponse, | ||||
|     checkout_data: Option<NoonCheckoutData>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| pub struct NoonPaymentsResponse { | ||||
|     result: NoonPaymentsResponseResult, | ||||
| } | ||||
|  | ||||
| impl<F, T> | ||||
| @ -100,11 +198,20 @@ impl<F, T> | ||||
|     fn try_from( | ||||
|         item: types::ResponseRouterData<F, NoonPaymentsResponse, T, types::PaymentsResponseData>, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let redirection_data = item.response.result.checkout_data.map(|redirection_data| { | ||||
|             services::RedirectForm::Form { | ||||
|                 endpoint: redirection_data.post_url.to_string(), | ||||
|                 method: services::Method::Post, | ||||
|                 form_fields: std::collections::HashMap::new(), | ||||
|             } | ||||
|         }); | ||||
|         Ok(Self { | ||||
|             status: enums::AttemptStatus::from(item.response.status), | ||||
|             status: enums::AttemptStatus::from(item.response.result.order.status), | ||||
|             response: Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                 resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), | ||||
|                 redirection_data: None, | ||||
|                 resource_id: types::ResponseId::ConnectorTransactionId( | ||||
|                     item.response.result.order.id.to_string(), | ||||
|                 ), | ||||
|                 redirection_data, | ||||
|                 mandate_reference: None, | ||||
|                 connector_metadata: None, | ||||
|                 network_txn_id: None, | ||||
| @ -114,52 +221,125 @@ impl<F, T> | ||||
|     } | ||||
| } | ||||
|  | ||||
| //TODO: Fill the struct with respective fields | ||||
| // REFUND : | ||||
| // Type definition for RefundRequest | ||||
| #[derive(Default, Debug, Serialize)] | ||||
| pub struct NoonRefundRequest { | ||||
|     pub amount: i64, | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonActionTransaction { | ||||
|     amount: String, | ||||
|     currency: storage_models::enums::Currency, | ||||
| } | ||||
|  | ||||
| impl<F> TryFrom<&types::RefundsRouterData<F>> for NoonRefundRequest { | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonActionOrder { | ||||
|     id: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonPaymentsActionRequest { | ||||
|     api_operation: NoonApiOperations, | ||||
|     order: NoonActionOrder, | ||||
|     transaction: NoonActionTransaction, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::PaymentsCaptureRouterData> for NoonPaymentsActionRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { | ||||
|     fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> { | ||||
|         let order = NoonActionOrder { | ||||
|             id: item.request.connector_transaction_id.clone(), | ||||
|         }; | ||||
|         let transaction = NoonActionTransaction { | ||||
|             amount: conn_utils::to_currency_base_unit( | ||||
|                 item.request.amount_to_capture, | ||||
|                 item.request.currency, | ||||
|             )?, | ||||
|             currency: item.request.currency, | ||||
|         }; | ||||
|         Ok(Self { | ||||
|             amount: item.request.amount, | ||||
|             api_operation: NoonApiOperations::Capture, | ||||
|             order, | ||||
|             transaction, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Type definition for Refund Response | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonPaymentsCancelRequest { | ||||
|     api_operation: NoonApiOperations, | ||||
|     order: NoonActionOrder, | ||||
| } | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| #[derive(Debug, Serialize, Default, Deserialize, Clone)] | ||||
| impl TryFrom<&types::PaymentsCancelRouterData> for NoonPaymentsCancelRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> { | ||||
|         let order = NoonActionOrder { | ||||
|             id: item.request.connector_transaction_id.clone(), | ||||
|         }; | ||||
|         Ok(Self { | ||||
|             api_operation: NoonApiOperations::Reverse, | ||||
|             order, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F> TryFrom<&types::RefundsRouterData<F>> for NoonPaymentsActionRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { | ||||
|         let order = NoonActionOrder { | ||||
|             id: item.request.connector_transaction_id.clone(), | ||||
|         }; | ||||
|         let transaction = NoonActionTransaction { | ||||
|             amount: conn_utils::to_currency_base_unit( | ||||
|                 item.request.refund_amount, | ||||
|                 item.request.currency, | ||||
|             )?, | ||||
|             currency: item.request.currency, | ||||
|         }; | ||||
|         Ok(Self { | ||||
|             api_operation: NoonApiOperations::Refund, | ||||
|             order, | ||||
|             transaction, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Deserialize, Clone)] | ||||
| #[serde(rename_all = "UPPERCASE")] | ||||
| pub enum RefundStatus { | ||||
|     Succeeded, | ||||
|     Success, | ||||
|     Failed, | ||||
|     #[default] | ||||
|     Processing, | ||||
|     Pending, | ||||
| } | ||||
|  | ||||
| impl From<RefundStatus> for enums::RefundStatus { | ||||
|     fn from(item: RefundStatus) -> Self { | ||||
|         match item { | ||||
|             RefundStatus::Succeeded => Self::Success, | ||||
|             RefundStatus::Success => Self::Success, | ||||
|             RefundStatus::Failed => Self::Failure, | ||||
|             RefundStatus::Processing => Self::Pending, | ||||
|             //TODO: Review mapping | ||||
|             RefundStatus::Pending => Self::Pending, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| //TODO: Fill the struct with respective fields | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct RefundResponse { | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonPaymentsTransactionResponse { | ||||
|     id: String, | ||||
|     status: RefundStatus, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| pub struct NoonRefundResponseResult { | ||||
|     transaction: NoonPaymentsTransactionResponse, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| pub struct RefundResponse { | ||||
|     result: NoonRefundResponseResult, | ||||
| } | ||||
|  | ||||
| impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>> | ||||
|     for types::RefundsRouterData<api::Execute> | ||||
| { | ||||
| @ -169,36 +349,60 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>> | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             response: Ok(types::RefundsResponseData { | ||||
|                 connector_refund_id: item.response.id.to_string(), | ||||
|                 refund_status: enums::RefundStatus::from(item.response.status), | ||||
|                 connector_refund_id: item.response.result.transaction.id, | ||||
|                 refund_status: enums::RefundStatus::from(item.response.result.transaction.status), | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>> | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| pub struct NoonRefundResponseTransactions { | ||||
|     id: String, | ||||
|     status: RefundStatus, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| pub struct NoonRefundSyncResponseResult { | ||||
|     transactions: Vec<NoonRefundResponseTransactions>, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| pub struct RefundSyncResponse { | ||||
|     result: NoonRefundSyncResponseResult, | ||||
| } | ||||
|  | ||||
| impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundSyncResponse>> | ||||
|     for types::RefundsRouterData<api::RSync> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::RefundsResponseRouterData<api::RSync, RefundResponse>, | ||||
|         item: types::RefundsResponseRouterData<api::RSync, RefundSyncResponse>, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let connector_refund_id = item.data.request.get_connector_refund_id()?; | ||||
|         let noon_transaction: &NoonRefundResponseTransactions = item | ||||
|             .response | ||||
|             .result | ||||
|             .transactions | ||||
|             .iter() | ||||
|             .find(|transaction| transaction.id == connector_refund_id) | ||||
|             .ok_or(errors::ConnectorError::ResponseHandlingFailed)?; | ||||
|  | ||||
|         Ok(Self { | ||||
|             response: Ok(types::RefundsResponseData { | ||||
|                 connector_refund_id: item.response.id.to_string(), | ||||
|                 refund_status: enums::RefundStatus::from(item.response.status), | ||||
|                 connector_refund_id: noon_transaction.id.to_owned(), | ||||
|                 refund_status: enums::RefundStatus::from(noon_transaction.status.to_owned()), | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| //TODO: Fill the struct with respective fields | ||||
| #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] | ||||
| #[derive(Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct NoonErrorResponse { | ||||
|     pub status_code: u16, | ||||
|     pub code: String, | ||||
|     pub result_code: u32, | ||||
|     pub message: String, | ||||
|     pub reason: Option<String>, | ||||
|     pub class_description: String, | ||||
| } | ||||
|  | ||||
| @ -261,7 +261,6 @@ default_imp_for_connector_redirect_response!( | ||||
|     connector::Multisafepay, | ||||
|     connector::Nexinets, | ||||
|     connector::Nmi, | ||||
|     connector::Noon, | ||||
|     connector::Opennode, | ||||
|     connector::Payeezy, | ||||
|     connector::Payu, | ||||
|  | ||||
| @ -226,7 +226,7 @@ impl ConnectorData { | ||||
|                 enums::Connector::Klarna => Ok(Box::new(&connector::Klarna)), | ||||
|                 enums::Connector::Mollie => Ok(Box::new(&connector::Mollie)), | ||||
|                 enums::Connector::Nmi => Ok(Box::new(&connector::Nmi)), | ||||
|                 // "noon" => Ok(Box::new(&connector::Noon)), added as template code for future usage | ||||
|                 enums::Connector::Noon => Ok(Box::new(&connector::Noon)), | ||||
|                 enums::Connector::Nuvei => Ok(Box::new(&connector::Nuvei)), | ||||
|                 enums::Connector::Opennode => Ok(Box::new(&connector::Opennode)), | ||||
|                 // "payeezy" => Ok(Box::new(&connector::Payeezy)), As psync and rsync are not supported by this connector, it is added as template code for future usage | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 SamraatBansal
					SamraatBansal