Files
Narayan Bhat 7068fbfbe2 refactor(merchant_id): create domain type for merchant_id (#5408)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
2024-07-24 13:48:25 +00:00

1080 lines
42 KiB
Rust

pub mod transformers;
use base64::Engine;
use common_utils::{
crypto,
errors::ReportSwitchExt,
ext_traits::ByteSliceExt,
request::RequestContent,
types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector},
};
use error_stack::{Report, ResultExt};
use masking::PeekInterface;
use transformers as trustpay;
use super::utils::{
self as connector_utils, collect_and_sort_values_by_removing_signature,
get_error_code_error_message_based_on_priority, ConnectorErrorType, ConnectorErrorTypeMapping,
PaymentsPreProcessingData,
};
use crate::{
configs::settings,
consts,
core::{
errors::{self, CustomResult},
payments,
},
events::connector_api_logs::ConnectorEvent,
headers, logger,
services::{
self,
request::{self, Mask},
ConnectorIntegration, ConnectorValidation,
},
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
ErrorResponse, Response,
},
utils::{self, BytesExt},
};
#[derive(Clone)]
pub struct Trustpay {
amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
}
impl Trustpay {
pub fn new() -> &'static Self {
&Self {
amount_converter: &StringMajorUnitForConnector,
}
}
}
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Trustpay
where
Self: ConnectorIntegration<Flow, Request, Response>,
{
fn build_headers(
&self,
req: &types::RouterData<Flow, Request, Response>,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
match req.payment_method {
diesel_models::enums::PaymentMethod::BankRedirect => {
let token = req
.access_token
.clone()
.ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![
(
headers::CONTENT_TYPE.to_string(),
"application/json".to_owned().into(),
),
(
headers::AUTHORIZATION.to_string(),
format!("Bearer {}", token.token.peek()).into_masked(),
),
])
}
_ => {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
self.get_content_type().to_string().into(),
)];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
header.append(&mut api_key);
Ok(header)
}
}
}
}
impl ConnectorCommon for Trustpay {
fn id(&self) -> &'static str {
"trustpay"
}
fn get_currency_unit(&self) -> api::CurrencyUnit {
api::CurrencyUnit::Base
}
fn common_get_content_type(&self) -> &'static str {
"application/x-www-form-urlencoded"
}
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
connectors.trustpay.base_url.as_ref()
}
fn get_auth_header(
&self,
auth_type: &types::ConnectorAuthType,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
let auth = trustpay::TrustpayAuthType::try_from(auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(
headers::X_API_KEY.to_string(),
auth.api_key.into_masked(),
)])
}
fn build_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: Result<
trustpay::TrustpayErrorResponse,
Report<common_utils::errors::ParsingError>,
> = res.response.parse_struct("trustpay ErrorResponse");
match response {
Ok(response_data) => {
event_builder.map(|i| i.set_error_response_body(&response_data));
router_env::logger::info!(connector_response=?response_data);
let error_list = response_data.errors.clone().unwrap_or_default();
let option_error_code_message = get_error_code_error_message_based_on_priority(
self.clone(),
error_list.into_iter().map(|errors| errors.into()).collect(),
);
let reason = response_data.errors.map(|errors| {
errors
.iter()
.map(|error| error.description.clone())
.collect::<Vec<String>>()
.join(" & ")
});
Ok(ErrorResponse {
status_code: res.status_code,
code: option_error_code_message
.clone()
.map(|error_code_message| error_code_message.error_code)
.unwrap_or(consts::NO_ERROR_CODE.to_string()),
// message vary for the same code, so relying on code alone as it is unique
message: option_error_code_message
.map(|error_code_message| error_code_message.error_code)
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
reason: reason
.or(response_data.description)
.or(response_data.payment_description),
attempt_status: None,
connector_transaction_id: response_data.instance_id,
})
}
Err(error_msg) => {
event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
logger::error!(deserialization_error =? error_msg);
utils::handle_json_response_deserialization_failure(res, "trustpay")
}
}
}
}
impl ConnectorValidation for Trustpay {}
impl api::Payment for Trustpay {}
impl api::PaymentToken for Trustpay {}
impl
ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> for Trustpay
{
// Not Implemented (R)
}
impl api::MandateSetup for Trustpay {}
impl
ConnectorIntegration<
api::SetupMandate,
types::SetupMandateRequestData,
types::PaymentsResponseData,
> for Trustpay
{
fn build_request(
&self,
_req: &types::RouterData<
api::SetupMandate,
types::SetupMandateRequestData,
types::PaymentsResponseData,
>,
_connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Err(
errors::ConnectorError::NotImplemented("Setup Mandate flow for Trustpay".to_string())
.into(),
)
}
}
impl api::PaymentVoid for Trustpay {}
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Trustpay
{
}
impl api::ConnectorAccessToken for Trustpay {}
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Trustpay
{
fn get_url(
&self,
_req: &types::RefreshTokenRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}{}",
connectors.trustpay.base_url_bank_redirects, "api/oauth2/token"
))
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_headers(
&self,
req: &types::RefreshTokenRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
let auth = trustpay::TrustpayAuthType::try_from(&req.connector_auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
let auth_value = auth
.project_id
.zip(auth.secret_key)
.map(|(project_id, secret_key)| {
format!(
"Basic {}",
consts::BASE64_ENGINE.encode(format!("{}:{}", project_id, secret_key))
)
});
Ok(vec![
(
headers::CONTENT_TYPE.to_string(),
types::RefreshTokenType::get_content_type(self)
.to_string()
.into(),
),
(headers::AUTHORIZATION.to_string(), auth_value.into_masked()),
])
}
fn get_request_body(
&self,
req: &types::RefreshTokenRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = trustpay::TrustpayAuthUpdateRequest::try_from(req)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_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)
.attach_default_headers()
.headers(types::RefreshTokenType::get_headers(self, req, connectors)?)
.url(&types::RefreshTokenType::get_url(self, req, connectors)?)
.set_body(types::RefreshTokenType::get_request_body(
self, req, connectors,
)?)
.build(),
);
Ok(req)
}
fn handle_response(
&self,
data: &types::RefreshTokenRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::RefreshTokenRouterData, errors::ConnectorError> {
let response: trustpay::TrustpayAuthUpdateResponse = res
.response
.parse_struct("trustpay TrustpayAuthUpdateResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: trustpay::TrustpayAccessTokenErrorResponse = res
.response
.parse_struct("Trustpay AccessTokenErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
Ok(ErrorResponse {
status_code: res.status_code,
code: response.result_info.result_code.to_string(),
// message vary for the same code, so relying on code alone as it is unique
message: response.result_info.result_code.to_string(),
reason: response.result_info.additional_info,
attempt_status: None,
connector_transaction_id: None,
})
}
}
impl api::PaymentSync for Trustpay {}
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
for Trustpay
{
fn get_headers(
&self,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let id = req.request.connector_transaction_id.clone();
match req.payment_method {
diesel_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}/{}",
connectors.trustpay.base_url_bank_redirects,
"api/Payments/Payment",
id.get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?
)),
_ => Ok(format!(
"{}{}/{}",
self.base_url(connectors),
"api/v1/instance",
id.get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?
)),
}
}
fn build_request(
&self,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
.build(),
))
}
fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
fn handle_response(
&self,
data: &types::PaymentsSyncRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
let response: trustpay::TrustpayPaymentsResponse = res
.response
.parse_struct("trustpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}
impl api::PaymentCapture for Trustpay {}
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
for Trustpay
{
}
impl api::PaymentsPreProcessing for Trustpay {}
impl
ConnectorIntegration<
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Trustpay
{
fn get_headers(
&self,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsPreProcessingType::get_content_type(self)
.to_string()
.into(),
)];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
header.append(&mut api_key);
Ok(header)
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
_req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}{}", self.base_url(connectors), "api/v1/intent"))
}
fn get_request_body(
&self,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let req_currency = req.request.get_currency()?;
let req_amount = req.request.get_minor_amount()?;
let amount =
connector_utils::convert_amount(self.amount_converter, req_amount, req_currency)?;
let connector_router_data = trustpay::TrustpayRouterData::try_from((amount, req))?;
let connector_req =
trustpay::TrustpayCreateIntentRequest::try_from(&connector_router_data)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
}
fn build_request(
&self,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let req = Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.attach_default_headers()
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.set_body(types::PaymentsPreProcessingType::get_request_body(
self, req, connectors,
)?)
.build(),
);
Ok(req)
}
fn handle_response(
&self,
data: &types::PaymentsPreProcessingRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
let response: trustpay::TrustpayCreateIntentResponse = res
.response
.parse_struct("TrustpayCreateIntentResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}
impl api::PaymentSession for Trustpay {}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Trustpay
{
}
impl api::PaymentAuthorize for Trustpay {}
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Trustpay
{
fn get_headers(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
match req.payment_method {
diesel_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}",
connectors.trustpay.base_url_bank_redirects, "api/Payments/Payment"
)),
_ => Ok(format!(
"{}{}",
self.base_url(connectors),
"api/v1/purchase"
)),
}
}
fn get_request_body(
&self,
req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let amount = connector_utils::convert_amount(
self.amount_converter,
req.request.minor_amount,
req.request.currency,
)?;
let connector_router_data = trustpay::TrustpayRouterData::try_from((amount, req))?;
let connector_req = trustpay::TrustpayPaymentsRequest::try_from(&connector_router_data)?;
match req.payment_method {
diesel_models::enums::PaymentMethod::BankRedirect => {
Ok(RequestContent::Json(Box::new(connector_req)))
}
_ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))),
}
}
fn build_request(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsAuthorizeType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::PaymentsAuthorizeType::get_headers(
self, req, connectors,
)?)
.set_body(types::PaymentsAuthorizeType::get_request_body(
self, req, connectors,
)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsAuthorizeRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
let response: trustpay::TrustpayPaymentsResponse = res
.response
.parse_struct("trustpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}
impl api::Refund for Trustpay {}
impl api::RefundExecute for Trustpay {}
impl api::RefundSync for Trustpay {}
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
for Trustpay
{
fn get_headers(
&self,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
match req.payment_method {
diesel_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}{}{}",
connectors.trustpay.base_url_bank_redirects,
"api/Payments/Payment/",
req.request.connector_transaction_id,
"/Refund"
)),
_ => Ok(format!("{}{}", self.base_url(connectors), "api/v1/Reverse")),
}
}
fn get_request_body(
&self,
req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let amount = connector_utils::convert_amount(
self.amount_converter,
req.request.minor_refund_amount,
req.request.currency,
)?;
let connector_router_data = trustpay::TrustpayRouterData::try_from((amount, req))?;
let connector_req = trustpay::TrustpayRefundRequest::try_from(&connector_router_data)?;
match req.payment_method {
diesel_models::enums::PaymentMethod::BankRedirect => {
Ok(RequestContent::Json(Box::new(connector_req)))
}
_ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_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)?)
.attach_default_headers()
.headers(types::RefundExecuteType::get_headers(
self, req, connectors,
)?)
.set_body(types::RefundExecuteType::get_request_body(
self, req, connectors,
)?)
.build();
Ok(Some(request))
}
fn handle_response(
&self,
data: &types::RefundsRouterData<api::Execute>,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
let response: trustpay::RefundResponse = res
.response
.parse_struct("trustpay RefundResponse")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Trustpay {
fn get_headers(
&self,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let id = req
.request
.connector_refund_id
.to_owned()
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?;
match req.payment_method {
diesel_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}/{}",
connectors.trustpay.base_url_bank_redirects, "api/Payments/Payment", id
)),
_ => Ok(format!(
"{}{}/{}",
self.base_url(connectors),
"api/v1/instance",
id
)),
}
}
fn build_request(
&self,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::RefundSyncRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: trustpay::RefundResponse = res
.response
.parse_struct("trustpay RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}
#[async_trait::async_trait]
impl api::IncomingWebhook for Trustpay {
fn get_webhook_object_reference_id(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
let details: trustpay::TrustpayWebhookResponse = request
.body
.parse_struct("TrustpayWebhookResponse")
.switch()?;
match details.payment_information.credit_debit_indicator {
trustpay::CreditDebitIndicator::Crdt => {
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::PaymentAttemptId(
details.payment_information.references.merchant_reference,
),
))
}
trustpay::CreditDebitIndicator::Dbit => {
if details.payment_information.status == trustpay::WebhookStatus::Chargebacked {
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::PaymentAttemptId(
details.payment_information.references.merchant_reference,
),
))
} else {
Ok(api_models::webhooks::ObjectReferenceId::RefundId(
api_models::webhooks::RefundIdType::RefundId(
details.payment_information.references.merchant_reference,
),
))
}
}
}
}
fn get_webhook_event_type(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
let response: trustpay::TrustpayWebhookResponse = request
.body
.parse_struct("TrustpayWebhookResponse")
.switch()?;
match (
response.payment_information.credit_debit_indicator,
response.payment_information.status,
) {
(trustpay::CreditDebitIndicator::Crdt, trustpay::WebhookStatus::Paid) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess)
}
(trustpay::CreditDebitIndicator::Crdt, trustpay::WebhookStatus::Rejected) => {
Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure)
}
(trustpay::CreditDebitIndicator::Dbit, trustpay::WebhookStatus::Paid) => {
Ok(api_models::webhooks::IncomingWebhookEvent::RefundSuccess)
}
(trustpay::CreditDebitIndicator::Dbit, trustpay::WebhookStatus::Refunded) => {
Ok(api_models::webhooks::IncomingWebhookEvent::RefundSuccess)
}
(trustpay::CreditDebitIndicator::Dbit, trustpay::WebhookStatus::Rejected) => {
Ok(api_models::webhooks::IncomingWebhookEvent::RefundFailure)
}
(trustpay::CreditDebitIndicator::Dbit, trustpay::WebhookStatus::Chargebacked) => {
Ok(api_models::webhooks::IncomingWebhookEvent::DisputeLost)
}
(
trustpay::CreditDebitIndicator::Dbit | trustpay::CreditDebitIndicator::Crdt,
trustpay::WebhookStatus::Unknown,
) => Ok(api::IncomingWebhookEvent::EventNotSupported),
(trustpay::CreditDebitIndicator::Crdt, trustpay::WebhookStatus::Refunded) => {
Ok(api::IncomingWebhookEvent::EventNotSupported)
}
(trustpay::CreditDebitIndicator::Crdt, trustpay::WebhookStatus::Chargebacked) => {
Ok(api::IncomingWebhookEvent::EventNotSupported)
}
}
}
fn get_webhook_resource_object(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
let details: trustpay::TrustpayWebhookResponse = request
.body
.parse_struct("TrustpayWebhookResponse")
.switch()?;
Ok(Box::new(details.payment_information))
}
fn get_webhook_source_verification_algorithm(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
Ok(Box::new(crypto::HmacSha256))
}
fn get_webhook_source_verification_signature(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let response: trustpay::TrustpayWebhookResponse = request
.body
.parse_struct("TrustpayWebhookResponse")
.switch()?;
hex::decode(response.signature)
.change_context(errors::ConnectorError::WebhookSignatureNotFound)
}
fn get_webhook_source_verification_message(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
_merchant_id: &common_utils::id_type::MerchantId,
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let trustpay_response: trustpay::TrustpayWebhookResponse = request
.body
.parse_struct("TrustpayWebhookResponse")
.switch()?;
let response: serde_json::Value = request.body.parse_struct("Webhook Value").switch()?;
let values =
collect_and_sort_values_by_removing_signature(&response, &trustpay_response.signature);
let payload = values.join("/");
Ok(payload.into_bytes())
}
fn get_dispute_details(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::disputes::DisputePayload, errors::ConnectorError> {
let trustpay_response: trustpay::TrustpayWebhookResponse = request
.body
.parse_struct("TrustpayWebhookResponse")
.switch()?;
let payment_info = trustpay_response.payment_information;
let reason = payment_info.status_reason_information.unwrap_or_default();
let connector_dispute_id = payment_info
.references
.payment_id
.ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?;
Ok(api::disputes::DisputePayload {
amount: payment_info.amount.amount.to_string(),
currency: payment_info.amount.currency,
dispute_stage: api_models::enums::DisputeStage::Dispute,
connector_dispute_id,
connector_reason: reason.reason.reject_reason,
connector_reason_code: reason.reason.code,
challenge_required_by: None,
connector_status: payment_info.status.to_string(),
created_at: None,
updated_at: None,
})
}
}
impl services::ConnectorRedirectResponse for Trustpay {
fn get_flow_type(
&self,
_query_params: &str,
_json_payload: Option<serde_json::Value>,
action: services::PaymentAction,
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
match action {
services::PaymentAction::PSync
| services::PaymentAction::CompleteAuthorize
| services::PaymentAction::PaymentAuthenticateCompleteAuthorize => {
Ok(payments::CallConnectorAction::Trigger)
}
}
}
}
impl ConnectorErrorTypeMapping for Trustpay {
fn get_connector_error_type(
&self,
error_code: String,
error_message: String,
) -> ConnectorErrorType {
match (error_code.as_str(), error_message.as_str()) {
// 2xx card api error codes and messages mapping
("100.100.600", "Empty CVV for VISA, MASTER not allowed") => ConnectorErrorType::UserError,
("100.350.100", "Referenced session is rejected (no action possible)") => ConnectorErrorType::TechnicalError,
("100.380.401", "User authentication failed") => ConnectorErrorType::UserError,
("100.380.501", "Risk management transaction timeout") => ConnectorErrorType::TechnicalError,
("100.390.103", "PARes validation failed - problem with signature") => ConnectorErrorType::TechnicalError,
("100.390.111", "Communication error to VISA/Mastercard Directory Server") => ConnectorErrorType::TechnicalError,
("100.390.112", "Technical error in 3D system") => ConnectorErrorType::TechnicalError,
("100.390.115", "Authentication failed due to invalid message format") => ConnectorErrorType::TechnicalError,
("100.390.118", "Authentication failed due to suspected fraud") => ConnectorErrorType::UserError,
("100.400.304", "Invalid input data") => ConnectorErrorType::UserError,
("200.300.404", "Invalid or missing parameter") => ConnectorErrorType::UserError,
("300.100.100", "Transaction declined (additional customer authentication required)") => ConnectorErrorType::UserError,
("400.001.301", "Card not enrolled in 3DS") => ConnectorErrorType::UserError,
("400.001.600", "Authentication error") => ConnectorErrorType::UserError,
("400.001.601", "Transaction declined (auth. declined)") => ConnectorErrorType::UserError,
("400.001.602", "Invalid transaction") => ConnectorErrorType::UserError,
("400.001.603", "Invalid transaction") => ConnectorErrorType::UserError,
("700.400.200", "Cannot refund (refund volume exceeded or tx reversed or invalid workflow)") => ConnectorErrorType::BusinessError,
("700.500.001", "Referenced session contains too many transactions") => ConnectorErrorType::TechnicalError,
("700.500.003", "Test accounts not allowed in production") => ConnectorErrorType::UserError,
("800.100.151", "Transaction declined (invalid card)") => ConnectorErrorType::UserError,
("800.100.152", "Transaction declined by authorization system") => ConnectorErrorType::UserError,
("800.100.153", "Transaction declined (invalid CVV)") => ConnectorErrorType::UserError,
("800.100.155", "Transaction declined (amount exceeds credit)") => ConnectorErrorType::UserError,
("800.100.157", "Transaction declined (wrong expiry date)") => ConnectorErrorType::UserError,
("800.100.162", "Transaction declined (limit exceeded)") => ConnectorErrorType::BusinessError,
("800.100.163", "Transaction declined (maximum transaction frequency exceeded)") => ConnectorErrorType::BusinessError,
("800.100.168", "Transaction declined (restricted card)") => ConnectorErrorType::UserError,
("800.100.170", "Transaction declined (transaction not permitted)") => ConnectorErrorType::UserError,
("800.100.172", "Transaction declined (account blocked)") => ConnectorErrorType::BusinessError,
("800.100.190", "Transaction declined (invalid configuration data)") => ConnectorErrorType::BusinessError,
("800.120.100", "Rejected by throttling") => ConnectorErrorType::TechnicalError,
("800.300.401", "Bin blacklisted") => ConnectorErrorType::BusinessError,
("800.700.100", "Transaction for the same session is currently being processed, please try again later") => ConnectorErrorType::TechnicalError,
("900.100.300", "Timeout, uncertain result") => ConnectorErrorType::TechnicalError,
// 4xx error codes for cards api are unique and messages vary, so we are relying only on error code to decide an error type
("4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "26" | "34" | "39" | "48" | "52" | "85" | "86", _) => ConnectorErrorType::UserError,
("21" | "22" | "23" | "30" | "31" | "32" | "35" | "37" | "40" | "41" | "45" | "46" | "49" | "50" | "56" | "60" | "67" | "81" | "82" | "83" | "84" | "87", _) => ConnectorErrorType::BusinessError,
("59", _) => ConnectorErrorType::TechnicalError,
("1", _) => ConnectorErrorType::UnknownError,
// Error codes for bank redirects api are unique and messages vary, so we are relying only on error code to decide an error type
("1112008" | "1132000" | "1152000", _) => ConnectorErrorType::UserError,
("1112009" | "1122006" | "1132001" | "1132002" | "1132003" | "1132004" | "1132005" | "1132006" | "1132008" | "1132009" | "1132010" | "1132011" | "1132012" | "1132013" | "1133000" | "1133001" | "1133002" | "1133003" | "1133004", _) => ConnectorErrorType::BusinessError,
("1132014", _) => ConnectorErrorType::TechnicalError,
("1132007", _) => ConnectorErrorType::UnknownError,
_ => ConnectorErrorType::UnknownError,
}
}
}