mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(revenue_recovery): add support for updating additional card info data from csv to redis (#9233)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -41,6 +41,8 @@ pub mod proxy;
|
||||
pub mod recon;
|
||||
pub mod refunds;
|
||||
pub mod relay;
|
||||
#[cfg(feature = "v2")]
|
||||
pub mod revenue_recovery_data_backfill;
|
||||
pub mod routing;
|
||||
pub mod surcharge_decision_configs;
|
||||
pub mod three_ds_decision_rule;
|
||||
|
||||
150
crates/api_models/src/revenue_recovery_data_backfill.rs
Normal file
150
crates/api_models/src/revenue_recovery_data_backfill.rs
Normal file
@ -0,0 +1,150 @@
|
||||
use std::{collections::HashMap, fs::File, io::BufReader};
|
||||
|
||||
use actix_multipart::form::{tempfile::TempFile, MultipartForm};
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use common_enums::{CardNetwork, PaymentMethodType};
|
||||
use common_utils::events::ApiEventMetric;
|
||||
use csv::Reader;
|
||||
use masking::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::Date;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct RevenueRecoveryBackfillRequest {
|
||||
pub bin_number: Option<Secret<String>>,
|
||||
pub customer_id_resp: String,
|
||||
pub connector_payment_id: Option<String>,
|
||||
pub token: Option<Secret<String>>,
|
||||
pub exp_date: Option<Secret<String>>,
|
||||
pub card_network: Option<CardNetwork>,
|
||||
pub payment_method_sub_type: Option<PaymentMethodType>,
|
||||
pub clean_bank_name: Option<String>,
|
||||
pub country_name: Option<String>,
|
||||
pub daily_retry_history: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct RevenueRecoveryDataBackfillResponse {
|
||||
pub processed_records: usize,
|
||||
pub failed_records: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CsvParsingResult {
|
||||
pub records: Vec<RevenueRecoveryBackfillRequest>,
|
||||
pub failed_records: Vec<CsvParsingError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CsvParsingError {
|
||||
pub row_number: usize,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
/// Comprehensive card
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ComprehensiveCardData {
|
||||
pub card_type: Option<String>,
|
||||
pub card_exp_month: Option<Secret<String>>,
|
||||
pub card_exp_year: Option<Secret<String>>,
|
||||
pub card_network: Option<CardNetwork>,
|
||||
pub card_issuer: Option<String>,
|
||||
pub card_issuing_country: Option<String>,
|
||||
pub daily_retry_history: Option<HashMap<Date, i32>>,
|
||||
}
|
||||
|
||||
impl ApiEventMetric for RevenueRecoveryDataBackfillResponse {
|
||||
fn get_api_event_type(&self) -> Option<common_utils::events::ApiEventsType> {
|
||||
Some(common_utils::events::ApiEventsType::Miscellaneous)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiEventMetric for CsvParsingResult {
|
||||
fn get_api_event_type(&self) -> Option<common_utils::events::ApiEventsType> {
|
||||
Some(common_utils::events::ApiEventsType::Miscellaneous)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiEventMetric for CsvParsingError {
|
||||
fn get_api_event_type(&self) -> Option<common_utils::events::ApiEventsType> {
|
||||
Some(common_utils::events::ApiEventsType::Miscellaneous)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum BackfillError {
|
||||
InvalidCardType(String),
|
||||
DatabaseError(String),
|
||||
RedisError(String),
|
||||
CsvParsingError(String),
|
||||
FileProcessingError(String),
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct BackfillQuery {
|
||||
pub cutoff_time: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BackfillError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidCardType(msg) => write!(f, "Invalid card type: {}", msg),
|
||||
Self::DatabaseError(msg) => write!(f, "Database error: {}", msg),
|
||||
Self::RedisError(msg) => write!(f, "Redis error: {}", msg),
|
||||
Self::CsvParsingError(msg) => write!(f, "CSV parsing error: {}", msg),
|
||||
Self::FileProcessingError(msg) => write!(f, "File processing error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for BackfillError {}
|
||||
|
||||
impl ResponseError for BackfillError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::BadRequest().json(serde_json::json!({
|
||||
"error": self.to_string()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, MultipartForm)]
|
||||
pub struct RevenueRecoveryDataBackfillForm {
|
||||
#[multipart(rename = "file")]
|
||||
pub file: TempFile,
|
||||
}
|
||||
|
||||
impl RevenueRecoveryDataBackfillForm {
|
||||
pub fn validate_and_get_records_with_errors(&self) -> Result<CsvParsingResult, BackfillError> {
|
||||
// Step 1: Open the file
|
||||
let file = File::open(self.file.file.path())
|
||||
.map_err(|error| BackfillError::FileProcessingError(error.to_string()))?;
|
||||
|
||||
let mut csv_reader = Reader::from_reader(BufReader::new(file));
|
||||
|
||||
// Step 2: Parse CSV into typed records
|
||||
let mut records = Vec::new();
|
||||
let mut failed_records = Vec::new();
|
||||
|
||||
for (row_index, record_result) in csv_reader
|
||||
.deserialize::<RevenueRecoveryBackfillRequest>()
|
||||
.enumerate()
|
||||
{
|
||||
match record_result {
|
||||
Ok(record) => {
|
||||
records.push(record);
|
||||
}
|
||||
Err(err) => {
|
||||
failed_records.push(CsvParsingError {
|
||||
row_number: row_index + 2, // +2 because enumerate starts at 0 and CSV has header row
|
||||
error: err.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CsvParsingResult {
|
||||
records,
|
||||
failed_records,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user