feat(globalpay): implement access token creation (#408)

This commit is contained in:
Narayan Bhat
2023-01-20 18:25:52 +05:30
committed by GitHub
parent 7edc167c89
commit b1b0500035
6 changed files with 213 additions and 18 deletions

View File

@ -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<Vec<u8>, errors::CryptoError>;
}
impl GenerateDigest for Sha512 {
fn generate_digest(&self, message: &[u8]) -> CustomResult<Vec<u8>, errors::CryptoError> {
let digest = ring::digest::digest(&ring::digest::SHA512, message);
Ok(digest.as_ref().to_vec())
}
}
#[cfg(test)] #[cfg(test)]
mod crypto_tests { mod crypto_tests {
#![allow(clippy::expect_used)] #![allow(clippy::expect_used)]

View File

@ -7,8 +7,11 @@ use std::fmt::Debug;
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use self::{ use self::{
requests::GlobalpayPaymentsRequest, response::GlobalpayPaymentsResponse, requests::{GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest},
transformers as globalpay, response::{
GlobalpayPaymentsResponse, GlobalpayRefreshTokenErrorResponse,
GlobalpayRefreshTokenResponse,
},
}; };
use crate::{ use crate::{
configs::settings, configs::settings,
@ -38,16 +41,22 @@ where
req: &types::RouterData<Flow, Request, Response>, req: &types::RouterData<Flow, Request, Response>,
_connectors: &settings::Connectors, _connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { ) -> CustomResult<Vec<(String, String)>, 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(), headers::CONTENT_TYPE.to_string(),
self.get_content_type().to_string(), self.get_content_type().to_string(),
), ),
("X-GP-Version".to_string(), "2021-03-22".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::AUTHORIZATION.to_string(),
headers.append(&mut api_key); format!("Bearer {}", access_token.token),
Ok(headers) ),
])
} }
} }
@ -66,19 +75,16 @@ impl ConnectorCommon for Globalpay {
fn get_auth_header( fn get_auth_header(
&self, &self,
auth_type: &types::ConnectorAuthType, _auth_type: &types::ConnectorAuthType,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let auth: globalpay::GlobalpayAuthType = auth_type Ok(vec![])
.try_into()
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
} }
fn build_error_response( fn build_error_response(
&self, &self,
res: types::Response, res: types::Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> { ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: globalpay::GlobalpayErrorResponse = res let response: transformers::GlobalpayErrorResponse = res
.response .response
.parse_struct("Globalpay ErrorResponse") .parse_struct("Globalpay ErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -97,6 +103,94 @@ impl api::ConnectorAccessToken for Globalpay {}
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken> impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Globalpay for Globalpay
{ {
fn get_headers(
&self,
_req: &types::RefreshTokenRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, 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<String, errors::ConnectorError> {
Ok(format!("{}{}", self.base_url(connectors), "accesstoken"))
}
fn build_request(
&self,
req: &types::RefreshTokenRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, 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<Option<String>, errors::ConnectorError> {
let req_obj = GlobalpayRefreshTokenRequest::try_from(req)?;
let globalpay_req =
utils::Encode::<GlobalpayPaymentsRequest>::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<types::RefreshTokenRouterData, errors::ConnectorError> {
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<ErrorResponse, errors::ConnectorError> {
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 {} impl api::Payment for Globalpay {}

View File

@ -72,6 +72,14 @@ pub struct GlobalpayPaymentsRequest {
pub user_reference: Option<String>, pub user_reference: Option<String>,
} }
#[derive(Debug, Serialize)]
pub struct GlobalpayRefreshTokenRequest {
pub app_id: String,
pub nonce: String,
pub secret: String,
pub grant_type: String,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct CurrencyConversion { pub struct CurrencyConversion {
/// A unique identifier generated by Global Payments to identify the currency conversion. It /// A unique identifier generated by Global Payments to identify the currency conversion. It

View File

@ -68,6 +68,18 @@ pub struct Action {
pub action_type: Option<String>, pub action_type: Option<String>,
} }
#[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. /// Information relating to a currency conversion.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct CurrencyConversion { pub struct CurrencyConversion {

View File

@ -1,8 +1,11 @@
use common_utils::crypto::{self, GenerateDigest};
use error_stack::ResultExt;
use rand::distributions::DistString;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{ use super::{
requests::{self, GlobalpayPaymentsRequest}, requests::{self, GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest},
response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse}, response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse, GlobalpayRefreshTokenResponse},
}; };
use crate::{ use crate::{
connector::utils::{self, CardData, PaymentsRequestData}, connector::utils::{self, CardData, PaymentsRequestData},
@ -69,21 +72,60 @@ impl TryFrom<&types::PaymentsCancelRouterData> for GlobalpayPaymentsRequest {
} }
pub struct GlobalpayAuthType { pub struct GlobalpayAuthType {
pub api_key: String, pub app_id: String,
pub key: String,
} }
impl TryFrom<&types::ConnectorAuthType> for GlobalpayAuthType { impl TryFrom<&types::ConnectorAuthType> for GlobalpayAuthType {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type { match auth_type {
types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
api_key: api_key.to_string(), app_id: key1.to_string(),
key: api_key.to_string(),
}), }),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
} }
} }
} }
impl TryFrom<GlobalpayRefreshTokenResponse> for types::AccessToken {
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(item: GlobalpayRefreshTokenResponse) -> Result<Self, Self::Error> {
Ok(Self {
token: item.token,
expires: item.seconds_to_expire,
})
}
}
impl TryFrom<&types::RefreshTokenRouterData> for GlobalpayRefreshTokenRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::RefreshTokenRouterData) -> Result<Self, Self::Error> {
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<GlobalpayPaymentStatus> for enums::AttemptStatus { impl From<GlobalpayPaymentStatus> for enums::AttemptStatus {
fn from(item: GlobalpayPaymentStatus) -> Self { fn from(item: GlobalpayPaymentStatus) -> Self {
match item { match item {
@ -134,6 +176,24 @@ impl<F, T>
} }
} }
impl<F, T>
TryFrom<types::ResponseRouterData<F, GlobalpayRefreshTokenResponse, T, types::AccessToken>>
for types::RouterData<F, T, types::AccessToken>
{
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(
item: types::ResponseRouterData<F, GlobalpayRefreshTokenResponse, T, types::AccessToken>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::AccessToken {
token: item.response.token,
expires: item.response.seconds_to_expire,
}),
..item.data
})
}
}
impl<F> TryFrom<&types::RefundsRouterData<F>> for requests::GlobalpayRefundRequest { impl<F> TryFrom<&types::RefundsRouterData<F>> for requests::GlobalpayRefundRequest {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {

View File

@ -59,11 +59,15 @@ pub type PaymentsSessionType =
dyn services::ConnectorIntegration<api::Session, PaymentsSessionData, PaymentsResponseData>; dyn services::ConnectorIntegration<api::Session, PaymentsSessionData, PaymentsResponseData>;
pub type PaymentsVoidType = pub type PaymentsVoidType =
dyn services::ConnectorIntegration<api::Void, PaymentsCancelData, PaymentsResponseData>; dyn services::ConnectorIntegration<api::Void, PaymentsCancelData, PaymentsResponseData>;
pub type RefundExecuteType = pub type RefundExecuteType =
dyn services::ConnectorIntegration<api::Execute, RefundsData, RefundsResponseData>; dyn services::ConnectorIntegration<api::Execute, RefundsData, RefundsResponseData>;
pub type RefundSyncType = pub type RefundSyncType =
dyn services::ConnectorIntegration<api::RSync, RefundsData, RefundsResponseData>; dyn services::ConnectorIntegration<api::RSync, RefundsData, RefundsResponseData>;
pub type RefreshTokenType =
dyn services::ConnectorIntegration<api::AccessTokenAuth, AccessTokenRequestData, AccessToken>;
pub type VerifyRouterData = RouterData<api::Verify, VerifyRequestData, PaymentsResponseData>; pub type VerifyRouterData = RouterData<api::Verify, VerifyRequestData, PaymentsResponseData>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]