mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
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:
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>,
|
||||
}
|
||||
|
||||
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()))
|
||||
}
|
||||
|
||||
@ -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)?
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
pub use diesel_models::cards_info::CardInfo;
|
||||
pub use diesel_models::cards_info::{CardInfo, UpdateCardInfo};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user