mod transformers; use std::fmt::Debug; use common_utils::crypto; use error_stack::{IntoReport, ResultExt}; use transformers as opennode; use self::opennode::OpennodeWebhookDetails; use crate::{ configs::settings, connector::utils as conn_utils, core::errors::{self, CustomResult}, db, headers, services::{self, ConnectorIntegration}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, utils::{BytesExt, Encode}, }; #[derive(Debug, Clone)] pub struct Opennode; impl api::Payment for Opennode {} impl api::PaymentSession for Opennode {} impl api::PaymentToken for Opennode {} impl api::ConnectorAccessToken for Opennode {} impl api::PreVerify for Opennode {} impl api::PaymentAuthorize for Opennode {} impl api::PaymentSync for Opennode {} impl api::PaymentCapture for Opennode {} impl api::PaymentVoid for Opennode {} impl api::Refund for Opennode {} impl api::RefundExecute for Opennode {} impl api::RefundSync for Opennode {} impl ConnectorCommonExt for Opennode where Self: ConnectorIntegration, { fn build_headers( &self, req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { let mut header = vec![ ( headers::CONTENT_TYPE.to_string(), self.common_get_content_type().to_string(), ), ( headers::ACCEPT.to_string(), self.common_get_content_type().to_string(), ), ]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); Ok(header) } } impl ConnectorCommon for Opennode { fn id(&self) -> &'static str { "opennode" } fn common_get_content_type(&self) -> &'static str { "application/json" } fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { connectors.opennode.base_url.as_ref() } fn get_auth_header( &self, auth_type: &types::ConnectorAuthType, ) -> CustomResult, errors::ConnectorError> { let auth = opennode::OpennodeAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)]) } fn build_error_response( &self, res: Response, ) -> CustomResult { let response: opennode::OpennodeErrorResponse = res .response .parse_struct("OpennodeErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; Ok(ErrorResponse { status_code: res.status_code, code: response.code, message: response.message, reason: response.reason, }) } } impl ConnectorIntegration< api::PaymentMethodToken, types::PaymentMethodTokenizationData, types::PaymentsResponseData, > for Opennode { // Not Implemented (R) } impl ConnectorIntegration for Opennode { //TODO: implement sessions flow } impl ConnectorIntegration for Opennode { } impl ConnectorIntegration for Opennode { } impl ConnectorIntegration for Opennode { fn get_headers( &self, req: &types::PaymentsAuthorizeRouterData, 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::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, ) -> CustomResult { Ok(format!("{}/v1/charges", self.base_url(_connectors))) } fn get_request_body( &self, req: &types::PaymentsAuthorizeRouterData, ) -> CustomResult, errors::ConnectorError> { let req_obj = opennode::OpennodePaymentsRequest::try_from(req)?; let opennode_req = Encode::::encode_to_string_of_json(&req_obj) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(opennode_req)) } fn build_request( &self, req: &types::PaymentsAuthorizeRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) .body(types::PaymentsAuthorizeType::get_request_body(self, req)?) .build(), )) } fn handle_response( &self, data: &types::PaymentsAuthorizeRouterData, res: Response, ) -> CustomResult { let response: opennode::OpennodePaymentsResponse = res .response .parse_struct("Opennode PaymentsAuthorizeResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } impl ConnectorIntegration for Opennode { fn get_headers( &self, req: &types::PaymentsSyncRouterData, 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::PaymentsSyncRouterData, _connectors: &settings::Connectors, ) -> CustomResult { let connector_id = _req .request .connector_transaction_id .get_connector_transaction_id() .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; Ok(format!( "{}/v2/charge/{}", self.base_url(_connectors), connector_id )) } fn build_request( &self, req: &types::PaymentsSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, data: &types::PaymentsSyncRouterData, res: Response, ) -> CustomResult { let response: opennode::OpennodePaymentsResponse = res .response .parse_struct("opennode PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } impl ConnectorIntegration for Opennode { fn get_headers( &self, req: &types::PaymentsCaptureRouterData, 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::PaymentsCaptureRouterData, _connectors: &settings::Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body( &self, _req: &types::PaymentsCaptureRouterData, ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } fn build_request( &self, req: &types::PaymentsCaptureRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) .build(), )) } fn handle_response( &self, data: &types::PaymentsCaptureRouterData, res: Response, ) -> CustomResult { let response: opennode::OpennodePaymentsResponse = res .response .parse_struct("Opennode PaymentsCaptureResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } impl ConnectorIntegration for Opennode { } impl ConnectorIntegration for Opennode { fn get_headers( &self, req: &types::RefundsRouterData, 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::RefundsRouterData, _connectors: &settings::Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body( &self, req: &types::RefundsRouterData, ) -> CustomResult, errors::ConnectorError> { let req_obj = opennode::OpennodeRefundRequest::try_from(req)?; let opennode_req = Encode::::encode_to_string_of_json(&req_obj) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(opennode_req)) } fn build_request( &self, req: &types::RefundsRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { let request = services::RequestBuilder::new() .method(services::Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .headers(types::RefundExecuteType::get_headers( self, req, connectors, )?) .body(types::RefundExecuteType::get_request_body(self, req)?) .build(); Ok(Some(request)) } fn handle_response( &self, data: &types::RefundsRouterData, res: Response, ) -> CustomResult, errors::ConnectorError> { let response: opennode::RefundResponse = res .response .parse_struct("opennode RefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } impl ConnectorIntegration for Opennode { fn get_headers( &self, req: &types::RefundSyncRouterData, 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::RefundSyncRouterData, _connectors: &settings::Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn build_request( &self, req: &types::RefundSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .headers(types::RefundSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, data: &types::RefundSyncRouterData, res: Response, ) -> CustomResult { let response: opennode::RefundResponse = res .response .parse_struct("opennode RefundSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } #[async_trait::async_trait] impl api::IncomingWebhook for Opennode { fn get_webhook_source_verification_algorithm( &self, _request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::HmacSha256)) } fn get_webhook_source_verification_signature( &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let notif = serde_urlencoded::from_bytes::(request.body) .into_report() .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; let base64_signature = notif.hashed_order; hex::decode(base64_signature) .into_report() .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) } fn get_webhook_source_verification_message( &self, request: &api::IncomingWebhookRequestDetails<'_>, _merchant_id: &str, _secret: &[u8], ) -> CustomResult, errors::ConnectorError> { let message = std::str::from_utf8(request.body) .into_report() .change_context(errors::ConnectorError::ParsingFailed)?; Ok(message.to_string().into_bytes()) } async fn get_webhook_source_verification_merchant_secret( &self, db: &dyn db::StorageInterface, merchant_id: &str, ) -> CustomResult, errors::ConnectorError> { let key = conn_utils::get_webhook_merchant_secret_key(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_object_reference_id( &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let notif = serde_urlencoded::from_bytes::(request.body) .into_report() .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; Ok(api_models::webhooks::ObjectReferenceId::PaymentId( api_models::payments::PaymentIdType::ConnectorTransactionId(notif.id), )) } fn get_webhook_event_type( &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let notif = serde_urlencoded::from_bytes::(request.body) .into_report() .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; match notif.status { opennode::OpennodePaymentStatus::Paid => { Ok(api::IncomingWebhookEvent::PaymentIntentSuccess) } opennode::OpennodePaymentStatus::Underpaid | opennode::OpennodePaymentStatus::Expired => { Ok(api::IncomingWebhookEvent::PaymentActionRequired) } opennode::OpennodePaymentStatus::Processing => { Ok(api::IncomingWebhookEvent::PaymentIntentProcessing) } _ => Ok(api::IncomingWebhookEvent::EventNotSupported), } } fn get_webhook_resource_object( &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let notif = serde_urlencoded::from_bytes::(request.body) .into_report() .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; Encode::::encode_to_value(¬if.status) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) } }