Issue:
Bug: [FEATURE] `Blocklist` initial implementation



### Feature Description:

This feature provides merchants with the ability to customize their transaction controls based on specific criteria. Merchants can tailor the blocklist according to their preferences, blocking the following elements as needed:
Card Numbers
Card ISINs
Extended BINs

Understanding Blocklist:
In the realm of payment processing, a blocklist serves as a security feature empowering merchants to restrict specific identifiers associated with payment methods or block certain card BINs. A fingerprint, a unique identifier linked to a payment method, and a card BIN, encompassing the first six digits of a credit card number, are key components. An extended card BIN covers the first eight digits.

### Testing
Refer to the attached postman collection for the API contracts for the blocklist APIs. Currently we support blocking three types of resources i.e. card numbers (payment intrument), card bin, and extended card bin.
[blocklist_api_postman.zip](https://github.com/juspay/hyperswitch/files/13893696/blocklist_api_postman.zip)

```
For Card Bin and Extended Card Bin :-
1. Setup a Merchant Account and any Connector account
2. Make a payment with a certain card (ensure it succeeds)
3. Block the card's card bin or extended card bin
4. Try the payment again (should fail this time with an API response saying that the payment was blocked)

For Payment Instrument :-
1. Repeat steps 1 and 2 of previous section
2. In the payment confirm response, there will be an additional field called "fingerprint". This is the fingerprint id that can be used to block a particular payment method. Use this to block the card.
3. Try the payment again (should fail)
```

Pull request description:
## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This feature will allow the merchants to block the following according to their needs:
```
  1. card_numbers
  2. card_isins
  3. extended_bins
```


### Additional Changes

- [x] This PR modifies the API contract
- [x] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
5. `crates/router/src/configs`
6. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Requested by Merchant.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Refer to the attached postman collection for the API contracts for the blocklist APIs. Currently we support blocking three types of resources i.e. card numbers (payment intrument), card bin, and extended card bin.

For Card Bin and Extended Card Bin :-
1. Setup a Merchant Account and any Connector account
2. Make a payment with a certain card (ensure it succeeds)
3. Block the card's card bin or extended card bin
4. Try the payment again (should fail this time with an API response saying that the payment was blocked)

For Payment Instrument :-
1. Repeat steps 1 and 2 of previous section
2. In the payment confirm response, there will be an additional field called "fingerprint". This is the fingerprint id that can be used to block a particular payment method. Use this to block the card.
3. Try the payment again (should fail)

[blocklist_api_postman.zip](https://github.com/juspay/hyperswitch/files/13893696/blocklist_api_postman.zip)

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable


Testing:

Refer to the attached postman collection for the API contracts for the blocklist APIs. Currently we support blocking three types of resources i.e. card numbers (payment intrument), card bin, and extended card bin.

For Card Bin and Extended Card Bin :-
1. Setup a Merchant Account and any Connector account
2. Make a payment with a certain card (ensure it succeeds)
3. Block the card's card bin or extended card bin
4. Try the payment again (should fail this time with an API response saying that the payment was blocked)

For Payment Instrument :-
1. Repeat steps 1 and 2 of previous section
2. In the payment confirm response, there will be an additional field called "fingerprint". This is the fingerprint id that can be used to block a particular payment method. Use this to block the card.
3. Try the payment again (should fail)

[blocklist_api_postman.zip](https://github.com/juspay/hyperswitch/files/13893696/blocklist_api_postman.zip)

Patch:
diff --git a/crates/api_models/src/blocklist.rs b/crates/api_models/src/blocklist.rs
new file mode 100644
index 00000000000..fc838eed5ce
--- /dev/null
+++ b/crates/api_models/src/blocklist.rs
@@ -0,0 +1,41 @@
+use common_enums::enums;
+use common_utils::events::ApiEventMetric;
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "snake_case", tag = "type", content = "data")]
+pub enum BlocklistRequest {
+    CardBin(String),
+    Fingerprint(String),
+    ExtendedCardBin(String),
+}
+
+pub type AddToBlocklistRequest = BlocklistRequest;
+pub type DeleteFromBlocklistRequest = BlocklistRequest;
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct BlocklistResponse {
+    pub fingerprint_id: String,
+    pub data_kind: enums::BlocklistDataKind,
+    #[serde(with = "common_utils::custom_serde::iso8601")]
+    pub created_at: time::PrimitiveDateTime,
+}
+
+pub type AddToBlocklistResponse = BlocklistResponse;
+pub type DeleteFromBlocklistResponse = BlocklistResponse;
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct ListBlocklistQuery {
+    pub data_kind: enums::BlocklistDataKind,
+    #[serde(default = "default_list_limit")]
+    pub limit: u16,
+    #[serde(default)]
+    pub offset: u16,
+}
+
+fn default_list_limit() -> u16 {
+    10
+}
+
+impl ApiEventMetric for BlocklistRequest {}
+impl ApiEventMetric for BlocklistResponse {}
+impl ApiEventMetric for ListBlocklistQuery {}
diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs
index 459443747e3..dc1f6eb6537 100644
--- a/crates/api_models/src/lib.rs
+++ b/crates/api_models/src/lib.rs
@@ -3,6 +3,7 @@ pub mod admin;
 pub mod analytics;
 pub mod api_keys;
 pub mod bank_accounts;
+pub mod blocklist;
 pub mod cards_info;
 pub mod conditional_configs;
 pub mod connector_onboarding;
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 45611a91458..f9077500dd4 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -2274,6 +2274,9 @@ pub struct PaymentsResponse {
 
     /// List of incremental authorizations happened to the payment
     pub incremental_authorizations: Option<Vec<IncrementalAuthorizationResponse>>,
+
+    /// Payment Fingerprint
+    pub fingerprint: Option<String>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs
index ca47c73c7c2..87b04baa1a2 100644
--- a/crates/cards/src/validate.rs
+++ b/crates/cards/src/validate.rs
@@ -24,6 +24,13 @@ impl CardNumber {
     pub fn get_card_isin(self) -> String {
         self.0.peek().chars().take(6).collect::<String>()
     }
+
+    pub fn get_extended_card_bin(self) -> String {
+        self.0.peek().chars().take(8).collect::<String>()
+    }
+    pub fn get_card_no(self) -> String {
+        self.0.peek().chars().collect::<String>()
+    }
     pub fn get_last4(self) -> String {
         self.0
             .peek()
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 3af1c0e826b..949cc2e0034 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -6,12 +6,13 @@ use utoipa::ToSchema;
 pub mod diesel_exports {
     pub use super::{
         DbAttemptStatus as AttemptStatus, DbAuthenticationType as AuthenticationType,
-        DbCaptureMethod as CaptureMethod, DbCaptureStatus as CaptureStatus,
-        DbConnectorType as ConnectorType, DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency,
-        DbDisputeStage as DisputeStage, DbDisputeStatus as DisputeStatus, DbEventType as EventType,
-        DbFutureUsage as FutureUsage, DbIntentStatus as IntentStatus,
-        DbMandateStatus as MandateStatus, DbPaymentMethodIssuerCode as PaymentMethodIssuerCode,
-        DbPaymentType as PaymentType, DbRefundStatus as RefundStatus,
+        DbBlocklistDataKind as BlocklistDataKind, DbCaptureMethod as CaptureMethod,
+        DbCaptureStatus as CaptureStatus, DbConnectorType as ConnectorType,
+        DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, DbDisputeStage as DisputeStage,
+        DbDisputeStatus as DisputeStatus, DbEventType as EventType, DbFutureUsage as FutureUsage,
+        DbIntentStatus as IntentStatus, DbMandateStatus as MandateStatus,
+        DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentType as PaymentType,
+        DbRefundStatus as RefundStatus,
         DbRequestIncrementalAuthorization as RequestIncrementalAuthorization,
     };
 }
@@ -275,6 +276,27 @@ pub enum AuthorizationStatus {
     Unresolved,
 }
 
+#[derive(
+    Clone,
+    Debug,
+    PartialEq,
+    Eq,
+    serde::Deserialize,
+    serde::Serialize,
+    strum::Display,
+    strum::EnumString,
+    ToSchema,
+    Hash,
+)]
+#[router_derive::diesel_enum(storage_type = "db_enum")]
+#[serde(rename_all = "snake_case")]
+#[strum(serialize_all = "snake_case")]
+pub enum BlocklistDataKind {
+    PaymentMethod,
+    CardBin,
+    ExtendedCardBin,
+}
+
 #[derive(
     Clone,
     Copy,
diff --git a/crates/data_models/src/errors.rs b/crates/data_models/src/errors.rs
index 9616a3a944c..bed1ab9ccbf 100644
--- a/crates/data_models/src/errors.rs
+++ b/crates/data_models/src/errors.rs
@@ -24,6 +24,8 @@ pub enum StorageError {
     SerializationFailed,
     #[error("MockDb error")]
     MockDbError,
+    #[error("Kafka error")]
+    KafkaError,
     #[error("Customer with this id is Redacted")]
     CustomerRedacted,
     #[error("Deserialization failure")]
diff --git a/crates/data_models/src/payments.rs b/crates/data_models/src/payments.rs
index cc6b03f89a5..713003d666b 100644
--- a/crates/data_models/src/payments.rs
+++ b/crates/data_models/src/payments.rs
@@ -53,5 +53,6 @@ pub struct PaymentIntent {
     pub request_incremental_authorization: Option<storage_enums::RequestIncrementalAuthorization>,
     pub incremental_authorization_allowed: Option<bool>,
     pub authorization_count: Option<i32>,
+    pub fingerprint_id: Option<String>,
     pub session_expiry: Option<PrimitiveDateTime>,
 }
diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs
index 80671ec7f61..7470b5f8502 100644
--- a/crates/data_models/src/payments/payment_intent.rs
+++ b/crates/data_models/src/payments/payment_intent.rs
@@ -110,6 +110,7 @@ pub struct PaymentIntentNew {
     pub request_incremental_authorization: Option<storage_enums::RequestIncrementalAuthorization>,
     pub incremental_authorization_allowed: Option<bool>,
     pub authorization_count: Option<i32>,
+    pub fingerprint_id: Option<String>,
     pub session_expiry: Option<PrimitiveDateTime>,
 }
 
@@ -163,6 +164,7 @@ pub enum PaymentIntentUpdate {
         metadata: Option<pii::SecretSerdeValue>,
         payment_confirm_source: Option<storage_enums::PaymentSource>,
         updated_by: String,
+        fingerprint_id: Option<String>,
         session_expiry: Option<PrimitiveDateTime>,
     },
     PaymentAttemptAndAttemptCountUpdate {
@@ -228,6 +230,7 @@ pub struct PaymentIntentUpdateInternal {
     pub surcharge_applicable: Option<bool>,
     pub incremental_authorization_allowed: Option<bool>,
     pub authorization_count: Option<i32>,
+    pub fingerprint_id: Option<String>,
     pub session_expiry: Option<PrimitiveDateTime>,
 }
 
@@ -252,6 +255,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 metadata,
                 payment_confirm_source,
                 updated_by,
+                fingerprint_id,
                 session_expiry,
             } => Self {
                 amount: Some(amount),
@@ -272,6 +276,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 metadata,
                 payment_confirm_source,
                 updated_by,
+                fingerprint_id,
                 session_expiry,
                 ..Default::default()
             },
diff --git a/crates/diesel_models/src/blocklist.rs b/crates/diesel_models/src/blocklist.rs
new file mode 100644
index 00000000000..9e88802aa3b
--- /dev/null
+++ b/crates/diesel_models/src/blocklist.rs
@@ -0,0 +1,26 @@
+use diesel::{Identifiable, Insertable, Queryable};
+use serde::{Deserialize, Serialize};
+
+use crate::schema::blocklist;
+
+#[derive(Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)]
+#[diesel(table_name = blocklist)]
+pub struct BlocklistNew {
+    pub merchant_id: String,
+    pub fingerprint_id: String,
+    pub data_kind: common_enums::BlocklistDataKind,
+    pub metadata: Option<serde_json::Value>,
+    pub created_at: time::PrimitiveDateTime,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Deserialize, Serialize)]
+#[diesel(table_name = blocklist)]
+pub struct Blocklist {
+    #[serde(skip)]
+    pub id: i32,
+    pub merchant_id: String,
+    pub fingerprint_id: String,
+    pub data_kind: common_enums::BlocklistDataKind,
+    pub metadata: Option<serde_json::Value>,
+    pub created_at: time::PrimitiveDateTime,
+}
diff --git a/crates/diesel_models/src/blocklist_fingerprint.rs b/crates/diesel_models/src/blocklist_fingerprint.rs
new file mode 100644
index 00000000000..e75856622e2
--- /dev/null
+++ b/crates/diesel_models/src/blocklist_fingerprint.rs
@@ -0,0 +1,26 @@
+use diesel::{Identifiable, Insertable, Queryable};
+use serde::{Deserialize, Serialize};
+
+use crate::schema::blocklist_fingerprint;
+
+#[derive(Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)]
+#[diesel(table_name = blocklist_fingerprint)]
+pub struct BlocklistFingerprintNew {
+    pub merchant_id: String,
+    pub fingerprint_id: String,
+    pub data_kind: common_enums::BlocklistDataKind,
+    pub encrypted_fingerprint: String,
+    pub created_at: time::PrimitiveDateTime,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Queryable, Identifiable, Deserialize, Serialize)]
+#[diesel(table_name = blocklist_fingerprint)]
+pub struct BlocklistFingerprint {
+    #[serde(skip_serializing)]
+    pub id: i32,
+    pub merchant_id: String,
+    pub fingerprint_id: String,
+    pub data_kind: common_enums::BlocklistDataKind,
+    pub encrypted_fingerprint: String,
+    pub created_at: time::PrimitiveDateTime,
+}
diff --git a/crates/diesel_models/src/blocklist_lookup.rs b/crates/diesel_models/src/blocklist_lookup.rs
new file mode 100644
index 00000000000..ad2a893e03d
--- /dev/null
+++ b/crates/diesel_models/src/blocklist_lookup.rs
@@ -0,0 +1,20 @@
+use diesel::{Identifiable, Insertable, Queryable};
+use serde::{Deserialize, Serialize};
+
+use crate::schema::blocklist_lookup;
+
+#[derive(Default, Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)]
+#[diesel(table_name = blocklist_lookup)]
+pub struct BlocklistLookupNew {
+    pub merchant_id: String,
+    pub fingerprint: String,
+}
+
+#[derive(Default, Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Deserialize, Serialize)]
+#[diesel(table_name = blocklist_lookup)]
+pub struct BlocklistLookup {
+    #[serde(skip)]
+    pub id: i32,
+    pub merchant_id: String,
+    pub fingerprint: String,
+}
diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs
index 792e8ffc8bb..a06937c99a6 100644
--- a/crates/diesel_models/src/enums.rs
+++ b/crates/diesel_models/src/enums.rs
@@ -2,9 +2,9 @@
 pub mod diesel_exports {
     pub use super::{
         DbAttemptStatus as AttemptStatus, DbAuthenticationType as AuthenticationType,
-        DbCaptureMethod as CaptureMethod, DbCaptureStatus as CaptureStatus,
-        DbConnectorStatus as ConnectorStatus, DbConnectorType as ConnectorType,
-        DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency,
+        DbBlocklistDataKind as BlocklistDataKind, DbCaptureMethod as CaptureMethod,
+        DbCaptureStatus as CaptureStatus, DbConnectorStatus as ConnectorStatus,
+        DbConnectorType as ConnectorType, DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency,
         DbDashboardMetadata as DashboardMetadata, DbDisputeStage as DisputeStage,
         DbDisputeStatus as DisputeStatus, DbEventClass as EventClass,
         DbEventObjectType as EventObjectType, DbEventType as EventType,
diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs
index fa32fb84a15..82b1e29ee83 100644
--- a/crates/diesel_models/src/lib.rs
+++ b/crates/diesel_models/src/lib.rs
@@ -1,11 +1,14 @@
 pub mod address;
 pub mod api_keys;
+pub mod blocklist_lookup;
 pub mod business_profile;
 pub mod capture;
 pub mod cards_info;
 pub mod configs;
 
 pub mod authorization;
+pub mod blocklist;
+pub mod blocklist_fingerprint;
 pub mod customers;
 pub mod dispute;
 pub mod encryption;
diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs
index 17784bc5659..31bc0c06c51 100644
--- a/crates/diesel_models/src/payment_intent.rs
+++ b/crates/diesel_models/src/payment_intent.rs
@@ -56,6 +56,7 @@ pub struct PaymentIntent {
     pub incremental_authorization_allowed: Option<bool>,
     pub authorization_count: Option<i32>,
     pub session_expiry: Option<PrimitiveDateTime>,
+    pub fingerprint_id: Option<String>,
 }
 
 #[derive(
@@ -107,6 +108,7 @@ pub struct PaymentIntentNew {
     pub authorization_count: Option<i32>,
     #[serde(with = "common_utils::custom_serde::iso8601::option")]
     pub session_expiry: Option<PrimitiveDateTime>,
+    pub fingerprint_id: Option<String>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -160,6 +162,7 @@ pub enum PaymentIntentUpdate {
         payment_confirm_source: Option<storage_enums::PaymentSource>,
         updated_by: String,
         session_expiry: Option<PrimitiveDateTime>,
+        fingerprint_id: Option<String>,
     },
     PaymentAttemptAndAttemptCountUpdate {
         active_attempt_id: String,
@@ -226,6 +229,7 @@ pub struct PaymentIntentUpdateInternal {
     pub incremental_authorization_allowed: Option<bool>,
     pub authorization_count: Option<i32>,
     pub session_expiry: Option<PrimitiveDateTime>,
+    pub fingerprint_id: Option<String>,
 }
 
 impl PaymentIntentUpdate {
@@ -259,6 +263,7 @@ impl PaymentIntentUpdate {
             incremental_authorization_allowed,
             authorization_count,
             session_expiry,
+            fingerprint_id,
         } = self.into();
         PaymentIntent {
             amount: amount.unwrap_or(source.amount),
@@ -288,9 +293,11 @@ impl PaymentIntentUpdate {
             payment_confirm_source: payment_confirm_source.or(source.payment_confirm_source),
             updated_by,
             surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable),
+
             incremental_authorization_allowed: incremental_authorization_allowed
                 .or(source.incremental_authorization_allowed),
             authorization_count: authorization_count.or(source.authorization_count),
+            fingerprint_id: fingerprint_id.or(source.fingerprint_id),
             session_expiry: session_expiry.or(source.session_expiry),
             ..source
         }
@@ -319,6 +326,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 payment_confirm_source,
                 updated_by,
                 session_expiry,
+                fingerprint_id,
             } => Self {
                 amount: Some(amount),
                 currency: Some(currency),
@@ -339,6 +347,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 payment_confirm_source,
                 updated_by,
                 session_expiry,
+                fingerprint_id,
                 ..Default::default()
             },
             PaymentIntentUpdate::MetadataUpdate {
diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs
index 3a3dee47a85..3a0a008b76b 100644
--- a/crates/diesel_models/src/query.rs
+++ b/crates/diesel_models/src/query.rs
@@ -1,11 +1,14 @@
 pub mod address;
 pub mod api_keys;
+pub mod blocklist_lookup;
 pub mod business_profile;
 mod capture;
 pub mod cards_info;
 pub mod configs;
 
 pub mod authorization;
+pub mod blocklist;
+pub mod blocklist_fingerprint;
 pub mod customers;
 pub mod dashboard_metadata;
 pub mod dispute;
diff --git a/crates/diesel_models/src/query/blocklist.rs b/crates/diesel_models/src/query/blocklist.rs
new file mode 100644
index 00000000000..e1ba5fa923d
--- /dev/null
+++ b/crates/diesel_models/src/query/blocklist.rs
@@ -0,0 +1,83 @@
+use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
+use router_env::{instrument, tracing};
+
+use super::generics;
+use crate::{
+    blocklist::{Blocklist, BlocklistNew},
+    schema::blocklist::dsl,
+    PgPooledConn, StorageResult,
+};
+
+impl BlocklistNew {
+    #[instrument(skip(conn))]
+    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Blocklist> {
+        generics::generic_insert(conn, self).await
+    }
+}
+
+impl Blocklist {
+    #[instrument(skip(conn))]
+    pub async fn find_by_merchant_id_fingerprint_id(
+        conn: &PgPooledConn,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> StorageResult<Self> {
+        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
+            conn,
+            dsl::merchant_id
+                .eq(merchant_id.to_owned())
+                .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())),
+        )
+        .await
+    }
+
+    #[instrument(skip(conn))]
+    pub async fn list_by_merchant_id_data_kind(
+        conn: &PgPooledConn,
+        merchant_id: &str,
+        data_kind: common_enums::BlocklistDataKind,
+        limit: i64,
+        offset: i64,
+    ) -> StorageResult<Vec<Self>> {
+        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
+            conn,
+            dsl::merchant_id
+                .eq(merchant_id.to_owned())
+                .and(dsl::data_kind.eq(data_kind.to_owned())),
+            Some(limit),
+            Some(offset),
+            Some(dsl::created_at.desc()),
+        )
+        .await
+    }
+
+    #[instrument(skip(conn))]
+    pub async fn list_by_merchant_id(
+        conn: &PgPooledConn,
+        merchant_id: &str,
+    ) -> StorageResult<Vec<Self>> {
+        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
+            conn,
+            dsl::merchant_id.eq(merchant_id.to_owned()),
+            None,
+            None,
+            Some(dsl::created_at.desc()),
+        )
+        .await
+    }
+
+    #[instrument(skip(conn))]
+    pub async fn delete_by_merchant_id_fingerprint_id(
+        conn: &PgPooledConn,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> StorageResult<Self> {
+        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
+            conn,
+            dsl::merchant_id
+                .eq(merchant_id.to_owned())
+                .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())),
+        )
+        .await
+    }
+}
diff --git a/crates/diesel_models/src/query/blocklist_fingerprint.rs b/crates/diesel_models/src/query/blocklist_fingerprint.rs
new file mode 100644
index 00000000000..4f3d77e63a8
--- /dev/null
+++ b/crates/diesel_models/src/query/blocklist_fingerprint.rs
@@ -0,0 +1,33 @@
+use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
+use router_env::{instrument, tracing};
+
+use super::generics;
+use crate::{
+    blocklist_fingerprint::{BlocklistFingerprint, BlocklistFingerprintNew},
+    schema::blocklist_fingerprint::dsl,
+    PgPooledConn, StorageResult,
+};
+
+impl BlocklistFingerprintNew {
+    #[instrument(skip(conn))]
+    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<BlocklistFingerprint> {
+        generics::generic_insert(conn, self).await
+    }
+}
+
+impl BlocklistFingerprint {
+    #[instrument(skip(conn))]
+    pub async fn find_by_merchant_id_fingerprint_id(
+        conn: &PgPooledConn,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> StorageResult<Self> {
+        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
+            conn,
+            dsl::merchant_id
+                .eq(merchant_id.to_owned())
+                .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())),
+        )
+        .await
+    }
+}
diff --git a/crates/diesel_models/src/query/blocklist_lookup.rs b/crates/diesel_models/src/query/blocklist_lookup.rs
new file mode 100644
index 00000000000..ea28c94e491
--- /dev/null
+++ b/crates/diesel_models/src/query/blocklist_lookup.rs
@@ -0,0 +1,48 @@
+use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
+use router_env::{instrument, tracing};
+
+use super::generics;
+use crate::{
+    blocklist_lookup::{BlocklistLookup, BlocklistLookupNew},
+    schema::blocklist_lookup::dsl,
+    PgPooledConn, StorageResult,
+};
+
+impl BlocklistLookupNew {
+    #[instrument(skip(conn))]
+    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<BlocklistLookup> {
+        generics::generic_insert(conn, self).await
+    }
+}
+
+impl BlocklistLookup {
+    #[instrument(skip(conn))]
+    pub async fn find_by_merchant_id_fingerprint(
+        conn: &PgPooledConn,
+        merchant_id: &str,
+        fingerprint: &str,
+    ) -> StorageResult<Self> {
+        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
+            conn,
+            dsl::merchant_id
+                .eq(merchant_id.to_owned())
+                .and(dsl::fingerprint.eq(fingerprint.to_owned())),
+        )
+        .await
+    }
+
+    #[instrument(skip(conn))]
+    pub async fn delete_by_merchant_id_fingerprint(
+        conn: &PgPooledConn,
+        merchant_id: &str,
+        fingerprint: &str,
+    ) -> StorageResult<Self> {
+        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
+            conn,
+            dsl::merchant_id
+                .eq(merchant_id.to_owned())
+                .and(dsl::fingerprint.eq(fingerprint.to_owned())),
+        )
+        .await
+    }
+}
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index b29a362e3b0..131d2b18266 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -57,6 +57,50 @@ diesel::table! {
     }
 }
 
+diesel::table! {
+    use diesel::sql_types::*;
+    use crate::enums::diesel_exports::*;
+
+    blocklist (id) {
+        id -> Int4,
+        #[max_length = 64]
+        merchant_id -> Varchar,
+        #[max_length = 64]
+        fingerprint_id -> Varchar,
+        data_kind -> BlocklistDataKind,
+        metadata -> Nullable<Jsonb>,
+        created_at -> Timestamp,
+    }
+}
+
+diesel::table! {
+    use diesel::sql_types::*;
+    use crate::enums::diesel_exports::*;
+
+    blocklist_fingerprint (id) {
+        id -> Int4,
+        #[max_length = 64]
+        merchant_id -> Varchar,
+        #[max_length = 64]
+        fingerprint_id -> Varchar,
+        data_kind -> BlocklistDataKind,
+        encrypted_fingerprint -> Text,
+        created_at -> Timestamp,
+    }
+}
+
+diesel::table! {
+    use diesel::sql_types::*;
+    use crate::enums::diesel_exports::*;
+
+    blocklist_lookup (id) {
+        id -> Int4,
+        #[max_length = 64]
+        merchant_id -> Varchar,
+        fingerprint -> Text,
+    }
+}
+
 diesel::table! {
     use diesel::sql_types::*;
     use crate::enums::diesel_exports::*;
@@ -709,6 +753,8 @@ diesel::table! {
         incremental_authorization_allowed -> Nullable<Bool>,
         authorization_count -> Nullable<Int4>,
         session_expiry -> Nullable<Timestamp>,
+        #[max_length = 64]
+        fingerprint_id -> Nullable<Varchar>,
     }
 }
 
@@ -1016,6 +1062,9 @@ diesel::table! {
 diesel::allow_tables_to_appear_in_same_query!(
     address,
     api_keys,
+    blocklist,
+    blocklist_fingerprint,
+    blocklist_lookup,
     business_profile,
     captures,
     cards_info,
diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs
index 5963110c632..63205ea68ca 100644
--- a/crates/router/src/compatibility/stripe/errors.rs
+++ b/crates/router/src/compatibility/stripe/errors.rs
@@ -520,6 +520,7 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
                 connector_name,
             },
             errors::ApiErrorResponse::DuplicatePaymentMethod => Self::DuplicatePaymentMethod,
+            errors::ApiErrorResponse::PaymentBlocked => Self::PaymentFailed,
             errors::ApiErrorResponse::ClientSecretInvalid => Self::PaymentIntentInvalidParameter {
                 param: "client_secret".to_owned(),
             },
diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index afe76184630..ed020b0c7e0 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -27,6 +27,9 @@ pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60;
 /// Payment intent default client secret expiry (in seconds)
 pub const DEFAULT_SESSION_EXPIRY: i64 = 15 * 60;
 
+/// The length of a merchant fingerprint secret
+pub const FINGERPRINT_SECRET_LENGTH: usize = 64;
+
 // String literals
 pub(crate) const NO_ERROR_MESSAGE: &str = "No error message";
 pub(crate) const NO_ERROR_CODE: &str = "No error code";
diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs
index 0bd197ee22e..5ae4b0be33d 100644
--- a/crates/router/src/core.rs
+++ b/crates/router/src/core.rs
@@ -1,6 +1,7 @@
 pub mod admin;
 pub mod api_keys;
 pub mod api_locking;
+pub mod blocklist;
 pub mod cache;
 pub mod cards_info;
 pub mod conditional_config;
diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs
index 2577bb83a3a..e8593581126 100644
--- a/crates/router/src/core/admin.rs
+++ b/crates/router/src/core/admin.rs
@@ -10,6 +10,7 @@ use common_utils::{
     ext_traits::{AsyncExt, ConfigExt, Encode, ValueExt},
     pii,
 };
+use diesel_models::configs;
 use error_stack::{report, FutureExt, IntoReport, ResultExt};
 use futures::future::try_join_all;
 use masking::{PeekInterface, Secret};
@@ -141,6 +142,17 @@ pub async fn create_merchant_account(
         .transpose()?
         .map(Secret::new);
 
+    let fingerprint = Some(utils::generate_id(consts::FINGERPRINT_SECRET_LENGTH, "fs"));
+    if let Some(fingerprint) = fingerprint {
+        db.insert_config(configs::ConfigNew {
+            key: format!("fingerprint_secret_{}", req.merchant_id),
+            config: fingerprint,
+        })
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Mot able to generate Merchant fingerprint")?;
+    };
+
     let organization_id = if let Some(organization_id) = req.organization_id.as_ref() {
         db.find_organization_by_org_id(organization_id)
             .await
diff --git a/crates/router/src/core/blocklist.rs b/crates/router/src/core/blocklist.rs
new file mode 100644
index 00000000000..85845602449
--- /dev/null
+++ b/crates/router/src/core/blocklist.rs
@@ -0,0 +1,41 @@
+pub mod transformers;
+pub mod utils;
+
+use api_models::blocklist as api_blocklist;
+
+use crate::{
+    core::errors::{self, RouterResponse},
+    routes::AppState,
+    services,
+    types::domain,
+};
+
+pub async fn add_entry_to_blocklist(
+    state: AppState,
+    merchant_account: domain::MerchantAccount,
+    body: api_blocklist::AddToBlocklistRequest,
+) -> RouterResponse<api_blocklist::AddToBlocklistResponse> {
+    utils::insert_entry_into_blocklist(&state, merchant_account.merchant_id, body)
+        .await
+        .map(services::ApplicationResponse::Json)
+}
+
+pub async fn remove_entry_from_blocklist(
+    state: AppState,
+    merchant_account: domain::MerchantAccount,
+    body: api_blocklist::DeleteFromBlocklistRequest,
+) -> RouterResponse<api_blocklist::DeleteFromBlocklistResponse> {
+    utils::delete_entry_from_blocklist(&state, merchant_account.merchant_id, body)
+        .await
+        .map(services::ApplicationResponse::Json)
+}
+
+pub async fn list_blocklist_entries(
+    state: AppState,
+    merchant_account: domain::MerchantAccount,
+    query: api_blocklist::ListBlocklistQuery,
+) -> RouterResponse<Vec<api_blocklist::BlocklistResponse>> {
+    utils::list_blocklist_entries_for_merchant(&state, merchant_account.merchant_id, query)
+        .await
+        .map(services::ApplicationResponse::Json)
+}
diff --git a/crates/router/src/core/blocklist/transformers.rs b/crates/router/src/core/blocklist/transformers.rs
new file mode 100644
index 00000000000..2cb5f86a264
--- /dev/null
+++ b/crates/router/src/core/blocklist/transformers.rs
@@ -0,0 +1,13 @@
+use api_models::blocklist;
+
+use crate::types::{storage, transformers::ForeignFrom};
+
+impl ForeignFrom<storage::Blocklist> for blocklist::AddToBlocklistResponse {
+    fn foreign_from(from: storage::Blocklist) -> Self {
+        Self {
+            fingerprint_id: from.fingerprint_id,
+            data_kind: from.data_kind,
+            created_at: from.created_at,
+        }
+    }
+}
diff --git a/crates/router/src/core/blocklist/utils.rs b/crates/router/src/core/blocklist/utils.rs
new file mode 100644
index 00000000000..b7effaf63ac
--- /dev/null
+++ b/crates/router/src/core/blocklist/utils.rs
@@ -0,0 +1,359 @@
+use api_models::blocklist as api_blocklist;
+use common_utils::crypto::{self, SignMessage};
+use error_stack::{IntoReport, ResultExt};
+#[cfg(feature = "kms")]
+use external_services::kms;
+
+use super::{errors, AppState};
+use crate::{
+    consts,
+    core::errors::{RouterResult, StorageErrorExt},
+    types::{storage, transformers::ForeignInto},
+    utils,
+};
+
+pub async fn delete_entry_from_blocklist(
+    state: &AppState,
+    merchant_id: String,
+    request: api_blocklist::DeleteFromBlocklistRequest,
+) -> RouterResult<api_blocklist::DeleteFromBlocklistResponse> {
+    let blocklist_entry = match request {
+        api_blocklist::DeleteFromBlocklistRequest::CardBin(bin) => {
+            delete_card_bin_blocklist_entry(state, &bin, &merchant_id).await?
+        }
+
+        api_blocklist::DeleteFromBlocklistRequest::ExtendedCardBin(xbin) => {
+            delete_card_bin_blocklist_entry(state, &xbin, &merchant_id).await?
+        }
+
+        api_blocklist::DeleteFromBlocklistRequest::Fingerprint(fingerprint_id) => {
+            let blocklist_fingerprint = state
+                .store
+                .find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
+                    &merchant_id,
+                    &fingerprint_id,
+                )
+                .await
+                .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
+                    message: "blocklist record with given fingerprint id not found".to_string(),
+                })?;
+
+            #[cfg(feature = "kms")]
+            let decrypted_fingerprint = kms::get_kms_client(&state.conf.kms)
+                .await
+                .decrypt(blocklist_fingerprint.encrypted_fingerprint)
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("failed to kms decrypt fingerprint")?;
+
+            #[cfg(not(feature = "kms"))]
+            let decrypted_fingerprint = blocklist_fingerprint.encrypted_fingerprint;
+
+            let blocklist_entry = state
+                .store
+                .delete_blocklist_entry_by_merchant_id_fingerprint_id(&merchant_id, &fingerprint_id)
+                .await
+                .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
+                    message: "no blocklist record for the given fingerprint id was found"
+                        .to_string(),
+                })?;
+
+            state
+                .store
+                .delete_blocklist_lookup_entry_by_merchant_id_fingerprint(
+                    &merchant_id,
+                    &decrypted_fingerprint,
+                )
+                .await
+                .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
+                    message: "no blocklist record for the given fingerprint id was found"
+                        .to_string(),
+                })?;
+
+            blocklist_entry
+        }
+    };
+
+    Ok(blocklist_entry.foreign_into())
+}
+
+pub async fn list_blocklist_entries_for_merchant(
+    state: &AppState,
+    merchant_id: String,
+    query: api_blocklist::ListBlocklistQuery,
+) -> RouterResult<Vec<api_blocklist::BlocklistResponse>> {
+    state
+        .store
+        .list_blocklist_entries_by_merchant_id_data_kind(
+            &merchant_id,
+            query.data_kind,
+            query.limit.into(),
+            query.offset.into(),
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
+            message: "no blocklist records found".to_string(),
+        })
+        .map(|v| v.into_iter().map(ForeignInto::foreign_into).collect())
+}
+
+fn validate_card_bin(bin: &str) -> RouterResult<()> {
+    if bin.len() == 6 && bin.chars().all(|c| c.is_ascii_digit()) {
+        Ok(())
+    } else {
+        Err(errors::ApiErrorResponse::InvalidDataFormat {
+            field_name: "data".to_string(),
+            expected_format: "a 6 digit number".to_string(),
+        })
+        .into_report()
+    }
+}
+
+fn validate_extended_card_bin(bin: &str) -> RouterResult<()> {
+    if bin.len() == 8 && bin.chars().all(|c| c.is_ascii_digit()) {
+        Ok(())
+    } else {
+        Err(errors::ApiErrorResponse::InvalidDataFormat {
+            field_name: "data".to_string(),
+            expected_format: "an 8 digit number".to_string(),
+        })
+        .into_report()
+    }
+}
+
+pub async fn insert_entry_into_blocklist(
+    state: &AppState,
+    merchant_id: String,
+    to_block: api_blocklist::AddToBlocklistRequest,
+) -> RouterResult<api_blocklist::AddToBlocklistResponse> {
+    let blocklist_entry = match &to_block {
+        api_blocklist::AddToBlocklistRequest::CardBin(bin) => {
+            validate_card_bin(bin)?;
+            duplicate_check_insert_bin(
+                bin,
+                state,
+                &merchant_id,
+                common_enums::BlocklistDataKind::CardBin,
+            )
+            .await?
+        }
+
+        api_blocklist::AddToBlocklistRequest::ExtendedCardBin(bin) => {
+            validate_extended_card_bin(bin)?;
+            duplicate_check_insert_bin(
+                bin,
+                state,
+                &merchant_id,
+                common_enums::BlocklistDataKind::ExtendedCardBin,
+            )
+            .await?
+        }
+
+        api_blocklist::AddToBlocklistRequest::Fingerprint(fingerprint_id) => {
+            let blocklist_entry_result = state
+                .store
+                .find_blocklist_entry_by_merchant_id_fingerprint_id(&merchant_id, fingerprint_id)
+                .await;
+
+            match blocklist_entry_result {
+                Ok(_) => {
+                    return Err(errors::ApiErrorResponse::PreconditionFailed {
+                        message: "data associated with the given fingerprint is already blocked"
+                            .to_string(),
+                    })
+                    .into_report();
+                }
+
+                // if it is a db not found error, we can proceed as normal
+                Err(inner) if inner.current_context().is_db_not_found() => {}
+
+                err @ Err(_) => {
+                    err.change_context(errors::ApiErrorResponse::InternalServerError)
+                        .attach_printable("error fetching blocklist entry from table")?;
+                }
+            }
+
+            let blocklist_fingerprint = state
+                .store
+                .find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
+                    &merchant_id,
+                    fingerprint_id,
+                )
+                .await
+                .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
+                    message: "fingerprint not found".to_string(),
+                })?;
+
+            #[cfg(feature = "kms")]
+            let decrypted_fingerprint = kms::get_kms_client(&state.conf.kms)
+                .await
+                .decrypt(blocklist_fingerprint.encrypted_fingerprint)
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("failed to kms decrypt encrypted fingerprint")?;
+
+            #[cfg(not(feature = "kms"))]
+            let decrypted_fingerprint = blocklist_fingerprint.encrypted_fingerprint;
+
+            state
+                .store
+                .insert_blocklist_lookup_entry(
+                    diesel_models::blocklist_lookup::BlocklistLookupNew {
+                        merchant_id: merchant_id.clone(),
+                        fingerprint: decrypted_fingerprint,
+                    },
+                )
+                .await
+                .to_duplicate_response(errors::ApiErrorResponse::PreconditionFailed {
+                    message: "the payment instrument associated with the given fingerprint is already in the blocklist".to_string(),
+                })
+                .attach_printable("failed to add fingerprint to blocklist lookup")?;
+
+            state
+                .store
+                .insert_blocklist_entry(storage::BlocklistNew {
+                    merchant_id: merchant_id.clone(),
+                    fingerprint_id: fingerprint_id.clone(),
+                    data_kind: blocklist_fingerprint.data_kind,
+                    metadata: None,
+                    created_at: common_utils::date_time::now(),
+                })
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("failed to add fingerprint to pm blocklist")?
+        }
+    };
+
+    Ok(blocklist_entry.foreign_into())
+}
+
+pub async fn get_merchant_fingerprint_secret(
+    state: &AppState,
+    merchant_id: &str,
+) -> RouterResult<String> {
+    let key = get_merchant_fingerprint_secret_key(merchant_id);
+    let config_fetch_result = state.store.find_config_by_key(&key).await;
+
+    match config_fetch_result {
+        Ok(config) => Ok(config.config),
+
+        Err(e) if e.current_context().is_db_not_found() => {
+            let new_fingerprint_secret =
+                utils::generate_id(consts::FINGERPRINT_SECRET_LENGTH, "fs");
+            let new_config = storage::ConfigNew {
+                key,
+                config: new_fingerprint_secret.clone(),
+            };
+
+            state
+                .store
+                .insert_config(new_config)
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("unable to create new fingerprint secret for merchant")?;
+
+            Ok(new_fingerprint_secret)
+        }
+
+        Err(e) => Err(e)
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("error fetching merchant fingerprint secret"),
+    }
+}
+
+pub fn get_merchant_fingerprint_secret_key(merchant_id: &str) -> String {
+    format!("fingerprint_secret_{merchant_id}")
+}
+
+async fn duplicate_check_insert_bin(
+    bin: &str,
+    state: &AppState,
+    merchant_id: &str,
+    data_kind: common_enums::BlocklistDataKind,
+) -> RouterResult<storage::Blocklist> {
+    let merchant_secret = get_merchant_fingerprint_secret(state, merchant_id).await?;
+    let bin_fingerprint = crypto::HmacSha512::sign_message(
+        &crypto::HmacSha512,
+        merchant_secret.clone().as_bytes(),
+        bin.as_bytes(),
+    )
+    .change_context(errors::ApiErrorResponse::InternalServerError)
+    .attach_printable("error in bin hash creation")?;
+
+    let encoded_fingerprint = hex::encode(bin_fingerprint.clone());
+
+    let blocklist_entry_result = state
+        .store
+        .find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin)
+        .await;
+
+    match blocklist_entry_result {
+        Ok(_) => {
+            return Err(errors::ApiErrorResponse::PreconditionFailed {
+                message: "provided bin is already blocked".to_string(),
+            })
+            .into_report();
+        }
+
+        Err(e) if e.current_context().is_db_not_found() => {}
+
+        err @ Err(_) => {
+            return err
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("unable to fetch blocklist entry");
+        }
+    }
+
+    // Checking for duplicacy
+    state
+        .store
+        .insert_blocklist_lookup_entry(diesel_models::blocklist_lookup::BlocklistLookupNew {
+            merchant_id: merchant_id.to_string(),
+            fingerprint: encoded_fingerprint.clone(),
+        })
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("error inserting blocklist lookup entry")?;
+
+    state
+        .store
+        .insert_blocklist_entry(storage::BlocklistNew {
+            merchant_id: merchant_id.to_string(),
+            fingerprint_id: bin.to_string(),
+            data_kind,
+            metadata: None,
+            created_at: common_utils::date_time::now(),
+        })
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("error inserting pm blocklist item")
+}
+
+async fn delete_card_bin_blocklist_entry(
+    state: &AppState,
+    bin: &str,
+    merchant_id: &str,
+) -> RouterResult<storage::Blocklist> {
+    let merchant_secret = get_merchant_fingerprint_secret(state, merchant_id).await?;
+    let bin_fingerprint = crypto::HmacSha512
+        .sign_message(merchant_secret.as_bytes(), bin.as_bytes())
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("error when hashing card bin")?;
+    let encoded_fingerprint = hex::encode(bin_fingerprint);
+
+    state
+        .store
+        .delete_blocklist_lookup_entry_by_merchant_id_fingerprint(merchant_id, &encoded_fingerprint)
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
+            message: "could not find a blocklist entry for the given bin".to_string(),
+        })?;
+
+    state
+        .store
+        .delete_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin)
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
+            message: "could not find a blocklist entry for the given bin".to_string(),
+        })
+}
diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs
index f94504cf274..54ec4ec1e29 100644
--- a/crates/router/src/core/errors/api_error_response.rs
+++ b/crates/router/src/core/errors/api_error_response.rs
@@ -186,6 +186,8 @@ pub enum ApiErrorResponse {
     PaymentNotSucceeded,
     #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified merchant connector account is disabled")]
     MerchantConnectorAccountDisabled,
+    #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified payment is blocked")]
+    PaymentBlocked,
     #[error(error_type= ErrorType::ObjectNotFound, code = "HE_04", message = "Successful payment not found for the given payment id")]
     SuccessfulPaymentNotFound,
     #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "The connector provided in the request is incorrect or not available")]
diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs
index fa9a5185790..ff764cafed6 100644
--- a/crates/router/src/core/errors/transformers.rs
+++ b/crates/router/src/core/errors/transformers.rs
@@ -187,6 +187,7 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
                 AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.clone()), ..Default::default() })))
             }
             Self::PaymentNotSucceeded => AER::BadRequest(ApiError::new("HE", 3, "The payment has not succeeded yet. Please pass a successful payment to initiate refund", None)),
+            Self::PaymentBlocked => AER::BadRequest(ApiError::new("HE", 3, "The payment is blocked", None)),
             Self::SuccessfulPaymentNotFound => {
                 AER::NotFound(ApiError::new("HE", 4, "Successful payment not found for the given payment id", None))
             }
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index ec6371f310f..003c09b7381 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -2586,6 +2586,7 @@ mod tests {
             modified_at: common_utils::date_time::now(),
             last_synced: None,
             setup_future_usage: None,
+            fingerprint_id: None,
             off_session: None,
             client_secret: Some("1".to_string()),
             active_attempt: data_models::RemoteStorageObject::ForeignID("nopes".to_string()),
@@ -2638,6 +2639,7 @@ mod tests {
             statement_descriptor_suffix: None,
             created_at: common_utils::date_time::now().saturating_sub(time::Duration::seconds(20)),
             modified_at: common_utils::date_time::now(),
+            fingerprint_id: None,
             last_synced: None,
             setup_future_usage: None,
             off_session: None,
@@ -2695,6 +2697,7 @@ mod tests {
             setup_future_usage: None,
             off_session: None,
             client_secret: None,
+            fingerprint_id: None,
             active_attempt: data_models::RemoteStorageObject::ForeignID("nopes".to_string()),
             business_country: None,
             business_label: None,
diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs
index 00ae8da6ae4..c81145c5de7 100644
--- a/crates/router/src/core/payments/operations/payment_confirm.rs
+++ b/crates/router/src/core/payments/operations/payment_confirm.rs
@@ -2,23 +2,30 @@ use std::marker::PhantomData;
 
 use api_models::enums::FrmSuggestion;
 use async_trait::async_trait;
-use common_utils::ext_traits::{AsyncExt, Encode};
+use common_utils::{
+    crypto::{self, SignMessage},
+    ext_traits::{AsyncExt, Encode},
+};
 use error_stack::{report, IntoReport, ResultExt};
+#[cfg(feature = "kms")]
+use external_services::kms;
 use futures::FutureExt;
 use router_derive::PaymentOperation;
-use router_env::{instrument, tracing};
+use router_env::{instrument, logger, tracing};
 use tracing_futures::Instrument;
 
 use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
 use crate::{
+    consts,
     core::{
+        blocklist::utils as blocklist_utils,
         errors::{self, CustomResult, RouterResult, StorageErrorExt},
         payment_methods::PaymentMethodRetrieve,
         payments::{
             self, helpers, operations, populate_surcharge_details, CustomerDetails, PaymentAddress,
             PaymentData,
         },
-        utils::{self as core_utils},
+        utils as core_utils,
     },
     db::StorageInterface,
     routes::AppState,
@@ -620,32 +627,34 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
     where
         F: 'b + Send,
     {
+        let db = state.store.as_ref();
         let payment_method = payment_data.payment_attempt.payment_method;
         let browser_info = payment_data.payment_attempt.browser_info.clone();
         let frm_message = payment_data.frm_message.clone();
 
-        let (intent_status, attempt_status, (error_code, error_message)) = match frm_suggestion {
-            Some(FrmSuggestion::FrmCancelTransaction) => (
-                storage_enums::IntentStatus::Failed,
-                storage_enums::AttemptStatus::Failure,
-                frm_message.map_or((None, None), |fraud_check| {
-                    (
-                        Some(Some(fraud_check.frm_status.to_string())),
-                        Some(fraud_check.frm_reason.map(|reason| reason.to_string())),
-                    )
-                }),
-            ),
-            Some(FrmSuggestion::FrmManualReview) => (
-                storage_enums::IntentStatus::RequiresMerchantAction,
-                storage_enums::AttemptStatus::Unresolved,
-                (None, None),
-            ),
-            _ => (
-                storage_enums::IntentStatus::Processing,
-                storage_enums::AttemptStatus::Pending,
-                (None, None),
-            ),
-        };
+        let (mut intent_status, mut attempt_status, (error_code, error_message)) =
+            match frm_suggestion {
+                Some(FrmSuggestion::FrmCancelTransaction) => (
+                    storage_enums::IntentStatus::Failed,
+                    storage_enums::AttemptStatus::Failure,
+                    frm_message.map_or((None, None), |fraud_check| {
+                        (
+                            Some(Some(fraud_check.frm_status.to_string())),
+                            Some(fraud_check.frm_reason.map(|reason| reason.to_string())),
+                        )
+                    }),
+                ),
+                Some(FrmSuggestion::FrmManualReview) => (
+                    storage_enums::IntentStatus::RequiresMerchantAction,
+                    storage_enums::AttemptStatus::Unresolved,
+                    (None, None),
+                ),
+                _ => (
+                    storage_enums::IntentStatus::Processing,
+                    storage_enums::AttemptStatus::Pending,
+                    (None, None),
+                ),
+            };
 
         let connector = payment_data.payment_attempt.connector.clone();
         let merchant_connector_id = payment_data.payment_attempt.merchant_connector_id.clone();
@@ -709,6 +718,157 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
         let m_error_message = error_message.clone();
         let m_db = state.clone().store;
 
+        // Validate Blocklist
+        let merchant_id = payment_data.payment_attempt.merchant_id;
+        let merchant_fingerprint_secret =
+            blocklist_utils::get_merchant_fingerprint_secret(state, &merchant_id).await?;
+
+        // Hashed Fingerprint to check whether or not this payment should be blocked.
+        let card_number_fingerprint = payment_data
+            .payment_method_data
+            .as_ref()
+            .and_then(|pm_data| match pm_data {
+                api_models::payments::PaymentMethodData::Card(card) => {
+                    crypto::HmacSha512::sign_message(
+                        &crypto::HmacSha512,
+                        merchant_fingerprint_secret.as_bytes(),
+                        card.card_number.clone().get_card_no().as_bytes(),
+                    )
+                    .attach_printable("error in pm fingerprint creation")
+                    .map_or_else(
+                        |err| {
+                            logger::error!(error=?err);
+                            None
+                        },
+                        Some,
+                    )
+                }
+                _ => None,
+            })
+            .map(hex::encode);
+
+        // Hashed Cardbin to check whether or not this payment should be blocked.
+        let card_bin_fingerprint = payment_data
+            .payment_method_data
+            .as_ref()
+            .and_then(|pm_data| match pm_data {
+                api_models::payments::PaymentMethodData::Card(card) => {
+                    crypto::HmacSha512::sign_message(
+                        &crypto::HmacSha512,
+                        merchant_fingerprint_secret.as_bytes(),
+                        card.card_number.clone().get_card_isin().as_bytes(),
+                    )
+                    .attach_printable("error in card bin hash creation")
+                    .map_or_else(
+                        |err| {
+                            logger::error!(error=?err);
+                            None
+                        },
+                        Some,
+                    )
+                }
+                _ => None,
+            })
+            .map(hex::encode);
+
+        // Hashed Extended Cardbin to check whether or not this payment should be blocked.
+        let extended_card_bin_fingerprint = payment_data
+            .payment_method_data
+            .as_ref()
+            .and_then(|pm_data| match pm_data {
+                api_models::payments::PaymentMethodData::Card(card) => {
+                    crypto::HmacSha512::sign_message(
+                        &crypto::HmacSha512,
+                        merchant_fingerprint_secret.as_bytes(),
+                        card.card_number.clone().get_extended_card_bin().as_bytes(),
+                    )
+                    .attach_printable("error in extended card bin hash creation")
+                    .map_or_else(
+                        |err| {
+                            logger::error!(error=?err);
+                            None
+                        },
+                        Some,
+                    )
+                }
+                _ => None,
+            })
+            .map(hex::encode);
+
+        let mut fingerprint_id = None;
+
+        //validating the payment method.
+        let mut is_pm_blocklisted = false;
+
+        let mut blocklist_futures = Vec::new();
+        if let Some(card_number_fingerprint) = card_number_fingerprint.as_ref() {
+            blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint(
+                &merchant_id,
+                card_number_fingerprint,
+            ));
+        }
+
+        if let Some(card_bin_fingerprint) = card_bin_fingerprint.as_ref() {
+            blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint(
+                &merchant_id,
+                card_bin_fingerprint,
+            ));
+        }
+
+        if let Some(extended_card_bin_fingerprint) = extended_card_bin_fingerprint.as_ref() {
+            blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint(
+                &merchant_id,
+                extended_card_bin_fingerprint,
+            ));
+        }
+
+        let blocklist_lookups = futures::future::join_all(blocklist_futures).await;
+
+        if blocklist_lookups.iter().any(|x| x.is_ok()) {
+            intent_status = storage_enums::IntentStatus::Failed;
+            attempt_status = storage_enums::AttemptStatus::Failure;
+            is_pm_blocklisted = true;
+        }
+
+        if let Some(encoded_hash) = card_number_fingerprint {
+            #[cfg(feature = "kms")]
+            let encrypted_fingerprint = kms::get_kms_client(&state.conf.kms)
+                .await
+                .encrypt(encoded_hash)
+                .await
+                .map_or_else(
+                    |e| {
+                        logger::error!(error=?e, "failed kms encryption of card fingerprint");
+                        None
+                    },
+                    Some,
+                );
+
+            #[cfg(not(feature = "kms"))]
+            let encrypted_fingerprint = Some(encoded_hash);
+
+            if let Some(encrypted_fingerprint) = encrypted_fingerprint {
+                fingerprint_id = db
+                    .insert_blocklist_fingerprint_entry(
+                        diesel_models::blocklist_fingerprint::BlocklistFingerprintNew {
+                            merchant_id,
+                            fingerprint_id: utils::generate_id(consts::ID_LENGTH, "fingerprint"),
+                            encrypted_fingerprint,
+                            data_kind: common_enums::BlocklistDataKind::PaymentMethod,
+                            created_at: common_utils::date_time::now(),
+                        },
+                    )
+                    .await
+                    .map_or_else(
+                        |e| {
+                            logger::error!(error=?e, "failed storing card fingerprint in db");
+                            None
+                        },
+                        |fp| Some(fp.fingerprint_id),
+                    );
+            }
+        }
+
         let surcharge_amount = payment_data
             .surcharge_details
             .as_ref()
@@ -789,6 +949,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
                         metadata: m_metadata,
                         payment_confirm_source: header_payload.payment_confirm_source,
                         updated_by: m_storage_scheme,
+                        fingerprint_id,
                         session_expiry,
                     },
                     storage_scheme,
@@ -838,6 +999,11 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
         payment_data.payment_intent = payment_intent;
         payment_data.payment_attempt = payment_attempt;
 
+        // Block the payment if the entry was present in the Blocklist
+        if is_pm_blocklisted {
+            return Err(errors::ApiErrorResponse::PaymentBlocked.into());
+        }
+
         Ok((Box::new(self), payment_data))
     }
 }
diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs
index 09ec436ed00..2b25a74deb1 100644
--- a/crates/router/src/core/payments/operations/payment_create.rs
+++ b/crates/router/src/core/payments/operations/payment_create.rs
@@ -825,6 +825,7 @@ impl PaymentCreate {
             request_incremental_authorization,
             incremental_authorization_allowed: None,
             authorization_count: None,
+            fingerprint_id: None,
             session_expiry: Some(session_expiry),
         })
     }
diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs
index afb83d38dc5..e002b92d181 100644
--- a/crates/router/src/core/payments/operations/payment_update.rs
+++ b/crates/router/src/core/payments/operations/payment_update.rs
@@ -617,6 +617,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
                     metadata,
                     payment_confirm_source: None,
                     updated_by: storage_scheme.to_string(),
+                    fingerprint_id: None,
                     session_expiry,
                 },
                 storage_scheme,
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 7b7d64a5f81..c3f52618f0a 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -706,6 +706,7 @@ where
                         .set_incremental_authorization_allowed(
                             payment_intent.incremental_authorization_allowed,
                         )
+                        .set_fingerprint(payment_intent.fingerprint_id)
                         .set_authorization_count(payment_intent.authorization_count)
                         .set_incremental_authorizations(incremental_authorizations_response)
                         .to_owned(),
diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs
index 5beace9cbb8..b9d346b7a71 100644
--- a/crates/router/src/db.rs
+++ b/crates/router/src/db.rs
@@ -1,6 +1,9 @@
 pub mod address;
 pub mod api_keys;
 pub mod authorization;
+pub mod blocklist;
+pub mod blocklist_fingerprint;
+pub mod blocklist_lookup;
 pub mod business_profile;
 pub mod cache;
 pub mod capture;
@@ -68,6 +71,7 @@ pub trait StorageInterface:
     + dyn_clone::DynClone
     + address::AddressInterface
     + api_keys::ApiKeyInterface
+    + blocklist_lookup::BlocklistLookupInterface
     + configs::ConfigInterface
     + capture::CaptureInterface
     + customers::CustomerInterface
@@ -85,6 +89,8 @@ pub trait StorageInterface:
     + PaymentAttemptInterface
     + PaymentIntentInterface
     + payment_method::PaymentMethodInterface
+    + blocklist::BlocklistInterface
+    + blocklist_fingerprint::BlocklistFingerprintInterface
     + scheduler::SchedulerInterface
     + payout_attempt::PayoutAttemptInterface
     + payouts::PayoutsInterface
diff --git a/crates/router/src/db/blocklist.rs b/crates/router/src/db/blocklist.rs
new file mode 100644
index 00000000000..c263bef63c5
--- /dev/null
+++ b/crates/router/src/db/blocklist.rs
@@ -0,0 +1,203 @@
+use error_stack::IntoReport;
+use router_env::{instrument, tracing};
+use storage_impl::MockDb;
+
+use super::Store;
+use crate::{
+    connection,
+    core::errors::{self, CustomResult},
+    db::kafka_store::KafkaStore,
+    types::storage,
+};
+
+#[async_trait::async_trait]
+pub trait BlocklistInterface {
+    async fn insert_blocklist_entry(
+        &self,
+        pm_blocklist_new: storage::BlocklistNew,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError>;
+
+    async fn find_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError>;
+
+    async fn delete_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError>;
+
+    async fn list_blocklist_entries_by_merchant_id(
+        &self,
+        merchant_id: &str,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError>;
+
+    async fn list_blocklist_entries_by_merchant_id_data_kind(
+        &self,
+        merchant_id: &str,
+        data_kind: common_enums::BlocklistDataKind,
+        limit: i64,
+        offset: i64,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError>;
+}
+
+#[async_trait::async_trait]
+impl BlocklistInterface for Store {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_entry(
+        &self,
+        pm_blocklist: storage::BlocklistNew,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        pm_blocklist
+            .insert(&conn)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+
+    async fn find_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::Blocklist::find_by_merchant_id_fingerprint_id(&conn, merchant_id, fingerprint_id)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+
+    async fn list_blocklist_entries_by_merchant_id(
+        &self,
+        merchant_id: &str,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::Blocklist::list_by_merchant_id(&conn, merchant_id)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+
+    async fn list_blocklist_entries_by_merchant_id_data_kind(
+        &self,
+        merchant_id: &str,
+        data_kind: common_enums::BlocklistDataKind,
+        limit: i64,
+        offset: i64,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::Blocklist::list_by_merchant_id_data_kind(
+            &conn,
+            merchant_id,
+            data_kind,
+            limit,
+            offset,
+        )
+        .await
+        .map_err(Into::into)
+        .into_report()
+    }
+
+    async fn delete_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::Blocklist::delete_by_merchant_id_fingerprint_id(&conn, merchant_id, fingerprint_id)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+}
+
+#[async_trait::async_trait]
+impl BlocklistInterface for MockDb {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_entry(
+        &self,
+        _pm_blocklist: storage::BlocklistNew,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    async fn find_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        _merchant_id: &str,
+        _fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    async fn list_blocklist_entries_by_merchant_id(
+        &self,
+        _merchant_id: &str,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    async fn list_blocklist_entries_by_merchant_id_data_kind(
+        &self,
+        _merchant_id: &str,
+        _data_kind: common_enums::BlocklistDataKind,
+        _limit: i64,
+        _offset: i64,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    async fn delete_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        _merchant_id: &str,
+        _fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+}
+
+#[async_trait::async_trait]
+impl BlocklistInterface for KafkaStore {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_entry(
+        &self,
+        _pm_blocklist: storage::BlocklistNew,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+
+    async fn find_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        _merchant_id: &str,
+        _fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+
+    async fn delete_blocklist_entry_by_merchant_id_fingerprint_id(
+        &self,
+        _merchant_id: &str,
+        _fingerprint_id: &str,
+    ) -> CustomResult<storage::Blocklist, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+
+    async fn list_blocklist_entries_by_merchant_id_data_kind(
+        &self,
+        _merchant_id: &str,
+        _data_kind: common_enums::BlocklistDataKind,
+        _limit: i64,
+        _offset: i64,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+
+    async fn list_blocklist_entries_by_merchant_id(
+        &self,
+        _merchant_id: &str,
+    ) -> CustomResult<Vec<storage::Blocklist>, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+}
diff --git a/crates/router/src/db/blocklist_fingerprint.rs b/crates/router/src/db/blocklist_fingerprint.rs
new file mode 100644
index 00000000000..9da7c7d8fb2
--- /dev/null
+++ b/crates/router/src/db/blocklist_fingerprint.rs
@@ -0,0 +1,95 @@
+use error_stack::IntoReport;
+use router_env::{instrument, tracing};
+use storage_impl::MockDb;
+
+use super::Store;
+use crate::{
+    connection,
+    core::errors::{self, CustomResult},
+    db::kafka_store::KafkaStore,
+    types::storage,
+};
+
+#[async_trait::async_trait]
+pub trait BlocklistFingerprintInterface {
+    async fn insert_blocklist_fingerprint_entry(
+        &self,
+        pm_fingerprint_new: storage::BlocklistFingerprintNew,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError>;
+
+    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
+        &self,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError>;
+}
+
+#[async_trait::async_trait]
+impl BlocklistFingerprintInterface for Store {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_fingerprint_entry(
+        &self,
+        pm_fingerprint_new: storage::BlocklistFingerprintNew,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        pm_fingerprint_new
+            .insert(&conn)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+
+    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
+        &self,
+        merchant_id: &str,
+        fingerprint_id: &str,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::BlocklistFingerprint::find_by_merchant_id_fingerprint_id(
+            &conn,
+            merchant_id,
+            fingerprint_id,
+        )
+        .await
+        .map_err(Into::into)
+        .into_report()
+    }
+}
+
+#[async_trait::async_trait]
+impl BlocklistFingerprintInterface for MockDb {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_fingerprint_entry(
+        &self,
+        _pm_fingerprint_new: storage::BlocklistFingerprintNew,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
+        &self,
+        _merchant_id: &str,
+        _fingerprint_id: &str,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+}
+
+#[async_trait::async_trait]
+impl BlocklistFingerprintInterface for KafkaStore {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_fingerprint_entry(
+        &self,
+        _pm_fingerprint_new: storage::BlocklistFingerprintNew,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+
+    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
+        &self,
+        _merchant_id: &str,
+        _fingerprint_id: &str,
+    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+}
diff --git a/crates/router/src/db/blocklist_lookup.rs b/crates/router/src/db/blocklist_lookup.rs
new file mode 100644
index 00000000000..0dfd81c8b8a
--- /dev/null
+++ b/crates/router/src/db/blocklist_lookup.rs
@@ -0,0 +1,125 @@
+use error_stack::IntoReport;
+use router_env::{instrument, tracing};
+use storage_impl::MockDb;
+
+use super::Store;
+use crate::{
+    connection,
+    core::errors::{self, CustomResult},
+    db::kafka_store::KafkaStore,
+    types::storage,
+};
+
+#[async_trait::async_trait]
+pub trait BlocklistLookupInterface {
+    async fn insert_blocklist_lookup_entry(
+        &self,
+        blocklist_lookup_new: storage::BlocklistLookupNew,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError>;
+
+    async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        merchant_id: &str,
+        fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError>;
+
+    async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        merchant_id: &str,
+        fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError>;
+}
+
+#[async_trait::async_trait]
+impl BlocklistLookupInterface for Store {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_lookup_entry(
+        &self,
+        blocklist_lookup_entry: storage::BlocklistLookupNew,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        blocklist_lookup_entry
+            .insert(&conn)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+
+    async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        merchant_id: &str,
+        fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::BlocklistLookup::find_by_merchant_id_fingerprint(&conn, merchant_id, fingerprint)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+
+    async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        merchant_id: &str,
+        fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::BlocklistLookup::delete_by_merchant_id_fingerprint(&conn, merchant_id, fingerprint)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+}
+
+#[async_trait::async_trait]
+impl BlocklistLookupInterface for MockDb {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_lookup_entry(
+        &self,
+        _blocklist_lookup_entry: storage::BlocklistLookupNew,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        _merchant_id: &str,
+        _fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        _merchant_id: &str,
+        _fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+}
+
+#[async_trait::async_trait]
+impl BlocklistLookupInterface for KafkaStore {
+    #[instrument(skip_all)]
+    async fn insert_blocklist_lookup_entry(
+        &self,
+        _blocklist_lookup_entry: storage::BlocklistLookupNew,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+
+    async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        _merchant_id: &str,
+        _fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+
+    async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint(
+        &self,
+        _merchant_id: &str,
+        _fingerprint: &str,
+    ) -> CustomResult<storage::BlocklistLookup, errors::StorageError> {
+        Err(errors::StorageError::KafkaError)?
+    }
+}
diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs
index 3b4c7ce9b7d..696198f2153 100644
--- a/crates/router/src/lib.rs
+++ b/crates/router/src/lib.rs
@@ -129,9 +129,9 @@ pub fn mk_app(
     #[cfg(feature = "oltp")]
     {
         server_app = server_app
-            .service(routes::PaymentMethods::server(state.clone()))
             .service(routes::EphemeralKey::server(state.clone()))
             .service(routes::Webhooks::server(state.clone()))
+            .service(routes::PaymentMethods::server(state.clone()))
     }
 
     #[cfg(feature = "olap")]
@@ -143,6 +143,7 @@ pub fn mk_app(
             .service(routes::Disputes::server(state.clone()))
             .service(routes::Analytics::server(state.clone()))
             .service(routes::Routing::server(state.clone()))
+            .service(routes::Blocklist::server(state.clone()))
             .service(routes::LockerMigrate::server(state.clone()))
             .service(routes::Gsm::server(state.clone()))
             .service(routes::PaymentLink::server(state.clone()))
diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs
index ec718b2dde9..d4bfabb6f92 100644
--- a/crates/router/src/routes.rs
+++ b/crates/router/src/routes.rs
@@ -1,6 +1,8 @@
 pub mod admin;
 pub mod api_keys;
 pub mod app;
+#[cfg(feature = "olap")]
+pub mod blocklist;
 pub mod cache;
 pub mod cards_info;
 pub mod configs;
@@ -42,14 +44,15 @@ pub mod webhooks;
 pub mod locker_migration;
 #[cfg(any(feature = "olap", feature = "oltp"))]
 pub mod pm_auth;
+#[cfg(feature = "olap")]
+pub use app::{Blocklist, Routing};
+
 #[cfg(feature = "dummy_connector")]
 pub use self::app::DummyConnector;
 #[cfg(any(feature = "olap", feature = "oltp"))]
 pub use self::app::Forex;
 #[cfg(feature = "payouts")]
 pub use self::app::Payouts;
-#[cfg(feature = "olap")]
-pub use self::app::Routing;
 #[cfg(all(feature = "olap", feature = "kms"))]
 pub use self::app::Verify;
 pub use self::app::{
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 6625a206be2..0efe2218a9a 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -14,6 +14,8 @@ use scheduler::SchedulerInterface;
 use storage_impl::MockDb;
 use tokio::sync::oneshot;
 
+#[cfg(feature = "olap")]
+use super::blocklist;
 #[cfg(any(feature = "olap", feature = "oltp"))]
 use super::currency;
 #[cfg(feature = "dummy_connector")]
@@ -566,6 +568,23 @@ impl PaymentMethods {
     }
 }
 
+#[cfg(feature = "olap")]
+pub struct Blocklist;
+
+#[cfg(feature = "olap")]
+impl Blocklist {
+    pub fn server(state: AppState) -> Scope {
+        web::scope("/blocklist")
+            .app_data(web::Data::new(state))
+            .service(
+                web::resource("")
+                    .route(web::get().to(blocklist::list_blocked_payment_methods))
+                    .route(web::post().to(blocklist::add_entry_to_blocklist))
+                    .route(web::delete().to(blocklist::remove_entry_from_blocklist)),
+            )
+    }
+}
+
 pub struct MerchantAccount;
 
 #[cfg(feature = "olap")]
diff --git a/crates/router/src/routes/blocklist.rs b/crates/router/src/routes/blocklist.rs
new file mode 100644
index 00000000000..7c268dddeec
--- /dev/null
+++ b/crates/router/src/routes/blocklist.rs
@@ -0,0 +1,81 @@
+use actix_web::{web, HttpRequest, HttpResponse};
+use api_models::blocklist as api_blocklist;
+use router_env::Flow;
+
+use crate::{
+    core::{api_locking, blocklist},
+    routes::AppState,
+    services::{api, authentication as auth, authorization::permissions::Permission},
+};
+
+pub async fn add_entry_to_blocklist(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<api_blocklist::AddToBlocklistRequest>,
+) -> HttpResponse {
+    let flow = Flow::AddToBlocklist;
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        json_payload.into_inner(),
+        |state, auth: auth::AuthenticationData, body| {
+            blocklist::add_entry_to_blocklist(state, auth.merchant_account, body)
+        },
+        auth::auth_type(
+            &auth::ApiKeyAuth,
+            &auth::JWTAuth(Permission::MerchantAccountWrite),
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn remove_entry_from_blocklist(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<api_blocklist::DeleteFromBlocklistRequest>,
+) -> HttpResponse {
+    let flow = Flow::DeleteFromBlocklist;
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        json_payload.into_inner(),
+        |state, auth: auth::AuthenticationData, body| {
+            blocklist::remove_entry_from_blocklist(state, auth.merchant_account, body)
+        },
+        auth::auth_type(
+            &auth::ApiKeyAuth,
+            &auth::JWTAuth(Permission::MerchantAccountWrite),
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn list_blocked_payment_methods(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    query_payload: web::Query<api_blocklist::ListBlocklistQuery>,
+) -> HttpResponse {
+    let flow = Flow::ListBlocklist;
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        query_payload.into_inner(),
+        |state, auth: auth::AuthenticationData, query| {
+            blocklist::list_blocklist_entries(state, auth.merchant_account, query)
+        },
+        auth::auth_type(
+            &auth::ApiKeyAuth,
+            &auth::JWTAuth(Permission::MerchantAccountRead),
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 10f408f3d4f..55c6cbc23d7 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -24,6 +24,7 @@ pub enum ApiIdentifier {
     ApiKeys,
     PaymentLink,
     Routing,
+    Blocklist,
     Forex,
     RustLockerMigration,
     Gsm,
@@ -57,6 +58,10 @@ impl From<Flow> for ApiIdentifier {
 
             Flow::RetrieveForexFlow => Self::Forex,
 
+            Flow::AddToBlocklist => Self::Blocklist,
+            Flow::DeleteFromBlocklist => Self::Blocklist,
+            Flow::ListBlocklist => Self::Blocklist,
+
             Flow::MerchantConnectorsCreate
             | Flow::MerchantConnectorsRetrieve
             | Flow::MerchantConnectorsUpdate
diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs
index 56d3272b947..b93cbbbbba9 100644
--- a/crates/router/src/types/storage.rs
+++ b/crates/router/src/types/storage.rs
@@ -1,6 +1,9 @@
 pub mod address;
 pub mod api_keys;
 pub mod authorization;
+pub mod blocklist;
+pub mod blocklist_fingerprint;
+pub mod blocklist_lookup;
 pub mod business_profile;
 pub mod capture;
 pub mod cards_info;
@@ -43,7 +46,8 @@ pub use diesel_models::{ProcessTracker, ProcessTrackerNew, ProcessTrackerUpdate}
 pub use scheduler::db::process_tracker;
 
 pub use self::{
-    address::*, api_keys::*, authorization::*, capture::*, cards_info::*, configs::*, customers::*,
+    address::*, api_keys::*, authorization::*, blocklist::*, blocklist_fingerprint::*,
+    blocklist_lookup::*, capture::*, cards_info::*, configs::*, customers::*,
     dashboard_metadata::*, dispute::*, ephemeral_key::*, events::*, file::*, fraud_check::*,
     gsm::*, locker_mock_up::*, mandate::*, merchant_account::*, merchant_connector_account::*,
     merchant_key_store::*, payment_link::*, payment_method::*, payout_attempt::*, payouts::*,
diff --git a/crates/router/src/types/storage/blocklist.rs b/crates/router/src/types/storage/blocklist.rs
new file mode 100644
index 00000000000..7e7648dd4a0
--- /dev/null
+++ b/crates/router/src/types/storage/blocklist.rs
@@ -0,0 +1 @@
+pub use diesel_models::blocklist::{Blocklist, BlocklistNew};
diff --git a/crates/router/src/types/storage/blocklist_fingerprint.rs b/crates/router/src/types/storage/blocklist_fingerprint.rs
new file mode 100644
index 00000000000..092d881e3fa
--- /dev/null
+++ b/crates/router/src/types/storage/blocklist_fingerprint.rs
@@ -0,0 +1 @@
+pub use diesel_models::blocklist_fingerprint::{BlocklistFingerprint, BlocklistFingerprintNew};
diff --git a/crates/router/src/types/storage/blocklist_lookup.rs b/crates/router/src/types/storage/blocklist_lookup.rs
new file mode 100644
index 00000000000..978708ff7c3
--- /dev/null
+++ b/crates/router/src/types/storage/blocklist_lookup.rs
@@ -0,0 +1 @@
+pub use diesel_models::blocklist_lookup::{BlocklistLookup, BlocklistLookupNew};
diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs
index 33f1e211534..dcf635595e0 100644
--- a/crates/router/src/utils/user/sample_data.rs
+++ b/crates/router/src/utils/user/sample_data.rs
@@ -199,6 +199,7 @@ pub async fn generate_sample_data(
             request_incremental_authorization: Default::default(),
             incremental_authorization_allowed: Default::default(),
             authorization_count: Default::default(),
+            fingerprint_id: None,
             session_expiry: Some(session_expiry),
         };
         let payment_attempt = PaymentAttemptBatchNew {
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index e37e15443bd..a6ac1b1e0a1 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -185,6 +185,12 @@ pub enum Flow {
     RoutingUpdateDefaultConfig,
     /// Routing delete config
     RoutingDeleteConfig,
+    /// Add record to blocklist
+    AddToBlocklist,
+    /// Delete record from blocklist
+    DeleteFromBlocklist,
+    /// List entries from blocklist
+    ListBlocklist,
     /// Incoming Webhook Receive
     IncomingWebhookReceive,
     /// Validate payment method flow
diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs
index 50173bb1c73..ac3a04e85b2 100644
--- a/crates/storage_impl/src/errors.rs
+++ b/crates/storage_impl/src/errors.rs
@@ -55,6 +55,8 @@ pub enum StorageError {
     SerializationFailed,
     #[error("MockDb error")]
     MockDbError,
+    #[error("Kafka error")]
+    KafkaError,
     #[error("Customer with this id is Redacted")]
     CustomerRedacted,
     #[error("Deserialization failure")]
@@ -103,6 +105,7 @@ impl Into<DataStorageError> for &StorageError {
             StorageError::KVError => DataStorageError::KVError,
             StorageError::SerializationFailed => DataStorageError::SerializationFailed,
             StorageError::MockDbError => DataStorageError::MockDbError,
+            StorageError::KafkaError => DataStorageError::KafkaError,
             StorageError::CustomerRedacted => DataStorageError::CustomerRedacted,
             StorageError::DeserializationFailed => DataStorageError::DeserializationFailed,
             StorageError::EncryptionError => DataStorageError::EncryptionError,
diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs
index ee8676106f1..3f892ed9fa7 100644
--- a/crates/storage_impl/src/mock_db/payment_intent.rs
+++ b/crates/storage_impl/src/mock_db/payment_intent.rs
@@ -109,6 +109,7 @@ impl PaymentIntentInterface for MockDb {
             request_incremental_authorization: new.request_incremental_authorization,
             incremental_authorization_allowed: new.incremental_authorization_allowed,
             authorization_count: new.authorization_count,
+            fingerprint_id: new.fingerprint_id,
             session_expiry: new.session_expiry,
         };
         payment_intents.push(payment_intent.clone());
diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs
index 07d70c9056b..8d20dfe0f32 100644
--- a/crates/storage_impl/src/payments/payment_intent.rs
+++ b/crates/storage_impl/src/payments/payment_intent.rs
@@ -101,6 +101,7 @@ impl<T: DatabaseStore> PaymentIntentInterface for KVRouterStore<T> {
                     request_incremental_authorization: new.request_incremental_authorization,
                     incremental_authorization_allowed: new.incremental_authorization_allowed,
                     authorization_count: new.authorization_count,
+                    fingerprint_id: new.fingerprint_id.clone(),
                     session_expiry: new.session_expiry,
                 };
                 let redis_entry = kv::TypedSql {
@@ -769,6 +770,7 @@ impl DataModelExt for PaymentIntentNew {
             request_incremental_authorization: self.request_incremental_authorization,
             incremental_authorization_allowed: self.incremental_authorization_allowed,
             authorization_count: self.authorization_count,
+            fingerprint_id: self.fingerprint_id,
             session_expiry: self.session_expiry,
         }
     }
@@ -813,6 +815,7 @@ impl DataModelExt for PaymentIntentNew {
             request_incremental_authorization: storage_model.request_incremental_authorization,
             incremental_authorization_allowed: storage_model.incremental_authorization_allowed,
             authorization_count: storage_model.authorization_count,
+            fingerprint_id: storage_model.fingerprint_id,
             session_expiry: storage_model.session_expiry,
         }
     }
@@ -862,6 +865,7 @@ impl DataModelExt for PaymentIntent {
             request_incremental_authorization: self.request_incremental_authorization,
             incremental_authorization_allowed: self.incremental_authorization_allowed,
             authorization_count: self.authorization_count,
+            fingerprint_id: self.fingerprint_id,
             session_expiry: self.session_expiry,
         }
     }
@@ -907,6 +911,7 @@ impl DataModelExt for PaymentIntent {
             request_incremental_authorization: storage_model.request_incremental_authorization,
             incremental_authorization_allowed: storage_model.incremental_authorization_allowed,
             authorization_count: storage_model.authorization_count,
+            fingerprint_id: storage_model.fingerprint_id,
             session_expiry: storage_model.session_expiry,
         }
     }
@@ -990,6 +995,7 @@ impl DataModelExt for PaymentIntentUpdate {
                 metadata,
                 payment_confirm_source,
                 updated_by,
+                fingerprint_id,
                 session_expiry,
             } => DieselPaymentIntentUpdate::Update {
                 amount,
@@ -1009,6 +1015,7 @@ impl DataModelExt for PaymentIntentUpdate {
                 metadata,
                 payment_confirm_source,
                 updated_by,
+                fingerprint_id,
                 session_expiry,
             },
             Self::PaymentAttemptAndAttemptCountUpdate {
diff --git a/migrations/2023-12-11-075542_create_pm_fingerprint_table/down.sql b/migrations/2023-12-11-075542_create_pm_fingerprint_table/down.sql
new file mode 100644
index 00000000000..74c450622a7
--- /dev/null
+++ b/migrations/2023-12-11-075542_create_pm_fingerprint_table/down.sql
@@ -0,0 +1,5 @@
+-- This file should undo anything in `up.sql`
+
+DROP TABLE blocklist_fingerprint;
+
+DROP TYPE "BlocklistDataKind";
diff --git a/migrations/2023-12-11-075542_create_pm_fingerprint_table/up.sql b/migrations/2023-12-11-075542_create_pm_fingerprint_table/up.sql
new file mode 100644
index 00000000000..417d779200f
--- /dev/null
+++ b/migrations/2023-12-11-075542_create_pm_fingerprint_table/up.sql
@@ -0,0 +1,19 @@
+-- Your SQL goes here
+
+CREATE TYPE "BlocklistDataKind" AS ENUM (
+    'payment_method',
+    'card_bin',
+    'extended_card_bin'
+);
+
+CREATE TABLE blocklist_fingerprint (
+  id SERIAL PRIMARY KEY,
+  merchant_id VARCHAR(64) NOT NULL,
+  fingerprint_id VARCHAR(64) NOT NULL,
+  data_kind "BlocklistDataKind" NOT NULL,
+  encrypted_fingerprint TEXT NOT NULL,
+  created_at TIMESTAMP NOT NULL
+);
+
+CREATE UNIQUE INDEX blocklist_fingerprint_merchant_id_fingerprint_id_index
+ON blocklist_fingerprint (merchant_id, fingerprint_id);
diff --git a/migrations/2023-12-12-112941_create_pm_blocklist_table/down.sql b/migrations/2023-12-12-112941_create_pm_blocklist_table/down.sql
new file mode 100644
index 00000000000..cd7d412aad9
--- /dev/null
+++ b/migrations/2023-12-12-112941_create_pm_blocklist_table/down.sql
@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+
+DROP TABLE blocklist;
diff --git a/migrations/2023-12-12-112941_create_pm_blocklist_table/up.sql b/migrations/2023-12-12-112941_create_pm_blocklist_table/up.sql
new file mode 100644
index 00000000000..6d921dd78c3
--- /dev/null
+++ b/migrations/2023-12-12-112941_create_pm_blocklist_table/up.sql
@@ -0,0 +1,13 @@
+-- Your SQL goes here
+
+CREATE TABLE blocklist (
+  id SERIAL PRIMARY KEY,
+  merchant_id VARCHAR(64) NOT NULL,
+  fingerprint_id VARCHAR(64) NOT NULL,
+  data_kind "BlocklistDataKind" NOT NULL,
+  metadata JSONB,
+  created_at TIMESTAMP NOT NULL
+);
+
+CREATE UNIQUE INDEX blocklist_unique_fingerprint_id_index ON blocklist (merchant_id, fingerprint_id);
+CREATE INDEX blocklist_merchant_id_data_kind_created_at_index ON blocklist (merchant_id, data_kind, created_at DESC);
diff --git a/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/down.sql b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/down.sql
new file mode 100644
index 00000000000..46b871b6ee4
--- /dev/null
+++ b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/down.sql
@@ -0,0 +1,2 @@
+-- This file should undo anything in `up.sql`
+ALTER TABLE payment_intent DROP COLUMN IF EXISTS fingerprint_id;
diff --git a/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/up.sql b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/up.sql
new file mode 100644
index 00000000000..831fb7b6ffc
--- /dev/null
+++ b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/up.sql
@@ -0,0 +1,2 @@
+-- Your SQL goes here
+ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS fingerprint_id VARCHAR(64);
diff --git a/migrations/2023-12-18-062613_create_blocklist_lookup_table/down.sql b/migrations/2023-12-18-062613_create_blocklist_lookup_table/down.sql
new file mode 100644
index 00000000000..d2363f547a5
--- /dev/null
+++ b/migrations/2023-12-18-062613_create_blocklist_lookup_table/down.sql
@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+
+DROP TABLE blocklist_lookup;
diff --git a/migrations/2023-12-18-062613_create_blocklist_lookup_table/up.sql b/migrations/2023-12-18-062613_create_blocklist_lookup_table/up.sql
new file mode 100644
index 00000000000..8af3e209fc6
--- /dev/null
+++ b/migrations/2023-12-18-062613_create_blocklist_lookup_table/up.sql
@@ -0,0 +1,9 @@
+-- Your SQL goes here
+
+CREATE TABLE blocklist_lookup (
+  id SERIAL PRIMARY KEY,
+  merchant_id VARCHAR(64) NOT NULL,
+  fingerprint TEXT NOT NULL
+);
+
+CREATE UNIQUE INDEX blocklist_lookup_merchant_id_fingerprint_index ON blocklist_lookup (merchant_id, fingerprint);
diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json
index dd27b5d609d..74cfc752c57 100644
--- a/openapi/openapi_spec.json
+++ b/openapi/openapi_spec.json
@@ -10744,6 +10744,11 @@
             },
             "description": "List of incremental authorizations happened to the payment",
             "nullable": true
+          },
+          "fingerprint": {
+            "type": "string",
+            "description": "Payment Fingerprint",
+            "nullable": true
           }
         }
       },