diff --git a/config/config.example.toml b/config/config.example.toml index c5e84d38b3..28113bfb3c 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -96,10 +96,12 @@ use_xray_generator = false # Set this to true for AWS # This section provides some secret values. [secrets] +master_enc_key = "sample_key" # Master Encryption key used to encrypt merchant wise encryption key. Should be 32-byte long. admin_api_key = "test_admin" # admin API key for admin authentication. Only applicable when KMS is disabled. kms_encrypted_admin_api_key = "" # Base64-encoded (KMS encrypted) ciphertext of the admin_api_key. Only applicable when KMS is enabled. jwt_secret = "secret" # JWT secret used for user authentication. Only applicable when KMS is disabled. kms_encrypted_jwt_secret = "" # Base64-encoded (KMS encrypted) ciphertext of the jwt_secret. Only applicable when KMS is enabled. +migration_encryption_timestamp = 0 # Timestamp to decide which entries are not encrypted in the database. # Locker settings contain details for accessing a card locker, a # PCI Compliant storage entity which stores payment method information diff --git a/config/development.toml b/config/development.toml index 79b760d8ed..48a476231c 100644 --- a/config/development.toml +++ b/config/development.toml @@ -32,6 +32,8 @@ connection_timeout = 10 [secrets] admin_api_key = "test_admin" +migration_encryption_timestamp = 1682425530 +master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a" [locker] host = "" diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 94f2956de3..e7164a2493 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1,4 +1,7 @@ -use common_utils::pii; +use common_utils::{ + crypto::{Encryptable, OptionalEncryptableName}, + pii, +}; use masking::Secret; use serde::{Deserialize, Serialize}; use url; @@ -15,8 +18,8 @@ pub struct MerchantAccountCreate { pub merchant_id: String, /// Name of the Merchant Account - #[schema(example = "NewAge Retailer")] - pub merchant_name: Option, + #[schema(value_type= Option,example = "NewAge Retailer")] + pub merchant_name: Option>, /// Merchant related details pub merchant_details: Option, @@ -157,8 +160,8 @@ pub struct MerchantAccountResponse { pub merchant_id: String, /// Name of the Merchant Account - #[schema(example = "NewAge Retailer")] - pub merchant_name: Option, + #[schema(value_type = Option,example = "NewAge Retailer")] + pub merchant_name: OptionalEncryptableName, /// The URL to redirect after the completion of the operation #[schema(max_length = 255, example = "https://www.example.com/success")] @@ -178,7 +181,7 @@ pub struct MerchantAccountResponse { /// Merchant related details #[schema(value_type = Option)] - pub merchant_details: Option, + pub merchant_details: Option>, /// Webhook related details #[schema(value_type = Option)] diff --git a/crates/api_models/src/customers.rs b/crates/api_models/src/customers.rs index b95d1758a4..7e4962a895 100644 --- a/crates/api_models/src/customers.rs +++ b/crates/api_models/src/customers.rs @@ -1,4 +1,4 @@ -use common_utils::{consts, custom_serde, pii}; +use common_utils::{consts, crypto, custom_serde, pii}; use masking::Secret; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -16,7 +16,7 @@ pub struct CustomerRequest { pub merchant_id: String, /// The customer's name #[schema(max_length = 255, example = "Jon Test")] - pub name: Option, + pub name: Option>, /// The customer's email address #[schema(value_type = Option,max_length = 255, example = "JonTest@test.com")] pub email: Option, @@ -56,13 +56,13 @@ pub struct CustomerResponse { pub customer_id: String, /// The customer's name #[schema(max_length = 255, example = "Jon Test")] - pub name: Option, + pub name: crypto::OptionalEncryptableName, /// The customer's email address #[schema(value_type = Option,max_length = 255, example = "JonTest@test.com")] - pub email: Option, + pub email: crypto::OptionalEncryptableEmail, /// The customer's phone number #[schema(value_type = Option,max_length = 255, example = "9999999999")] - pub phone: Option>, + pub phone: crypto::OptionalEncryptablePhone, /// The country code for the customer phone number #[schema(max_length = 255, example = "+65")] pub phone_country_code: Option, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index eeaceec211..56b781da2f 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1,7 +1,10 @@ use std::{collections::HashMap, num::NonZeroI64}; use cards::CardNumber; -use common_utils::{pii, pii::Email}; +use common_utils::{ + crypto, + pii::{self, Email}, +}; use masking::{PeekInterface, Secret}; use router_derive::Setter; use time::PrimitiveDateTime; @@ -1124,7 +1127,7 @@ pub struct ReceiverDetails { amount_remaining: Option, } -#[derive(Setter, Clone, Default, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] +#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] pub struct PaymentsResponse { /// Unique identifier for the payment. This ensures idempotency for multiple payments /// that have been done by a single merchant. @@ -1240,15 +1243,15 @@ pub struct PaymentsResponse { /// description: The customer's email address #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] - pub email: Option, + pub email: crypto::OptionalEncryptableEmail, /// description: The customer's name #[schema(value_type = Option, max_length = 255, example = "John Test")] - pub name: Option>, + pub name: crypto::OptionalEncryptableName, /// The customer's phone number #[schema(value_type = Option, max_length = 255, example = "3141592653")] - pub phone: Option>, + pub phone: crypto::OptionalEncryptablePhone, /// The URL to redirect after the completion of the operation #[schema(example = "https://hyperswitch.io")] @@ -1377,16 +1380,16 @@ pub struct PaymentListResponse { pub data: Vec, } -#[derive(Setter, Clone, Default, Debug, serde::Serialize)] +#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize)] pub struct VerifyResponse { pub verify_id: Option, pub merchant_id: Option, // pub status: enums::VerifyStatus, pub client_secret: Option>, pub customer_id: Option, - pub email: Option, - pub name: Option>, - pub phone: Option>, + pub email: crypto::OptionalEncryptableEmail, + pub name: crypto::OptionalEncryptableName, + pub phone: crypto::OptionalEncryptablePhone, pub mandate_id: Option, #[auth_based] pub payment_method: Option, @@ -1441,24 +1444,6 @@ impl From<&VerifyRequest> for MandateValidationFields { } } -impl From for VerifyResponse { - fn from(item: VerifyRequest) -> Self { - Self { - merchant_id: item.merchant_id, - customer_id: item.customer_id, - email: item.email, - name: item.name, - phone: item.phone, - payment_method: item.payment_method, - payment_method_data: item - .payment_method_data - .map(PaymentMethodDataResponse::from), - payment_token: item.payment_token, - ..Default::default() - } - } -} - impl From for PaymentsSessionResponse { fn from(item: PaymentsSessionRequest) -> Self { let client_secret: Secret = Secret::new(item.client_secret); diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index 092f616245..ada1549a23 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -24,12 +24,15 @@ once_cell = "1.17.1" quick-xml = { version = "0.28.2", features = ["serialize"] } rand = "0.8.5" regex = "1.7.3" -ring = "0.16.20" +ring = { version = "0.16.20", features = ["std"] } serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" serde_urlencoded = "0.7.1" signal-hook = { version = "0.3.15", optional = true } -tokio = { version = "1.27.0", features = ["macros", "rt-multi-thread"], optional = true } +tokio = { version = "1.27.0", features = [ + "macros", + "rt-multi-thread", +], optional = true } thiserror = "1.0.40" time = { version = "0.3.20", features = ["serde", "serde-well-known", "std"] } md5 = "0.7.0" diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index 1a41b8739f..27d941b323 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -1,11 +1,69 @@ //! Utilities for cryptographic algorithms +use std::ops::Deref; + use error_stack::{IntoReport, ResultExt}; use md5; -use ring::{aead, hmac}; +use ring::{ + aead::{self, BoundKey, OpeningKey, SealingKey, UnboundKey}, + hmac, +}; -use crate::errors::{self, CustomResult}; +use crate::{ + errors::{self, CustomResult}, + pii, +}; -const RING_ERR_UNSPECIFIED: &str = "ring::error::Unspecified"; +#[derive(Clone, Debug)] +struct NonceSequence(u128); + +impl NonceSequence { + /// Byte index at which sequence number starts in a 16-byte (128-bit) sequence. + /// This byte index considers the big endian order used while encoding and decoding the nonce + /// to/from a 128-bit unsigned integer. + const SEQUENCE_NUMBER_START_INDEX: usize = 4; + + /// Generate a random nonce sequence. + fn new() -> Result { + use ring::rand::{SecureRandom, SystemRandom}; + + let rng = SystemRandom::new(); + + // 96-bit sequence number, stored in a 128-bit unsigned integer in big-endian order + let mut sequence_number = [0_u8; 128 / 8]; + rng.fill(&mut sequence_number[Self::SEQUENCE_NUMBER_START_INDEX..])?; + let sequence_number = u128::from_be_bytes(sequence_number); + + Ok(Self(sequence_number)) + } + + /// Returns the current nonce value as bytes. + fn current(&self) -> [u8; ring::aead::NONCE_LEN] { + let mut nonce = [0_u8; ring::aead::NONCE_LEN]; + nonce.copy_from_slice(&self.0.to_be_bytes()[Self::SEQUENCE_NUMBER_START_INDEX..]); + nonce + } + + /// Constructs a nonce sequence from bytes + fn from_bytes(bytes: [u8; ring::aead::NONCE_LEN]) -> Self { + let mut sequence_number = [0_u8; 128 / 8]; + sequence_number[Self::SEQUENCE_NUMBER_START_INDEX..].copy_from_slice(&bytes); + let sequence_number = u128::from_be_bytes(sequence_number); + Self(sequence_number) + } +} + +impl ring::aead::NonceSequence for NonceSequence { + fn advance(&mut self) -> Result { + let mut nonce = [0_u8; ring::aead::NONCE_LEN]; + nonce.copy_from_slice(&self.0.to_be_bytes()[Self::SEQUENCE_NUMBER_START_INDEX..]); + + // Increment sequence number + self.0 = self.0.wrapping_add(1); + + // Return previous sequence number as bytes + Ok(ring::aead::Nonce::assume_unique_for_key(nonce)) + } +} /// Trait for cryptographically signing messages pub trait SignMessage { @@ -36,7 +94,7 @@ pub trait EncodeMessage { &self, _secret: &[u8], _msg: &[u8], - ) -> CustomResult<(Vec, Vec), errors::CryptoError>; + ) -> CustomResult, errors::CryptoError>; } /// Trait for cryptographically decoding a message @@ -45,7 +103,7 @@ pub trait DecodeMessage { fn decode_message( &self, _secret: &[u8], - _msg: &[u8], + _msg: Vec, ) -> CustomResult, errors::CryptoError>; } @@ -80,8 +138,8 @@ impl EncodeMessage for NoAlgorithm { &self, _secret: &[u8], msg: &[u8], - ) -> CustomResult<(Vec, Vec), errors::CryptoError> { - Ok((msg.to_vec(), Vec::new())) + ) -> CustomResult, errors::CryptoError> { + Ok(msg.to_vec()) } } @@ -89,7 +147,7 @@ impl DecodeMessage for NoAlgorithm { fn decode_message( &self, _secret: &[u8], - msg: &[u8], + msg: Vec, ) -> CustomResult, errors::CryptoError> { Ok(msg.to_vec()) } @@ -153,36 +211,30 @@ impl VerifySignature for HmacSha512 { /// Represents the GCM-AES-256 algorithm #[derive(Debug)] -pub struct GcmAes256 { - nonce: Vec, -} +pub struct GcmAes256; impl EncodeMessage for GcmAes256 { fn encode_message( &self, secret: &[u8], msg: &[u8], - ) -> CustomResult<(Vec, Vec), errors::CryptoError> { - let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, secret) - .map_err(|_| errors::CryptoError::EncodingFailed) + ) -> CustomResult, errors::CryptoError> { + let nonce_sequence = NonceSequence::new() .into_report() - .attach_printable(RING_ERR_UNSPECIFIED)?; - - let nonce = aead::Nonce::try_assume_unique_for_key(&self.nonce) - .map_err(|_| errors::CryptoError::EncodingFailed) + .change_context(errors::CryptoError::EncodingFailed)?; + let current_nonce = nonce_sequence.current(); + let key = UnboundKey::new(&aead::AES_256_GCM, secret) .into_report() - .attach_printable(RING_ERR_UNSPECIFIED)?; + .change_context(errors::CryptoError::EncodingFailed)?; + let mut key = SealingKey::new(key, nonce_sequence); + let mut in_out = msg.to_vec(); - let sealing_key = aead::LessSafeKey::new(unbound_key); - let mut mutable_msg = msg.to_vec(); - - let tag = sealing_key - .seal_in_place_separate_tag(nonce, aead::Aad::empty(), &mut mutable_msg) - .map_err(|_| errors::CryptoError::EncodingFailed) + key.seal_in_place_append_tag(aead::Aad::empty(), &mut in_out) .into_report() - .attach_printable(RING_ERR_UNSPECIFIED)?; + .change_context(errors::CryptoError::EncodingFailed)?; + in_out.splice(0..0, current_nonce); - Ok((mutable_msg, tag.as_ref().to_vec())) + Ok(in_out) } } @@ -190,29 +242,29 @@ impl DecodeMessage for GcmAes256 { fn decode_message( &self, secret: &[u8], - msg: &[u8], + msg: Vec, ) -> CustomResult, errors::CryptoError> { - let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, secret) - .map_err(|_| errors::CryptoError::DecodingFailed) + let key = UnboundKey::new(&aead::AES_256_GCM, secret) .into_report() - .attach_printable(RING_ERR_UNSPECIFIED)?; + .change_context(errors::CryptoError::DecodingFailed)?; - let nonce = aead::Nonce::try_assume_unique_for_key(&self.nonce) - .map_err(|_| errors::CryptoError::DecodingFailed) + let nonce_sequence = NonceSequence::from_bytes( + msg[..ring::aead::NONCE_LEN] + .try_into() + .into_report() + .change_context(errors::CryptoError::DecodingFailed)?, + ); + + let mut key = OpeningKey::new(key, nonce_sequence); + let mut binding = msg; + let output = binding.as_mut_slice(); + + let result = key + .open_within(aead::Aad::empty(), output, ring::aead::NONCE_LEN..) .into_report() - .attach_printable(RING_ERR_UNSPECIFIED)?; + .change_context(errors::CryptoError::DecodingFailed)?; - let opening_key = aead::LessSafeKey::new(unbound_key); - - let mut mutable_msg = msg.to_vec(); - - let output = opening_key - .open_in_place(nonce, aead::Aad::empty(), &mut mutable_msg) - .map_err(|_| errors::CryptoError::DecodingFailed) - .into_report() - .attach_printable(RING_ERR_UNSPECIFIED)?; - - Ok(output.to_vec()) + Ok(result.into()) } } @@ -322,6 +374,84 @@ pub fn generate_cryptographically_secure_random_bytes() -> [u8; bytes } +/// +/// A wrapper type to store the encrypted data for sensitive pii domain data types +/// +#[derive(Debug, Clone)] +pub struct Encryptable { + inner: T, + encrypted: Vec, +} + +impl> Encryptable> { + /// + /// constructor function to be used by the encryptor and decryptor to generate the data type + /// + pub fn new(masked_data: masking::Secret, encrypted_data: Vec) -> Self { + Self { + inner: masked_data, + encrypted: encrypted_data, + } + } +} + +impl Encryptable { + /// + /// Get the inner data while consumping self + /// + pub fn into_inner(self) -> T { + self.inner + } + /// + /// Get the inner encrypted data while consuming self + /// + pub fn into_encrypted(self) -> Vec { + self.encrypted + } +} + +impl Deref for Encryptable> { + type Target = masking::Secret; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl masking::Serialize for Encryptable +where + T: masking::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.inner.serialize(serializer) + } +} + +impl PartialEq for Encryptable +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.inner.eq(&other.inner) + } +} + +/// Type alias for `Option>>` +pub type OptionalEncryptableSecretString = Option>>; +/// Type alias for `Option>>` used for `name` field +pub type OptionalEncryptableName = Option>>; +/// Type alias for `Option>>` used for `email` field +pub type OptionalEncryptableEmail = + Option>>; +/// Type alias for `Option>>` used for `phone` field +pub type OptionalEncryptablePhone = Option>>; +/// Type alias for `Option>>` used for `phone` field +pub type OptionalEncryptableValue = Option>>; +/// Type alias for `Option>` used for `phone` field +pub type OptionalSecretValue = Option>; + #[cfg(test)] mod crypto_tests { #![allow(clippy::expect_used)] @@ -434,22 +564,18 @@ mod crypto_tests { let secret = hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f") .expect("Secret decoding"); - let nonce = hex::decode("000000000000000000000000").expect("Nonce hex decoding"); - let actual_encoded_message = - hex::decode("0A3471C72D9BE49A8520F79C66BBD9A12FF9").expect("Message decoding"); - let actual_auth_tag = - hex::decode("CE573FB7A41AB78E743180DC83FF09BD").expect("Auth tag decoding"); + let algorithm = super::GcmAes256; - let algorithm = super::GcmAes256 { - nonce: nonce.to_vec(), - }; - - let (encoded_message, auth_tag) = algorithm + let encoded_message = algorithm .encode_message(&secret, message) .expect("Encoded message and tag"); - assert_eq!(encoded_message, actual_encoded_message); - assert_eq!(auth_tag, actual_auth_tag); + assert_eq!( + algorithm + .decode_message(&secret, encoded_message) + .expect("Decode Failed"), + message + ); } #[test] @@ -460,25 +586,20 @@ mod crypto_tests { let wrong_secret = hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0e") .expect("Secret decoding"); - let nonce = hex::decode("000000000000000000000000").expect("Nonce hex decoding"); - let mut auth_tag = - hex::decode("CE573FB7A41AB78E743180DC83FF09BD").expect("Auth tag decoding"); - let mut message = + let message = hex::decode("0A3471C72D9BE49A8520F79C66BBD9A12FF9").expect("Message decoding"); - message.append(&mut auth_tag); - let algorithm = super::GcmAes256 { - nonce: nonce.to_vec(), + // nonce: nonce.to_vec(), }; let decoded = algorithm - .decode_message(&right_secret, &message) + .decode_message(&right_secret, message.clone()) .expect("Decoded message"); assert_eq!(decoded, r#"{"type":"PAYMENT"}"#.as_bytes()); - let err_decoded = algorithm.decode_message(&wrong_secret, &message); + let err_decoded = algorithm.decode_message(&wrong_secret, message); assert!(err_decoded.is_err()); } diff --git a/crates/common_utils/src/pii.rs b/crates/common_utils/src/pii.rs index 611402c335..6b109ad1dd 100644 --- a/crates/common_utils/src/pii.rs +++ b/crates/common_utils/src/pii.rs @@ -11,9 +11,10 @@ use diesel::{ sql_types, AsExpression, }; use error_stack::{IntoReport, ResultExt}; -use masking::{Secret, Strategy, WithType}; +use masking::{ExposeInterface, Secret, Strategy, WithType}; use crate::{ + crypto::Encryptable, errors::{self, ValidationError}, validation::validate_email, }; @@ -107,6 +108,18 @@ where #[serde(try_from = "String")] pub struct Email(Secret); +impl From>> for Email { + fn from(item: Encryptable>) -> Self { + Self(item.into_inner()) + } +} + +impl ExposeInterface> for Email { + fn expose(self) -> Secret { + self.0 + } +} + impl TryFrom for Email { type Error = error_stack::Report; diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 254503f57d..04838e9113 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -16,8 +16,8 @@ kms = ["external_services/kms","dep:aws-config"] email = ["external_services/email","dep:aws-config"] basilisk = ["kms"] stripe = ["dep:serde_qs"] -sandbox = ["kms", "stripe", "basilisk", "s3", "email"] -production = ["kms", "stripe", "basilisk", "s3", "email"] +sandbox = ["kms", "stripe", "basilisk", "s3","email"] +production = ["kms", "stripe", "basilisk", "s3","pii-encryption-script","email"] olap = [] oltp = [] kv_store = [] @@ -25,6 +25,7 @@ accounts_cache = [] openapi = ["olap", "oltp"] vergen = ["router_env/vergen"] multiple_mca = ["api_models/multiple_mca"] +pii-encryption-script = [] dummy_connector = ["api_models/dummy_connector"] external_access_dc = ["dummy_connector"] detailed_errors = ["api_models/detailed_errors", "error-stack/serde"] diff --git a/crates/router/src/bin/router.rs b/crates/router/src/bin/router.rs index 02ff2c241e..de786275ba 100644 --- a/crates/router/src/bin/router.rs +++ b/crates/router/src/bin/router.rs @@ -36,6 +36,24 @@ async fn main() -> ApplicationResult<()> { let _guard = logger::setup(&conf.log); + #[cfg(feature = "pii-encryption-script")] + { + let store = + router::services::Store::new(&conf, false, tokio::sync::oneshot::channel().0).await; + + // ^-------- KMS decryption of the master key is a fallible and the server will panic in + // the above mentioned line + + router::scripts::pii_encryption::test_2_step_encryption(&store).await; + + #[allow(clippy::expect_used)] + router::scripts::pii_encryption::encrypt_merchant_account_fields(&store) + .await + .expect("Failed while encrypting merchant account"); + + crate::logger::error!("Done with everything"); + } + logger::info!("Application started [{:?}] [{:?}]", conf.server, conf.log); #[allow(clippy::expect_used)] diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 5d969d88cc..c605a4efef 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -1,7 +1,11 @@ use std::{convert::From, default::Default}; use api_models::payment_methods as api_types; -use common_utils::{date_time, pii, pii::Email}; +use common_utils::{ + crypto::Encryptable, + date_time, + pii::{self, Email}, +}; use serde::{Deserialize, Serialize}; use crate::{logger, types::api}; @@ -10,7 +14,7 @@ use crate::{logger, types::api}; pub struct CreateCustomerRequest { pub email: Option, pub invoice_prefix: Option, - pub name: Option, + pub name: Option>, pub phone: Option>, pub address: Option>, pub metadata: Option, @@ -22,7 +26,7 @@ pub struct CustomerUpdateRequest { pub description: Option, pub email: Option, pub phone: Option>, - pub name: Option, + pub name: Option>, pub address: Option>, pub metadata: Option, } @@ -35,7 +39,7 @@ pub struct CreateCustomerResponse { pub description: Option, pub email: Option, pub metadata: Option, - pub name: Option, + pub name: Option>, pub phone: Option>, } @@ -95,10 +99,10 @@ impl From for CreateCustomerResponse { }, ), description: cust.description, - email: cust.email, + email: cust.email.map(|inner| inner.into()), metadata: cust.metadata, - name: cust.name, - phone: cust.phone, + name: cust.name.map(Encryptable::into_inner), + phone: cust.phone.map(Encryptable::into_inner), } } } diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index befd17c965..e9aae53c9c 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -1,5 +1,5 @@ use api_models::payments; -use common_utils::{date_time, ext_traits::StringExt, pii as secret}; +use common_utils::{crypto::Encryptable, date_time, ext_traits::StringExt, pii as secret}; use error_stack::{IntoReport, ResultExt}; use serde::{Deserialize, Serialize}; @@ -369,9 +369,9 @@ impl From for StripePaymentIntentResponse { payment_token: resp.payment_token, shipping: resp.shipping, billing: resp.billing, - email: resp.email, - name: resp.name, - phone: resp.phone, + email: resp.email.map(|inner| inner.into()), + name: resp.name.map(Encryptable::into_inner), + phone: resp.phone.map(Encryptable::into_inner), authentication_type: resp.authentication_type, statement_descriptor_name: resp.statement_descriptor_name, statement_descriptor_suffix: resp.statement_descriptor_suffix, diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 7e0fe53604..3a3980aac6 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -35,11 +35,12 @@ impl Default for super::settings::Secrets { jwt_secret: "secret".into(), #[cfg(not(feature = "kms"))] admin_api_key: "test_admin".into(), - + master_enc_key: "".into(), #[cfg(feature = "kms")] kms_encrypted_jwt_secret: "".into(), #[cfg(feature = "kms")] kms_encrypted_admin_api_key: "".into(), + migration_encryption_timestamp: 0, } } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index bbbe217146..3b39124e12 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -37,6 +37,8 @@ pub enum Subcommand { #[cfg(feature = "openapi")] /// Generate the OpenAPI specification file from code. GenerateOpenapiSpec, + #[cfg(feature = "pii-encryption-script")] + EncryptDatabase, } #[cfg(feature = "kms")] @@ -281,11 +283,13 @@ pub struct Secrets { pub jwt_secret: String, #[cfg(not(feature = "kms"))] pub admin_api_key: String, - + pub master_enc_key: String, #[cfg(feature = "kms")] pub kms_encrypted_jwt_secret: String, #[cfg(feature = "kms")] pub kms_encrypted_admin_api_key: String, + + pub migration_encryption_timestamp: i64, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs index a3e56ac2e6..5a78486ad9 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -18,7 +18,7 @@ impl super::settings::Secrets { Err(ApplicationError::InvalidConfigurationValueError( "admin API key must not be empty".into(), )) - }) + })?; } #[cfg(feature = "kms")] @@ -36,8 +36,13 @@ impl super::settings::Secrets { "KMS encrypted admin API key must not be empty".into(), )) }, - ) + )?; } + when(self.master_enc_key.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Master encryption key must not be empty".into(), + )) + }) } } diff --git a/crates/router/src/connector/bluesnap.rs b/crates/router/src/connector/bluesnap.rs index b70f5610a4..1a394a8d14 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/router/src/connector/bluesnap.rs @@ -966,7 +966,7 @@ impl api::IncomingWebhook for Bluesnap { serde_urlencoded::from_bytes(request.body) .into_report() .change_context(errors::ConnectorError::WebhookSignatureNotFound)?; - let msg = webhook_body.reference_number + &webhook_body.contract_id; + let msg = webhook_body.reference_number + webhook_body.contract_id.as_str(); Ok(msg.into_bytes()) } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 5f945964fe..44c4242f30 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1,8 +1,12 @@ use api_models::admin::PrimaryBusinessDetails; -use common_utils::{crypto::generate_cryptographically_secure_random_string, ext_traits::ValueExt}; +use common_utils::{ + crypto::{generate_cryptographically_secure_random_string, OptionalSecretValue}, + date_time, + ext_traits::ValueExt, +}; use error_stack::{report, FutureExt, ResultExt}; -use masking::Secret; -use storage_models::{enums, merchant_account}; +use masking::Secret; //PeekInterface +use storage_models::enums; use uuid::Uuid; use crate::{ @@ -13,11 +17,15 @@ use crate::{ }, db::StorageInterface, routes::metrics, - services::api as service_api, + services::{self, api as service_api}, types::{ self, api, - storage::{self, MerchantAccount}, - transformers::{ForeignInto, ForeignTryFrom, ForeignTryInto}, + domain::{ + self, merchant_key_store, + types::{self as domain_types, AsyncLift}, + }, + storage, + transformers::ForeignInto, }, utils::{self, OptionExt}, }; @@ -60,6 +68,12 @@ pub async fn create_merchant_account( db: &dyn StorageInterface, req: api::MerchantAccountCreate, ) -> RouterResponse { + let master_key = db.get_master_key(); + + let key = services::generate_aes256_key() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to generate aes 256 key")?; + let publishable_key = Some(create_merchant_publishable_key()); let primary_business_details = utils::Encode::>::encode_to_value( @@ -69,7 +83,7 @@ pub async fn create_merchant_account( field_name: "primary_business_details", })?; - let merchant_details = + let merchant_details: OptionalSecretValue = req.merchant_details .as_ref() .map(|merchant_details| { @@ -78,7 +92,8 @@ pub async fn create_merchant_account( field_name: "merchant_details", }) }) - .transpose()?; + .transpose()? + .map(Into::into); let webhook_details = req.webhook_details @@ -101,48 +116,72 @@ pub async fn create_merchant_account( .attach_printable("Invalid routing algorithm given")?; } - let enable_payment_response_hash = req.enable_payment_response_hash.or(Some(true)); + let key_store = merchant_key_store::MerchantKeyStore { + merchant_id: req.merchant_id.clone(), + key: domain_types::encrypt(key.to_vec().into(), master_key) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to decrypt data from key store")?, + created_at: date_time::now(), + }; + + let enable_payment_response_hash = req.enable_payment_response_hash.unwrap_or(true); let payment_response_hash_key = req .payment_response_hash_key .or(Some(generate_cryptographically_secure_random_string(32))); - let merchant_account = storage::MerchantAccountNew { - merchant_id: req.merchant_id, - merchant_name: req.merchant_name, - merchant_details, - return_url: req.return_url.map(|a| a.to_string()), - webhook_details, - routing_algorithm: req.routing_algorithm, - sub_merchants_enabled: req.sub_merchants_enabled, - parent_merchant_id: get_parent_merchant( - db, - req.sub_merchants_enabled, - req.parent_merchant_id, - ) - .await?, - enable_payment_response_hash, - payment_response_hash_key, - redirect_to_merchant_with_http_post: req.redirect_to_merchant_with_http_post, - publishable_key, - locker_id: req.locker_id, - metadata: req.metadata, - primary_business_details, - frm_routing_algorithm: req.frm_routing_algorithm, - intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from), - }; + db.insert_merchant_key_store(key_store) + .await + .to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?; + + let parent_merchant_id = + get_parent_merchant(db, req.sub_merchants_enabled, req.parent_merchant_id).await?; + + let merchant_account = async { + Ok(domain::MerchantAccount { + merchant_id: req.merchant_id, + merchant_name: req + .merchant_name + .async_lift(|inner| domain_types::encrypt_optional(inner, &key)) + .await?, + merchant_details: merchant_details + .async_lift(|inner| domain_types::encrypt_optional(inner, &key)) + .await?, + return_url: req.return_url.map(|a| a.to_string()), + webhook_details, + routing_algorithm: req.routing_algorithm, + sub_merchants_enabled: req.sub_merchants_enabled, + parent_merchant_id, + enable_payment_response_hash, + payment_response_hash_key, + redirect_to_merchant_with_http_post: req + .redirect_to_merchant_with_http_post + .unwrap_or_default(), + publishable_key, + locker_id: req.locker_id, + metadata: req.metadata, + storage_scheme: storage_models::enums::MerchantStorageScheme::PostgresOnly, + primary_business_details, + created_at: date_time::now(), + modified_at: date_time::now(), + frm_routing_algorithm: req.frm_routing_algorithm, + intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from), + id: None, + }) + } + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; let merchant_account = db .insert_merchant(merchant_account) .await .to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?; - Ok(service_api::ApplicationResponse::Json( - ForeignTryFrom::foreign_try_from(merchant_account).change_context( - errors::ApiErrorResponse::InvalidDataValue { - field_name: "merchant_account", - }, - )?, + merchant_account + .try_into() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while generating response")?, )) } @@ -156,11 +195,10 @@ pub async fn get_merchant_account( .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; Ok(service_api::ApplicationResponse::Json( - ForeignTryFrom::foreign_try_from(merchant_account).change_context( - errors::ApiErrorResponse::InvalidDataValue { - field_name: "merchant_account", - }, - )?, + merchant_account + .try_into() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct response")?, )) } pub async fn merchant_account_update( @@ -168,6 +206,11 @@ pub async fn merchant_account_update( merchant_id: &String, req: api::MerchantAccountUpdate, ) -> RouterResponse { + let key = domain_types::get_merchant_enc_key(db, merchant_id.clone()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to get data from merchant key store")?; + if &req.merchant_id != merchant_id { Err(report!(errors::ValidationError::IncorrectValueProvided { field_name: "parent_merchant_id" @@ -202,14 +245,26 @@ pub async fn merchant_account_update( .transpose()?; let updated_merchant_account = storage::MerchantAccountUpdate::Update { - merchant_name: req.merchant_name, + merchant_name: req + .merchant_name + .map(masking::Secret::new) + .async_lift(|inner| domain_types::encrypt_optional(inner, &key)) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt merchant name")?, merchant_details: req .merchant_details .as_ref() .map(utils::Encode::::encode_to_value) .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError)?, + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to convert merchant_details to a value")? + .map(masking::Secret::new) + .async_lift(|inner| domain_types::encrypt_optional(inner, &key)) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt merchant details")?, return_url: req.return_url.map(|a| a.to_string()), @@ -246,11 +301,10 @@ pub async fn merchant_account_update( .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; Ok(service_api::ApplicationResponse::Json( - ForeignTryFrom::foreign_try_from(response).change_context( - errors::ApiErrorResponse::InvalidDataValue { - field_name: "merchant_account", - }, - )?, + response + .try_into() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while generating response")?, )) } @@ -299,7 +353,7 @@ async fn get_parent_merchant( async fn validate_merchant_id>( db: &dyn StorageInterface, merchant_id: S, -) -> RouterResult { +) -> RouterResult { db.find_merchant_account_by_merchant_id(&merchant_id.into()) .await .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound) @@ -307,7 +361,7 @@ async fn validate_merchant_id>( fn get_business_details_wrapper( request: &api::MerchantConnectorCreate, - _merchant_account: &MerchantAccount, + _merchant_account: &domain::MerchantAccount, ) -> RouterResult<(enums::CountryAlpha2, String)> { #[cfg(feature = "multiple_mca")] { @@ -331,6 +385,11 @@ pub async fn create_payment_connector( req: api::MerchantConnectorCreate, merchant_id: &String, ) -> RouterResponse { + let key = domain_types::get_merchant_enc_key(store, merchant_id.clone()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to get key from merchant key store")?; + let merchant_account = store .find_merchant_account_by_merchant_id(merchant_id) .await @@ -370,6 +429,7 @@ pub async fn create_payment_connector( field_name: "connector_account_details".to_string(), expected_format: "auth_type and api_key".to_string(), })?; + let frm_configs = match req.frm_configs { Some(frm_value) => { let configs_for_frm_value: serde_json::Value = @@ -380,12 +440,22 @@ pub async fn create_payment_connector( None => None, }; - let merchant_connector_account = storage::MerchantConnectorAccountNew { - merchant_id: Some(merchant_id.to_string()), - connector_type: Some(req.connector_type.foreign_into()), - connector_name: Some(req.connector_name.to_owned()), + let merchant_connector_account = domain::MerchantConnectorAccount { + merchant_id: merchant_id.to_string(), + connector_type: req.connector_type.foreign_into(), + connector_name: req.connector_name.clone(), merchant_connector_id: utils::generate_id(consts::ID_LENGTH, "mca"), - connector_account_details: req.connector_account_details, + connector_account_details: domain_types::encrypt( + req.connector_account_details.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "connector_account_details", + }, + )?, + &key, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt connector account details")?, payment_methods_enabled, test_mode: req.test_mode, disabled: req.disabled, @@ -397,6 +467,7 @@ pub async fn create_payment_connector( business_sub_label: req.business_sub_label, created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), + id: None, }; let mca = store @@ -417,7 +488,7 @@ pub async fn create_payment_connector( ], ); - let mca_response = ForeignTryFrom::foreign_try_from(mca)?; + let mca_response = mca.try_into()?; Ok(service_api::ApplicationResponse::Json(mca_response)) } @@ -442,9 +513,7 @@ pub async fn retrieve_payment_connector( id: merchant_connector_id.clone(), })?; - Ok(service_api::ApplicationResponse::Json( - ForeignTryFrom::foreign_try_from(mca)?, - )) + Ok(service_api::ApplicationResponse::Json(mca.try_into()?)) } pub async fn list_payment_connectors( @@ -465,7 +534,7 @@ pub async fn list_payment_connectors( // The can be eliminated once [#79711](https://github.com/rust-lang/rust/issues/79711) is stabilized for mca in merchant_connector_accounts.into_iter() { - response.push(mca.foreign_try_into()?); + response.push(mca.try_into()?); } Ok(service_api::ApplicationResponse::Json(response)) @@ -477,6 +546,10 @@ pub async fn update_payment_connector( merchant_connector_id: &str, req: api_models::admin::MerchantConnectorUpdate, ) -> RouterResponse { + let key = domain_types::get_merchant_enc_key(db, merchant_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to get key from merchant key store")?; let _merchant_account = db .find_merchant_account_by_merchant_id(merchant_id) .await @@ -500,6 +573,7 @@ pub async fn update_payment_connector( }) .collect::>() }); + let frm_configs = match req.frm_configs.as_ref() { Some(frm_value) => { let configs_for_frm_value: serde_json::Value = @@ -509,29 +583,36 @@ pub async fn update_payment_connector( } None => None, }; + let payment_connector = storage::MerchantConnectorAccountUpdate::Update { - merchant_id: Some(merchant_id.to_string()), + merchant_id: None, connector_type: Some(req.connector_type.foreign_into()), - merchant_connector_id: Some(merchant_connector_id.to_string()), - connector_account_details: req.connector_account_details, + connector_name: None, + merchant_connector_id: None, + connector_account_details: req + .connector_account_details + .async_lift(|inner| domain_types::encrypt_optional(inner, &key)) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting data")?, + test_mode: mca.test_mode, + disabled: mca.disabled, payment_methods_enabled, - test_mode: req.test_mode, - disabled: req.disabled, metadata: req.metadata, frm_configs, }; let updated_mca = db - .update_merchant_connector_account(mca, payment_connector) + .update_merchant_connector_account(mca, payment_connector.into()) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable_lazy(|| { format!("Failed while updating MerchantConnectorAccount: id: {merchant_connector_id}") })?; - let mca_response = ForeignTryFrom::foreign_try_from(updated_mca)?; + let response = updated_mca.try_into()?; - Ok(service_api::ApplicationResponse::Json(mca_response)) + Ok(service_api::ApplicationResponse::Json(response)) } pub async fn delete_payment_connector( @@ -578,7 +659,7 @@ pub async fn kv_for_merchant( (true, enums::MerchantStorageScheme::PostgresOnly) => { db.update_merchant( merchant_account, - merchant_account::MerchantAccountUpdate::StorageSchemeUpdate { + storage::MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme: enums::MerchantStorageScheme::RedisKv, }, ) @@ -587,7 +668,7 @@ pub async fn kv_for_merchant( (false, enums::MerchantStorageScheme::RedisKv) => { db.update_merchant( merchant_account, - merchant_account::MerchantAccountUpdate::StorageSchemeUpdate { + storage::MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme: enums::MerchantStorageScheme::PostgresOnly, }, ) diff --git a/crates/router/src/core/cards_info.rs b/crates/router/src/core/cards_info.rs index baaf201adb..827bec8333 100644 --- a/crates/router/src/core/cards_info.rs +++ b/crates/router/src/core/cards_info.rs @@ -9,7 +9,7 @@ use crate::{ }, routes, services::ApplicationResponse, - types::{storage, transformers::ForeignFrom}, + types::{domain, transformers::ForeignFrom}, }; fn verify_iin_length(card_iin: &str) -> Result<(), errors::ApiErrorResponse> { @@ -22,7 +22,7 @@ fn verify_iin_length(card_iin: &str) -> Result<(), errors::ApiErrorResponse> { #[instrument(skip_all)] pub async fn retrieve_card_info( state: &routes::AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, request: api_models::cards_info::CardsInfoRequest, ) -> RouterResponse { let db = &*state.store; diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 09fd6af83f..5fc35af9f2 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -1,11 +1,14 @@ -use std::str::FromStr; - -use common_utils::{ext_traits::ValueExt, pii::Email}; +use common_utils::{ + crypto::{Encryptable, GcmAes256}, + ext_traits::ValueExt, +}; use error_stack::ResultExt; +use masking::ExposeInterface; use router_env::{instrument, tracing}; use storage_models::errors as storage_errors; use crate::{ + consts, core::{ errors::{self, RouterResponse, StorageErrorExt}, payment_methods::cards, @@ -16,8 +19,13 @@ use crate::{ services, types::{ api::customers, + domain::{ + self, + types::{self, AsyncLift, TypeEncryption}, + }, storage::{self, enums}, }, + utils::generate_id, }; pub const REDACTED: &str = "Redacted"; @@ -25,53 +33,111 @@ pub const REDACTED: &str = "Redacted"; #[instrument(skip(db))] pub async fn create_customer( db: &dyn StorageInterface, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, mut customer_data: customers::CustomerRequest, ) -> RouterResponse { let customer_id = &customer_data.customer_id; let merchant_id = &merchant_account.merchant_id; customer_data.merchant_id = merchant_id.to_owned(); + let key = types::get_merchant_enc_key(db, merchant_id.clone()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting encryption key")?; + if let Some(addr) = &customer_data.address { let customer_address: api_models::payments::AddressDetails = addr .peek() .clone() .parse_value("AddressDetails") - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "address", - })?; - db.insert_address(storage::AddressNew { - city: customer_address.city, - country: customer_address.country, - line1: customer_address.line1, - line2: customer_address.line2, - line3: customer_address.line3, - zip: customer_address.zip, - state: customer_address.state, - first_name: customer_address.first_name, - last_name: customer_address.last_name, - phone_number: customer_data.phone.clone(), - country_code: customer_data.phone_country_code.clone(), - customer_id: customer_id.to_string(), - merchant_id: merchant_id.to_string(), - ..Default::default() - }) + .change_context(errors::ApiErrorResponse::AddressNotFound)?; + + let address = async { + Ok(domain::Address { + city: customer_address.city, + country: customer_address.country, + line1: customer_address + .line1 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line2: customer_address + .line2 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line3: customer_address + .line3 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + zip: customer_address + .zip + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + state: customer_address + .state + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + first_name: customer_address + .first_name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + last_name: customer_address + .last_name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + phone_number: customer_data + .phone + .clone() + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + country_code: customer_data.phone_country_code.clone(), + customer_id: customer_id.to_string(), + merchant_id: merchant_id.to_string(), + id: None, + address_id: generate_id(consts::ID_LENGTH, "add"), + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + }) + } .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while inserting new address")?; - }; + .attach_printable("Failed while encrypting address")?; - let new_customer = storage::CustomerNew { - customer_id: customer_id.to_string(), - merchant_id: merchant_id.to_string(), - name: customer_data.name, - email: customer_data.email, - phone: customer_data.phone, - description: customer_data.description, - phone_country_code: customer_data.phone_country_code, - metadata: customer_data.metadata, - connector_customer: None, - }; + db.insert_address(address) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while inserting new address")?; + } + + let new_customer = async { + Ok(domain::Customer { + customer_id: customer_id.to_string(), + merchant_id: merchant_id.to_string(), + name: customer_data + .name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + email: customer_data + .email + .async_lift(|inner| { + types::encrypt_optional(inner.map(|inner| inner.expose()), &key) + }) + .await?, + phone: customer_data + .phone + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + description: customer_data.description, + phone_country_code: customer_data.phone_country_code, + metadata: customer_data.metadata, + id: None, + connector_customer: None, + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + }) + } + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting Customer")?; let customer = match db.insert_customer(new_customer).await { Ok(customer) => customer, @@ -99,7 +165,7 @@ pub async fn create_customer( #[instrument(skip(db))] pub async fn retrieve_customer( db: &dyn StorageInterface, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: customers::CustomerId, ) -> RouterResponse { let response = db @@ -113,7 +179,7 @@ pub async fn retrieve_customer( #[instrument(skip_all)] pub async fn delete_customer( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: customers::CustomerId, ) -> RouterResponse { let db = &state.store; @@ -168,17 +234,26 @@ pub async fn delete_customer( }?, }; + let key = types::get_merchant_enc_key(&**db, merchant_account.merchant_id.clone()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting key for encryption")?; + let redacted_encrypted_value: Encryptable> = + Encryptable::encrypt(REDACTED.to_string().into(), &key, GcmAes256 {}) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let update_address = storage::AddressUpdate::Update { city: Some(REDACTED.to_string()), country: None, - line1: Some(REDACTED.to_string().into()), - line2: Some(REDACTED.to_string().into()), - line3: Some(REDACTED.to_string().into()), - state: Some(REDACTED.to_string().into()), - zip: Some(REDACTED.to_string().into()), - first_name: Some(REDACTED.to_string().into()), - last_name: Some(REDACTED.to_string().into()), - phone_number: Some(REDACTED.to_string().into()), + line1: Some(redacted_encrypted_value.clone()), + line2: Some(redacted_encrypted_value.clone()), + line3: Some(redacted_encrypted_value.clone()), + state: Some(redacted_encrypted_value.clone()), + zip: Some(redacted_encrypted_value.clone()), + first_name: Some(redacted_encrypted_value.clone()), + last_name: Some(redacted_encrypted_value.clone()), + phone_number: Some(redacted_encrypted_value.clone()), country_code: Some(REDACTED.to_string()), }; @@ -201,9 +276,13 @@ pub async fn delete_customer( }?; let updated_customer = storage::CustomerUpdate::Update { - name: Some(REDACTED.to_string()), - email: Email::from_str(REDACTED).ok(), - phone: Some(REDACTED.to_string().into()), + name: Some(redacted_encrypted_value.clone()), + email: Some( + Encryptable::encrypt(REDACTED.to_string().into(), &key, GcmAes256 {}) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?, + ), + phone: Some(redacted_encrypted_value.clone()), description: Some(REDACTED.to_string()), phone_country_code: Some(REDACTED.to_string()), metadata: None, @@ -230,7 +309,7 @@ pub async fn delete_customer( #[instrument(skip(db))] pub async fn update_customer( db: &dyn StorageInterface, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, update_customer: customers::CustomerRequest, ) -> RouterResponse { //Add this in update call if customer can be updated anywhere else @@ -241,25 +320,60 @@ pub async fn update_customer( .await .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; + let key = types::get_merchant_enc_key(db, merchant_account.merchant_id.clone()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting key for encryption")?; + if let Some(addr) = &update_customer.address { let customer_address: api_models::payments::AddressDetails = addr .peek() .clone() .parse_value("AddressDetails") .change_context(errors::ApiErrorResponse::AddressNotFound)?; - let update_address = storage::AddressUpdate::Update { - city: customer_address.city, - country: customer_address.country, - line1: customer_address.line1, - line2: customer_address.line2, - line3: customer_address.line3, - zip: customer_address.zip, - state: customer_address.state, - first_name: customer_address.first_name, - last_name: customer_address.last_name, - phone_number: update_customer.phone.clone(), - country_code: update_customer.phone_country_code.clone(), - }; + let update_address = async { + Ok(storage::AddressUpdate::Update { + city: customer_address.city, + country: customer_address.country, + line1: customer_address + .line1 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line2: customer_address + .line2 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line3: customer_address + .line3 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + zip: customer_address + .zip + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + state: customer_address + .state + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + first_name: customer_address + .first_name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + last_name: customer_address + .last_name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + phone_number: update_customer + .phone + .clone() + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + country_code: update_customer.phone_country_code.clone(), + }) + } + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting Address while Update")?; db.update_address_by_merchant_id_customer_id( &update_customer.customer_id, &merchant_account.merchant_id, @@ -277,15 +391,31 @@ pub async fn update_customer( .update_customer_by_customer_id_merchant_id( update_customer.customer_id.to_owned(), merchant_account.merchant_id.to_owned(), - storage::CustomerUpdate::Update { - name: update_customer.name, - email: update_customer.email, - phone: update_customer.phone, - phone_country_code: update_customer.phone_country_code, - metadata: update_customer.metadata, - description: update_customer.description, - connector_customer: None, - }, + async { + Ok(storage::CustomerUpdate::Update { + name: update_customer + .name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + email: update_customer + .email + .async_lift(|inner| { + types::encrypt_optional(inner.map(|inner| inner.expose()), &key) + }) + .await?, + phone: update_customer + .phone + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + phone_country_code: update_customer.phone_country_code, + metadata: update_customer.metadata, + description: update_customer.description, + connector_customer: None, + }) + } + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting while updating customer")?, ) .await .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; diff --git a/crates/router/src/core/disputes.rs b/crates/router/src/core/disputes.rs index 50b88adc5d..78c8941f70 100644 --- a/crates/router/src/core/disputes.rs +++ b/crates/router/src/core/disputes.rs @@ -14,7 +14,8 @@ use crate::{ services, types::{ api::{self, disputes}, - storage::{self, enums as storage_enums}, + domain, + storage::enums as storage_enums, transformers::{ForeignFrom, ForeignInto}, AcceptDisputeRequestData, AcceptDisputeResponse, DefendDisputeRequestData, DefendDisputeResponse, SubmitEvidenceRequestData, SubmitEvidenceResponse, @@ -25,7 +26,7 @@ use crate::{ #[instrument(skip(state))] pub async fn retrieve_dispute( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: disputes::DisputeId, ) -> RouterResponse { let dispute = state @@ -42,7 +43,7 @@ pub async fn retrieve_dispute( #[instrument(skip(state))] pub async fn retrieve_disputes_list( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, constraints: api_models::disputes::DisputeListConstraints, ) -> RouterResponse> { let disputes = state @@ -61,7 +62,7 @@ pub async fn retrieve_disputes_list( #[instrument(skip(state))] pub async fn accept_dispute( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: disputes::DisputeId, ) -> RouterResponse { let db = &state.store; @@ -161,7 +162,7 @@ pub async fn accept_dispute( #[instrument(skip(state))] pub async fn submit_evidence( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: dispute_models::SubmitEvidenceRequest, ) -> RouterResponse { let db = &state.store; @@ -309,7 +310,7 @@ pub async fn submit_evidence( pub async fn attach_evidence( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, attach_evidence_request: api::AttachEvidenceRequest, ) -> RouterResponse { let db = &state.store; @@ -382,7 +383,7 @@ pub async fn attach_evidence( #[instrument(skip(state))] pub async fn retrieve_dispute_evidence( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: disputes::DisputeId, ) -> RouterResponse> { let dispute = state diff --git a/crates/router/src/core/disputes/transformers.rs b/crates/router/src/core/disputes/transformers.rs index 4cba43f3f4..1e9fdc899e 100644 --- a/crates/router/src/core/disputes/transformers.rs +++ b/crates/router/src/core/disputes/transformers.rs @@ -7,7 +7,7 @@ use crate::{ routes::AppState, types::{ api::{self, DisputeEvidence}, - storage, + domain, transformers::ForeignFrom, SubmitEvidenceRequestData, }, @@ -15,7 +15,7 @@ use crate::{ pub async fn get_evidence_request_data( state: &AppState, - merchant_account: &storage_models::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, evidence_request: api_models::disputes::SubmitEvidenceRequest, dispute: &storage_models::dispute::Dispute, ) -> CustomResult { @@ -193,7 +193,7 @@ pub fn update_dispute_evidence( pub async fn get_dispute_evidence_block( state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, evidence_type: EvidenceType, file_id: String, ) -> CustomResult { @@ -213,7 +213,7 @@ pub async fn get_dispute_evidence_block( pub async fn get_dispute_evidence_vec( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, dispute_evidence: DisputeEvidence, ) -> CustomResult, errors::ApiErrorResponse> { let mut dispute_evidence_blocks: Vec = vec![]; diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index da30b8b7cf..ffebb823f0 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -71,6 +71,10 @@ pub enum StorageError { CustomerRedacted, #[error("Deserialization failure")] DeserializationFailed, + #[error("Error while encrypting data")] + EncryptionError, + #[error("Error while decrypting data from database")] + DecryptionError, #[error("RedisError: {0:?}")] RedisError(error_stack::Report), } diff --git a/crates/router/src/core/files.rs b/crates/router/src/core/files.rs index 32ff6f84b3..f2f70547a1 100644 --- a/crates/router/src/core/files.rs +++ b/crates/router/src/core/files.rs @@ -13,12 +13,12 @@ use crate::{ consts, routes::AppState, services::{self, ApplicationResponse}, - types::{api, storage}, + types::{api, domain}, }; pub async fn files_create_core( state: &AppState, - merchant_account: storage::merchant_account::MerchantAccount, + merchant_account: domain::MerchantAccount, create_file_request: api::CreateFileRequest, ) -> RouterResponse { helpers::validate_file_upload(state, merchant_account.clone(), create_file_request.clone()) @@ -75,7 +75,7 @@ pub async fn files_create_core( pub async fn files_delete_core( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: api::FileId, ) -> RouterResponse { helpers::delete_file_using_file_id(state, req.file_id.clone(), &merchant_account).await?; @@ -90,7 +90,7 @@ pub async fn files_delete_core( pub async fn files_retrieve_core( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: api::FileId, ) -> RouterResponse { let file_metadata_object = state diff --git a/crates/router/src/core/files/helpers.rs b/crates/router/src/core/files/helpers.rs index 71a50d7fdc..ed748badc0 100644 --- a/crates/router/src/core/files/helpers.rs +++ b/crates/router/src/core/files/helpers.rs @@ -12,7 +12,11 @@ use crate::{ }, routes::AppState, services, - types::{self, api, storage, transformers::ForeignTryFrom}, + types::{ + self, api, + domain::{self}, + transformers::ForeignTryFrom, + }, }; pub async fn read_string(field: &mut Field) -> Option { @@ -65,7 +69,7 @@ pub async fn retrieve_file( pub async fn validate_file_upload( state: &AppState, - merchant_account: storage::merchant_account::MerchantAccount, + merchant_account: domain::MerchantAccount, create_file_request: api::CreateFileRequest, ) -> CustomResult<(), errors::ApiErrorResponse> { //File Validation based on the purpose of file upload @@ -113,7 +117,7 @@ pub async fn validate_file_upload( pub async fn delete_file_using_file_id( state: &AppState, file_key: String, - merchant_account: &storage_models::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> CustomResult<(), errors::ApiErrorResponse> { let file_metadata_object = state .store @@ -149,7 +153,7 @@ pub async fn delete_file_using_file_id( pub async fn retrieve_file_from_connector( state: &AppState, file_metadata: storage_models::file::FileMetadata, - merchant_account: &storage_models::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> CustomResult, errors::ApiErrorResponse> { let connector = &types::Connector::foreign_try_from( file_metadata @@ -204,7 +208,7 @@ pub async fn retrieve_file_from_connector( pub async fn retrieve_file_and_provider_file_id_from_file_id( state: &AppState, file_id: Option, - merchant_account: &storage_models::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, is_connector_file_data_required: api::FileDataRequired, ) -> CustomResult<(Option>, Option), errors::ApiErrorResponse> { match file_id { @@ -259,7 +263,7 @@ pub async fn retrieve_file_and_provider_file_id_from_file_id( //Upload file to connector if it supports / store it in S3 and return file_upload_provider, provider_file_id accordingly pub async fn upload_and_get_provider_provider_file_id_connector_label( state: &AppState, - merchant_account: &storage::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, create_file_request: &api::CreateFileRequest, file_key: String, ) -> CustomResult< diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index 8b65b7f14c..e7518e0e37 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -16,7 +16,7 @@ use crate::{ customers, mandates::{self, MandateResponseExt}, }, - storage, + domain, storage, transformers::{ForeignInto, ForeignTryFrom}, }, utils::OptionExt, @@ -25,7 +25,7 @@ use crate::{ #[instrument(skip(state))] pub async fn get_mandate( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: mandates::MandateId, ) -> RouterResponse { let mandate = state @@ -41,7 +41,7 @@ pub async fn get_mandate( #[instrument(skip(db))] pub async fn revoke_mandate( db: &dyn StorageInterface, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: mandates::MandateId, ) -> RouterResponse { let mandate = db @@ -97,7 +97,7 @@ pub async fn update_connector_mandate_id( #[instrument(skip(state))] pub async fn get_customer_mandates( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: customers::CustomerId, ) -> RouterResponse> { let mandates = state @@ -129,7 +129,7 @@ pub async fn get_customer_mandates( pub async fn mandate_procedure( state: &AppState, mut resp: types::RouterData, - maybe_customer: &Option, + maybe_customer: &Option, pm_id: Option, ) -> errors::RouterResult> where @@ -261,7 +261,7 @@ where #[instrument(skip(state))] pub async fn retrieve_mandates_list( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, constraints: api_models::mandates::MandateListConstraints, ) -> RouterResponse> { let mandates = state diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 73baebdf3e..cba6aef730 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -33,10 +33,14 @@ use crate::{ }, db, logger, pii::prelude::*, - routes::{self, metrics}, + routes::{ + self, + metrics::{self, request}, + }, services, types::{ api::{self, PaymentMethodCreateExt}, + domain::{self}, storage::{self, enums}, transformers::ForeignInto, }, @@ -73,7 +77,7 @@ pub async fn create_payment_method( pub async fn add_payment_method( state: &routes::AppState, req: api::PaymentMethodCreate, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> errors::RouterResponse { req.validate()?; let merchant_id = &merchant_account.merchant_id; @@ -107,7 +111,7 @@ pub async fn add_payment_method( #[instrument(skip_all)] pub async fn update_customer_payment_method( state: &routes::AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: api::PaymentMethodUpdate, payment_method_id: &str, ) -> errors::RouterResponse { @@ -152,10 +156,10 @@ pub async fn add_card_to_locker( req: api::PaymentMethodCreate, card: api::CardDetail, customer_id: String, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> errors::CustomResult<(api::PaymentMethodResponse, bool), errors::VaultError> { metrics::STORED_TO_LOCKER.add(&metrics::CONTEXT, 1, &[]); - metrics::request::record_card_operation_time( + request::record_operation_time( async { match state.conf.locker.locker_setup { settings::LockerSetup::BasiliskLocker => { @@ -184,7 +188,7 @@ pub async fn get_card_from_locker( ) -> errors::RouterResult { metrics::GET_FROM_LOCKER.add(&metrics::CONTEXT, 1, &[]); - metrics::request::record_card_operation_time( + request::record_operation_time( async { match state.conf.locker.locker_setup { settings::LockerSetup::LegacyLocker => { @@ -220,7 +224,7 @@ pub async fn delete_card_from_locker( ) -> errors::RouterResult { metrics::DELETE_FROM_LOCKER.add(&metrics::CONTEXT, 1, &[]); - metrics::request::record_card_operation_time( + request::record_operation_time( async { match state.conf.locker.locker_setup { settings::LockerSetup::LegacyLocker => { @@ -247,7 +251,7 @@ pub async fn add_card_hs( req: api::PaymentMethodCreate, card: api::CardDetail, customer_id: String, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> errors::CustomResult<(api::PaymentMethodResponse, bool), errors::VaultError> { let locker = &state.conf.locker; #[cfg(not(feature = "kms"))] @@ -313,7 +317,7 @@ pub async fn add_card( req: api::PaymentMethodCreate, card: api::CardDetail, customer_id: String, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> errors::CustomResult<(api::PaymentMethodResponse, bool), errors::VaultError> { let locker = &state.conf.locker; let db = &*state.store; @@ -766,7 +770,7 @@ pub fn get_banks( pub async fn list_payment_methods( state: &routes::AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, mut req: api::PaymentMethodListRequest, ) -> errors::RouterResponse { let db = &*state.store; @@ -1125,7 +1129,7 @@ pub async fn filter_payment_methods( resp: &mut Vec, payment_intent: Option<&storage::PaymentIntent>, payment_attempt: Option<&storage::PaymentAttempt>, - address: Option<&storage::Address>, + address: Option<&domain::Address>, connector: String, config: &settings::ConnectorFilters, ) -> errors::CustomResult<(), errors::ApiErrorResponse> { @@ -1510,7 +1514,7 @@ fn filter_installment_based( async fn filter_payment_country_based( pm: &RequestPaymentMethodTypes, - address: Option<&storage::Address>, + address: Option<&domain::Address>, ) -> errors::CustomResult { Ok(address.map_or(true, |address| { address.country.as_ref().map_or(true, |country| { @@ -1559,7 +1563,7 @@ async fn filter_payment_mandate_based( pub async fn list_customer_payment_method( state: &routes::AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, customer_id: &str, ) -> errors::RouterResponse { let db = &*state.store; @@ -1859,7 +1863,7 @@ impl BasiliskCardSupport { pub async fn retrieve_payment_method( state: &routes::AppState, pm: api::PaymentMethodId, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, ) -> errors::RouterResponse { let db = &*state.store; let pm = db @@ -1904,7 +1908,7 @@ pub async fn retrieve_payment_method( #[instrument(skip_all)] pub async fn delete_payment_method( state: &routes::AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, pm: api::PaymentMethodId, ) -> errors::RouterResponse { let (_, supplementary_data) = diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 0ad80cbf19..196d6b4c48 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -38,8 +38,7 @@ use crate::{ scheduler::utils as pt_utils, services::{self, api::Authenticate}, types::{ - self, - api::{self}, + self, api, domain, storage::{self, enums as storage_enums}, }, utils::{Encode, OptionExt, ValueExt}, @@ -48,11 +47,11 @@ use crate::{ #[instrument(skip_all, fields(payment_id, merchant_id))] pub async fn payments_operation_core( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, operation: Op, req: Req, call_connector_action: CallConnectorAction, -) -> RouterResult<(PaymentData, Req, Option)> +) -> RouterResult<(PaymentData, Req, Option)> where F: Send + Clone + Sync, Req: Authenticate, @@ -201,7 +200,7 @@ where #[allow(clippy::too_many_arguments)] pub async fn payments_core( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, operation: Op, req: Req, auth_flow: services::AuthFlow, @@ -263,7 +262,7 @@ pub trait PaymentRedirectFlow: Sync { async fn call_payment_flow( &self, state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: PaymentsRedirectResponseData, connector_action: CallConnectorAction, ) -> RouterResponse; @@ -273,7 +272,7 @@ pub trait PaymentRedirectFlow: Sync { fn generate_response( &self, payments_response: api_models::payments::PaymentsResponse, - merchant_account: storage_models::merchant_account::MerchantAccount, + merchant_account: types::domain::MerchantAccount, payment_id: String, connector: String, ) -> RouterResult; @@ -282,7 +281,7 @@ pub trait PaymentRedirectFlow: Sync { async fn handle_payments_redirect_response( &self, state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: PaymentsRedirectResponseData, ) -> RouterResponse { metrics::REDIRECTION_TRIGGERED.add( @@ -350,7 +349,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { async fn call_payment_flow( &self, state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: PaymentsRedirectResponseData, connector_action: CallConnectorAction, ) -> RouterResponse { @@ -387,7 +386,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { fn generate_response( &self, payments_response: api_models::payments::PaymentsResponse, - merchant_account: storage_models::merchant_account::MerchantAccount, + merchant_account: types::domain::MerchantAccount, payment_id: String, connector: String, ) -> RouterResult { @@ -437,7 +436,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { async fn call_payment_flow( &self, state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, req: PaymentsRedirectResponseData, connector_action: CallConnectorAction, ) -> RouterResponse { @@ -468,7 +467,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { fn generate_response( &self, payments_response: api_models::payments::PaymentsResponse, - merchant_account: storage_models::merchant_account::MerchantAccount, + merchant_account: types::domain::MerchantAccount, payment_id: String, connector: String, ) -> RouterResult { @@ -488,11 +487,11 @@ impl PaymentRedirectFlow for PaymentRedirectSync { #[allow(clippy::too_many_arguments)] pub async fn call_connector_service( state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, connector: api::ConnectorData, _operation: &Op, payment_data: &PaymentData, - customer: &Option, + customer: &Option, call_connector_action: CallConnectorAction, tokenization_action: TokenizationAction, ) -> RouterResult> @@ -567,11 +566,11 @@ where pub async fn call_multiple_connectors_service( state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, connectors: Vec, _operation: &Op, mut payment_data: PaymentData, - customer: &Option, + customer: &Option, ) -> RouterResult> where Op: Debug, @@ -641,8 +640,8 @@ where pub async fn call_create_connector_customer_if_required( state: &AppState, connector_name: &Option, - customer: &Option, - merchant_account: &storage::MerchantAccount, + customer: &Option, + merchant_account: &domain::MerchantAccount, payment_data: &mut PaymentData, ) -> RouterResult> where @@ -1059,7 +1058,7 @@ pub fn is_operation_confirm(operation: &Op) -> bool { #[cfg(feature = "olap")] pub async fn list_payments( db: &dyn StorageInterface, - merchant: storage::MerchantAccount, + merchant: domain::MerchantAccount, constraints: api::PaymentListConstraints, ) -> RouterResponse { use futures::stream::StreamExt; @@ -1153,7 +1152,7 @@ pub async fn get_connector_choice( operation: &BoxedOperation<'_, F, Req>, state: &AppState, req: &Req, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, payment_data: &mut PaymentData, ) -> RouterResult> where @@ -1195,7 +1194,7 @@ where pub fn connector_selection( state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, payment_data: &mut PaymentData, request_straight_through: Option, ) -> RouterResult @@ -1254,7 +1253,7 @@ where pub fn decide_connector( state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, request_straight_through: Option, routing_data: &mut storage::RoutingData, ) -> RouterResult { diff --git a/crates/router/src/core/payments/access_token.rs b/crates/router/src/core/payments/access_token.rs index 855e11785d..ad4706abde 100644 --- a/crates/router/src/core/payments/access_token.rs +++ b/crates/router/src/core/payments/access_token.rs @@ -10,7 +10,7 @@ use crate::{ }, routes::{metrics, AppState}, services, - types::{self, api as api_types, storage, transformers::ForeignInto}, + types::{self, api as api_types, domain, transformers::ForeignInto}, }; pub fn update_router_data_with_access_token_result( @@ -50,7 +50,7 @@ pub async fn add_access_token< >( state: &AppState, connector: &api_types::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, router_data: &types::RouterData, ) -> RouterResult { if connector @@ -133,7 +133,7 @@ pub async fn add_access_token< pub async fn refresh_connector_auth( state: &AppState, connector: &api_types::ConnectorData, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, router_data: &types::RouterData< api_types::AccessTokenAuth, types::AccessTokenRequestData, diff --git a/crates/router/src/core/payments/customers.rs b/crates/router/src/core/payments/customers.rs index 4b8e99fcb9..9b23271f2d 100644 --- a/crates/router/src/core/payments/customers.rs +++ b/crates/router/src/core/payments/customers.rs @@ -9,7 +9,7 @@ use crate::{ logger, routes::{metrics, AppState}, services, - types::{self, api, storage}, + types::{self, api, domain, storage}, }; pub async fn create_connector_customer( @@ -90,7 +90,7 @@ type CreateCustomerCheck = ( pub fn should_call_connector_create_customer( state: &AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, ) -> RouterResult { let connector_name = connector.connector_name.to_string(); //Check if create customer is required for the connector diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 9ac6e9cd3b..4bf7ece15a 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -16,7 +16,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, storage}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -25,8 +25,8 @@ pub trait ConstructFlowSpecificData { &self, state: &AppState, connector_id: &str, - merchant_account: &storage::MerchantAccount, - customer: &Option, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult>; } @@ -36,9 +36,9 @@ pub trait Feature { self, state: &AppState, connector: &api::ConnectorData, - maybe_customer: &Option, + maybe_customer: &Option, call_connector_action: payments::CallConnectorAction, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult where Self: Sized, @@ -49,7 +49,7 @@ pub trait Feature { &self, state: &AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult where F: Clone, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index fd9a17a017..abf3ec081d 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -11,7 +11,7 @@ use crate::{ logger, routes::{metrics, AppState}, services, - types::{self, api, storage}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -26,8 +26,8 @@ impl &self, state: &AppState, connector_id: &str, - merchant_account: &storage::MerchantAccount, - customer: &Option, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult< types::RouterData< api::Authorize, @@ -51,9 +51,9 @@ impl Feature for types::PaymentsAu mut self, state: &AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, call_connector_action: payments::CallConnectorAction, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { let resp = self .decide_flow( @@ -75,7 +75,7 @@ impl Feature for types::PaymentsAu &self, state: &AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self).await } @@ -126,10 +126,10 @@ impl types::PaymentsAuthorizeRouterData { &'b mut self, state: &'a AppState, connector: &api::ConnectorData, - maybe_customer: &Option, + maybe_customer: &Option, confirm: Option, call_connector_action: payments::CallConnectorAction, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { match confirm { Some(true) => { diff --git a/crates/router/src/core/payments/flows/cancel_flow.rs b/crates/router/src/core/payments/flows/cancel_flow.rs index 0f5e5ca281..11ca81a9df 100644 --- a/crates/router/src/core/payments/flows/cancel_flow.rs +++ b/crates/router/src/core/payments/flows/cancel_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::{metrics, AppState}, services, - types::{self, api, storage}, + types::{self, api, domain}, }; #[async_trait] @@ -19,8 +19,8 @@ impl ConstructFlowSpecificData, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult { transformers::construct_payment_router_data::( state, @@ -41,9 +41,9 @@ impl Feature self, state: &AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, call_connector_action: payments::CallConnectorAction, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, ) -> RouterResult { metrics::PAYMENT_CANCEL_COUNT.add( &metrics::CONTEXT, @@ -67,7 +67,7 @@ impl Feature &self, state: &AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self).await } @@ -79,7 +79,7 @@ impl types::PaymentsCancelRouterData { &'b self, state: &AppState, connector: &api::ConnectorData, - _maybe_customer: &Option, + _maybe_customer: &Option, _confirm: Option, call_connector_action: payments::CallConnectorAction, ) -> RouterResult { diff --git a/crates/router/src/core/payments/flows/capture_flow.rs b/crates/router/src/core/payments/flows/capture_flow.rs index 7a4d9d53c2..bbe33e749d 100644 --- a/crates/router/src/core/payments/flows/capture_flow.rs +++ b/crates/router/src/core/payments/flows/capture_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, storage}, + types::{self, api, domain}, }; #[async_trait] @@ -20,8 +20,8 @@ impl &self, state: &AppState, connector_id: &str, - merchant_account: &storage::MerchantAccount, - customer: &Option, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult { transformers::construct_payment_router_data::( state, @@ -42,9 +42,9 @@ impl Feature self, state: &AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, call_connector_action: payments::CallConnectorAction, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, ) -> RouterResult { self.decide_flow( state, @@ -60,7 +60,7 @@ impl Feature &self, state: &AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self).await } @@ -72,7 +72,7 @@ impl types::PaymentsCaptureRouterData { &'b self, state: &'a AppState, connector: &api::ConnectorData, - _maybe_customer: &Option, + _maybe_customer: &Option, _confirm: Option, call_connector_action: payments::CallConnectorAction, ) -> RouterResult { diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index fe324dfb1e..0fecb78617 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, storage}, + types::{self, api, domain}, }; #[async_trait] @@ -23,8 +23,8 @@ impl &self, state: &AppState, connector_id: &str, - merchant_account: &storage::MerchantAccount, - customer: &Option, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult< types::RouterData< api::CompleteAuthorize, @@ -58,9 +58,9 @@ impl Feature mut self, state: &AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, call_connector_action: payments::CallConnectorAction, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, ) -> RouterResult { self.decide_flow( state, @@ -76,7 +76,7 @@ impl Feature &self, state: &AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self).await } @@ -87,7 +87,7 @@ impl types::PaymentsCompleteAuthorizeRouterData { &'b mut self, state: &'a AppState, connector: &api::ConnectorData, - _maybe_customer: &Option, + _maybe_customer: &Option, _confirm: Option, call_connector_action: payments::CallConnectorAction, ) -> RouterResult { diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index f4cfcc1e00..436b8026cd 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -8,7 +8,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, storage}, + types::{self, api, domain}, }; #[async_trait] @@ -19,8 +19,8 @@ impl ConstructFlowSpecificData, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult< types::RouterData, > { @@ -43,9 +43,9 @@ impl Feature self, state: &AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, call_connector_action: payments::CallConnectorAction, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, ) -> RouterResult { self.decide_flow( state, @@ -61,7 +61,7 @@ impl Feature &self, state: &AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self).await } @@ -72,7 +72,7 @@ impl types::PaymentsSyncRouterData { &'b self, state: &'a AppState, connector: &api::ConnectorData, - _maybe_customer: &Option, + _maybe_customer: &Option, _confirm: Option, call_connector_action: payments::CallConnectorAction, ) -> RouterResult { diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 20d820a520..5a4e65f350 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -13,7 +13,7 @@ use crate::{ headers, routes::{self, metrics}, services, - types::{self, api, storage}, + types::{self, api, domain}, utils::{self, OptionExt}, }; @@ -26,8 +26,8 @@ impl &self, state: &routes::AppState, connector_id: &str, - merchant_account: &storage::MerchantAccount, - customer: &Option, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult { transformers::construct_payment_router_data::( state, @@ -46,9 +46,9 @@ impl Feature for types::PaymentsSessio self, state: &routes::AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, call_connector_action: payments::CallConnectorAction, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, ) -> RouterResult { metrics::SESSION_TOKEN_CREATED.add( &metrics::CONTEXT, @@ -72,7 +72,7 @@ impl Feature for types::PaymentsSessio &self, state: &routes::AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self).await } @@ -283,7 +283,7 @@ impl types::PaymentsSessionRouterData { &'b self, state: &'a routes::AppState, connector: &api::ConnectorData, - _customer: &Option, + _customer: &Option, _confirm: Option, call_connector_action: payments::CallConnectorAction, ) -> RouterResult { diff --git a/crates/router/src/core/payments/flows/verfiy_flow.rs b/crates/router/src/core/payments/flows/verfiy_flow.rs index d09be85233..2ff61f4baf 100644 --- a/crates/router/src/core/payments/flows/verfiy_flow.rs +++ b/crates/router/src/core/payments/flows/verfiy_flow.rs @@ -9,7 +9,7 @@ use crate::{ }, routes::AppState, services, - types::{self, api, storage}, + types::{self, api, domain, storage}, }; #[async_trait] @@ -20,8 +20,8 @@ impl ConstructFlowSpecificData, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult { transformers::construct_payment_router_data::( state, @@ -40,9 +40,9 @@ impl Feature for types::VerifyRouterData self, state: &AppState, connector: &api::ConnectorData, - customer: &Option, + customer: &Option, call_connector_action: payments::CallConnectorAction, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { self.decide_flow( state, @@ -59,7 +59,7 @@ impl Feature for types::VerifyRouterData &self, state: &AppState, connector: &api::ConnectorData, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self).await } @@ -115,10 +115,10 @@ impl types::VerifyRouterData { &'b self, state: &'a AppState, connector: &api::ConnectorData, - maybe_customer: &Option, + maybe_customer: &Option, confirm: Option, call_connector_action: payments::CallConnectorAction, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { match confirm { Some(true) => { diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 1ab31c6157..fc67e4b111 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2,14 +2,14 @@ use std::borrow::Cow; use common_utils::{ ext_traits::{AsyncExt, ByteSliceExt, ValueExt}, - fp_utils, pii, + fp_utils, generate_id, pii, }; // TODO : Evaluate all the helper functions () use error_stack::{report, IntoReport, ResultExt}; use josekit::jwe; -use masking::{ExposeOptionInterface, PeekInterface}; +use masking::{ExposeInterface, PeekInterface}; use router_env::{instrument, tracing}; -use storage_models::{enums, merchant_account, payment_intent}; +use storage_models::{enums, payment_intent}; use time::Duration; use uuid::Uuid; @@ -30,10 +30,14 @@ use crate::{ scheduler::{metrics as scheduler_metrics, workflows::payment_sync}, services, types::{ - self, api::{self, admin, enums as api_enums, CustomerAcceptanceExt, MandateValidationFieldsExt}, + domain::{ + self, + types::{self, AsyncLift}, + }, storage::{self, enums as storage_enums, ephemeral_key}, transformers::ForeignInto, + ErrorResponse, RouterData, }, utils::{ self, @@ -43,11 +47,9 @@ use crate::{ }; pub fn filter_mca_based_on_business_details( - merchant_connector_accounts: Vec< - storage_models::merchant_connector_account::MerchantConnectorAccount, - >, + merchant_connector_accounts: Vec, payment_intent: Option<&storage_models::payment_intent::PaymentIntent>, -) -> Vec { +) -> Vec { if let Some(payment_intent) = payment_intent { merchant_connector_accounts .into_iter() @@ -67,15 +69,86 @@ pub async fn get_address_for_payment_request( address_id: Option<&str>, merchant_id: &str, customer_id: &Option, -) -> CustomResult, errors::ApiErrorResponse> { +) -> CustomResult, errors::ApiErrorResponse> { + let key = types::get_merchant_enc_key(db, merchant_id.to_string()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting key for encryption")?; + Ok(match req_address { Some(address) => { match address_id { - Some(id) => Some( - db.update_address(id.to_owned(), address.foreign_into()) - .await - .to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?, - ), + Some(id) => { + let address_update = async { + Ok(storage::AddressUpdate::Update { + city: address + .address + .as_ref() + .and_then(|value| value.city.clone()), + country: address.address.as_ref().and_then(|value| value.country), + line1: address + .address + .as_ref() + .and_then(|value| value.line1.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line2: address + .address + .as_ref() + .and_then(|value| value.line2.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line3: address + .address + .as_ref() + .and_then(|value| value.line3.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + state: address + .address + .as_ref() + .and_then(|value| value.state.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + zip: address + .address + .as_ref() + .and_then(|value| value.zip.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + first_name: address + .address + .as_ref() + .and_then(|value| value.first_name.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + last_name: address + .address + .as_ref() + .and_then(|value| value.last_name.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + phone_number: address + .phone + .as_ref() + .and_then(|value| value.number.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + country_code: address + .phone + .as_ref() + .and_then(|value| value.country_code.clone()), + }) + } + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting address")?; + Some( + db.update_address(id.to_owned(), address_update) + .await + .to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?, + ) + } None => { // generate a new address here let customer_id = customer_id @@ -85,17 +158,61 @@ pub async fn get_address_for_payment_request( let address_details = address.address.clone().unwrap_or_default(); Some( - db.insert_address(storage::AddressNew { - phone_number: address.phone.as_ref().and_then(|a| a.number.clone()), - country_code: address - .phone - .as_ref() - .and_then(|a| a.country_code.clone()), - customer_id: customer_id.to_string(), - merchant_id: merchant_id.to_string(), - - ..address_details.foreign_into() - }) + db.insert_address( + async { + Ok(domain::Address { + phone_number: address + .phone + .as_ref() + .and_then(|a| a.number.clone()) + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + country_code: address + .phone + .as_ref() + .and_then(|a| a.country_code.clone()), + customer_id: customer_id.to_string(), + merchant_id: merchant_id.to_string(), + address_id: generate_id(consts::ID_LENGTH, "add"), + city: address_details.city, + country: address_details.country, + line1: address_details + .line1 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line2: address_details + .line2 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + line3: address_details + .line3 + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + id: None, + state: address_details + .state + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + created_at: common_utils::date_time::now(), + first_name: address_details + .first_name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + last_name: address_details + .last_name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + modified_at: common_utils::date_time::now(), + zip: address_details + .zip + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + }) + } + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting address while insert")?, + ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while inserting new address")?, @@ -115,7 +232,7 @@ pub async fn get_address_for_payment_request( pub async fn get_address_by_id( db: &dyn StorageInterface, address_id: Option, -) -> CustomResult, errors::ApiErrorResponse> { +) -> CustomResult, errors::ApiErrorResponse> { match address_id { None => Ok(None), Some(address_id) => Ok(db.find_address(&address_id).await.ok()), @@ -126,7 +243,7 @@ pub async fn get_token_pm_type_mandate_details( state: &AppState, request: &api::PaymentsRequest, mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( Option, Option, @@ -160,7 +277,7 @@ pub async fn get_token_pm_type_mandate_details( pub async fn get_token_for_recurring_mandate( state: &AppState, req: &api::PaymentsRequest, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<(Option, Option)> { let db = &*state.store; let mandate_id = req.mandate_id.clone().get_required_value("mandate_id")?; @@ -559,7 +676,7 @@ where pub(crate) async fn get_payment_method_create_request( payment_method: Option<&api::PaymentMethodData>, payment_method_type: Option, - customer: &storage::Customer, + customer: &domain::Customer, ) -> RouterResult { match payment_method { Some(pm_data) => match payment_method_type { @@ -618,17 +735,21 @@ pub async fn get_customer_from_details( customer_id: Option, merchant_id: &str, payment_data: &mut PaymentData, -) -> CustomResult, errors::StorageError> { +) -> CustomResult, errors::StorageError> { match customer_id { None => Ok(None), Some(c_id) => { let customer = db .find_customer_optional_by_customer_id_merchant_id(&c_id, merchant_id) .await?; - payment_data.email = payment_data - .email - .clone() - .or_else(|| customer.as_ref().and_then(|inner| inner.email.clone())); + payment_data.email = payment_data.email.clone().or_else(|| { + customer.as_ref().and_then(|inner| { + inner + .email + .clone() + .map(|encrypted_value| encrypted_value.into()) + }) + }); Ok(customer) } } @@ -651,7 +772,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>( payment_data: &mut PaymentData, req: Option, merchant_id: &str, -) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError> { +) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError> { let req = req .get_required_value("customer") .change_context(errors::StorageError::ValueNotFound("customer".to_owned()))?; @@ -663,16 +784,39 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>( Some(match customer_data { Some(c) => Ok(c), None => { - let new_customer = storage::CustomerNew { - customer_id: customer_id.to_string(), - merchant_id: merchant_id.to_string(), - name: req.name.expose_option(), - email: req.email.clone(), - phone: req.phone.clone(), - phone_country_code: req.phone_country_code.clone(), - ..storage::CustomerNew::default() - }; - + let key = types::get_merchant_enc_key(db, merchant_id.to_string()).await?; + let new_customer = async { + Ok(domain::Customer { + customer_id: customer_id.to_string(), + merchant_id: merchant_id.to_string(), + name: req + .name + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + email: req + .email + .clone() + .async_lift(|inner| { + types::encrypt_optional(inner.map(|inner| inner.expose()), &key) + }) + .await?, + phone: req + .phone + .clone() + .async_lift(|inner| types::encrypt_optional(inner, &key)) + .await?, + phone_country_code: req.phone_country_code.clone(), + description: None, + created_at: common_utils::date_time::now(), + id: None, + metadata: None, + modified_at: common_utils::date_time::now(), + connector_customer: None, + }) + } + .await + .change_context(errors::StorageError::SerializationFailed) + .attach_printable("Failed while encrypting Customer while insert")?; metrics::CUSTOMER_CREATED.add(&metrics::CONTEXT, 1, &[]); db.insert_customer(new_customer).await } @@ -693,10 +837,12 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>( let customer = customer?; payment_data.payment_intent.customer_id = Some(customer.customer_id.clone()); - payment_data.email = payment_data - .email - .clone() - .or_else(|| customer.email.clone()); + payment_data.email = payment_data.email.clone().or_else(|| { + customer + .email + .clone() + .map(|encrypted_value| encrypted_value.into()) + }); Some(customer) } @@ -970,7 +1116,7 @@ pub(super) fn validate_payment_list_request( pub fn get_handle_response_url( payment_id: String, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, response: api::PaymentsResponse, connector: String, ) -> RouterResult { @@ -989,7 +1135,7 @@ pub fn get_handle_response_url( } pub fn make_merchant_url_with_response( - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, redirection_response: api::PgRedirectResponse, request_return_url: Option<&String>, ) -> RouterResult { @@ -1081,7 +1227,7 @@ pub fn make_pg_redirect_response( pub fn make_url_with_signature( redirect_url: &str, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { let mut url = url::Url::parse(redirect_url) .into_report() @@ -1159,7 +1305,7 @@ pub fn generate_mandate( merchant_id: String, connector: String, setup_mandate_details: Option, - customer: &Option, + customer: &Option, payment_method_id: String, connector_mandate_id: Option, network_txn_id: Option, @@ -1286,7 +1432,7 @@ pub(crate) fn validate_pm_or_token_given( // A function to perform database lookup and then verify the client secret pub async fn verify_payment_intent_time_and_client_secret( db: &dyn StorageInterface, - merchant_account: &merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, client_secret: Option, ) -> error_stack::Result, errors::ApiErrorResponse> { client_secret @@ -1354,7 +1500,7 @@ pub fn get_connector_label( pub fn get_business_details( business_country: Option, business_label: Option<&String>, - merchant_account: &storage_models::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<(api_enums::CountryAlpha2, String)> { let (business_country, business_label) = match business_country.zip(business_label) { Some((business_country, business_label)) => { @@ -1555,7 +1701,7 @@ pub async fn insert_merchant_connector_creds_to_config( } pub enum MerchantConnectorAccountType { - DbVal(storage::MerchantConnectorAccount), + DbVal(domain::MerchantConnectorAccount), CacheVal(api_models::admin::MerchantConnectorDetails), } @@ -1568,7 +1714,7 @@ impl MerchantConnectorAccountType { } pub fn get_connector_account_details(&self) -> serde_json::Value { match self { - Self::DbVal(val) => val.connector_account_details.to_owned(), + Self::DbVal(val) => val.connector_account_details.peek().to_owned(), Self::CacheVal(val) => val.connector_account_details.peek().to_owned(), } } @@ -1644,11 +1790,11 @@ pub async fn get_merchant_connector_account( /// * `request` - new request /// * `response` - new response pub fn router_data_type_conversion( - router_data: types::RouterData, + router_data: RouterData, request: Req2, - response: Result, -) -> types::RouterData { - types::RouterData { + response: Result, +) -> RouterData { + RouterData { flow: std::marker::PhantomData, request, response, diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 773f219cf0..c253a23315 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -27,7 +27,7 @@ use crate::{ db::StorageInterface, routes::AppState, types::{ - self, api, + self, api, domain, storage::{self, enums}, PaymentsResponseData, }, @@ -78,7 +78,7 @@ pub trait ValidateRequest { fn validate_request<'a, 'b>( &'b self, request: &R, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<(BoxedOperation<'b, F, R>, ValidateResult<'a>)>; } @@ -91,7 +91,7 @@ pub trait GetTracker: Send { payment_id: &api::PaymentIdType, request: &R, mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<(BoxedOperation<'a, F, R>, D, Option)>; } @@ -104,7 +104,7 @@ pub trait Domain: Send + Sync { payment_data: &mut PaymentData, request: Option, merchant_id: &str, - ) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError>; + ) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError>; #[allow(clippy::too_many_arguments)] async fn make_pm_data<'a>( @@ -124,7 +124,7 @@ pub trait Domain: Send + Sync { async fn get_connector<'a>( &'a self, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, state: &AppState, request: &R, payment_intent: &storage::payment_intent::PaymentIntent, @@ -138,7 +138,7 @@ pub trait UpdateTracker: Send { db: &dyn StorageInterface, payment_id: &api::PaymentIdType, payment_data: D, - customer: Option, + customer: Option, storage_scheme: enums::MerchantStorageScheme, updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, Req>, D)> @@ -176,7 +176,7 @@ where ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRetrieveRequest>, - Option, + Option, ), errors::StorageError, > { @@ -194,7 +194,7 @@ where async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, _request: &api::PaymentsRetrieveRequest, _payment_intent: &storage::payment_intent::PaymentIntent, @@ -232,7 +232,7 @@ where ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsCaptureRequest>, - Option, + Option, ), errors::StorageError, > { @@ -262,7 +262,7 @@ where async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, _request: &api::PaymentsCaptureRequest, _payment_intent: &storage::payment_intent::PaymentIntent, @@ -287,7 +287,7 @@ where ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsCancelRequest>, - Option, + Option, ), errors::StorageError, > { @@ -318,7 +318,7 @@ where async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, _request: &api::PaymentsCancelRequest, _payment_intent: &storage::payment_intent::PaymentIntent, diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index e5468b3139..90058f627d 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -16,8 +16,8 @@ use crate::{ routes::AppState, types::{ api::{self, PaymentIdTypeExt}, - storage::{self, enums, Customer}, - transformers::ForeignInto, + domain, + storage::{self, enums}, }, utils::OptionExt, }; @@ -35,7 +35,7 @@ impl GetTracker, api::PaymentsCancelRequest> payment_id: &api::PaymentIdType, request: &api::PaymentsCancelRequest, _mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsCancelRequest>, PaymentData, @@ -137,8 +137,8 @@ impl GetTracker, api::PaymentsCancelRequest> setup_mandate: None, token: None, address: PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: None, payment_method_data: None, @@ -167,7 +167,7 @@ impl UpdateTracker, api::PaymentsCancelRequest> for db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - _customer: Option, + _customer: Option, storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<( @@ -218,7 +218,7 @@ impl ValidateRequest for Payment fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsCancelRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsCancelRequest>, operations::ValidateResult<'a>, diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 3903c989f5..7aec05f375 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -15,8 +15,8 @@ use crate::{ routes::AppState, types::{ api::{self, PaymentIdTypeExt}, + domain, storage::{self, enums}, - transformers::ForeignInto, }, utils::OptionExt, }; @@ -36,7 +36,7 @@ impl GetTracker, api::PaymentsCaptu payment_id: &api::PaymentIdType, request: &api::PaymentsCaptureRequest, _mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsCaptureRequest>, payments::PaymentData, @@ -144,8 +144,8 @@ impl GetTracker, api::PaymentsCaptu setup_mandate: None, token: None, address: payments::PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: None, payment_method_data: None, @@ -175,7 +175,7 @@ impl UpdateTracker, api::PaymentsCaptureRe _db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, payment_data: payments::PaymentData, - _customer: Option, + _customer: Option, _storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<( @@ -194,7 +194,7 @@ impl ValidateRequest for Paymen fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsCaptureRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsCaptureRequest>, operations::ValidateResult<'a>, diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 065f3a1467..39448d6412 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -17,6 +17,7 @@ use crate::{ types::{ self, api::{self, PaymentIdTypeExt}, + domain, storage::{self, enums as storage_enums}, transformers::ForeignInto, }, @@ -36,7 +37,7 @@ impl GetTracker, api::PaymentsRequest> for Co payment_id: &api::PaymentIdType, request: &api::PaymentsRequest, mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest>, PaymentData, @@ -188,8 +189,8 @@ impl GetTracker, api::PaymentsRequest> for Co setup_mandate, token, address: PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: request.confirm, payment_method_data: request.payment_method_data.clone(), @@ -227,7 +228,7 @@ impl Domain for CompleteAuthorize { ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRequest>, - Option, + Option, ), errors::StorageError, > { @@ -272,7 +273,7 @@ impl Domain for CompleteAuthorize { async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, request: &api::PaymentsRequest, _payment_intent: &storage::payment_intent::PaymentIntent, @@ -291,7 +292,7 @@ impl UpdateTracker, api::PaymentsRequest> for Comple _db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, payment_data: PaymentData, - _customer: Option, + _customer: Option, _storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -307,7 +308,7 @@ impl ValidateRequest for CompleteAutho fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRequest>, operations::ValidateResult<'a>, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 9d413d6903..9d28f77dcc 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -18,6 +18,7 @@ use crate::{ types::{ self, api::{self, PaymentIdTypeExt}, + domain, storage::{self, enums as storage_enums}, transformers::ForeignInto, }, @@ -37,7 +38,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_id: &api::PaymentIdType, request: &api::PaymentsRequest, mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest>, PaymentData, @@ -231,8 +232,8 @@ impl GetTracker, api::PaymentsRequest> for Pa setup_mandate, token, address: PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: request.confirm, payment_method_data: request.payment_method_data.clone(), @@ -270,7 +271,7 @@ impl Domain for PaymentConfirm { ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRequest>, - Option, + Option, ), errors::StorageError, > { @@ -315,7 +316,7 @@ impl Domain for PaymentConfirm { async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, request: &api::PaymentsRequest, _payment_intent: &storage::payment_intent::PaymentIntent, @@ -334,7 +335,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - customer: Option, + customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -443,7 +444,7 @@ impl ValidateRequest for PaymentConfir fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRequest>, operations::ValidateResult<'a>, diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 82bb804775..6130afd0b9 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -22,6 +22,7 @@ use crate::{ types::{ self, api::{self, PaymentIdTypeExt}, + domain, storage::{ self, enums::{self, IntentStatus}, @@ -43,7 +44,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_id: &api::PaymentIdType, request: &api::PaymentsRequest, mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest>, PaymentData, @@ -235,8 +236,8 @@ impl GetTracker, api::PaymentsRequest> for Pa setup_mandate, token, address: PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: request.confirm, payment_method_data: request.payment_method_data.clone(), @@ -275,7 +276,7 @@ impl Domain for PaymentCreate { ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRequest>, - Option, + Option, ), errors::StorageError, > { @@ -313,7 +314,7 @@ impl Domain for PaymentCreate { async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, request: &api::PaymentsRequest, _payment_intent: &storage::payment_intent::PaymentIntent, @@ -330,7 +331,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - _customer: Option, + _customer: Option, storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -402,7 +403,7 @@ impl ValidateRequest for PaymentCreate fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRequest>, operations::ValidateResult<'a>, @@ -519,7 +520,7 @@ impl PaymentCreate { #[instrument(skip_all)] fn make_payment_intent( payment_id: &str, - merchant_account: &storage::MerchantAccount, + merchant_account: &types::domain::MerchantAccount, money: (api::Amount, enums::Currency), request: &api::PaymentsRequest, shipping_address_id: Option, @@ -599,7 +600,7 @@ impl PaymentCreate { pub async fn get_ephemeral_key( request: &api::PaymentsRequest, state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> Option { match request.customer_id.clone() { Some(customer_id) => helpers::make_ephemeral_key( diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index 9b3375aa33..7cdf9fee75 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -20,6 +20,7 @@ use crate::{ types::{ self, api::{self, enums as api_enums, PaymentIdTypeExt}, + domain, storage::{self, enums as storage_enums}, transformers::ForeignInto, }, @@ -35,7 +36,7 @@ impl ValidateRequest for PaymentMethodVa fn validate_request<'a, 'b>( &'b self, request: &api::VerifyRequest, - merchant_account: &'a types::storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::VerifyRequest>, operations::ValidateResult<'a>, @@ -68,7 +69,7 @@ impl GetTracker, api::VerifyRequest> for Paym payment_id: &api::PaymentIdType, request: &api::VerifyRequest, _mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::VerifyRequest>, PaymentData, @@ -200,7 +201,7 @@ impl UpdateTracker, api::VerifyRequest> for PaymentM db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - _customer: Option, + _customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::VerifyRequest>, PaymentData)> @@ -248,7 +249,7 @@ where ) -> CustomResult< ( BoxedOperation<'a, F, api::VerifyRequest>, - Option, + Option, ), errors::StorageError, > { @@ -277,7 +278,7 @@ where async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, _request: &api::VerifyRequest, _payment_intent: &storage::payment_intent::PaymentIntent, diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 409818125c..08ca7f678c 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -17,8 +17,8 @@ use crate::{ routes::AppState, types::{ api::{self, PaymentIdTypeExt}, + domain, storage::{self, enums as storage_enums}, - transformers::ForeignInto, }, utils::OptionExt, }; @@ -38,7 +38,7 @@ impl GetTracker, api::PaymentsSessionRequest> payment_id: &api::PaymentIdType, request: &api::PaymentsSessionRequest, _mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsSessionRequest>, PaymentData, @@ -156,8 +156,8 @@ impl GetTracker, api::PaymentsSessionRequest> token: None, setup_mandate: None, address: payments::PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: None, payment_method_data: None, @@ -186,7 +186,7 @@ impl UpdateTracker, api::PaymentsSessionRequest> for db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - _customer: Option, + _customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<( @@ -218,7 +218,7 @@ impl ValidateRequest for Paymen fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsSessionRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsSessionRequest>, operations::ValidateResult<'a>, @@ -254,7 +254,7 @@ where ) -> errors::CustomResult< ( BoxedOperation<'a, F, api::PaymentsSessionRequest>, - Option, + Option, ), errors::StorageError, > { @@ -293,7 +293,7 @@ where /// or from separate implementation ( for googlepay - from metadata and applepay - from metadata and call connector) async fn get_connector<'a>( &'a self, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, state: &AppState, request: &api::PaymentsSessionRequest, payment_intent: &storage::payment_intent::PaymentIntent, diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index b8f657b569..b48cc1348c 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -15,8 +15,8 @@ use crate::{ routes::AppState, types::{ api::{self, PaymentIdTypeExt}, + domain, storage::{self, enums as storage_enums}, - transformers::ForeignInto, }, utils::OptionExt, }; @@ -34,7 +34,7 @@ impl GetTracker, api::PaymentsStartRequest> f payment_id: &api::PaymentIdType, _request: &api::PaymentsStartRequest, _mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsStartRequest>, PaymentData, @@ -128,8 +128,8 @@ impl GetTracker, api::PaymentsStartRequest> f setup_mandate: None, token: payment_attempt.payment_token.clone(), address: PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: Some(payment_attempt.confirm), payment_attempt, @@ -158,7 +158,7 @@ impl UpdateTracker, api::PaymentsStartRequest> for P _db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, payment_data: PaymentData, - _customer: Option, + _customer: Option, _storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<( @@ -177,7 +177,7 @@ impl ValidateRequest for PaymentS fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsStartRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsStartRequest>, operations::ValidateResult<'a>, @@ -219,7 +219,7 @@ where ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsStartRequest>, - Option, + Option, ), errors::StorageError, > { @@ -248,7 +248,7 @@ where async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, _request: &api::PaymentsStartRequest, _payment_intent: &storage::payment_intent::PaymentIntent, diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 780ec27c83..731e799040 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -15,9 +15,8 @@ use crate::{ db::StorageInterface, routes::AppState, types::{ - api, + api, domain, storage::{self, enums}, - transformers::ForeignInto, }, utils::OptionExt, }; @@ -61,7 +60,7 @@ impl Domain for PaymentStatus { ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRequest>, - Option, + Option, ), errors::StorageError, > { @@ -99,7 +98,7 @@ impl Domain for PaymentStatus { async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, request: &api::PaymentsRequest, _payment_intent: &storage::payment_intent::PaymentIntent, @@ -115,7 +114,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, payment_data: PaymentData, - _customer: Option, + _customer: Option, _storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -133,7 +132,7 @@ impl UpdateTracker, api::PaymentsRetrieveRequest> fo _db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, payment_data: PaymentData, - _customer: Option, + _customer: Option, _storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<( @@ -158,7 +157,7 @@ impl GetTracker, api::PaymentsRetrieveRequest payment_id: &api::PaymentIdType, request: &api::PaymentsRetrieveRequest, _mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRetrieveRequest>, PaymentData, @@ -270,8 +269,8 @@ async fn get_tracker_for_sync< setup_mandate: None, token: None, address: PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: Some(request.force_sync), payment_method_data: None, @@ -301,7 +300,7 @@ impl ValidateRequest for Payme fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRetrieveRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRetrieveRequest>, operations::ValidateResult<'a>, diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 853df5630b..bdf381004b 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -17,6 +17,7 @@ use crate::{ routes::AppState, types::{ api::{self, PaymentIdTypeExt}, + domain, storage::{self, enums as storage_enums}, transformers::ForeignInto, }, @@ -35,7 +36,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_id: &api::PaymentIdType, request: &api::PaymentsRequest, mandate_type: Option, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest>, PaymentData, @@ -289,8 +290,8 @@ impl GetTracker, api::PaymentsRequest> for Pa token, setup_mandate, address: PaymentAddress { - shipping: shipping_address.as_ref().map(|a| a.foreign_into()), - billing: billing_address.as_ref().map(|a| a.foreign_into()), + shipping: shipping_address.as_ref().map(|a| a.into()), + billing: billing_address.as_ref().map(|a| a.into()), }, confirm: request.confirm, payment_method_data: request.payment_method_data.clone(), @@ -329,7 +330,7 @@ impl Domain for PaymentUpdate { ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRequest>, - Option, + Option, ), errors::StorageError, > { @@ -367,7 +368,7 @@ impl Domain for PaymentUpdate { async fn get_connector<'a>( &'a self, - _merchant_account: &storage::MerchantAccount, + _merchant_account: &domain::MerchantAccount, state: &AppState, request: &api::PaymentsRequest, _payment_intent: &storage::payment_intent::PaymentIntent, @@ -384,7 +385,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen db: &dyn StorageInterface, _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, - customer: Option, + customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -499,7 +500,7 @@ impl ValidateRequest for PaymentUpdate fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, - merchant_account: &'a storage::MerchantAccount, + merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRequest>, operations::ValidateResult<'a>, diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 8dbea2295c..5213889a2e 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -14,7 +14,7 @@ use crate::{ types::{ self, api::{self, PaymentMethodCreateExt}, - storage, + domain, }, utils::OptionExt, }; @@ -23,8 +23,8 @@ pub async fn save_payment_method( state: &AppState, connector: &api::ConnectorData, resp: types::RouterData, - maybe_customer: &Option, - merchant_account: &storage::MerchantAccount, + maybe_customer: &Option, + merchant_account: &domain::MerchantAccount, ) -> RouterResult> where FData: mandate::MandateBehaviour, @@ -128,7 +128,7 @@ where pub async fn save_in_locker( state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, payment_method_request: api::PaymentMethodCreate, ) -> RouterResult<(api_models::payment_methods::PaymentMethodResponse, bool)> { payment_method_request.validate()?; diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 0a8edf9a2a..83e14f9dc2 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -16,7 +16,7 @@ use crate::{ routes::{metrics, AppState}, services::{self, RedirectForm}, types::{ - self, api, + self, api, domain, storage::{self, enums}, transformers::{ForeignFrom, ForeignInto}, }, @@ -28,8 +28,8 @@ pub async fn construct_payment_router_data<'a, F, T>( state: &'a AppState, payment_data: PaymentData, connector_id: &str, - merchant_account: &storage::MerchantAccount, - customer: &Option, + merchant_account: &domain::MerchantAccount, + customer: &Option, ) -> RouterResult> where T: TryFrom>, @@ -133,7 +133,7 @@ where fn generate_response( req: Option, data: D, - customer: Option, + customer: Option, auth_flow: services::AuthFlow, server: &Server, operation: Op, @@ -148,7 +148,7 @@ where fn generate_response( req: Option, payment_data: PaymentData, - customer: Option, + customer: Option, auth_flow: services::AuthFlow, server: &Server, operation: Op, @@ -180,7 +180,7 @@ where fn generate_response( _req: Option, payment_data: PaymentData, - _customer: Option, + _customer: Option, _auth_flow: services::AuthFlow, _server: &Server, _operation: Op, @@ -206,7 +206,7 @@ where fn generate_response( _req: Option, data: PaymentData, - customer: Option, + customer: Option, _auth_flow: services::AuthFlow, _server: &Server, _operation: Op, @@ -221,7 +221,7 @@ where .and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())), name: customer .as_ref() - .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned().into())), + .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())), phone: customer .as_ref() .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), @@ -251,7 +251,7 @@ pub fn payments_to_payments_response( refunds: Vec, disputes: Vec, payment_method_data: Option, - customer: Option, + customer: Option, auth_flow: services::AuthFlow, address: PaymentAddress, server: &Server, @@ -380,7 +380,7 @@ where .set_name( customer .as_ref() - .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned().into())), + .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())), ) .set_phone( customer @@ -478,7 +478,7 @@ where .and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())), name: customer .as_ref() - .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned().into())), + .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())), phone: customer .as_ref() .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 38b9511be9..02e1acb5c2 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -18,18 +18,18 @@ use crate::{ types::{ self, api::{self, refunds}, + domain, storage::{self, enums, ProcessTrackerExt}, transformers::{ForeignFrom, ForeignInto}, }, utils::{self, OptionExt}, }; - // ********************************************** REFUND EXECUTE ********************************************** #[instrument(skip_all)] pub async fn refund_create_core( state: &AppState, - merchant_account: storage::merchant_account::MerchantAccount, + merchant_account: domain::MerchantAccount, req: refunds::RefundRequest, ) -> RouterResponse { let db = &*state.store; @@ -114,7 +114,7 @@ pub async fn refund_create_core( pub async fn trigger_refund_to_gateway( state: &AppState, refund: &storage::Refund, - merchant_account: &storage::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, creds_identifier: Option, @@ -245,12 +245,12 @@ pub async fn trigger_refund_to_gateway( pub async fn refund_response_wrapper<'a, F, Fut, T, Req>( state: &'a AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, request: Req, f: F, ) -> RouterResponse where - F: Fn(&'a AppState, storage::MerchantAccount, Req) -> Fut, + F: Fn(&'a AppState, domain::MerchantAccount, Req) -> Fut, Fut: futures::Future>, T: ForeignInto, { @@ -262,7 +262,7 @@ where #[instrument(skip_all)] pub async fn refund_retrieve_core( state: &AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, request: refunds::RefundsRetrieveRequest, ) -> RouterResult { let refund_id = request.refund_id; @@ -354,7 +354,7 @@ fn should_call_refund(refund: &storage_models::refund::Refund, force_sync: bool) #[instrument(skip_all)] pub async fn sync_refund_with_gateway( state: &AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, refund: &storage::Refund, @@ -452,7 +452,7 @@ pub async fn sync_refund_with_gateway( pub async fn refund_update_core( db: &dyn db::StorageInterface, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, refund_id: &str, req: refunds::RefundUpdateRequest, ) -> RouterResponse { @@ -486,7 +486,7 @@ pub async fn refund_update_core( #[instrument(skip_all)] pub async fn validate_and_create_refund( state: &AppState, - merchant_account: &storage::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, refund_amount: i64, @@ -628,7 +628,7 @@ pub async fn validate_and_create_refund( #[cfg(feature = "olap")] pub async fn refund_list( db: &dyn db::StorageInterface, - merchant_account: storage::merchant_account::MerchantAccount, + merchant_account: domain::MerchantAccount, req: api_models::refunds::RefundListRequest, ) -> RouterResponse { let limit = validator::validate_refund_list(req.limit)?; @@ -681,7 +681,7 @@ pub async fn schedule_refund_execution( state: &AppState, refund: storage::Refund, refund_type: api_models::refunds::RefundType, - merchant_account: &storage::merchant_account::MerchantAccount, + merchant_account: &domain::MerchantAccount, payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, creds_identifier: Option, diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index a1df464adb..370f970f73 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -11,7 +11,7 @@ use crate::{ core::errors::{self, RouterResult}, routes::AppState, types::{ - self, + self, domain, storage::{self, enums}, ErrorResponse, }, @@ -23,7 +23,7 @@ use crate::{ pub async fn construct_refund_router_data<'a, F>( state: &'a AppState, connector_id: &str, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, money: (i64, enums::Currency), payment_intent: &'a storage::PaymentIntent, payment_attempt: &storage::PaymentAttempt, @@ -237,7 +237,7 @@ pub async fn construct_accept_dispute_router_data<'a>( state: &'a AppState, payment_intent: &'a storage::PaymentIntent, payment_attempt: &storage::PaymentAttempt, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, dispute: &storage::Dispute, ) -> RouterResult { let connector_id = &dispute.connector; @@ -298,7 +298,7 @@ pub async fn construct_submit_evidence_router_data<'a>( state: &'a AppState, payment_intent: &'a storage::PaymentIntent, payment_attempt: &storage::PaymentAttempt, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, dispute: &storage::Dispute, submit_evidence_request_data: types::SubmitEvidenceRequestData, ) -> RouterResult { @@ -358,7 +358,7 @@ pub async fn construct_upload_file_router_data<'a>( state: &'a AppState, payment_intent: &'a storage::PaymentIntent, payment_attempt: &storage::PaymentAttempt, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, create_file_request: &types::api::CreateFileRequest, connector_id: &str, file_key: String, @@ -417,7 +417,7 @@ pub async fn construct_defend_dispute_router_data<'a>( state: &'a AppState, payment_intent: &'a storage::PaymentIntent, payment_attempt: &storage::PaymentAttempt, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, dispute: &storage::Dispute, ) -> RouterResult { let _db = &*state.store; @@ -477,7 +477,7 @@ pub async fn construct_defend_dispute_router_data<'a>( #[instrument(skip_all)] pub async fn construct_retrieve_file_router_data<'a>( state: &'a AppState, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, file_metadata: &storage_models::file::FileMetadata, connector_id: &str, ) -> RouterResult { diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 8db1408f77..e4ad140cf9 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -18,7 +18,7 @@ use crate::{ routes::AppState, services, types::{ - api, + api, domain, storage::{self, enums}, transformers::{ForeignInto, ForeignTryInto}, }, @@ -30,7 +30,7 @@ const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5; #[instrument(skip_all)] pub async fn payments_incoming_webhook_flow( state: AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, webhook_details: api::IncomingWebhookDetails, source_verified: bool, ) -> CustomResult<(), errors::ApiErrorResponse> { @@ -106,7 +106,7 @@ pub async fn payments_incoming_webhook_flow( #[instrument(skip_all)] pub async fn refunds_incoming_webhook_flow( state: AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, webhook_details: api::IncomingWebhookDetails, connector_name: &str, source_verified: bool, @@ -211,7 +211,7 @@ pub async fn refunds_incoming_webhook_flow( pub async fn get_payment_attempt_from_object_reference_id( state: &AppState, object_reference_id: api_models::webhooks::ObjectReferenceId, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> CustomResult { let db = &*state.store; match object_reference_id { @@ -323,7 +323,7 @@ pub async fn get_or_update_dispute_object( #[instrument(skip_all)] pub async fn disputes_incoming_webhook_flow( state: AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, webhook_details: api::IncomingWebhookDetails, source_verified: bool, connector: &(dyn api::Connector + Sync), @@ -386,7 +386,7 @@ pub async fn disputes_incoming_webhook_flow( async fn bank_transfer_webhook_flow( state: AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, webhook_details: api::IncomingWebhookDetails, source_verified: bool, ) -> CustomResult<(), errors::ApiErrorResponse> { @@ -461,7 +461,7 @@ async fn bank_transfer_webhook_flow( #[instrument(skip_all)] pub async fn create_event_and_trigger_outgoing_webhook( state: AppState, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, event_type: enums::EventType, event_class: enums::EventClass, intent_reference_id: Option, @@ -539,7 +539,7 @@ pub async fn create_event_and_trigger_outgoing_webhook( - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, webhook: api::OutgoingWebhook, outgoing_webhooks_signature: Option, state: &AppState, @@ -620,7 +620,7 @@ pub async fn trigger_webhook_to_merchant( pub async fn webhooks_core( state: &AppState, req: &actix_web::HttpRequest, - merchant_account: storage::MerchantAccount, + merchant_account: domain::MerchantAccount, connector_name: &str, body: actix_web::web::Bytes, ) -> RouterResponse { diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index ee9cd03e69..b60eb29e6a 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -13,6 +13,7 @@ pub mod locker_mock_up; pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; +pub mod merchant_key_store; pub mod payment_attempt; pub mod payment_intent; pub mod payment_method; @@ -64,10 +65,41 @@ pub trait StorageInterface: + refund::RefundInterface + reverse_lookup::ReverseLookupInterface + cards_info::CardsInfoInterface + + merchant_key_store::MerchantKeyStoreInterface + + MasterKeyInterface + services::RedisConnInterface + 'static { } + +pub trait MasterKeyInterface { + fn get_master_key(&self) -> &[u8]; + fn get_migration_timestamp(&self) -> i64; +} + +impl MasterKeyInterface for Store { + fn get_master_key(&self) -> &[u8] { + &self.master_key + } + fn get_migration_timestamp(&self) -> i64 { + self.migration_timestamp + } +} + +/// Default dummy key for MockDb +impl MasterKeyInterface for MockDb { + fn get_master_key(&self) -> &[u8] { + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ] + } + + fn get_migration_timestamp(&self) -> i64 { + 0 + } +} + #[async_trait::async_trait] impl StorageInterface for Store {} diff --git a/crates/router/src/db/address.rs b/crates/router/src/db/address.rs index 0f7d457005..1e94911bed 100644 --- a/crates/router/src/db/address.rs +++ b/crates/router/src/db/address.rs @@ -1,37 +1,47 @@ -use error_stack::IntoReport; +use common_utils::ext_traits::AsyncExt; +use error_stack::{IntoReport, ResultExt}; use storage_models::address::AddressUpdateInternal; -use super::{MockDb, Store}; +use super::{MasterKeyInterface, MockDb, Store}; use crate::{ connection, core::errors::{self, CustomResult}, - types::storage, + types::{ + domain::{ + self, + behaviour::{Conversion, ReverseConversion}, + }, + storage, + }, }; #[async_trait::async_trait] -pub trait AddressInterface { +pub trait AddressInterface +where + domain::Address: Conversion, +{ async fn update_address( &self, address_id: String, address: storage::AddressUpdate, - ) -> CustomResult; + ) -> CustomResult; async fn insert_address( &self, - address: storage::AddressNew, - ) -> CustomResult; + address: domain::Address, + ) -> CustomResult; async fn find_address( &self, address_id: &str, - ) -> CustomResult; + ) -> CustomResult; async fn update_address_by_merchant_id_customer_id( &self, customer_id: &str, merchant_id: &str, address: storage::AddressUpdate, - ) -> CustomResult, errors::StorageError>; + ) -> CustomResult, errors::StorageError>; } #[async_trait::async_trait] @@ -39,36 +49,63 @@ impl AddressInterface for Store { async fn find_address( &self, address_id: &str, - ) -> CustomResult { + ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; storage::Address::find_by_address_id(&conn, address_id) .await .map_err(Into::into) .into_report() + .async_and_then(|address| async { + let merchant_id = address.merchant_id.clone(); + address + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn update_address( &self, address_id: String, address: storage::AddressUpdate, - ) -> CustomResult { + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - storage::Address::update_by_address_id(&conn, address_id, address) + storage::Address::update_by_address_id(&conn, address_id, address.into()) .await .map_err(Into::into) .into_report() + .async_and_then(|address| async { + let merchant_id = address.merchant_id.clone(); + address + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn insert_address( &self, - address: storage::AddressNew, - ) -> CustomResult { + address: domain::Address, + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; address + .construct_new() + .await + .change_context(errors::StorageError::EncryptionError)? .insert(&conn) .await .map_err(Into::into) .into_report() + .async_and_then(|address| async { + let merchant_id = address.merchant_id.clone(); + address + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn update_address_by_merchant_id_customer_id( @@ -76,17 +113,31 @@ impl AddressInterface for Store { customer_id: &str, merchant_id: &str, address: storage::AddressUpdate, - ) -> CustomResult, errors::StorageError> { + ) -> CustomResult, errors::StorageError> { let conn = connection::pg_connection_write(self).await?; storage::Address::update_by_merchant_id_customer_id( &conn, customer_id, merchant_id, - address, + address.into(), ) .await .map_err(Into::into) .into_report() + .async_and_then(|addresses| async { + let mut output = Vec::with_capacity(addresses.len()); + for address in addresses.into_iter() { + let merchant_id = address.merchant_id.clone(); + output.push( + address + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError)?, + ) + } + Ok(output) + }) + .await } } @@ -95,7 +146,7 @@ impl AddressInterface for MockDb { async fn find_address( &self, address_id: &str, - ) -> CustomResult { + ) -> CustomResult { match self .addresses .lock() @@ -103,7 +154,14 @@ impl AddressInterface for MockDb { .iter() .find(|address| address.address_id == address_id) { - Some(address) => return Ok(address.clone()), + Some(address) => { + let merchant_id = address.merchant_id.clone(); + address + .clone() + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + } None => { return Err( errors::StorageError::ValueNotFound("address not found".to_string()).into(), @@ -116,7 +174,7 @@ impl AddressInterface for MockDb { &self, address_id: String, address_update: storage::AddressUpdate, - ) -> CustomResult { + ) -> CustomResult { match self .addresses .lock() @@ -129,7 +187,13 @@ impl AddressInterface for MockDb { *a = address_updated.clone(); address_updated }) { - Some(address_updated) => Ok(address_updated), + Some(address_updated) => { + let merchant_id = address_updated.merchant_id.clone(); + address_updated + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + } None => { return Err(errors::StorageError::ValueNotFound( "cannot find address to update".to_string(), @@ -141,35 +205,21 @@ impl AddressInterface for MockDb { async fn insert_address( &self, - address_new: storage::AddressNew, - ) -> CustomResult { + address_new: domain::Address, + ) -> CustomResult { let mut addresses = self.addresses.lock().await; - let now = common_utils::date_time::now(); - let address = storage::Address { - #[allow(clippy::as_conversions)] - id: addresses.len() as i32, - address_id: address_new.address_id, - city: address_new.city, - country: address_new.country, - line1: address_new.line1, - line2: address_new.line2, - line3: address_new.line3, - state: address_new.state, - zip: address_new.zip, - first_name: address_new.first_name, - last_name: address_new.last_name, - phone_number: address_new.phone_number, - country_code: address_new.country_code, - created_at: now, - modified_at: now, - customer_id: address_new.customer_id, - merchant_id: address_new.merchant_id, - }; + let address = Conversion::convert(address_new) + .await + .change_context(errors::StorageError::EncryptionError)?; + let merchant_id = address.merchant_id.clone(); addresses.push(address.clone()); - Ok(address) + address + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) } async fn update_address_by_merchant_id_customer_id( @@ -177,7 +227,7 @@ impl AddressInterface for MockDb { customer_id: &str, merchant_id: &str, address_update: storage::AddressUpdate, - ) -> CustomResult, errors::StorageError> { + ) -> CustomResult, errors::StorageError> { match self .addresses .lock() @@ -192,7 +242,13 @@ impl AddressInterface for MockDb { *a = address_updated.clone(); address_updated }) { - Some(address) => Ok(vec![address]), + Some(address) => { + let address: domain::Address = address + .convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError)?; + Ok(vec![address]) + } None => { return Err( errors::StorageError::ValueNotFound("address not found".to_string()).into(), diff --git a/crates/router/src/db/customers.rs b/crates/router/src/db/customers.rs index 7e1519b6fa..fe8610a643 100644 --- a/crates/router/src/db/customers.rs +++ b/crates/router/src/db/customers.rs @@ -1,17 +1,28 @@ -use error_stack::IntoReport; +use common_utils::ext_traits::AsyncExt; +use error_stack::{IntoReport, ResultExt}; +use masking::PeekInterface; -use super::{MockDb, Store}; +use super::{MasterKeyInterface, MockDb, Store}; use crate::{ connection, core::{ customers::REDACTED, errors::{self, CustomResult}, }, - types::storage, + types::{ + domain::{ + self, + behaviour::{Conversion, ReverseConversion}, + }, + storage, + }, }; #[async_trait::async_trait] -pub trait CustomerInterface { +pub trait CustomerInterface +where + domain::Customer: Conversion, +{ async fn delete_customer_by_customer_id_merchant_id( &self, customer_id: &str, @@ -22,25 +33,25 @@ pub trait CustomerInterface { &self, customer_id: &str, merchant_id: &str, - ) -> CustomResult, errors::StorageError>; + ) -> CustomResult, errors::StorageError>; async fn update_customer_by_customer_id_merchant_id( &self, customer_id: String, merchant_id: String, customer: storage::CustomerUpdate, - ) -> CustomResult; + ) -> CustomResult; async fn find_customer_by_customer_id_merchant_id( &self, customer_id: &str, merchant_id: &str, - ) -> CustomResult; + ) -> CustomResult; async fn insert_customer( &self, - customer_data: storage::CustomerNew, - ) -> CustomResult; + customer_data: domain::Customer, + ) -> CustomResult; } #[async_trait::async_trait] @@ -49,21 +60,31 @@ impl CustomerInterface for Store { &self, customer_id: &str, merchant_id: &str, - ) -> CustomResult, errors::StorageError> { + ) -> CustomResult, errors::StorageError> { let conn = connection::pg_connection_read(self).await?; - let maybe_customer = storage::Customer::find_optional_by_customer_id_merchant_id( - &conn, - customer_id, - merchant_id, - ) - .await - .map_err(Into::into) - .into_report()?; + let maybe_customer: Option = + storage::Customer::find_optional_by_customer_id_merchant_id( + &conn, + customer_id, + merchant_id, + ) + .await + .map_err(Into::into) + .into_report()? + .async_map(|c| async { + c.convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await + .transpose()?; maybe_customer.map_or(Ok(None), |customer| { // in the future, once #![feature(is_some_and)] is stable, we can make this more concise: // `if customer.name.is_some_and(|ref name| name == REDACTED) ...` match customer.name { - Some(ref name) if name == REDACTED => Err(errors::StorageError::CustomerRedacted)?, + Some(ref name) if name.peek() == REDACTED => { + Err(errors::StorageError::CustomerRedacted)? + } _ => Ok(Some(customer)), } }) @@ -74,46 +95,72 @@ impl CustomerInterface for Store { customer_id: String, merchant_id: String, customer: storage::CustomerUpdate, - ) -> CustomResult { + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; storage::Customer::update_by_customer_id_merchant_id( &conn, customer_id, merchant_id, - customer, + customer.into(), ) .await .map_err(Into::into) .into_report() + .async_and_then(|c| async { + let merchant_id = c.merchant_id.clone(); + c.convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn find_customer_by_customer_id_merchant_id( &self, customer_id: &str, merchant_id: &str, - ) -> CustomResult { + ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - let customer = + let customer: domain::Customer = storage::Customer::find_by_customer_id_merchant_id(&conn, customer_id, merchant_id) .await .map_err(Into::into) - .into_report()?; + .into_report() + .async_and_then(|c| async { + let merchant_id = c.merchant_id.clone(); + c.convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await?; match customer.name { - Some(ref name) if name == REDACTED => Err(errors::StorageError::CustomerRedacted)?, + Some(ref name) if name.peek() == REDACTED => { + Err(errors::StorageError::CustomerRedacted)? + } _ => Ok(customer), } } async fn insert_customer( &self, - customer_data: storage::CustomerNew, - ) -> CustomResult { + customer_data: domain::Customer, + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; customer_data + .construct_new() + .await + .change_context(errors::StorageError::EncryptionError)? .insert(&conn) .await .map_err(Into::into) .into_report() + .async_and_then(|c| async { + let merchant_id = c.merchant_id.clone(); + c.convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn delete_customer_by_customer_id_merchant_id( @@ -136,15 +183,23 @@ impl CustomerInterface for MockDb { &self, customer_id: &str, merchant_id: &str, - ) -> CustomResult, errors::StorageError> { + ) -> CustomResult, errors::StorageError> { let customers = self.customers.lock().await; - - Ok(customers + let customer = customers .iter() .find(|customer| { customer.customer_id == customer_id && customer.merchant_id == merchant_id }) - .cloned()) + .cloned(); + customer + .async_map(|c| async { + let merchant_id = c.merchant_id.clone(); + c.convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await + .transpose() } async fn update_customer_by_customer_id_merchant_id( @@ -152,7 +207,7 @@ impl CustomerInterface for MockDb { _customer_id: String, _merchant_id: String, _customer: storage::CustomerUpdate, - ) -> CustomResult { + ) -> CustomResult { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } @@ -161,7 +216,7 @@ impl CustomerInterface for MockDb { &self, _customer_id: &str, _merchant_id: &str, - ) -> CustomResult { + ) -> CustomResult { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } @@ -169,26 +224,21 @@ impl CustomerInterface for MockDb { #[allow(clippy::panic)] async fn insert_customer( &self, - customer_data: storage::CustomerNew, - ) -> CustomResult { + customer_data: domain::Customer, + ) -> CustomResult { let mut customers = self.customers.lock().await; - let customer = storage::Customer { - #[allow(clippy::as_conversions)] - id: customers.len() as i32, - customer_id: customer_data.customer_id, - merchant_id: customer_data.merchant_id, - name: customer_data.name, - email: customer_data.email, - phone: customer_data.phone, - phone_country_code: customer_data.phone_country_code, - description: customer_data.description, - created_at: common_utils::date_time::now(), - metadata: customer_data.metadata, - connector_customer: customer_data.connector_customer, - modified_at: common_utils::date_time::now(), - }; + + let customer = Conversion::convert(customer_data) + .await + .change_context(errors::StorageError::EncryptionError)?; + + let merchant_id = customer.merchant_id.clone(); customers.push(customer.clone()); - Ok(customer) + + customer + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) } async fn delete_customer_by_customer_id_merchant_id( diff --git a/crates/router/src/db/merchant_account.rs b/crates/router/src/db/merchant_account.rs index 61b30bb5cd..7200ec61b6 100644 --- a/crates/router/src/db/merchant_account.rs +++ b/crates/router/src/db/merchant_account.rs @@ -1,4 +1,5 @@ -use error_stack::IntoReport; +use common_utils::ext_traits::AsyncExt; +use error_stack::{IntoReport, ResultExt}; use super::{MockDb, Store}; #[cfg(feature = "accounts_cache")] @@ -6,37 +7,48 @@ use crate::cache::{self, ACCOUNTS_CACHE}; use crate::{ connection, core::errors::{self, CustomResult}, - types::storage::{self, enums}, + db::MasterKeyInterface, + types::{ + domain::{ + self, + behaviour::{Conversion, ReverseConversion}, + }, + storage, + }, }; #[async_trait::async_trait] -pub trait MerchantAccountInterface { +pub trait MerchantAccountInterface +where + domain::MerchantAccount: + Conversion, +{ async fn insert_merchant( &self, - merchant_account: storage::MerchantAccountNew, - ) -> CustomResult; + merchant_account: domain::MerchantAccount, + ) -> CustomResult; async fn find_merchant_account_by_merchant_id( &self, merchant_id: &str, - ) -> CustomResult; + ) -> CustomResult; async fn update_merchant( &self, - this: storage::MerchantAccount, + this: domain::MerchantAccount, merchant_account: storage::MerchantAccountUpdate, - ) -> CustomResult; + ) -> CustomResult; async fn update_specific_fields_in_merchant( &self, merchant_id: &str, merchant_account: storage::MerchantAccountUpdate, - ) -> CustomResult; + ) -> CustomResult; async fn find_merchant_account_by_publishable_key( &self, publishable_key: &str, - ) -> CustomResult; + ) -> CustomResult; async fn delete_merchant_account_by_merchant_id( &self, @@ -48,20 +60,27 @@ pub trait MerchantAccountInterface { impl MerchantAccountInterface for Store { async fn insert_merchant( &self, - merchant_account: storage::MerchantAccountNew, - ) -> CustomResult { + merchant_account: domain::MerchantAccount, + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; + let merchant_id = merchant_account.merchant_id.clone(); merchant_account + .construct_new() + .await + .change_context(errors::StorageError::EncryptionError)? .insert(&conn) .await .map_err(Into::into) - .into_report() + .into_report()? + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) } async fn find_merchant_account_by_merchant_id( &self, merchant_id: &str, - ) -> CustomResult { + ) -> CustomResult { let fetch_func = || async { let conn = connection::pg_connection_read(self).await?; storage::MerchantAccount::find_by_merchant_id(&conn, merchant_id) @@ -72,28 +91,44 @@ impl MerchantAccountInterface for Store { #[cfg(not(feature = "accounts_cache"))] { - fetch_func().await + fetch_func() + .await? + .convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) } #[cfg(feature = "accounts_cache")] { super::cache::get_or_populate_in_memory(self, merchant_id, fetch_func, &ACCOUNTS_CACHE) + .await? + .convert(self, merchant_id, self.get_migration_timestamp()) .await + .change_context(errors::StorageError::DecryptionError) } } async fn update_merchant( &self, - this: storage::MerchantAccount, + this: domain::MerchantAccount, merchant_account: storage::MerchantAccountUpdate, - ) -> CustomResult { + ) -> CustomResult { let _merchant_id = this.merchant_id.clone(); let update_func = || async { let conn = connection::pg_connection_write(self).await?; - this.update(&conn, merchant_account) + Conversion::convert(this) + .await + .change_context(errors::StorageError::EncryptionError)? + .update(&conn, merchant_account.into()) .await .map_err(Into::into) .into_report() + .async_and_then(|item| async { + item.convert(self, &_merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await }; #[cfg(not(feature = "accounts_cache"))] @@ -105,7 +140,7 @@ impl MerchantAccountInterface for Store { { super::cache::publish_and_redact( self, - cache::CacheKind::Accounts(_merchant_id.into()), + cache::CacheKind::Accounts((&_merchant_id).into()), update_func, ) .await @@ -116,17 +151,23 @@ impl MerchantAccountInterface for Store { &self, merchant_id: &str, merchant_account: storage::MerchantAccountUpdate, - ) -> CustomResult { + ) -> CustomResult { let update_func = || async { let conn = connection::pg_connection_write(self).await?; storage::MerchantAccount::update_with_specific_fields( &conn, merchant_id, - merchant_account, + merchant_account.into(), ) .await .map_err(Into::into) .into_report() + .async_and_then(|item| async { + item.convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await }; #[cfg(not(feature = "accounts_cache"))] @@ -148,12 +189,19 @@ impl MerchantAccountInterface for Store { async fn find_merchant_account_by_publishable_key( &self, publishable_key: &str, - ) -> CustomResult { + ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; storage::MerchantAccount::find_by_publishable_key(&conn, publishable_key) .await .map_err(Into::into) .into_report() + .async_and_then(|item| async { + let merchant_id = item.merchant_id.clone(); + item.convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn delete_merchant_account_by_merchant_id( @@ -190,53 +238,41 @@ impl MerchantAccountInterface for MockDb { #[allow(clippy::panic)] async fn insert_merchant( &self, - merchant_account: storage::MerchantAccountNew, - ) -> CustomResult { + merchant_account: domain::MerchantAccount, + ) -> CustomResult { let mut accounts = self.merchant_accounts.lock().await; - let account = storage::MerchantAccount { - #[allow(clippy::as_conversions)] - id: accounts.len() as i32, - merchant_id: merchant_account.merchant_id, - return_url: merchant_account.return_url, - enable_payment_response_hash: merchant_account - .enable_payment_response_hash - .unwrap_or_default(), - payment_response_hash_key: merchant_account.payment_response_hash_key, - redirect_to_merchant_with_http_post: merchant_account - .redirect_to_merchant_with_http_post - .unwrap_or_default(), - merchant_name: merchant_account.merchant_name, - merchant_details: merchant_account.merchant_details, - webhook_details: merchant_account.webhook_details, - routing_algorithm: merchant_account.routing_algorithm, - sub_merchants_enabled: merchant_account.sub_merchants_enabled, - parent_merchant_id: merchant_account.parent_merchant_id, - publishable_key: merchant_account.publishable_key, - storage_scheme: enums::MerchantStorageScheme::PostgresOnly, - locker_id: merchant_account.locker_id, - metadata: merchant_account.metadata, - primary_business_details: merchant_account.primary_business_details, - created_at: common_utils::date_time::now(), - modified_at: common_utils::date_time::now(), - frm_routing_algorithm: merchant_account.frm_routing_algorithm, - intent_fulfillment_time: merchant_account.intent_fulfillment_time, - }; + let account = Conversion::convert(merchant_account) + .await + .change_context(errors::StorageError::EncryptionError)?; + let merchant_id = account.merchant_id.clone(); accounts.push(account.clone()); - Ok(account) + + account + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) } #[allow(clippy::panic)] async fn find_merchant_account_by_merchant_id( &self, merchant_id: &str, - ) -> CustomResult { + ) -> CustomResult { let accounts = self.merchant_accounts.lock().await; - let account = accounts + let account: Option = accounts .iter() - .find(|account| account.merchant_id == merchant_id); + .find(|account| account.merchant_id == merchant_id) + .cloned() + .async_map(|a| async { + a.convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await + .transpose()?; match account { - Some(account) => Ok(account.clone()), + Some(account) => Ok(account), // [#172]: Implement function for `MockDb` None => Err(errors::StorageError::MockDbError)?, } @@ -244,9 +280,9 @@ impl MerchantAccountInterface for MockDb { async fn update_merchant( &self, - _this: storage::MerchantAccount, + _this: domain::MerchantAccount, _merchant_account: storage::MerchantAccountUpdate, - ) -> CustomResult { + ) -> CustomResult { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } @@ -255,7 +291,7 @@ impl MerchantAccountInterface for MockDb { &self, _merchant_id: &str, _merchant_account: storage::MerchantAccountUpdate, - ) -> CustomResult { + ) -> CustomResult { // [#TODO]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } @@ -263,7 +299,7 @@ impl MerchantAccountInterface for MockDb { async fn find_merchant_account_by_publishable_key( &self, _publishable_key: &str, - ) -> CustomResult { + ) -> CustomResult { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 0869fed932..8bc484f4e5 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -1,13 +1,22 @@ -use common_utils::ext_traits::{ByteSliceExt, Encode}; +use common_utils::ext_traits::{AsyncExt, ByteSliceExt, Encode}; use error_stack::{IntoReport, ResultExt}; -use masking::ExposeInterface; +#[cfg(feature = "accounts_cache")] +use super::cache; use super::{MockDb, Store}; use crate::{ connection, core::errors::{self, CustomResult}, + db::MasterKeyInterface, services::logger, - types::{self, storage}, + types::{ + self, + domain::{ + self, + behaviour::{Conversion, ReverseConversion}, + }, + storage, + }, }; #[async_trait::async_trait] @@ -97,35 +106,41 @@ impl ConnectorAccessToken for MockDb { } #[async_trait::async_trait] -pub trait MerchantConnectorAccountInterface { +pub trait MerchantConnectorAccountInterface +where + domain::MerchantConnectorAccount: Conversion< + DstType = storage::MerchantConnectorAccount, + NewDstType = storage::MerchantConnectorAccountNew, + >, +{ async fn find_merchant_connector_account_by_merchant_id_connector_label( &self, merchant_id: &str, - connector: &str, - ) -> CustomResult; + connector_label: &str, + ) -> CustomResult; async fn insert_merchant_connector_account( &self, - t: storage::MerchantConnectorAccountNew, - ) -> CustomResult; + t: domain::MerchantConnectorAccount, + ) -> CustomResult; async fn find_by_merchant_connector_account_merchant_id_merchant_connector_id( &self, merchant_id: &str, merchant_connector_id: &str, - ) -> CustomResult; + ) -> CustomResult; async fn find_merchant_connector_account_by_merchant_id_and_disabled_list( &self, merchant_id: &str, get_disabled: bool, - ) -> CustomResult, errors::StorageError>; + ) -> CustomResult, errors::StorageError>; async fn update_merchant_connector_account( &self, - this: storage::MerchantConnectorAccount, - merchant_connector_account: storage::MerchantConnectorAccountUpdate, - ) -> CustomResult; + this: domain::MerchantConnectorAccount, + merchant_connector_account: storage::MerchantConnectorAccountUpdateInternal, + ) -> CustomResult; async fn delete_merchant_connector_account_by_merchant_id_merchant_connector_id( &self, @@ -140,7 +155,7 @@ impl MerchantConnectorAccountInterface for Store { &self, merchant_id: &str, connector_label: &str, - ) -> CustomResult { + ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; storage::MerchantConnectorAccount::find_by_merchant_id_connector( &conn, @@ -150,13 +165,19 @@ impl MerchantConnectorAccountInterface for Store { .await .map_err(Into::into) .into_report() + .async_and_then(|item| async { + item.convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn find_by_merchant_connector_account_merchant_id_merchant_connector_id( &self, merchant_id: &str, merchant_connector_id: &str, - ) -> CustomResult { + ) -> CustomResult { let find_call = || async { let conn = connection::pg_connection_read(self).await?; storage::MerchantConnectorAccount::find_by_merchant_id_merchant_connector_id( @@ -168,54 +189,98 @@ impl MerchantConnectorAccountInterface for Store { .map_err(Into::into) .into_report() }; + #[cfg(not(feature = "accounts_cache"))] { - find_call().await + find_call() + .await? + .convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DeserializationFailed) } #[cfg(feature = "accounts_cache")] { - super::cache::get_or_populate_redis(self, merchant_connector_id, find_call).await + cache::get_or_populate_redis(self, merchant_connector_id, find_call) + .await? + .convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DeserializationFailed) } } async fn insert_merchant_connector_account( &self, - t: storage::MerchantConnectorAccountNew, - ) -> CustomResult { + t: domain::MerchantConnectorAccount, + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - t.insert(&conn).await.map_err(Into::into).into_report() + t.construct_new() + .await + .change_context(errors::StorageError::EncryptionError)? + .insert(&conn) + .await + .map_err(Into::into) + .into_report() + .async_and_then(|item| async { + let merchant_id = item.merchant_id.clone(); + item.convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await } async fn find_merchant_connector_account_by_merchant_id_and_disabled_list( &self, merchant_id: &str, get_disabled: bool, - ) -> CustomResult, errors::StorageError> { + ) -> CustomResult, errors::StorageError> { let conn = connection::pg_connection_read(self).await?; storage::MerchantConnectorAccount::find_by_merchant_id(&conn, merchant_id, get_disabled) .await .map_err(Into::into) .into_report() + .async_and_then(|items| async { + let mut output = Vec::with_capacity(items.len()); + for item in items.into_iter() { + output.push( + item.convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError)?, + ) + } + Ok(output) + }) + .await } async fn update_merchant_connector_account( &self, - this: storage::MerchantConnectorAccount, - merchant_connector_account: storage::MerchantConnectorAccountUpdate, - ) -> CustomResult { + this: domain::MerchantConnectorAccount, + merchant_connector_account: storage::MerchantConnectorAccountUpdateInternal, + ) -> CustomResult { let _merchant_connector_id = this.merchant_connector_id.clone(); let update_call = || async { let conn = connection::pg_connection_write(self).await?; - this.update(&conn, merchant_connector_account) + Conversion::convert(this) + .await + .change_context(errors::StorageError::EncryptionError)? + .update(&conn, merchant_connector_account) .await .map_err(Into::into) .into_report() + .async_and_then(|item| async { + let merchant_id = item.merchant_id.clone(); + item.convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + }) + .await }; #[cfg(feature = "accounts_cache")] { - super::cache::redact_cache(self, &_merchant_connector_id, update_call, None).await + cache::redact_cache(self, &_merchant_connector_id, update_call, None).await } #[cfg(not(feature = "accounts_cache"))] @@ -249,7 +314,7 @@ impl MerchantConnectorAccountInterface for MockDb { &self, merchant_id: &str, connector: &str, - ) -> CustomResult { + ) -> CustomResult { let accounts = self.merchant_connector_accounts.lock().await; let account = accounts .iter() @@ -258,14 +323,17 @@ impl MerchantConnectorAccountInterface for MockDb { }) .cloned() .unwrap(); - Ok(account) + account + .convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) } async fn find_by_merchant_connector_account_merchant_id_merchant_connector_id( &self, _merchant_id: &str, _merchant_connector_id: &str, - ) -> CustomResult { + ) -> CustomResult { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } @@ -273,24 +341,23 @@ impl MerchantConnectorAccountInterface for MockDb { #[allow(clippy::panic)] async fn insert_merchant_connector_account( &self, - t: storage::MerchantConnectorAccountNew, - ) -> CustomResult { + t: domain::MerchantConnectorAccount, + ) -> CustomResult { let mut accounts = self.merchant_connector_accounts.lock().await; + let merchant_id = t.merchant_id.clone(); let account = storage::MerchantConnectorAccount { #[allow(clippy::as_conversions)] id: accounts.len() as i32, - merchant_id: t.merchant_id.unwrap_or_default(), - connector_name: t.connector_name.unwrap_or_default(), - connector_account_details: t.connector_account_details.unwrap_or_default().expose(), + merchant_id: t.merchant_id, + connector_name: t.connector_name, + connector_account_details: t.connector_account_details.into(), test_mode: t.test_mode, disabled: t.disabled, merchant_connector_id: t.merchant_connector_id, payment_methods_enabled: t.payment_methods_enabled, metadata: t.metadata, frm_configs: t.frm_configs, - connector_type: t - .connector_type - .unwrap_or(crate::types::storage::enums::ConnectorType::FinOperations), + connector_type: t.connector_type, connector_label: t.connector_label, business_country: t.business_country, business_label: t.business_label, @@ -299,23 +366,26 @@ impl MerchantConnectorAccountInterface for MockDb { modified_at: common_utils::date_time::now(), }; accounts.push(account.clone()); - Ok(account) + account + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) } async fn find_merchant_connector_account_by_merchant_id_and_disabled_list( &self, _merchant_id: &str, _get_disabled: bool, - ) -> CustomResult, errors::StorageError> { + ) -> CustomResult, errors::StorageError> { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } async fn update_merchant_connector_account( &self, - _this: storage::MerchantConnectorAccount, - _merchant_connector_account: storage::MerchantConnectorAccountUpdate, - ) -> CustomResult { + _this: domain::MerchantConnectorAccount, + _merchant_connector_account: storage::MerchantConnectorAccountUpdateInternal, + ) -> CustomResult { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? } diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs new file mode 100644 index 0000000000..3e78e8b2b8 --- /dev/null +++ b/crates/router/src/db/merchant_key_store.rs @@ -0,0 +1,82 @@ +use error_stack::{IntoReport, ResultExt}; + +use super::MasterKeyInterface; +use crate::{ + connection, + core::errors::{self, CustomResult}, + db::MockDb, + services::Store, + types::domain::{ + behaviour::{Conversion, ReverseConversion}, + merchant_key_store, + }, +}; + +#[async_trait::async_trait] +pub trait MerchantKeyStoreInterface { + async fn insert_merchant_key_store( + &self, + merchant_key_store: merchant_key_store::MerchantKeyStore, + ) -> CustomResult; + + async fn get_merchant_key_store_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl MerchantKeyStoreInterface for Store { + async fn insert_merchant_key_store( + &self, + merchant_key_store: merchant_key_store::MerchantKeyStore, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + let merchant_id = merchant_key_store.merchant_id.clone(); + merchant_key_store + .construct_new() + .await + .change_context(errors::StorageError::EncryptionError)? + .insert(&conn) + .await + .map_err(Into::into) + .into_report()? + .convert(self, &merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + } + async fn get_merchant_key_store_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_read(self).await?; + storage_models::merchant_key_store::MerchantKeyStore::find_by_merchant_id( + &conn, + merchant_id, + ) + .await + .map_err(Into::into) + .into_report()? + .convert(self, merchant_id, self.get_migration_timestamp()) + .await + .change_context(errors::StorageError::DecryptionError) + } +} + +#[async_trait::async_trait] +impl MerchantKeyStoreInterface for MockDb { + async fn insert_merchant_key_store( + &self, + _merchant_key_store: merchant_key_store::MerchantKeyStore, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(errors::StorageError::MockDbError.into()) + } + async fn get_merchant_key_store_by_merchant_id( + &self, + _merchant_id: &str, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(errors::StorageError::MockDbError.into()) + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 820f9c5aab..c3925fa5f4 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -19,6 +19,7 @@ pub mod scheduler; pub mod middleware; #[cfg(feature = "openapi")] pub mod openapi; +pub mod scripts; pub mod services; pub mod types; pub mod utils; diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 3cfd000f0c..8c2eb6c4d6 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -78,5 +78,9 @@ histogram_metric!(CARD_ADD_TIME, GLOBAL_METER); histogram_metric!(CARD_GET_TIME, GLOBAL_METER); histogram_metric!(CARD_DELETE_TIME, GLOBAL_METER); +// Encryption and Decryption metrics +histogram_metric!(ENCRYPTION_TIME, GLOBAL_METER); +histogram_metric!(DECRYPTION_TIME, GLOBAL_METER); + pub mod request; pub mod utils; diff --git a/crates/router/src/routes/metrics/request.rs b/crates/router/src/routes/metrics/request.rs index 68734bfd66..6ac3d692b4 100644 --- a/crates/router/src/routes/metrics/request.rs +++ b/crates/router/src/routes/metrics/request.rs @@ -20,7 +20,7 @@ where } #[inline] -pub async fn record_card_operation_time( +pub async fn record_operation_time( future: F, metric: &once_cell::sync::Lazy>, ) -> R diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index c2bd36f04b..25724a549b 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -9,7 +9,10 @@ use crate::{ payments::{self, PaymentRedirectFlow}, }, services::{api, authentication as auth}, - types::api::{self as api_types, enums as api_enums, payments as payment_types}, + types::{ + api::{self as api_types, enums as api_enums, payments as payment_types}, + domain, + }, }; /// Payments - Create @@ -694,7 +697,7 @@ pub async fn payments_list( async fn authorize_verify_select( operation: Op, state: &app::AppState, - merchant_account: storage_models::merchant_account::MerchantAccount, + merchant_account: domain::MerchantAccount, req: api_models::payments::PaymentsRequest, auth_flow: api::AuthFlow, ) -> app::core::errors::RouterResponse diff --git a/crates/router/src/scripts.rs b/crates/router/src/scripts.rs new file mode 100644 index 0000000000..dc9f789c16 --- /dev/null +++ b/crates/router/src/scripts.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "pii-encryption-script")] +pub mod pii_encryption; diff --git a/crates/router/src/scripts/pii_encryption.rs b/crates/router/src/scripts/pii_encryption.rs new file mode 100644 index 0000000000..ce71edffb0 --- /dev/null +++ b/crates/router/src/scripts/pii_encryption.rs @@ -0,0 +1,421 @@ +use async_bb8_diesel::AsyncConnection; +use common_utils::errors::CustomResult; +use diesel::{associations::HasTable, ExpressionMethods, Table}; +use error_stack::{IntoReport, ResultExt}; +use storage_models::{ + address::Address, + customers::Customer, + merchant_account::MerchantAccount, + merchant_connector_account::MerchantConnectorAccount, + query::generics::generic_filter, + schema::{ + address::dsl as ad_dsl, customers::dsl as cu_dsl, merchant_account::dsl as ma_dsl, + merchant_connector_account::dsl as mca_dsl, + }, +}; + +use crate::{ + connection, + core::errors, + db::{ + merchant_account::MerchantAccountInterface, merchant_key_store::MerchantKeyStoreInterface, + MasterKeyInterface, + }, + services::{self, Store}, + types::{ + domain::{ + self, + behaviour::{Conversion, ReverseConversion}, + merchant_key_store, types, + }, + storage, + }, +}; + +pub async fn create_merchant_key_store( + state: &Store, + merchant_id: &str, + key: Vec, +) -> CustomResult<(), errors::ApiErrorResponse> { + crate::logger::info!("Trying to create MerchantKeyStore for {}", merchant_id); + let master_key = state.get_master_key(); + let key_store = merchant_key_store::MerchantKeyStore { + merchant_id: merchant_id.to_string(), + key: types::encrypt(key.into(), master_key) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to decrypt data from key store")?, + created_at: common_utils::date_time::now(), + }; + + match state.insert_merchant_key_store(key_store).await { + Ok(_) => Ok(()), + Err(err) => match err.current_context() { + errors::StorageError::DatabaseError(f) => match f.current_context() { + storage_models::errors::DatabaseError::UniqueViolation => Ok(()), + _ => Err(err.change_context(errors::ApiErrorResponse::InternalServerError)), + }, + _ => Err(err.change_context(errors::ApiErrorResponse::InternalServerError)), + }, + } +} + +pub async fn encrypt_merchant_account_fields( + state: &Store, +) -> CustomResult<(), errors::ApiErrorResponse> { + let conn = connection::pg_connection_write(state) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let merchants: Vec = generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + &conn, + ma_dsl::merchant_id.eq(ma_dsl::merchant_id), + None, + None, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + for mer in merchants.iter() { + let key = services::generate_aes256_key() + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + create_merchant_key_store(state, &mer.merchant_id, key.to_vec()).await?; + } + let mut domain_merchants = Vec::with_capacity(merchants.len()); + for mf in merchants.into_iter() { + let merchant_id = mf.merchant_id.clone(); + let domain_merchant: domain::MerchantAccount = mf + .convert(state, &merchant_id, state.get_migration_timestamp()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + domain_merchants.push(domain_merchant); + } + for m in domain_merchants { + let merchant_id = m.merchant_id.clone(); + let updated_merchant_account = storage::MerchantAccountUpdate::Update { + merchant_name: m.merchant_name.clone(), + merchant_details: m.merchant_details.clone(), + return_url: None, + webhook_details: None, + sub_merchants_enabled: None, + parent_merchant_id: None, + primary_business_details: None, + enable_payment_response_hash: None, + payment_response_hash_key: None, + redirect_to_merchant_with_http_post: None, + routing_algorithm: None, + locker_id: None, + publishable_key: None, + metadata: None, + intent_fulfillment_time: None, + frm_routing_algorithm: None, + }; + crate::logger::warn!("Started for {}", merchant_id); + state + .update_merchant(m, updated_merchant_account) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + encrypt_merchant_connector_account_fields(state, &merchant_id).await?; + encrypt_customer_fields(state, &merchant_id).await?; + encrypt_address_fields(state, &merchant_id).await?; + crate::logger::warn!("Done for {}", merchant_id); + } + + Ok(()) +} + +pub async fn encrypt_merchant_connector_account_fields( + state: &Store, + merchant_id: &str, +) -> CustomResult<(), errors::ApiErrorResponse> { + crate::logger::warn!("Updating MerchantConnectorAccount for {}", merchant_id); + let conn = connection::pg_connection_write(state) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let merchants: Vec = generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + &conn, + mca_dsl::merchant_id.eq(merchant_id.to_string()), + None, + None, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let mut domain_merchants = Vec::with_capacity(merchants.len()); + for m in merchants.into_iter() { + let merchant_id = m.merchant_id.clone(); + let domain_merchant: domain::MerchantConnectorAccount = m + .convert(state, &merchant_id, state.get_migration_timestamp()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + domain_merchants.push(domain_merchant); + } + + conn.transaction_async::<(), async_bb8_diesel::ConnectionError, _, _>(|conn| async move { + for m in domain_merchants { + let updated_merchant_connector_account = + storage::MerchantConnectorAccountUpdate::Update { + merchant_id: None, + connector_name: None, + connector_type: None, + frm_configs: None, + test_mode: None, + disabled: None, + merchant_connector_id: None, + payment_methods_enabled: None, + metadata: None, + connector_account_details: Some(m.connector_account_details.clone()), + }; + + Conversion::convert(m) + .await + .map_err(|_| { + async_bb8_diesel::ConnectionError::Query( + diesel::result::Error::QueryBuilderError( + "Error while decrypting MerchantConnectorAccount".into(), + ), + ) + })? + .update(&conn, updated_merchant_connector_account.into()) + .await + .map_err(|_| { + async_bb8_diesel::ConnectionError::Query( + diesel::result::Error::QueryBuilderError( + "Error while updating MerchantConnectorAccount".into(), + ), + ) + })?; + } + Ok(()) + }) + .await + .into_report() + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + crate::logger::warn!( + "Done: Updating MerchantConnectorAccount for {}", + merchant_id + ); + Ok(()) +} + +pub async fn encrypt_customer_fields( + state: &Store, + merchant_id: &str, +) -> CustomResult<(), errors::ApiErrorResponse> { + crate::logger::warn!("Updating Customer for {}", merchant_id); + let conn = connection::pg_connection_write(state) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let merchants: Vec = generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + &conn, + cu_dsl::merchant_id.eq(merchant_id.to_string()), + None, + None, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let mut domain_merchants = Vec::with_capacity(merchants.len()); + for m in merchants.into_iter() { + let merchant_id = m.merchant_id.clone(); + let domain_merchant: domain::Customer = m + .convert(state, &merchant_id, state.get_migration_timestamp()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + domain_merchants.push(domain_merchant); + } + + conn.transaction_async::<(), async_bb8_diesel::ConnectionError, _, _>(|conn| async move { + for m in domain_merchants { + let update_customer = storage::CustomerUpdate::Update { + name: m.name.clone(), + email: m.email.clone(), + phone: m.phone.clone(), + description: None, + metadata: None, + phone_country_code: None, + connector_customer: None, + }; + + Customer::update_by_customer_id_merchant_id( + &conn, + m.customer_id.to_string(), + m.merchant_id.to_string(), + update_customer.into(), + ) + .await + .map_err(|_| { + async_bb8_diesel::ConnectionError::Query(diesel::result::Error::QueryBuilderError( + "Error while updating Customer".into(), + )) + })?; + } + + Ok(()) + }) + .await + .into_report() + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + crate::logger::warn!("Done: Updating Customer for {}", merchant_id); + Ok(()) +} + +pub async fn encrypt_address_fields( + state: &Store, + merchant_id: &str, +) -> CustomResult<(), errors::ApiErrorResponse> { + crate::logger::warn!("Updating Address for {}", merchant_id); + let conn = connection::pg_connection_write(state) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let merchants: Vec
= generic_filter::< +
::Table, + _, + <
::Table as Table>::PrimaryKey, + _, + >( + &conn, + ad_dsl::merchant_id.eq(merchant_id.to_string()), + None, + None, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let mut domain_merchants = Vec::with_capacity(merchants.len()); + for m in merchants.into_iter() { + let merchant_id = m.merchant_id.clone(); + let domain_merchant: domain::Address = m + .convert(state, &merchant_id, state.get_migration_timestamp()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + domain_merchants.push(domain_merchant); + } + + conn.transaction_async::<(), async_bb8_diesel::ConnectionError, _, _>(|conn| async move { + for m in domain_merchants { + let update_address = storage::address::AddressUpdate::Update { + line1: m.line1.clone(), + line2: m.line2.clone(), + line3: m.line3.clone(), + state: m.state.clone(), + zip: m.zip.clone(), + first_name: m.first_name.clone(), + last_name: m.last_name.clone(), + phone_number: m.phone_number.clone(), + city: None, + country: None, + country_code: None, + }; + + Address::update_by_address_id(&conn, m.address_id, update_address.into()) + .await + .map_err(|_| { + async_bb8_diesel::ConnectionError::Query( + diesel::result::Error::QueryBuilderError( + "Error while updating Address".into(), + ), + ) + })?; + } + Ok(()) + }) + .await + .into_report() + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + crate::logger::warn!("Done: Updating Address for {}", merchant_id); + Ok(()) +} + +/// +/// # Panics +/// +/// The functions runs at the start of the migration and tests, all the functional parts of +/// encryption. +/// +#[allow(clippy::unwrap_used)] +pub async fn test_2_step_encryption(store: &Store) { + use masking::ExposeInterface; + let (encrypted_merchant_key, master_key) = { + let master_key = store.get_master_key(); + let merchant_key: Vec = services::generate_aes256_key().unwrap().into(); + let encrypted_merchant_key = + types::encrypt::<_, crate::pii::WithType>(merchant_key.into(), master_key) + .await + .unwrap() + .into_encrypted(); + let encrypted_merchant_key = + storage_models::encryption::Encryption::new(encrypted_merchant_key); + (encrypted_merchant_key, master_key) + }; + + let dummy_data = "Hello, World!".to_string(); + let encrypted_dummy_data = storage_models::encryption::Encryption::new( + types::encrypt::<_, crate::pii::WithType>( + masking::Secret::new(dummy_data.clone()), + &types::decrypt::, crate::pii::WithType>( + Some(encrypted_merchant_key.clone()), + master_key, + 0, + 0, + ) + .await + .unwrap() + .unwrap() + .into_inner() + .expose(), + ) + .await + .unwrap() + .into_encrypted(), + ); + + let dummy_data_returned = types::decrypt::( + Some(encrypted_dummy_data), + &types::decrypt::, crate::pii::WithType>( + Some(encrypted_merchant_key), + master_key, + 0, + 0, + ) + .await + .unwrap() + .unwrap() + .into_inner() + .expose(), + 0, + 0, + ) + .await + .unwrap() + .unwrap() + .into_inner() + .expose(); + + assert!(dummy_data_returned == dummy_data) +} diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 6e5ff33572..42f9602d7a 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -6,6 +6,8 @@ pub mod logger; use std::sync::{atomic, Arc}; use error_stack::{IntoReport, ResultExt}; +#[cfg(feature = "kms")] +use external_services::kms; use redis_interface::{errors as redis_errors, PubsubInterface, RedisValue}; use tokio::sync::oneshot; @@ -108,6 +110,12 @@ pub trait RedisConnInterface { fn get_redis_conn(&self) -> Arc; } +impl RedisConnInterface for Store { + fn get_redis_conn(&self) -> Arc { + self.redis_conn.clone() + } +} + #[derive(Clone)] pub struct Store { pub master_pool: PgPool, @@ -116,6 +124,8 @@ pub struct Store { pub redis_conn: Arc, #[cfg(feature = "kv_store")] pub(crate) config: StoreConfig, + pub master_key: Vec, + pub migration_timestamp: i64, } #[cfg(feature = "kv_store")] @@ -149,6 +159,13 @@ impl Store { redis_clone.on_error(shut_down_signal).await; }); + let master_enc_key = get_master_enc_key( + config, + #[cfg(feature = "kms")] + &config.kms, + ) + .await; + Self { master_pool: diesel_make_pg_pool( &config.master_database, @@ -171,6 +188,8 @@ impl Store { drainer_stream_name: config.drainer.stream_name.clone(), drainer_num_partitions: config.drainer.num_partitions, }, + master_key: master_enc_key, + migration_timestamp: config.secrets.migration_encryption_timestamp, } } @@ -219,8 +238,36 @@ impl Store { } } -impl RedisConnInterface for Store { - fn get_redis_conn(&self) -> Arc { - self.redis_conn.clone() - } +#[allow(clippy::expect_used)] +async fn get_master_enc_key( + conf: &crate::configs::settings::Settings, + #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, +) -> Vec { + #[cfg(feature = "kms")] + let master_enc_key = hex::decode( + kms::get_kms_client(kms_config) + .await + .decrypt(&conf.secrets.master_enc_key) + .await + .expect("Failed to decrypt master enc key"), + ) + .expect("Failed to decode from hex"); + + #[cfg(not(feature = "kms"))] + let master_enc_key = + hex::decode(&conf.secrets.master_enc_key).expect("Failed to decode from hex"); + + master_enc_key +} + +#[inline] +pub fn generate_aes256_key() -> errors::CustomResult<[u8; 32], common_utils::errors::CryptoError> { + use ring::rand::SecureRandom; + + let rng = ring::rand::SystemRandom::new(); + let mut key: [u8; 256 / 8] = [0_u8; 256 / 8]; + rng.fill(&mut key) + .into_report() + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + Ok(key) } diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index ca66993985..ff7f0220b2 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -17,7 +17,7 @@ use crate::{ db::StorageInterface, routes::app::AppStateInfo, services::api, - types::storage, + types::domain, utils::OptionExt, }; @@ -31,7 +31,7 @@ impl AuthInfo for () { } } -impl AuthInfo for storage::MerchantAccount { +impl AuthInfo for domain::MerchantAccount { fn get_merchant_id(&self) -> Option<&str> { Some(&self.merchant_id) } @@ -70,7 +70,7 @@ where } #[async_trait] -impl AuthenticateAndFetch for ApiKeyAuth +impl AuthenticateAndFetch for ApiKeyAuth where A: AppStateInfo + Sync, { @@ -78,7 +78,7 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult { let api_key = get_api_key(request_headers) .change_context(errors::ApiErrorResponse::Unauthorized)? .trim(); @@ -194,7 +194,7 @@ where pub struct MerchantIdAuth(pub String); #[async_trait] -impl AuthenticateAndFetch for MerchantIdAuth +impl AuthenticateAndFetch for MerchantIdAuth where A: AppStateInfo + Sync, { @@ -202,7 +202,7 @@ where &self, _request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult { state .store() .find_merchant_account_by_merchant_id(self.0.as_ref()) @@ -221,7 +221,7 @@ where pub struct PublishableKeyAuth; #[async_trait] -impl AuthenticateAndFetch for PublishableKeyAuth +impl AuthenticateAndFetch for PublishableKeyAuth where A: AppStateInfo + Sync, { @@ -229,7 +229,7 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult { let publishable_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; state @@ -279,7 +279,7 @@ struct JwtAuthPayloadFetchMerchantAccount { } #[async_trait] -impl AuthenticateAndFetch for JWTAuth +impl AuthenticateAndFetch for JWTAuth where A: AppStateInfo + Sync, { @@ -287,7 +287,7 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult { let mut token = get_jwt(request_headers)?; token = strip_jwt_token(token)?; let payload = decode_jwt::(token, state).await?; @@ -337,7 +337,7 @@ where pub fn get_auth_type_and_flow( headers: &HeaderMap, ) -> RouterResult<( - Box>, + Box>, api::AuthFlow, )> { let api_key = get_api_key(headers)?; @@ -352,13 +352,13 @@ pub fn check_client_secret_and_get_auth( headers: &HeaderMap, payload: &impl ClientSecretFetch, ) -> RouterResult<( - Box>, + Box>, api::AuthFlow, )> where T: AppStateInfo, - ApiKeyAuth: AuthenticateAndFetch, - PublishableKeyAuth: AuthenticateAndFetch, + ApiKeyAuth: AuthenticateAndFetch, + PublishableKeyAuth: AuthenticateAndFetch, { let api_key = get_api_key(headers)?; @@ -386,7 +386,7 @@ pub async fn is_ephemeral_auth( headers: &HeaderMap, db: &dyn StorageInterface, customer_id: &str, -) -> RouterResult>> { +) -> RouterResult>> { let api_key = get_api_key(headers)?; if !api_key.starts_with("epk") { diff --git a/crates/router/src/services/encryption.rs b/crates/router/src/services/encryption.rs index 39ad3ee995..9079afceac 100644 --- a/crates/router/src/services/encryption.rs +++ b/crates/router/src/services/encryption.rs @@ -241,6 +241,7 @@ mod tests { } #[actix_rt::test] + #[ignore] async fn test_jwe() { let conf = settings::Settings::new().unwrap(); let jwt = encrypt_jwe( @@ -262,6 +263,7 @@ mod tests { } #[actix_rt::test] + #[ignore] async fn test_jws() { let conf = settings::Settings::new().unwrap(); let jwt = jws_sign_payload("jws payload".as_bytes(), "1", conf.jwekey.vault_private_key) diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 347adcc92f..fde4204845 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -7,6 +7,7 @@ // Separation of concerns instead of separation of forms. pub mod api; +pub mod domain; pub mod storage; pub mod transformers; diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index a2b03d1ccf..1695f88a36 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -7,14 +7,11 @@ pub use api_models::admin::{ }; use common_utils::ext_traits::ValueExt; -use crate::{ - core::errors, - types::{storage, transformers::ForeignTryFrom}, -}; +use crate::{core::errors, types::domain}; -impl ForeignTryFrom for MerchantAccountResponse { +impl TryFrom for MerchantAccountResponse { type Error = error_stack::Report; - fn foreign_try_from(item: storage::MerchantAccount) -> Result { + fn try_from(item: domain::MerchantAccount) -> Result { let primary_business_details: Vec = item .primary_business_details .parse_value("primary_business_details")?; diff --git a/crates/router/src/types/api/customers.rs b/crates/router/src/types/api/customers.rs index 904e223c12..6878ea8360 100644 --- a/crates/router/src/types/api/customers.rs +++ b/crates/router/src/types/api/customers.rs @@ -2,15 +2,19 @@ use api_models::customers; pub use api_models::customers::{CustomerDeleteResponse, CustomerId, CustomerRequest}; use serde::Serialize; -use crate::{newtype, types::storage}; +use crate::{core::errors::RouterResult, newtype, types::domain}; newtype!( pub CustomerResponse = customers::CustomerResponse, derives = (Debug, Clone, Serialize) ); -impl From for CustomerResponse { - fn from(cust: storage::Customer) -> Self { +pub(crate) trait CustomerRequestExt: Sized { + fn validate(self) -> RouterResult; +} + +impl From for CustomerResponse { + fn from(cust: domain::Customer) -> Self { customers::CustomerResponse { customer_id: cust.customer_id, name: cust.name, diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs index 21412f22bf..d2d93a6397 100644 --- a/crates/router/src/types/api/mandates.rs +++ b/crates/router/src/types/api/mandates.rs @@ -11,7 +11,7 @@ use crate::{ newtype, routes::AppState, types::{ - api, + api, domain, storage::{self, enums as storage_enums}, transformers::ForeignInto, }, @@ -27,7 +27,7 @@ pub(crate) trait MandateResponseExt: Sized { async fn from_db_mandate( state: &AppState, mandate: storage::Mandate, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult; } @@ -36,7 +36,7 @@ impl MandateResponseExt for MandateResponse { async fn from_db_mandate( state: &AppState, mandate: storage::Mandate, - merchant_account: &storage::MerchantAccount, + merchant_account: &domain::MerchantAccount, ) -> RouterResult { let db = &*state.store; let payment_method = db diff --git a/crates/router/src/types/api/webhooks.rs b/crates/router/src/types/api/webhooks.rs index 7cfc61bdf5..a14b9ba044 100644 --- a/crates/router/src/types/api/webhooks.rs +++ b/crates/router/src/types/api/webhooks.rs @@ -60,7 +60,7 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; algorithm - .decode_message(&secret, &message) + .decode_message(&secret, message) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) } diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs new file mode 100644 index 0000000000..2c9b13d8a3 --- /dev/null +++ b/crates/router/src/types/domain.rs @@ -0,0 +1,12 @@ +mod address; +pub mod behaviour; +mod customer; +mod merchant_account; +mod merchant_connector_account; +pub mod merchant_key_store; +pub mod types; + +pub use address::*; +pub use customer::*; +pub use merchant_account::*; +pub use merchant_connector_account::*; diff --git a/crates/router/src/types/domain/address.rs b/crates/router/src/types/domain/address.rs new file mode 100644 index 0000000000..ed3851ea8f --- /dev/null +++ b/crates/router/src/types/domain/address.rs @@ -0,0 +1,191 @@ +use async_trait::async_trait; +use common_utils::{ + crypto::{self}, + date_time, + errors::{CustomResult, ValidationError}, +}; +use error_stack::ResultExt; +use storage_models::{address::AddressUpdateInternal, encryption::Encryption, enums}; +use time::{OffsetDateTime, PrimitiveDateTime}; + +use super::{ + behaviour, + types::{self, AsyncLift}, +}; +use crate::db::StorageInterface; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct Address { + #[serde(skip_serializing)] + pub id: Option, + #[serde(skip_serializing)] + pub address_id: String, + pub city: Option, + pub country: Option, + pub line1: crypto::OptionalEncryptableSecretString, + pub line2: crypto::OptionalEncryptableSecretString, + pub line3: crypto::OptionalEncryptableSecretString, + pub state: crypto::OptionalEncryptableSecretString, + pub zip: crypto::OptionalEncryptableSecretString, + pub first_name: crypto::OptionalEncryptableSecretString, + pub last_name: crypto::OptionalEncryptableSecretString, + pub phone_number: crypto::OptionalEncryptableSecretString, + pub country_code: Option, + #[serde(skip_serializing)] + #[serde(with = "custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(skip_serializing)] + #[serde(with = "custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub customer_id: String, + pub merchant_id: String, +} + +#[async_trait] +impl behaviour::Conversion for Address { + type DstType = storage_models::address::Address; + type NewDstType = storage_models::address::AddressNew; + + async fn convert(self) -> CustomResult { + Ok(storage_models::address::Address { + id: self.id.ok_or(ValidationError::MissingRequiredField { + field_name: "id".to_string(), + })?, + address_id: self.address_id, + city: self.city, + country: self.country, + line1: self.line1.map(Encryption::from), + line2: self.line2.map(Encryption::from), + line3: self.line3.map(Encryption::from), + state: self.state.map(Encryption::from), + zip: self.zip.map(Encryption::from), + first_name: self.first_name.map(Encryption::from), + last_name: self.last_name.map(Encryption::from), + phone_number: self.phone_number.map(Encryption::from), + country_code: self.country_code, + created_at: self.created_at, + modified_at: self.modified_at, + customer_id: self.customer_id, + merchant_id: self.merchant_id, + }) + } + + async fn convert_back( + other: Self::DstType, + db: &dyn StorageInterface, + merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult { + let key = types::get_merchant_enc_key(db, merchant_id) + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while getting key from key store".to_string(), + })?; + + async { + let modified_at = other.modified_at.assume_utc().unix_timestamp(); + let inner_decrypt = + |inner| types::decrypt(inner, &key, modified_at, migration_timestamp); + Ok(Self { + id: Some(other.id), + address_id: other.address_id, + city: other.city, + country: other.country, + line1: other.line1.async_lift(inner_decrypt).await?, + line2: other.line2.async_lift(inner_decrypt).await?, + line3: other.line3.async_lift(inner_decrypt).await?, + state: other.state.async_lift(inner_decrypt).await?, + zip: other.zip.async_lift(inner_decrypt).await?, + first_name: other.first_name.async_lift(inner_decrypt).await?, + last_name: other.last_name.async_lift(inner_decrypt).await?, + phone_number: other.phone_number.async_lift(inner_decrypt).await?, + country_code: other.country_code, + created_at: other.created_at, + modified_at: other.modified_at, + customer_id: other.customer_id, + merchant_id: other.merchant_id, + }) + } + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting".to_string(), + }) + } + + async fn construct_new(self) -> CustomResult { + common_utils::fp_utils::when(self.id.is_some(), || { + Err(ValidationError::InvalidValue { + message: "id present while creating a new database entry".to_string(), + }) + })?; + let now = date_time::now(); + Ok(Self::NewDstType { + address_id: self.address_id, + city: self.city, + country: self.country, + line1: self.line1.map(Encryption::from), + line2: self.line2.map(Encryption::from), + line3: self.line3.map(Encryption::from), + state: self.state.map(Encryption::from), + zip: self.zip.map(Encryption::from), + first_name: self.first_name.map(Encryption::from), + last_name: self.last_name.map(Encryption::from), + phone_number: self.phone_number.map(Encryption::from), + country_code: self.country_code, + customer_id: self.customer_id, + merchant_id: self.merchant_id, + created_at: now, + modified_at: now, + }) + } +} + +#[derive(Debug, frunk::LabelledGeneric)] +pub enum AddressUpdate { + Update { + city: Option, + country: Option, + line1: crypto::OptionalEncryptableSecretString, + line2: crypto::OptionalEncryptableSecretString, + line3: crypto::OptionalEncryptableSecretString, + state: crypto::OptionalEncryptableSecretString, + zip: crypto::OptionalEncryptableSecretString, + first_name: crypto::OptionalEncryptableSecretString, + last_name: crypto::OptionalEncryptableSecretString, + phone_number: crypto::OptionalEncryptableSecretString, + country_code: Option, + }, +} + +impl From for AddressUpdateInternal { + fn from(address_update: AddressUpdate) -> Self { + match address_update { + AddressUpdate::Update { + city, + country, + line1, + line2, + line3, + state, + zip, + first_name, + last_name, + phone_number, + country_code, + } => Self { + city, + country, + line1: line1.map(Encryption::from), + line2: line2.map(Encryption::from), + line3: line3.map(Encryption::from), + state: state.map(Encryption::from), + zip: zip.map(Encryption::from), + first_name: first_name.map(Encryption::from), + last_name: last_name.map(Encryption::from), + phone_number: phone_number.map(Encryption::from), + country_code, + modified_at: date_time::convert_to_pdt(OffsetDateTime::now_utc()), + }, + } + } +} diff --git a/crates/router/src/types/domain/behaviour.rs b/crates/router/src/types/domain/behaviour.rs new file mode 100644 index 0000000000..5046622f6d --- /dev/null +++ b/crates/router/src/types/domain/behaviour.rs @@ -0,0 +1,44 @@ +use common_utils::errors::{CustomResult, ValidationError}; + +use crate::db::StorageInterface; + +/// Trait for converting domain types to storage models +#[async_trait::async_trait] +pub trait Conversion { + type DstType; + type NewDstType; + async fn convert(self) -> CustomResult; + + async fn convert_back( + item: Self::DstType, + db: &dyn StorageInterface, + merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult + where + Self: Sized; + + async fn construct_new(self) -> CustomResult; +} + +#[async_trait::async_trait] +pub trait ReverseConversion { + async fn convert( + self, + db: &dyn StorageInterface, + merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl> ReverseConversion for T { + async fn convert( + self, + db: &dyn StorageInterface, + merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult { + U::convert_back(self, db, merchant_id, migration_timestamp).await + } +} diff --git a/crates/router/src/types/domain/customer.rs b/crates/router/src/types/domain/customer.rs new file mode 100644 index 0000000000..060a15928d --- /dev/null +++ b/crates/router/src/types/domain/customer.rs @@ -0,0 +1,158 @@ +use common_utils::{ + crypto::{self}, + date_time, pii, +}; +use error_stack::ResultExt; +use storage_models::{customers::CustomerUpdateInternal, encryption::Encryption}; +use time::PrimitiveDateTime; + +use super::types::{self, AsyncLift}; +use crate::{ + db::StorageInterface, + errors::{CustomResult, ValidationError}, +}; + +#[derive(Clone, Debug)] +pub struct Customer { + pub id: Option, + pub customer_id: String, + pub merchant_id: String, + pub name: crypto::OptionalEncryptableName, + pub email: crypto::OptionalEncryptableEmail, + pub phone: crypto::OptionalEncryptablePhone, + pub phone_country_code: Option, + pub description: Option, + pub created_at: PrimitiveDateTime, + pub metadata: Option, + pub modified_at: PrimitiveDateTime, + pub connector_customer: Option, +} + +#[async_trait::async_trait] +impl super::behaviour::Conversion for Customer { + type DstType = storage_models::customers::Customer; + type NewDstType = storage_models::customers::CustomerNew; + async fn convert(self) -> CustomResult { + Ok(storage_models::customers::Customer { + id: self.id.ok_or(ValidationError::MissingRequiredField { + field_name: "id".to_string(), + })?, + customer_id: self.customer_id, + merchant_id: self.merchant_id, + name: self.name.map(|value| value.into()), + email: self.email.map(|value| value.into()), + phone: self.phone.map(Encryption::from), + phone_country_code: self.phone_country_code, + description: self.description, + created_at: self.created_at, + metadata: self.metadata, + modified_at: self.modified_at, + connector_customer: self.connector_customer, + }) + } + + async fn convert_back( + item: Self::DstType, + db: &dyn StorageInterface, + merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult + where + Self: Sized, + { + let key = types::get_merchant_enc_key(db, merchant_id) + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while getting key from key store".to_string(), + })?; + async { + let modified_at = item.modified_at.assume_utc().unix_timestamp(); + + let inner_decrypt = + |inner| types::decrypt(inner, &key, modified_at, migration_timestamp); + let inner_decrypt_email = + |inner| types::decrypt(inner, &key, modified_at, migration_timestamp); + Ok(Self { + id: Some(item.id), + customer_id: item.customer_id, + merchant_id: item.merchant_id, + name: item.name.async_lift(inner_decrypt).await?, + email: item.email.async_lift(inner_decrypt_email).await?, + phone: item.phone.async_lift(inner_decrypt).await?, + phone_country_code: item.phone_country_code, + description: item.description, + created_at: item.created_at, + metadata: item.metadata, + modified_at: item.modified_at, + connector_customer: item.connector_customer, + }) + } + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting customer data".to_string(), + }) + } + + async fn construct_new(self) -> CustomResult { + let now = date_time::now(); + Ok(storage_models::customers::CustomerNew { + customer_id: self.customer_id, + merchant_id: self.merchant_id, + name: self.name.map(Encryption::from), + email: self.email.map(Encryption::from), + phone: self.phone.map(Encryption::from), + description: self.description, + phone_country_code: self.phone_country_code, + metadata: self.metadata, + created_at: now, + modified_at: now, + connector_customer: self.connector_customer, + }) + } +} + +#[derive(Debug)] +pub enum CustomerUpdate { + Update { + name: crypto::OptionalEncryptableName, + email: crypto::OptionalEncryptableEmail, + phone: crypto::OptionalEncryptablePhone, + description: Option, + phone_country_code: Option, + metadata: Option, + connector_customer: Option, + }, + ConnectorCustomer { + connector_customer: Option, + }, +} + +impl From for CustomerUpdateInternal { + fn from(customer_update: CustomerUpdate) -> Self { + match customer_update { + CustomerUpdate::Update { + name, + email, + phone, + description, + phone_country_code, + metadata, + connector_customer, + } => Self { + name: name.map(Encryption::from), + email: email.map(Encryption::from), + phone: phone.map(Encryption::from), + description, + phone_country_code, + metadata, + connector_customer, + modified_at: Some(date_time::now()), + }, + CustomerUpdate::ConnectorCustomer { connector_customer } => Self { + connector_customer, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + } + } +} diff --git a/crates/router/src/types/domain/merchant_account.rs b/crates/router/src/types/domain/merchant_account.rs new file mode 100644 index 0000000000..10106b1afe --- /dev/null +++ b/crates/router/src/types/domain/merchant_account.rs @@ -0,0 +1,228 @@ +use common_utils::{ + crypto::{OptionalEncryptableName, OptionalEncryptableValue}, + date_time, pii, +}; +use error_stack::ResultExt; +use storage_models::{ + encryption::Encryption, enums, merchant_account::MerchantAccountUpdateInternal, +}; + +use crate::{ + db::StorageInterface, + errors::{CustomResult, ValidationError}, + types::domain::types::{self, AsyncLift}, +}; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct MerchantAccount { + pub id: Option, + pub merchant_id: String, + pub return_url: Option, + pub enable_payment_response_hash: bool, + pub payment_response_hash_key: Option, + pub redirect_to_merchant_with_http_post: bool, + pub merchant_name: OptionalEncryptableName, + pub merchant_details: OptionalEncryptableValue, + pub webhook_details: Option, + pub sub_merchants_enabled: Option, + pub parent_merchant_id: Option, + pub publishable_key: Option, + pub storage_scheme: enums::MerchantStorageScheme, + pub locker_id: Option, + pub metadata: Option, + pub routing_algorithm: Option, + pub primary_business_details: serde_json::Value, + pub frm_routing_algorithm: Option, + pub created_at: time::PrimitiveDateTime, + pub modified_at: time::PrimitiveDateTime, + pub intent_fulfillment_time: Option, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +pub enum MerchantAccountUpdate { + Update { + merchant_name: OptionalEncryptableName, + merchant_details: OptionalEncryptableValue, + return_url: Option, + webhook_details: Option, + sub_merchants_enabled: Option, + parent_merchant_id: Option, + enable_payment_response_hash: Option, + payment_response_hash_key: Option, + redirect_to_merchant_with_http_post: Option, + publishable_key: Option, + locker_id: Option, + metadata: Option, + routing_algorithm: Option, + primary_business_details: Option, + intent_fulfillment_time: Option, + frm_routing_algorithm: Option, + }, + StorageSchemeUpdate { + storage_scheme: enums::MerchantStorageScheme, + }, +} + +impl From for MerchantAccountUpdateInternal { + fn from(merchant_account_update: MerchantAccountUpdate) -> Self { + match merchant_account_update { + MerchantAccountUpdate::Update { + merchant_name, + merchant_details, + return_url, + webhook_details, + routing_algorithm, + sub_merchants_enabled, + parent_merchant_id, + enable_payment_response_hash, + payment_response_hash_key, + redirect_to_merchant_with_http_post, + publishable_key, + locker_id, + metadata, + primary_business_details, + intent_fulfillment_time, + frm_routing_algorithm, + } => Self { + merchant_name: merchant_name.map(Encryption::from), + merchant_details: merchant_details.map(Encryption::from), + frm_routing_algorithm, + return_url, + webhook_details, + routing_algorithm, + sub_merchants_enabled, + parent_merchant_id, + enable_payment_response_hash, + payment_response_hash_key, + redirect_to_merchant_with_http_post, + publishable_key, + locker_id, + metadata, + primary_business_details, + modified_at: Some(date_time::now()), + intent_fulfillment_time, + ..Default::default() + }, + MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self { + storage_scheme: Some(storage_scheme), + modified_at: Some(date_time::now()), + ..Default::default() + }, + } + } +} + +#[async_trait::async_trait] +impl super::behaviour::Conversion for MerchantAccount { + type DstType = storage_models::merchant_account::MerchantAccount; + type NewDstType = storage_models::merchant_account::MerchantAccountNew; + async fn convert(self) -> CustomResult { + Ok(storage_models::merchant_account::MerchantAccount { + id: self.id.ok_or(ValidationError::MissingRequiredField { + field_name: "id".to_string(), + })?, + merchant_id: self.merchant_id, + return_url: self.return_url, + enable_payment_response_hash: self.enable_payment_response_hash, + payment_response_hash_key: self.payment_response_hash_key, + redirect_to_merchant_with_http_post: self.redirect_to_merchant_with_http_post, + merchant_name: self.merchant_name.map(|name| name.into()), + merchant_details: self.merchant_details.map(|details| details.into()), + webhook_details: self.webhook_details, + sub_merchants_enabled: self.sub_merchants_enabled, + parent_merchant_id: self.parent_merchant_id, + publishable_key: self.publishable_key, + storage_scheme: self.storage_scheme, + locker_id: self.locker_id, + metadata: self.metadata, + routing_algorithm: self.routing_algorithm, + primary_business_details: self.primary_business_details, + created_at: self.created_at, + modified_at: self.modified_at, + intent_fulfillment_time: self.intent_fulfillment_time, + frm_routing_algorithm: self.frm_routing_algorithm, + }) + } + + async fn convert_back( + item: Self::DstType, + db: &dyn StorageInterface, + merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult + where + Self: Sized, + { + let key = types::get_merchant_enc_key(db, merchant_id.to_owned()) + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while getting key from key store".to_string(), + })?; + async { + let modified_at = item.modified_at.assume_utc().unix_timestamp(); + Ok(Self { + id: Some(item.id), + merchant_id: item.merchant_id, + return_url: item.return_url, + enable_payment_response_hash: item.enable_payment_response_hash, + payment_response_hash_key: item.payment_response_hash_key, + redirect_to_merchant_with_http_post: item.redirect_to_merchant_with_http_post, + merchant_name: item + .merchant_name + .async_lift(|inner| { + types::decrypt(inner, &key, modified_at, migration_timestamp) + }) + .await?, + merchant_details: item + .merchant_details + .async_lift(|inner| { + types::decrypt(inner, &key, modified_at, migration_timestamp) + }) + .await?, + webhook_details: item.webhook_details, + sub_merchants_enabled: item.sub_merchants_enabled, + parent_merchant_id: item.parent_merchant_id, + publishable_key: item.publishable_key, + storage_scheme: item.storage_scheme, + locker_id: item.locker_id, + metadata: item.metadata, + routing_algorithm: item.routing_algorithm, + frm_routing_algorithm: item.frm_routing_algorithm, + primary_business_details: item.primary_business_details, + created_at: item.created_at, + modified_at: item.modified_at, + intent_fulfillment_time: item.intent_fulfillment_time, + }) + } + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting merchant data".to_string(), + }) + } + + async fn construct_new(self) -> CustomResult { + let now = date_time::now(); + Ok(storage_models::merchant_account::MerchantAccountNew { + merchant_id: self.merchant_id, + merchant_name: self.merchant_name.map(Encryption::from), + merchant_details: self.merchant_details.map(Encryption::from), + return_url: self.return_url, + webhook_details: self.webhook_details, + sub_merchants_enabled: self.sub_merchants_enabled, + parent_merchant_id: self.parent_merchant_id, + enable_payment_response_hash: Some(self.enable_payment_response_hash), + payment_response_hash_key: self.payment_response_hash_key, + redirect_to_merchant_with_http_post: Some(self.redirect_to_merchant_with_http_post), + publishable_key: self.publishable_key, + locker_id: self.locker_id, + metadata: self.metadata, + routing_algorithm: self.routing_algorithm, + primary_business_details: self.primary_business_details, + created_at: now, + modified_at: now, + intent_fulfillment_time: self.intent_fulfillment_time, + frm_routing_algorithm: self.frm_routing_algorithm, + }) + } +} diff --git a/crates/router/src/types/domain/merchant_connector_account.rs b/crates/router/src/types/domain/merchant_connector_account.rs new file mode 100644 index 0000000000..a0a1a8a3e1 --- /dev/null +++ b/crates/router/src/types/domain/merchant_connector_account.rs @@ -0,0 +1,185 @@ +use common_utils::{ + crypto::{Encryptable, GcmAes256}, + date_time, + errors::{CustomResult, ValidationError}, + pii, +}; +use error_stack::ResultExt; +use masking::Secret; +use storage_models::{ + encryption::Encryption, enums, + merchant_connector_account::MerchantConnectorAccountUpdateInternal, +}; + +use super::{ + behaviour, + types::{self, TypeEncryption}, +}; +use crate::db::StorageInterface; + +#[derive(Clone, Debug)] +pub struct MerchantConnectorAccount { + pub id: Option, + pub merchant_id: String, + pub connector_name: String, + pub connector_account_details: Encryptable>, + pub test_mode: Option, + pub disabled: Option, + pub merchant_connector_id: String, + pub payment_methods_enabled: Option>, + pub connector_type: enums::ConnectorType, + pub metadata: Option, + pub frm_configs: Option>, //Option + pub connector_label: String, + pub business_country: enums::CountryAlpha2, + pub business_label: String, + pub business_sub_label: Option, + pub created_at: time::PrimitiveDateTime, + pub modified_at: time::PrimitiveDateTime, +} + +#[derive(Debug)] +pub enum MerchantConnectorAccountUpdate { + Update { + merchant_id: Option, + connector_type: Option, + connector_name: Option, + connector_account_details: Option>>, + test_mode: Option, + disabled: Option, + merchant_connector_id: Option, + payment_methods_enabled: Option>, + metadata: Option, + frm_configs: Option>, + }, +} + +#[async_trait::async_trait] +impl behaviour::Conversion for MerchantConnectorAccount { + type DstType = storage_models::merchant_connector_account::MerchantConnectorAccount; + type NewDstType = storage_models::merchant_connector_account::MerchantConnectorAccountNew; + + async fn convert(self) -> CustomResult { + Ok( + storage_models::merchant_connector_account::MerchantConnectorAccount { + id: self.id.ok_or(ValidationError::MissingRequiredField { + field_name: "id".to_string(), + })?, + merchant_id: self.merchant_id, + connector_name: self.connector_name, + connector_account_details: self.connector_account_details.into(), + test_mode: self.test_mode, + disabled: self.disabled, + merchant_connector_id: self.merchant_connector_id, + payment_methods_enabled: self.payment_methods_enabled, + connector_type: self.connector_type, + metadata: self.metadata, + frm_configs: self.frm_configs, + business_country: self.business_country, + business_label: self.business_label, + connector_label: self.connector_label, + business_sub_label: self.business_sub_label, + created_at: self.created_at, + modified_at: self.modified_at, + }, + ) + } + + async fn convert_back( + other: Self::DstType, + db: &dyn StorageInterface, + merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult { + let key = types::get_merchant_enc_key(db, merchant_id) + .await + .change_context(ValidationError::InvalidValue { + message: "Error while getting key from keystore".to_string(), + })?; + let modified_at = other.modified_at.assume_utc().unix_timestamp(); + + Ok(Self { + id: Some(other.id), + merchant_id: other.merchant_id, + connector_name: other.connector_name, + connector_account_details: Encryptable::decrypt( + other.connector_account_details, + &key, + GcmAes256 {}, + modified_at, + migration_timestamp, + ) + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting connector account details".to_string(), + })?, + test_mode: other.test_mode, + disabled: other.disabled, + merchant_connector_id: other.merchant_connector_id, + payment_methods_enabled: other.payment_methods_enabled, + connector_type: other.connector_type, + metadata: other.metadata, + + frm_configs: other.frm_configs, + business_country: other.business_country, + business_label: other.business_label, + connector_label: other.connector_label, + business_sub_label: other.business_sub_label, + created_at: other.created_at, + modified_at: other.modified_at, + }) + } + + async fn construct_new(self) -> CustomResult { + let now = date_time::now(); + Ok(Self::NewDstType { + merchant_id: Some(self.merchant_id), + connector_name: Some(self.connector_name), + connector_account_details: Some(self.connector_account_details.into()), + test_mode: self.test_mode, + disabled: self.disabled, + merchant_connector_id: self.merchant_connector_id, + payment_methods_enabled: self.payment_methods_enabled, + connector_type: Some(self.connector_type), + metadata: self.metadata, + frm_configs: self.frm_configs, + business_country: self.business_country, + business_label: self.business_label, + connector_label: self.connector_label, + business_sub_label: self.business_sub_label, + created_at: now, + modified_at: now, + }) + } +} + +impl From for MerchantConnectorAccountUpdateInternal { + fn from(merchant_connector_account_update: MerchantConnectorAccountUpdate) -> Self { + match merchant_connector_account_update { + MerchantConnectorAccountUpdate::Update { + merchant_id, + connector_type, + connector_name, + connector_account_details, + test_mode, + disabled, + merchant_connector_id, + payment_methods_enabled, + metadata, + frm_configs, + } => Self { + merchant_id, + connector_type, + connector_name, + connector_account_details: connector_account_details.map(Encryption::from), + test_mode, + disabled, + merchant_connector_id, + payment_methods_enabled, + metadata, + frm_configs, + modified_at: Some(common_utils::date_time::now()), + }, + } + } +} diff --git a/crates/router/src/types/domain/merchant_key_store.rs b/crates/router/src/types/domain/merchant_key_store.rs new file mode 100644 index 0000000000..efe4fb8403 --- /dev/null +++ b/crates/router/src/types/domain/merchant_key_store.rs @@ -0,0 +1,63 @@ +use common_utils::{ + crypto::{Encryptable, GcmAes256}, + custom_serde, date_time, +}; +use error_stack::ResultExt; +use masking::Secret; +use time::PrimitiveDateTime; + +use crate::{ + db::StorageInterface, + errors::{CustomResult, ValidationError}, + types::domain::types::TypeEncryption, +}; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct MerchantKeyStore { + pub merchant_id: String, + pub key: Encryptable>>, + #[serde(with = "custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, +} + +#[async_trait::async_trait] +impl super::behaviour::Conversion for MerchantKeyStore { + type DstType = storage_models::merchant_key_store::MerchantKeyStore; + type NewDstType = storage_models::merchant_key_store::MerchantKeyStoreNew; + async fn convert(self) -> CustomResult { + Ok(storage_models::merchant_key_store::MerchantKeyStore { + key: self.key.into(), + merchant_id: self.merchant_id, + created_at: self.created_at, + }) + } + + async fn convert_back( + item: Self::DstType, + db: &dyn StorageInterface, + _merchant_id: &str, + migration_timestamp: i64, + ) -> CustomResult + where + Self: Sized, + { + let key = &db.get_master_key(); + Ok(Self { + key: Encryptable::decrypt(item.key, key, GcmAes256 {}, i64::MAX, migration_timestamp) + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting customer data".to_string(), + })?, + merchant_id: item.merchant_id, + created_at: item.created_at, + }) + } + + async fn construct_new(self) -> CustomResult { + Ok(storage_models::merchant_key_store::MerchantKeyStoreNew { + merchant_id: self.merchant_id, + key: self.key.into(), + created_at: date_time::now(), + }) + } +} diff --git a/crates/router/src/types/domain/types.rs b/crates/router/src/types/domain/types.rs new file mode 100644 index 0000000000..aa6b63af06 --- /dev/null +++ b/crates/router/src/types/domain/types.rs @@ -0,0 +1,287 @@ +use async_trait::async_trait; +use common_utils::{ + crypto, + errors::{self, CustomResult}, + ext_traits::AsyncExt, +}; +use error_stack::{IntoReport, ResultExt}; +use masking::{ExposeInterface, PeekInterface, Secret}; +use router_env::{instrument, tracing}; +use storage_models::encryption::Encryption; + +use crate::routes::metrics::{request, DECRYPTION_TIME, ENCRYPTION_TIME}; + +#[async_trait] +pub trait TypeEncryption< + T, + V: crypto::EncodeMessage + crypto::DecodeMessage, + S: masking::Strategy, +>: Sized +{ + async fn encrypt( + masked_data: Secret, + key: &[u8], + crypt_algo: V, + ) -> CustomResult; + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + timestamp: i64, + migration_timestamp: i64, + ) -> CustomResult; +} + +#[async_trait] +impl< + V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, + S: masking::Strategy + Send, + > TypeEncryption for crypto::Encryptable> +{ + #[instrument(skip_all)] + async fn encrypt( + masked_data: Secret, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let encrypted_data = crypt_algo.encode_message(key, masked_data.peek().as_bytes())?; + + Ok(Self::new(masked_data, encrypted_data)) + } + + #[instrument(skip_all)] + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + timestamp: i64, + migration_timestamp: i64, + ) -> CustomResult { + let encrypted = encrypted_data.into_inner(); + + let (data, encrypted) = if timestamp < migration_timestamp { + ( + encrypted.clone(), + crypt_algo.encode_message(key, &encrypted)?, + ) + } else { + ( + crypt_algo.decode_message(key, encrypted.clone())?, + encrypted, + ) + }; + + let value: String = std::str::from_utf8(&data) + .into_report() + .change_context(errors::CryptoError::DecodingFailed)? + .to_string(); + + Ok(Self::new(value.into(), encrypted)) + } +} + +#[async_trait] +impl< + V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, + S: masking::Strategy + Send, + > TypeEncryption + for crypto::Encryptable> +{ + #[instrument(skip_all)] + async fn encrypt( + masked_data: Secret, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let data = serde_json::to_vec(&masked_data.peek()) + .into_report() + .change_context(errors::CryptoError::DecodingFailed)?; + let encrypted_data = crypt_algo.encode_message(key, &data)?; + + Ok(Self::new(masked_data, encrypted_data)) + } + + #[instrument(skip_all)] + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + timestamp: i64, + migration_timestamp: i64, + ) -> CustomResult { + let encrypted = encrypted_data.into_inner(); + let (data, encrypted) = if timestamp < migration_timestamp { + ( + encrypted.clone(), + crypt_algo.encode_message(key, &encrypted)?, + ) + } else { + ( + crypt_algo.decode_message(key, encrypted.clone())?, + encrypted, + ) + }; + + let value: serde_json::Value = serde_json::from_slice(&data) + .into_report() + .change_context(errors::CryptoError::DecodingFailed)?; + + Ok(Self::new(value.into(), encrypted)) + } +} + +#[async_trait] +impl< + V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, + S: masking::Strategy> + Send, + > TypeEncryption, V, S> for crypto::Encryptable, S>> +{ + #[instrument(skip_all)] + async fn encrypt( + masked_data: Secret, S>, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let encrypted_data = crypt_algo.encode_message(key, masked_data.peek())?; + + Ok(Self::new(masked_data, encrypted_data)) + } + + #[instrument(skip_all)] + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + timestamp: i64, + migration_timestamp: i64, + ) -> CustomResult { + let encrypted = encrypted_data.into_inner(); + + let (data, encrypted) = if timestamp < migration_timestamp { + ( + encrypted.clone(), + crypt_algo.encode_message(key, &encrypted)?, + ) + } else { + ( + crypt_algo.decode_message(key, encrypted.clone())?, + encrypted, + ) + }; + Ok(Self::new(data.into(), encrypted)) + } +} + +pub async fn get_merchant_enc_key( + db: &dyn crate::db::StorageInterface, + merchant_id: impl AsRef, +) -> CustomResult, crate::core::errors::StorageError> { + let merchant_id = merchant_id.as_ref(); + let key = db + .get_merchant_key_store_by_merchant_id(merchant_id) + .await? + .key + .into_inner(); + Ok(key.expose()) +} + +pub trait Lift { + type SelfWrapper; + type OtherWrapper; + + fn lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> Self::OtherWrapper; +} + +impl Lift for Option { + type SelfWrapper = Option; + type OtherWrapper = CustomResult, E>; + + fn lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> Self::OtherWrapper, + { + func(self) + } +} + +#[async_trait] +pub trait AsyncLift { + type SelfWrapper; + type OtherWrapper; + + async fn async_lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> F + Send + Sync, + F: futures::Future> + Send; +} + +#[async_trait] +impl + Lift = V> + Send> AsyncLift for V { + type SelfWrapper = >::SelfWrapper; + type OtherWrapper = >::OtherWrapper; + + async fn async_lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> F + Send + Sync, + F: futures::Future> + Send, + { + func(self).await + } +} + +#[inline] +pub async fn encrypt( + inner: Secret, + key: &[u8], +) -> CustomResult>, errors::CryptoError> +where + S: masking::Strategy, + crypto::Encryptable>: TypeEncryption, +{ + request::record_operation_time( + crypto::Encryptable::encrypt(inner, key, crypto::GcmAes256 {}), + &ENCRYPTION_TIME, + ) + .await +} + +#[inline] +pub async fn encrypt_optional( + inner: Option>, + key: &[u8], +) -> CustomResult>>, errors::CryptoError> +where + Secret: Send, + S: masking::Strategy, + crypto::Encryptable>: TypeEncryption, +{ + inner.async_map(|f| encrypt(f, key)).await.transpose() +} + +#[inline] +pub async fn decrypt>( + inner: Option, + key: &[u8], + timestamp: i64, + migration_timestamp: i64, +) -> CustomResult>>, errors::CryptoError> +where + crypto::Encryptable>: TypeEncryption, +{ + request::record_operation_time( + inner.async_map(|item| { + crypto::Encryptable::decrypt( + item, + key, + crypto::GcmAes256 {}, + timestamp, + migration_timestamp, + ) + }), + &DECRYPTION_TIME, + ) + .await + .transpose() +} diff --git a/crates/router/src/types/storage/address.rs b/crates/router/src/types/storage/address.rs index 8dd7323715..e4a8775836 100644 --- a/crates/router/src/types/storage/address.rs +++ b/crates/router/src/types/storage/address.rs @@ -1 +1,3 @@ -pub use storage_models::address::{Address, AddressNew, AddressUpdate, AddressUpdateInternal}; +pub use storage_models::address::{Address, AddressNew, AddressUpdateInternal}; + +pub use crate::types::domain::AddressUpdate; diff --git a/crates/router/src/types/storage/customers.rs b/crates/router/src/types/storage/customers.rs index abb0569539..5c82232050 100644 --- a/crates/router/src/types/storage/customers.rs +++ b/crates/router/src/types/storage/customers.rs @@ -1,3 +1,3 @@ -pub use storage_models::customers::{ - Customer, CustomerNew, CustomerUpdate, CustomerUpdateInternal, -}; +pub use storage_models::customers::{Customer, CustomerNew, CustomerUpdateInternal}; + +pub use crate::types::domain::CustomerUpdate; diff --git a/crates/router/src/types/storage/merchant_account.rs b/crates/router/src/types/storage/merchant_account.rs index e6bd21d03c..5dea35b0ec 100644 --- a/crates/router/src/types/storage/merchant_account.rs +++ b/crates/router/src/types/storage/merchant_account.rs @@ -1,3 +1,5 @@ pub use storage_models::merchant_account::{ - MerchantAccount, MerchantAccountNew, MerchantAccountUpdate, MerchantAccountUpdateInternal, + MerchantAccount, MerchantAccountNew, MerchantAccountUpdateInternal, }; + +pub use crate::types::domain::MerchantAccountUpdate; diff --git a/crates/router/src/types/storage/merchant_connector_account.rs b/crates/router/src/types/storage/merchant_connector_account.rs index 249538f582..58bbc4259c 100644 --- a/crates/router/src/types/storage/merchant_connector_account.rs +++ b/crates/router/src/types/storage/merchant_connector_account.rs @@ -1,4 +1,5 @@ pub use storage_models::merchant_connector_account::{ - MerchantConnectorAccount, MerchantConnectorAccountNew, MerchantConnectorAccountUpdate, - MerchantConnectorAccountUpdateInternal, + MerchantConnectorAccount, MerchantConnectorAccountNew, MerchantConnectorAccountUpdateInternal, }; + +pub use crate::types::domain::MerchantConnectorAccountUpdate; diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index b02d5d5003..473685f4e1 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1,9 +1,10 @@ use api_models::enums as api_enums; -use common_utils::ext_traits::ValueExt; +use common_utils::{crypto::Encryptable, ext_traits::ValueExt}; use error_stack::ResultExt; -use masking::{PeekInterface, Secret}; +use masking::PeekInterface; use storage_models::enums as storage_enums; +use super::domain; use crate::{ core::errors, types::{api as api_types, storage}, @@ -333,25 +334,6 @@ impl ForeignFrom for api_enums::Currency { } } -impl<'a> ForeignFrom<&'a api_types::Address> for storage::AddressUpdate { - fn foreign_from(address: &api_types::Address) -> Self { - let address = address; - Self::Update { - city: address.address.as_ref().and_then(|a| a.city.clone()), - country: address.address.as_ref().and_then(|a| a.country), - line1: address.address.as_ref().and_then(|a| a.line1.clone()), - line2: address.address.as_ref().and_then(|a| a.line2.clone()), - line3: address.address.as_ref().and_then(|a| a.line3.clone()), - state: address.address.as_ref().and_then(|a| a.state.clone()), - zip: address.address.as_ref().and_then(|a| a.zip.clone()), - first_name: address.address.as_ref().and_then(|a| a.first_name.clone()), - last_name: address.address.as_ref().and_then(|a| a.last_name.clone()), - phone_number: address.phone.as_ref().and_then(|a| a.number.clone()), - country_code: address.phone.as_ref().and_then(|a| a.country_code.clone()), - } - } -} - impl ForeignFrom for api_types::Config { fn foreign_from(config: storage::Config) -> Self { let config = config; @@ -371,23 +353,23 @@ impl<'a> ForeignFrom<&'a api_types::ConfigUpdate> for storage::ConfigUpdate { } } -impl<'a> ForeignFrom<&'a storage::Address> for api_types::Address { - fn foreign_from(address: &storage::Address) -> Self { +impl<'a> From<&'a domain::Address> for api_types::Address { + fn from(address: &domain::Address) -> Self { let address = address; Self { address: Some(api_types::AddressDetails { city: address.city.clone(), country: address.country, - line1: address.line1.clone(), - line2: address.line2.clone(), - line3: address.line3.clone(), - state: address.state.clone(), - zip: address.zip.clone(), - first_name: address.first_name.clone(), - last_name: address.last_name.clone(), + line1: address.line1.clone().map(Encryptable::into_inner), + line2: address.line2.clone().map(Encryptable::into_inner), + line3: address.line3.clone().map(Encryptable::into_inner), + state: address.state.clone().map(Encryptable::into_inner), + zip: address.zip.clone().map(Encryptable::into_inner), + first_name: address.first_name.clone().map(Encryptable::into_inner), + last_name: address.last_name.clone().map(Encryptable::into_inner), }), phone: Some(api_types::PhoneDetails { - number: address.phone_number.clone(), + number: address.phone_number.clone().map(Encryptable::into_inner), country_code: address.country_code.clone(), }), } @@ -410,24 +392,6 @@ impl ForeignFrom } } -impl ForeignFrom for storage_models::address::AddressNew { - fn foreign_from(item: api_models::payments::AddressDetails) -> Self { - let address = item; - Self { - city: address.city, - country: address.country, - line1: address.line1, - line2: address.line2, - line3: address.line3, - state: address.state, - zip: address.zip, - first_name: address.first_name, - last_name: address.last_name, - ..Default::default() - } - } -} - impl ForeignFrom<( storage_models::api_keys::ApiKey, @@ -609,13 +573,9 @@ impl ForeignFrom } } -impl ForeignTryFrom - for api_models::admin::MerchantConnectorResponse -{ +impl TryFrom for api_models::admin::MerchantConnectorResponse { type Error = error_stack::Report; - fn foreign_try_from( - item: storage_models::merchant_connector_account::MerchantConnectorAccount, - ) -> Result { + fn try_from(item: domain::MerchantConnectorAccount) -> Result { let payment_methods_enabled = match item.payment_methods_enabled { Some(val) => serde_json::Value::Array(val) .parse_value("PaymentMethods") @@ -641,7 +601,7 @@ impl ForeignTryFrom, pub country: Option, - pub line1: Option>, - pub line2: Option>, - pub line3: Option>, - pub state: Option>, - pub zip: Option>, - pub first_name: Option>, - pub last_name: Option>, - pub phone_number: Option>, + pub line1: Option, + pub line2: Option, + pub line3: Option, + pub state: Option, + pub zip: Option, + pub first_name: Option, + pub last_name: Option, + pub phone_number: Option, pub country_code: Option, pub customer_id: String, pub merchant_id: String, + pub created_at: PrimitiveDateTime, + pub modified_at: PrimitiveDateTime, } -#[derive(Clone, Debug, Deserialize, Serialize, Identifiable, Queryable, frunk::LabelledGeneric)] +#[derive(Clone, Debug, Identifiable, Queryable, frunk::LabelledGeneric)] #[diesel(table_name = address)] pub struct Address { - #[serde(skip_serializing)] pub id: i32, - #[serde(skip_serializing)] pub address_id: String, pub city: Option, pub country: Option, - pub line1: Option>, - pub line2: Option>, - pub line3: Option>, - pub state: Option>, - pub zip: Option>, - pub first_name: Option>, - pub last_name: Option>, - pub phone_number: Option>, + pub line1: Option, + pub line2: Option, + pub line3: Option, + pub state: Option, + pub zip: Option, + pub first_name: Option, + pub last_name: Option, + pub phone_number: Option, pub country_code: Option, - #[serde(skip_serializing)] - #[serde(with = "custom_serde::iso8601")] pub created_at: PrimitiveDateTime, - #[serde(skip_serializing)] - #[serde(with = "custom_serde::iso8601")] pub modified_at: PrimitiveDateTime, pub customer_id: String, pub merchant_id: String, } -#[derive(Debug, frunk::LabelledGeneric)] -pub enum AddressUpdate { - Update { - city: Option, - country: Option, - line1: Option>, - line2: Option>, - line3: Option>, - state: Option>, - zip: Option>, - first_name: Option>, - last_name: Option>, - phone_number: Option>, - country_code: Option, - }, -} - #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = address)] pub struct AddressUpdateInternal { - city: Option, - country: Option, - line1: Option>, - line2: Option>, - line3: Option>, - state: Option>, - zip: Option>, - first_name: Option>, - last_name: Option>, - phone_number: Option>, - country_code: Option, - modified_at: PrimitiveDateTime, + pub city: Option, + pub country: Option, + pub line1: Option, + pub line2: Option, + pub line3: Option, + pub state: Option, + pub zip: Option, + pub first_name: Option, + pub last_name: Option, + pub phone_number: Option, + pub country_code: Option, + pub modified_at: PrimitiveDateTime, } impl AddressUpdateInternal { @@ -108,57 +83,3 @@ impl AddressUpdateInternal { } } } - -impl From for AddressUpdateInternal { - fn from(address_update: AddressUpdate) -> Self { - match address_update { - AddressUpdate::Update { - city, - country, - line1, - line2, - line3, - state, - zip, - first_name, - last_name, - phone_number, - country_code, - } => Self { - city, - country, - line1, - line2, - line3, - state, - zip, - first_name, - last_name, - phone_number, - country_code, - modified_at: date_time::convert_to_pdt(OffsetDateTime::now_utc()), - }, - } - } -} - -impl Default for AddressNew { - fn default() -> Self { - Self { - address_id: generate_id(consts::ID_LENGTH, "add"), - city: None, - country: None, - line1: None, - line2: None, - line3: None, - state: None, - zip: None, - first_name: None, - last_name: None, - phone_number: None, - country_code: None, - customer_id: String::default(), - merchant_id: String::default(), - } - } -} diff --git a/crates/storage_models/src/customers.rs b/crates/storage_models/src/customers.rs index ea4154bdd6..68ae2aba01 100644 --- a/crates/storage_models/src/customers.rs +++ b/crates/storage_models/src/customers.rs @@ -1,22 +1,23 @@ -use common_utils::{pii, pii::Email}; +use common_utils::pii; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; -use masking::Secret; use time::PrimitiveDateTime; -use crate::schema::customers; +use crate::{encryption::Encryption, schema::customers}; -#[derive(Default, Clone, Debug, Insertable, router_derive::DebugAsDisplay)] +#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)] #[diesel(table_name = customers)] pub struct CustomerNew { pub customer_id: String, pub merchant_id: String, - pub name: Option, - pub email: Option, - pub phone: Option>, + pub name: Option, + pub email: Option, + pub phone: Option, pub description: Option, pub phone_country_code: Option, pub metadata: Option, pub connector_customer: Option, + pub created_at: PrimitiveDateTime, + pub modified_at: PrimitiveDateTime, } #[derive(Clone, Debug, Identifiable, Queryable)] @@ -25,9 +26,9 @@ pub struct Customer { pub id: i32, pub customer_id: String, pub merchant_id: String, - pub name: Option, - pub email: Option, - pub phone: Option>, + pub name: Option, + pub email: Option, + pub phone: Option, pub phone_country_code: Option, pub description: Option, pub created_at: PrimitiveDateTime, @@ -36,61 +37,15 @@ pub struct Customer { pub modified_at: PrimitiveDateTime, } -#[derive(Debug)] -pub enum CustomerUpdate { - Update { - name: Option, - email: Option, - phone: Option>, - description: Option, - phone_country_code: Option, - metadata: Option, - connector_customer: Option, - }, - ConnectorCustomer { - connector_customer: Option, - }, -} - #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = customers)] pub struct CustomerUpdateInternal { - name: Option, - email: Option, - phone: Option>, - description: Option, - phone_country_code: Option, - metadata: Option, - connector_customer: Option, - modified_at: Option, -} - -impl From for CustomerUpdateInternal { - fn from(customer_update: CustomerUpdate) -> Self { - match customer_update { - CustomerUpdate::Update { - name, - email, - phone, - description, - phone_country_code, - metadata, - connector_customer, - } => Self { - name, - email, - phone, - description, - phone_country_code, - metadata, - connector_customer, - modified_at: Some(common_utils::date_time::now()), - }, - CustomerUpdate::ConnectorCustomer { connector_customer } => Self { - connector_customer, - modified_at: Some(common_utils::date_time::now()), - ..Default::default() - }, - } - } + pub name: Option, + pub email: Option, + pub phone: Option, + pub description: Option, + pub phone_country_code: Option, + pub metadata: Option, + pub modified_at: Option, + pub connector_customer: Option, } diff --git a/crates/storage_models/src/encryption.rs b/crates/storage_models/src/encryption.rs new file mode 100644 index 0000000000..2790eaf668 --- /dev/null +++ b/crates/storage_models/src/encryption.rs @@ -0,0 +1,69 @@ +use diesel::{ + backend::Backend, + deserialize::{self, FromSql, Queryable}, + serialize::ToSql, + sql_types, AsExpression, +}; + +#[derive(Debug, AsExpression, Clone, serde::Serialize, serde::Deserialize)] +#[diesel(sql_type = diesel::sql_types::Binary)] +#[repr(transparent)] +pub struct Encryption { + inner: Vec, +} + +impl From> for Encryption { + fn from(value: common_utils::crypto::Encryptable) -> Self { + Self::new(value.into_encrypted()) + } +} + +impl Encryption { + pub fn new(item: Vec) -> Self { + Self { inner: item } + } + + #[inline] + pub fn into_inner(self) -> Vec { + self.inner + } + + #[inline] + pub fn get_inner(&self) -> &Vec { + &self.inner + } +} + +impl FromSql for Encryption +where + DB: Backend, + Vec: FromSql, +{ + fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result { + >::from_sql(bytes).map(Self::new) + } +} + +impl ToSql for Encryption +where + DB: Backend, + Vec: ToSql, +{ + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.get_inner().to_sql(out) + } +} + +impl Queryable for Encryption +where + DB: Backend, + Vec: FromSql, +{ + type Row = Vec; + fn build(row: Self::Row) -> deserialize::Result { + Ok(Self { inner: row }) + } +} diff --git a/crates/storage_models/src/lib.rs b/crates/storage_models/src/lib.rs index 265be16622..3caedbfc27 100644 --- a/crates/storage_models/src/lib.rs +++ b/crates/storage_models/src/lib.rs @@ -5,6 +5,7 @@ pub mod configs; pub mod connector_response; pub mod customers; pub mod dispute; +pub mod encryption; pub mod enums; pub mod ephemeral_key; pub mod errors; @@ -16,6 +17,7 @@ pub mod locker_mock_up; pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; +pub mod merchant_key_store; pub mod payment_attempt; pub mod payment_intent; pub mod payment_method; diff --git a/crates/storage_models/src/merchant_account.rs b/crates/storage_models/src/merchant_account.rs index 463379966a..c4dd26f206 100644 --- a/crates/storage_models/src/merchant_account.rs +++ b/crates/storage_models/src/merchant_account.rs @@ -1,15 +1,13 @@ use common_utils::pii; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; -use crate::{enums as storage_enums, schema::merchant_account}; +use crate::{encryption::Encryption, enums as storage_enums, schema::merchant_account}; #[derive( Clone, Debug, serde::Deserialize, serde::Serialize, - Eq, - PartialEq, Identifiable, Queryable, router_derive::DebugAsDisplay, @@ -22,8 +20,8 @@ pub struct MerchantAccount { pub enable_payment_response_hash: bool, pub payment_response_hash_key: Option, pub redirect_to_merchant_with_http_post: bool, - pub merchant_name: Option, - pub merchant_details: Option, + pub merchant_name: Option, + pub merchant_details: Option, pub webhook_details: Option, pub sub_merchants_enabled: Option, pub parent_merchant_id: Option, @@ -39,12 +37,12 @@ pub struct MerchantAccount { pub frm_routing_algorithm: Option, } -#[derive(Clone, Debug, Default, Insertable, router_derive::DebugAsDisplay)] +#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)] #[diesel(table_name = merchant_account)] pub struct MerchantAccountNew { pub merchant_id: String, - pub merchant_name: Option, - pub merchant_details: Option, + pub merchant_name: Option, + pub merchant_details: Option, pub return_url: Option, pub webhook_details: Option, pub sub_merchants_enabled: Option, @@ -58,101 +56,30 @@ pub struct MerchantAccountNew { pub routing_algorithm: Option, pub primary_business_details: serde_json::Value, pub intent_fulfillment_time: Option, + pub created_at: time::PrimitiveDateTime, + pub modified_at: time::PrimitiveDateTime, pub frm_routing_algorithm: Option, } -#[derive(Debug)] -pub enum MerchantAccountUpdate { - Update { - merchant_name: Option, - merchant_details: Option, - return_url: Option, - webhook_details: Option, - sub_merchants_enabled: Option, - parent_merchant_id: Option, - enable_payment_response_hash: Option, - payment_response_hash_key: Option, - redirect_to_merchant_with_http_post: Option, - publishable_key: Option, - locker_id: Option, - metadata: Option, - routing_algorithm: Option, - primary_business_details: Option, - intent_fulfillment_time: Option, - frm_routing_algorithm: Option, - }, - StorageSchemeUpdate { - storage_scheme: storage_enums::MerchantStorageScheme, - }, -} - #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = merchant_account)] pub struct MerchantAccountUpdateInternal { - merchant_name: Option, - merchant_details: Option, - return_url: Option, - webhook_details: Option, - sub_merchants_enabled: Option, - parent_merchant_id: Option, - enable_payment_response_hash: Option, - payment_response_hash_key: Option, - redirect_to_merchant_with_http_post: Option, - publishable_key: Option, - storage_scheme: Option, - locker_id: Option, - metadata: Option, - routing_algorithm: Option, - primary_business_details: Option, - modified_at: Option, - intent_fulfillment_time: Option, - frm_routing_algorithm: Option, -} - -impl From for MerchantAccountUpdateInternal { - fn from(merchant_account_update: MerchantAccountUpdate) -> Self { - match merchant_account_update { - MerchantAccountUpdate::Update { - merchant_name, - merchant_details, - return_url, - webhook_details, - routing_algorithm, - sub_merchants_enabled, - parent_merchant_id, - enable_payment_response_hash, - payment_response_hash_key, - redirect_to_merchant_with_http_post, - publishable_key, - locker_id, - metadata, - primary_business_details, - intent_fulfillment_time, - frm_routing_algorithm, - } => Self { - merchant_name, - merchant_details, - return_url, - webhook_details, - routing_algorithm, - sub_merchants_enabled, - parent_merchant_id, - enable_payment_response_hash, - payment_response_hash_key, - redirect_to_merchant_with_http_post, - publishable_key, - locker_id, - metadata, - primary_business_details, - modified_at: Some(common_utils::date_time::now()), - intent_fulfillment_time, - frm_routing_algorithm, - ..Default::default() - }, - MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self { - storage_scheme: Some(storage_scheme), - ..Default::default() - }, - } - } + pub merchant_name: Option, + pub merchant_details: Option, + pub return_url: Option, + pub webhook_details: Option, + pub sub_merchants_enabled: Option, + pub parent_merchant_id: Option, + pub enable_payment_response_hash: Option, + pub payment_response_hash_key: Option, + pub redirect_to_merchant_with_http_post: Option, + pub publishable_key: Option, + pub storage_scheme: Option, + pub locker_id: Option, + pub metadata: Option, + pub routing_algorithm: Option, + pub primary_business_details: Option, + pub modified_at: Option, + pub intent_fulfillment_time: Option, + pub frm_routing_algorithm: Option, } diff --git a/crates/storage_models/src/merchant_connector_account.rs b/crates/storage_models/src/merchant_connector_account.rs index 5fe26ff2db..c798602f64 100644 --- a/crates/storage_models/src/merchant_connector_account.rs +++ b/crates/storage_models/src/merchant_connector_account.rs @@ -1,16 +1,13 @@ use common_utils::pii; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; -use masking::Secret; -use crate::{enums as storage_enums, schema::merchant_connector_account}; +use crate::{encryption::Encryption, enums as storage_enums, schema::merchant_connector_account}; #[derive( Clone, Debug, - Eq, serde::Serialize, serde::Deserialize, - PartialEq, Identifiable, Queryable, router_derive::DebugAsDisplay, @@ -20,7 +17,7 @@ pub struct MerchantConnectorAccount { pub id: i32, pub merchant_id: String, pub connector_name: String, - pub connector_account_details: serde_json::Value, + pub connector_account_details: Encryption, pub test_mode: Option, pub disabled: Option, pub merchant_connector_id: String, @@ -32,7 +29,7 @@ pub struct MerchantConnectorAccount { pub business_country: storage_enums::CountryAlpha2, pub business_label: String, pub business_sub_label: Option, - pub frm_configs: Option>, + pub frm_configs: Option>, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, } @@ -43,7 +40,7 @@ pub struct MerchantConnectorAccountNew { pub merchant_id: Option, pub connector_type: Option, pub connector_name: Option, - pub connector_account_details: Option>, + pub connector_account_details: Option, pub test_mode: Option, pub disabled: Option, pub merchant_connector_id: String, @@ -53,65 +50,23 @@ pub struct MerchantConnectorAccountNew { pub business_country: storage_enums::CountryAlpha2, pub business_label: String, pub business_sub_label: Option, - pub frm_configs: Option>, + pub frm_configs: Option>, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, } -#[derive(Debug)] -pub enum MerchantConnectorAccountUpdate { - Update { - merchant_id: Option, - connector_type: Option, - connector_account_details: Option>, - test_mode: Option, - disabled: Option, - merchant_connector_id: Option, - payment_methods_enabled: Option>, - metadata: Option, - frm_configs: Option>, - }, -} -#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] +#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = merchant_connector_account)] pub struct MerchantConnectorAccountUpdateInternal { - merchant_id: Option, - connector_type: Option, - connector_account_details: Option>, - test_mode: Option, - disabled: Option, - merchant_connector_id: Option, - payment_methods_enabled: Option>, - metadata: Option, - frm_configs: Option>, - modified_at: time::PrimitiveDateTime, -} - -impl From for MerchantConnectorAccountUpdateInternal { - fn from(merchant_connector_account_update: MerchantConnectorAccountUpdate) -> Self { - match merchant_connector_account_update { - MerchantConnectorAccountUpdate::Update { - merchant_id, - connector_type, - connector_account_details, - test_mode, - disabled, - merchant_connector_id, - payment_methods_enabled, - metadata, - frm_configs, - } => Self { - merchant_id, - connector_type, - connector_account_details, - test_mode, - disabled, - merchant_connector_id, - payment_methods_enabled, - metadata, - frm_configs, - modified_at: common_utils::date_time::now(), - }, - } - } + pub merchant_id: Option, + pub connector_type: Option, + pub connector_name: Option, + pub connector_account_details: Option, + pub test_mode: Option, + pub disabled: Option, + pub merchant_connector_id: Option, + pub payment_methods_enabled: Option>, + pub metadata: Option, + pub frm_configs: Option>, + pub modified_at: Option, } diff --git a/crates/storage_models/src/merchant_key_store.rs b/crates/storage_models/src/merchant_key_store.rs new file mode 100644 index 0000000000..ef17ace149 --- /dev/null +++ b/crates/storage_models/src/merchant_key_store.rs @@ -0,0 +1,42 @@ +use common_utils::custom_serde; +use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; +use time::PrimitiveDateTime; + +use crate::{encryption::Encryption, schema::merchant_key_store}; + +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + Identifiable, + Queryable, + router_derive::DebugAsDisplay, +)] +#[diesel(table_name = merchant_key_store)] +#[diesel(primary_key(merchant_id))] +pub struct MerchantKeyStore { + pub merchant_id: String, + pub key: Encryption, + #[serde(with = "custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, +} + +#[derive( + Clone, Debug, serde::Serialize, serde::Deserialize, Insertable, router_derive::DebugAsDisplay, +)] +#[diesel(table_name = merchant_key_store)] +pub struct MerchantKeyStoreNew { + pub merchant_id: String, + pub key: Encryption, + pub created_at: PrimitiveDateTime, +} + +#[derive( + Clone, Debug, serde::Serialize, serde::Deserialize, AsChangeset, router_derive::DebugAsDisplay, +)] +#[diesel(table_name = merchant_key_store)] +pub struct MerchantKeyStoreUpdateInternal { + pub merchant_id: String, + pub key: Encryption, +} diff --git a/crates/storage_models/src/query.rs b/crates/storage_models/src/query.rs index e26e754bdb..8da83de0ea 100644 --- a/crates/storage_models/src/query.rs +++ b/crates/storage_models/src/query.rs @@ -12,6 +12,7 @@ pub mod locker_mock_up; pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; +pub mod merchant_key_store; pub mod payment_attempt; pub mod payment_intent; pub mod payment_method; diff --git a/crates/storage_models/src/query/address.rs b/crates/storage_models/src/query/address.rs index b4acbc44c6..4474154cf3 100644 --- a/crates/storage_models/src/query/address.rs +++ b/crates/storage_models/src/query/address.rs @@ -3,7 +3,7 @@ use router_env::{instrument, tracing}; use super::generics; use crate::{ - address::{Address, AddressNew, AddressUpdate, AddressUpdateInternal}, + address::{Address, AddressNew, AddressUpdateInternal}, errors, schema::address::dsl, PgPooledConn, StorageResult, @@ -21,12 +21,12 @@ impl Address { pub async fn update_by_address_id( conn: &PgPooledConn, address_id: String, - address: AddressUpdate, + address: AddressUpdateInternal, ) -> StorageResult { match generics::generic_update_by_id::<::Table, _, _, _>( conn, address_id.clone(), - AddressUpdateInternal::from(address), + address, ) .await { @@ -63,14 +63,14 @@ impl Address { conn: &PgPooledConn, customer_id: &str, merchant_id: &str, - address: AddressUpdate, + address: AddressUpdateInternal, ) -> StorageResult> { generics::generic_update_with_results::<::Table, _, _, _>( conn, dsl::merchant_id .eq(merchant_id.to_owned()) .and(dsl::customer_id.eq(customer_id.to_owned())), - AddressUpdateInternal::from(address), + address, ) .await } diff --git a/crates/storage_models/src/query/customers.rs b/crates/storage_models/src/query/customers.rs index cd4510d35d..cd1691b39d 100644 --- a/crates/storage_models/src/query/customers.rs +++ b/crates/storage_models/src/query/customers.rs @@ -3,7 +3,7 @@ use router_env::{instrument, tracing}; use super::generics; use crate::{ - customers::{Customer, CustomerNew, CustomerUpdate, CustomerUpdateInternal}, + customers::{Customer, CustomerNew, CustomerUpdateInternal}, errors, schema::customers::dsl, PgPooledConn, StorageResult, @@ -22,12 +22,12 @@ impl Customer { conn: &PgPooledConn, customer_id: String, merchant_id: String, - customer: CustomerUpdate, + customer: CustomerUpdateInternal, ) -> StorageResult { match generics::generic_update_by_id::<::Table, _, _, _>( conn, (customer_id.clone(), merchant_id.clone()), - CustomerUpdateInternal::from(customer), + customer, ) .await { diff --git a/crates/storage_models/src/query/merchant_account.rs b/crates/storage_models/src/query/merchant_account.rs index 70461d6aae..598a4889a7 100644 --- a/crates/storage_models/src/query/merchant_account.rs +++ b/crates/storage_models/src/query/merchant_account.rs @@ -4,9 +4,7 @@ use router_env::{instrument, tracing}; use super::generics; use crate::{ errors, - merchant_account::{ - MerchantAccount, MerchantAccountNew, MerchantAccountUpdate, MerchantAccountUpdateInternal, - }, + merchant_account::{MerchantAccount, MerchantAccountNew, MerchantAccountUpdateInternal}, schema::merchant_account::dsl, PgPooledConn, StorageResult, }; @@ -23,12 +21,12 @@ impl MerchantAccount { pub async fn update( self, conn: &PgPooledConn, - merchant_account: MerchantAccountUpdate, + merchant_account: MerchantAccountUpdateInternal, ) -> StorageResult { match generics::generic_update_by_id::<::Table, _, _, _>( conn, self.id, - MerchantAccountUpdateInternal::from(merchant_account), + merchant_account, ) .await { @@ -43,7 +41,7 @@ impl MerchantAccount { pub async fn update_with_specific_fields( conn: &PgPooledConn, merchant_id: &str, - merchant_account: MerchantAccountUpdate, + merchant_account: MerchantAccountUpdateInternal, ) -> StorageResult { generics::generic_update_with_unique_predicate_get_result::< ::Table, @@ -53,7 +51,7 @@ impl MerchantAccount { >( conn, dsl::merchant_id.eq(merchant_id.to_owned()), - MerchantAccountUpdateInternal::from(merchant_account), + merchant_account, ) .await } diff --git a/crates/storage_models/src/query/merchant_connector_account.rs b/crates/storage_models/src/query/merchant_connector_account.rs index bc417a65f7..1b921c59dc 100644 --- a/crates/storage_models/src/query/merchant_connector_account.rs +++ b/crates/storage_models/src/query/merchant_connector_account.rs @@ -5,7 +5,7 @@ use super::generics; use crate::{ errors, merchant_connector_account::{ - MerchantConnectorAccount, MerchantConnectorAccountNew, MerchantConnectorAccountUpdate, + MerchantConnectorAccount, MerchantConnectorAccountNew, MerchantConnectorAccountUpdateInternal, }, schema::merchant_connector_account::dsl, @@ -24,12 +24,12 @@ impl MerchantConnectorAccount { pub async fn update( self, conn: &PgPooledConn, - merchant_connector_account: MerchantConnectorAccountUpdate, + merchant_connector_account: MerchantConnectorAccountUpdateInternal, ) -> StorageResult { match generics::generic_update_by_id::<::Table, _, _, _>( conn, self.id, - MerchantConnectorAccountUpdateInternal::from(merchant_connector_account), + merchant_connector_account, ) .await { diff --git a/crates/storage_models/src/query/merchant_key_store.rs b/crates/storage_models/src/query/merchant_key_store.rs new file mode 100644 index 0000000000..02a31a1f39 --- /dev/null +++ b/crates/storage_models/src/query/merchant_key_store.rs @@ -0,0 +1,30 @@ +use diesel::{associations::HasTable, ExpressionMethods}; +use router_env::{instrument, tracing}; + +use super::generics; +use crate::{ + merchant_key_store::{MerchantKeyStore, MerchantKeyStoreNew}, + schema::merchant_key_store::dsl, + PgPooledConn, StorageResult, +}; + +impl MerchantKeyStoreNew { + #[instrument(skip(conn))] + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl MerchantKeyStore { + #[instrument(skip(conn))] + pub async fn find_by_merchant_id( + conn: &PgPooledConn, + merchant_id: &str, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::merchant_id.eq(merchant_id.to_owned()), + ) + .await + } +} diff --git a/crates/storage_models/src/schema.rs b/crates/storage_models/src/schema.rs index ea23a6a7ef..1996a95c65 100644 --- a/crates/storage_models/src/schema.rs +++ b/crates/storage_models/src/schema.rs @@ -11,22 +11,14 @@ diesel::table! { #[max_length = 128] city -> Nullable, country -> Nullable, - #[max_length = 255] - line1 -> Nullable, - #[max_length = 255] - line2 -> Nullable, - #[max_length = 255] - line3 -> Nullable, - #[max_length = 128] - state -> Nullable, - #[max_length = 16] - zip -> Nullable, - #[max_length = 255] - first_name -> Nullable, - #[max_length = 255] - last_name -> Nullable, - #[max_length = 32] - phone_number -> Nullable, + line1 -> Nullable, + line2 -> Nullable, + line3 -> Nullable, + state -> Nullable, + zip -> Nullable, + first_name -> Nullable, + last_name -> Nullable, + phone_number -> Nullable, #[max_length = 8] country_code -> Nullable, created_at -> Timestamp, @@ -130,12 +122,9 @@ diesel::table! { customer_id -> Varchar, #[max_length = 64] merchant_id -> Varchar, - #[max_length = 255] - name -> Nullable, - #[max_length = 255] - email -> Nullable, - #[max_length = 32] - phone -> Nullable, + name -> Nullable, + email -> Nullable, + phone -> Nullable, #[max_length = 8] phone_country_code -> Nullable, #[max_length = 255] @@ -321,9 +310,8 @@ diesel::table! { #[max_length = 255] payment_response_hash_key -> Nullable, redirect_to_merchant_with_http_post -> Bool, - #[max_length = 128] - merchant_name -> Nullable, - merchant_details -> Nullable, + merchant_name -> Nullable, + merchant_details -> Nullable, webhook_details -> Nullable, sub_merchants_enabled -> Nullable, #[max_length = 64] @@ -353,7 +341,7 @@ diesel::table! { merchant_id -> Varchar, #[max_length = 64] connector_name -> Varchar, - connector_account_details -> Json, + connector_account_details -> Bytea, test_mode -> Nullable, disabled -> Nullable, #[max_length = 128] @@ -374,6 +362,18 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + merchant_key_store (merchant_id) { + #[max_length = 255] + merchant_id -> Varchar, + key -> Bytea, + created_at -> Timestamp, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -618,6 +618,7 @@ diesel::allow_tables_to_appear_in_same_query!( mandate, merchant_account, merchant_connector_account, + merchant_key_store, payment_attempt, payment_intent, payment_methods, diff --git a/migrations/2023-04-06-092008_create_merchant_ek/down.sql b/migrations/2023-04-06-092008_create_merchant_ek/down.sql new file mode 100644 index 0000000000..c0a1beb942 --- /dev/null +++ b/migrations/2023-04-06-092008_create_merchant_ek/down.sql @@ -0,0 +1 @@ +DROP TABLE merchant_key_store; diff --git a/migrations/2023-04-06-092008_create_merchant_ek/up.sql b/migrations/2023-04-06-092008_create_merchant_ek/up.sql new file mode 100644 index 0000000000..6d1de425fa --- /dev/null +++ b/migrations/2023-04-06-092008_create_merchant_ek/up.sql @@ -0,0 +1,6 @@ +CREATE TABLE merchant_key_store( + merchant_id VARCHAR(255) NOT NULL PRIMARY KEY, + key bytea NOT NULL, + created_at TIMESTAMP NOT NULL +); + diff --git a/migrations/2023-04-11-084958_pii-migration/down.sql b/migrations/2023-04-11-084958_pii-migration/down.sql new file mode 100644 index 0000000000..4c184965b9 --- /dev/null +++ b/migrations/2023-04-11-084958_pii-migration/down.sql @@ -0,0 +1,24 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE merchant_connector_account + ALTER COLUMN connector_account_details TYPE JSON + USING convert_from(connector_account_details, 'UTF8')::json; + +ALTER TABLE merchant_account + ALTER COLUMN merchant_name TYPE VARCHAR(128) USING convert_from(merchant_name, 'UTF8')::text, + ALTER merchant_details TYPE JSON USING convert_from(merchant_details, 'UTF8')::json; + +ALTER TABLE address + ALTER COLUMN line1 TYPE VARCHAR(255) USING convert_from(line1, 'UTF8')::text, + ALTER COLUMN line2 TYPE VARCHAR(255) USING convert_from(line2, 'UTF8')::text, + ALTER COLUMN line3 TYPE VARCHAR(255) USING convert_from(line3, 'UTF8')::text, + ALTER COLUMN state TYPE VARCHAR(128) USING convert_from(state, 'UTF8')::text, + ALTER COLUMN zip TYPE VARCHAR(16) USING convert_from(zip, 'UTF8')::text, + ALTER COLUMN first_name TYPE VARCHAR(255) USING convert_from(first_name, 'UTF8')::text, + ALTER COLUMN last_name TYPE VARCHAR(255) USING convert_from(last_name, 'UTF8')::text, + ALTER COLUMN phone_number TYPE VARCHAR(32) USING convert_from(phone_number, 'UTF8')::text; + +ALTER TABLE customers + ALTER COLUMN name TYPE VARCHAR(255) USING convert_from(name, 'UTF8')::text, + ALTER COLUMN email TYPE VARCHAR(255) USING convert_from(email, 'UTF8')::text, + ALTER COLUMN phone TYPE VARCHAR(32) USING convert_from(phone, 'UTF8')::text; diff --git a/migrations/2023-04-11-084958_pii-migration/up.sql b/migrations/2023-04-11-084958_pii-migration/up.sql new file mode 100644 index 0000000000..6b1cdcd320 --- /dev/null +++ b/migrations/2023-04-11-084958_pii-migration/up.sql @@ -0,0 +1,23 @@ +-- Your SQL goes here +ALTER TABLE merchant_connector_account + ALTER COLUMN connector_account_details TYPE bytea + USING convert_to(connector_account_details::text, 'UTF8'); + +ALTER TABLE merchant_account + ALTER COLUMN merchant_name TYPE bytea USING convert_to(merchant_name, 'UTF8'), + ALTER merchant_details TYPE bytea USING convert_to(merchant_details::text, 'UTF8'); + +ALTER TABLE address + ALTER COLUMN line1 TYPE bytea USING convert_to(line1, 'UTF8'), + ALTER COLUMN line2 TYPE bytea USING convert_to(line2, 'UTF8'), + ALTER COLUMN line3 TYPE bytea USING convert_to(line3, 'UTF8'), + ALTER COLUMN state TYPE bytea USING convert_to(state, 'UTF8'), + ALTER COLUMN zip TYPE bytea USING convert_to(zip, 'UTF8'), + ALTER COLUMN first_name TYPE bytea USING convert_to(first_name, 'UTF8'), + ALTER COLUMN last_name TYPE bytea USING convert_to(last_name, 'UTF8'), + ALTER COLUMN phone_number TYPE bytea USING convert_to(phone_number, 'UTF8'); + +ALTER TABLE customers + ALTER COLUMN name TYPE bytea USING convert_to(name, 'UTF8'), + ALTER COLUMN email TYPE bytea USING convert_to(email, 'UTF8'), + ALTER COLUMN phone TYPE bytea USING convert_to(phone, 'UTF8'); diff --git a/migrations/2023-04-26-090005_remove_default_created_at_modified_at/down.sql b/migrations/2023-04-26-090005_remove_default_created_at_modified_at/down.sql new file mode 100644 index 0000000000..fb33e2d56d --- /dev/null +++ b/migrations/2023-04-26-090005_remove_default_created_at_modified_at/down.sql @@ -0,0 +1,60 @@ +-- Merchant Account +ALTER TABLE merchant_account +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE merchant_account +ALTER COLUMN created_at SET DEFAULT now(); + + +-- Merchant Connector Account +ALTER TABLE merchant_connector_account +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE merchant_connector_account +ALTER COLUMN created_at SET DEFAULT now(); + +-- Customers +ALTER TABLE customers +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE customers +ALTER COLUMN created_at SET DEFAULT now(); + +-- Address +ALTER TABLE address +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE address +ALTER COLUMN created_at SET DEFAULT now(); + +-- Refunds +ALTER TABLE refund +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE refund +ALTER COLUMN created_at SET DEFAULT now(); + +-- Connector Response +ALTER TABLE connector_response +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE connector_response +ALTER COLUMN created_at SET DEFAULT now(); + +-- Payment methods +ALTER TABLE payment_methods +ALTER COLUMN created_at SET DEFAULT now(); + +-- Payment Intent +ALTER TABLE payment_intent +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE payment_intent +ALTER COLUMN created_at SET DEFAULT now(); + +--- Payment Attempt +ALTER TABLE payment_attempt +ALTER COLUMN modified_at SET DEFAULT now(); + +ALTER TABLE payment_attempt +ALTER COLUMN created_at SET DEFAULT now(); diff --git a/migrations/2023-04-26-090005_remove_default_created_at_modified_at/up.sql b/migrations/2023-04-26-090005_remove_default_created_at_modified_at/up.sql new file mode 100644 index 0000000000..03beea413a --- /dev/null +++ b/migrations/2023-04-26-090005_remove_default_created_at_modified_at/up.sql @@ -0,0 +1,60 @@ +-- Merchant Account +ALTER TABLE merchant_account +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE merchant_account +ALTER COLUMN created_at DROP DEFAULT; + + +-- Merchant Connector Account +ALTER TABLE merchant_connector_account +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE merchant_connector_account +ALTER COLUMN created_at DROP DEFAULT; + +-- Customers +ALTER TABLE customers +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE customers +ALTER COLUMN created_at DROP DEFAULT; + +-- Address +ALTER TABLE address +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE address +ALTER COLUMN created_at DROP DEFAULT; + +-- Refunds +ALTER TABLE refund +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE refund +ALTER COLUMN created_at DROP DEFAULT; + +-- Connector Response +ALTER TABLE connector_response +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE connector_response +ALTER COLUMN created_at DROP DEFAULT; + +-- Payment methods +ALTER TABLE payment_methods +ALTER COLUMN created_at DROP DEFAULT; + +-- Payment Intent +ALTER TABLE payment_intent +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE payment_intent +ALTER COLUMN created_at DROP DEFAULT; + +--- Payment Attempt +ALTER TABLE payment_attempt +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE payment_attempt +ALTER COLUMN created_at DROP DEFAULT;