diff --git a/crates/api_models/src/blocklist.rs b/crates/api_models/src/blocklist.rs index fc838eed5ce6..888b9106cccc 100644 --- a/crates/api_models/src/blocklist.rs +++ b/crates/api_models/src/blocklist.rs @@ -1,7 +1,8 @@ use common_enums::enums; use common_utils::events::ApiEventMetric; +use utoipa::ToSchema; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[serde(rename_all = "snake_case", tag = "type", content = "data")] pub enum BlocklistRequest { CardBin(String), @@ -12,9 +13,10 @@ pub enum BlocklistRequest { pub type AddToBlocklistRequest = BlocklistRequest; pub type DeleteFromBlocklistRequest = BlocklistRequest; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct BlocklistResponse { pub fingerprint_id: String, + #[schema(value_type = BlocklistDataKind)] pub data_kind: enums::BlocklistDataKind, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: time::PrimitiveDateTime, @@ -23,8 +25,9 @@ pub struct BlocklistResponse { pub type AddToBlocklistResponse = BlocklistResponse; pub type DeleteFromBlocklistResponse = BlocklistResponse; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct ListBlocklistQuery { + #[schema(value_type = BlocklistDataKind)] pub data_kind: enums::BlocklistDataKind, #[serde(default = "default_list_limit")] pub limit: u16, diff --git a/crates/router/src/db/blocklist.rs b/crates/router/src/db/blocklist.rs index c263bef63c5a..93361552de70 100644 --- a/crates/router/src/db/blocklist.rs +++ b/crates/router/src/db/blocklist.rs @@ -163,41 +163,49 @@ impl BlocklistInterface for KafkaStore { #[instrument(skip_all)] async fn insert_blocklist_entry( &self, - _pm_blocklist: storage::BlocklistNew, + pm_blocklist: storage::BlocklistNew, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store.insert_blocklist_entry(pm_blocklist).await } async fn find_blocklist_entry_by_merchant_id_fingerprint_id( &self, - _merchant_id: &str, - _fingerprint_id: &str, + merchant_id: &str, + fingerprint: &str, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, fingerprint) + .await } async fn delete_blocklist_entry_by_merchant_id_fingerprint_id( &self, - _merchant_id: &str, - _fingerprint_id: &str, + merchant_id: &str, + fingerprint: &str, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .delete_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, fingerprint) + .await } async fn list_blocklist_entries_by_merchant_id_data_kind( &self, - _merchant_id: &str, - _data_kind: common_enums::BlocklistDataKind, - _limit: i64, - _offset: i64, + merchant_id: &str, + data_kind: common_enums::BlocklistDataKind, + limit: i64, + offset: i64, ) -> CustomResult, errors::StorageError> { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .list_blocklist_entries_by_merchant_id_data_kind(merchant_id, data_kind, limit, offset) + .await } async fn list_blocklist_entries_by_merchant_id( &self, - _merchant_id: &str, + merchant_id: &str, ) -> CustomResult, errors::StorageError> { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .list_blocklist_entries_by_merchant_id(merchant_id) + .await } } diff --git a/crates/router/src/db/blocklist_fingerprint.rs b/crates/router/src/db/blocklist_fingerprint.rs index 9da7c7d8fb2c..d9107d3d1c13 100644 --- a/crates/router/src/db/blocklist_fingerprint.rs +++ b/crates/router/src/db/blocklist_fingerprint.rs @@ -80,16 +80,20 @@ impl BlocklistFingerprintInterface for KafkaStore { #[instrument(skip_all)] async fn insert_blocklist_fingerprint_entry( &self, - _pm_fingerprint_new: storage::BlocklistFingerprintNew, + pm_fingerprint_new: storage::BlocklistFingerprintNew, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .insert_blocklist_fingerprint_entry(pm_fingerprint_new) + .await } async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id( &self, - _merchant_id: &str, - _fingerprint_id: &str, + merchant_id: &str, + fingerprint: &str, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .find_blocklist_fingerprint_by_merchant_id_fingerprint_id(merchant_id, fingerprint) + .await } } diff --git a/crates/router/src/db/blocklist_lookup.rs b/crates/router/src/db/blocklist_lookup.rs index 0dfd81c8b8a2..f5fb4ea9ed8c 100644 --- a/crates/router/src/db/blocklist_lookup.rs +++ b/crates/router/src/db/blocklist_lookup.rs @@ -102,24 +102,30 @@ impl BlocklistLookupInterface for KafkaStore { #[instrument(skip_all)] async fn insert_blocklist_lookup_entry( &self, - _blocklist_lookup_entry: storage::BlocklistLookupNew, + blocklist_lookup_entry: storage::BlocklistLookupNew, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .insert_blocklist_lookup_entry(blocklist_lookup_entry) + .await } async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint( &self, - _merchant_id: &str, - _fingerprint: &str, + merchant_id: &str, + fingerprint: &str, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .find_blocklist_lookup_entry_by_merchant_id_fingerprint(merchant_id, fingerprint) + .await } async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint( &self, - _merchant_id: &str, - _fingerprint: &str, + merchant_id: &str, + fingerprint: &str, ) -> CustomResult { - Err(errors::StorageError::KafkaError)? + self.diesel_store + .delete_blocklist_lookup_entry_by_merchant_id_fingerprint(merchant_id, fingerprint) + .await } } diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 79b38e03f31d..174926c7d360 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -119,6 +119,9 @@ Never share your secret api keys. Keep them guarded and secure. crate::routes::gsm::get_gsm_rule, crate::routes::gsm::update_gsm_rule, crate::routes::gsm::delete_gsm_rule, + crate::routes::blocklist::add_entry_to_blocklist, + crate::routes::blocklist::list_blocked_payment_methods, + crate::routes::blocklist::remove_entry_from_blocklist ), components(schemas( crate::types::api::refunds::RefundRequest, @@ -370,7 +373,11 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentLinkResponse, api_models::payments::RetrievePaymentLinkResponse, api_models::payments::PaymentLinkInitiateRequest, - api_models::payments::PaymentLinkStatus + api_models::payments::PaymentLinkStatus, + api_models::blocklist::BlocklistRequest, + api_models::blocklist::BlocklistResponse, + api_models::blocklist::ListBlocklistQuery, + common_enums::enums::BlocklistDataKind )), modifiers(&SecurityAddon) )] diff --git a/crates/router/src/routes/blocklist.rs b/crates/router/src/routes/blocklist.rs index 7c268dddeec0..9c93f49ab83f 100644 --- a/crates/router/src/routes/blocklist.rs +++ b/crates/router/src/routes/blocklist.rs @@ -8,6 +8,18 @@ use crate::{ services::{api, authentication as auth, authorization::permissions::Permission}, }; +#[utoipa::path( + post, + path = "/blocklist", + request_body = BlocklistRequest, + responses( + (status = 200, description = "Fingerprint Blocked", body = BlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "Block a Fingerprint", + security(("api_key" = [])) +)] pub async fn add_entry_to_blocklist( state: web::Data, req: HttpRequest, @@ -32,6 +44,18 @@ pub async fn add_entry_to_blocklist( .await } +#[utoipa::path( + delete, + path = "/blocklist", + request_body = BlocklistRequest, + responses( + (status = 200, description = "Fingerprint Unblocked", body = BlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "Unblock a Fingerprint", + security(("api_key" = [])) +)] pub async fn remove_entry_from_blocklist( state: web::Data, req: HttpRequest, @@ -56,6 +80,20 @@ pub async fn remove_entry_from_blocklist( .await } +#[utoipa::path( + get, + path = "/blocklist", + params ( + ("data_kind" = BlocklistDataKind, Query, description = "Kind of the fingerprint list requested"), + ), + responses( + (status = 200, description = "Blocked Fingerprints", body = BlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "List Blocked fingerprints of a particular kind", + security(("api_key" = [])) +)] pub async fn list_blocked_payment_methods( state: web::Data, req: HttpRequest, diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 3e582cfed528..c50f687a1810 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -382,6 +382,117 @@ ] } }, + "/blocklist": { + "get": { + "tags": [ + "Blocklist" + ], + "operationId": "List Blocked fingerprints of a particular kind", + "parameters": [ + { + "name": "data_kind", + "in": "query", + "description": "Kind of the fingerprint list requested", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlocklistDataKind" + } + } + ], + "responses": { + "200": { + "description": "Blocked Fingerprints", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "Blocklist" + ], + "operationId": "Block a Fingerprint", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Fingerprint Blocked", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": [ + "Blocklist" + ], + "operationId": "Unblock a Fingerprint", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Fingerprint Unblocked", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/customers": { "post": { "tags": [ @@ -4035,6 +4146,95 @@ } ] }, + "BlocklistDataKind": { + "type": "string", + "enum": [ + "payment_method", + "card_bin", + "extended_card_bin" + ] + }, + "BlocklistRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "card_bin" + ] + }, + "data": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "fingerprint" + ] + }, + "data": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "extended_card_bin" + ] + }, + "data": { + "type": "string" + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, + "BlocklistResponse": { + "type": "object", + "required": [ + "fingerprint_id", + "data_kind", + "created_at" + ], + "properties": { + "fingerprint_id": { + "type": "string" + }, + "data_kind": { + "$ref": "#/components/schemas/BlocklistDataKind" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, "BoletoVoucherData": { "type": "object", "properties": { @@ -6576,6 +6776,27 @@ } } }, + "ListBlocklistQuery": { + "type": "object", + "required": [ + "data_kind" + ], + "properties": { + "data_kind": { + "$ref": "#/components/schemas/BlocklistDataKind" + }, + "limit": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "offset": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, "MandateAmountData": { "type": "object", "required": [