refactor: Move trait ConnectorIntegration to crate hyperswitch_interfaces (#4946)

Co-authored-by: Deepanshu Bansal <deepanshu.bansal@Deepanshu-Bansal-K3PYF02LFW.local>
This commit is contained in:
DEEPANSHU BANSAL
2024-06-18 13:53:40 +05:30
committed by GitHub
parent a0f3887d1f
commit cbe3a6d43e
19 changed files with 635 additions and 509 deletions

View File

@ -6,12 +6,28 @@ rust-version.workspace = true
readme = "README.md"
license.workspace = true
[features]
default = ["dummy_connector", "payouts"]
dummy_connector = []
payouts = []
[dependencies]
async-trait = "0.1.79"
bytes = "1.6.0"
dyn-clone = "1.0.17"
http = "0.2.12"
mime = "0.3.17"
once_cell = "1.19.0"
reqwest = "0.11.27"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
thiserror = "1.0.58"
time = "0.3.35"
# First party crates
common_utils = { version = "0.1.0", path = "../common_utils" }
hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false }
masking = { version = "0.1.0", path = "../masking" }
router_derive = { version = "0.1.0", path = "../router_derive" }
router_env = { version = "0.1.0", path = "../router_env" }
storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false }

View File

@ -0,0 +1,183 @@
//! API interface
use common_utils::{
errors::CustomResult,
request::{Method, Request, RequestContent},
};
use hyperswitch_domain_models::router_data::{ErrorResponse, RouterData};
use masking::Maskable;
use serde_json::json;
use crate::{
configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, metrics, types,
};
/// type BoxedConnectorIntegration
pub type BoxedConnectorIntegration<'a, T, Req, Resp> =
Box<&'a (dyn ConnectorIntegration<T, Req, Resp> + Send + Sync)>;
/// trait ConnectorIntegrationAny
pub trait ConnectorIntegrationAny<T, Req, Resp>: Send + Sync + 'static {
/// fn get_connector_integration
fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp>;
}
impl<S, T, Req, Resp> ConnectorIntegrationAny<T, Req, Resp> for S
where
S: ConnectorIntegration<T, Req, Resp> + Send + Sync,
{
fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp> {
Box::new(self)
}
}
/// trait ConnectorIntegration
pub trait ConnectorIntegration<T, Req, Resp>: ConnectorIntegrationAny<T, Req, Resp> + Sync {
/// fn get_headers
fn get_headers(
&self,
_req: &RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
Ok(vec![])
}
/// fn get_content_type
fn get_content_type(&self) -> &'static str {
mime::APPLICATION_JSON.essence_str()
}
/// primarily used when creating signature based on request method of payment flow
fn get_http_method(&self) -> Method {
Method::Post
}
/// fn get_url
fn get_url(
&self,
_req: &RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(String::new())
}
/// fn get_request_body
fn get_request_body(
&self,
_req: &RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
Ok(RequestContent::Json(Box::new(json!(r#"{}"#))))
}
/// fn get_request_form_data
fn get_request_form_data(
&self,
_req: &RouterData<T, Req, Resp>,
) -> CustomResult<Option<reqwest::multipart::Form>, errors::ConnectorError> {
Ok(None)
}
/// fn build_request
fn build_request(
&self,
req: &RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<Option<Request>, errors::ConnectorError> {
metrics::UNIMPLEMENTED_FLOW.add(
&metrics::CONTEXT,
1,
&[metrics::add_attributes("connector", req.connector.clone())],
);
Ok(None)
}
/// fn handle_response
fn handle_response(
&self,
data: &RouterData<T, Req, Resp>,
event_builder: Option<&mut ConnectorEvent>,
_res: types::Response,
) -> CustomResult<RouterData<T, Req, Resp>, errors::ConnectorError>
where
T: Clone,
Req: Clone,
Resp: Clone,
{
event_builder.map(|e| e.set_error(json!({"error": "Not Implemented"})));
Ok(data.clone())
}
/// fn get_error_response
fn get_error_response(
&self,
res: types::Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
Ok(ErrorResponse::get_not_implemented())
}
/// fn get_5xx_error_response
fn get_5xx_error_response(
&self,
res: types::Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
let error_message = match res.status_code {
500 => "internal_server_error",
501 => "not_implemented",
502 => "bad_gateway",
503 => "service_unavailable",
504 => "gateway_timeout",
505 => "http_version_not_supported",
506 => "variant_also_negotiates",
507 => "insufficient_storage",
508 => "loop_detected",
510 => "not_extended",
511 => "network_authentication_required",
_ => "unknown_error",
};
Ok(ErrorResponse {
code: res.status_code.to_string(),
message: error_message.to_string(),
reason: String::from_utf8(res.response.to_vec()).ok(),
status_code: res.status_code,
attempt_status: None,
connector_transaction_id: None,
})
}
/// whenever capture sync is implemented at the connector side, this method should be overridden
fn get_multiple_capture_sync_method(
&self,
) -> CustomResult<CaptureSyncMethod, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("multiple capture sync".into()).into())
}
/// fn get_certificate
fn get_certificate(
&self,
_req: &RouterData<T, Req, Resp>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
Ok(None)
}
/// fn get_certificate_key
fn get_certificate_key(
&self,
_req: &RouterData<T, Req, Resp>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
Ok(None)
}
}
/// Sync Methods for multiple captures
#[derive(Debug)]
pub enum CaptureSyncMethod {
/// For syncing multiple captures individually
Individual,
/// For syncing multiple captures together
Bulk,
}

View File

@ -0,0 +1,132 @@
//! Configs interface
use router_derive;
use serde::Deserialize;
use storage_impl::errors::ApplicationError;
// struct Connectors
#[allow(missing_docs, missing_debug_implementations)]
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct Connectors {
pub aci: ConnectorParams,
#[cfg(feature = "payouts")]
pub adyen: ConnectorParamsWithSecondaryBaseUrl,
pub adyenplatform: ConnectorParams,
#[cfg(not(feature = "payouts"))]
pub adyen: ConnectorParams,
pub airwallex: ConnectorParams,
pub applepay: ConnectorParams,
pub authorizedotnet: ConnectorParams,
pub bambora: ConnectorParams,
pub bankofamerica: ConnectorParams,
pub billwerk: ConnectorParams,
pub bitpay: ConnectorParams,
pub bluesnap: ConnectorParamsWithSecondaryBaseUrl,
pub boku: ConnectorParams,
pub braintree: ConnectorParams,
pub cashtocode: ConnectorParams,
pub checkout: ConnectorParams,
pub coinbase: ConnectorParams,
pub cryptopay: ConnectorParams,
pub cybersource: ConnectorParams,
pub datatrans: ConnectorParams,
pub dlocal: ConnectorParams,
#[cfg(feature = "dummy_connector")]
pub dummyconnector: ConnectorParams,
pub ebanx: ConnectorParams,
pub fiserv: ConnectorParams,
pub forte: ConnectorParams,
pub globalpay: ConnectorParams,
pub globepay: ConnectorParams,
pub gocardless: ConnectorParams,
pub gpayments: ConnectorParams,
pub helcim: ConnectorParams,
pub iatapay: ConnectorParams,
pub klarna: ConnectorParams,
pub mifinity: ConnectorParams,
pub mollie: ConnectorParams,
pub multisafepay: ConnectorParams,
pub netcetera: ConnectorParams,
pub nexinets: ConnectorParams,
pub nmi: ConnectorParams,
pub noon: ConnectorParamsWithModeType,
pub nuvei: ConnectorParams,
pub opayo: ConnectorParams,
pub opennode: ConnectorParams,
pub payeezy: ConnectorParams,
pub payme: ConnectorParams,
pub payone: ConnectorParams,
pub paypal: ConnectorParams,
pub payu: ConnectorParams,
pub placetopay: ConnectorParams,
pub powertranz: ConnectorParams,
pub prophetpay: ConnectorParams,
pub rapyd: ConnectorParams,
pub riskified: ConnectorParams,
pub shift4: ConnectorParams,
pub signifyd: ConnectorParams,
pub square: ConnectorParams,
pub stax: ConnectorParams,
pub stripe: ConnectorParamsWithFileUploadUrl,
pub threedsecureio: ConnectorParams,
pub trustpay: ConnectorParamsWithMoreUrls,
pub tsys: ConnectorParams,
pub volt: ConnectorParams,
pub wise: ConnectorParams,
pub worldline: ConnectorParams,
pub worldpay: ConnectorParams,
pub zen: ConnectorParams,
pub zsl: ConnectorParams,
}
/// struct ConnectorParams
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParams {
/// base url
pub base_url: String,
/// secondary base url
pub secondary_base_url: Option<String>,
}
/// struct ConnectorParamsWithModeType
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithModeType {
/// base url
pub base_url: String,
/// secondary base url
pub secondary_base_url: Option<String>,
/// Can take values like Test or Live for Noon
pub key_mode: String,
}
/// struct ConnectorParamsWithMoreUrls
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithMoreUrls {
/// base url
pub base_url: String,
/// base url for bank redirects
pub base_url_bank_redirects: String,
}
/// struct ConnectorParamsWithFileUploadUrl
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithFileUploadUrl {
/// base url
pub base_url: String,
/// base url for file upload
pub base_url_file_upload: String,
}
/// struct ConnectorParamsWithSecondaryBaseUrl
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithSecondaryBaseUrl {
/// base url
pub base_url: String,
/// secondary base url
pub secondary_base_url: String,
}

View File

@ -0,0 +1,149 @@
//! Errors interface
use common_utils::errors::ErrorSwitch;
use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
/// Connector Errors
#[allow(missing_docs, missing_debug_implementations)]
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum ConnectorError {
#[error("Error while obtaining URL for the integration")]
FailedToObtainIntegrationUrl,
#[error("Failed to encode connector request")]
RequestEncodingFailed,
#[error("Request encoding failed : {0}")]
RequestEncodingFailedWithReason(String),
#[error("Parsing failed")]
ParsingFailed,
#[error("Failed to deserialize connector response")]
ResponseDeserializationFailed,
#[error("Failed to execute a processing step: {0:?}")]
ProcessingStepFailed(Option<bytes::Bytes>),
#[error("The connector returned an unexpected response: {0:?}")]
UnexpectedResponseError(bytes::Bytes),
#[error("Failed to parse custom routing rules from merchant account")]
RoutingRulesParsingError,
#[error("Failed to obtain preferred connector from merchant account")]
FailedToObtainPreferredConnector,
#[error("An invalid connector name was provided")]
InvalidConnectorName,
#[error("An invalid Wallet was used")]
InvalidWallet,
#[error("Failed to handle connector response")]
ResponseHandlingFailed,
#[error("Missing required field: {field_name}")]
MissingRequiredField { field_name: &'static str },
#[error("Missing required fields: {field_names:?}")]
MissingRequiredFields { field_names: Vec<&'static str> },
#[error("Failed to obtain authentication type")]
FailedToObtainAuthType,
#[error("Failed to obtain certificate")]
FailedToObtainCertificate,
#[error("Connector meta data not found")]
NoConnectorMetaData,
#[error("Failed to obtain certificate key")]
FailedToObtainCertificateKey,
#[error("This step has not been implemented for: {0}")]
NotImplemented(String),
#[error("{message} is not supported by {connector}")]
NotSupported {
message: String,
connector: &'static str,
},
#[error("{flow} flow not supported by {connector} connector")]
FlowNotSupported { flow: String, connector: String },
#[error("Capture method not supported")]
CaptureMethodNotSupported,
#[error("Missing connector mandate ID")]
MissingConnectorMandateID,
#[error("Missing connector transaction ID")]
MissingConnectorTransactionID,
#[error("Missing connector refund ID")]
MissingConnectorRefundID,
#[error("Missing apple pay tokenization data")]
MissingApplePayTokenData,
#[error("Webhooks not implemented for this connector")]
WebhooksNotImplemented,
#[error("Failed to decode webhook event body")]
WebhookBodyDecodingFailed,
#[error("Signature not found for incoming webhook")]
WebhookSignatureNotFound,
#[error("Failed to verify webhook source")]
WebhookSourceVerificationFailed,
#[error("Could not find merchant secret in DB for incoming webhook source verification")]
WebhookVerificationSecretNotFound,
#[error("Merchant secret found for incoming webhook source verification is invalid")]
WebhookVerificationSecretInvalid,
#[error("Incoming webhook object reference ID not found")]
WebhookReferenceIdNotFound,
#[error("Incoming webhook event type not found")]
WebhookEventTypeNotFound,
#[error("Incoming webhook event resource object not found")]
WebhookResourceObjectNotFound,
#[error("Could not respond to the incoming webhook event")]
WebhookResponseEncodingFailed,
#[error("Invalid Date/time format")]
InvalidDateFormat,
#[error("Date Formatting Failed")]
DateFormattingFailed,
#[error("Invalid Data format")]
InvalidDataFormat { field_name: &'static str },
#[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")]
MismatchedPaymentData,
#[error("Failed to parse {wallet_name} wallet token")]
InvalidWalletToken { wallet_name: String },
#[error("Missing Connector Related Transaction ID")]
MissingConnectorRelatedTransactionID { id: String },
#[error("File Validation failed")]
FileValidationFailed { reason: String },
#[error("Missing 3DS redirection payload: {field_name}")]
MissingConnectorRedirectionPayload { field_name: &'static str },
#[error("Failed at connector's end with code '{code}'")]
FailedAtConnector { message: String, code: String },
#[error("Payment Method Type not found")]
MissingPaymentMethodType,
#[error("Balance in the payment method is low")]
InSufficientBalanceInPaymentMethod,
#[error("Server responded with Request Timeout")]
RequestTimeoutReceived,
#[error("The given currency method is not configured with the given connector")]
CurrencyNotSupported {
message: String,
connector: &'static str,
},
#[error("Invalid Configuration")]
InvalidConnectorConfig { config: &'static str },
#[error("Failed to convert amount to required type")]
AmountConversionFailed,
}
impl ConnectorError {
/// fn is_connector_timeout
pub fn is_connector_timeout(&self) -> bool {
self == &Self::RequestTimeoutReceived
}
}
impl ErrorSwitch<ConnectorError> for common_utils::errors::ParsingError {
fn switch(&self) -> ConnectorError {
ConnectorError::ParsingFailed
}
}
impl ErrorSwitch<ApiErrorResponse> for ConnectorError {
fn switch(&self) -> ApiErrorResponse {
match self {
Self::WebhookSourceVerificationFailed => ApiErrorResponse::WebhookAuthenticationFailed,
Self::WebhookSignatureNotFound
| Self::WebhookReferenceIdNotFound
| Self::WebhookResourceObjectNotFound
| Self::WebhookBodyDecodingFailed
| Self::WebhooksNotImplemented => ApiErrorResponse::WebhookBadRequest,
Self::WebhookEventTypeNotFound => ApiErrorResponse::WebhookUnprocessableEntity,
Self::WebhookVerificationSecretInvalid => {
ApiErrorResponse::WebhookInvalidMerchantSecret
}
_ => ApiErrorResponse::InternalServerError,
}
}
}

View File

@ -0,0 +1,3 @@
//! Events interface
pub mod connector_api_logs;

View File

@ -0,0 +1,96 @@
//! Connector API logs interface
use common_utils::request::Method;
use router_env::tracing_actix_web::RequestId;
use serde::Serialize;
use serde_json::json;
use time::OffsetDateTime;
/// struct ConnectorEvent
#[derive(Debug, Serialize)]
pub struct ConnectorEvent {
connector_name: String,
flow: String,
request: String,
masked_response: Option<String>,
error: Option<String>,
url: String,
method: String,
payment_id: String,
merchant_id: String,
created_at: i128,
/// Connector Event Request ID
pub request_id: String,
latency: u128,
refund_id: Option<String>,
dispute_id: Option<String>,
status_code: u16,
}
impl ConnectorEvent {
/// fn new ConnectorEvent
#[allow(clippy::too_many_arguments)]
pub fn new(
connector_name: String,
flow: &str,
request: serde_json::Value,
url: String,
method: Method,
payment_id: String,
merchant_id: String,
request_id: Option<&RequestId>,
latency: u128,
refund_id: Option<String>,
dispute_id: Option<String>,
status_code: u16,
) -> Self {
Self {
connector_name,
flow: flow
.rsplit_once("::")
.map(|(_, s)| s)
.unwrap_or(flow)
.to_string(),
request: request.to_string(),
masked_response: None,
error: None,
url,
method: method.to_string(),
payment_id,
merchant_id,
created_at: OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000,
request_id: request_id
.map(|i| i.as_hyphenated().to_string())
.unwrap_or("NO_REQUEST_ID".to_string()),
latency,
refund_id,
dispute_id,
status_code,
}
}
/// fn set_response_body
pub fn set_response_body<T: Serialize>(&mut self, response: &T) {
match masking::masked_serialize(response) {
Ok(masked) => {
self.masked_response = Some(masked.to_string());
}
Err(er) => self.set_error(json!({"error": er.to_string()})),
}
}
/// fn set_error_response_body
pub fn set_error_response_body<T: Serialize>(&mut self, response: &T) {
match masking::masked_serialize(response) {
Ok(masked) => {
self.error = Some(masked.to_string());
}
Err(er) => self.set_error(json!({"error": er.to_string()})),
}
}
/// fn set_error
pub fn set_error(&mut self, error: serde_json::Value) {
self.error = Some(error.to_string());
}
}

View File

@ -1,7 +1,11 @@
//! Hyperswitch interface
#![warn(missing_docs, missing_debug_implementations)]
pub mod secrets_interface;
pub mod api;
pub mod configs;
pub mod encryption_interface;
pub mod errors;
pub mod events;
pub mod metrics;
pub mod secrets_interface;
pub mod types;

View File

@ -0,0 +1,16 @@
//! Metrics interface
use router_env::{counter_metric, global_meter, metrics_context, opentelemetry};
metrics_context!(CONTEXT);
global_meter!(GLOBAL_METER, "ROUTER_API");
counter_metric!(UNIMPLEMENTED_FLOW, GLOBAL_METER);
/// fn add attributes
pub fn add_attributes<T: Into<opentelemetry::Value>>(
key: &'static str,
value: T,
) -> opentelemetry::KeyValue {
opentelemetry::KeyValue::new(key, value)
}

View File

@ -0,0 +1,12 @@
//! Types interface
/// struct Response
#[derive(Clone, Debug)]
pub struct Response {
/// headers
pub headers: Option<http::HeaderMap>,
/// response
pub response: bytes::Bytes,
/// status code
pub status_code: u16,
}

View File

@ -17,6 +17,7 @@ use external_services::{
secrets_management::SecretsManagementConfig,
},
};
pub use hyperswitch_interfaces::configs::Connectors;
use hyperswitch_interfaces::secrets_interface::secret_state::{
RawSecret, SecretState, SecretStateContainer, SecuredSecret,
};
@ -544,117 +545,6 @@ pub struct SupportedConnectors {
pub wallets: Vec<String>,
}
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct Connectors {
pub aci: ConnectorParams,
#[cfg(feature = "payouts")]
pub adyen: ConnectorParamsWithSecondaryBaseUrl,
pub adyenplatform: ConnectorParams,
#[cfg(not(feature = "payouts"))]
pub adyen: ConnectorParams,
pub airwallex: ConnectorParams,
pub applepay: ConnectorParams,
pub authorizedotnet: ConnectorParams,
pub bambora: ConnectorParams,
pub bankofamerica: ConnectorParams,
pub billwerk: ConnectorParams,
pub bitpay: ConnectorParams,
pub bluesnap: ConnectorParamsWithSecondaryBaseUrl,
pub boku: ConnectorParams,
pub braintree: ConnectorParams,
pub cashtocode: ConnectorParams,
pub checkout: ConnectorParams,
pub coinbase: ConnectorParams,
pub cryptopay: ConnectorParams,
pub cybersource: ConnectorParams,
pub datatrans: ConnectorParams,
pub dlocal: ConnectorParams,
#[cfg(feature = "dummy_connector")]
pub dummyconnector: ConnectorParams,
pub ebanx: ConnectorParams,
pub fiserv: ConnectorParams,
pub forte: ConnectorParams,
pub globalpay: ConnectorParams,
pub globepay: ConnectorParams,
pub gocardless: ConnectorParams,
pub gpayments: ConnectorParams,
pub helcim: ConnectorParams,
pub iatapay: ConnectorParams,
pub klarna: ConnectorParams,
pub mifinity: ConnectorParams,
pub mollie: ConnectorParams,
pub multisafepay: ConnectorParams,
pub netcetera: ConnectorParams,
pub nexinets: ConnectorParams,
pub nmi: ConnectorParams,
pub noon: ConnectorParamsWithModeType,
pub nuvei: ConnectorParams,
pub opayo: ConnectorParams,
pub opennode: ConnectorParams,
pub payeezy: ConnectorParams,
pub payme: ConnectorParams,
pub payone: ConnectorParams,
pub paypal: ConnectorParams,
pub payu: ConnectorParams,
pub placetopay: ConnectorParams,
pub powertranz: ConnectorParams,
pub prophetpay: ConnectorParams,
pub rapyd: ConnectorParams,
pub riskified: ConnectorParams,
pub shift4: ConnectorParams,
pub signifyd: ConnectorParams,
pub square: ConnectorParams,
pub stax: ConnectorParams,
pub stripe: ConnectorParamsWithFileUploadUrl,
pub threedsecureio: ConnectorParams,
pub trustpay: ConnectorParamsWithMoreUrls,
pub tsys: ConnectorParams,
pub volt: ConnectorParams,
pub wise: ConnectorParams,
pub worldline: ConnectorParams,
pub worldpay: ConnectorParams,
pub zen: ConnectorParams,
pub zsl: ConnectorParams,
}
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParams {
pub base_url: String,
pub secondary_base_url: Option<String>,
}
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithModeType {
pub base_url: String,
pub secondary_base_url: Option<String>,
/// Can take values like Test or Live for Noon
pub key_mode: String,
}
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithMoreUrls {
pub base_url: String,
pub base_url_bank_redirects: String,
}
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithFileUploadUrl {
pub base_url: String,
pub base_url_file_upload: String,
}
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
#[serde(default)]
pub struct ConnectorParamsWithSecondaryBaseUrl {
pub base_url: String,
pub secondary_base_url: String,
}
#[cfg(feature = "kv_store")]
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]

View File

@ -1837,12 +1837,6 @@ where
json.parse_value(std::any::type_name::<T>()).switch()
}
impl common_utils::errors::ErrorSwitch<errors::ConnectorError> for errors::ParsingError {
fn switch(&self) -> errors::ConnectorError {
errors::ConnectorError::ParsingFailed
}
}
pub fn base64_decode(data: String) -> Result<Vec<u8>, Error> {
consts::BASE64_ENGINE
.decode(data)

View File

@ -14,6 +14,7 @@ pub use hyperswitch_domain_models::errors::{
api_error_response::{ApiErrorResponse, ErrorType, NotImplementedMessage},
StorageError as DataStorageError,
};
pub use hyperswitch_interfaces::errors::ConnectorError;
pub use redis_interface::errors::RedisError;
use scheduler::errors as sch_errors;
use storage_impl::errors as storage_impl_errors;
@ -111,118 +112,6 @@ pub fn http_not_implemented() -> actix_web::HttpResponse<BoxBody> {
.error_response()
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum ConnectorError {
#[error("Error while obtaining URL for the integration")]
FailedToObtainIntegrationUrl,
#[error("Failed to encode connector request")]
RequestEncodingFailed,
#[error("Request encoding failed : {0}")]
RequestEncodingFailedWithReason(String),
#[error("Parsing failed")]
ParsingFailed,
#[error("Failed to deserialize connector response")]
ResponseDeserializationFailed,
#[error("Failed to execute a processing step: {0:?}")]
ProcessingStepFailed(Option<bytes::Bytes>),
#[error("The connector returned an unexpected response: {0:?}")]
UnexpectedResponseError(bytes::Bytes),
#[error("Failed to parse custom routing rules from merchant account")]
RoutingRulesParsingError,
#[error("Failed to obtain preferred connector from merchant account")]
FailedToObtainPreferredConnector,
#[error("An invalid connector name was provided")]
InvalidConnectorName,
#[error("An invalid Wallet was used")]
InvalidWallet,
#[error("Failed to handle connector response")]
ResponseHandlingFailed,
#[error("Missing required field: {field_name}")]
MissingRequiredField { field_name: &'static str },
#[error("Missing required fields: {field_names:?}")]
MissingRequiredFields { field_names: Vec<&'static str> },
#[error("Failed to obtain authentication type")]
FailedToObtainAuthType,
#[error("Failed to obtain certificate")]
FailedToObtainCertificate,
#[error("Connector meta data not found")]
NoConnectorMetaData,
#[error("Failed to obtain certificate key")]
FailedToObtainCertificateKey,
#[error("This step has not been implemented for: {0}")]
NotImplemented(String),
#[error("{message} is not supported by {connector}")]
NotSupported {
message: String,
connector: &'static str,
},
#[error("{flow} flow not supported by {connector} connector")]
FlowNotSupported { flow: String, connector: String },
#[error("Capture method not supported")]
CaptureMethodNotSupported,
#[error("Missing connector mandate ID")]
MissingConnectorMandateID,
#[error("Missing connector transaction ID")]
MissingConnectorTransactionID,
#[error("Missing connector refund ID")]
MissingConnectorRefundID,
#[error("Missing apple pay tokenization data")]
MissingApplePayTokenData,
#[error("Webhooks not implemented for this connector")]
WebhooksNotImplemented,
#[error("Failed to decode webhook event body")]
WebhookBodyDecodingFailed,
#[error("Signature not found for incoming webhook")]
WebhookSignatureNotFound,
#[error("Failed to verify webhook source")]
WebhookSourceVerificationFailed,
#[error("Could not find merchant secret in DB for incoming webhook source verification")]
WebhookVerificationSecretNotFound,
#[error("Merchant secret found for incoming webhook source verification is invalid")]
WebhookVerificationSecretInvalid,
#[error("Incoming webhook object reference ID not found")]
WebhookReferenceIdNotFound,
#[error("Incoming webhook event type not found")]
WebhookEventTypeNotFound,
#[error("Incoming webhook event resource object not found")]
WebhookResourceObjectNotFound,
#[error("Could not respond to the incoming webhook event")]
WebhookResponseEncodingFailed,
#[error("Invalid Date/time format")]
InvalidDateFormat,
#[error("Date Formatting Failed")]
DateFormattingFailed,
#[error("Invalid Data format")]
InvalidDataFormat { field_name: &'static str },
#[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")]
MismatchedPaymentData,
#[error("Failed to parse {wallet_name} wallet token")]
InvalidWalletToken { wallet_name: String },
#[error("Missing Connector Related Transaction ID")]
MissingConnectorRelatedTransactionID { id: String },
#[error("File Validation failed")]
FileValidationFailed { reason: String },
#[error("Missing 3DS redirection payload: {field_name}")]
MissingConnectorRedirectionPayload { field_name: &'static str },
#[error("Failed at connector's end with code '{code}'")]
FailedAtConnector { message: String, code: String },
#[error("Payment Method Type not found")]
MissingPaymentMethodType,
#[error("Balance in the payment method is low")]
InSufficientBalanceInPaymentMethod,
#[error("Server responded with Request Timeout")]
RequestTimeoutReceived,
#[error("The given currency method is not configured with the given connector")]
CurrencyNotSupported {
message: String,
connector: &'static str,
},
#[error("Invalid Configuration")]
InvalidConnectorConfig { config: &'static str },
#[error("Failed to convert amount to required type")]
AmountConversionFailed,
}
#[derive(Debug, thiserror::Error)]
pub enum HealthCheckOutGoing {
#[error("Outgoing call failed with error: {message}")]
@ -335,12 +224,6 @@ pub enum ApplePayDecryptionError {
DerivingSharedSecretKeyFailed,
}
impl ConnectorError {
pub fn is_connector_timeout(&self) -> bool {
self == &Self::RequestTimeoutReceived
}
}
#[cfg(feature = "detailed_errors")]
pub mod error_stack_parsing {

View File

@ -1,25 +1,7 @@
use common_utils::errors::ErrorSwitch;
use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
use super::{ConnectorError, CustomersErrorResponse, StorageError};
impl ErrorSwitch<ApiErrorResponse> for ConnectorError {
fn switch(&self) -> ApiErrorResponse {
match self {
Self::WebhookSourceVerificationFailed => ApiErrorResponse::WebhookAuthenticationFailed,
Self::WebhookSignatureNotFound
| Self::WebhookReferenceIdNotFound
| Self::WebhookResourceObjectNotFound
| Self::WebhookBodyDecodingFailed
| Self::WebhooksNotImplemented => ApiErrorResponse::WebhookBadRequest,
Self::WebhookEventTypeNotFound => ApiErrorResponse::WebhookUnprocessableEntity,
Self::WebhookVerificationSecretInvalid => {
ApiErrorResponse::WebhookInvalidMerchantSecret
}
_ => ApiErrorResponse::InternalServerError,
}
}
}
use super::{CustomersErrorResponse, StorageError};
impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for CustomersErrorResponse {
fn switch(&self) -> api_models::errors::types::ApiErrorResponse {

View File

@ -1,95 +1,8 @@
use common_utils::request::Method;
use router_env::tracing_actix_web::RequestId;
use serde::Serialize;
use serde_json::json;
use time::OffsetDateTime;
pub use hyperswitch_interfaces::events::connector_api_logs::ConnectorEvent;
use super::EventType;
use crate::services::kafka::KafkaMessage;
#[derive(Debug, Serialize)]
pub struct ConnectorEvent {
connector_name: String,
flow: String,
request: String,
masked_response: Option<String>,
error: Option<String>,
url: String,
method: String,
payment_id: String,
merchant_id: String,
created_at: i128,
request_id: String,
latency: u128,
refund_id: Option<String>,
dispute_id: Option<String>,
status_code: u16,
}
impl ConnectorEvent {
#[allow(clippy::too_many_arguments)]
pub fn new(
connector_name: String,
flow: &str,
request: serde_json::Value,
url: String,
method: Method,
payment_id: String,
merchant_id: String,
request_id: Option<&RequestId>,
latency: u128,
refund_id: Option<String>,
dispute_id: Option<String>,
status_code: u16,
) -> Self {
Self {
connector_name,
flow: flow
.rsplit_once("::")
.map(|(_, s)| s)
.unwrap_or(flow)
.to_string(),
request: request.to_string(),
masked_response: None,
error: None,
url,
method: method.to_string(),
payment_id,
merchant_id,
created_at: OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000,
request_id: request_id
.map(|i| i.as_hyphenated().to_string())
.unwrap_or("NO_REQUEST_ID".to_string()),
latency,
refund_id,
dispute_id,
status_code,
}
}
pub fn set_response_body<T: Serialize>(&mut self, response: &T) {
match masking::masked_serialize(response) {
Ok(masked) => {
self.masked_response = Some(masked.to_string());
}
Err(er) => self.set_error(json!({"error": er.to_string()})),
}
}
pub fn set_error_response_body<T: Serialize>(&mut self, response: &T) {
match masking::masked_serialize(response) {
Ok(masked) => {
self.error = Some(masked.to_string());
}
Err(er) => self.set_error(json!({"error": er.to_string()})),
}
}
pub fn set_error(&mut self, error: serde_json::Value) {
self.error = Some(error.to_string());
}
}
impl KafkaMessage for ConnectorEvent {
fn event_type(&self) -> EventType {
EventType::ConnectorApiLogs

View File

@ -76,7 +76,6 @@ counter_metric!(REDIRECTION_TRIGGERED, GLOBAL_METER);
// Connector Level Metric
counter_metric!(REQUEST_BUILD_FAILURE, GLOBAL_METER);
counter_metric!(UNIMPLEMENTED_FLOW, GLOBAL_METER);
// Connector http status code metrics
counter_metric!(CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT, GLOBAL_METER);
counter_metric!(CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT, GLOBAL_METER);

View File

@ -25,6 +25,9 @@ use common_utils::{
};
use error_stack::{report, Report, ResultExt};
pub use hyperswitch_domain_models::router_response_types::RedirectForm;
pub use hyperswitch_interfaces::api::{
BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration, ConnectorIntegrationAny,
};
use masking::{Maskable, PeekInterface};
use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag};
use serde::Serialize;
@ -34,7 +37,7 @@ use tera::{Context, Tera};
use self::request::{HeaderExt, RequestBuilderExt};
use super::authentication::AuthenticateAndFetch;
use crate::{
configs::{settings::Connectors, Settings},
configs::Settings,
consts,
core::{
api_locking,
@ -58,22 +61,6 @@ use crate::{
},
};
pub type BoxedConnectorIntegration<'a, T, Req, Resp> =
Box<&'a (dyn ConnectorIntegration<T, Req, Resp> + Send + Sync)>;
pub trait ConnectorIntegrationAny<T, Req, Resp>: Send + Sync + 'static {
fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp>;
}
impl<S, T, Req, Resp> ConnectorIntegrationAny<T, Req, Resp> for S
where
S: ConnectorIntegration<T, Req, Resp> + Send + Sync,
{
fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp> {
Box::new(self)
}
}
pub trait ConnectorValidation: ConnectorCommon {
fn validate_capture_method(
&self,
@ -129,145 +116,6 @@ pub trait ConnectorValidation: ConnectorCommon {
}
}
#[async_trait::async_trait]
pub trait ConnectorIntegration<T, Req, Resp>: ConnectorIntegrationAny<T, Req, Resp> + Sync {
fn get_headers(
&self,
_req: &types::RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
Ok(vec![])
}
fn get_content_type(&self) -> &'static str {
mime::APPLICATION_JSON.essence_str()
}
/// primarily used when creating signature based on request method of payment flow
fn get_http_method(&self) -> Method {
Method::Post
}
fn get_url(
&self,
_req: &types::RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(String::new())
}
fn get_request_body(
&self,
_req: &types::RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
Ok(RequestContent::Json(Box::new(json!(r#"{}"#))))
}
fn get_request_form_data(
&self,
_req: &types::RouterData<T, Req, Resp>,
) -> CustomResult<Option<reqwest::multipart::Form>, errors::ConnectorError> {
Ok(None)
}
fn build_request(
&self,
req: &types::RouterData<T, Req, Resp>,
_connectors: &Connectors,
) -> CustomResult<Option<Request>, errors::ConnectorError> {
metrics::UNIMPLEMENTED_FLOW.add(
&metrics::CONTEXT,
1,
&[metrics::request::add_attributes(
"connector",
req.connector.clone(),
)],
);
Ok(None)
}
fn handle_response(
&self,
data: &types::RouterData<T, Req, Resp>,
event_builder: Option<&mut ConnectorEvent>,
_res: types::Response,
) -> CustomResult<types::RouterData<T, Req, Resp>, errors::ConnectorError>
where
T: Clone,
Req: Clone,
Resp: Clone,
{
event_builder.map(|e| e.set_error(json!({"error": "Not Implemented"})));
Ok(data.clone())
}
fn get_error_response(
&self,
res: types::Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
Ok(ErrorResponse::get_not_implemented())
}
fn get_5xx_error_response(
&self,
res: types::Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
let error_message = match res.status_code {
500 => "internal_server_error",
501 => "not_implemented",
502 => "bad_gateway",
503 => "service_unavailable",
504 => "gateway_timeout",
505 => "http_version_not_supported",
506 => "variant_also_negotiates",
507 => "insufficient_storage",
508 => "loop_detected",
510 => "not_extended",
511 => "network_authentication_required",
_ => "unknown_error",
};
Ok(ErrorResponse {
code: res.status_code.to_string(),
message: error_message.to_string(),
reason: String::from_utf8(res.response.to_vec()).ok(),
status_code: res.status_code,
attempt_status: None,
connector_transaction_id: None,
})
}
// whenever capture sync is implemented at the connector side, this method should be overridden
fn get_multiple_capture_sync_method(
&self,
) -> CustomResult<CaptureSyncMethod, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("multiple capture sync".into()).into())
}
fn get_certificate(
&self,
_req: &types::RouterData<T, Req, Resp>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
Ok(None)
}
fn get_certificate_key(
&self,
_req: &types::RouterData<T, Req, Resp>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
Ok(None)
}
}
pub enum CaptureSyncMethod {
Individual,
Bulk,
}
/// Handle the flow by interacting with connector module
/// `connector_request` is applicable only in case if the `CallConnectorAction` is `Trigger`
/// In other cases, It will be created if required, even if it is not passed

View File

@ -52,6 +52,7 @@ pub use hyperswitch_domain_models::{
VerifyWebhookSourceResponseData, VerifyWebhookStatus,
},
};
pub use hyperswitch_interfaces::types::Response;
pub use crate::core::payments::CustomerDetails;
#[cfg(feature = "payouts")]
@ -684,13 +685,6 @@ pub struct ConnectorsList {
pub connectors: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct Response {
pub headers: Option<http::HeaderMap>,
pub response: bytes::Bytes,
pub status_code: u16,
}
impl ForeignTryFrom<ConnectorAuthType> for AccessTokenRequestData {
type Error = errors::ApiErrorResponse;
fn foreign_try_from(connector_auth: ConnectorAuthType) -> Result<Self, Self::Error> {

View File

@ -52,6 +52,7 @@ pub fn validate_config(input: syn::DeriveInput) -> Result<proc_macro2::TokenStre
let expansion = quote::quote! {
impl #struct_name {
/// Validates that the configuration provided for the `parent_field` does not contain empty or default values
pub fn validate(&self, parent_field: &str) -> Result<(), ApplicationError> {
#(#function_expansions)*
Ok(())