mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +08:00
feat(connector): [Fiserv] add Refunds, Cancel and Wallets flow along with Unit Tests (#593)
Co-authored-by: samraat bansal <samraat.bansal@samraat.bansal-MacBookPro> Co-authored-by: Nishant Joshi <nishant.joshi@juspay.in> Co-authored-by: Jagan <jaganelavarasan@gmail.com> Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
@ -155,10 +155,7 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for AirwallexPaymentsCaptureRequ
|
||||
Ok(Self {
|
||||
request_id: Uuid::new_v4().to_string(),
|
||||
amount: match item.request.amount_to_capture {
|
||||
Some(_a) => Some(utils::to_currency_base_unit(
|
||||
item.request.amount,
|
||||
item.request.currency,
|
||||
)?),
|
||||
Some(a) => Some(utils::to_currency_base_unit(a, item.request.currency)?),
|
||||
_ => None,
|
||||
},
|
||||
})
|
||||
|
||||
@ -16,10 +16,11 @@ use crate::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
headers, services,
|
||||
headers, logger,
|
||||
services::{self, api::ConnectorIntegration},
|
||||
types::{
|
||||
self,
|
||||
api::{self},
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
},
|
||||
utils::{self, BytesExt},
|
||||
};
|
||||
@ -49,7 +50,44 @@ impl Fiserv {
|
||||
}
|
||||
}
|
||||
|
||||
impl api::ConnectorCommon for Fiserv {
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Fiserv
|
||||
where
|
||||
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 timestamp = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000;
|
||||
let auth: fiserv::FiservAuthType =
|
||||
fiserv::FiservAuthType::try_from(&req.connector_auth_type)?;
|
||||
let mut auth_header = self.get_auth_header(&req.connector_auth_type)?;
|
||||
|
||||
let fiserv_req = self
|
||||
.get_request_body(req)?
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
let client_request_id = Uuid::new_v4().to_string();
|
||||
let hmac = self
|
||||
.generate_authorization_signature(auth, &client_request_id, &fiserv_req, timestamp)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let mut headers = vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
|
||||
),
|
||||
("Client-Request-Id".to_string(), client_request_id),
|
||||
("Auth-Token-Type".to_string(), "HMAC".to_string()),
|
||||
(headers::TIMESTAMP.to_string(), timestamp.to_string()),
|
||||
(headers::AUTHORIZATION.to_string(), hmac),
|
||||
];
|
||||
headers.append(&mut auth_header);
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorCommon for Fiserv {
|
||||
fn id(&self) -> &'static str {
|
||||
"fiserv"
|
||||
}
|
||||
@ -61,16 +99,54 @@ impl api::ConnectorCommon for Fiserv {
|
||||
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
||||
connectors.fiserv.base_url.as_ref()
|
||||
}
|
||||
fn get_auth_header(
|
||||
&self,
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let auth: fiserv::FiservAuthType = auth_type
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![(headers::API_KEY.to_string(), auth.api_key)])
|
||||
}
|
||||
fn build_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: fiserv::ErrorResponse = res
|
||||
.response
|
||||
.parse_struct("Fiserv ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let fiserv::ErrorResponse { error, details } = response;
|
||||
|
||||
Ok(error
|
||||
.or(details)
|
||||
.and_then(|error_details| {
|
||||
error_details
|
||||
.first()
|
||||
.map(|first_error| types::ErrorResponse {
|
||||
code: first_error
|
||||
.code
|
||||
.to_owned()
|
||||
.unwrap_or(consts::NO_ERROR_CODE.to_string()),
|
||||
message: first_error.message.to_owned(),
|
||||
reason: first_error.field.to_owned(),
|
||||
status_code: res.status_code,
|
||||
})
|
||||
})
|
||||
.unwrap_or(types::ErrorResponse {
|
||||
code: consts::NO_ERROR_CODE.to_string(),
|
||||
message: consts::NO_ERROR_MESSAGE.to_string(),
|
||||
reason: None,
|
||||
status_code: res.status_code,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl api::ConnectorAccessToken for Fiserv {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::AccessTokenAuth,
|
||||
types::AccessTokenRequestData,
|
||||
types::AccessToken,
|
||||
> for Fiserv
|
||||
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
|
||||
for Fiserv
|
||||
{
|
||||
// Not Implemented (R)
|
||||
}
|
||||
@ -80,73 +156,188 @@ impl api::Payment for Fiserv {}
|
||||
impl api::PreVerify for Fiserv {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Verify,
|
||||
types::VerifyRequestData,
|
||||
types::PaymentsResponseData,
|
||||
> for Fiserv
|
||||
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentVoid for Fiserv {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Void,
|
||||
types::PaymentsCancelData,
|
||||
types::PaymentsResponseData,
|
||||
> for Fiserv
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
"application/json"
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
//The docs has this url wrong, cancels is the working endpoint
|
||||
"{}ch/payments/v1/cancels",
|
||||
connectors.fiserv.base_url
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let connector_req = fiserv::FiservCancelRequest::try_from(req)?;
|
||||
let fiserv_req =
|
||||
utils::Encode::<fiserv::FiservCancelRequest>::encode_to_string_of_json(&connector_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(fiserv_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
|
||||
.body(types::PaymentsVoidType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
);
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCancelRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
|
||||
let response: fiserv::FiservPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Fiserv PaymentResponse")
|
||||
.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<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentSync for Fiserv {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl
|
||||
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
"application/json"
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}ch/payments/v1/transaction-inquiry",
|
||||
connectors.fiserv.base_url
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let connector_req = fiserv::FiservSyncRequest::try_from(req)?;
|
||||
let fiserv_req =
|
||||
utils::Encode::<fiserv::FiservSyncRequest>::encode_to_string_of_json(&connector_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(fiserv_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
|
||||
.body(types::PaymentsSyncType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
);
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSyncRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
let response: fiserv::FiservSyncResponse = res
|
||||
.response
|
||||
.parse_struct("Fiserv PaymentSyncResponse")
|
||||
.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<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentCapture for Fiserv {}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Capture,
|
||||
types::PaymentsCaptureData,
|
||||
types::PaymentsResponseData,
|
||||
> for Fiserv
|
||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let timestamp = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000;
|
||||
let auth: fiserv::FiservAuthType =
|
||||
fiserv::FiservAuthType::try_from(&req.connector_auth_type)?;
|
||||
let api_key = auth.api_key.clone();
|
||||
|
||||
let fiserv_req = self
|
||||
.get_request_body(req)?
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let client_request_id = Uuid::new_v4().to_string();
|
||||
let hmac = self
|
||||
.generate_authorization_signature(auth, &client_request_id, &fiserv_req, timestamp)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let headers = vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
|
||||
),
|
||||
("Client-Request-Id".to_string(), client_request_id),
|
||||
("Auth-Token-Type".to_string(), "HMAC".to_string()),
|
||||
("Api-Key".to_string(), api_key),
|
||||
("Timestamp".to_string(), timestamp.to_string()),
|
||||
("Authorization".to_string(), hmac),
|
||||
];
|
||||
Ok(headers)
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
@ -215,86 +406,29 @@ impl
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: fiserv::ErrorResponse = res
|
||||
.response
|
||||
.parse_struct("Fiserv ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let fiserv::ErrorResponse { error, details } = response;
|
||||
|
||||
let message = match (error, details) {
|
||||
(Some(err), _) => err
|
||||
.iter()
|
||||
.map(|v| v.message.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
(None, Some(err_details)) => err_details
|
||||
.iter()
|
||||
.map(|v| v.message.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
(None, None) => consts::NO_ERROR_MESSAGE.to_string(),
|
||||
};
|
||||
|
||||
Ok(types::ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: consts::NO_ERROR_CODE.to_string(),
|
||||
message,
|
||||
reason: None,
|
||||
})
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentSession for Fiserv {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Session,
|
||||
types::PaymentsSessionData,
|
||||
types::PaymentsResponseData,
|
||||
> for Fiserv
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentAuthorize for Fiserv {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Authorize,
|
||||
types::PaymentsAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
> for Fiserv
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let timestamp = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000;
|
||||
let auth: fiserv::FiservAuthType =
|
||||
fiserv::FiservAuthType::try_from(&req.connector_auth_type)?;
|
||||
let api_key = auth.api_key.clone();
|
||||
|
||||
let fiserv_req = self
|
||||
.get_request_body(req)?
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let client_request_id = Uuid::new_v4().to_string();
|
||||
let hmac = self
|
||||
.generate_authorization_signature(auth, &client_request_id, &fiserv_req, timestamp)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let headers = vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
|
||||
),
|
||||
("Client-Request-Id".to_string(), client_request_id),
|
||||
("Auth-Token-Type".to_string(), "HMAC".to_string()),
|
||||
("Api-Key".to_string(), api_key),
|
||||
("Timestamp".to_string(), timestamp.to_string()),
|
||||
("Authorization".to_string(), hmac),
|
||||
];
|
||||
Ok(headers)
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
@ -367,32 +501,7 @@ impl
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: fiserv::ErrorResponse = res
|
||||
.response
|
||||
.parse_struct("Fiserv ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let fiserv::ErrorResponse { error, details } = response;
|
||||
|
||||
let message = match (error, details) {
|
||||
(Some(err), _) => err
|
||||
.iter()
|
||||
.map(|v| v.message.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
(None, Some(err_details)) => err_details
|
||||
.iter()
|
||||
.map(|v| v.message.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
(None, None) => consts::NO_ERROR_MESSAGE.to_string(),
|
||||
};
|
||||
Ok(types::ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: consts::NO_ERROR_CODE.to_string(),
|
||||
message,
|
||||
reason: None,
|
||||
})
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,15 +510,157 @@ impl api::RefundExecute for Fiserv {}
|
||||
impl api::RefundSync for Fiserv {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Fiserv {
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
"application/json"
|
||||
}
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}ch/payments/v1/refunds",
|
||||
connectors.fiserv.base_url
|
||||
))
|
||||
}
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let connector_req = fiserv::FiservRefundRequest::try_from(req)?;
|
||||
let fiserv_req =
|
||||
utils::Encode::<fiserv::FiservRefundRequest>::encode_to_string_of_json(&connector_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(fiserv_req))
|
||||
}
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundExecuteType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::RefundExecuteType::get_request_body(self, req)?)
|
||||
.build();
|
||||
Ok(Some(request))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefundsRouterData<api::Execute>,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
|
||||
logger::debug!(target: "router::connector::fiserv", response=?res);
|
||||
let response: fiserv::RefundResponse =
|
||||
res.response
|
||||
.parse_struct("fiserv RefundResponse")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
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<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
|
||||
for Fiserv
|
||||
{
|
||||
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Fiserv {
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
"application/json"
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}ch/payments/v1/transaction-inquiry",
|
||||
connectors.fiserv.base_url
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let connector_req = fiserv::FiservSyncRequest::try_from(req)?;
|
||||
let fiserv_req =
|
||||
utils::Encode::<fiserv::FiservSyncRequest>::encode_to_string_of_json(&connector_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(fiserv_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
|
||||
.body(types::RefundSyncType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
);
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefundSyncRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
|
||||
logger::debug!(target: "router::connector::fiserv", response=?res);
|
||||
|
||||
let response: fiserv::FiservSyncResponse = res
|
||||
.response
|
||||
.parse_struct("Fiserv Refund Response")
|
||||
.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<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
@ -3,12 +3,13 @@ use error_stack::ResultExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::{self, PaymentsCancelRequestData, PaymentsSyncRequestData, RouterData},
|
||||
core::errors,
|
||||
pii::{self, Secret},
|
||||
types::{self, api, storage::enums},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FiservPaymentsRequest {
|
||||
amount: Amount,
|
||||
@ -18,11 +19,18 @@ pub struct FiservPaymentsRequest {
|
||||
transaction_interaction: TransactionInteraction,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
source_type: String,
|
||||
card: CardData,
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(tag = "sourceType")]
|
||||
pub enum Source {
|
||||
PaymentCard {
|
||||
card: CardData,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
GooglePay {
|
||||
data: String,
|
||||
signature: String,
|
||||
version: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
@ -34,90 +42,119 @@ pub struct CardData {
|
||||
security_code: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GooglePayToken {
|
||||
signature: String,
|
||||
signed_message: String,
|
||||
protocol_version: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct Amount {
|
||||
total: i64,
|
||||
#[serde(serialize_with = "utils::str_to_f32")]
|
||||
total: String,
|
||||
currency: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionDetails {
|
||||
capture_flag: bool,
|
||||
capture_flag: Option<bool>,
|
||||
reversal_reason_code: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MerchantDetails {
|
||||
merchant_id: String,
|
||||
terminal_id: String,
|
||||
terminal_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionInteraction {
|
||||
origin: String,
|
||||
eci_indicator: String,
|
||||
pos_condition_code: String,
|
||||
origin: TransactionInteractionOrigin,
|
||||
eci_indicator: TransactionInteractionEciIndicator,
|
||||
pos_condition_code: TransactionInteractionPosConditionCode,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum TransactionInteractionOrigin {
|
||||
#[default]
|
||||
Ecom,
|
||||
}
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum TransactionInteractionEciIndicator {
|
||||
#[default]
|
||||
ChannelEncrypted,
|
||||
}
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum TransactionInteractionPosConditionCode {
|
||||
#[default]
|
||||
CardNotPresentEcom,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for FiservPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
match item.request.payment_method_data {
|
||||
api::PaymentMethodData::Card(ref ccard) => {
|
||||
let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
|
||||
let amount = Amount {
|
||||
total: item.request.amount,
|
||||
currency: item.request.currency.to_string(),
|
||||
};
|
||||
let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
|
||||
let amount = Amount {
|
||||
total: utils::to_currency_base_unit(item.request.amount, item.request.currency)?,
|
||||
currency: item.request.currency.to_string(),
|
||||
};
|
||||
let transaction_details = TransactionDetails {
|
||||
capture_flag: Some(matches!(
|
||||
item.request.capture_method,
|
||||
Some(enums::CaptureMethod::Automatic) | None
|
||||
)),
|
||||
reversal_reason_code: None,
|
||||
};
|
||||
let metadata = item.get_connector_meta()?;
|
||||
let session: SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
let merchant_details = MerchantDetails {
|
||||
merchant_id: auth.merchant_account,
|
||||
terminal_id: Some(session.terminal_id),
|
||||
};
|
||||
|
||||
let transaction_interaction = TransactionInteraction {
|
||||
//Payment is being made in online mode, card not present
|
||||
origin: TransactionInteractionOrigin::Ecom,
|
||||
// transaction encryption such as SSL/TLS, but authentication was not performed
|
||||
eci_indicator: TransactionInteractionEciIndicator::ChannelEncrypted,
|
||||
//card not present in online transaction
|
||||
pos_condition_code: TransactionInteractionPosConditionCode::CardNotPresentEcom,
|
||||
};
|
||||
let source = match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(ref ccard) => {
|
||||
let card = CardData {
|
||||
card_data: ccard.card_number.clone(),
|
||||
card_data: ccard
|
||||
.card_number
|
||||
.clone()
|
||||
.map(|card| card.split_whitespace().collect()),
|
||||
expiration_month: ccard.card_exp_month.clone(),
|
||||
expiration_year: ccard.card_exp_year.clone(),
|
||||
security_code: ccard.card_cvc.clone(),
|
||||
};
|
||||
let source = Source {
|
||||
source_type: "PaymentCard".to_string(),
|
||||
card,
|
||||
};
|
||||
let transaction_details = TransactionDetails {
|
||||
capture_flag: matches!(
|
||||
item.request.capture_method,
|
||||
Some(enums::CaptureMethod::Automatic) | None
|
||||
),
|
||||
};
|
||||
let metadata = item
|
||||
.connector_meta_data
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let session: SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
let merchant_details = MerchantDetails {
|
||||
merchant_id: auth.merchant_account,
|
||||
terminal_id: session.terminal_id,
|
||||
};
|
||||
|
||||
let transaction_interaction = TransactionInteraction {
|
||||
origin: "ECOM".to_string(), //Payment is being made in online mode, card not present
|
||||
eci_indicator: "CHANNEL_ENCRYPTED".to_string(), // transaction encryption such as SSL/TLS, but authentication was not performed
|
||||
pos_condition_code: "CARD_NOT_PRESENT_ECOM".to_string(), //card not present in online transaction
|
||||
};
|
||||
Ok(Self {
|
||||
amount,
|
||||
source,
|
||||
transaction_details,
|
||||
merchant_details,
|
||||
transaction_interaction,
|
||||
})
|
||||
Source::PaymentCard { card }
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented(
|
||||
"Payment Methods".to_string(),
|
||||
))?,
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
amount,
|
||||
source,
|
||||
transaction_details,
|
||||
merchant_details,
|
||||
transaction_interaction,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,6 +184,38 @@ impl TryFrom<&types::ConnectorAuthType> for FiservAuthType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FiservCancelRequest {
|
||||
transaction_details: TransactionDetails,
|
||||
merchant_details: MerchantDetails,
|
||||
reference_transaction_details: ReferenceTransactionDetails,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsCancelRouterData> for FiservCancelRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
|
||||
let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
|
||||
let metadata = item.get_connector_meta()?;
|
||||
let session: SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Self {
|
||||
merchant_details: MerchantDetails {
|
||||
merchant_id: auth.merchant_account,
|
||||
terminal_id: Some(session.terminal_id),
|
||||
},
|
||||
reference_transaction_details: ReferenceTransactionDetails {
|
||||
reference_transaction_id: item.request.connector_transaction_id.to_string(),
|
||||
},
|
||||
transaction_details: TransactionDetails {
|
||||
capture_flag: None,
|
||||
reversal_reason_code: Some(item.request.get_cancellation_reason()?),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ErrorResponse {
|
||||
@ -161,6 +230,7 @@ pub struct ErrorDetails {
|
||||
pub error_type: String,
|
||||
pub code: Option<String>,
|
||||
pub message: String,
|
||||
pub field: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
|
||||
@ -188,12 +258,31 @@ impl From<FiservPaymentStatus> for enums::AttemptStatus {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FiservPaymentStatus> for enums::RefundStatus {
|
||||
fn from(item: FiservPaymentStatus) -> Self {
|
||||
match item {
|
||||
FiservPaymentStatus::Succeeded
|
||||
| FiservPaymentStatus::Authorized
|
||||
| FiservPaymentStatus::Captured => Self::Success,
|
||||
FiservPaymentStatus::Declined | FiservPaymentStatus::Failed => Self::Failure,
|
||||
_ => Self::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FiservPaymentsResponse {
|
||||
gateway_response: GatewayResponse,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(transparent)]
|
||||
pub struct FiservSyncResponse {
|
||||
sync_responses: Vec<FiservPaymentsResponse>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayResponse {
|
||||
@ -220,7 +309,7 @@ impl<F, T>
|
||||
let gateway_resp = item.response.gateway_response;
|
||||
|
||||
Ok(Self {
|
||||
status: gateway_resp.transaction_state.into(),
|
||||
status: enums::AttemptStatus::from(gateway_resp.transaction_state),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
gateway_resp.transaction_processing_details.transaction_id,
|
||||
@ -234,6 +323,39 @@ impl<F, T>
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<types::ResponseRouterData<F, FiservSyncResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, FiservSyncResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let gateway_resp = match item.response.sync_responses.first() {
|
||||
Some(gateway_response) => gateway_response,
|
||||
_ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(
|
||||
gateway_resp.gateway_response.transaction_state.clone(),
|
||||
),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
gateway_resp
|
||||
.gateway_response
|
||||
.transaction_processing_details
|
||||
.transaction_id
|
||||
.clone(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FiservCaptureRequest {
|
||||
@ -266,19 +388,22 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for FiservCaptureRequest {
|
||||
let session: SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let amount = item
|
||||
.request
|
||||
.amount_to_capture
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let amount = match item.request.amount_to_capture {
|
||||
Some(a) => utils::to_currency_base_unit(a, item.request.currency)?,
|
||||
_ => utils::to_currency_base_unit(item.request.amount, item.request.currency)?,
|
||||
};
|
||||
Ok(Self {
|
||||
amount: Amount {
|
||||
total: amount,
|
||||
currency: item.request.currency.to_string(),
|
||||
},
|
||||
transaction_details: TransactionDetails { capture_flag: true },
|
||||
transaction_details: TransactionDetails {
|
||||
capture_flag: Some(true),
|
||||
reversal_reason_code: None,
|
||||
},
|
||||
merchant_details: MerchantDetails {
|
||||
merchant_id: auth.merchant_account,
|
||||
terminal_id: session.terminal_id,
|
||||
terminal_id: Some(session.terminal_id),
|
||||
},
|
||||
reference_transaction_details: ReferenceTransactionDetails {
|
||||
reference_transaction_id: item.request.connector_transaction_id.to_string(),
|
||||
@ -287,56 +412,143 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for FiservCaptureRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FiservSyncRequest {
|
||||
merchant_details: MerchantDetails,
|
||||
reference_transaction_details: ReferenceTransactionDetails,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsSyncRouterData> for FiservSyncRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
|
||||
let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
|
||||
Ok(Self {
|
||||
merchant_details: MerchantDetails {
|
||||
merchant_id: auth.merchant_account,
|
||||
terminal_id: None,
|
||||
},
|
||||
reference_transaction_details: ReferenceTransactionDetails {
|
||||
reference_transaction_id: item
|
||||
.request
|
||||
.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&types::RefundSyncRouterData> for FiservSyncRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefundSyncRouterData) -> Result<Self, Self::Error> {
|
||||
let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
|
||||
Ok(Self {
|
||||
merchant_details: MerchantDetails {
|
||||
merchant_id: auth.merchant_account,
|
||||
terminal_id: None,
|
||||
},
|
||||
reference_transaction_details: ReferenceTransactionDetails {
|
||||
reference_transaction_id: item
|
||||
.request
|
||||
.connector_refund_id
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
pub struct FiservRefundRequest {}
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FiservRefundRequest {
|
||||
amount: Amount,
|
||||
merchant_details: MerchantDetails,
|
||||
reference_transaction_details: ReferenceTransactionDetails,
|
||||
}
|
||||
|
||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for FiservRefundRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(_item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
|
||||
let metadata = item
|
||||
.connector_meta_data
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let session: SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Self {
|
||||
amount: Amount {
|
||||
total: utils::to_currency_base_unit(
|
||||
item.request.refund_amount,
|
||||
item.request.currency,
|
||||
)?,
|
||||
currency: item.request.currency.to_string(),
|
||||
},
|
||||
merchant_details: MerchantDetails {
|
||||
merchant_id: auth.merchant_account,
|
||||
terminal_id: Some(session.terminal_id),
|
||||
},
|
||||
reference_transaction_details: ReferenceTransactionDetails {
|
||||
reference_transaction_id: item.request.connector_transaction_id.to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
|
||||
pub enum RefundStatus {
|
||||
Succeeded,
|
||||
Failed,
|
||||
#[default]
|
||||
Processing,
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RefundResponse {
|
||||
gateway_response: GatewayResponse,
|
||||
}
|
||||
|
||||
impl From<RefundStatus> for enums::RefundStatus {
|
||||
fn from(item: RefundStatus) -> Self {
|
||||
match item {
|
||||
RefundStatus::Succeeded => Self::Success,
|
||||
RefundStatus::Failed => Self::Failure,
|
||||
RefundStatus::Processing => Self::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RefundResponse {}
|
||||
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
|
||||
for types::RefundsRouterData<api::Execute>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
_item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
|
||||
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
|
||||
Ok(Self {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: item
|
||||
.response
|
||||
.gateway_response
|
||||
.transaction_processing_details
|
||||
.transaction_id,
|
||||
refund_status: enums::RefundStatus::from(
|
||||
item.response.gateway_response.transaction_state,
|
||||
),
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::RSync, FiservSyncResponse>>
|
||||
for types::RefundsRouterData<api::RSync>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
_item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
|
||||
item: types::RefundsResponseRouterData<api::RSync, FiservSyncResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Err(errors::ConnectorError::NotImplemented("fiserv".to_string()).into())
|
||||
let gateway_resp = item
|
||||
.response
|
||||
.sync_responses
|
||||
.first()
|
||||
.ok_or(errors::ConnectorError::ResponseHandlingFailed)?;
|
||||
Ok(Self {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: gateway_resp
|
||||
.gateway_response
|
||||
.transaction_processing_details
|
||||
.transaction_id
|
||||
.clone(),
|
||||
refund_status: enums::RefundStatus::from(
|
||||
gateway_resp.gateway_response.transaction_state.clone(),
|
||||
),
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,11 +5,12 @@ use error_stack::{report, IntoReport, ResultExt};
|
||||
use masking::Secret;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::Serializer;
|
||||
|
||||
use crate::{
|
||||
core::errors::{self, CustomResult},
|
||||
pii::PeekInterface,
|
||||
types::{self, api, PaymentsCancelData},
|
||||
types::{self, api, PaymentsCancelData, ResponseId},
|
||||
utils::OptionExt,
|
||||
};
|
||||
|
||||
@ -142,17 +143,29 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData {
|
||||
|
||||
pub trait PaymentsSyncRequestData {
|
||||
fn is_auto_capture(&self) -> bool;
|
||||
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError>;
|
||||
}
|
||||
|
||||
impl PaymentsSyncRequestData for types::PaymentsSyncData {
|
||||
fn is_auto_capture(&self) -> bool {
|
||||
self.capture_method == Some(storage_models::enums::CaptureMethod::Automatic)
|
||||
}
|
||||
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError> {
|
||||
match self.connector_transaction_id.clone() {
|
||||
ResponseId::ConnectorTransactionId(txn_id) => Ok(txn_id),
|
||||
_ => Err(errors::ValidationError::IncorrectValueProvided {
|
||||
field_name: "connector_transaction_id",
|
||||
})
|
||||
.into_report()
|
||||
.attach_printable("Expected connector transaction ID not found"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PaymentsCancelRequestData {
|
||||
fn get_amount(&self) -> Result<i64, Error>;
|
||||
fn get_currency(&self) -> Result<storage_models::enums::Currency, Error>;
|
||||
fn get_cancellation_reason(&self) -> Result<String, Error>;
|
||||
}
|
||||
|
||||
impl PaymentsCancelRequestData for PaymentsCancelData {
|
||||
@ -162,6 +175,11 @@ impl PaymentsCancelRequestData for PaymentsCancelData {
|
||||
fn get_currency(&self) -> Result<storage_models::enums::Currency, Error> {
|
||||
self.currency.ok_or_else(missing_field_err("currency"))
|
||||
}
|
||||
fn get_cancellation_reason(&self) -> Result<String, Error> {
|
||||
self.cancellation_reason
|
||||
.clone()
|
||||
.ok_or_else(missing_field_err("cancellation_reason"))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RefundsRequestData {
|
||||
@ -355,3 +373,13 @@ pub fn to_currency_base_unit(
|
||||
_ => Ok((f64::from(amount_u32) / 100.0).to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn str_to_f32<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let float_value = value.parse::<f64>().map_err(|_| {
|
||||
serde::ser::Error::custom("Invalid string, cannot be converted to float value")
|
||||
})?;
|
||||
serializer.serialize_f64(float_value)
|
||||
}
|
||||
|
||||
@ -278,6 +278,8 @@ pub enum ConnectorError {
|
||||
WebhookResourceObjectNotFound,
|
||||
#[error("Invalid Date/time format")]
|
||||
InvalidDateFormat,
|
||||
#[error("Invalid Data format")]
|
||||
InvalidDataFormat { field_name: &'static str },
|
||||
#[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")]
|
||||
MismatchedPaymentData,
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ pub async fn construct_refund_router_data<'a, F>(
|
||||
// Does refund need shipping/billing address ?
|
||||
address: PaymentAddress::default(),
|
||||
auth_type: payment_attempt.authentication_type.unwrap_or_default(),
|
||||
connector_meta_data: None,
|
||||
connector_meta_data: merchant_connector_account.metadata,
|
||||
amount_captured: payment_intent.amount_captured,
|
||||
request: types::RefundsData {
|
||||
refund_id: refund.refund_id.clone(),
|
||||
|
||||
@ -42,14 +42,16 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
/// Header Constants
|
||||
pub mod headers {
|
||||
pub const X_API_KEY: &str = "X-API-KEY";
|
||||
pub const CONTENT_TYPE: &str = "Content-Type";
|
||||
pub const X_ROUTER: &str = "X-router";
|
||||
pub const AUTHORIZATION: &str = "Authorization";
|
||||
pub const ACCEPT: &str = "Accept";
|
||||
pub const X_API_VERSION: &str = "X-ApiVersion";
|
||||
pub const API_KEY: &str = "API-KEY";
|
||||
pub const AUTHORIZATION: &str = "Authorization";
|
||||
pub const CONTENT_TYPE: &str = "Content-Type";
|
||||
pub const DATE: &str = "Date";
|
||||
pub const TIMESTAMP: &str = "Timestamp";
|
||||
pub const X_API_KEY: &str = "X-API-KEY";
|
||||
pub const X_API_VERSION: &str = "X-ApiVersion";
|
||||
pub const X_MERCHANT_ID: &str = "X-Merchant-Id";
|
||||
pub const X_ROUTER: &str = "X-router";
|
||||
pub const X_LOGIN: &str = "X-Login";
|
||||
pub const X_TRANS_KEY: &str = "X-Trans-Key";
|
||||
pub const X_VERSION: &str = "X-Version";
|
||||
|
||||
Reference in New Issue
Block a user