feat(core): Add support for cards bin update (#7194)

Co-authored-by: Mrudul Vajpayee <mrudul.vajpayee@mrudulvajpayee-XJWXCWP7HF.local>
Co-authored-by: Mrudul Vajpayee <mrudul.vajpayee@mrudulvWXCWP7HF.lan>
This commit is contained in:
Mrudul Vajpayee
2025-03-04 13:55:29 +05:30
committed by GitHub
parent 6553e29e47
commit 8e922d30da
12 changed files with 629 additions and 17 deletions

View File

@ -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<String>,
}
#[derive(serde::Serialize, Debug, ToSchema)]
pub struct CardInfoMigrateResponseRecord {
pub card_iin: Option<String>,
pub card_issuer: Option<String>,
pub card_network: Option<String>,
pub card_type: Option<String>,
pub card_sub_type: Option<String>,
pub card_issuing_country: Option<String>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct CardInfoCreateRequest {
pub card_iin: String,
pub card_issuer: Option<String>,
pub card_network: Option<enums::CardNetwork>,
pub card_type: Option<String>,
pub card_subtype: Option<String>,
pub card_issuing_country: Option<String>,
pub bank_code_id: Option<String>,
pub bank_code: Option<String>,
pub country_code: Option<String>,
pub last_updated_provider: Option<String>,
}
impl ApiEventMetric for CardInfoCreateRequest {}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct CardInfoUpdateRequest {
pub card_iin: String,
pub card_issuer: Option<String>,
pub card_network: Option<enums::CardNetwork>,
pub card_type: Option<String>,
pub card_subtype: Option<String>,
pub card_issuing_country: Option<String>,
pub bank_code_id: Option<String>,
pub bank_code: Option<String>,
pub country_code: Option<String>,
pub last_updated_provider: Option<String>,
pub line_number: Option<i64>,
}
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<i64>,
pub card_iin: String,
pub card_issuer: Option<String>,
pub card_network: Option<String>,
pub card_type: Option<String>,
pub card_sub_type: Option<String>,
pub card_issuing_country: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub migration_error: Option<String>,
pub migration_status: CardInfoMigrationStatus,
}
impl ApiEventMetric for CardInfoMigrationResponse {}
type CardInfoMigrationResponseType = (
Result<CardInfoMigrateResponseRecord, String>,
CardInfoUpdateRequest,
);
impl From<CardInfoMigrationResponseType> 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()
},
}
}
}

View File

@ -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<PrimitiveDateTime>,
pub last_updated_provider: Option<String>,
}
#[derive(
Clone, Debug, PartialEq, Eq, AsChangeset, router_derive::DebugAsDisplay, serde::Deserialize,
)]
#[diesel(table_name = cards_info)]
pub struct UpdateCardInfo {
pub card_issuer: Option<String>,
pub card_network: Option<storage_enums::CardNetwork>,
pub card_type: Option<String>,
pub card_subtype: Option<String>,
pub card_issuing_country: Option<String>,
pub bank_code_id: Option<String>,
pub bank_code: Option<String>,
pub country_code: Option<String>,
pub last_updated: Option<PrimitiveDateTime>,
pub last_updated_provider: Option<String>,
}

View File

@ -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<Option<Self>> {
@ -10,4 +17,25 @@ impl CardInfo {
)
.await
}
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Self> {
generics::generic_insert(conn, self).await
}
pub async fn update(
conn: &PgPooledConn,
card_iin: String,
data: UpdateCardInfo,
) -> StorageResult<Self> {
generics::generic_update_with_results::<<Self as HasTable>::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")
})
}
}

View File

@ -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<api_models::cards_info::CardInfoResponse> {
request: cards_info_api_types::CardsInfoRequest,
) -> RouterResponse<cards_info_api_types::CardInfoResponse> {
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<cards_info_api_types::CardInfoResponse> {
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<cards_info_api_types::CardInfoResponse> {
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<Vec<cards_info_api_types::CardInfoUpdateRequest>> {
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<Vec<cards_info_api_types::CardInfoUpdateRequest>, 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<cards_info_api_types::CardInfoUpdateRequest>,
) -> RouterResponse<Vec<cards_info_api_types::CardInfoMigrationResponse>> {
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<S: State> {}
// 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<CardInfoAdd> for CardInfoFetch {}
impl TransitionTo<CardInfoUpdate> for CardInfoFetch {}
impl TransitionTo<CardInfoResponse> for CardInfoAdd {}
impl TransitionTo<CardInfoResponse> 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<Option<card_info_models::CardInfo>> {
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<card_info_models::CardInfo> {
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<card_info_models::CardInfo> {
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<S: State> {
state: std::marker::PhantomData<S>,
pub card_info: Option<card_info_models::CardInfo>,
}
impl CardInfoBuilder<CardInfoFetch> {
fn new() -> Self {
Self {
state: std::marker::PhantomData,
card_info: None,
}
}
}
impl CardInfoBuilder<CardInfoFetch> {
fn set_card_info(
self,
card_info: card_info_models::CardInfo,
) -> CardInfoBuilder<CardInfoUpdate> {
CardInfoBuilder {
state: std::marker::PhantomData,
card_info: Some(card_info),
}
}
fn transition(self) -> CardInfoBuilder<CardInfoAdd> {
CardInfoBuilder {
state: std::marker::PhantomData,
card_info: None,
}
}
}
impl CardInfoBuilder<CardInfoUpdate> {
fn set_updated_card_info(
self,
card_info: card_info_models::CardInfo,
) -> CardInfoBuilder<CardInfoResponse> {
CardInfoBuilder {
state: std::marker::PhantomData,
card_info: Some(card_info),
}
}
}
impl CardInfoBuilder<CardInfoAdd> {
fn set_added_card_info(
self,
card_info: card_info_models::CardInfo,
) -> CardInfoBuilder<CardInfoResponse> {
CardInfoBuilder {
state: std::marker::PhantomData,
card_info: Some(card_info),
}
}
}
impl CardInfoBuilder<CardInfoResponse> {
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<cards_info_api_types::CardInfoMigrateResponseRecord> {
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()))
}

View File

@ -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<Option<CardInfo>, errors::StorageError>;
async fn add_card_info(&self, data: CardInfo) -> CustomResult<CardInfo, errors::StorageError>;
async fn update_card_info(
&self,
card_iin: String,
data: UpdateCardInfo,
) -> CustomResult<CardInfo, errors::StorageError>;
}
#[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<CardInfo, errors::StorageError> {
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<CardInfo, errors::StorageError> {
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<CardInfo, errors::StorageError> {
Err(errors::StorageError::MockDbError)?
}
async fn update_card_info(
&self,
_card_iin: String,
_data: UpdateCardInfo,
) -> CustomResult<CardInfo, errors::StorageError> {
Err(errors::StorageError::MockDbError)?
}
}

View File

@ -291,6 +291,21 @@ impl CardsInfoInterface for KafkaStore {
) -> CustomResult<Option<storage::CardInfo>, errors::StorageError> {
self.diesel_store.get_card_info(card_iin).await
}
async fn add_card_info(
&self,
data: storage::CardInfo,
) -> CustomResult<storage::CardInfo, errors::StorageError> {
self.diesel_store.add_card_info(data).await
}
async fn update_card_info(
&self,
card_iin: String,
data: storage::UpdateCardInfo,
) -> CustomResult<storage::CardInfo, errors::StorageError> {
self.diesel_store.update_card_info(card_iin, data).await
}
}
#[async_trait::async_trait]

View File

@ -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)))
}
}

View File

@ -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<AppState>,
req: HttpRequest,
json_payload: web::Json<cards_info_api_types::CardInfoCreateRequest>,
) -> 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<AppState>,
req: HttpRequest,
json_payload: web::Json<cards_info_api_types::CardInfoUpdateRequest>,
) -> 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<AppState>,
req: HttpRequest,
MultipartForm(form): MultipartForm<cards_info::CardsInfoUpdateForm>,
) -> 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
}

View File

@ -196,7 +196,10 @@ impl From<Flow> 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,

View File

@ -1 +1 @@
pub use diesel_models::cards_info::CardInfo;
pub use diesel_models::cards_info::{CardInfo, UpdateCardInfo};

View File

@ -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<diesel_models::business_profile::BusinessGenericLinkConfig>
}
}
}
impl ForeignFrom<card_info_types::CardInfoCreateRequest> 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<card_info_types::CardInfoUpdateRequest> 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,
}
}
}

View File

@ -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