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)]
|
#[cfg(test)]
|
||||||
mod crypto_tests {
|
mod crypto_tests {
|
||||||
#![allow(clippy::expect_used)]
|
#![allow(clippy::expect_used)]
|
||||||
|
|||||||
@ -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 {}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user