Bug: Implement `PaymentMethodInterface` for `MockDb`



Spin out from https://github.com/juspay/hyperswitch/issues/172. Please refer to that issue for more information.
diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs
index 3cd510b33e5..378298727a8 100644
--- a/crates/router/src/db.rs
+++ b/crates/router/src/db.rs
@@ -102,6 +102,7 @@ pub struct MockDb {
     merchant_connector_accounts: Arc<Mutex<Vec<storage::MerchantConnectorAccount>>>,
     payment_attempts: Arc<Mutex<Vec<storage::PaymentAttempt>>>,
     payment_intents: Arc<Mutex<Vec<storage::PaymentIntent>>>,
+    payment_methods: Arc<Mutex<Vec<storage::PaymentMethod>>>,
     customers: Arc<Mutex<Vec<storage::Customer>>>,
     refunds: Arc<Mutex<Vec<storage::Refund>>>,
     processes: Arc<Mutex<Vec<storage::ProcessTracker>>>,
@@ -123,6 +124,7 @@ impl MockDb {
             merchant_connector_accounts: Default::default(),
             payment_attempts: Default::default(),
             payment_intents: Default::default(),
+            payment_methods: Default::default(),
             customers: Default::default(),
             refunds: Default::default(),
             processes: Default::default(),
diff --git a/crates/router/src/db/payment_method.rs b/crates/router/src/db/payment_method.rs
index f39bf75b31c..3de5f74e691 100644
--- a/crates/router/src/db/payment_method.rs
+++ b/crates/router/src/db/payment_method.rs
@@ -1,4 +1,5 @@
 use error_stack::IntoReport;
+use storage_models::payment_method::PaymentMethodUpdateInternal;
 
 use super::{MockDb, Store};
 use crate::{
@@ -22,7 +23,7 @@ pub trait PaymentMethodInterface {
 
     async fn insert_payment_method(
         &self,
-        m: storage::PaymentMethodNew,
+        payment_method_new: storage::PaymentMethodNew,
     ) -> CustomResult<storage::PaymentMethod, errors::StorageError>;
 
     async fn update_payment_method(
@@ -53,10 +54,14 @@ impl PaymentMethodInterface for Store {
 
     async fn insert_payment_method(
         &self,
-        m: storage::PaymentMethodNew,
+        payment_method_new: storage::PaymentMethodNew,
     ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
         let conn = connection::pg_connection_write(self).await?;
-        m.insert(&conn).await.map_err(Into::into).into_report()
+        payment_method_new
+            .insert(&conn)
+            .await
+            .map_err(Into::into)
+            .into_report()
     }
 
     async fn update_payment_method(
@@ -105,44 +110,122 @@ impl PaymentMethodInterface for Store {
 impl PaymentMethodInterface for MockDb {
     async fn find_payment_method(
         &self,
-        _payment_method_id: &str,
+        payment_method_id: &str,
     ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let payment_methods = self.payment_methods.lock().await;
+        let payment_method = payment_methods
+            .iter()
+            .find(|pm| pm.payment_method_id == payment_method_id)
+            .cloned();
+
+        match payment_method {
+            Some(pm) => Ok(pm),
+            None => Err(errors::StorageError::ValueNotFound(
+                "cannot find payment method".to_string(),
+            )
+            .into()),
+        }
     }
 
     async fn insert_payment_method(
         &self,
-        _m: storage::PaymentMethodNew,
+        payment_method_new: storage::PaymentMethodNew,
     ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let mut payment_methods = self.payment_methods.lock().await;
+
+        let payment_method = storage::PaymentMethod {
+            #[allow(clippy::as_conversions)]
+            id: payment_methods.len() as i32,
+            customer_id: payment_method_new.customer_id,
+            merchant_id: payment_method_new.merchant_id,
+            payment_method_id: payment_method_new.payment_method_id,
+            accepted_currency: payment_method_new.accepted_currency,
+            scheme: payment_method_new.scheme,
+            token: payment_method_new.token,
+            cardholder_name: payment_method_new.cardholder_name,
+            issuer_name: payment_method_new.issuer_name,
+            issuer_country: payment_method_new.issuer_country,
+            payer_country: payment_method_new.payer_country,
+            is_stored: payment_method_new.is_stored,
+            swift_code: payment_method_new.swift_code,
+            direct_debit_token: payment_method_new.direct_debit_token,
+            created_at: payment_method_new.created_at,
+            last_modified: payment_method_new.last_modified,
+            payment_method: payment_method_new.payment_method,
+            payment_method_type: payment_method_new.payment_method_type,
+            payment_method_issuer: payment_method_new.payment_method_issuer,
+            payment_method_issuer_code: payment_method_new.payment_method_issuer_code,
+            metadata: payment_method_new.metadata,
+        };
+        payment_methods.push(payment_method.clone());
+        Ok(payment_method)
     }
 
     async fn find_payment_method_by_customer_id_merchant_id_list(
         &self,
-        _customer_id: &str,
-        _merchant_id: &str,
+        customer_id: &str,
+        merchant_id: &str,
     ) -> CustomResult<Vec<storage::PaymentMethod>, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let payment_methods = self.payment_methods.lock().await;
+        let payment_methods_found: Vec<storage::PaymentMethod> = payment_methods
+            .iter()
+            .filter(|pm| pm.customer_id == customer_id && pm.merchant_id == merchant_id)
+            .cloned()
+            .collect();
+
+        if payment_methods_found.is_empty() {
+            Err(
+                errors::StorageError::ValueNotFound("cannot find payment method".to_string())
+                    .into(),
+            )
+        } else {
+            Ok(payment_methods_found)
+        }
     }
 
     async fn delete_payment_method_by_merchant_id_payment_method_id(
         &self,
-        _merchant_id: &str,
-        _payment_method_id: &str,
+        merchant_id: &str,
+        payment_method_id: &str,
     ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let mut payment_methods = self.payment_methods.lock().await;
+        match payment_methods.iter().position(|pm| {
+            pm.merchant_id == merchant_id && pm.payment_method_id == payment_method_id
+        }) {
+            Some(index) => {
+                let deleted_payment_method = payment_methods.remove(index);
+                Ok(deleted_payment_method)
+            }
+            None => Err(errors::StorageError::ValueNotFound(
+                "cannot find payment method to delete".to_string(),
+            )
+            .into()),
+        }
     }
 
     async fn update_payment_method(
         &self,
-        _payment_method: storage::PaymentMethod,
-        _payment_method_update: storage::PaymentMethodUpdate,
+        payment_method: storage::PaymentMethod,
+        payment_method_update: storage::PaymentMethodUpdate,
     ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        match self
+            .payment_methods
+            .lock()
+            .await
+            .iter_mut()
+            .find(|pm| pm.id == payment_method.id)
+            .map(|pm| {
+                let payment_method_updated =
+                    PaymentMethodUpdateInternal::from(payment_method_update)
+                        .create_payment_method(pm.clone());
+                *pm = payment_method_updated.clone();
+                payment_method_updated
+            }) {
+            Some(result) => Ok(result),
+            None => Err(errors::StorageError::ValueNotFound(
+                "cannot find payment method to update".to_string(),
+            )
+            .into()),
+        }
     }
 }
diff --git a/crates/storage_models/src/payment_method.rs b/crates/storage_models/src/payment_method.rs
index c66b00a1be3..be69d3fa5a3 100644
--- a/crates/storage_models/src/payment_method.rs
+++ b/crates/storage_models/src/payment_method.rs
@@ -105,6 +105,14 @@ pub struct PaymentMethodUpdateInternal {
     metadata: Option<serde_json::Value>,
 }
 
+impl PaymentMethodUpdateInternal {
+    pub fn create_payment_method(self, source: PaymentMethod) -> PaymentMethod {
+        let metadata = self.metadata.map(Secret::new);
+
+        PaymentMethod { metadata, ..source }
+    }
+}
+
 impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
     fn from(payment_method_update: PaymentMethodUpdate) -> Self {
         match payment_method_update {

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [x] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->


### Additional Changes

- [ ] This PR modifies the API contract
- [ ] 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`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
The main motivation is to have MockDb stubs, help to void mocking, and invocation of external database api's.
For more information check https://github.com/juspay/hyperswitch/issues/172

Fixes https://github.com/juspay/hyperswitch/issues/1491.


## 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)?
-->


## 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`
- [x] I reviewed submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
