mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 13:30:39 +08:00
414 lines
13 KiB
Rust
414 lines
13 KiB
Rust
//! API interface
|
|
|
|
pub mod disputes;
|
|
pub mod disputes_v2;
|
|
pub mod files;
|
|
pub mod files_v2;
|
|
#[cfg(feature = "frm")]
|
|
pub mod fraud_check;
|
|
#[cfg(feature = "frm")]
|
|
pub mod fraud_check_v2;
|
|
pub mod payments;
|
|
pub mod payments_v2;
|
|
#[cfg(feature = "payouts")]
|
|
pub mod payouts;
|
|
#[cfg(feature = "payouts")]
|
|
pub mod payouts_v2;
|
|
pub mod refunds;
|
|
pub mod refunds_v2;
|
|
|
|
use common_enums::enums::{CallConnectorAction, CaptureMethod, PaymentAction, PaymentMethodType};
|
|
use common_utils::{
|
|
errors::CustomResult,
|
|
request::{Method, Request, RequestContent},
|
|
};
|
|
use error_stack::ResultExt;
|
|
use hyperswitch_domain_models::{
|
|
payment_method_data::PaymentMethodData,
|
|
router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
|
|
router_data_v2::{
|
|
flow_common_types::WebhookSourceVerifyData, AccessTokenFlowData, MandateRevokeFlowData,
|
|
},
|
|
router_flow_types::{mandate_revoke::MandateRevoke, AccessTokenAuth, VerifyWebhookSource},
|
|
router_request_types::{
|
|
AccessTokenRequestData, MandateRevokeRequestData, VerifyWebhookSourceRequestData,
|
|
},
|
|
router_response_types::{MandateRevokeResponseData, VerifyWebhookSourceResponseData},
|
|
};
|
|
use masking::Maskable;
|
|
use router_env::metrics::add_attributes;
|
|
use serde_json::json;
|
|
|
|
pub use self::{payments::*, refunds::*};
|
|
use crate::{
|
|
configs::Connectors, connector_integration_v2::ConnectorIntegrationV2, consts, 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 + ConnectorCommon
|
|
{
|
|
/// 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,
|
|
&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,
|
|
}
|
|
|
|
/// Connector accepted currency unit as either "Base" or "Minor"
|
|
#[derive(Debug)]
|
|
pub enum CurrencyUnit {
|
|
/// Base currency unit
|
|
Base,
|
|
/// Minor currency unit
|
|
Minor,
|
|
}
|
|
|
|
/// The trait that provides the common
|
|
pub trait ConnectorCommon {
|
|
/// Name of the connector (in lowercase).
|
|
fn id(&self) -> &'static str;
|
|
|
|
/// Connector accepted currency unit as either "Base" or "Minor"
|
|
fn get_currency_unit(&self) -> CurrencyUnit {
|
|
CurrencyUnit::Minor // Default implementation should be remove once it is implemented in all connectors
|
|
}
|
|
|
|
/// HTTP header used for authorization.
|
|
fn get_auth_header(
|
|
&self,
|
|
_auth_type: &ConnectorAuthType,
|
|
) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
/// HTTP `Content-Type` to be used for POST requests.
|
|
/// Defaults to `application/json`.
|
|
fn common_get_content_type(&self) -> &'static str {
|
|
"application/json"
|
|
}
|
|
|
|
// FIXME write doc - think about this
|
|
// fn headers(&self) -> Vec<(&str, &str)>;
|
|
|
|
/// The base URL for interacting with the connector's API.
|
|
fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str;
|
|
|
|
/// common error response for a connector if it is same in all case
|
|
fn build_error_response(
|
|
&self,
|
|
res: types::Response,
|
|
_event_builder: Option<&mut ConnectorEvent>,
|
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
|
Ok(ErrorResponse {
|
|
status_code: res.status_code,
|
|
code: consts::NO_ERROR_CODE.to_string(),
|
|
message: consts::NO_ERROR_MESSAGE.to_string(),
|
|
reason: None,
|
|
attempt_status: None,
|
|
connector_transaction_id: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Extended trait for connector common to allow functions with generic type
|
|
pub trait ConnectorCommonExt<Flow, Req, Resp>:
|
|
ConnectorCommon + ConnectorIntegration<Flow, Req, Resp>
|
|
{
|
|
/// common header builder when every request for the connector have same headers
|
|
fn build_headers(
|
|
&self,
|
|
_req: &RouterData<Flow, Req, Resp>,
|
|
_connectors: &Connectors,
|
|
) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
|
|
Ok(Vec::new())
|
|
}
|
|
}
|
|
|
|
/// trait ConnectorMandateRevoke
|
|
pub trait ConnectorMandateRevoke:
|
|
ConnectorIntegration<MandateRevoke, MandateRevokeRequestData, MandateRevokeResponseData>
|
|
{
|
|
}
|
|
|
|
/// trait ConnectorMandateRevokeV2
|
|
pub trait ConnectorMandateRevokeV2:
|
|
ConnectorIntegrationV2<
|
|
MandateRevoke,
|
|
MandateRevokeFlowData,
|
|
MandateRevokeRequestData,
|
|
MandateRevokeResponseData,
|
|
>
|
|
{
|
|
}
|
|
|
|
/// trait ConnectorAccessToken
|
|
pub trait ConnectorAccessToken:
|
|
ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken>
|
|
{
|
|
}
|
|
|
|
/// trait ConnectorAccessTokenV2
|
|
pub trait ConnectorAccessTokenV2:
|
|
ConnectorIntegrationV2<AccessTokenAuth, AccessTokenFlowData, AccessTokenRequestData, AccessToken>
|
|
{
|
|
}
|
|
|
|
/// trait ConnectorVerifyWebhookSource
|
|
pub trait ConnectorVerifyWebhookSource:
|
|
ConnectorIntegration<
|
|
VerifyWebhookSource,
|
|
VerifyWebhookSourceRequestData,
|
|
VerifyWebhookSourceResponseData,
|
|
>
|
|
{
|
|
}
|
|
|
|
/// trait ConnectorVerifyWebhookSourceV2
|
|
pub trait ConnectorVerifyWebhookSourceV2:
|
|
ConnectorIntegrationV2<
|
|
VerifyWebhookSource,
|
|
WebhookSourceVerifyData,
|
|
VerifyWebhookSourceRequestData,
|
|
VerifyWebhookSourceResponseData,
|
|
>
|
|
{
|
|
}
|
|
|
|
/// trait ConnectorValidation
|
|
pub trait ConnectorValidation: ConnectorCommon {
|
|
/// fn validate_capture_method
|
|
fn validate_capture_method(
|
|
&self,
|
|
capture_method: Option<CaptureMethod>,
|
|
_pmt: Option<PaymentMethodType>,
|
|
) -> CustomResult<(), errors::ConnectorError> {
|
|
let capture_method = capture_method.unwrap_or_default();
|
|
match capture_method {
|
|
CaptureMethod::Automatic => Ok(()),
|
|
CaptureMethod::Manual | CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => {
|
|
Err(errors::ConnectorError::NotSupported {
|
|
message: capture_method.to_string(),
|
|
connector: self.id(),
|
|
}
|
|
.into())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// fn validate_mandate_payment
|
|
fn validate_mandate_payment(
|
|
&self,
|
|
pm_type: Option<PaymentMethodType>,
|
|
_pm_data: PaymentMethodData,
|
|
) -> CustomResult<(), errors::ConnectorError> {
|
|
let connector = self.id();
|
|
match pm_type {
|
|
Some(pm_type) => Err(errors::ConnectorError::NotSupported {
|
|
message: format!("{} mandate payment", pm_type),
|
|
connector,
|
|
}
|
|
.into()),
|
|
None => Err(errors::ConnectorError::NotSupported {
|
|
message: " mandate payment".to_string(),
|
|
connector,
|
|
}
|
|
.into()),
|
|
}
|
|
}
|
|
|
|
/// fn validate_psync_reference_id
|
|
fn validate_psync_reference_id(
|
|
&self,
|
|
data: &hyperswitch_domain_models::router_request_types::PaymentsSyncData,
|
|
_is_three_ds: bool,
|
|
_status: common_enums::enums::AttemptStatus,
|
|
_connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
|
|
) -> CustomResult<(), errors::ConnectorError> {
|
|
data.connector_transaction_id
|
|
.get_connector_transaction_id()
|
|
.change_context(errors::ConnectorError::MissingConnectorTransactionID)
|
|
.map(|_| ())
|
|
}
|
|
|
|
/// fn is_webhook_source_verification_mandatory
|
|
fn is_webhook_source_verification_mandatory(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// trait ConnectorRedirectResponse
|
|
pub trait ConnectorRedirectResponse {
|
|
/// fn get_flow_type
|
|
fn get_flow_type(
|
|
&self,
|
|
_query_params: &str,
|
|
_json_payload: Option<serde_json::Value>,
|
|
_action: PaymentAction,
|
|
) -> CustomResult<CallConnectorAction, errors::ConnectorError> {
|
|
Ok(CallConnectorAction::Avoid)
|
|
}
|
|
}
|