mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +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};
|
||||
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
use crate::user::sample_data::SampleDataRequest;
|
||||
use crate::user::{
|
||||
dashboard_metadata::{
|
||||
GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest,
|
||||
@ -29,3 +31,6 @@ common_utils::impl_misc_api_event_type!(
|
||||
CreateInternalUserRequest,
|
||||
UserMerchantCreate
|
||||
);
|
||||
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
common_utils::impl_misc_api_event_type!(SampleDataRequest);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use common_utils::pii;
|
||||
use masking::Secret;
|
||||
pub mod dashboard_metadata;
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
pub mod sample_data;
|
||||
|
||||
#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)]
|
||||
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 error_stack::report;
|
||||
use router_env::tracing::{self, instrument};
|
||||
pub mod sample_data;
|
||||
|
||||
use crate::{
|
||||
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 sample_data;
|
||||
#[derive(Clone, Debug, Identifiable, Queryable)]
|
||||
#[diesel(table_name = users)]
|
||||
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 UserResponse<T> = CustomResult<ApplicationResponse<T>, UserErrors>;
|
||||
pub mod sample_data;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
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,
|
||||
utils,
|
||||
};
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
pub mod sample_data;
|
||||
|
||||
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
|
||||
+ user::UserInterface
|
||||
+ user_role::UserRoleInterface
|
||||
+ user::sample_data::BatchSampleDataInterface
|
||||
+ 'static
|
||||
{
|
||||
fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface>;
|
||||
|
||||
@ -23,7 +23,8 @@ use storage_impl::redis::kv_store::RedisConnInterface;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use super::{
|
||||
dashboard_metadata::DashboardMetadataInterface, user::UserInterface,
|
||||
dashboard_metadata::DashboardMetadataInterface,
|
||||
user::{sample_data::BatchSampleDataInterface, UserInterface},
|
||||
user_role::UserRoleInterface,
|
||||
};
|
||||
use crate::{
|
||||
@ -1951,3 +1952,118 @@ impl DashboardMetadataInterface for KafkaStore {
|
||||
.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},
|
||||
services::Store,
|
||||
};
|
||||
pub mod sample_data;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
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")]
|
||||
impl User {
|
||||
pub fn server(state: AppState) -> Scope {
|
||||
web::scope("/user")
|
||||
.app_data(web::Data::new(state))
|
||||
let mut route = web::scope("/user").app_data(web::Data::new(state));
|
||||
|
||||
route = route
|
||||
.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("/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("/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/{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::InternalUserSignup
|
||||
| Flow::SwitchMerchant
|
||||
| Flow::UserMerchantAccountCreate => Self::User,
|
||||
| Flow::UserMerchantAccountCreate
|
||||
| Flow::GenerateSampleData
|
||||
| Flow::DeleteSampleData => Self::User,
|
||||
|
||||
Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => {
|
||||
Self::UserRole
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
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 router_env::Flow;
|
||||
|
||||
@ -158,3 +163,44 @@ pub async fn user_merchant_account_create(
|
||||
))
|
||||
.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 password;
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
pub mod sample_data;
|
||||
|
||||
impl UserFromToken {
|
||||
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,
|
||||
/// Create merchant account for user in a org
|
||||
UserMerchantAccountCreate,
|
||||
/// Generate Sample Data
|
||||
GenerateSampleData,
|
||||
/// Delete Sample Data
|
||||
DeleteSampleData,
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
-- 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