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:
SamraatBansal
2023-01-21 23:34:44 +05:30
committed by GitHub
parent beae6c474c
commit da6a026ef7
13 changed files with 396 additions and 133 deletions

View File

@ -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(

View File

@ -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,
}

View File

@ -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
))
}

View File

@ -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"
))

View File

@ -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,
}

View File

@ -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>;