From 8e922d30da367bc0baf3cba64a86c385764fff39 Mon Sep 17 00:00:00 2001 From: Mrudul Vajpayee <124863642+mrudulvajpayee4935@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:55:29 +0530 Subject: [PATCH] feat(core): Add support for cards bin update (#7194) Co-authored-by: Mrudul Vajpayee Co-authored-by: Mrudul Vajpayee --- crates/api_models/src/cards_info.rs | 97 ++++++ crates/diesel_models/src/cards_info.rs | 28 +- crates/diesel_models/src/query/cards_info.rs | 32 +- crates/router/src/core/cards_info.rs | 299 +++++++++++++++++- crates/router/src/db/cards_info.rs | 40 ++- crates/router/src/db/kafka_store.rs | 15 + crates/router/src/routes/app.rs | 9 +- crates/router/src/routes/cards_info.rs | 71 ++++- crates/router/src/routes/lock_utils.rs | 5 +- crates/router/src/types/storage/cards_info.rs | 2 +- crates/router/src/types/transformers.rs | 42 ++- crates/router_env/src/logger/types.rs | 6 + 12 files changed, 629 insertions(+), 17 deletions(-) diff --git a/crates/api_models/src/cards_info.rs b/crates/api_models/src/cards_info.rs index 9eda9c7c57..14f3d0adce 100644 --- a/crates/api_models/src/cards_info.rs +++ b/crates/api_models/src/cards_info.rs @@ -1,7 +1,10 @@ use std::fmt::Debug; +use common_utils::events::ApiEventMetric; use utoipa::ToSchema; +use crate::enums; + #[derive(serde::Deserialize, ToSchema)] pub struct CardsInfoRequestParams { #[schema(example = "pay_OSERgeV9qAy7tlK7aKpc_secret_TuDUoh11Msxh12sXn3Yp")] @@ -29,3 +32,97 @@ pub struct CardInfoResponse { #[schema(example = "INDIA")] pub card_issuing_country: Option, } + +#[derive(serde::Serialize, Debug, ToSchema)] +pub struct CardInfoMigrateResponseRecord { + pub card_iin: Option, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + pub card_sub_type: Option, + pub card_issuing_country: Option, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct CardInfoCreateRequest { + pub card_iin: String, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + pub card_subtype: Option, + pub card_issuing_country: Option, + pub bank_code_id: Option, + pub bank_code: Option, + pub country_code: Option, + pub last_updated_provider: Option, +} + +impl ApiEventMetric for CardInfoCreateRequest {} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct CardInfoUpdateRequest { + pub card_iin: String, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + pub card_subtype: Option, + pub card_issuing_country: Option, + pub bank_code_id: Option, + pub bank_code: Option, + pub country_code: Option, + pub last_updated_provider: Option, + pub line_number: Option, +} + +impl ApiEventMetric for CardInfoUpdateRequest {} + +#[derive(Debug, Default, serde::Serialize)] +pub enum CardInfoMigrationStatus { + Success, + #[default] + Failed, +} +#[derive(Debug, Default, serde::Serialize)] +pub struct CardInfoMigrationResponse { + pub line_number: Option, + pub card_iin: String, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + pub card_sub_type: Option, + pub card_issuing_country: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub migration_error: Option, + pub migration_status: CardInfoMigrationStatus, +} +impl ApiEventMetric for CardInfoMigrationResponse {} + +type CardInfoMigrationResponseType = ( + Result, + CardInfoUpdateRequest, +); + +impl From for CardInfoMigrationResponse { + fn from((response, record): CardInfoMigrationResponseType) -> Self { + match response { + Ok(res) => Self { + card_iin: record.card_iin, + line_number: record.line_number, + card_issuer: res.card_issuer, + card_network: res.card_network, + card_type: res.card_type, + card_sub_type: res.card_sub_type, + card_issuing_country: res.card_issuing_country, + migration_status: CardInfoMigrationStatus::Success, + migration_error: None, + }, + Err(e) => Self { + card_iin: record.card_iin, + migration_status: CardInfoMigrationStatus::Failed, + migration_error: Some(e), + line_number: record.line_number, + ..Self::default() + }, + } + } +} diff --git a/crates/diesel_models/src/cards_info.rs b/crates/diesel_models/src/cards_info.rs index 3e0d24723d..990e89ac17 100644 --- a/crates/diesel_models/src/cards_info.rs +++ b/crates/diesel_models/src/cards_info.rs @@ -1,10 +1,17 @@ -use diesel::{Identifiable, Queryable, Selectable}; +use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use time::PrimitiveDateTime; use crate::{enums as storage_enums, schema::cards_info}; #[derive( - Clone, Debug, Queryable, Identifiable, Selectable, serde::Deserialize, serde::Serialize, + Clone, + Debug, + Queryable, + Identifiable, + Selectable, + serde::Deserialize, + serde::Serialize, + Insertable, )] #[diesel(table_name = cards_info, primary_key(card_iin), check_for_backend(diesel::pg::Pg))] pub struct CardInfo { @@ -21,3 +28,20 @@ pub struct CardInfo { pub last_updated: Option, pub last_updated_provider: Option, } + +#[derive( + Clone, Debug, PartialEq, Eq, AsChangeset, router_derive::DebugAsDisplay, serde::Deserialize, +)] +#[diesel(table_name = cards_info)] +pub struct UpdateCardInfo { + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + pub card_subtype: Option, + pub card_issuing_country: Option, + pub bank_code_id: Option, + pub bank_code: Option, + pub country_code: Option, + pub last_updated: Option, + pub last_updated_provider: Option, +} diff --git a/crates/diesel_models/src/query/cards_info.rs b/crates/diesel_models/src/query/cards_info.rs index 17b82a7865..0699f318d9 100644 --- a/crates/diesel_models/src/query/cards_info.rs +++ b/crates/diesel_models/src/query/cards_info.rs @@ -1,6 +1,13 @@ -use diesel::associations::HasTable; +use diesel::{associations::HasTable, ExpressionMethods}; +use error_stack::report; -use crate::{cards_info::CardInfo, query::generics, PgPooledConn, StorageResult}; +use crate::{ + cards_info::{CardInfo, UpdateCardInfo}, + errors, + query::generics, + schema::cards_info::dsl, + PgPooledConn, StorageResult, +}; impl CardInfo { pub async fn find_by_iin(conn: &PgPooledConn, card_iin: &str) -> StorageResult> { @@ -10,4 +17,25 @@ impl CardInfo { ) .await } + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } + pub async fn update( + conn: &PgPooledConn, + card_iin: String, + data: UpdateCardInfo, + ) -> StorageResult { + generics::generic_update_with_results::<::Table, UpdateCardInfo, _, _>( + conn, + dsl::card_iin.eq(card_iin), + data, + ) + .await? + .first() + .cloned() + .ok_or_else(|| { + report!(errors::DatabaseError::NotFound) + .attach_printable("Error while updating card_info entry") + }) + } } diff --git a/crates/router/src/core/cards_info.rs b/crates/router/src/core/cards_info.rs index 98719b5a1e..a23b533f70 100644 --- a/crates/router/src/core/cards_info.rs +++ b/crates/router/src/core/cards_info.rs @@ -1,15 +1,24 @@ +use actix_multipart::form::{bytes::Bytes, MultipartForm}; +use api_models::cards_info as cards_info_api_types; use common_utils::fp_utils::when; +use csv::Reader; +use diesel_models::cards_info as card_info_models; use error_stack::{report, ResultExt}; +use rdkafka::message::ToBytes; use router_env::{instrument, tracing}; use crate::{ core::{ - errors::{self, RouterResponse}, + errors::{self, RouterResponse, RouterResult, StorageErrorExt}, payments::helpers, }, + db::cards_info::CardsInfoInterface, routes, services::ApplicationResponse, - types::{domain, transformers::ForeignFrom}, + types::{ + domain, + transformers::{ForeignFrom, ForeignInto}, + }, }; fn verify_iin_length(card_iin: &str) -> Result<(), errors::ApiErrorResponse> { @@ -24,8 +33,8 @@ pub async fn retrieve_card_info( state: routes::SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - request: api_models::cards_info::CardsInfoRequest, -) -> RouterResponse { + request: cards_info_api_types::CardsInfoRequest, +) -> RouterResponse { let db = state.store.as_ref(); verify_iin_length(&request.card_iin)?; @@ -45,6 +54,286 @@ pub async fn retrieve_card_info( .ok_or(report!(errors::ApiErrorResponse::InvalidCardIin))?; Ok(ApplicationResponse::Json( - api_models::cards_info::CardInfoResponse::foreign_from(card_info), + cards_info_api_types::CardInfoResponse::foreign_from(card_info), )) } + +#[instrument(skip_all)] +pub async fn create_card_info( + state: routes::SessionState, + card_info_request: cards_info_api_types::CardInfoCreateRequest, +) -> RouterResponse { + let db = state.store.as_ref(); + CardsInfoInterface::add_card_info(db, card_info_request.foreign_into()) + .await + .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError { + message: "CardInfo with given key already exists in our records".to_string(), + }) + .map(|card_info| ApplicationResponse::Json(card_info.foreign_into())) +} + +#[instrument(skip_all)] +pub async fn update_card_info( + state: routes::SessionState, + card_info_request: cards_info_api_types::CardInfoUpdateRequest, +) -> RouterResponse { + let db = state.store.as_ref(); + CardsInfoInterface::update_card_info( + db, + card_info_request.card_iin, + card_info_models::UpdateCardInfo { + card_issuer: card_info_request.card_issuer, + card_network: card_info_request.card_network, + card_type: card_info_request.card_type, + card_subtype: card_info_request.card_subtype, + card_issuing_country: card_info_request.card_issuing_country, + bank_code_id: card_info_request.bank_code_id, + bank_code: card_info_request.bank_code, + country_code: card_info_request.country_code, + last_updated: Some(common_utils::date_time::now()), + last_updated_provider: card_info_request.last_updated_provider, + }, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "Card info with given key does not exist in our records".to_string(), + }) + .attach_printable("Failed while updating card info") + .map(|card_info| ApplicationResponse::Json(card_info.foreign_into())) +} + +#[derive(Debug, MultipartForm)] +pub struct CardsInfoUpdateForm { + #[multipart(limit = "1MB")] + pub file: Bytes, +} + +fn parse_cards_bin_csv( + data: &[u8], +) -> csv::Result> { + let mut csv_reader = Reader::from_reader(data); + let mut records = Vec::new(); + let mut id_counter = 0; + for result in csv_reader.deserialize() { + let mut record: cards_info_api_types::CardInfoUpdateRequest = result?; + id_counter += 1; + record.line_number = Some(id_counter); + records.push(record); + } + Ok(records) +} + +pub fn get_cards_bin_records( + form: CardsInfoUpdateForm, +) -> Result, errors::ApiErrorResponse> { + match parse_cards_bin_csv(form.file.data.to_bytes()) { + Ok(records) => Ok(records), + Err(e) => Err(errors::ApiErrorResponse::PreconditionFailed { + message: e.to_string(), + }), + } +} + +#[instrument(skip_all)] +pub async fn migrate_cards_info( + state: routes::SessionState, + card_info_records: Vec, +) -> RouterResponse> { + let mut result = Vec::new(); + for record in card_info_records { + let res = card_info_flow(record.clone(), state.clone()).await; + result.push(cards_info_api_types::CardInfoMigrationResponse::from(( + match res { + Ok(ApplicationResponse::Json(response)) => Ok(response), + Err(e) => Err(e.to_string()), + _ => Err("Failed to migrate card info".to_string()), + }, + record, + ))); + } + Ok(ApplicationResponse::Json(result)) +} + +pub trait State {} +pub trait TransitionTo {} +// Available states for card info migration +pub struct CardInfoFetch; +pub struct CardInfoAdd; +pub struct CardInfoUpdate; +pub struct CardInfoResponse; + +impl State for CardInfoFetch {} +impl State for CardInfoAdd {} +impl State for CardInfoUpdate {} +impl State for CardInfoResponse {} + +// State transitions for card info migration +impl TransitionTo for CardInfoFetch {} +impl TransitionTo for CardInfoFetch {} +impl TransitionTo for CardInfoAdd {} +impl TransitionTo for CardInfoUpdate {} + +// Async executor +pub struct CardInfoMigrateExecutor<'a> { + state: &'a routes::SessionState, + record: &'a cards_info_api_types::CardInfoUpdateRequest, +} + +impl<'a> CardInfoMigrateExecutor<'a> { + fn new( + state: &'a routes::SessionState, + record: &'a cards_info_api_types::CardInfoUpdateRequest, + ) -> Self { + Self { state, record } + } + + async fn fetch_card_info(&self) -> RouterResult> { + let db = self.state.store.as_ref(); + let maybe_card_info = db + .get_card_info(&self.record.card_iin) + .await + .change_context(errors::ApiErrorResponse::InvalidCardIin)?; + Ok(maybe_card_info) + } + + async fn add_card_info(&self) -> RouterResult { + let db = self.state.store.as_ref(); + let card_info = CardsInfoInterface::add_card_info(db, self.record.clone().foreign_into()) + .await + .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError { + message: "CardInfo with given key already exists in our records".to_string(), + })?; + Ok(card_info) + } + + async fn update_card_info(&self) -> RouterResult { + let db = self.state.store.as_ref(); + let card_info = CardsInfoInterface::update_card_info( + db, + self.record.card_iin.clone(), + card_info_models::UpdateCardInfo { + card_issuer: self.record.card_issuer.clone(), + card_network: self.record.card_network.clone(), + card_type: self.record.card_type.clone(), + card_subtype: self.record.card_subtype.clone(), + card_issuing_country: self.record.card_issuing_country.clone(), + bank_code_id: self.record.bank_code_id.clone(), + bank_code: self.record.bank_code.clone(), + country_code: self.record.country_code.clone(), + last_updated: Some(common_utils::date_time::now()), + last_updated_provider: self.record.last_updated_provider.clone(), + }, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "Card info with given key does not exist in our records".to_string(), + }) + .attach_printable("Failed while updating card info")?; + Ok(card_info) + } +} + +// Builder +pub struct CardInfoBuilder { + state: std::marker::PhantomData, + pub card_info: Option, +} + +impl CardInfoBuilder { + fn new() -> Self { + Self { + state: std::marker::PhantomData, + card_info: None, + } + } +} + +impl CardInfoBuilder { + fn set_card_info( + self, + card_info: card_info_models::CardInfo, + ) -> CardInfoBuilder { + CardInfoBuilder { + state: std::marker::PhantomData, + card_info: Some(card_info), + } + } + + fn transition(self) -> CardInfoBuilder { + CardInfoBuilder { + state: std::marker::PhantomData, + card_info: None, + } + } +} + +impl CardInfoBuilder { + fn set_updated_card_info( + self, + card_info: card_info_models::CardInfo, + ) -> CardInfoBuilder { + CardInfoBuilder { + state: std::marker::PhantomData, + card_info: Some(card_info), + } + } +} + +impl CardInfoBuilder { + fn set_added_card_info( + self, + card_info: card_info_models::CardInfo, + ) -> CardInfoBuilder { + CardInfoBuilder { + state: std::marker::PhantomData, + card_info: Some(card_info), + } + } +} + +impl CardInfoBuilder { + pub fn build(self) -> cards_info_api_types::CardInfoMigrateResponseRecord { + match self.card_info { + Some(card_info) => cards_info_api_types::CardInfoMigrateResponseRecord { + card_iin: Some(card_info.card_iin), + card_issuer: card_info.card_issuer, + card_network: card_info.card_network.map(|cn| cn.to_string()), + card_type: card_info.card_type, + card_sub_type: card_info.card_subtype, + card_issuing_country: card_info.card_issuing_country, + }, + None => cards_info_api_types::CardInfoMigrateResponseRecord { + card_iin: None, + card_issuer: None, + card_network: None, + card_type: None, + card_sub_type: None, + card_issuing_country: None, + }, + } + } +} + +async fn card_info_flow( + record: cards_info_api_types::CardInfoUpdateRequest, + state: routes::SessionState, +) -> RouterResponse { + let builder = CardInfoBuilder::new(); + let executor = CardInfoMigrateExecutor::new(&state, &record); + let fetched_card_info_details = executor.fetch_card_info().await?; + + let builder = match fetched_card_info_details { + Some(card_info) => { + let builder = builder.set_card_info(card_info); + let updated_card_info = executor.update_card_info().await?; + builder.set_updated_card_info(updated_card_info) + } + None => { + let builder = builder.transition(); + let added_card_info = executor.add_card_info().await?; + builder.set_added_card_info(added_card_info) + } + }; + + Ok(ApplicationResponse::Json(builder.build())) +} diff --git a/crates/router/src/db/cards_info.rs b/crates/router/src/db/cards_info.rs index 68bd90946b..12e63cef62 100644 --- a/crates/router/src/db/cards_info.rs +++ b/crates/router/src/db/cards_info.rs @@ -6,7 +6,7 @@ use crate::{ core::errors::{self, CustomResult}, db::MockDb, services::Store, - types::storage::cards_info::CardInfo, + types::storage::cards_info::{CardInfo, UpdateCardInfo}, }; #[async_trait::async_trait] @@ -15,6 +15,12 @@ pub trait CardsInfoInterface { &self, _card_iin: &str, ) -> CustomResult, errors::StorageError>; + async fn add_card_info(&self, data: CardInfo) -> CustomResult; + async fn update_card_info( + &self, + card_iin: String, + data: UpdateCardInfo, + ) -> CustomResult; } #[async_trait::async_trait] @@ -29,6 +35,26 @@ impl CardsInfoInterface for Store { .await .map_err(|error| report!(errors::StorageError::from(error))) } + + #[instrument(skip_all)] + async fn add_card_info(&self, data: CardInfo) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + data.insert(&conn) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + } + + #[instrument(skip_all)] + async fn update_card_info( + &self, + card_iin: String, + data: UpdateCardInfo, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + CardInfo::update(&conn, card_iin, data) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + } } #[async_trait::async_trait] @@ -46,4 +72,16 @@ impl CardsInfoInterface for MockDb { .find(|ci| ci.card_iin == card_iin) .cloned()) } + + async fn add_card_info(&self, _data: CardInfo) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn update_card_info( + &self, + _card_iin: String, + _data: UpdateCardInfo, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } } diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index ac5db83b3f..461c9fb46a 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -291,6 +291,21 @@ impl CardsInfoInterface for KafkaStore { ) -> CustomResult, errors::StorageError> { self.diesel_store.get_card_info(card_iin).await } + + async fn add_card_info( + &self, + data: storage::CardInfo, + ) -> CustomResult { + self.diesel_store.add_card_info(data).await + } + + async fn update_card_info( + &self, + card_iin: String, + data: storage::UpdateCardInfo, + ) -> CustomResult { + self.diesel_store.update_card_info(card_iin, data).await + } } #[async_trait::async_trait] diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index df4b9977b9..4d6ceec021 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -71,7 +71,9 @@ use crate::analytics::AnalyticsProvider; #[cfg(feature = "partial-auth")] use crate::errors::RouterResult; #[cfg(feature = "v1")] -use crate::routes::cards_info::card_iin_info; +use crate::routes::cards_info::{ + card_iin_info, create_cards_info, migrate_cards_info, update_cards_info, +}; #[cfg(all(feature = "olap", feature = "v1"))] use crate::routes::feature_matrix; #[cfg(all(feature = "frm", feature = "oltp"))] @@ -1794,11 +1796,14 @@ impl Disputes { pub struct Cards; -#[cfg(feature = "v1")] +#[cfg(all(feature = "oltp", feature = "v1"))] impl Cards { pub fn server(state: AppState) -> Scope { web::scope("/cards") .app_data(web::Data::new(state)) + .service(web::resource("/create").route(web::post().to(create_cards_info))) + .service(web::resource("/update").route(web::post().to(update_cards_info))) + .service(web::resource("/update-batch").route(web::post().to(migrate_cards_info))) .service(web::resource("/{bin}").route(web::get().to(card_iin_info))) } } diff --git a/crates/router/src/routes/cards_info.rs b/crates/router/src/routes/cards_info.rs index 1fe2d6db34..f5db3b71e3 100644 --- a/crates/router/src/routes/cards_info.rs +++ b/crates/router/src/routes/cards_info.rs @@ -1,4 +1,6 @@ -use actix_web::{web, HttpRequest, Responder}; +use actix_multipart::form::MultipartForm; +use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use api_models::cards_info as cards_info_api_types; use router_env::{instrument, tracing, Flow}; use super::app::AppState; @@ -55,3 +57,70 @@ pub async fn card_iin_info( )) .await } + +#[instrument(skip_all, fields(flow = ?Flow::CardsInfoCreate))] +pub async fn create_cards_info( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> impl Responder { + let payload = json_payload.into_inner(); + let flow = Flow::CardsInfoCreate; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + payload, + |state, _, payload, _| cards_info::create_card_info(state, payload), + &auth::AdminApiAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[instrument(skip_all, fields(flow = ?Flow::CardsInfoUpdate))] +pub async fn update_cards_info( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> impl Responder { + let payload = json_payload.into_inner(); + let flow = Flow::CardsInfoUpdate; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + payload, + |state, _, payload, _| cards_info::update_card_info(state, payload), + &auth::AdminApiAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(all( + any(feature = "v1", feature = "v2", feature = "olap", feature = "oltp"), + not(feature = "customer_v2") +))] +#[instrument(skip_all, fields(flow = ?Flow::CardsInfoMigrate))] +pub async fn migrate_cards_info( + state: web::Data, + req: HttpRequest, + MultipartForm(form): MultipartForm, +) -> HttpResponse { + let flow = Flow::CardsInfoMigrate; + let records = match cards_info::get_cards_bin_records(form) { + Ok(records) => records, + Err(e) => return api::log_and_return_error_response(e.into()), + }; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + records, + |state, _, payload, _| cards_info::migrate_cards_info(state, payload), + &auth::AdminApiAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index fd490c6fe4..5d33037f6b 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -196,7 +196,10 @@ impl From for ApiIdentifier { | Flow::DisputesAggregate | Flow::DeleteDisputeEvidence => Self::Disputes, - Flow::CardsInfo => Self::CardsInfo, + Flow::CardsInfo + | Flow::CardsInfoCreate + | Flow::CardsInfoUpdate + | Flow::CardsInfoMigrate => Self::CardsInfo, Flow::CreateFile | Flow::DeleteFile | Flow::RetrieveFile => Self::Files, diff --git a/crates/router/src/types/storage/cards_info.rs b/crates/router/src/types/storage/cards_info.rs index 458ae58482..1092770ccc 100644 --- a/crates/router/src/types/storage/cards_info.rs +++ b/crates/router/src/types/storage/cards_info.rs @@ -1 +1 @@ -pub use diesel_models::cards_info::CardInfo; +pub use diesel_models::cards_info::{CardInfo, UpdateCardInfo}; diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 0171e6e7c9..514ddbbbb5 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1,8 +1,8 @@ // use actix_web::HttpMessage; use actix_web::http::header::HeaderMap; use api_models::{ - enums as api_enums, gsm as gsm_api_types, payment_methods, payments, - routing::ConnectorSelection, + cards_info as card_info_types, enums as api_enums, gsm as gsm_api_types, payment_methods, + payments, routing::ConnectorSelection, }; use common_utils::{ consts::X_HS_LATENCY, @@ -2269,3 +2269,41 @@ impl ForeignFrom } } } + +impl ForeignFrom for storage::CardInfo { + fn foreign_from(value: card_info_types::CardInfoCreateRequest) -> Self { + Self { + card_iin: value.card_iin, + card_issuer: value.card_issuer, + card_network: value.card_network, + card_type: value.card_type, + card_subtype: value.card_subtype, + card_issuing_country: value.card_issuing_country, + bank_code_id: value.bank_code_id, + bank_code: value.bank_code, + country_code: value.country_code, + date_created: common_utils::date_time::now(), + last_updated: Some(common_utils::date_time::now()), + last_updated_provider: value.last_updated_provider, + } + } +} + +impl ForeignFrom for storage::CardInfo { + fn foreign_from(value: card_info_types::CardInfoUpdateRequest) -> Self { + Self { + card_iin: value.card_iin, + card_issuer: value.card_issuer, + card_network: value.card_network, + card_type: value.card_type, + card_subtype: value.card_subtype, + card_issuing_country: value.card_issuing_country, + bank_code_id: value.bank_code_id, + bank_code: value.bank_code, + country_code: value.country_code, + date_created: common_utils::date_time::now(), + last_updated: Some(common_utils::date_time::now()), + last_updated_provider: value.last_updated_provider, + } + } +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index b4655b6b77..61e24d8156 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -561,6 +561,12 @@ pub enum Flow { PaymentMethodSessionUpdateSavedPaymentMethod, /// Confirm a payment method session with payment method data PaymentMethodSessionConfirm, + /// Create Cards Info flow + CardsInfoCreate, + /// Update Cards Info flow + CardsInfoUpdate, + /// Cards Info migrate flow + CardsInfoMigrate, } /// Trait for providing generic behaviour to flow metric