mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(user): generate and delete sample data (#2987)
Co-authored-by: Rachit Naithani <rachit.naithani@juspay.in> Co-authored-by: Mani Chandra Dulam <mani.dchandra@juspay.in>
This commit is contained in:
@ -1,5 +1,7 @@
|
|||||||
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
||||||
|
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
use crate::user::sample_data::SampleDataRequest;
|
||||||
use crate::user::{
|
use crate::user::{
|
||||||
dashboard_metadata::{
|
dashboard_metadata::{
|
||||||
GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest,
|
GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest,
|
||||||
@ -29,3 +31,6 @@ common_utils::impl_misc_api_event_type!(
|
|||||||
CreateInternalUserRequest,
|
CreateInternalUserRequest,
|
||||||
UserMerchantCreate
|
UserMerchantCreate
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
common_utils::impl_misc_api_event_type!(SampleDataRequest);
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
use common_utils::pii;
|
use common_utils::pii;
|
||||||
use masking::Secret;
|
use masking::Secret;
|
||||||
pub mod dashboard_metadata;
|
pub mod dashboard_metadata;
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
pub mod sample_data;
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)]
|
#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)]
|
||||||
pub struct ConnectAccountRequest {
|
pub struct ConnectAccountRequest {
|
||||||
|
|||||||
23
crates/api_models/src/user/sample_data.rs
Normal file
23
crates/api_models/src/user/sample_data.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use common_enums::{AuthenticationType, CountryAlpha2};
|
||||||
|
use common_utils::{self};
|
||||||
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
|
use crate::enums::Connector;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug, serde::Serialize)]
|
||||||
|
pub struct SampleDataRequest {
|
||||||
|
pub record: Option<usize>,
|
||||||
|
pub connector: Option<Vec<Connector>>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
|
||||||
|
pub start_time: Option<PrimitiveDateTime>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
|
||||||
|
pub end_time: Option<PrimitiveDateTime>,
|
||||||
|
// The amount for each sample will be between min_amount and max_amount (in dollars)
|
||||||
|
pub min_amount: Option<i64>,
|
||||||
|
pub max_amount: Option<i64>,
|
||||||
|
pub currency: Option<Vec<common_enums::Currency>>,
|
||||||
|
pub auth_type: Option<Vec<AuthenticationType>>,
|
||||||
|
pub business_country: Option<CountryAlpha2>,
|
||||||
|
pub business_label: Option<String>,
|
||||||
|
pub profile_id: Option<String>,
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
use diesel::{associations::HasTable, ExpressionMethods};
|
use diesel::{associations::HasTable, ExpressionMethods};
|
||||||
use error_stack::report;
|
use error_stack::report;
|
||||||
use router_env::tracing::{self, instrument};
|
use router_env::tracing::{self, instrument};
|
||||||
|
pub mod sample_data;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{self},
|
errors::{self},
|
||||||
|
|||||||
139
crates/diesel_models/src/query/user/sample_data.rs
Normal file
139
crates/diesel_models/src/query/user/sample_data.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use async_bb8_diesel::AsyncRunQueryDsl;
|
||||||
|
use diesel::{associations::HasTable, debug_query, ExpressionMethods, TextExpressionMethods};
|
||||||
|
use error_stack::{IntoReport, ResultExt};
|
||||||
|
use router_env::logger;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors,
|
||||||
|
schema::{
|
||||||
|
payment_attempt::dsl as payment_attempt_dsl, payment_intent::dsl as payment_intent_dsl,
|
||||||
|
refund::dsl as refund_dsl,
|
||||||
|
},
|
||||||
|
user::sample_data::PaymentAttemptBatchNew,
|
||||||
|
PaymentAttempt, PaymentIntent, PaymentIntentNew, PgPooledConn, Refund, RefundNew,
|
||||||
|
StorageResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn insert_payment_intents(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
batch: Vec<PaymentIntentNew>,
|
||||||
|
) -> StorageResult<Vec<PaymentIntent>> {
|
||||||
|
let query = diesel::insert_into(<PaymentIntent>::table()).values(batch);
|
||||||
|
|
||||||
|
logger::debug!(query = %debug_query::<diesel::pg::Pg,_>(&query).to_string());
|
||||||
|
|
||||||
|
query
|
||||||
|
.get_results_async(conn)
|
||||||
|
.await
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::DatabaseError::Others)
|
||||||
|
.attach_printable("Error while inserting payment intents")
|
||||||
|
}
|
||||||
|
pub async fn insert_payment_attempts(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
batch: Vec<PaymentAttemptBatchNew>,
|
||||||
|
) -> StorageResult<Vec<PaymentAttempt>> {
|
||||||
|
let query = diesel::insert_into(<PaymentAttempt>::table()).values(batch);
|
||||||
|
|
||||||
|
logger::debug!(query = %debug_query::<diesel::pg::Pg,_>(&query).to_string());
|
||||||
|
|
||||||
|
query
|
||||||
|
.get_results_async(conn)
|
||||||
|
.await
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::DatabaseError::Others)
|
||||||
|
.attach_printable("Error while inserting payment attempts")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert_refunds(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
batch: Vec<RefundNew>,
|
||||||
|
) -> StorageResult<Vec<Refund>> {
|
||||||
|
let query = diesel::insert_into(<Refund>::table()).values(batch);
|
||||||
|
|
||||||
|
logger::debug!(query = %debug_query::<diesel::pg::Pg,_>(&query).to_string());
|
||||||
|
|
||||||
|
query
|
||||||
|
.get_results_async(conn)
|
||||||
|
.await
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::DatabaseError::Others)
|
||||||
|
.attach_printable("Error while inserting refunds")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_payment_intents(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> StorageResult<Vec<PaymentIntent>> {
|
||||||
|
let query = diesel::delete(<PaymentIntent>::table())
|
||||||
|
.filter(payment_intent_dsl::merchant_id.eq(merchant_id.to_owned()))
|
||||||
|
.filter(payment_intent_dsl::payment_id.like("test_%"));
|
||||||
|
|
||||||
|
logger::debug!(query = %debug_query::<diesel::pg::Pg,_>(&query).to_string());
|
||||||
|
|
||||||
|
query
|
||||||
|
.get_results_async(conn)
|
||||||
|
.await
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::DatabaseError::Others)
|
||||||
|
.attach_printable("Error while deleting payment intents")
|
||||||
|
.and_then(|result| match result.len() {
|
||||||
|
n if n > 0 => {
|
||||||
|
logger::debug!("{n} records deleted");
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
|
||||||
|
.attach_printable("No records deleted")),
|
||||||
|
_ => Ok(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub async fn delete_payment_attempts(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> StorageResult<Vec<PaymentAttempt>> {
|
||||||
|
let query = diesel::delete(<PaymentAttempt>::table())
|
||||||
|
.filter(payment_attempt_dsl::merchant_id.eq(merchant_id.to_owned()))
|
||||||
|
.filter(payment_attempt_dsl::payment_id.like("test_%"));
|
||||||
|
|
||||||
|
logger::debug!(query = %debug_query::<diesel::pg::Pg,_>(&query).to_string());
|
||||||
|
|
||||||
|
query
|
||||||
|
.get_results_async(conn)
|
||||||
|
.await
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::DatabaseError::Others)
|
||||||
|
.attach_printable("Error while deleting payment attempts")
|
||||||
|
.and_then(|result| match result.len() {
|
||||||
|
n if n > 0 => {
|
||||||
|
logger::debug!("{n} records deleted");
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
|
||||||
|
.attach_printable("No records deleted")),
|
||||||
|
_ => Ok(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_refunds(conn: &PgPooledConn, merchant_id: &str) -> StorageResult<Vec<Refund>> {
|
||||||
|
let query = diesel::delete(<Refund>::table())
|
||||||
|
.filter(refund_dsl::merchant_id.eq(merchant_id.to_owned()))
|
||||||
|
.filter(refund_dsl::payment_id.like("test_%"));
|
||||||
|
|
||||||
|
logger::debug!(query = %debug_query::<diesel::pg::Pg,_>(&query).to_string());
|
||||||
|
|
||||||
|
query
|
||||||
|
.get_results_async(conn)
|
||||||
|
.await
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::DatabaseError::Others)
|
||||||
|
.attach_printable("Error while deleting refunds")
|
||||||
|
.and_then(|result| match result.len() {
|
||||||
|
n if n > 0 => {
|
||||||
|
logger::debug!("{n} records deleted");
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
|
||||||
|
.attach_printable("No records deleted")),
|
||||||
|
_ => Ok(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ use crate::schema::users;
|
|||||||
|
|
||||||
pub mod dashboard_metadata;
|
pub mod dashboard_metadata;
|
||||||
|
|
||||||
|
pub mod sample_data;
|
||||||
#[derive(Clone, Debug, Identifiable, Queryable)]
|
#[derive(Clone, Debug, Identifiable, Queryable)]
|
||||||
#[diesel(table_name = users)]
|
#[diesel(table_name = users)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
|||||||
119
crates/diesel_models/src/user/sample_data.rs
Normal file
119
crates/diesel_models/src/user/sample_data.rs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
use common_enums::{
|
||||||
|
AttemptStatus, AuthenticationType, CaptureMethod, Currency, PaymentExperience, PaymentMethod,
|
||||||
|
PaymentMethodType,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
|
use crate::{enums::MandateDataType, schema::payment_attempt, PaymentAttemptNew};
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, Default, diesel::Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
#[diesel(table_name = payment_attempt)]
|
||||||
|
pub struct PaymentAttemptBatchNew {
|
||||||
|
pub payment_id: String,
|
||||||
|
pub merchant_id: String,
|
||||||
|
pub attempt_id: String,
|
||||||
|
pub status: AttemptStatus,
|
||||||
|
pub amount: i64,
|
||||||
|
pub currency: Option<Currency>,
|
||||||
|
pub save_to_locker: Option<bool>,
|
||||||
|
pub connector: Option<String>,
|
||||||
|
pub error_message: Option<String>,
|
||||||
|
pub offer_amount: Option<i64>,
|
||||||
|
pub surcharge_amount: Option<i64>,
|
||||||
|
pub tax_amount: Option<i64>,
|
||||||
|
pub payment_method_id: Option<String>,
|
||||||
|
pub payment_method: Option<PaymentMethod>,
|
||||||
|
pub capture_method: Option<CaptureMethod>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
|
||||||
|
pub capture_on: Option<PrimitiveDateTime>,
|
||||||
|
pub confirm: bool,
|
||||||
|
pub authentication_type: Option<AuthenticationType>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
|
||||||
|
pub created_at: Option<PrimitiveDateTime>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
|
||||||
|
pub modified_at: Option<PrimitiveDateTime>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
|
||||||
|
pub last_synced: Option<PrimitiveDateTime>,
|
||||||
|
pub cancellation_reason: Option<String>,
|
||||||
|
pub amount_to_capture: Option<i64>,
|
||||||
|
pub mandate_id: Option<String>,
|
||||||
|
pub browser_info: Option<serde_json::Value>,
|
||||||
|
pub payment_token: Option<String>,
|
||||||
|
pub error_code: Option<String>,
|
||||||
|
pub connector_metadata: Option<serde_json::Value>,
|
||||||
|
pub payment_experience: Option<PaymentExperience>,
|
||||||
|
pub payment_method_type: Option<PaymentMethodType>,
|
||||||
|
pub payment_method_data: Option<serde_json::Value>,
|
||||||
|
pub business_sub_label: Option<String>,
|
||||||
|
pub straight_through_algorithm: Option<serde_json::Value>,
|
||||||
|
pub preprocessing_step_id: Option<String>,
|
||||||
|
pub mandate_details: Option<MandateDataType>,
|
||||||
|
pub error_reason: Option<String>,
|
||||||
|
pub connector_response_reference_id: Option<String>,
|
||||||
|
pub connector_transaction_id: Option<String>,
|
||||||
|
pub multiple_capture_count: Option<i16>,
|
||||||
|
pub amount_capturable: i64,
|
||||||
|
pub updated_by: String,
|
||||||
|
pub merchant_connector_id: Option<String>,
|
||||||
|
pub authentication_data: Option<serde_json::Value>,
|
||||||
|
pub encoded_data: Option<String>,
|
||||||
|
pub unified_code: Option<String>,
|
||||||
|
pub unified_message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl PaymentAttemptBatchNew {
|
||||||
|
// Used to verify compatibility with PaymentAttemptTable
|
||||||
|
fn convert_into_normal_attempt_insert(self) -> PaymentAttemptNew {
|
||||||
|
PaymentAttemptNew {
|
||||||
|
payment_id: self.payment_id,
|
||||||
|
merchant_id: self.merchant_id,
|
||||||
|
attempt_id: self.attempt_id,
|
||||||
|
status: self.status,
|
||||||
|
amount: self.amount,
|
||||||
|
currency: self.currency,
|
||||||
|
save_to_locker: self.save_to_locker,
|
||||||
|
connector: self.connector,
|
||||||
|
error_message: self.error_message,
|
||||||
|
offer_amount: self.offer_amount,
|
||||||
|
surcharge_amount: self.surcharge_amount,
|
||||||
|
tax_amount: self.tax_amount,
|
||||||
|
payment_method_id: self.payment_method_id,
|
||||||
|
payment_method: self.payment_method,
|
||||||
|
capture_method: self.capture_method,
|
||||||
|
capture_on: self.capture_on,
|
||||||
|
confirm: self.confirm,
|
||||||
|
authentication_type: self.authentication_type,
|
||||||
|
created_at: self.created_at,
|
||||||
|
modified_at: self.modified_at,
|
||||||
|
last_synced: self.last_synced,
|
||||||
|
cancellation_reason: self.cancellation_reason,
|
||||||
|
amount_to_capture: self.amount_to_capture,
|
||||||
|
mandate_id: self.mandate_id,
|
||||||
|
browser_info: self.browser_info,
|
||||||
|
payment_token: self.payment_token,
|
||||||
|
error_code: self.error_code,
|
||||||
|
connector_metadata: self.connector_metadata,
|
||||||
|
payment_experience: self.payment_experience,
|
||||||
|
payment_method_type: self.payment_method_type,
|
||||||
|
payment_method_data: self.payment_method_data,
|
||||||
|
business_sub_label: self.business_sub_label,
|
||||||
|
straight_through_algorithm: self.straight_through_algorithm,
|
||||||
|
preprocessing_step_id: self.preprocessing_step_id,
|
||||||
|
mandate_details: self.mandate_details,
|
||||||
|
error_reason: self.error_reason,
|
||||||
|
multiple_capture_count: self.multiple_capture_count,
|
||||||
|
connector_response_reference_id: self.connector_response_reference_id,
|
||||||
|
amount_capturable: self.amount_capturable,
|
||||||
|
updated_by: self.updated_by,
|
||||||
|
merchant_connector_id: self.merchant_connector_id,
|
||||||
|
authentication_data: self.authentication_data,
|
||||||
|
encoded_data: self.encoded_data,
|
||||||
|
unified_code: self.unified_code,
|
||||||
|
unified_message: self.unified_message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ use crate::services::ApplicationResponse;
|
|||||||
|
|
||||||
pub type UserResult<T> = CustomResult<T, UserErrors>;
|
pub type UserResult<T> = CustomResult<T, UserErrors>;
|
||||||
pub type UserResponse<T> = CustomResult<ApplicationResponse<T>, UserErrors>;
|
pub type UserResponse<T> = CustomResult<ApplicationResponse<T>, UserErrors>;
|
||||||
|
pub mod sample_data;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum UserErrors {
|
pub enum UserErrors {
|
||||||
|
|||||||
73
crates/router/src/core/errors/user/sample_data.rs
Normal file
73
crates/router/src/core/errors/user/sample_data.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use api_models::errors::types::{ApiError, ApiErrorResponse};
|
||||||
|
use common_utils::errors::{CustomResult, ErrorSwitch, ErrorSwitchFrom};
|
||||||
|
use data_models::errors::StorageError;
|
||||||
|
|
||||||
|
pub type SampleDataResult<T> = CustomResult<T, SampleDataError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, thiserror::Error)]
|
||||||
|
pub enum SampleDataError {
|
||||||
|
#[error["Internal Server Error"]]
|
||||||
|
InternalServerError,
|
||||||
|
#[error("Data Does Not Exist")]
|
||||||
|
DataDoesNotExist,
|
||||||
|
#[error("Server Error")]
|
||||||
|
DatabaseError,
|
||||||
|
#[error("Merchant Id Not Found")]
|
||||||
|
MerchantIdNotFound,
|
||||||
|
#[error("Invalid Parameters")]
|
||||||
|
InvalidParameters,
|
||||||
|
#[error["Invalid Records"]]
|
||||||
|
InvalidRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorSwitch<ApiErrorResponse> for SampleDataError {
|
||||||
|
fn switch(&self) -> ApiErrorResponse {
|
||||||
|
match self {
|
||||||
|
Self::InternalServerError => ApiErrorResponse::InternalServerError(ApiError::new(
|
||||||
|
"SD",
|
||||||
|
0,
|
||||||
|
"Something went wrong",
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
Self::DatabaseError => ApiErrorResponse::InternalServerError(ApiError::new(
|
||||||
|
"SD",
|
||||||
|
1,
|
||||||
|
"Server Error(DB is down)",
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
Self::DataDoesNotExist => ApiErrorResponse::NotFound(ApiError::new(
|
||||||
|
"SD",
|
||||||
|
2,
|
||||||
|
"Sample Data not present for given request",
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
Self::MerchantIdNotFound => ApiErrorResponse::BadRequest(ApiError::new(
|
||||||
|
"SD",
|
||||||
|
3,
|
||||||
|
"Merchant ID not provided",
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
Self::InvalidParameters => ApiErrorResponse::BadRequest(ApiError::new(
|
||||||
|
"SD",
|
||||||
|
4,
|
||||||
|
"Invalid parameters to generate Sample Data",
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
Self::InvalidRange => ApiErrorResponse::BadRequest(ApiError::new(
|
||||||
|
"SD",
|
||||||
|
5,
|
||||||
|
"Records to be generated should be between range 10 and 100",
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorSwitchFrom<StorageError> for SampleDataError {
|
||||||
|
fn switch_from(error: &StorageError) -> Self {
|
||||||
|
match matches!(error, StorageError::ValueNotFound(_)) {
|
||||||
|
true => Self::DataDoesNotExist,
|
||||||
|
false => Self::DatabaseError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,8 @@ use crate::{
|
|||||||
types::domain,
|
types::domain,
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
pub mod sample_data;
|
||||||
|
|
||||||
pub mod dashboard_metadata;
|
pub mod dashboard_metadata;
|
||||||
|
|
||||||
|
|||||||
82
crates/router/src/core/user/sample_data.rs
Normal file
82
crates/router/src/core/user/sample_data.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use api_models::user::sample_data::SampleDataRequest;
|
||||||
|
use common_utils::errors::ReportSwitchExt;
|
||||||
|
use data_models::payments::payment_intent::PaymentIntentNew;
|
||||||
|
use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew};
|
||||||
|
|
||||||
|
pub type SampleDataApiResponse<T> = SampleDataResult<ApplicationResponse<T>>;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
core::errors::sample_data::SampleDataResult,
|
||||||
|
routes::AppState,
|
||||||
|
services::{authentication::UserFromToken, ApplicationResponse},
|
||||||
|
utils::user::sample_data::generate_sample_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn generate_sample_data_for_user(
|
||||||
|
state: AppState,
|
||||||
|
user_from_token: UserFromToken,
|
||||||
|
req: SampleDataRequest,
|
||||||
|
) -> SampleDataApiResponse<()> {
|
||||||
|
let sample_data =
|
||||||
|
generate_sample_data(&state, req, user_from_token.merchant_id.as_str()).await?;
|
||||||
|
|
||||||
|
let (payment_intents, payment_attempts, refunds): (
|
||||||
|
Vec<PaymentIntentNew>,
|
||||||
|
Vec<PaymentAttemptBatchNew>,
|
||||||
|
Vec<RefundNew>,
|
||||||
|
) = sample_data.into_iter().fold(
|
||||||
|
(Vec::new(), Vec::new(), Vec::new()),
|
||||||
|
|(mut pi, mut pa, mut rf), (payment_intent, payment_attempt, refund)| {
|
||||||
|
pi.push(payment_intent);
|
||||||
|
pa.push(payment_attempt);
|
||||||
|
if let Some(refund) = refund {
|
||||||
|
rf.push(refund);
|
||||||
|
}
|
||||||
|
(pi, pa, rf)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.insert_payment_intents_batch_for_sample_data(payment_intents)
|
||||||
|
.await
|
||||||
|
.switch()?;
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.insert_payment_attempts_batch_for_sample_data(payment_attempts)
|
||||||
|
.await
|
||||||
|
.switch()?;
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.insert_refunds_batch_for_sample_data(refunds)
|
||||||
|
.await
|
||||||
|
.switch()?;
|
||||||
|
|
||||||
|
Ok(ApplicationResponse::StatusOk)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_sample_data_for_user(
|
||||||
|
state: AppState,
|
||||||
|
user_from_token: UserFromToken,
|
||||||
|
_req: SampleDataRequest,
|
||||||
|
) -> SampleDataApiResponse<()> {
|
||||||
|
let merchant_id_del = user_from_token.merchant_id.as_str();
|
||||||
|
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.delete_payment_intents_for_sample_data(merchant_id_del)
|
||||||
|
.await
|
||||||
|
.switch()?;
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.delete_payment_attempts_for_sample_data(merchant_id_del)
|
||||||
|
.await
|
||||||
|
.switch()?;
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.delete_refunds_for_sample_data(merchant_id_del)
|
||||||
|
.await
|
||||||
|
.switch()?;
|
||||||
|
|
||||||
|
Ok(ApplicationResponse::StatusOk)
|
||||||
|
}
|
||||||
@ -100,6 +100,7 @@ pub trait StorageInterface:
|
|||||||
+ gsm::GsmInterface
|
+ gsm::GsmInterface
|
||||||
+ user::UserInterface
|
+ user::UserInterface
|
||||||
+ user_role::UserRoleInterface
|
+ user_role::UserRoleInterface
|
||||||
|
+ user::sample_data::BatchSampleDataInterface
|
||||||
+ 'static
|
+ 'static
|
||||||
{
|
{
|
||||||
fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface>;
|
fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface>;
|
||||||
|
|||||||
@ -23,7 +23,8 @@ use storage_impl::redis::kv_store::RedisConnInterface;
|
|||||||
use time::PrimitiveDateTime;
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
dashboard_metadata::DashboardMetadataInterface, user::UserInterface,
|
dashboard_metadata::DashboardMetadataInterface,
|
||||||
|
user::{sample_data::BatchSampleDataInterface, UserInterface},
|
||||||
user_role::UserRoleInterface,
|
user_role::UserRoleInterface,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -1951,3 +1952,118 @@ impl DashboardMetadataInterface for KafkaStore {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl BatchSampleDataInterface for KafkaStore {
|
||||||
|
async fn insert_payment_intents_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<data_models::payments::payment_intent::PaymentIntentNew>,
|
||||||
|
) -> CustomResult<Vec<data_models::payments::PaymentIntent>, data_models::errors::StorageError>
|
||||||
|
{
|
||||||
|
let payment_intents_list = self
|
||||||
|
.diesel_store
|
||||||
|
.insert_payment_intents_batch_for_sample_data(batch)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for payment_intent in payment_intents_list.iter() {
|
||||||
|
let _ = self
|
||||||
|
.kafka_producer
|
||||||
|
.log_payment_intent(payment_intent, None)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Ok(payment_intents_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_payment_attempts_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<diesel_models::user::sample_data::PaymentAttemptBatchNew>,
|
||||||
|
) -> CustomResult<
|
||||||
|
Vec<data_models::payments::payment_attempt::PaymentAttempt>,
|
||||||
|
data_models::errors::StorageError,
|
||||||
|
> {
|
||||||
|
let payment_attempts_list = self
|
||||||
|
.diesel_store
|
||||||
|
.insert_payment_attempts_batch_for_sample_data(batch)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for payment_attempt in payment_attempts_list.iter() {
|
||||||
|
let _ = self
|
||||||
|
.kafka_producer
|
||||||
|
.log_payment_attempt(payment_attempt, None)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Ok(payment_attempts_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_refunds_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<diesel_models::RefundNew>,
|
||||||
|
) -> CustomResult<Vec<diesel_models::Refund>, data_models::errors::StorageError> {
|
||||||
|
let refunds_list = self
|
||||||
|
.diesel_store
|
||||||
|
.insert_refunds_batch_for_sample_data(batch)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for refund in refunds_list.iter() {
|
||||||
|
let _ = self.kafka_producer.log_refund(refund, None).await;
|
||||||
|
}
|
||||||
|
Ok(refunds_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_payment_intents_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<data_models::payments::PaymentIntent>, data_models::errors::StorageError>
|
||||||
|
{
|
||||||
|
let payment_intents_list = self
|
||||||
|
.diesel_store
|
||||||
|
.delete_payment_intents_for_sample_data(merchant_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for payment_intent in payment_intents_list.iter() {
|
||||||
|
let _ = self
|
||||||
|
.kafka_producer
|
||||||
|
.log_payment_intent_delete(payment_intent)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Ok(payment_intents_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_payment_attempts_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<
|
||||||
|
Vec<data_models::payments::payment_attempt::PaymentAttempt>,
|
||||||
|
data_models::errors::StorageError,
|
||||||
|
> {
|
||||||
|
let payment_attempts_list = self
|
||||||
|
.diesel_store
|
||||||
|
.delete_payment_attempts_for_sample_data(merchant_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for payment_attempt in payment_attempts_list.iter() {
|
||||||
|
let _ = self
|
||||||
|
.kafka_producer
|
||||||
|
.log_payment_attempt_delete(payment_attempt)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(payment_attempts_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_refunds_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<diesel_models::Refund>, data_models::errors::StorageError> {
|
||||||
|
let refunds_list = self
|
||||||
|
.diesel_store
|
||||||
|
.delete_refunds_for_sample_data(merchant_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for refund in refunds_list.iter() {
|
||||||
|
let _ = self.kafka_producer.log_refund_delete(refund).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(refunds_list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ use crate::{
|
|||||||
core::errors::{self, CustomResult},
|
core::errors::{self, CustomResult},
|
||||||
services::Store,
|
services::Store,
|
||||||
};
|
};
|
||||||
|
pub mod sample_data;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait UserInterface {
|
pub trait UserInterface {
|
||||||
|
|||||||
205
crates/router/src/db/user/sample_data.rs
Normal file
205
crates/router/src/db/user/sample_data.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
use data_models::{
|
||||||
|
errors::StorageError,
|
||||||
|
payments::{payment_attempt::PaymentAttempt, payment_intent::PaymentIntentNew, PaymentIntent},
|
||||||
|
};
|
||||||
|
use diesel_models::{
|
||||||
|
errors::DatabaseError,
|
||||||
|
query::user::sample_data as sample_data_queries,
|
||||||
|
refund::{Refund, RefundNew},
|
||||||
|
user::sample_data::PaymentAttemptBatchNew,
|
||||||
|
};
|
||||||
|
use error_stack::{Report, ResultExt};
|
||||||
|
use storage_impl::DataModelExt;
|
||||||
|
|
||||||
|
use crate::{connection::pg_connection_write, core::errors::CustomResult, services::Store};
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait BatchSampleDataInterface {
|
||||||
|
async fn insert_payment_intents_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<PaymentIntentNew>,
|
||||||
|
) -> CustomResult<Vec<PaymentIntent>, StorageError>;
|
||||||
|
|
||||||
|
async fn insert_payment_attempts_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<PaymentAttemptBatchNew>,
|
||||||
|
) -> CustomResult<Vec<PaymentAttempt>, StorageError>;
|
||||||
|
|
||||||
|
async fn insert_refunds_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<RefundNew>,
|
||||||
|
) -> CustomResult<Vec<Refund>, StorageError>;
|
||||||
|
|
||||||
|
async fn delete_payment_intents_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<PaymentIntent>, StorageError>;
|
||||||
|
|
||||||
|
async fn delete_payment_attempts_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<PaymentAttempt>, StorageError>;
|
||||||
|
|
||||||
|
async fn delete_refunds_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<Refund>, StorageError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl BatchSampleDataInterface for Store {
|
||||||
|
async fn insert_payment_intents_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<PaymentIntentNew>,
|
||||||
|
) -> CustomResult<Vec<PaymentIntent>, StorageError> {
|
||||||
|
let conn = pg_connection_write(self)
|
||||||
|
.await
|
||||||
|
.change_context(StorageError::DatabaseConnectionError)?;
|
||||||
|
let new_intents = batch.into_iter().map(|i| i.to_storage_model()).collect();
|
||||||
|
sample_data_queries::insert_payment_intents(&conn, new_intents)
|
||||||
|
.await
|
||||||
|
.map_err(diesel_error_to_data_error)
|
||||||
|
.map(|v| {
|
||||||
|
v.into_iter()
|
||||||
|
.map(PaymentIntent::from_storage_model)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_payment_attempts_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<PaymentAttemptBatchNew>,
|
||||||
|
) -> CustomResult<Vec<PaymentAttempt>, StorageError> {
|
||||||
|
let conn = pg_connection_write(self)
|
||||||
|
.await
|
||||||
|
.change_context(StorageError::DatabaseConnectionError)?;
|
||||||
|
sample_data_queries::insert_payment_attempts(&conn, batch)
|
||||||
|
.await
|
||||||
|
.map_err(diesel_error_to_data_error)
|
||||||
|
.map(|res| {
|
||||||
|
res.into_iter()
|
||||||
|
.map(PaymentAttempt::from_storage_model)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async fn insert_refunds_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
batch: Vec<RefundNew>,
|
||||||
|
) -> CustomResult<Vec<Refund>, StorageError> {
|
||||||
|
let conn = pg_connection_write(self)
|
||||||
|
.await
|
||||||
|
.change_context(StorageError::DatabaseConnectionError)?;
|
||||||
|
sample_data_queries::insert_refunds(&conn, batch)
|
||||||
|
.await
|
||||||
|
.map_err(diesel_error_to_data_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_payment_intents_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<PaymentIntent>, StorageError> {
|
||||||
|
let conn = pg_connection_write(self)
|
||||||
|
.await
|
||||||
|
.change_context(StorageError::DatabaseConnectionError)?;
|
||||||
|
sample_data_queries::delete_payment_intents(&conn, merchant_id)
|
||||||
|
.await
|
||||||
|
.map_err(diesel_error_to_data_error)
|
||||||
|
.map(|v| {
|
||||||
|
v.into_iter()
|
||||||
|
.map(PaymentIntent::from_storage_model)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_payment_attempts_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<PaymentAttempt>, StorageError> {
|
||||||
|
let conn = pg_connection_write(self)
|
||||||
|
.await
|
||||||
|
.change_context(StorageError::DatabaseConnectionError)?;
|
||||||
|
sample_data_queries::delete_payment_attempts(&conn, merchant_id)
|
||||||
|
.await
|
||||||
|
.map_err(diesel_error_to_data_error)
|
||||||
|
.map(|res| {
|
||||||
|
res.into_iter()
|
||||||
|
.map(PaymentAttempt::from_storage_model)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async fn delete_refunds_for_sample_data(
|
||||||
|
&self,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<Refund>, StorageError> {
|
||||||
|
let conn = pg_connection_write(self)
|
||||||
|
.await
|
||||||
|
.change_context(StorageError::DatabaseConnectionError)?;
|
||||||
|
sample_data_queries::delete_refunds(&conn, merchant_id)
|
||||||
|
.await
|
||||||
|
.map_err(diesel_error_to_data_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl BatchSampleDataInterface for storage_impl::MockDb {
|
||||||
|
async fn insert_payment_intents_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
_batch: Vec<PaymentIntentNew>,
|
||||||
|
) -> CustomResult<Vec<PaymentIntent>, StorageError> {
|
||||||
|
Err(StorageError::MockDbError)?
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_payment_attempts_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
_batch: Vec<PaymentAttemptBatchNew>,
|
||||||
|
) -> CustomResult<Vec<PaymentAttempt>, StorageError> {
|
||||||
|
Err(StorageError::MockDbError)?
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_refunds_batch_for_sample_data(
|
||||||
|
&self,
|
||||||
|
_batch: Vec<RefundNew>,
|
||||||
|
) -> CustomResult<Vec<Refund>, StorageError> {
|
||||||
|
Err(StorageError::MockDbError)?
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_payment_intents_for_sample_data(
|
||||||
|
&self,
|
||||||
|
_merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<PaymentIntent>, StorageError> {
|
||||||
|
Err(StorageError::MockDbError)?
|
||||||
|
}
|
||||||
|
async fn delete_payment_attempts_for_sample_data(
|
||||||
|
&self,
|
||||||
|
_merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<PaymentAttempt>, StorageError> {
|
||||||
|
Err(StorageError::MockDbError)?
|
||||||
|
}
|
||||||
|
async fn delete_refunds_for_sample_data(
|
||||||
|
&self,
|
||||||
|
_merchant_id: &str,
|
||||||
|
) -> CustomResult<Vec<Refund>, StorageError> {
|
||||||
|
Err(StorageError::MockDbError)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This error conversion is re-used from storage_impl and is not DRY when it should be
|
||||||
|
// Ideally the impl's here should be defined in that crate avoiding this re-definition
|
||||||
|
fn diesel_error_to_data_error(diesel_error: Report<DatabaseError>) -> Report<StorageError> {
|
||||||
|
let new_err = match diesel_error.current_context() {
|
||||||
|
DatabaseError::DatabaseConnectionError => StorageError::DatabaseConnectionError,
|
||||||
|
DatabaseError::NotFound => StorageError::ValueNotFound("Value not found".to_string()),
|
||||||
|
DatabaseError::UniqueViolation => StorageError::DuplicateValue {
|
||||||
|
entity: "entity ",
|
||||||
|
key: None,
|
||||||
|
},
|
||||||
|
DatabaseError::NoFieldsToUpdate => {
|
||||||
|
StorageError::DatabaseError("No fields to update".to_string())
|
||||||
|
}
|
||||||
|
DatabaseError::QueryGenerationFailed => {
|
||||||
|
StorageError::DatabaseError("Query generation failed".to_string())
|
||||||
|
}
|
||||||
|
DatabaseError::Others => StorageError::DatabaseError("Others".to_string()),
|
||||||
|
};
|
||||||
|
diesel_error.change_context(new_err)
|
||||||
|
}
|
||||||
@ -820,8 +820,9 @@ pub struct User;
|
|||||||
#[cfg(feature = "olap")]
|
#[cfg(feature = "olap")]
|
||||||
impl User {
|
impl User {
|
||||||
pub fn server(state: AppState) -> Scope {
|
pub fn server(state: AppState) -> Scope {
|
||||||
web::scope("/user")
|
let mut route = web::scope("/user").app_data(web::Data::new(state));
|
||||||
.app_data(web::Data::new(state))
|
|
||||||
|
route = route
|
||||||
.service(web::resource("/signin").route(web::post().to(user_connect_account)))
|
.service(web::resource("/signin").route(web::post().to(user_connect_account)))
|
||||||
.service(web::resource("/signup").route(web::post().to(user_connect_account)))
|
.service(web::resource("/signup").route(web::post().to(user_connect_account)))
|
||||||
.service(web::resource("/v2/signin").route(web::post().to(user_connect_account)))
|
.service(web::resource("/v2/signin").route(web::post().to(user_connect_account)))
|
||||||
@ -842,7 +843,17 @@ impl User {
|
|||||||
.service(web::resource("/permission_info").route(web::get().to(get_authorization_info)))
|
.service(web::resource("/permission_info").route(web::get().to(get_authorization_info)))
|
||||||
.service(web::resource("/user/update_role").route(web::post().to(update_user_role)))
|
.service(web::resource("/user/update_role").route(web::post().to(update_user_role)))
|
||||||
.service(web::resource("/role/list").route(web::get().to(list_roles)))
|
.service(web::resource("/role/list").route(web::get().to(list_roles)))
|
||||||
.service(web::resource("/role/{role_id}").route(web::get().to(get_role)))
|
.service(web::resource("/role/{role_id}").route(web::get().to(get_role)));
|
||||||
|
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
{
|
||||||
|
route = route.service(
|
||||||
|
web::resource("/sample_data")
|
||||||
|
.route(web::post().to(generate_sample_data))
|
||||||
|
.route(web::delete().to(delete_sample_data)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
route
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -155,7 +155,9 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::VerifyPaymentConnector
|
| Flow::VerifyPaymentConnector
|
||||||
| Flow::InternalUserSignup
|
| Flow::InternalUserSignup
|
||||||
| Flow::SwitchMerchant
|
| Flow::SwitchMerchant
|
||||||
| Flow::UserMerchantAccountCreate => Self::User,
|
| Flow::UserMerchantAccountCreate
|
||||||
|
| Flow::GenerateSampleData
|
||||||
|
| Flow::DeleteSampleData => Self::User,
|
||||||
|
|
||||||
Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => {
|
Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => {
|
||||||
Self::UserRole
|
Self::UserRole
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use api_models::{errors::types::ApiErrorResponse, user as user_api};
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
use api_models::user::sample_data::SampleDataRequest;
|
||||||
|
use api_models::{
|
||||||
|
errors::types::ApiErrorResponse,
|
||||||
|
user::{self as user_api},
|
||||||
|
};
|
||||||
use common_utils::errors::ReportSwitchExt;
|
use common_utils::errors::ReportSwitchExt;
|
||||||
use router_env::Flow;
|
use router_env::Flow;
|
||||||
|
|
||||||
@ -158,3 +163,44 @@ pub async fn user_merchant_account_create(
|
|||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
pub async fn generate_sample_data(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
http_req: HttpRequest,
|
||||||
|
payload: web::Json<SampleDataRequest>,
|
||||||
|
) -> impl actix_web::Responder {
|
||||||
|
use crate::core::user::sample_data;
|
||||||
|
|
||||||
|
let flow = Flow::GenerateSampleData;
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state,
|
||||||
|
&http_req,
|
||||||
|
payload.into_inner(),
|
||||||
|
sample_data::generate_sample_data_for_user,
|
||||||
|
&auth::JWTAuth(Permission::MerchantAccountWrite),
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
pub async fn delete_sample_data(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
http_req: HttpRequest,
|
||||||
|
payload: web::Json<SampleDataRequest>,
|
||||||
|
) -> impl actix_web::Responder {
|
||||||
|
use crate::core::user::sample_data;
|
||||||
|
|
||||||
|
let flow = Flow::DeleteSampleData;
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state,
|
||||||
|
&http_req,
|
||||||
|
payload.into_inner(),
|
||||||
|
sample_data::delete_sample_data_for_user,
|
||||||
|
&auth::JWTAuth(Permission::MerchantAccountWrite),
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ use crate::{
|
|||||||
|
|
||||||
pub mod dashboard_metadata;
|
pub mod dashboard_metadata;
|
||||||
pub mod password;
|
pub mod password;
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
pub mod sample_data;
|
||||||
|
|
||||||
impl UserFromToken {
|
impl UserFromToken {
|
||||||
pub async fn get_merchant_account(&self, state: AppState) -> UserResult<MerchantAccount> {
|
pub async fn get_merchant_account(&self, state: AppState) -> UserResult<MerchantAccount> {
|
||||||
|
|||||||
291
crates/router/src/utils/user/sample_data.rs
Normal file
291
crates/router/src/utils/user/sample_data.rs
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
use api_models::{
|
||||||
|
enums::Connector::{DummyConnector4, DummyConnector7},
|
||||||
|
user::sample_data::SampleDataRequest,
|
||||||
|
};
|
||||||
|
use data_models::payments::payment_intent::PaymentIntentNew;
|
||||||
|
use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew};
|
||||||
|
use error_stack::{IntoReport, ResultExt};
|
||||||
|
use rand::{prelude::SliceRandom, thread_rng, Rng};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
consts,
|
||||||
|
core::errors::sample_data::{SampleDataError, SampleDataResult},
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub async fn generate_sample_data(
|
||||||
|
state: &AppState,
|
||||||
|
req: SampleDataRequest,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> SampleDataResult<Vec<(PaymentIntentNew, PaymentAttemptBatchNew, Option<RefundNew>)>> {
|
||||||
|
let merchant_id = merchant_id.to_string();
|
||||||
|
let sample_data_size: usize = req.record.unwrap_or(100);
|
||||||
|
|
||||||
|
if !(10..=100).contains(&sample_data_size) {
|
||||||
|
return Err(SampleDataError::InvalidRange.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_store = state
|
||||||
|
.store
|
||||||
|
.get_merchant_key_store_by_merchant_id(
|
||||||
|
merchant_id.as_str(),
|
||||||
|
&state.store.get_master_key().to_vec().into(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(SampleDataError::DatabaseError)?;
|
||||||
|
|
||||||
|
let merchant_from_db = state
|
||||||
|
.store
|
||||||
|
.find_merchant_account_by_merchant_id(merchant_id.as_str(), &key_store)
|
||||||
|
.await
|
||||||
|
.change_context::<SampleDataError>(SampleDataError::DataDoesNotExist)?;
|
||||||
|
|
||||||
|
let merchant_parsed_details: Vec<api_models::admin::PrimaryBusinessDetails> =
|
||||||
|
serde_json::from_value(merchant_from_db.primary_business_details.clone())
|
||||||
|
.into_report()
|
||||||
|
.change_context(SampleDataError::InternalServerError)
|
||||||
|
.attach_printable("Error while parsing primary business details")?;
|
||||||
|
|
||||||
|
let business_country_default = merchant_parsed_details.get(0).map(|x| x.country);
|
||||||
|
|
||||||
|
let business_label_default = merchant_parsed_details.get(0).map(|x| x.business.clone());
|
||||||
|
|
||||||
|
let profile_id = crate::core::utils::get_profile_id_from_business_details(
|
||||||
|
business_country_default,
|
||||||
|
business_label_default.as_ref(),
|
||||||
|
&merchant_from_db,
|
||||||
|
req.profile_id.as_ref(),
|
||||||
|
&*state.store,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(SampleDataError::InternalServerError)
|
||||||
|
.attach_printable("Failed to get business profile")?;
|
||||||
|
|
||||||
|
// 10 percent payments should be failed
|
||||||
|
#[allow(clippy::as_conversions)]
|
||||||
|
let failure_attempts = usize::try_from((sample_data_size as f32 / 10.0).round() as i64)
|
||||||
|
.into_report()
|
||||||
|
.change_context(SampleDataError::InvalidParameters)?;
|
||||||
|
|
||||||
|
let failure_after_attempts = sample_data_size / failure_attempts;
|
||||||
|
|
||||||
|
// 20 percent refunds for payments
|
||||||
|
#[allow(clippy::as_conversions)]
|
||||||
|
let number_of_refunds = usize::try_from((sample_data_size as f32 / 5.0).round() as i64)
|
||||||
|
.into_report()
|
||||||
|
.change_context(SampleDataError::InvalidParameters)?;
|
||||||
|
|
||||||
|
let mut refunds_count = 0;
|
||||||
|
|
||||||
|
let mut random_array: Vec<usize> = (1..=sample_data_size).collect();
|
||||||
|
|
||||||
|
// Shuffle the array
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
random_array.shuffle(&mut rng);
|
||||||
|
|
||||||
|
let mut res: Vec<(PaymentIntentNew, PaymentAttemptBatchNew, Option<RefundNew>)> = Vec::new();
|
||||||
|
let start_time = req
|
||||||
|
.start_time
|
||||||
|
.unwrap_or(common_utils::date_time::now() - time::Duration::days(7))
|
||||||
|
.assume_utc()
|
||||||
|
.unix_timestamp();
|
||||||
|
let end_time = req
|
||||||
|
.end_time
|
||||||
|
.unwrap_or_else(common_utils::date_time::now)
|
||||||
|
.assume_utc()
|
||||||
|
.unix_timestamp();
|
||||||
|
|
||||||
|
let current_time = common_utils::date_time::now().assume_utc().unix_timestamp();
|
||||||
|
|
||||||
|
let min_amount = req.min_amount.unwrap_or(100);
|
||||||
|
let max_amount = req.max_amount.unwrap_or(min_amount + 100);
|
||||||
|
|
||||||
|
if min_amount > max_amount
|
||||||
|
|| start_time > end_time
|
||||||
|
|| start_time > current_time
|
||||||
|
|| end_time > current_time
|
||||||
|
{
|
||||||
|
return Err(SampleDataError::InvalidParameters.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let currency_vec = req.currency.unwrap_or(vec![common_enums::Currency::USD]);
|
||||||
|
let currency_vec_len = currency_vec.len();
|
||||||
|
|
||||||
|
let connector_vec = req
|
||||||
|
.connector
|
||||||
|
.unwrap_or(vec![DummyConnector4, DummyConnector7]);
|
||||||
|
let connector_vec_len = connector_vec.len();
|
||||||
|
|
||||||
|
let auth_type = req.auth_type.unwrap_or(vec![
|
||||||
|
common_enums::AuthenticationType::ThreeDs,
|
||||||
|
common_enums::AuthenticationType::NoThreeDs,
|
||||||
|
]);
|
||||||
|
let auth_type_len = auth_type.len();
|
||||||
|
|
||||||
|
if currency_vec_len == 0 || connector_vec_len == 0 || auth_type_len == 0 {
|
||||||
|
return Err(SampleDataError::InvalidParameters.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
for num in 1..=sample_data_size {
|
||||||
|
let payment_id = common_utils::generate_id_with_default_len("test");
|
||||||
|
let attempt_id = crate::utils::get_payment_attempt_id(&payment_id, 1);
|
||||||
|
let client_secret = common_utils::generate_id(
|
||||||
|
consts::ID_LENGTH,
|
||||||
|
format!("{}_secret", payment_id.clone()).as_str(),
|
||||||
|
);
|
||||||
|
let amount = thread_rng().gen_range(min_amount..=max_amount);
|
||||||
|
|
||||||
|
let created_at @ modified_at @ last_synced =
|
||||||
|
OffsetDateTime::from_unix_timestamp(thread_rng().gen_range(start_time..=end_time))
|
||||||
|
.map(common_utils::date_time::convert_to_pdt)
|
||||||
|
.unwrap_or(
|
||||||
|
req.start_time.unwrap_or_else(|| {
|
||||||
|
common_utils::date_time::now() - time::Duration::days(7)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// After some set of payments sample data will have a failed attempt
|
||||||
|
let is_failed_payment =
|
||||||
|
(random_array.get(num - 1).unwrap_or(&0) % failure_after_attempts) == 0;
|
||||||
|
|
||||||
|
let payment_intent = PaymentIntentNew {
|
||||||
|
payment_id: payment_id.clone(),
|
||||||
|
merchant_id: merchant_id.clone(),
|
||||||
|
status: match is_failed_payment {
|
||||||
|
true => common_enums::IntentStatus::Failed,
|
||||||
|
_ => common_enums::IntentStatus::Succeeded,
|
||||||
|
},
|
||||||
|
amount: amount * 100,
|
||||||
|
currency: Some(
|
||||||
|
*currency_vec
|
||||||
|
.get((num - 1) % currency_vec_len)
|
||||||
|
.unwrap_or(&common_enums::Currency::USD),
|
||||||
|
),
|
||||||
|
description: Some("This is a sample payment".to_string()),
|
||||||
|
created_at: Some(created_at),
|
||||||
|
modified_at: Some(modified_at),
|
||||||
|
last_synced: Some(last_synced),
|
||||||
|
client_secret: Some(client_secret),
|
||||||
|
business_country: business_country_default,
|
||||||
|
business_label: business_label_default.clone(),
|
||||||
|
active_attempt: data_models::RemoteStorageObject::ForeignID(attempt_id.clone()),
|
||||||
|
attempt_count: 1,
|
||||||
|
customer_id: Some("hs-dashboard-user".to_string()),
|
||||||
|
amount_captured: Some(amount * 100),
|
||||||
|
profile_id: Some(profile_id.clone()),
|
||||||
|
return_url: Default::default(),
|
||||||
|
metadata: Default::default(),
|
||||||
|
connector_id: Default::default(),
|
||||||
|
shipping_address_id: Default::default(),
|
||||||
|
billing_address_id: Default::default(),
|
||||||
|
statement_descriptor_name: Default::default(),
|
||||||
|
statement_descriptor_suffix: Default::default(),
|
||||||
|
setup_future_usage: Default::default(),
|
||||||
|
off_session: Default::default(),
|
||||||
|
order_details: Default::default(),
|
||||||
|
allowed_payment_method_types: Default::default(),
|
||||||
|
connector_metadata: Default::default(),
|
||||||
|
feature_metadata: Default::default(),
|
||||||
|
merchant_decision: Default::default(),
|
||||||
|
payment_link_id: Default::default(),
|
||||||
|
payment_confirm_source: Default::default(),
|
||||||
|
updated_by: merchant_from_db.storage_scheme.to_string(),
|
||||||
|
surcharge_applicable: Default::default(),
|
||||||
|
request_incremental_authorization: Default::default(),
|
||||||
|
incremental_authorization_allowed: Default::default(),
|
||||||
|
};
|
||||||
|
let payment_attempt = PaymentAttemptBatchNew {
|
||||||
|
attempt_id: attempt_id.clone(),
|
||||||
|
payment_id: payment_id.clone(),
|
||||||
|
connector_transaction_id: Some(attempt_id.clone()),
|
||||||
|
merchant_id: merchant_id.clone(),
|
||||||
|
status: match is_failed_payment {
|
||||||
|
true => common_enums::AttemptStatus::Failure,
|
||||||
|
_ => common_enums::AttemptStatus::Charged,
|
||||||
|
},
|
||||||
|
amount: amount * 100,
|
||||||
|
currency: payment_intent.currency,
|
||||||
|
connector: Some(
|
||||||
|
(*connector_vec
|
||||||
|
.get((num - 1) % connector_vec_len)
|
||||||
|
.unwrap_or(&DummyConnector4))
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
payment_method: Some(common_enums::PaymentMethod::Card),
|
||||||
|
payment_method_type: Some(get_payment_method_type(thread_rng().gen_range(1..=2))),
|
||||||
|
authentication_type: Some(
|
||||||
|
*auth_type
|
||||||
|
.get((num - 1) % auth_type_len)
|
||||||
|
.unwrap_or(&common_enums::AuthenticationType::NoThreeDs),
|
||||||
|
),
|
||||||
|
error_message: match is_failed_payment {
|
||||||
|
true => Some("This is a test payment which has a failed status".to_string()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
error_code: match is_failed_payment {
|
||||||
|
true => Some("HS001".to_string()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
confirm: true,
|
||||||
|
created_at: Some(created_at),
|
||||||
|
modified_at: Some(modified_at),
|
||||||
|
last_synced: Some(last_synced),
|
||||||
|
amount_to_capture: Some(amount * 100),
|
||||||
|
connector_response_reference_id: Some(attempt_id.clone()),
|
||||||
|
updated_by: merchant_from_db.storage_scheme.to_string(),
|
||||||
|
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let refund = if refunds_count < number_of_refunds && !is_failed_payment {
|
||||||
|
refunds_count += 1;
|
||||||
|
Some(RefundNew {
|
||||||
|
refund_id: common_utils::generate_id_with_default_len("test"),
|
||||||
|
internal_reference_id: common_utils::generate_id_with_default_len("test"),
|
||||||
|
external_reference_id: None,
|
||||||
|
payment_id: payment_id.clone(),
|
||||||
|
attempt_id: attempt_id.clone(),
|
||||||
|
merchant_id: merchant_id.clone(),
|
||||||
|
connector_transaction_id: attempt_id.clone(),
|
||||||
|
connector_refund_id: None,
|
||||||
|
description: Some("This is a sample refund".to_string()),
|
||||||
|
created_at: Some(created_at),
|
||||||
|
modified_at: Some(modified_at),
|
||||||
|
refund_reason: Some("Sample Refund".to_string()),
|
||||||
|
connector: payment_attempt
|
||||||
|
.connector
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(DummyConnector4.to_string()),
|
||||||
|
currency: *currency_vec
|
||||||
|
.get((num - 1) % currency_vec_len)
|
||||||
|
.unwrap_or(&common_enums::Currency::USD),
|
||||||
|
total_amount: amount * 100,
|
||||||
|
refund_amount: amount * 100,
|
||||||
|
refund_status: common_enums::RefundStatus::Success,
|
||||||
|
sent_to_gateway: true,
|
||||||
|
refund_type: diesel_models::enums::RefundType::InstantRefund,
|
||||||
|
metadata: None,
|
||||||
|
refund_arn: None,
|
||||||
|
profile_id: payment_intent.profile_id.clone(),
|
||||||
|
updated_by: merchant_from_db.storage_scheme.to_string(),
|
||||||
|
merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
res.push((payment_intent, payment_attempt, refund));
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_payment_method_type(num: u8) -> common_enums::PaymentMethodType {
|
||||||
|
let rem: u8 = (num) % 2;
|
||||||
|
match rem {
|
||||||
|
0 => common_enums::PaymentMethodType::Debit,
|
||||||
|
_ => common_enums::PaymentMethodType::Credit,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -279,6 +279,10 @@ pub enum Flow {
|
|||||||
UpdateUserRole,
|
UpdateUserRole,
|
||||||
/// Create merchant account for user in a org
|
/// Create merchant account for user in a org
|
||||||
UserMerchantAccountCreate,
|
UserMerchantAccountCreate,
|
||||||
|
/// Generate Sample Data
|
||||||
|
GenerateSampleData,
|
||||||
|
/// Delete Sample Data
|
||||||
|
DeleteSampleData,
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
-- Your SQL goes here
|
-- Your SQL goes here
|
||||||
CREATE TABLE IF NOT EXISTS dashboard_metadata (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id VARCHAR(64),
|
|
||||||
merchant_id VARCHAR(64) NOT NULL,
|
|
||||||
org_id VARCHAR(64) NOT NULL,
|
|
||||||
data_key VARCHAR(64) NOT NULL,
|
|
||||||
data_value JSON NOT NULL,
|
|
||||||
created_by VARCHAR(64) NOT NULL,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
|
||||||
last_modified_by VARCHAR(64) NOT NULL,
|
|
||||||
last_modified_at TIMESTAMP NOT NULL DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS dashboard_metadata_index ON dashboard_metadata (COALESCE(user_id,'0'), merchant_id, org_id, data_key);
|
CREATE TABLE IF NOT EXISTS dashboard_metadata (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id VARCHAR(64),
|
||||||
|
merchant_id VARCHAR(64) NOT NULL,
|
||||||
|
org_id VARCHAR(64) NOT NULL,
|
||||||
|
data_key VARCHAR(64) NOT NULL,
|
||||||
|
data_value JSON NOT NULL,
|
||||||
|
created_by VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
last_modified_by VARCHAR(64) NOT NULL,
|
||||||
|
last_modified_at TIMESTAMP NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS dashboard_metadata_index ON dashboard_metadata (
|
||||||
|
COALESCE(user_id, '0'),
|
||||||
|
merchant_id,
|
||||||
|
org_id,
|
||||||
|
data_key
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user