mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(globalpay): implement access token creation (#408)
This commit is contained in:
@ -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)]
|
||||
mod crypto_tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
@ -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<Flow, Request, Response>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> 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(),
|
||||
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<Vec<(String, String)>, 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<ErrorResponse, errors::ConnectorError> {
|
||||
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<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
|
||||
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 {}
|
||||
|
||||
@ -72,6 +72,14 @@ pub struct GlobalpayPaymentsRequest {
|
||||
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)]
|
||||
pub struct CurrencyConversion {
|
||||
/// A unique identifier generated by Global Payments to identify the currency conversion. It
|
||||
|
||||
@ -68,6 +68,18 @@ pub struct Action {
|
||||
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.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CurrencyConversion {
|
||||
|
||||
@ -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<errors::ConnectorError>;
|
||||
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||
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<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 {
|
||||
fn from(item: GlobalpayPaymentStatus) -> Self {
|
||||
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 {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
|
||||
@ -59,11 +59,15 @@ pub type PaymentsSessionType =
|
||||
dyn services::ConnectorIntegration<api::Session, PaymentsSessionData, PaymentsResponseData>;
|
||||
pub type PaymentsVoidType =
|
||||
dyn services::ConnectorIntegration<api::Void, PaymentsCancelData, PaymentsResponseData>;
|
||||
|
||||
pub type RefundExecuteType =
|
||||
dyn services::ConnectorIntegration<api::Execute, RefundsData, RefundsResponseData>;
|
||||
pub type RefundSyncType =
|
||||
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>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
Reference in New Issue
Block a user