mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(connector): add auth_token_refresh for payu and some quick bug fixes (#426)
Co-authored-by: Narayan Bhat <narayan.bhat@juspay.in> Co-authored-by: samraat bansal <samraat.bansal@samraat.bansal-MacBookPro> Co-authored-by: Jagan Elavarasan <jaganelavarasan@gmail.com>
This commit is contained in:
@ -19,7 +19,7 @@ use crate::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
},
|
||||
utils::{self, BytesExt},
|
||||
utils::{self, BytesExt, OptionExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -633,10 +633,17 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let refund_id = req
|
||||
.response
|
||||
.clone()
|
||||
.ok()
|
||||
.get_required_value("response")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?
|
||||
.connector_refund_id;
|
||||
Ok(format!(
|
||||
"{}tss/v2/transactions/{}",
|
||||
self.base_url(connectors),
|
||||
req.request.connector_transaction_id
|
||||
refund_id
|
||||
))
|
||||
}
|
||||
fn build_request(
|
||||
|
||||
@ -197,7 +197,7 @@ pub struct FiservPaymentsResponse {
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayResponse {
|
||||
gateway_transaction_id: String,
|
||||
gateway_transaction_id: Option<String>,
|
||||
transaction_state: FiservPaymentStatus,
|
||||
transaction_processing_details: TransactionProcessingDetails,
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ use crate::{
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
ErrorResponse,
|
||||
},
|
||||
utils::{self, BytesExt},
|
||||
utils::{self, BytesExt, OptionExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -633,10 +633,17 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let refund_id = req
|
||||
.response
|
||||
.clone()
|
||||
.ok()
|
||||
.get_required_value("response")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?
|
||||
.connector_refund_id;
|
||||
Ok(format!(
|
||||
"{}transactions/{}",
|
||||
self.base_url(connectors),
|
||||
req.request.connector_transaction_id
|
||||
refund_id
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,8 @@ use crate::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
headers, logger, services,
|
||||
headers, logger,
|
||||
services::{self, ConnectorIntegration},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
@ -25,20 +26,29 @@ pub struct Payu;
|
||||
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Payu
|
||||
where
|
||||
Self: services::ConnectorIntegration<Flow, Request, Response>,
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||
{
|
||||
fn build_headers(
|
||||
&self,
|
||||
req: &types::RouterData<Flow, Request, Response>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let mut header = vec![(
|
||||
let mut headers = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
|
||||
self.get_content_type().to_string(),
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
Ok(header)
|
||||
let access_token = req
|
||||
.access_token
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
|
||||
let auth_header = (
|
||||
headers::AUTHORIZATION.to_string(),
|
||||
format!("Bearer {}", access_token.token),
|
||||
);
|
||||
|
||||
headers.push(auth_header);
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,10 +74,12 @@ impl ConnectorCommon for Payu {
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
|
||||
}
|
||||
|
||||
fn build_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
logger::debug!(payu_error_response=?res);
|
||||
let response: payu::PayuErrorResponse = res
|
||||
.response
|
||||
.parse_struct("Payu ErrorResponse")
|
||||
@ -85,23 +97,15 @@ impl ConnectorCommon for Payu {
|
||||
impl api::Payment for Payu {}
|
||||
|
||||
impl api::PreVerify for Payu {}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Verify,
|
||||
types::VerifyRequestData,
|
||||
types::PaymentsResponseData,
|
||||
> for Payu
|
||||
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
|
||||
for Payu
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentVoid for Payu {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Void,
|
||||
types::PaymentsCancelData,
|
||||
types::PaymentsResponseData,
|
||||
> for Payu
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Payu
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
@ -124,7 +128,7 @@ impl
|
||||
Ok(format!(
|
||||
"{}{}{}",
|
||||
self.base_url(connectors),
|
||||
"v2_1/orders/",
|
||||
"api/v2_1/orders/",
|
||||
connector_payment_id
|
||||
))
|
||||
}
|
||||
@ -167,18 +171,106 @@ impl
|
||||
|
||||
impl api::ConnectorAccessToken for Payu {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::AccessTokenAuth,
|
||||
types::AccessTokenRequestData,
|
||||
types::AccessToken,
|
||||
> for Payu
|
||||
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
|
||||
for Payu
|
||||
{
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::RefreshTokenRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}{}",
|
||||
self.base_url(connectors),
|
||||
"pl/standard/user/oauth/authorize"
|
||||
))
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
"application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
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(),
|
||||
)])
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::RefreshTokenRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let payu_req = utils::Encode::<payu::PayuAuthUpdateRequest>::convert_and_url_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
logger::debug!(payu_access_token_request=?payu_req);
|
||||
Ok(Some(payu_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefreshTokenRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let req = Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.headers(types::RefreshTokenType::get_headers(self, req, connectors)?)
|
||||
.url(&types::RefreshTokenType::get_url(self, req, connectors)?)
|
||||
.body(types::RefreshTokenType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
);
|
||||
|
||||
logger::debug!(payu_access_token_request=?req);
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefreshTokenRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::RefreshTokenRouterData, errors::ConnectorError> {
|
||||
logger::debug!(access_token_response=?res);
|
||||
let response: payu::PayuAuthUpdateResponse = res
|
||||
.response
|
||||
.parse_struct("payu PayuAuthUpdateResponse")
|
||||
.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> {
|
||||
logger::debug!(access_token_error_response=?res);
|
||||
let response: payu::PayuAccessTokenErrorResponse = res
|
||||
.response
|
||||
.parse_struct("Payu AccessTokenErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response.error,
|
||||
message: response.error_description,
|
||||
reason: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentSync for Payu {}
|
||||
impl
|
||||
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Payu
|
||||
{
|
||||
fn get_headers(
|
||||
@ -206,7 +298,7 @@ impl
|
||||
Ok(format!(
|
||||
"{}{}{}",
|
||||
self.base_url(connectors),
|
||||
"v2_1/orders/",
|
||||
"api/v2_1/orders/",
|
||||
connector_payment_id
|
||||
))
|
||||
}
|
||||
@ -253,12 +345,8 @@ impl
|
||||
}
|
||||
|
||||
impl api::PaymentCapture for Payu {}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Capture,
|
||||
types::PaymentsCaptureData,
|
||||
types::PaymentsResponseData,
|
||||
> for Payu
|
||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||
for Payu
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
@ -280,7 +368,7 @@ impl
|
||||
Ok(format!(
|
||||
"{}{}{}{}",
|
||||
self.base_url(connectors),
|
||||
"v2_1/orders/",
|
||||
"api/v2_1/orders/",
|
||||
req.request.connector_transaction_id,
|
||||
"/status"
|
||||
))
|
||||
@ -343,24 +431,16 @@ impl
|
||||
|
||||
impl api::PaymentSession for Payu {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Session,
|
||||
types::PaymentsSessionData,
|
||||
types::PaymentsResponseData,
|
||||
> for Payu
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Payu
|
||||
{
|
||||
//TODO: implement sessions flow
|
||||
}
|
||||
|
||||
impl api::PaymentAuthorize for Payu {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Authorize,
|
||||
types::PaymentsAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
> for Payu
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
for Payu
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
@ -379,7 +459,11 @@ impl
|
||||
_req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}{}", self.base_url(connectors), "v2_1/orders"))
|
||||
Ok(format!(
|
||||
"{}{}",
|
||||
self.base_url(connectors),
|
||||
"api/v2_1/orders"
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -447,9 +531,7 @@ impl api::Refund for Payu {}
|
||||
impl api::RefundExecute for Payu {}
|
||||
impl api::RefundSync for Payu {}
|
||||
|
||||
impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
||||
for Payu
|
||||
{
|
||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Payu {
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
@ -470,7 +552,7 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
|
||||
Ok(format!(
|
||||
"{}{}{}{}",
|
||||
self.base_url(connectors),
|
||||
"v2_1/orders/",
|
||||
"api/v2_1/orders/",
|
||||
req.request.connector_transaction_id,
|
||||
"/refund"
|
||||
))
|
||||
@ -530,9 +612,7 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
|
||||
}
|
||||
}
|
||||
|
||||
impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
|
||||
for Payu
|
||||
{
|
||||
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Payu {
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
@ -553,7 +633,7 @@ impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::Refun
|
||||
Ok(format!(
|
||||
"{}{}{}{}",
|
||||
self.base_url(connectors),
|
||||
"v2_1/orders/",
|
||||
"api/v2_1/orders/",
|
||||
req.request.connector_transaction_id,
|
||||
"/refunds"
|
||||
))
|
||||
|
||||
@ -3,6 +3,7 @@ use error_stack::{IntoReport, ResultExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::AccessTokenRequestInfo,
|
||||
consts,
|
||||
core::errors,
|
||||
pii::{self, Secret},
|
||||
@ -268,6 +269,48 @@ impl<F, T>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
pub struct PayuAuthUpdateRequest {
|
||||
grant_type: String,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::RefreshTokenRouterData> for PayuAuthUpdateRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefreshTokenRouterData) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
grant_type: "client_credentials".to_string(),
|
||||
client_id: item.get_request_id()?,
|
||||
client_secret: item.request.app_id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Default, Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct PayuAuthUpdateResponse {
|
||||
pub access_token: String,
|
||||
pub token_type: String,
|
||||
pub expires_in: i64,
|
||||
pub grant_type: String,
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<types::ResponseRouterData<F, PayuAuthUpdateResponse, T, types::AccessToken>>
|
||||
for types::RouterData<F, T, types::AccessToken>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, PayuAuthUpdateResponse, T, types::AccessToken>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
response: Ok(types::AccessToken {
|
||||
token: item.response.access_token,
|
||||
expires: item.response.expires_in,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PayuPaymentsCancelResponse {
|
||||
@ -456,9 +499,9 @@ impl<F> TryFrom<&types::RefundsRouterData<F>> for PayuRefundRequest {
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
refund: PayuRefundRequestData {
|
||||
description: item.description.clone().ok_or(
|
||||
description: item.request.reason.clone().ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "item.description".to_string(),
|
||||
field_name: "item.request.reason".to_string(),
|
||||
},
|
||||
)?,
|
||||
amount: None,
|
||||
@ -474,6 +517,7 @@ impl<F> TryFrom<&types::RefundsRouterData<F>> for PayuRefundRequest {
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum RefundStatus {
|
||||
Finalized,
|
||||
Completed,
|
||||
Canceled,
|
||||
#[default]
|
||||
Pending,
|
||||
@ -482,7 +526,7 @@ pub enum RefundStatus {
|
||||
impl From<RefundStatus> for enums::RefundStatus {
|
||||
fn from(item: RefundStatus) -> Self {
|
||||
match item {
|
||||
RefundStatus::Finalized => Self::Success,
|
||||
RefundStatus::Finalized | RefundStatus::Completed => Self::Success,
|
||||
RefundStatus::Canceled => Self::Failure,
|
||||
RefundStatus::Pending => Self::Pending,
|
||||
}
|
||||
@ -563,3 +607,9 @@ pub struct PayuErrorData {
|
||||
pub struct PayuErrorResponse {
|
||||
pub status: PayuErrorData,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct PayuAccessTokenErrorResponse {
|
||||
pub error: String,
|
||||
pub error_description: String,
|
||||
}
|
||||
|
||||
@ -18,6 +18,20 @@ pub fn missing_field_err(
|
||||
}
|
||||
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
|
||||
pub trait AccessTokenRequestInfo {
|
||||
fn get_request_id(&self) -> Result<String, Error>;
|
||||
}
|
||||
|
||||
impl AccessTokenRequestInfo for types::RefreshTokenRouterData {
|
||||
fn get_request_id(&self) -> Result<String, Error> {
|
||||
self.request
|
||||
.id
|
||||
.clone()
|
||||
.ok_or_else(missing_field_err("request.id"))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PaymentsRequestData {
|
||||
fn get_attempt_id(&self) -> Result<String, Error>;
|
||||
fn get_billing(&self) -> Result<&api::Address, Error>;
|
||||
|
||||
Reference in New Issue
Block a user