mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
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:
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -3695,11 +3695,22 @@ name = "hyperswitch_interfaces"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"bytes 1.6.0",
|
||||||
"common_utils",
|
"common_utils",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
|
"http 0.2.12",
|
||||||
|
"hyperswitch_domain_models",
|
||||||
"masking",
|
"masking",
|
||||||
|
"mime",
|
||||||
|
"once_cell",
|
||||||
|
"reqwest",
|
||||||
|
"router_derive",
|
||||||
|
"router_env",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"storage_impl",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -6,12 +6,28 @@ rust-version.workspace = true
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["dummy_connector", "payouts"]
|
||||||
|
dummy_connector = []
|
||||||
|
payouts = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1.79"
|
async-trait = "0.1.79"
|
||||||
|
bytes = "1.6.0"
|
||||||
dyn-clone = "1.0.17"
|
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 = { version = "1.0.197", features = ["derive"] }
|
||||||
|
serde_json = "1.0.115"
|
||||||
thiserror = "1.0.58"
|
thiserror = "1.0.58"
|
||||||
|
time = "0.3.35"
|
||||||
|
|
||||||
# First party crates
|
# First party crates
|
||||||
common_utils = { version = "0.1.0", path = "../common_utils" }
|
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" }
|
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 }
|
||||||
|
|||||||
183
crates/hyperswitch_interfaces/src/api.rs
Normal file
183
crates/hyperswitch_interfaces/src/api.rs
Normal 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,
|
||||||
|
}
|
||||||
132
crates/hyperswitch_interfaces/src/configs.rs
Normal file
132
crates/hyperswitch_interfaces/src/configs.rs
Normal 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,
|
||||||
|
}
|
||||||
149
crates/hyperswitch_interfaces/src/errors.rs
Normal file
149
crates/hyperswitch_interfaces/src/errors.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
crates/hyperswitch_interfaces/src/events.rs
Normal file
3
crates/hyperswitch_interfaces/src/events.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//! Events interface
|
||||||
|
|
||||||
|
pub mod connector_api_logs;
|
||||||
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,11 @@
|
|||||||
//! Hyperswitch interface
|
//! Hyperswitch interface
|
||||||
|
|
||||||
#![warn(missing_docs, missing_debug_implementations)]
|
#![warn(missing_docs, missing_debug_implementations)]
|
||||||
|
|
||||||
pub mod secrets_interface;
|
pub mod api;
|
||||||
|
pub mod configs;
|
||||||
pub mod encryption_interface;
|
pub mod encryption_interface;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod events;
|
||||||
|
pub mod metrics;
|
||||||
|
pub mod secrets_interface;
|
||||||
|
pub mod types;
|
||||||
|
|||||||
16
crates/hyperswitch_interfaces/src/metrics.rs
Normal file
16
crates/hyperswitch_interfaces/src/metrics.rs
Normal 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)
|
||||||
|
}
|
||||||
12
crates/hyperswitch_interfaces/src/types.rs
Normal file
12
crates/hyperswitch_interfaces/src/types.rs
Normal 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,
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ use external_services::{
|
|||||||
secrets_management::SecretsManagementConfig,
|
secrets_management::SecretsManagementConfig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
pub use hyperswitch_interfaces::configs::Connectors;
|
||||||
use hyperswitch_interfaces::secrets_interface::secret_state::{
|
use hyperswitch_interfaces::secrets_interface::secret_state::{
|
||||||
RawSecret, SecretState, SecretStateContainer, SecuredSecret,
|
RawSecret, SecretState, SecretStateContainer, SecuredSecret,
|
||||||
};
|
};
|
||||||
@ -544,117 +545,6 @@ pub struct SupportedConnectors {
|
|||||||
pub wallets: Vec<String>,
|
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")]
|
#[cfg(feature = "kv_store")]
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
@ -1837,12 +1837,6 @@ where
|
|||||||
json.parse_value(std::any::type_name::<T>()).switch()
|
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> {
|
pub fn base64_decode(data: String) -> Result<Vec<u8>, Error> {
|
||||||
consts::BASE64_ENGINE
|
consts::BASE64_ENGINE
|
||||||
.decode(data)
|
.decode(data)
|
||||||
|
|||||||
@ -14,6 +14,7 @@ pub use hyperswitch_domain_models::errors::{
|
|||||||
api_error_response::{ApiErrorResponse, ErrorType, NotImplementedMessage},
|
api_error_response::{ApiErrorResponse, ErrorType, NotImplementedMessage},
|
||||||
StorageError as DataStorageError,
|
StorageError as DataStorageError,
|
||||||
};
|
};
|
||||||
|
pub use hyperswitch_interfaces::errors::ConnectorError;
|
||||||
pub use redis_interface::errors::RedisError;
|
pub use redis_interface::errors::RedisError;
|
||||||
use scheduler::errors as sch_errors;
|
use scheduler::errors as sch_errors;
|
||||||
use storage_impl::errors as storage_impl_errors;
|
use storage_impl::errors as storage_impl_errors;
|
||||||
@ -111,118 +112,6 @@ pub fn http_not_implemented() -> actix_web::HttpResponse<BoxBody> {
|
|||||||
.error_response()
|
.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)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum HealthCheckOutGoing {
|
pub enum HealthCheckOutGoing {
|
||||||
#[error("Outgoing call failed with error: {message}")]
|
#[error("Outgoing call failed with error: {message}")]
|
||||||
@ -335,12 +224,6 @@ pub enum ApplePayDecryptionError {
|
|||||||
DerivingSharedSecretKeyFailed,
|
DerivingSharedSecretKeyFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectorError {
|
|
||||||
pub fn is_connector_timeout(&self) -> bool {
|
|
||||||
self == &Self::RequestTimeoutReceived
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "detailed_errors")]
|
#[cfg(feature = "detailed_errors")]
|
||||||
pub mod error_stack_parsing {
|
pub mod error_stack_parsing {
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,7 @@
|
|||||||
use common_utils::errors::ErrorSwitch;
|
use common_utils::errors::ErrorSwitch;
|
||||||
use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
|
use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
|
||||||
|
|
||||||
use super::{ConnectorError, CustomersErrorResponse, StorageError};
|
use super::{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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for CustomersErrorResponse {
|
impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for CustomersErrorResponse {
|
||||||
fn switch(&self) -> api_models::errors::types::ApiErrorResponse {
|
fn switch(&self) -> api_models::errors::types::ApiErrorResponse {
|
||||||
|
|||||||
@ -1,95 +1,8 @@
|
|||||||
use common_utils::request::Method;
|
pub use hyperswitch_interfaces::events::connector_api_logs::ConnectorEvent;
|
||||||
use router_env::tracing_actix_web::RequestId;
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde_json::json;
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
use super::EventType;
|
use super::EventType;
|
||||||
use crate::services::kafka::KafkaMessage;
|
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 {
|
impl KafkaMessage for ConnectorEvent {
|
||||||
fn event_type(&self) -> EventType {
|
fn event_type(&self) -> EventType {
|
||||||
EventType::ConnectorApiLogs
|
EventType::ConnectorApiLogs
|
||||||
|
|||||||
@ -76,7 +76,6 @@ counter_metric!(REDIRECTION_TRIGGERED, GLOBAL_METER);
|
|||||||
|
|
||||||
// Connector Level Metric
|
// Connector Level Metric
|
||||||
counter_metric!(REQUEST_BUILD_FAILURE, GLOBAL_METER);
|
counter_metric!(REQUEST_BUILD_FAILURE, GLOBAL_METER);
|
||||||
counter_metric!(UNIMPLEMENTED_FLOW, GLOBAL_METER);
|
|
||||||
// Connector http status code metrics
|
// Connector http status code metrics
|
||||||
counter_metric!(CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT, GLOBAL_METER);
|
counter_metric!(CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT, GLOBAL_METER);
|
||||||
counter_metric!(CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT, GLOBAL_METER);
|
counter_metric!(CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT, GLOBAL_METER);
|
||||||
|
|||||||
@ -25,6 +25,9 @@ use common_utils::{
|
|||||||
};
|
};
|
||||||
use error_stack::{report, Report, ResultExt};
|
use error_stack::{report, Report, ResultExt};
|
||||||
pub use hyperswitch_domain_models::router_response_types::RedirectForm;
|
pub use hyperswitch_domain_models::router_response_types::RedirectForm;
|
||||||
|
pub use hyperswitch_interfaces::api::{
|
||||||
|
BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration, ConnectorIntegrationAny,
|
||||||
|
};
|
||||||
use masking::{Maskable, PeekInterface};
|
use masking::{Maskable, PeekInterface};
|
||||||
use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag};
|
use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -34,7 +37,7 @@ use tera::{Context, Tera};
|
|||||||
use self::request::{HeaderExt, RequestBuilderExt};
|
use self::request::{HeaderExt, RequestBuilderExt};
|
||||||
use super::authentication::AuthenticateAndFetch;
|
use super::authentication::AuthenticateAndFetch;
|
||||||
use crate::{
|
use crate::{
|
||||||
configs::{settings::Connectors, Settings},
|
configs::Settings,
|
||||||
consts,
|
consts,
|
||||||
core::{
|
core::{
|
||||||
api_locking,
|
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 {
|
pub trait ConnectorValidation: ConnectorCommon {
|
||||||
fn validate_capture_method(
|
fn validate_capture_method(
|
||||||
&self,
|
&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
|
/// Handle the flow by interacting with connector module
|
||||||
/// `connector_request` is applicable only in case if the `CallConnectorAction` is `Trigger`
|
/// `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
|
/// In other cases, It will be created if required, even if it is not passed
|
||||||
|
|||||||
@ -52,6 +52,7 @@ pub use hyperswitch_domain_models::{
|
|||||||
VerifyWebhookSourceResponseData, VerifyWebhookStatus,
|
VerifyWebhookSourceResponseData, VerifyWebhookStatus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
pub use hyperswitch_interfaces::types::Response;
|
||||||
|
|
||||||
pub use crate::core::payments::CustomerDetails;
|
pub use crate::core::payments::CustomerDetails;
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
@ -684,13 +685,6 @@ pub struct ConnectorsList {
|
|||||||
pub connectors: Vec<String>,
|
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 {
|
impl ForeignTryFrom<ConnectorAuthType> for AccessTokenRequestData {
|
||||||
type Error = errors::ApiErrorResponse;
|
type Error = errors::ApiErrorResponse;
|
||||||
fn foreign_try_from(connector_auth: ConnectorAuthType) -> Result<Self, Self::Error> {
|
fn foreign_try_from(connector_auth: ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||||
|
|||||||
@ -52,6 +52,7 @@ pub fn validate_config(input: syn::DeriveInput) -> Result<proc_macro2::TokenStre
|
|||||||
|
|
||||||
let expansion = quote::quote! {
|
let expansion = quote::quote! {
|
||||||
impl #struct_name {
|
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> {
|
pub fn validate(&self, parent_field: &str) -> Result<(), ApplicationError> {
|
||||||
#(#function_expansions)*
|
#(#function_expansions)*
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user