mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(core): Add ability to verify connector credentials before integrating the connector (#2986)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use common_utils::{
|
||||
crypto::{Encryptable, OptionalEncryptableName},
|
||||
pii,
|
||||
@ -614,6 +616,36 @@ pub struct MerchantConnectorCreate {
|
||||
pub status: Option<api_enums::ConnectorStatus>,
|
||||
}
|
||||
|
||||
// Different patterns of authentication.
|
||||
#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(tag = "auth_type")]
|
||||
pub enum ConnectorAuthType {
|
||||
TemporaryAuth,
|
||||
HeaderKey {
|
||||
api_key: Secret<String>,
|
||||
},
|
||||
BodyKey {
|
||||
api_key: Secret<String>,
|
||||
key1: Secret<String>,
|
||||
},
|
||||
SignatureKey {
|
||||
api_key: Secret<String>,
|
||||
key1: Secret<String>,
|
||||
api_secret: Secret<String>,
|
||||
},
|
||||
MultiAuthKey {
|
||||
api_key: Secret<String>,
|
||||
key1: Secret<String>,
|
||||
api_secret: Secret<String>,
|
||||
key2: Secret<String>,
|
||||
},
|
||||
CurrencyAuthKey {
|
||||
auth_key_map: HashMap<common_enums::Currency, pii::SecretSerdeValue>,
|
||||
},
|
||||
#[default]
|
||||
NoKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MerchantConnectorWebhookDetails {
|
||||
|
||||
@ -27,4 +27,5 @@ pub mod routing;
|
||||
pub mod surcharge_decision_configs;
|
||||
pub mod user;
|
||||
pub mod verifications;
|
||||
pub mod verify_connector;
|
||||
pub mod webhooks;
|
||||
|
||||
11
crates/api_models/src/verify_connector.rs
Normal file
11
crates/api_models/src/verify_connector.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
||||
|
||||
use crate::{admin, enums};
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct VerifyConnectorRequest {
|
||||
pub connector_name: enums::Connector,
|
||||
pub connector_account_details: admin::ConnectorAuthType,
|
||||
}
|
||||
|
||||
common_utils::impl_misc_api_event_type!(VerifyConnectorRequest);
|
||||
@ -65,3 +65,8 @@ pub const JWT_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24 * 2; // 2 days
|
||||
#[cfg(feature = "email")]
|
||||
pub const EMAIL_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day
|
||||
pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin";
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
pub const VERIFY_CONNECTOR_ID_PREFIX: &str = "conn_verify";
|
||||
#[cfg(feature = "olap")]
|
||||
pub const VERIFY_CONNECTOR_MERCHANT_ID: &str = "test_merchant";
|
||||
|
||||
@ -28,4 +28,6 @@ pub mod user;
|
||||
pub mod utils;
|
||||
#[cfg(all(feature = "olap", feature = "kms"))]
|
||||
pub mod verification;
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod verify_connector;
|
||||
pub mod webhooks;
|
||||
|
||||
63
crates/router/src/core/verify_connector.rs
Normal file
63
crates/router/src/core/verify_connector.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use api_models::{enums::Connector, verify_connector::VerifyConnectorRequest};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
|
||||
use crate::{
|
||||
connector,
|
||||
core::errors,
|
||||
services,
|
||||
types::{
|
||||
api,
|
||||
api::verify_connector::{self as types, VerifyConnector},
|
||||
},
|
||||
utils::verify_connector as utils,
|
||||
AppState,
|
||||
};
|
||||
|
||||
pub async fn verify_connector_credentials(
|
||||
state: AppState,
|
||||
req: VerifyConnectorRequest,
|
||||
) -> errors::RouterResponse<()> {
|
||||
let boxed_connector = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
&req.connector_name.to_string(),
|
||||
api::GetToken::Connector,
|
||||
None,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven)?;
|
||||
|
||||
let card_details = utils::get_test_card_details(req.connector_name)?
|
||||
.ok_or(errors::ApiErrorResponse::FlowNotSupported {
|
||||
flow: "Verify credentials".to_string(),
|
||||
connector: req.connector_name.to_string(),
|
||||
})
|
||||
.into_report()?;
|
||||
|
||||
match req.connector_name {
|
||||
Connector::Stripe => {
|
||||
connector::Stripe::verify(
|
||||
&state,
|
||||
types::VerifyConnectorData {
|
||||
connector: *boxed_connector.connector,
|
||||
connector_auth: req.connector_account_details.into(),
|
||||
card_details,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
Connector::Paypal => connector::Paypal::get_access_token(
|
||||
&state,
|
||||
types::VerifyConnectorData {
|
||||
connector: *boxed_connector.connector,
|
||||
connector_auth: req.connector_account_details.into(),
|
||||
card_details,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_| services::ApplicationResponse::StatusOk),
|
||||
_ => Err(errors::ApiErrorResponse::FlowNotSupported {
|
||||
flow: "Verify credentials".to_string(),
|
||||
connector: req.connector_name.to_string(),
|
||||
})
|
||||
.into_report(),
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,8 @@ pub mod routing;
|
||||
pub mod user;
|
||||
#[cfg(all(feature = "olap", feature = "kms"))]
|
||||
pub mod verification;
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod verify_connector;
|
||||
pub mod webhooks;
|
||||
|
||||
pub mod locker_migration;
|
||||
|
||||
@ -30,6 +30,8 @@ use super::{cache::*, health::*};
|
||||
use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*};
|
||||
#[cfg(feature = "oltp")]
|
||||
use super::{ephemeral_key::*, payment_methods::*, webhooks::*};
|
||||
#[cfg(feature = "olap")]
|
||||
use crate::routes::verify_connector::payment_connector_verify;
|
||||
pub use crate::{
|
||||
configs::settings,
|
||||
db::{StorageImpl, StorageInterface},
|
||||
@ -548,6 +550,10 @@ impl MerchantConnectorAccount {
|
||||
use super::admin::*;
|
||||
|
||||
route = route
|
||||
.service(
|
||||
web::resource("/connectors/verify")
|
||||
.route(web::post().to(payment_connector_verify)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{merchant_id}/connectors")
|
||||
.route(web::post().to(payment_connector_create))
|
||||
|
||||
@ -147,7 +147,9 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::GsmRuleUpdate
|
||||
| Flow::GsmRuleDelete => Self::Gsm,
|
||||
|
||||
Flow::UserConnectAccount | Flow::ChangePassword => Self::User,
|
||||
Flow::UserConnectAccount | Flow::ChangePassword | Flow::VerifyPaymentConnector => {
|
||||
Self::User
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
crates/router/src/routes/verify_connector.rs
Normal file
28
crates/router/src/routes/verify_connector.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use api_models::verify_connector::VerifyConnectorRequest;
|
||||
use router_env::{instrument, tracing, Flow};
|
||||
|
||||
use super::AppState;
|
||||
use crate::{
|
||||
core::{api_locking, verify_connector},
|
||||
services::{self, authentication as auth, authorization::permissions::Permission},
|
||||
};
|
||||
|
||||
#[instrument(skip_all, fields(flow = ?Flow::VerifyPaymentConnector))]
|
||||
pub async fn payment_connector_verify(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<VerifyConnectorRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::VerifyPaymentConnector;
|
||||
Box::pin(services::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
|state, _: (), req| verify_connector::verify_connector_credentials(state, req),
|
||||
&auth::JWTAuth(Permission::MerchantConnectorAccountWrite),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@ -33,7 +33,7 @@ use crate::{
|
||||
payments::{PaymentData, RecurringMandatePaymentData},
|
||||
},
|
||||
services,
|
||||
types::storage::payment_attempt::PaymentAttemptExt,
|
||||
types::{storage::payment_attempt::PaymentAttemptExt, transformers::ForeignFrom},
|
||||
utils::OptionExt,
|
||||
};
|
||||
|
||||
@ -942,6 +942,78 @@ pub enum ConnectorAuthType {
|
||||
NoKey,
|
||||
}
|
||||
|
||||
impl From<api_models::admin::ConnectorAuthType> for ConnectorAuthType {
|
||||
fn from(value: api_models::admin::ConnectorAuthType) -> Self {
|
||||
match value {
|
||||
api_models::admin::ConnectorAuthType::TemporaryAuth => Self::TemporaryAuth,
|
||||
api_models::admin::ConnectorAuthType::HeaderKey { api_key } => {
|
||||
Self::HeaderKey { api_key }
|
||||
}
|
||||
api_models::admin::ConnectorAuthType::BodyKey { api_key, key1 } => {
|
||||
Self::BodyKey { api_key, key1 }
|
||||
}
|
||||
api_models::admin::ConnectorAuthType::SignatureKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
} => Self::SignatureKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
},
|
||||
api_models::admin::ConnectorAuthType::MultiAuthKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
key2,
|
||||
} => Self::MultiAuthKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
key2,
|
||||
},
|
||||
api_models::admin::ConnectorAuthType::CurrencyAuthKey { auth_key_map } => {
|
||||
Self::CurrencyAuthKey { auth_key_map }
|
||||
}
|
||||
api_models::admin::ConnectorAuthType::NoKey => Self::NoKey,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignFrom<ConnectorAuthType> for api_models::admin::ConnectorAuthType {
|
||||
fn foreign_from(from: ConnectorAuthType) -> Self {
|
||||
match from {
|
||||
ConnectorAuthType::TemporaryAuth => Self::TemporaryAuth,
|
||||
ConnectorAuthType::HeaderKey { api_key } => Self::HeaderKey { api_key },
|
||||
ConnectorAuthType::BodyKey { api_key, key1 } => Self::BodyKey { api_key, key1 },
|
||||
ConnectorAuthType::SignatureKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
} => Self::SignatureKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
},
|
||||
ConnectorAuthType::MultiAuthKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
key2,
|
||||
} => Self::MultiAuthKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
key2,
|
||||
},
|
||||
ConnectorAuthType::CurrencyAuthKey { auth_key_map } => {
|
||||
Self::CurrencyAuthKey { auth_key_map }
|
||||
}
|
||||
ConnectorAuthType::NoKey => Self::NoKey,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConnectorsList {
|
||||
pub connectors: Vec<String>,
|
||||
|
||||
@ -13,6 +13,8 @@ pub mod payments;
|
||||
pub mod payouts;
|
||||
pub mod refunds;
|
||||
pub mod routing;
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod verify_connector;
|
||||
pub mod webhooks;
|
||||
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
|
||||
181
crates/router/src/types/api/verify_connector.rs
Normal file
181
crates/router/src/types/api/verify_connector.rs
Normal file
@ -0,0 +1,181 @@
|
||||
pub mod paypal;
|
||||
pub mod stripe;
|
||||
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
|
||||
use crate::{
|
||||
consts,
|
||||
core::errors,
|
||||
services,
|
||||
services::ConnectorIntegration,
|
||||
types::{self, api, storage::enums as storage_enums},
|
||||
AppState,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerifyConnectorData {
|
||||
pub connector: &'static (dyn types::api::Connector + Sync),
|
||||
pub connector_auth: types::ConnectorAuthType,
|
||||
pub card_details: api::Card,
|
||||
}
|
||||
|
||||
impl VerifyConnectorData {
|
||||
fn get_payment_authorize_data(&self) -> types::PaymentsAuthorizeData {
|
||||
types::PaymentsAuthorizeData {
|
||||
payment_method_data: api::PaymentMethodData::Card(self.card_details.clone()),
|
||||
email: None,
|
||||
amount: 1000,
|
||||
confirm: true,
|
||||
currency: storage_enums::Currency::USD,
|
||||
mandate_id: None,
|
||||
webhook_url: None,
|
||||
customer_id: None,
|
||||
off_session: None,
|
||||
browser_info: None,
|
||||
session_token: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
capture_method: None,
|
||||
enrolled_for_3ds: false,
|
||||
router_return_url: None,
|
||||
surcharge_details: None,
|
||||
setup_future_usage: None,
|
||||
payment_experience: None,
|
||||
payment_method_type: None,
|
||||
statement_descriptor: None,
|
||||
setup_mandate_details: None,
|
||||
complete_authorize_url: None,
|
||||
related_transaction_id: None,
|
||||
statement_descriptor_suffix: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_router_data<F, R1, R2>(
|
||||
&self,
|
||||
request_data: R1,
|
||||
access_token: Option<types::AccessToken>,
|
||||
) -> types::RouterData<F, R1, R2> {
|
||||
let attempt_id =
|
||||
common_utils::generate_id_with_default_len(consts::VERIFY_CONNECTOR_ID_PREFIX);
|
||||
types::RouterData {
|
||||
flow: std::marker::PhantomData,
|
||||
status: storage_enums::AttemptStatus::Started,
|
||||
request: request_data,
|
||||
response: Err(errors::ApiErrorResponse::InternalServerError.into()),
|
||||
connector: self.connector.id().to_string(),
|
||||
auth_type: storage_enums::AuthenticationType::NoThreeDs,
|
||||
test_mode: None,
|
||||
return_url: None,
|
||||
attempt_id: attempt_id.clone(),
|
||||
description: None,
|
||||
customer_id: None,
|
||||
merchant_id: consts::VERIFY_CONNECTOR_MERCHANT_ID.to_string(),
|
||||
reference_id: None,
|
||||
access_token,
|
||||
session_token: None,
|
||||
payment_method: storage_enums::PaymentMethod::Card,
|
||||
amount_captured: None,
|
||||
preprocessing_id: None,
|
||||
payment_method_id: None,
|
||||
connector_customer: None,
|
||||
connector_auth_type: self.connector_auth.clone(),
|
||||
connector_meta_data: None,
|
||||
payment_method_token: None,
|
||||
connector_api_version: None,
|
||||
recurring_mandate_payment_data: None,
|
||||
connector_request_reference_id: attempt_id,
|
||||
address: types::PaymentAddress {
|
||||
shipping: None,
|
||||
billing: None,
|
||||
},
|
||||
payment_id: common_utils::generate_id_with_default_len(
|
||||
consts::VERIFY_CONNECTOR_ID_PREFIX,
|
||||
),
|
||||
#[cfg(feature = "payouts")]
|
||||
payout_method_data: None,
|
||||
#[cfg(feature = "payouts")]
|
||||
quote_id: None,
|
||||
payment_method_balance: None,
|
||||
connector_http_status_code: None,
|
||||
external_latency: None,
|
||||
apple_pay_flow: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait VerifyConnector {
|
||||
async fn verify(
|
||||
state: &AppState,
|
||||
connector_data: VerifyConnectorData,
|
||||
) -> errors::RouterResponse<()> {
|
||||
let authorize_data = connector_data.get_payment_authorize_data();
|
||||
let access_token = Self::get_access_token(state, connector_data.clone()).await?;
|
||||
let router_data = connector_data.get_router_data(authorize_data, access_token);
|
||||
|
||||
let request = connector_data
|
||||
.connector
|
||||
.build_request(&router_data, &state.conf.connectors)
|
||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "Payment request cannot be built".to_string(),
|
||||
})?
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
let response = services::call_connector_api(&state.to_owned(), request)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
match response {
|
||||
Ok(_) => Ok(services::ApplicationResponse::StatusOk),
|
||||
Err(error_response) => {
|
||||
Self::handle_payment_error_response::<
|
||||
api::Authorize,
|
||||
types::PaymentsAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
>(connector_data.connector, error_response)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_access_token(
|
||||
_state: &AppState,
|
||||
_connector_data: VerifyConnectorData,
|
||||
) -> errors::CustomResult<Option<types::AccessToken>, errors::ApiErrorResponse> {
|
||||
// AccessToken is None for the connectors without the AccessToken Flow.
|
||||
// If a connector has that, then it should override this implementation.
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn handle_payment_error_response<F, R1, R2>(
|
||||
connector: &(dyn types::api::Connector + Sync),
|
||||
error_response: types::Response,
|
||||
) -> errors::RouterResponse<()>
|
||||
where
|
||||
dyn types::api::Connector + Sync: ConnectorIntegration<F, R1, R2>,
|
||||
{
|
||||
let error = connector
|
||||
.get_error_response(error_response)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: error.reason.unwrap_or(error.message),
|
||||
})
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn handle_access_token_error_response<F, R1, R2>(
|
||||
connector: &(dyn types::api::Connector + Sync),
|
||||
error_response: types::Response,
|
||||
) -> errors::RouterResult<Option<types::AccessToken>>
|
||||
where
|
||||
dyn types::api::Connector + Sync: ConnectorIntegration<F, R1, R2>,
|
||||
{
|
||||
let error = connector
|
||||
.get_error_response(error_response)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: error.reason.unwrap_or(error.message),
|
||||
})
|
||||
.into_report()
|
||||
}
|
||||
}
|
||||
54
crates/router/src/types/api/verify_connector/paypal.rs
Normal file
54
crates/router/src/types/api/verify_connector/paypal.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use super::{VerifyConnector, VerifyConnectorData};
|
||||
use crate::{
|
||||
connector,
|
||||
core::errors,
|
||||
routes::AppState,
|
||||
services,
|
||||
types::{self, api},
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl VerifyConnector for connector::Paypal {
|
||||
async fn get_access_token(
|
||||
state: &AppState,
|
||||
connector_data: VerifyConnectorData,
|
||||
) -> errors::CustomResult<Option<types::AccessToken>, errors::ApiErrorResponse> {
|
||||
let token_data: types::AccessTokenRequestData =
|
||||
connector_data.connector_auth.clone().try_into()?;
|
||||
let router_data = connector_data.get_router_data(token_data, None);
|
||||
|
||||
let request = connector_data
|
||||
.connector
|
||||
.build_request(&router_data, &state.conf.connectors)
|
||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "Payment request cannot be built".to_string(),
|
||||
})?
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
let response = services::call_connector_api(&state.to_owned(), request)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
match response {
|
||||
Ok(res) => Some(
|
||||
connector_data
|
||||
.connector
|
||||
.handle_response(&router_data, res)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.response
|
||||
.map_err(|_| errors::ApiErrorResponse::InternalServerError.into()),
|
||||
)
|
||||
.transpose(),
|
||||
Err(response_data) => {
|
||||
Self::handle_access_token_error_response::<
|
||||
api::AccessTokenAuth,
|
||||
types::AccessTokenRequestData,
|
||||
types::AccessToken,
|
||||
>(connector_data.connector, response_data)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
crates/router/src/types/api/verify_connector/stripe.rs
Normal file
36
crates/router/src/types/api/verify_connector/stripe.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use router_env::env;
|
||||
|
||||
use super::VerifyConnector;
|
||||
use crate::{
|
||||
connector,
|
||||
core::errors,
|
||||
services::{self, ConnectorIntegration},
|
||||
types,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl VerifyConnector for connector::Stripe {
|
||||
async fn handle_payment_error_response<F, R1, R2>(
|
||||
connector: &(dyn types::api::Connector + Sync),
|
||||
error_response: types::Response,
|
||||
) -> errors::RouterResponse<()>
|
||||
where
|
||||
dyn types::api::Connector + Sync: ConnectorIntegration<F, R1, R2>,
|
||||
{
|
||||
let error = connector
|
||||
.get_error_response(error_response)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
match (env::which(), error.code.as_str()) {
|
||||
// In situations where an attempt is made to process a payment using a
|
||||
// Stripe production key along with a test card (which verify_connector is using),
|
||||
// Stripe will respond with a "card_declined" error. In production,
|
||||
// when this scenario occurs we will send back an "Ok" response.
|
||||
(env::Env::Production, "card_declined") => Ok(services::ApplicationResponse::StatusOk),
|
||||
_ => Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: error.reason.unwrap_or(error.message),
|
||||
})
|
||||
.into_report(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,8 @@ pub mod ext_traits;
|
||||
pub mod storage_partitioning;
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod user;
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod verify_connector;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
||||
49
crates/router/src/utils/verify_connector.rs
Normal file
49
crates/router/src/utils/verify_connector.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use api_models::enums::Connector;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
|
||||
use crate::{core::errors, types::api};
|
||||
|
||||
pub fn generate_card_from_details(
|
||||
card_number: String,
|
||||
card_exp_year: String,
|
||||
card_exp_month: String,
|
||||
card_cvv: String,
|
||||
) -> errors::RouterResult<api::Card> {
|
||||
Ok(api::Card {
|
||||
card_number: card_number
|
||||
.parse()
|
||||
.into_report()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Error while parsing card number")?,
|
||||
card_issuer: None,
|
||||
card_cvc: masking::Secret::new(card_cvv),
|
||||
card_network: None,
|
||||
card_exp_year: masking::Secret::new(card_exp_year),
|
||||
card_exp_month: masking::Secret::new(card_exp_month),
|
||||
card_holder_name: masking::Secret::new("HyperSwitch".to_string()),
|
||||
nick_name: None,
|
||||
card_type: None,
|
||||
card_issuing_country: None,
|
||||
bank_code: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_test_card_details(connector_name: Connector) -> errors::RouterResult<Option<api::Card>> {
|
||||
match connector_name {
|
||||
Connector::Stripe => Some(generate_card_from_details(
|
||||
"4242424242424242".to_string(),
|
||||
"2025".to_string(),
|
||||
"12".to_string(),
|
||||
"100".to_string(),
|
||||
))
|
||||
.transpose(),
|
||||
Connector::Paypal => Some(generate_card_from_details(
|
||||
"4111111111111111".to_string(),
|
||||
"2025".to_string(),
|
||||
"02".to_string(),
|
||||
"123".to_string(),
|
||||
))
|
||||
.transpose(),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
@ -259,6 +259,8 @@ pub enum Flow {
|
||||
DecisionManagerRetrieveConfig,
|
||||
/// Change password flow
|
||||
ChangePassword,
|
||||
/// Payment Connector Verify
|
||||
VerifyPaymentConnector,
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user