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"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes 1.6.0",
|
||||
"common_utils",
|
||||
"dyn-clone",
|
||||
"http 0.2.12",
|
||||
"hyperswitch_domain_models",
|
||||
"masking",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"reqwest",
|
||||
"router_derive",
|
||||
"router_env",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"storage_impl",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -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 }
|
||||
|
||||
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
|
||||
|
||||
#![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;
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
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)]
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user