diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index 493e2b73c7..da21a317c5 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -215,6 +215,23 @@ impl DecodeMessage for GcmAes256 { } } +/// Secure Hash Algorithm 512 +#[derive(Debug)] +pub struct Sha512; + +/// Trait for generating a digest for SHA +pub trait GenerateDigest { + /// takes a message and creates a digest for it + fn generate_digest(&self, message: &[u8]) -> CustomResult, errors::CryptoError>; +} + +impl GenerateDigest for Sha512 { + fn generate_digest(&self, message: &[u8]) -> CustomResult, errors::CryptoError> { + let digest = ring::digest::digest(&ring::digest::SHA512, message); + Ok(digest.as_ref().to_vec()) + } +} + #[cfg(test)] mod crypto_tests { #![allow(clippy::expect_used)] diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 9e132d5695..6d2b2be63a 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -7,8 +7,11 @@ use std::fmt::Debug; use error_stack::{IntoReport, ResultExt}; use self::{ - requests::GlobalpayPaymentsRequest, response::GlobalpayPaymentsResponse, - transformers as globalpay, + requests::{GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest}, + response::{ + GlobalpayPaymentsResponse, GlobalpayRefreshTokenErrorResponse, + GlobalpayRefreshTokenResponse, + }, }; use crate::{ configs::settings, @@ -38,16 +41,22 @@ where req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - let mut headers = vec![ + let access_token = req + .access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)?; + + Ok(vec![ ( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string(), ), ("X-GP-Version".to_string(), "2021-03-22".to_string()), - ]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - headers.append(&mut api_key); - Ok(headers) + ( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token), + ), + ]) } } @@ -66,19 +75,16 @@ impl ConnectorCommon for Globalpay { fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, + _auth_type: &types::ConnectorAuthType, ) -> CustomResult, errors::ConnectorError> { - let auth: globalpay::GlobalpayAuthType = auth_type - .try_into() - .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)]) + Ok(vec![]) } fn build_error_response( &self, res: types::Response, ) -> CustomResult { - let response: globalpay::GlobalpayErrorResponse = res + let response: transformers::GlobalpayErrorResponse = res .response .parse_struct("Globalpay ErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -97,6 +103,94 @@ impl api::ConnectorAccessToken for Globalpay {} impl ConnectorIntegration for Globalpay { + fn get_headers( + &self, + _req: &types::RefreshTokenRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::RefreshTokenType::get_content_type(self).to_string(), + ), + ("X-GP-Version".to_string(), "2021-03-22".to_string()), + ]) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "accesstoken")) + } + + fn build_request( + &self, + req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefreshTokenType::get_url(self, req, connectors)?) + .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) + .body(types::RefreshTokenType::get_request_body(self, req)?) + .build(), + )) + } + + fn get_request_body( + &self, + req: &types::RefreshTokenRouterData, + ) -> CustomResult, errors::ConnectorError> { + let req_obj = GlobalpayRefreshTokenRequest::try_from(req)?; + let globalpay_req = + utils::Encode::::encode_to_string_of_json(&req_obj) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(globalpay_req)) + } + + fn handle_response( + &self, + data: &types::RefreshTokenRouterData, + res: types::Response, + ) -> CustomResult { + logger::debug!(globalpaypayments_raw_refresh_token_response=?res); + let response: GlobalpayRefreshTokenResponse = res + .response + .parse_struct("Globalpay PaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + } + .try_into() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: GlobalpayRefreshTokenErrorResponse = res + .response + .parse_struct("Globalpay ErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error_code, + message: response.detailed_error_description, + reason: None, + }) + } } impl api::Payment for Globalpay {} diff --git a/crates/router/src/connector/globalpay/requests.rs b/crates/router/src/connector/globalpay/requests.rs index 3ccd8ff766..dd4c2f3c3d 100644 --- a/crates/router/src/connector/globalpay/requests.rs +++ b/crates/router/src/connector/globalpay/requests.rs @@ -72,6 +72,14 @@ pub struct GlobalpayPaymentsRequest { pub user_reference: Option, } +#[derive(Debug, Serialize)] +pub struct GlobalpayRefreshTokenRequest { + pub app_id: String, + pub nonce: String, + pub secret: String, + pub grant_type: String, +} + #[derive(Debug, Serialize, Deserialize)] pub struct CurrencyConversion { /// A unique identifier generated by Global Payments to identify the currency conversion. It diff --git a/crates/router/src/connector/globalpay/response.rs b/crates/router/src/connector/globalpay/response.rs index 32064add8a..495a7e6be5 100644 --- a/crates/router/src/connector/globalpay/response.rs +++ b/crates/router/src/connector/globalpay/response.rs @@ -68,6 +68,18 @@ pub struct Action { pub action_type: Option, } +#[derive(Debug, Deserialize)] +pub struct GlobalpayRefreshTokenResponse { + pub token: String, + pub seconds_to_expire: i64, +} + +#[derive(Debug, Deserialize)] +pub struct GlobalpayRefreshTokenErrorResponse { + pub error_code: String, + pub detailed_error_description: String, +} + /// Information relating to a currency conversion. #[derive(Debug, Serialize, Deserialize)] pub struct CurrencyConversion { diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/router/src/connector/globalpay/transformers.rs index abc3a79957..467ab5eb44 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/router/src/connector/globalpay/transformers.rs @@ -1,8 +1,11 @@ +use common_utils::crypto::{self, GenerateDigest}; +use error_stack::ResultExt; +use rand::distributions::DistString; use serde::{Deserialize, Serialize}; use super::{ - requests::{self, GlobalpayPaymentsRequest}, - response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse}, + requests::{self, GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest}, + response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse, GlobalpayRefreshTokenResponse}, }; use crate::{ connector::utils::{self, CardData, PaymentsRequestData}, @@ -69,21 +72,60 @@ impl TryFrom<&types::PaymentsCancelRouterData> for GlobalpayPaymentsRequest { } pub struct GlobalpayAuthType { - pub api_key: String, + pub app_id: String, + pub key: String, } impl TryFrom<&types::ConnectorAuthType> for GlobalpayAuthType { type Error = error_stack::Report; fn try_from(auth_type: &types::ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_string(), + types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + app_id: key1.to_string(), + key: api_key.to_string(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } +impl TryFrom for types::AccessToken { + type Error = error_stack::Report; + + fn try_from(item: GlobalpayRefreshTokenResponse) -> Result { + Ok(Self { + token: item.token, + expires: item.seconds_to_expire, + }) + } +} + +impl TryFrom<&types::RefreshTokenRouterData> for GlobalpayRefreshTokenRequest { + type Error = error_stack::Report; + + fn try_from(item: &types::RefreshTokenRouterData) -> Result { + let globalpay_auth = GlobalpayAuthType::try_from(&item.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType) + .attach_printable("Could not convert connector_auth to globalpay_auth")?; + + let nonce = rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 12); + let nonce_with_api_key = format!("{}{}", nonce, globalpay_auth.key); + let secret_vec = crypto::Sha512 + .generate_digest(nonce_with_api_key.as_bytes()) + .change_context(errors::ConnectorError::RequestEncodingFailed) + .attach_printable("error creating request nonce")?; + + let secret = hex::encode(secret_vec); + + Ok(Self { + app_id: globalpay_auth.app_id, + nonce, + secret, + grant_type: "client_credentials".to_string(), + }) + } +} + impl From for enums::AttemptStatus { fn from(item: GlobalpayPaymentStatus) -> Self { match item { @@ -134,6 +176,24 @@ impl } } +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::AccessToken { + token: item.response.token, + expires: item.response.seconds_to_expire, + }), + ..item.data + }) + } +} + impl TryFrom<&types::RefundsRouterData> for requests::GlobalpayRefundRequest { type Error = error_stack::Report; fn try_from(item: &types::RefundsRouterData) -> Result { diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 52c4e756f3..2a57d79668 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -59,11 +59,15 @@ pub type PaymentsSessionType = dyn services::ConnectorIntegration; pub type PaymentsVoidType = dyn services::ConnectorIntegration; + pub type RefundExecuteType = dyn services::ConnectorIntegration; pub type RefundSyncType = dyn services::ConnectorIntegration; +pub type RefreshTokenType = + dyn services::ConnectorIntegration; + pub type VerifyRouterData = RouterData; #[derive(Debug, Clone)]