feat(connector): add authorize, capture, void, psync, refund, rsync for Forte connector (#955)

Co-authored-by: Arjun Karthik <m.arjunkarthik@gmail.com>
Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
Prasunna Soppa
2023-05-04 19:22:28 +05:30
committed by GitHub
parent c888635166
commit f0464bc4f5
23 changed files with 998 additions and 238 deletions

View File

@ -186,7 +186,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for AciPaymentsRequest {
}
api::PaymentMethodData::Crypto(_) | api::PaymentMethodData::BankDebit(_) => {
Err(errors::ConnectorError::NotSupported {
payment_method: format!("{:?}", item.payment_method),
message: format!("{:?}", item.payment_method),
connector: "Aci",
payment_experience: api_models::enums::PaymentExperience::RedirectToUrl
.to_string(),

View File

@ -348,7 +348,7 @@ impl TryFrom<&api_enums::BankNames> for OnlineBankingCzechRepublicBanks {
api::enums::BankNames::CeskaSporitelna => Ok(Self::CS),
api::enums::BankNames::PlatnoscOnlineKartaPlatnicza => Ok(Self::C),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: String::from("BankRedirect"),
message: String::from("BankRedirect"),
connector: "Adyen",
payment_experience: api_enums::PaymentExperience::RedirectToUrl.to_string(),
})?,
@ -429,7 +429,7 @@ impl TryFrom<&api_enums::BankNames> for OnlineBankingPolandBanks {
api_models::enums::BankNames::VeloBank => Ok(Self::VeloBank),
api_models::enums::BankNames::ETransferPocztowy24 => Ok(Self::ETransferPocztowy24),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: String::from("BankRedirect"),
message: String::from("BankRedirect"),
connector: "Adyen",
payment_experience: api_enums::PaymentExperience::RedirectToUrl.to_string(),
})?,
@ -465,7 +465,7 @@ impl TryFrom<&api_enums::BankNames> for OnlineBankingSlovakiaBanks {
api::enums::BankNames::TatraPay => Ok(Self::Tatra),
api::enums::BankNames::Viamo => Ok(Self::Viamo),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: String::from("BankRedirect"),
message: String::from("BankRedirect"),
connector: "Adyen",
payment_experience: api_enums::PaymentExperience::RedirectToUrl.to_string(),
})?,
@ -697,7 +697,7 @@ impl<'a> TryFrom<&api_enums::BankNames> for AdyenTestBankNames<'a> {
Self("4a0a975b-0594-4b40-9068-39f77b3a91f9")
}
_ => Err(errors::ConnectorError::NotSupported {
payment_method: String::from("BankRedirect"),
message: String::from("BankRedirect"),
connector: "Adyen",
payment_experience: api_enums::PaymentExperience::RedirectToUrl.to_string(),
})?,
@ -743,7 +743,7 @@ impl<'a> TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest<'a
AdyenPaymentRequest::try_from((item, bank_redirect))
}
_ => Err(errors::ConnectorError::NotSupported {
payment_method: format!("{:?}", item.request.payment_method_type),
message: format!("{:?}", item.request.payment_method_type),
connector: "Adyen",
payment_experience: api_models::enums::PaymentExperience::RedirectToUrl
.to_string(),
@ -1130,7 +1130,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)>
Ok(AdyenPaymentMethod::AdyenCard(Box::new(adyen_card)))
}
_ => Err(errors::ConnectorError::NotSupported {
payment_method: format!("mandate_{:?}", item.payment_method),
message: format!("mandate_{:?}", item.payment_method),
connector: "Adyen",
payment_experience: api_models::enums::PaymentExperience::RedirectToUrl
.to_string(),

View File

@ -104,7 +104,7 @@ fn get_pm_and_subsequent_auth_detail(
Ok((payment_details, processing_options, subseuent_auth_info))
}
_ => Err(errors::ConnectorError::NotSupported {
payment_method: format!("{:?}", item.request.payment_method_data),
message: format!("{:?}", item.request.payment_method_data),
connector: "AuthorizeDotNet",
payment_experience: api_models::enums::PaymentExperience::RedirectToUrl
.to_string(),
@ -131,7 +131,7 @@ fn get_pm_and_subsequent_auth_detail(
}
api::PaymentMethodData::Crypto(_) | api::PaymentMethodData::BankDebit(_) => {
Err(errors::ConnectorError::NotSupported {
payment_method: format!("{:?}", item.request.payment_method_data),
message: format!("{:?}", item.request.payment_method_data),
connector: "AuthorizeDotNet",
payment_experience: api_models::enums::PaymentExperience::RedirectToUrl
.to_string(),

View File

@ -2,11 +2,14 @@ mod transformers;
use std::fmt::Debug;
use base64::Engine;
use error_stack::{IntoReport, ResultExt};
use transformers as forte;
use crate::{
configs::settings,
connector::utils::{PaymentsSyncRequestData, RefundsRequestData},
consts,
core::errors::{self, CustomResult},
headers,
services::{self, ConnectorIntegration},
@ -42,6 +45,7 @@ impl
> for Forte
{
}
pub const AUTH_ORG_ID_HEADER: &str = "X-Forte-Auth-Organization-Id";
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Forte
where
@ -52,13 +56,10 @@ where
req: &types::RouterData<Flow, Request, Response>,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
)];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
header.append(&mut api_key);
Ok(header)
let content_type = ConnectorCommon::common_get_content_type(self);
let mut common_headers = self.get_auth_header(&req.connector_auth_type)?;
common_headers.push((headers::CONTENT_TYPE.to_string(), content_type.to_string()));
Ok(common_headers)
}
}
@ -79,25 +80,34 @@ impl ConnectorCommon for Forte {
&self,
auth_type: &types::ConnectorAuthType,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let auth = forte::ForteAuthType::try_from(auth_type)
let auth: forte::ForteAuthType = auth_type
.try_into()
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
let raw_basic_token = format!("{}:{}", auth.api_access_id, auth.api_secret_key);
let basic_token = format!("Basic {}", consts::BASE64_ENGINE.encode(raw_basic_token));
Ok(vec![
(headers::AUTHORIZATION.to_string(), basic_token),
(AUTH_ORG_ID_HEADER.to_string(), auth.organization_id),
])
}
fn build_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: forte::ForteErrorResponse =
res.response
.parse_struct("ForteErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let response: forte::ForteErrorResponse = res
.response
.parse_struct("Forte ErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let message = response.response.response_desc;
let code = response
.response
.response_code
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string());
Ok(ErrorResponse {
status_code: res.status_code,
code: response.code,
message: response.message,
reason: response.reason,
code,
message,
reason: None,
})
}
}
@ -134,19 +144,25 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
fn get_url(
&self,
_req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?;
Ok(format!(
"{}/organizations/{}/locations/{}/transactions",
self.base_url(connectors),
auth.organization_id,
auth.location_id
))
}
fn get_request_body(
&self,
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let req_obj = forte::FortePaymentsRequest::try_from(req)?;
let connector_req = forte::FortePaymentsRequest::try_from(req)?;
let forte_req =
utils::Encode::<forte::FortePaymentsRequest>::encode_to_string_of_json(&req_obj)
utils::Encode::<forte::FortePaymentsRequest>::encode_to_string_of_json(&connector_req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(forte_req))
}
@ -178,14 +194,13 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
let response: forte::FortePaymentsResponse = res
.response
.parse_struct("Forte PaymentsAuthorizeResponse")
.parse_struct("Forte AuthorizeResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -213,10 +228,19 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
fn get_url(
&self,
_req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?;
let txn_id = PaymentsSyncRequestData::get_connector_transaction_id(&req.request)
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
Ok(format!(
"{}/organizations/{}/locations/{}/transactions/{}",
self.base_url(connectors),
auth.organization_id,
auth.location_id,
txn_id
))
}
fn build_request(
@ -233,13 +257,12 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsSyncRouterData,
res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
let response: forte::FortePaymentsResponse = res
let response: forte::FortePaymentsSyncResponse = res
.response
.parse_struct("forte PaymentsSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -248,7 +271,6 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -276,17 +298,27 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
fn get_url(
&self,
_req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?;
Ok(format!(
"{}/organizations/{}/locations/{}/transactions",
self.base_url(connectors),
auth.organization_id,
auth.location_id
))
}
fn get_request_body(
&self,
_req: &types::PaymentsCaptureRouterData,
req: &types::PaymentsCaptureRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into())
let connector_req = forte::ForteCaptureRequest::try_from(req)?;
let forte_req =
utils::Encode::<forte::ForteCaptureRequest>::encode_to_string_of_json(&connector_req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(forte_req))
}
fn build_request(
@ -296,12 +328,13 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.method(services::Method::Put)
.url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::PaymentsCaptureType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
.build(),
))
}
@ -311,7 +344,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
data: &types::PaymentsCaptureRouterData,
res: Response,
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
let response: forte::FortePaymentsResponse = res
let response: forte::ForteCaptureResponse = res
.response
.parse_struct("Forte PaymentsCaptureResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -320,7 +353,6 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -330,10 +362,84 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Forte
{
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 {
self.common_get_content_type()
}
fn get_url(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?;
Ok(format!(
"{}/organizations/{}/locations/{}/transactions/{}",
self.base_url(connectors),
auth.organization_id,
auth.location_id,
req.request.connector_transaction_id
))
}
fn get_request_body(
&self,
req: &types::PaymentsCancelRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = forte::ForteCancelRequest::try_from(req)?;
let forte_req =
utils::Encode::<forte::ForteCancelRequest>::encode_to_string_of_json(&connector_req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(forte_req))
}
fn build_request(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Put)
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
.body(types::PaymentsVoidType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsCancelRouterData,
res: Response,
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
let response: forte::ForteCancelResponse = res
.response
.parse_struct("forte CancelResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Forte {
@ -351,19 +457,25 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
fn get_url(
&self,
_req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?;
Ok(format!(
"{}/organizations/{}/locations/{}/transactions",
self.base_url(connectors),
auth.organization_id,
auth.location_id
))
}
fn get_request_body(
&self,
req: &types::RefundsRouterData<api::Execute>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let req_obj = forte::ForteRefundRequest::try_from(req)?;
let connector_req = forte::ForteRefundRequest::try_from(req)?;
let forte_req =
utils::Encode::<forte::ForteRefundRequest>::encode_to_string_of_json(&req_obj)
utils::Encode::<forte::ForteRefundRequest>::encode_to_string_of_json(&connector_req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(forte_req))
}
@ -399,7 +511,6 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -425,10 +536,17 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
fn get_url(
&self,
_req: &types::RefundSyncRouterData,
_connectors: &settings::Connectors,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?;
Ok(format!(
"{}/organizations/{}/locations/{}/transactions/{}",
self.base_url(connectors),
auth.organization_id,
auth.location_id,
req.request.get_connector_refund_id()?
))
}
fn build_request(
@ -442,7 +560,6 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.body(types::RefundSyncType::get_request_body(self, req)?)
.build(),
))
}
@ -452,7 +569,7 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
data: &types::RefundSyncRouterData,
res: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: forte::RefundResponse = res
let response: forte::RefundSyncResponse = res
.response
.parse_struct("forte RefundSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -461,7 +578,6 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(

View File

@ -2,90 +2,242 @@ use masking::Secret;
use serde::{Deserialize, Serialize};
use crate::{
connector::utils::PaymentsAuthorizeRequestData,
connector::utils::{
self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData, RouterData,
},
core::errors,
types::{self, api, storage::enums},
pii::{self},
types::{self, api, storage::enums, transformers::ForeignFrom},
};
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[derive(Debug, Serialize)]
pub struct FortePaymentsRequest {
amount: i64,
card: ForteCard,
action: ForteAction,
authorization_amount: f64,
billing_address: BillingAddress,
card: Card,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BillingAddress {
first_name: Secret<String>,
last_name: Secret<String>,
}
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct ForteCard {
name: Secret<String>,
number: Secret<String, common_utils::pii::CardNumber>,
expiry_month: Secret<String>,
expiry_year: Secret<String>,
cvc: Secret<String>,
complete: bool,
#[derive(Debug, Serialize)]
pub struct Card {
card_type: ForteCardType,
name_on_card: Secret<String>,
account_number: Secret<String, pii::CardNumber>,
expire_month: Secret<String>,
expire_year: Secret<String>,
card_verification_value: Secret<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ForteCardType {
Visa,
MasterCard,
Amex,
Discover,
DinersClub,
Jcb,
}
impl TryFrom<utils::CardIssuer> for ForteCardType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(issuer: utils::CardIssuer) -> Result<Self, Self::Error> {
match issuer {
utils::CardIssuer::AmericanExpress => Ok(Self::Amex),
utils::CardIssuer::Master => Ok(Self::MasterCard),
utils::CardIssuer::Discover => Ok(Self::Discover),
utils::CardIssuer::Visa => Ok(Self::Visa),
utils::CardIssuer::DinersClub => Ok(Self::DinersClub),
utils::CardIssuer::JCB => Ok(Self::Jcb),
_ => Err(errors::ConnectorError::NotSupported {
message: issuer.to_string(),
connector: "Forte",
payment_experience: api::enums::PaymentExperience::RedirectToUrl.to_string(),
}
.into()),
}
}
}
impl TryFrom<&types::PaymentsAuthorizeRouterData> for FortePaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
match item.request.payment_method_data.clone() {
api::PaymentMethodData::Card(req_card) => {
let card = ForteCard {
name: req_card.card_holder_name,
number: req_card.card_number,
expiry_month: req_card.card_exp_month,
expiry_year: req_card.card_exp_year,
cvc: req_card.card_cvc,
complete: item.request.is_auto_capture()?,
if item.request.currency != enums::Currency::USD {
Err(errors::ConnectorError::NotSupported {
message: item.request.currency.to_string(),
connector: "Forte",
payment_experience: api::enums::PaymentExperience::RedirectToUrl.to_string(),
})?
}
match item.request.payment_method_data {
api_models::payments::PaymentMethodData::Card(ref ccard) => {
let action = match item.request.is_auto_capture()? {
true => ForteAction::Sale,
false => ForteAction::Authorize,
};
let card_type = ForteCardType::try_from(ccard.get_card_issuer()?)?;
let address = item.get_billing_address()?;
let card = Card {
card_type,
name_on_card: ccard.card_holder_name.clone(),
account_number: ccard.card_number.clone(),
expire_month: ccard.card_exp_month.clone(),
expire_year: ccard.card_exp_year.clone(),
card_verification_value: ccard.card_cvc.clone(),
};
let billing_address = BillingAddress {
first_name: address.get_first_name()?.to_owned(),
last_name: address.get_last_name()?.to_owned(),
};
let authorization_amount =
utils::to_currency_base_unit_asf64(item.request.amount, item.request.currency)?;
Ok(Self {
amount: item.request.amount,
action,
authorization_amount,
billing_address,
card,
})
}
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
_ => Err(errors::ConnectorError::NotImplemented(
"Payment Method".to_string(),
))?,
}
}
}
// Auth Struct
pub struct ForteAuthType {
pub(super) api_key: String,
pub(super) api_access_id: String,
pub(super) organization_id: String,
pub(super) location_id: String,
pub(super) api_secret_key: String,
}
impl TryFrom<&types::ConnectorAuthType> for ForteAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type {
types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
api_key: api_key.to_string(),
types::ConnectorAuthType::MultiAuthKey {
api_key,
key1,
api_secret,
key2,
} => Ok(Self {
api_access_id: api_key.to_string(),
organization_id: format!("org_{}", key1),
location_id: format!("loc_{}", key2),
api_secret_key: api_secret.to_string(),
}),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
_ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
}
}
}
// PaymentsResponse
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FortePaymentStatus {
Succeeded,
Complete,
Failed,
#[default]
Processing,
Authorized,
Ready,
Voided,
Settled,
}
impl From<FortePaymentStatus> for enums::AttemptStatus {
fn from(item: FortePaymentStatus) -> Self {
match item {
FortePaymentStatus::Succeeded => Self::Charged,
FortePaymentStatus::Complete | FortePaymentStatus::Settled => Self::Charged,
FortePaymentStatus::Failed => Self::Failure,
FortePaymentStatus::Processing => Self::Authorizing,
FortePaymentStatus::Ready => Self::Pending,
FortePaymentStatus::Authorized => Self::Authorized,
FortePaymentStatus::Voided => Self::Voided,
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
impl ForeignFrom<(ForteResponseCode, ForteAction)> for enums::AttemptStatus {
fn foreign_from((response_code, action): (ForteResponseCode, ForteAction)) -> Self {
match response_code {
ForteResponseCode::A01 => match action {
ForteAction::Authorize => Self::Authorized,
ForteAction::Sale => Self::Pending,
},
ForteResponseCode::A05 | ForteResponseCode::A06 => Self::Authorizing,
_ => Self::Failure,
}
}
}
#[derive(Debug, Deserialize)]
pub struct CardResponse {
pub name_on_card: Secret<String>,
pub last_4_account_number: String,
pub masked_account_number: String,
pub card_type: String,
}
#[derive(Debug, Deserialize)]
pub enum ForteResponseCode {
A01,
A05,
A06,
U13,
U14,
U18,
U20,
}
impl From<ForteResponseCode> for enums::AttemptStatus {
fn from(item: ForteResponseCode) -> Self {
match item {
ForteResponseCode::A01 | ForteResponseCode::A05 | ForteResponseCode::A06 => {
Self::Pending
}
_ => Self::Failure,
}
}
}
#[derive(Debug, Deserialize)]
pub struct ResponseStatus {
pub environment: String,
pub response_type: String,
pub response_code: ForteResponseCode,
pub response_desc: String,
pub authorization_code: String,
pub avs_result: Option<String>,
pub cvv_result: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ForteAction {
Sale,
Authorize,
}
#[derive(Debug, Deserialize)]
pub struct FortePaymentsResponse {
status: FortePaymentStatus,
id: String,
pub transaction_id: String,
pub location_id: String,
pub action: ForteAction,
pub authorization_amount: Option<f64>,
pub authorization_code: String,
pub entered_by: String,
pub billing_address: Option<BillingAddress>,
pub card: Option<CardResponse>,
pub response: ResponseStatus,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ForteMeta {
pub auth_id: String,
}
impl<F, T>
@ -96,13 +248,197 @@ impl<F, T>
fn try_from(
item: types::ResponseRouterData<F, FortePaymentsResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let response_code = item.response.response.response_code;
let action = item.response.action;
let transaction_id = &item.response.transaction_id;
Ok(Self {
status: enums::AttemptStatus::foreign_from((response_code, action)),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(transaction_id.to_string()),
redirection_data: None,
mandate_reference: None,
connector_metadata: Some(serde_json::json!(ForteMeta {
auth_id: item.response.authorization_code,
})),
network_txn_id: None,
}),
..item.data
})
}
}
//PsyncResponse
#[derive(Debug, Deserialize)]
pub struct FortePaymentsSyncResponse {
pub transaction_id: String,
pub location_id: String,
pub status: FortePaymentStatus,
pub action: ForteAction,
pub authorization_amount: Option<f64>,
pub authorization_code: String,
pub entered_by: String,
pub billing_address: Option<BillingAddress>,
pub card: Option<CardResponse>,
pub response: ResponseStatus,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, FortePaymentsSyncResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
FortePaymentsSyncResponse,
T,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let transaction_id = &item.response.transaction_id;
Ok(Self {
status: enums::AttemptStatus::from(item.response.status),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
resource_id: types::ResponseId::ConnectorTransactionId(transaction_id.to_string()),
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
connector_metadata: Some(serde_json::json!(ForteMeta {
auth_id: item.response.authorization_code,
})),
network_txn_id: None,
}),
..item.data
})
}
}
// Capture
#[derive(Debug, Serialize)]
pub struct ForteCaptureRequest {
action: String,
transaction_id: String,
authorization_code: String,
}
impl TryFrom<&types::PaymentsCaptureRouterData> for ForteCaptureRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
let trn_id = item.request.connector_transaction_id.clone();
let connector_auth_id: ForteMeta =
utils::to_connector_meta(item.request.connector_meta.clone())?;
let auth_code = connector_auth_id.auth_id;
Ok(Self {
action: "capture".to_string(),
transaction_id: trn_id,
authorization_code: auth_code,
})
}
}
#[derive(Debug, Deserialize)]
pub struct CaptureResponseStatus {
pub environment: String,
pub response_type: String,
pub response_code: ForteResponseCode,
pub response_desc: String,
pub authorization_code: String,
}
// Capture Response
#[derive(Debug, Deserialize)]
pub struct ForteCaptureResponse {
pub transaction_id: String,
pub original_transaction_id: String,
pub entered_by: String,
pub authorization_code: String,
pub response: CaptureResponseStatus,
}
impl TryFrom<types::PaymentsCaptureResponseRouterData<ForteCaptureResponse>>
for types::PaymentsCaptureRouterData
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::PaymentsCaptureResponseRouterData<ForteCaptureResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
status: enums::AttemptStatus::from(item.response.response.response_code),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata: Some(serde_json::json!(ForteMeta {
auth_id: item.response.authorization_code,
})),
network_txn_id: None,
}),
amount_captured: None,
..item.data
})
}
}
//Cancel
#[derive(Debug, Serialize)]
pub struct ForteCancelRequest {
action: String,
authorization_code: String,
}
impl TryFrom<&types::PaymentsCancelRouterData> for ForteCancelRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
let action = "void".to_string();
let connector_auth_id: ForteMeta =
utils::to_connector_meta(item.request.connector_meta.clone())?;
let authorization_code = connector_auth_id.auth_id;
Ok(Self {
action,
authorization_code,
})
}
}
#[derive(Debug, Deserialize)]
pub struct CancelResponseStatus {
pub response_type: String,
pub response_code: ForteResponseCode,
pub response_desc: String,
pub authorization_code: String,
}
#[derive(Debug, Deserialize)]
pub struct ForteCancelResponse {
pub transaction_id: String,
pub location_id: String,
pub action: String,
pub authorization_code: String,
pub entered_by: String,
pub response: CancelResponseStatus,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, ForteCancelResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, ForteCancelResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let transaction_id = &item.response.transaction_id;
Ok(Self {
status: enums::AttemptStatus::from(item.response.response.response_code),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(transaction_id.to_string()),
redirection_data: None,
mandate_reference: None,
connector_metadata: Some(serde_json::json!(ForteMeta {
auth_id: item.response.authorization_code,
})),
network_txn_id: None,
}),
..item.data
@ -111,46 +447,68 @@ impl<F, T>
}
// REFUND :
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct ForteRefundRequest {
pub amount: i64,
action: String,
authorization_amount: f64,
original_transaction_id: String,
authorization_code: String,
}
impl<F> TryFrom<&types::RefundsRouterData<F>> for ForteRefundRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
let trn_id = item.request.connector_transaction_id.clone();
let connector_auth_id: ForteMeta =
utils::to_connector_meta(item.request.connector_metadata.clone())?;
let auth_code = connector_auth_id.auth_id;
let authorization_amount =
utils::to_currency_base_unit_asf64(item.request.amount, item.request.currency)?;
Ok(Self {
amount: item.request.amount,
action: "reverse".to_string(),
authorization_amount,
original_transaction_id: trn_id,
authorization_code: auth_code,
})
}
}
// Type definition for Refund Response
#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RefundStatus {
Succeeded,
Complete,
Ready,
Failed,
#[default]
Processing,
}
impl From<RefundStatus> for enums::RefundStatus {
fn from(item: RefundStatus) -> Self {
match item {
RefundStatus::Succeeded => Self::Success,
RefundStatus::Complete => Self::Success,
RefundStatus::Ready => Self::Pending,
RefundStatus::Failed => Self::Failure,
RefundStatus::Processing => Self::Pending,
}
}
}
impl From<ForteResponseCode> for enums::RefundStatus {
fn from(item: ForteResponseCode) -> Self {
match item {
ForteResponseCode::A01 | ForteResponseCode::A05 | ForteResponseCode::A06 => {
Self::Pending
}
_ => Self::Failure,
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct RefundResponse {
id: String,
status: RefundStatus,
pub transaction_id: String,
pub original_transaction_id: String,
pub action: String,
pub authorization_amount: Option<f64>,
pub authorization_code: String,
pub response: ResponseStatus,
}
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
@ -162,24 +520,30 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(),
refund_status: enums::RefundStatus::from(item.response.status),
connector_refund_id: item.response.transaction_id,
refund_status: enums::RefundStatus::from(item.response.response.response_code),
}),
..item.data
})
}
}
impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
#[derive(Debug, Deserialize)]
pub struct RefundSyncResponse {
status: RefundStatus,
transaction_id: String,
}
impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundSyncResponse>>
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, RefundSyncResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(),
connector_refund_id: item.response.transaction_id,
refund_status: enums::RefundStatus::from(item.response.status),
}),
..item.data
@ -187,10 +551,15 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct ForteErrorResponse {
pub status_code: u16,
pub code: String,
pub message: String,
pub reason: Option<String>,
#[derive(Debug, Deserialize)]
pub struct ErrorResponseStatus {
pub environment: String,
pub response_type: Option<String>,
pub response_code: Option<String>,
pub response_desc: String,
}
#[derive(Debug, Deserialize)]
pub struct ForteErrorResponse {
pub response: ErrorResponseStatus,
}

View File

@ -261,7 +261,7 @@ impl
token
)),
_ => Err(error_stack::report!(errors::ConnectorError::NotSupported {
payment_method: payment_method_type.to_string(),
message: payment_method_type.to_string(),
connector: "klarna",
payment_experience: payment_experience.to_string()
})),

View File

@ -194,14 +194,21 @@ pub struct MultisafepayPaymentsRequest {
pub var3: Option<String>,
}
impl From<utils::CardIssuer> for Gateway {
fn from(issuer: utils::CardIssuer) -> Self {
impl TryFrom<utils::CardIssuer> for Gateway {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(issuer: utils::CardIssuer) -> Result<Self, Self::Error> {
match issuer {
utils::CardIssuer::AmericanExpress => Self::Amex,
utils::CardIssuer::Master => Self::MasterCard,
utils::CardIssuer::Maestro => Self::Maestro,
utils::CardIssuer::Visa => Self::Visa,
utils::CardIssuer::Discover => Self::Discover,
utils::CardIssuer::AmericanExpress => Ok(Self::Amex),
utils::CardIssuer::Master => Ok(Self::MasterCard),
utils::CardIssuer::Maestro => Ok(Self::Maestro),
utils::CardIssuer::Discover => Ok(Self::Discover),
utils::CardIssuer::Visa => Ok(Self::Visa),
_ => Err(errors::ConnectorError::NotSupported {
message: issuer.to_string(),
connector: "Multisafe pay",
payment_experience: api::enums::PaymentExperience::RedirectToUrl.to_string(),
}
.into()),
}
}
}
@ -216,7 +223,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques
};
let gateway = match item.request.payment_method_data {
api::PaymentMethodData::Card(ref ccard) => Gateway::from(ccard.get_card_issuer()?),
api::PaymentMethodData::Card(ref ccard) => Gateway::try_from(ccard.get_card_issuer()?)?,
api::PaymentMethodData::PayLater(
api_models::payments::PayLaterData::KlarnaRedirect {
billing_email: _,

View File

@ -539,7 +539,7 @@ impl<F>
)
}
_ => Err(errors::ConnectorError::NotSupported {
payment_method: "Bank Redirect".to_string(),
message: "Bank Redirect".to_string(),
connector: "Nuvei",
payment_experience: "Redirection".to_string(),
})?,
@ -583,7 +583,7 @@ impl<F>
item,
)),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: "Wallet".to_string(),
message: "Wallet".to_string(),
connector: "Nuvei",
payment_experience: "RedirectToUrl".to_string(),
}
@ -611,7 +611,7 @@ impl<F>
item,
)),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: "Bank Redirect".to_string(),
message: "Bank Redirect".to_string(),
connector: "Nuvei",
payment_experience: "RedirectToUrl".to_string(),
}

View File

@ -38,7 +38,7 @@ impl TryFrom<utils::CardIssuer> for PayeezyCardType {
utils::CardIssuer::Discover => Ok(Self::Discover),
utils::CardIssuer::Visa => Ok(Self::Visa),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: api::enums::PaymentMethod::Card.to_string(),
message: issuer.to_string(),
connector: "Payeezy",
payment_experience: api::enums::PaymentExperience::RedirectToUrl.to_string(),
}

View File

@ -454,7 +454,7 @@ impl TryFrom<&api_models::enums::BankNames> for StripeBankNames {
api_models::enums::BankNames::VolkskreditbankAg => Self::VolkskreditbankAg,
api_models::enums::BankNames::VrBankBraunau => Self::VrBankBraunau,
_ => Err(errors::ConnectorError::NotSupported {
payment_method: api_enums::PaymentMethod::BankRedirect.to_string(),
message: api_enums::PaymentMethod::BankRedirect.to_string(),
connector: "Stripe",
payment_experience: api_enums::PaymentExperience::RedirectToUrl.to_string(),
})?,
@ -497,14 +497,14 @@ fn infer_stripe_pay_later_type(
Ok(StripePaymentMethodType::AfterpayClearpay)
}
_ => Err(errors::ConnectorError::NotSupported {
payment_method: pm_type.to_string(),
message: pm_type.to_string(),
connector: "stripe",
payment_experience: experience.to_string(),
}),
}
} else {
Err(errors::ConnectorError::NotSupported {
payment_method: pm_type.to_string(),
message: pm_type.to_string(),
connector: "stripe",
payment_experience: experience.to_string(),
})
@ -1865,7 +1865,7 @@ impl
}))
}
api::PaymentMethodData::Crypto(_) => Err(errors::ConnectorError::NotSupported {
payment_method: format!("{pm_type:?}"),
message: format!("{pm_type:?}"),
connector: "Stripe",
payment_experience: api_models::enums::PaymentExperience::RedirectToUrl.to_string(),
})?,

View File

@ -325,6 +325,14 @@ static CARD_REGEX: Lazy<HashMap<CardIssuer, Result<Regex, regex::Error>>> = Lazy
CardIssuer::Maestro,
Regex::new(r"^(5018|5020|5038|5893|6304|6759|6761|6762|6763)[0-9]{8,15}$"),
);
map.insert(
CardIssuer::DinersClub,
Regex::new(r"^3(?:0[0-5]|[68][0-9])[0-9]{11}$"),
);
map.insert(
CardIssuer::JCB,
Regex::new(r"^(3(?:088|096|112|158|337|5(?:2[89]|[3-8][0-9]))\d{12})$"),
);
map
});
@ -335,6 +343,8 @@ pub enum CardIssuer {
Maestro,
Visa,
Discover,
DinersClub,
JCB,
}
pub trait CardData {
@ -630,6 +640,14 @@ pub fn to_currency_base_unit(
amount: i64,
currency: storage_models::enums::Currency,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
let amount_f64 = to_currency_base_unit_asf64(amount, currency)?;
Ok(format!("{amount_f64:.2}"))
}
pub fn to_currency_base_unit_asf64(
amount: i64,
currency: storage_models::enums::Currency,
) -> Result<f64, error_stack::Report<errors::ConnectorError>> {
let amount_u32 = u32::try_from(amount)
.into_report()
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
@ -642,7 +660,7 @@ pub fn to_currency_base_unit(
| storage_models::enums::Currency::OMR => amount_f64 / 1000.00,
_ => amount_f64 / 100.00,
};
Ok(format!("{amount:.2}"))
Ok(amount)
}
pub fn str_to_f32<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>

View File

@ -130,7 +130,7 @@ impl TryFrom<utils::CardIssuer> for Gateway {
utils::CardIssuer::Discover => Ok(Self::Discover),
utils::CardIssuer::Visa => Ok(Self::Visa),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: api_enums::PaymentMethod::Card.to_string(),
message: issuer.to_string(),
connector: "worldline",
payment_experience: api_enums::PaymentExperience::RedirectToUrl.to_string(),
}

View File

@ -253,9 +253,9 @@ pub enum ConnectorError {
FailedToObtainCertificateKey,
#[error("This step has not been implemented for: {0}")]
NotImplemented(String),
#[error("{payment_method} is not supported by {connector}")]
#[error("{message} is not supported by {connector}")]
NotSupported {
payment_method: String,
message: String,
connector: &'static str,
payment_experience: String,
},

View File

@ -107,8 +107,8 @@ impl ConnectorErrorExt for error_stack::Report<errors::ConnectorError> {
"payment_method_data, payment_method_type and payment_experience does not match",
}
},
errors::ConnectorError::NotSupported { payment_method, connector, payment_experience } => {
errors::ApiErrorResponse::NotSupported { message: format!("Payment method type {payment_method} is not supported by {connector} through payment experience {payment_experience}") }
errors::ConnectorError::NotSupported { message, connector, payment_experience } => {
errors::ApiErrorResponse::NotSupported { message: format!("{message} is not supported by {connector} through payment experience {payment_experience}") }
},
errors::ConnectorError::FlowNotSupported{ flow, connector } => {
errors::ApiErrorResponse::FlowNotSupported { flow: flow.to_owned(), connector: connector.to_owned() }

View File

@ -537,6 +537,12 @@ pub enum ConnectorAuthType {
key1: String,
api_secret: String,
},
MultiAuthKey {
api_key: String,
key1: String,
api_secret: String,
key2: String,
},
#[default]
NoKey,
}

View File

@ -204,7 +204,7 @@ impl ConnectorData {
"cybersource" => Ok(Box::new(&connector::Cybersource)),
"dlocal" => Ok(Box::new(&connector::Dlocal)),
"fiserv" => Ok(Box::new(&connector::Fiserv)),
// "forte" => Ok(Box::new(&connector::Forte)),
"forte" => Ok(Box::new(&connector::Forte)),
"globalpay" => Ok(Box::new(&connector::Globalpay)),
"klarna" => Ok(Box::new(&connector::Klarna)),
"mollie" => Ok(Box::new(&connector::Mollie)),