mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
refactor(paymentMethods): move all pm migration related changes to payment methods crate (#7786)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com>
This commit is contained in:
297
crates/payment_methods/src/controller.rs
Normal file
297
crates/payment_methods/src/controller.rs
Normal file
@ -0,0 +1,297 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
use api_models::payouts;
|
||||
use api_models::{enums as api_enums, payment_methods as api};
|
||||
use common_enums::enums as common_enums;
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
use common_utils::encryption;
|
||||
use common_utils::{crypto, ext_traits, id_type, type_name, types::keymanager};
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{merchant_key_store, payment_methods, type_encryption};
|
||||
use masking::{PeekInterface, Secret};
|
||||
use scheduler::errors as sch_errors;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use storage_impl::{errors as storage_errors, payment_method};
|
||||
|
||||
use crate::core::errors;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct DeleteCardResp {
|
||||
pub status: String,
|
||||
pub error_message: Option<String>,
|
||||
pub error_code: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DataDuplicationCheck {
|
||||
Duplicated,
|
||||
MetaDataChanged,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait PaymentMethodsController {
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2"),
|
||||
not(feature = "customer_v2")
|
||||
))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn create_payment_method(
|
||||
&self,
|
||||
req: &api::PaymentMethodCreate,
|
||||
customer_id: &id_type::CustomerId,
|
||||
payment_method_id: &str,
|
||||
locker_id: Option<String>,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
pm_metadata: Option<serde_json::Value>,
|
||||
customer_acceptance: Option<serde_json::Value>,
|
||||
payment_method_data: crypto::OptionalEncryptableValue,
|
||||
connector_mandate_details: Option<serde_json::Value>,
|
||||
status: Option<common_enums::PaymentMethodStatus>,
|
||||
network_transaction_id: Option<String>,
|
||||
payment_method_billing_address: crypto::OptionalEncryptableValue,
|
||||
card_scheme: Option<String>,
|
||||
network_token_requestor_reference_id: Option<String>,
|
||||
network_token_locker_id: Option<String>,
|
||||
network_token_payment_method_data: crypto::OptionalEncryptableValue,
|
||||
) -> errors::PmResult<payment_methods::PaymentMethod>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn insert_payment_method(
|
||||
&self,
|
||||
resp: &api::PaymentMethodResponse,
|
||||
req: &api::PaymentMethodCreate,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
customer_id: &id_type::CustomerId,
|
||||
pm_metadata: Option<serde_json::Value>,
|
||||
customer_acceptance: Option<serde_json::Value>,
|
||||
locker_id: Option<String>,
|
||||
connector_mandate_details: Option<serde_json::Value>,
|
||||
network_transaction_id: Option<String>,
|
||||
payment_method_billing_address: crypto::OptionalEncryptableValue,
|
||||
network_token_requestor_reference_id: Option<String>,
|
||||
network_token_locker_id: Option<String>,
|
||||
network_token_payment_method_data: crypto::OptionalEncryptableValue,
|
||||
) -> errors::PmResult<payment_methods::PaymentMethod>;
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn insert_payment_method(
|
||||
&self,
|
||||
resp: &api::PaymentMethodResponse,
|
||||
req: &api::PaymentMethodCreate,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
customer_id: &id_type::CustomerId,
|
||||
pm_metadata: Option<serde_json::Value>,
|
||||
customer_acceptance: Option<serde_json::Value>,
|
||||
locker_id: Option<String>,
|
||||
connector_mandate_details: Option<serde_json::Value>,
|
||||
network_transaction_id: Option<String>,
|
||||
payment_method_billing_address: Option<encryption::Encryption>,
|
||||
) -> errors::PmResult<payment_methods::PaymentMethod>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
async fn add_payment_method(
|
||||
&self,
|
||||
req: &api::PaymentMethodCreate,
|
||||
) -> errors::PmResponse<api::PaymentMethodResponse>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v2", feature = "v1"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
async fn retrieve_payment_method(
|
||||
&self,
|
||||
pm: api::PaymentMethodId,
|
||||
) -> errors::PmResponse<api::PaymentMethodResponse>;
|
||||
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
||||
async fn delete_payment_method(
|
||||
&self,
|
||||
pm_id: api::PaymentMethodId,
|
||||
) -> errors::PmResponse<api::PaymentMethodDeleteResponse>;
|
||||
|
||||
async fn add_card_hs(
|
||||
&self,
|
||||
req: api::PaymentMethodCreate,
|
||||
card: &api::CardDetail,
|
||||
customer_id: &id_type::CustomerId,
|
||||
locker_choice: api_enums::LockerChoice,
|
||||
card_reference: Option<&str>,
|
||||
) -> errors::VaultResult<(api::PaymentMethodResponse, Option<DataDuplicationCheck>)>;
|
||||
|
||||
/// The response will be the tuple of PaymentMethodResponse and the duplication check of payment_method
|
||||
async fn add_card_to_locker(
|
||||
&self,
|
||||
req: api::PaymentMethodCreate,
|
||||
card: &api::CardDetail,
|
||||
customer_id: &id_type::CustomerId,
|
||||
card_reference: Option<&str>,
|
||||
) -> errors::VaultResult<(api::PaymentMethodResponse, Option<DataDuplicationCheck>)>;
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
async fn add_bank_to_locker(
|
||||
&self,
|
||||
req: api::PaymentMethodCreate,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
bank: &payouts::Bank,
|
||||
customer_id: &id_type::CustomerId,
|
||||
) -> errors::VaultResult<(api::PaymentMethodResponse, Option<DataDuplicationCheck>)>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
async fn get_or_insert_payment_method(
|
||||
&self,
|
||||
req: api::PaymentMethodCreate,
|
||||
resp: &mut api::PaymentMethodResponse,
|
||||
customer_id: &id_type::CustomerId,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
) -> errors::PmResult<payment_methods::PaymentMethod>;
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
async fn get_or_insert_payment_method(
|
||||
&self,
|
||||
req: api::PaymentMethodCreate,
|
||||
resp: &mut api::PaymentMethodResponse,
|
||||
customer_id: &id_type::CustomerId,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
) -> errors::PmResult<payment_methods::PaymentMethod> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v2", feature = "v1"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
async fn get_card_details_with_locker_fallback(
|
||||
&self,
|
||||
pm: &payment_methods::PaymentMethod,
|
||||
) -> errors::PmResult<Option<api::CardDetailFromLocker>>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v2", feature = "v1"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
async fn get_card_details_without_locker_fallback(
|
||||
&self,
|
||||
pm: &payment_methods::PaymentMethod,
|
||||
) -> errors::PmResult<api::CardDetailFromLocker>;
|
||||
|
||||
async fn delete_card_from_locker(
|
||||
&self,
|
||||
customer_id: &id_type::CustomerId,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
card_reference: &str,
|
||||
) -> errors::PmResult<DeleteCardResp>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
fn store_default_payment_method(
|
||||
&self,
|
||||
req: &api::PaymentMethodCreate,
|
||||
customer_id: &id_type::CustomerId,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
) -> (api::PaymentMethodResponse, Option<DataDuplicationCheck>);
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
fn store_default_payment_method(
|
||||
&self,
|
||||
req: &api::PaymentMethodCreate,
|
||||
customer_id: &id_type::CustomerId,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
) -> (api::PaymentMethodResponse, Option<DataDuplicationCheck>);
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2"),
|
||||
not(feature = "customer_v2")
|
||||
))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn save_network_token_and_update_payment_method(
|
||||
&self,
|
||||
req: &api::PaymentMethodMigrate,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
network_token_data: &api_models::payment_methods::MigrateNetworkTokenData,
|
||||
network_token_requestor_ref_id: String,
|
||||
pm_id: String,
|
||||
) -> errors::PmResult<bool>;
|
||||
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
||||
async fn set_default_payment_method(
|
||||
&self,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
customer_id: &id_type::CustomerId,
|
||||
payment_method_id: String,
|
||||
) -> errors::PmResponse<api_models::payment_methods::CustomerDefaultPaymentMethodResponse>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
async fn add_payment_method_status_update_task(
|
||||
&self,
|
||||
payment_method: &payment_methods::PaymentMethod,
|
||||
prev_status: common_enums::PaymentMethodStatus,
|
||||
curr_status: common_enums::PaymentMethodStatus,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
) -> Result<(), sch_errors::ProcessTrackerError>;
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
async fn validate_merchant_connector_ids_in_connector_mandate_details(
|
||||
&self,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
connector_mandate_details: &api_models::payment_methods::CommonMandateReference,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
card_network: Option<common_enums::CardNetwork>,
|
||||
) -> errors::PmResult<()>;
|
||||
}
|
||||
|
||||
pub async fn create_encrypted_data<T>(
|
||||
key_manager_state: &keymanager::KeyManagerState,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
data: T,
|
||||
) -> Result<
|
||||
crypto::Encryptable<Secret<serde_json::Value>>,
|
||||
error_stack::Report<storage_errors::StorageError>,
|
||||
>
|
||||
where
|
||||
T: Debug + Serialize,
|
||||
{
|
||||
let key = key_store.key.get_inner().peek();
|
||||
let identifier = keymanager::Identifier::Merchant(key_store.merchant_id.clone());
|
||||
|
||||
let encoded_data = ext_traits::Encode::encode_to_value(&data)
|
||||
.change_context(storage_errors::StorageError::SerializationFailed)
|
||||
.attach_printable("Unable to encode data")?;
|
||||
|
||||
let secret_data = Secret::<_, masking::WithType>::new(encoded_data);
|
||||
|
||||
let encrypted_data = type_encryption::crypto_operation(
|
||||
key_manager_state,
|
||||
type_name!(payment_method::PaymentMethod),
|
||||
type_encryption::CryptoOperation::Encrypt(secret_data),
|
||||
identifier.clone(),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.and_then(|val| val.try_into_operation())
|
||||
.change_context(storage_errors::StorageError::EncryptionError)
|
||||
.attach_printable("Unable to encrypt data")?;
|
||||
|
||||
Ok(encrypted_data)
|
||||
}
|
||||
2
crates/payment_methods/src/core.rs
Normal file
2
crates/payment_methods/src/core.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod errors;
|
||||
pub mod migration;
|
||||
49
crates/payment_methods/src/core/errors.rs
Normal file
49
crates/payment_methods/src/core/errors.rs
Normal file
@ -0,0 +1,49 @@
|
||||
pub use common_utils::errors::{CustomResult, ParsingError, ValidationError};
|
||||
pub use hyperswitch_domain_models::{
|
||||
api,
|
||||
errors::api_error_response::{self, *},
|
||||
};
|
||||
|
||||
pub type PmResult<T> = CustomResult<T, ApiErrorResponse>;
|
||||
pub type PmResponse<T> = CustomResult<api::ApplicationResponse<T>, ApiErrorResponse>;
|
||||
pub type VaultResult<T> = CustomResult<T, VaultError>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum VaultError {
|
||||
#[error("Failed to save card in card vault")]
|
||||
SaveCardFailed,
|
||||
#[error("Failed to fetch card details from card vault")]
|
||||
FetchCardFailed,
|
||||
#[error("Failed to delete card in card vault")]
|
||||
DeleteCardFailed,
|
||||
#[error("Failed to encode card vault request")]
|
||||
RequestEncodingFailed,
|
||||
#[error("Failed to deserialize card vault response")]
|
||||
ResponseDeserializationFailed,
|
||||
#[error("Failed to create payment method")]
|
||||
PaymentMethodCreationFailed,
|
||||
#[error("The given payment method is currently not supported in vault")]
|
||||
PaymentMethodNotSupported,
|
||||
#[error("The given payout method is currently not supported in vault")]
|
||||
PayoutMethodNotSupported,
|
||||
#[error("Missing required field: {field_name}")]
|
||||
MissingRequiredField { field_name: &'static str },
|
||||
#[error("The card vault returned an unexpected response: {0:?}")]
|
||||
UnexpectedResponseError(bytes::Bytes),
|
||||
#[error("Failed to update in PMD table")]
|
||||
UpdateInPaymentMethodDataTableFailed,
|
||||
#[error("Failed to fetch payment method in vault")]
|
||||
FetchPaymentMethodFailed,
|
||||
#[error("Failed to save payment method in vault")]
|
||||
SavePaymentMethodFailed,
|
||||
#[error("Failed to generate fingerprint")]
|
||||
GenerateFingerprintFailed,
|
||||
#[error("Failed to encrypt vault request")]
|
||||
RequestEncryptionFailed,
|
||||
#[error("Failed to decrypt vault response")]
|
||||
ResponseDecryptionFailed,
|
||||
#[error("Failed to call vault")]
|
||||
VaultAPIError,
|
||||
#[error("Failed while calling locker API")]
|
||||
ApiError,
|
||||
}
|
||||
212
crates/payment_methods/src/core/migration.rs
Normal file
212
crates/payment_methods/src/core/migration.rs
Normal file
@ -0,0 +1,212 @@
|
||||
use actix_multipart::form::{self, bytes, text};
|
||||
use api_models::payment_methods as pm_api;
|
||||
use csv::Reader;
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{api, merchant_context};
|
||||
use masking::PeekInterface;
|
||||
use rdkafka::message::ToBytes;
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
use crate::{controller as pm, core::errors, state};
|
||||
pub mod payment_methods;
|
||||
pub use payment_methods::migrate_payment_method;
|
||||
|
||||
type PmMigrationResult<T> =
|
||||
errors::CustomResult<api::ApplicationResponse<T>, errors::ApiErrorResponse>;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v2", feature = "v1"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
pub async fn migrate_payment_methods(
|
||||
state: &state::PaymentMethodsState,
|
||||
payment_methods: Vec<pm_api::PaymentMethodRecord>,
|
||||
merchant_id: &common_utils::id_type::MerchantId,
|
||||
merchant_context: &merchant_context::MerchantContext,
|
||||
mca_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
|
||||
controller: &dyn pm::PaymentMethodsController,
|
||||
) -> PmMigrationResult<Vec<pm_api::PaymentMethodMigrationResponse>> {
|
||||
let mut result = Vec::new();
|
||||
for record in payment_methods {
|
||||
let req = pm_api::PaymentMethodMigrate::try_from((
|
||||
record.clone(),
|
||||
merchant_id.clone(),
|
||||
mca_id.clone(),
|
||||
))
|
||||
.map_err(|err| errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: format!("error: {:?}", err),
|
||||
})
|
||||
.attach_printable("record deserialization failed");
|
||||
let res = match req {
|
||||
Ok(migrate_request) => {
|
||||
let res = migrate_payment_method(
|
||||
state,
|
||||
migrate_request,
|
||||
merchant_id,
|
||||
merchant_context,
|
||||
controller,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(api::ApplicationResponse::Json(response)) => Ok(response),
|
||||
Err(e) => Err(e.to_string()),
|
||||
_ => Err("Failed to migrate payment method".to_string()),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
};
|
||||
result.push(pm_api::PaymentMethodMigrationResponse::from((res, record)));
|
||||
}
|
||||
Ok(api::ApplicationResponse::Json(result))
|
||||
}
|
||||
|
||||
#[derive(Debug, form::MultipartForm)]
|
||||
pub struct PaymentMethodsMigrateForm {
|
||||
#[multipart(limit = "1MB")]
|
||||
pub file: bytes::Bytes,
|
||||
|
||||
pub merchant_id: text::Text<common_utils::id_type::MerchantId>,
|
||||
|
||||
pub merchant_connector_id:
|
||||
text::Text<Option<common_utils::id_type::MerchantConnectorAccountId>>,
|
||||
}
|
||||
|
||||
fn parse_csv(data: &[u8]) -> csv::Result<Vec<pm_api::PaymentMethodRecord>> {
|
||||
let mut csv_reader = Reader::from_reader(data);
|
||||
let mut records = Vec::new();
|
||||
let mut id_counter = 0;
|
||||
for result in csv_reader.deserialize() {
|
||||
let mut record: pm_api::PaymentMethodRecord = result?;
|
||||
id_counter += 1;
|
||||
record.line_number = Some(id_counter);
|
||||
records.push(record);
|
||||
}
|
||||
Ok(records)
|
||||
}
|
||||
pub fn get_payment_method_records(
|
||||
form: PaymentMethodsMigrateForm,
|
||||
) -> Result<
|
||||
(
|
||||
common_utils::id_type::MerchantId,
|
||||
Vec<pm_api::PaymentMethodRecord>,
|
||||
Option<common_utils::id_type::MerchantConnectorAccountId>,
|
||||
),
|
||||
errors::ApiErrorResponse,
|
||||
> {
|
||||
match parse_csv(form.file.data.to_bytes()) {
|
||||
Ok(records) => {
|
||||
let merchant_id = form.merchant_id.clone();
|
||||
let mca_id = form.merchant_connector_id.clone();
|
||||
Ok((merchant_id.clone(), records, mca_id))
|
||||
}
|
||||
Err(e) => Err(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: e.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn validate_card_expiry(
|
||||
card_exp_month: &masking::Secret<String>,
|
||||
card_exp_year: &masking::Secret<String>,
|
||||
) -> errors::CustomResult<(), errors::ApiErrorResponse> {
|
||||
let exp_month = card_exp_month
|
||||
.peek()
|
||||
.to_string()
|
||||
.parse::<u8>()
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "card_exp_month",
|
||||
})?;
|
||||
::cards::CardExpirationMonth::try_from(exp_month).change_context(
|
||||
errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "Invalid Expiry Month".to_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let year_str = card_exp_year.peek().to_string();
|
||||
|
||||
validate_card_exp_year(year_str).change_context(
|
||||
errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "Invalid Expiry Year".to_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_card_exp_year(year: String) -> Result<(), errors::ValidationError> {
|
||||
let year_str = year.to_string();
|
||||
if year_str.len() == 2 || year_str.len() == 4 {
|
||||
year_str
|
||||
.parse::<u16>()
|
||||
.map_err(|_| errors::ValidationError::InvalidValue {
|
||||
message: "card_exp_year".to_string(),
|
||||
})?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors::ValidationError::InvalidValue {
|
||||
message: "invalid card expiration year".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RecordMigrationStatus {
|
||||
pub card_migrated: Option<bool>,
|
||||
pub network_token_migrated: Option<bool>,
|
||||
pub connector_mandate_details_migrated: Option<bool>,
|
||||
pub network_transaction_migrated: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RecordMigrationStatusBuilder {
|
||||
pub card_migrated: Option<bool>,
|
||||
pub network_token_migrated: Option<bool>,
|
||||
pub connector_mandate_details_migrated: Option<bool>,
|
||||
pub network_transaction_migrated: Option<bool>,
|
||||
}
|
||||
|
||||
impl RecordMigrationStatusBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
card_migrated: None,
|
||||
network_token_migrated: None,
|
||||
connector_mandate_details_migrated: None,
|
||||
network_transaction_migrated: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn card_migrated(&mut self, card_migrated: bool) {
|
||||
self.card_migrated = Some(card_migrated);
|
||||
}
|
||||
|
||||
pub fn network_token_migrated(&mut self, network_token_migrated: Option<bool>) {
|
||||
self.network_token_migrated = network_token_migrated;
|
||||
}
|
||||
|
||||
pub fn connector_mandate_details_migrated(
|
||||
&mut self,
|
||||
connector_mandate_details_migrated: Option<bool>,
|
||||
) {
|
||||
self.connector_mandate_details_migrated = connector_mandate_details_migrated;
|
||||
}
|
||||
|
||||
pub fn network_transaction_id_migrated(&mut self, network_transaction_migrated: Option<bool>) {
|
||||
self.network_transaction_migrated = network_transaction_migrated;
|
||||
}
|
||||
|
||||
pub fn build(self) -> RecordMigrationStatus {
|
||||
RecordMigrationStatus {
|
||||
card_migrated: self.card_migrated,
|
||||
network_token_migrated: self.network_token_migrated,
|
||||
connector_mandate_details_migrated: self.connector_mandate_details_migrated,
|
||||
network_transaction_migrated: self.network_transaction_migrated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RecordMigrationStatusBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
710
crates/payment_methods/src/core/migration/payment_methods.rs
Normal file
710
crates/payment_methods/src/core/migration/payment_methods.rs
Normal file
@ -0,0 +1,710 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
use api_models::enums as api_enums;
|
||||
use api_models::{enums, payment_methods as pm_api};
|
||||
use common_utils::{
|
||||
consts,
|
||||
crypto::Encryptable,
|
||||
errors::CustomResult,
|
||||
ext_traits::{AsyncExt, ConfigExt},
|
||||
generate_id, id_type,
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{
|
||||
api::ApplicationResponse, errors::api_error_response as errors, ext_traits::OptionExt,
|
||||
merchant_context, payment_methods as domain_pm,
|
||||
};
|
||||
use masking::{PeekInterface, Secret};
|
||||
use router_env::{instrument, logger, tracing};
|
||||
use serde_json::json;
|
||||
use storage_impl::cards_info;
|
||||
|
||||
use crate::{
|
||||
controller::{create_encrypted_data, PaymentMethodsController},
|
||||
core::migration,
|
||||
helpers::{ForeignFrom, ForeignTryFrom, StorageErrorExt},
|
||||
state,
|
||||
};
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
pub async fn migrate_payment_method(
|
||||
state: &state::PaymentMethodsState,
|
||||
req: pm_api::PaymentMethodMigrate,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
merchant_context: &merchant_context::MerchantContext,
|
||||
controller: &dyn PaymentMethodsController,
|
||||
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodMigrateResponse>, errors::ApiErrorResponse>
|
||||
{
|
||||
let mut req = req;
|
||||
let card_details = &req.card.get_required_value("card")?;
|
||||
|
||||
let card_number_validation_result =
|
||||
cards::CardNumber::from_str(card_details.card_number.peek());
|
||||
|
||||
let card_bin_details =
|
||||
populate_bin_details_for_masked_card(card_details, &*state.store).await?;
|
||||
|
||||
req.card = Some(api_models::payment_methods::MigrateCardDetail {
|
||||
card_issuing_country: card_bin_details.issuer_country.clone(),
|
||||
card_network: card_bin_details.card_network.clone(),
|
||||
card_issuer: card_bin_details.card_issuer.clone(),
|
||||
card_type: card_bin_details.card_type.clone(),
|
||||
..card_details.clone()
|
||||
});
|
||||
|
||||
if let Some(connector_mandate_details) = &req.connector_mandate_details {
|
||||
controller
|
||||
.validate_merchant_connector_ids_in_connector_mandate_details(
|
||||
merchant_context.get_merchant_key_store(),
|
||||
connector_mandate_details,
|
||||
merchant_id,
|
||||
card_bin_details.card_network.clone(),
|
||||
)
|
||||
.await?;
|
||||
};
|
||||
|
||||
let should_require_connector_mandate_details = req.network_token.is_none();
|
||||
|
||||
let mut migration_status = migration::RecordMigrationStatusBuilder::new();
|
||||
|
||||
let resp = match card_number_validation_result {
|
||||
Ok(card_number) => {
|
||||
let payment_method_create_request =
|
||||
pm_api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate(
|
||||
card_number,
|
||||
&req,
|
||||
);
|
||||
|
||||
logger::debug!("Storing the card in locker and migrating the payment method");
|
||||
get_client_secret_or_add_payment_method_for_migration(
|
||||
state,
|
||||
payment_method_create_request,
|
||||
merchant_context,
|
||||
&mut migration_status,
|
||||
controller,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Err(card_validation_error) => {
|
||||
logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}");
|
||||
skip_locker_call_and_migrate_payment_method(
|
||||
state,
|
||||
&req,
|
||||
merchant_id.to_owned(),
|
||||
merchant_context,
|
||||
card_bin_details.clone(),
|
||||
should_require_connector_mandate_details,
|
||||
&mut migration_status,
|
||||
controller,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
let payment_method_response = match resp {
|
||||
ApplicationResponse::Json(response) => response,
|
||||
_ => Err(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to fetch the payment method response")?,
|
||||
};
|
||||
|
||||
let pm_id = payment_method_response.payment_method_id.clone();
|
||||
|
||||
let network_token = req.network_token.clone();
|
||||
|
||||
let network_token_migrated = match network_token {
|
||||
Some(nt_detail) => {
|
||||
logger::debug!("Network token migration");
|
||||
let network_token_requestor_ref_id = nt_detail.network_token_requestor_ref_id.clone();
|
||||
let network_token_data = &nt_detail.network_token_data;
|
||||
|
||||
Some(
|
||||
controller
|
||||
.save_network_token_and_update_payment_method(
|
||||
&req,
|
||||
merchant_context.get_merchant_key_store(),
|
||||
network_token_data,
|
||||
network_token_requestor_ref_id,
|
||||
pm_id,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| logger::error!(?err, "Failed to save network token"))
|
||||
.ok()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
logger::debug!("Network token data is not available");
|
||||
None
|
||||
}
|
||||
};
|
||||
migration_status.network_token_migrated(network_token_migrated);
|
||||
let migrate_status = migration_status.build();
|
||||
|
||||
Ok(ApplicationResponse::Json(
|
||||
pm_api::PaymentMethodMigrateResponse {
|
||||
payment_method_response,
|
||||
card_migrated: migrate_status.card_migrated,
|
||||
network_token_migrated: migrate_status.network_token_migrated,
|
||||
connector_mandate_details_migrated: migrate_status.connector_mandate_details_migrated,
|
||||
network_transaction_id_migrated: migrate_status.network_transaction_migrated,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
pub async fn migrate_payment_method(
|
||||
_state: &state::PaymentMethodsState,
|
||||
_req: pm_api::PaymentMethodMigrate,
|
||||
_merchant_id: &id_type::MerchantId,
|
||||
merchant_context: &merchant_context::MerchantContext,
|
||||
controller: &dyn PaymentMethodsController,
|
||||
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodMigrateResponse>, errors::ApiErrorResponse>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
pub async fn populate_bin_details_for_masked_card(
|
||||
card_details: &api_models::payment_methods::MigrateCardDetail,
|
||||
db: &dyn state::PaymentMethodsStorageInterface,
|
||||
) -> CustomResult<pm_api::CardDetailFromLocker, errors::ApiErrorResponse> {
|
||||
migration::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year)?;
|
||||
let card_number = card_details.card_number.clone();
|
||||
|
||||
let (card_isin, _last4_digits) = get_card_bin_and_last4_digits_for_masked_card(
|
||||
card_number.peek(),
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "Invalid card number".to_string(),
|
||||
})?;
|
||||
|
||||
let card_bin_details = if card_details.card_issuer.is_some()
|
||||
&& card_details.card_network.is_some()
|
||||
&& card_details.card_type.is_some()
|
||||
&& card_details.card_issuing_country.is_some()
|
||||
{
|
||||
pm_api::CardDetailFromLocker::foreign_try_from((card_details, None))?
|
||||
} else {
|
||||
let card_info = db
|
||||
.get_card_info(&card_isin)
|
||||
.await
|
||||
.map_err(|error| logger::error!(card_info_error=?error))
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
pm_api::CardDetailFromLocker::foreign_try_from((card_details, card_info))?
|
||||
};
|
||||
Ok(card_bin_details)
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
impl
|
||||
ForeignTryFrom<(
|
||||
&api_models::payment_methods::MigrateCardDetail,
|
||||
Option<cards_info::CardInfo>,
|
||||
)> for pm_api::CardDetailFromLocker
|
||||
{
|
||||
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||
fn foreign_try_from(
|
||||
(card_details, card_info): (
|
||||
&api_models::payment_methods::MigrateCardDetail,
|
||||
Option<cards_info::CardInfo>,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let (card_isin, last4_digits) =
|
||||
get_card_bin_and_last4_digits_for_masked_card(card_details.card_number.peek())
|
||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "Invalid card number".to_string(),
|
||||
})?;
|
||||
if let Some(card_bin_info) = card_info {
|
||||
Ok(Self {
|
||||
scheme: card_details
|
||||
.card_network
|
||||
.clone()
|
||||
.or(card_bin_info.card_network.clone())
|
||||
.map(|card_network| card_network.to_string()),
|
||||
last4_digits: Some(last4_digits.clone()),
|
||||
issuer_country: card_details
|
||||
.card_issuing_country
|
||||
.clone()
|
||||
.or(card_bin_info.card_issuing_country),
|
||||
card_number: None,
|
||||
expiry_month: Some(card_details.card_exp_month.clone()),
|
||||
expiry_year: Some(card_details.card_exp_year.clone()),
|
||||
card_token: None,
|
||||
card_fingerprint: None,
|
||||
card_holder_name: card_details.card_holder_name.clone(),
|
||||
nick_name: card_details.nick_name.clone(),
|
||||
card_isin: Some(card_isin.clone()),
|
||||
card_issuer: card_details
|
||||
.card_issuer
|
||||
.clone()
|
||||
.or(card_bin_info.card_issuer),
|
||||
card_network: card_details
|
||||
.card_network
|
||||
.clone()
|
||||
.or(card_bin_info.card_network),
|
||||
card_type: card_details.card_type.clone().or(card_bin_info.card_type),
|
||||
saved_to_locker: false,
|
||||
})
|
||||
} else {
|
||||
Ok(Self {
|
||||
scheme: card_details
|
||||
.card_network
|
||||
.clone()
|
||||
.map(|card_network| card_network.to_string()),
|
||||
last4_digits: Some(last4_digits.clone()),
|
||||
issuer_country: card_details.card_issuing_country.clone(),
|
||||
card_number: None,
|
||||
expiry_month: Some(card_details.card_exp_month.clone()),
|
||||
expiry_year: Some(card_details.card_exp_year.clone()),
|
||||
card_token: None,
|
||||
card_fingerprint: None,
|
||||
card_holder_name: card_details.card_holder_name.clone(),
|
||||
nick_name: card_details.nick_name.clone(),
|
||||
card_isin: Some(card_isin.clone()),
|
||||
card_issuer: card_details.card_issuer.clone(),
|
||||
card_network: card_details.card_network.clone(),
|
||||
card_type: card_details.card_type.clone(),
|
||||
saved_to_locker: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
impl
|
||||
ForeignTryFrom<(
|
||||
&api_models::payment_methods::MigrateCardDetail,
|
||||
Option<cards_info::CardInfo>,
|
||||
)> for pm_api::CardDetailFromLocker
|
||||
{
|
||||
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||
fn foreign_try_from(
|
||||
(card_details, card_info): (
|
||||
&api_models::payment_methods::MigrateCardDetail,
|
||||
Option<cards_info::CardInfo>,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let (card_isin, last4_digits) =
|
||||
get_card_bin_and_last4_digits_for_masked_card(card_details.card_number.peek())
|
||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "Invalid card number".to_string(),
|
||||
})?;
|
||||
if let Some(card_bin_info) = card_info {
|
||||
Ok(Self {
|
||||
last4_digits: Some(last4_digits.clone()),
|
||||
issuer_country: card_details
|
||||
.card_issuing_country
|
||||
.as_ref()
|
||||
.map(|c| api_enums::CountryAlpha2::from_str(c))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
.or(card_bin_info
|
||||
.card_issuing_country
|
||||
.as_ref()
|
||||
.map(|c| api_enums::CountryAlpha2::from_str(c))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()),
|
||||
card_number: None,
|
||||
expiry_month: Some(card_details.card_exp_month.clone()),
|
||||
expiry_year: Some(card_details.card_exp_year.clone()),
|
||||
card_fingerprint: None,
|
||||
card_holder_name: card_details.card_holder_name.clone(),
|
||||
nick_name: card_details.nick_name.clone(),
|
||||
card_isin: Some(card_isin.clone()),
|
||||
card_issuer: card_details
|
||||
.card_issuer
|
||||
.clone()
|
||||
.or(card_bin_info.card_issuer),
|
||||
card_network: card_details
|
||||
.card_network
|
||||
.clone()
|
||||
.or(card_bin_info.card_network),
|
||||
card_type: card_details.card_type.clone().or(card_bin_info.card_type),
|
||||
saved_to_locker: false,
|
||||
})
|
||||
} else {
|
||||
Ok(Self {
|
||||
last4_digits: Some(last4_digits.clone()),
|
||||
issuer_country: card_details
|
||||
.card_issuing_country
|
||||
.as_ref()
|
||||
.map(|c| api_enums::CountryAlpha2::from_str(c))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten(),
|
||||
card_number: None,
|
||||
expiry_month: Some(card_details.card_exp_month.clone()),
|
||||
expiry_year: Some(card_details.card_exp_year.clone()),
|
||||
card_fingerprint: None,
|
||||
card_holder_name: card_details.card_holder_name.clone(),
|
||||
nick_name: card_details.nick_name.clone(),
|
||||
card_isin: Some(card_isin.clone()),
|
||||
card_issuer: card_details.card_issuer.clone(),
|
||||
card_network: card_details.card_network.clone(),
|
||||
card_type: card_details.card_type.clone(),
|
||||
saved_to_locker: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_client_secret_or_add_payment_method_for_migration(
|
||||
state: &state::PaymentMethodsState,
|
||||
req: pm_api::PaymentMethodCreate,
|
||||
merchant_context: &merchant_context::MerchantContext,
|
||||
migration_status: &mut migration::RecordMigrationStatusBuilder,
|
||||
controller: &dyn PaymentMethodsController,
|
||||
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
|
||||
let merchant_id = merchant_context.get_merchant_account().get_id();
|
||||
let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
|
||||
|
||||
#[cfg(not(feature = "payouts"))]
|
||||
let condition = req.card.is_some();
|
||||
#[cfg(feature = "payouts")]
|
||||
let condition = req.card.is_some() || req.bank_transfer.is_some() || req.wallet.is_some();
|
||||
let key_manager_state = &state.into();
|
||||
|
||||
let payment_method_billing_address: Option<Encryptable<Secret<serde_json::Value>>> = req
|
||||
.billing
|
||||
.clone()
|
||||
.async_map(|billing| {
|
||||
create_encrypted_data(
|
||||
key_manager_state,
|
||||
merchant_context.get_merchant_key_store(),
|
||||
billing,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.transpose()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Unable to encrypt Payment method billing address")?;
|
||||
|
||||
let connector_mandate_details = req
|
||||
.connector_mandate_details
|
||||
.clone()
|
||||
.map(serde_json::to_value)
|
||||
.transpose()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
if condition {
|
||||
Box::pin(save_migration_payment_method(
|
||||
req,
|
||||
migration_status,
|
||||
controller,
|
||||
))
|
||||
.await
|
||||
} else {
|
||||
let payment_method_id = generate_id(consts::ID_LENGTH, "pm");
|
||||
|
||||
let res = controller
|
||||
.create_payment_method(
|
||||
&req,
|
||||
&customer_id,
|
||||
payment_method_id.as_str(),
|
||||
None,
|
||||
merchant_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
connector_mandate_details.clone(),
|
||||
Some(enums::PaymentMethodStatus::AwaitingData),
|
||||
None,
|
||||
payment_method_billing_address,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
migration_status.connector_mandate_details_migrated(
|
||||
connector_mandate_details
|
||||
.clone()
|
||||
.and_then(|val| (val != json!({})).then_some(true))
|
||||
.or_else(|| {
|
||||
req.connector_mandate_details
|
||||
.clone()
|
||||
.and_then(|val| (!val.0.is_empty()).then_some(false))
|
||||
}),
|
||||
);
|
||||
//card is not migrated in this case
|
||||
migration_status.card_migrated(false);
|
||||
|
||||
if res.status == enums::PaymentMethodStatus::AwaitingData {
|
||||
controller
|
||||
.add_payment_method_status_update_task(
|
||||
&res,
|
||||
enums::PaymentMethodStatus::AwaitingData,
|
||||
enums::PaymentMethodStatus::Inactive,
|
||||
merchant_id,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable(
|
||||
"Failed to add payment method status update task in process tracker",
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(ApplicationResponse::Json(
|
||||
pm_api::PaymentMethodResponse::foreign_from((None, res)),
|
||||
))
|
||||
}
|
||||
}
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2"),
|
||||
not(feature = "customer_v2")
|
||||
))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn skip_locker_call_and_migrate_payment_method(
|
||||
state: &state::PaymentMethodsState,
|
||||
req: &pm_api::PaymentMethodMigrate,
|
||||
merchant_id: id_type::MerchantId,
|
||||
merchant_context: &merchant_context::MerchantContext,
|
||||
card: pm_api::CardDetailFromLocker,
|
||||
should_require_connector_mandate_details: bool,
|
||||
migration_status: &mut migration::RecordMigrationStatusBuilder,
|
||||
controller: &dyn PaymentMethodsController,
|
||||
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
|
||||
let db = &*state.store;
|
||||
let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
|
||||
|
||||
// In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details.
|
||||
//if network token data is present, then connector mandate details are not mandatory
|
||||
|
||||
let connector_mandate_details = if should_require_connector_mandate_details {
|
||||
let connector_mandate_details_req = req
|
||||
.connector_mandate_details
|
||||
.clone()
|
||||
.and_then(|c| c.payments)
|
||||
.clone()
|
||||
.get_required_value("connector mandate details")?;
|
||||
|
||||
Some(
|
||||
serde_json::to_value(&connector_mandate_details_req)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse connector mandate details")?,
|
||||
)
|
||||
} else {
|
||||
req.connector_mandate_details
|
||||
.clone()
|
||||
.and_then(|c| c.payments)
|
||||
.map(|mandate_details_req| {
|
||||
serde_json::to_value(&mandate_details_req)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse connector mandate details")
|
||||
})
|
||||
.transpose()?
|
||||
};
|
||||
let key_manager_state = &state.into();
|
||||
let payment_method_billing_address: Option<Encryptable<Secret<serde_json::Value>>> = req
|
||||
.billing
|
||||
.clone()
|
||||
.async_map(|billing| {
|
||||
create_encrypted_data(
|
||||
key_manager_state,
|
||||
merchant_context.get_merchant_key_store(),
|
||||
billing,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.transpose()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Unable to encrypt Payment method billing address")?;
|
||||
|
||||
let customer = db
|
||||
.find_customer_by_customer_id_merchant_id(
|
||||
&state.into(),
|
||||
&customer_id,
|
||||
&merchant_id,
|
||||
merchant_context.get_merchant_key_store(),
|
||||
merchant_context.get_merchant_account().storage_scheme,
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
|
||||
|
||||
let payment_method_card_details =
|
||||
pm_api::PaymentMethodsData::Card(pm_api::CardDetailsPaymentMethod::from(card.clone()));
|
||||
|
||||
let payment_method_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> = Some(
|
||||
create_encrypted_data(
|
||||
&state.into(),
|
||||
merchant_context.get_merchant_key_store(),
|
||||
payment_method_card_details,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Unable to encrypt Payment method card details")?,
|
||||
);
|
||||
|
||||
let payment_method_metadata: Option<serde_json::Value> =
|
||||
req.metadata.as_ref().map(|data| data.peek()).cloned();
|
||||
|
||||
let network_transaction_id = req.network_transaction_id.clone();
|
||||
|
||||
let payment_method_id = generate_id(consts::ID_LENGTH, "pm");
|
||||
|
||||
let current_time = common_utils::date_time::now();
|
||||
|
||||
let response = db
|
||||
.insert_payment_method(
|
||||
&state.into(),
|
||||
merchant_context.get_merchant_key_store(),
|
||||
domain_pm::PaymentMethod {
|
||||
customer_id: customer_id.to_owned(),
|
||||
merchant_id: merchant_id.to_owned(),
|
||||
payment_method_id: payment_method_id.to_string(),
|
||||
locker_id: None,
|
||||
payment_method: req.payment_method,
|
||||
payment_method_type: req.payment_method_type,
|
||||
payment_method_issuer: req.payment_method_issuer.clone(),
|
||||
scheme: req.card_network.clone().or(card.scheme.clone()),
|
||||
metadata: payment_method_metadata.map(Secret::new),
|
||||
payment_method_data: payment_method_data_encrypted,
|
||||
connector_mandate_details: connector_mandate_details.clone(),
|
||||
customer_acceptance: None,
|
||||
client_secret: None,
|
||||
status: enums::PaymentMethodStatus::Active,
|
||||
network_transaction_id: network_transaction_id.clone(),
|
||||
payment_method_issuer_code: None,
|
||||
accepted_currency: None,
|
||||
token: None,
|
||||
cardholder_name: None,
|
||||
issuer_name: None,
|
||||
issuer_country: None,
|
||||
payer_country: None,
|
||||
is_stored: None,
|
||||
swift_code: None,
|
||||
direct_debit_token: None,
|
||||
created_at: current_time,
|
||||
last_modified: current_time,
|
||||
last_used_at: current_time,
|
||||
payment_method_billing_address,
|
||||
updated_by: None,
|
||||
version: common_types::consts::API_VERSION,
|
||||
network_token_requestor_reference_id: None,
|
||||
network_token_locker_id: None,
|
||||
network_token_payment_method_data: None,
|
||||
},
|
||||
merchant_context.get_merchant_account().storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to add payment method in db")?;
|
||||
|
||||
logger::debug!("Payment method inserted in db");
|
||||
|
||||
migration_status.network_transaction_id_migrated(
|
||||
network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)),
|
||||
);
|
||||
|
||||
migration_status.connector_mandate_details_migrated(
|
||||
connector_mandate_details
|
||||
.clone()
|
||||
.and_then(|val| if val == json!({}) { None } else { Some(true) })
|
||||
.or_else(|| {
|
||||
req.connector_mandate_details.clone().and_then(|val| {
|
||||
val.payments
|
||||
.and_then(|payin_val| (!payin_val.0.is_empty()).then_some(false))
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
if customer.default_payment_method_id.is_none() && req.payment_method.is_some() {
|
||||
let _ = controller
|
||||
.set_default_payment_method(&merchant_id, &customer_id, payment_method_id.to_owned())
|
||||
.await
|
||||
.map_err(|error| logger::error!(?error, "Failed to set the payment method as default"));
|
||||
}
|
||||
Ok(ApplicationResponse::Json(
|
||||
pm_api::PaymentMethodResponse::foreign_from((Some(card), response)),
|
||||
))
|
||||
}
|
||||
|
||||
// need to discuss regarding the migration APIs for v2
|
||||
#[cfg(all(
|
||||
feature = "v2",
|
||||
feature = "payment_methods_v2",
|
||||
feature = "customer_v2"
|
||||
))]
|
||||
pub async fn skip_locker_call_and_migrate_payment_method(
|
||||
_state: state::PaymentMethodsState,
|
||||
_req: &pm_api::PaymentMethodMigrate,
|
||||
_merchant_id: id_type::MerchantId,
|
||||
_merchant_context: &merchant_context::MerchantContext,
|
||||
_card: pm_api::CardDetailFromLocker,
|
||||
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
|
||||
todo!()
|
||||
}
|
||||
pub fn get_card_bin_and_last4_digits_for_masked_card(
|
||||
masked_card_number: &str,
|
||||
) -> Result<(String, String), cards::CardNumberValidationErr> {
|
||||
let last4_digits = masked_card_number
|
||||
.chars()
|
||||
.rev()
|
||||
.take(4)
|
||||
.collect::<String>()
|
||||
.chars()
|
||||
.rev()
|
||||
.collect::<String>();
|
||||
|
||||
let card_isin = masked_card_number.chars().take(6).collect::<String>();
|
||||
|
||||
cards::validate::validate_card_number_chars(&card_isin)
|
||||
.and_then(|_| cards::validate::validate_card_number_chars(&last4_digits))?;
|
||||
|
||||
Ok((card_isin, last4_digits))
|
||||
}
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn save_migration_payment_method(
|
||||
req: pm_api::PaymentMethodCreate,
|
||||
migration_status: &mut migration::RecordMigrationStatusBuilder,
|
||||
controller: &dyn PaymentMethodsController,
|
||||
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
|
||||
let connector_mandate_details = req
|
||||
.connector_mandate_details
|
||||
.clone()
|
||||
.map(serde_json::to_value)
|
||||
.transpose()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
let network_transaction_id = req.network_transaction_id.clone();
|
||||
|
||||
let res = controller.add_payment_method(&req).await?;
|
||||
|
||||
migration_status.card_migrated(true);
|
||||
migration_status.network_transaction_id_migrated(
|
||||
network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)),
|
||||
);
|
||||
|
||||
migration_status.connector_mandate_details_migrated(
|
||||
connector_mandate_details
|
||||
.and_then(|val| if val == json!({}) { None } else { Some(true) })
|
||||
.or_else(|| {
|
||||
req.connector_mandate_details
|
||||
.and_then(|val| (!val.0.is_empty()).then_some(false))
|
||||
}),
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
330
crates/payment_methods/src/helpers.rs
Normal file
330
crates/payment_methods/src/helpers.rs
Normal file
@ -0,0 +1,330 @@
|
||||
use api_models::{enums as api_enums, payment_methods as api};
|
||||
use common_utils::ext_traits::AsyncExt;
|
||||
pub use hyperswitch_domain_models::{errors::api_error_response, payment_methods as domain};
|
||||
use router_env::logger;
|
||||
|
||||
use crate::state;
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
pub async fn populate_bin_details_for_payment_method_create(
|
||||
card_details: api_models::payment_methods::CardDetail,
|
||||
db: Box<dyn state::PaymentMethodsStorageInterface>,
|
||||
) -> api_models::payment_methods::CardDetail {
|
||||
let card_isin: Option<_> = Some(card_details.card_number.get_card_isin());
|
||||
if card_details.card_issuer.is_some()
|
||||
&& card_details.card_network.is_some()
|
||||
&& card_details.card_type.is_some()
|
||||
&& card_details.card_issuing_country.is_some()
|
||||
{
|
||||
api::CardDetail {
|
||||
card_issuer: card_details.card_issuer.to_owned(),
|
||||
card_network: card_details.card_network.clone(),
|
||||
card_type: card_details.card_type.to_owned(),
|
||||
card_issuing_country: card_details.card_issuing_country.to_owned(),
|
||||
card_exp_month: card_details.card_exp_month.clone(),
|
||||
card_exp_year: card_details.card_exp_year.clone(),
|
||||
card_holder_name: card_details.card_holder_name.clone(),
|
||||
card_number: card_details.card_number.clone(),
|
||||
nick_name: card_details.nick_name.clone(),
|
||||
}
|
||||
} else {
|
||||
let card_info = card_isin
|
||||
.clone()
|
||||
.async_and_then(|card_isin| async move {
|
||||
db.get_card_info(&card_isin)
|
||||
.await
|
||||
.map_err(|error| logger::error!(card_info_error=?error))
|
||||
.ok()
|
||||
})
|
||||
.await
|
||||
.flatten()
|
||||
.map(|card_info| api::CardDetail {
|
||||
card_issuer: card_info.card_issuer,
|
||||
card_network: card_info.card_network.clone(),
|
||||
card_type: card_info.card_type,
|
||||
card_issuing_country: card_info.card_issuing_country,
|
||||
card_exp_month: card_details.card_exp_month.clone(),
|
||||
card_exp_year: card_details.card_exp_year.clone(),
|
||||
card_holder_name: card_details.card_holder_name.clone(),
|
||||
card_number: card_details.card_number.clone(),
|
||||
nick_name: card_details.nick_name.clone(),
|
||||
});
|
||||
card_info.unwrap_or_else(|| api::CardDetail {
|
||||
card_issuer: None,
|
||||
card_network: None,
|
||||
card_type: None,
|
||||
card_issuing_country: None,
|
||||
card_exp_month: card_details.card_exp_month.clone(),
|
||||
card_exp_year: card_details.card_exp_year.clone(),
|
||||
card_holder_name: card_details.card_holder_name.clone(),
|
||||
card_number: card_details.card_number.clone(),
|
||||
nick_name: card_details.nick_name.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
pub async fn populate_bin_details_for_payment_method_create(
|
||||
_card_details: api_models::payment_methods::CardDetail,
|
||||
_db: &dyn state::PaymentMethodsStorageInterface,
|
||||
) -> api_models::payment_methods::CardDetail {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn validate_payment_method_type_against_payment_method(
|
||||
payment_method: api_enums::PaymentMethod,
|
||||
payment_method_type: api_enums::PaymentMethodType,
|
||||
) -> bool {
|
||||
match payment_method {
|
||||
#[cfg(feature = "v1")]
|
||||
api_enums::PaymentMethod::Card => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit
|
||||
),
|
||||
#[cfg(feature = "v2")]
|
||||
api_enums::PaymentMethod::Card => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Credit
|
||||
| api_enums::PaymentMethodType::Debit
|
||||
| api_enums::PaymentMethodType::Card
|
||||
),
|
||||
api_enums::PaymentMethod::PayLater => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Affirm
|
||||
| api_enums::PaymentMethodType::Alma
|
||||
| api_enums::PaymentMethodType::AfterpayClearpay
|
||||
| api_enums::PaymentMethodType::Klarna
|
||||
| api_enums::PaymentMethodType::PayBright
|
||||
| api_enums::PaymentMethodType::Atome
|
||||
| api_enums::PaymentMethodType::Walley
|
||||
),
|
||||
api_enums::PaymentMethod::Wallet => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::AmazonPay
|
||||
| api_enums::PaymentMethodType::ApplePay
|
||||
| api_enums::PaymentMethodType::GooglePay
|
||||
| api_enums::PaymentMethodType::Paypal
|
||||
| api_enums::PaymentMethodType::AliPay
|
||||
| api_enums::PaymentMethodType::AliPayHk
|
||||
| api_enums::PaymentMethodType::Dana
|
||||
| api_enums::PaymentMethodType::MbWay
|
||||
| api_enums::PaymentMethodType::MobilePay
|
||||
| api_enums::PaymentMethodType::SamsungPay
|
||||
| api_enums::PaymentMethodType::Twint
|
||||
| api_enums::PaymentMethodType::Vipps
|
||||
| api_enums::PaymentMethodType::TouchNGo
|
||||
| api_enums::PaymentMethodType::Swish
|
||||
| api_enums::PaymentMethodType::WeChatPay
|
||||
| api_enums::PaymentMethodType::GoPay
|
||||
| api_enums::PaymentMethodType::Gcash
|
||||
| api_enums::PaymentMethodType::Momo
|
||||
| api_enums::PaymentMethodType::KakaoPay
|
||||
| api_enums::PaymentMethodType::Cashapp
|
||||
| api_enums::PaymentMethodType::Mifinity
|
||||
| api_enums::PaymentMethodType::Paze
|
||||
),
|
||||
api_enums::PaymentMethod::BankRedirect => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Giropay
|
||||
| api_enums::PaymentMethodType::Ideal
|
||||
| api_enums::PaymentMethodType::Sofort
|
||||
| api_enums::PaymentMethodType::Eft
|
||||
| api_enums::PaymentMethodType::Eps
|
||||
| api_enums::PaymentMethodType::BancontactCard
|
||||
| api_enums::PaymentMethodType::Blik
|
||||
| api_enums::PaymentMethodType::LocalBankRedirect
|
||||
| api_enums::PaymentMethodType::OnlineBankingThailand
|
||||
| api_enums::PaymentMethodType::OnlineBankingCzechRepublic
|
||||
| api_enums::PaymentMethodType::OnlineBankingFinland
|
||||
| api_enums::PaymentMethodType::OnlineBankingFpx
|
||||
| api_enums::PaymentMethodType::OnlineBankingPoland
|
||||
| api_enums::PaymentMethodType::OnlineBankingSlovakia
|
||||
| api_enums::PaymentMethodType::Przelewy24
|
||||
| api_enums::PaymentMethodType::Trustly
|
||||
| api_enums::PaymentMethodType::Bizum
|
||||
| api_enums::PaymentMethodType::Interac
|
||||
| api_enums::PaymentMethodType::OpenBankingUk
|
||||
| api_enums::PaymentMethodType::OpenBankingPIS
|
||||
),
|
||||
api_enums::PaymentMethod::BankTransfer => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Ach
|
||||
| api_enums::PaymentMethodType::SepaBankTransfer
|
||||
| api_enums::PaymentMethodType::Bacs
|
||||
| api_enums::PaymentMethodType::Multibanco
|
||||
| api_enums::PaymentMethodType::Pix
|
||||
| api_enums::PaymentMethodType::Pse
|
||||
| api_enums::PaymentMethodType::PermataBankTransfer
|
||||
| api_enums::PaymentMethodType::BcaBankTransfer
|
||||
| api_enums::PaymentMethodType::BniVa
|
||||
| api_enums::PaymentMethodType::BriVa
|
||||
| api_enums::PaymentMethodType::CimbVa
|
||||
| api_enums::PaymentMethodType::DanamonVa
|
||||
| api_enums::PaymentMethodType::MandiriVa
|
||||
| api_enums::PaymentMethodType::LocalBankTransfer
|
||||
| api_enums::PaymentMethodType::InstantBankTransfer
|
||||
),
|
||||
api_enums::PaymentMethod::BankDebit => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Ach
|
||||
| api_enums::PaymentMethodType::Sepa
|
||||
| api_enums::PaymentMethodType::Bacs
|
||||
| api_enums::PaymentMethodType::Becs
|
||||
),
|
||||
api_enums::PaymentMethod::Crypto => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::CryptoCurrency
|
||||
),
|
||||
api_enums::PaymentMethod::Reward => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Evoucher | api_enums::PaymentMethodType::ClassicReward
|
||||
),
|
||||
api_enums::PaymentMethod::RealTimePayment => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Fps
|
||||
| api_enums::PaymentMethodType::DuitNow
|
||||
| api_enums::PaymentMethodType::PromptPay
|
||||
| api_enums::PaymentMethodType::VietQr
|
||||
),
|
||||
api_enums::PaymentMethod::Upi => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::UpiCollect | api_enums::PaymentMethodType::UpiIntent
|
||||
),
|
||||
api_enums::PaymentMethod::Voucher => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Boleto
|
||||
| api_enums::PaymentMethodType::Efecty
|
||||
| api_enums::PaymentMethodType::PagoEfectivo
|
||||
| api_enums::PaymentMethodType::RedCompra
|
||||
| api_enums::PaymentMethodType::RedPagos
|
||||
| api_enums::PaymentMethodType::Indomaret
|
||||
| api_enums::PaymentMethodType::Alfamart
|
||||
| api_enums::PaymentMethodType::Oxxo
|
||||
| api_enums::PaymentMethodType::SevenEleven
|
||||
| api_enums::PaymentMethodType::Lawson
|
||||
| api_enums::PaymentMethodType::MiniStop
|
||||
| api_enums::PaymentMethodType::FamilyMart
|
||||
| api_enums::PaymentMethodType::Seicomart
|
||||
| api_enums::PaymentMethodType::PayEasy
|
||||
),
|
||||
api_enums::PaymentMethod::GiftCard => {
|
||||
matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Givex | api_enums::PaymentMethodType::PaySafeCard
|
||||
)
|
||||
}
|
||||
api_enums::PaymentMethod::CardRedirect => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::Knet
|
||||
| api_enums::PaymentMethodType::Benefit
|
||||
| api_enums::PaymentMethodType::MomoAtm
|
||||
| api_enums::PaymentMethodType::CardRedirect
|
||||
),
|
||||
api_enums::PaymentMethod::OpenBanking => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::OpenBankingPIS
|
||||
),
|
||||
api_enums::PaymentMethod::MobilePayment => matches!(
|
||||
payment_method_type,
|
||||
api_enums::PaymentMethodType::DirectCarrierBilling
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ForeignFrom<F> {
|
||||
fn foreign_from(from: F) -> Self;
|
||||
}
|
||||
|
||||
/// Trait for converting from one foreign type to another
|
||||
pub trait ForeignTryFrom<F>: Sized {
|
||||
/// Custom error for conversion failure
|
||||
type Error;
|
||||
/// Convert from a foreign type to the current type and return an error if the conversion fails
|
||||
fn foreign_try_from(from: F) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
impl ForeignFrom<(Option<api::CardDetailFromLocker>, domain::PaymentMethod)>
|
||||
for api::PaymentMethodResponse
|
||||
{
|
||||
fn foreign_from(
|
||||
(card_details, item): (Option<api::CardDetailFromLocker>, domain::PaymentMethod),
|
||||
) -> Self {
|
||||
Self {
|
||||
merchant_id: item.merchant_id.to_owned(),
|
||||
customer_id: Some(item.customer_id.to_owned()),
|
||||
payment_method_id: item.get_id().clone(),
|
||||
payment_method: item.get_payment_method_type(),
|
||||
payment_method_type: item.get_payment_method_subtype(),
|
||||
card: card_details,
|
||||
recurring_enabled: false,
|
||||
installment_payment_enabled: false,
|
||||
payment_experience: None,
|
||||
metadata: item.metadata,
|
||||
created: Some(item.created_at),
|
||||
#[cfg(feature = "payouts")]
|
||||
bank_transfer: None,
|
||||
last_used_at: None,
|
||||
client_secret: item.client_secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
impl ForeignFrom<(Option<api::CardDetailFromLocker>, domain::PaymentMethod)>
|
||||
for api::PaymentMethodResponse
|
||||
{
|
||||
fn foreign_from(
|
||||
(card_details, item): (Option<api::CardDetailFromLocker>, domain::PaymentMethod),
|
||||
) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StorageErrorExt<T, E> {
|
||||
#[track_caller]
|
||||
fn to_not_found_response(self, not_found_response: E) -> error_stack::Result<T, E>;
|
||||
|
||||
#[track_caller]
|
||||
fn to_duplicate_response(self, duplicate_response: E) -> error_stack::Result<T, E>;
|
||||
}
|
||||
|
||||
impl<T> StorageErrorExt<T, api_error_response::ApiErrorResponse>
|
||||
for error_stack::Result<T, storage_impl::StorageError>
|
||||
{
|
||||
#[track_caller]
|
||||
fn to_not_found_response(
|
||||
self,
|
||||
not_found_response: api_error_response::ApiErrorResponse,
|
||||
) -> error_stack::Result<T, api_error_response::ApiErrorResponse> {
|
||||
self.map_err(|err| {
|
||||
let new_err = match err.current_context() {
|
||||
storage_impl::StorageError::ValueNotFound(_) => not_found_response,
|
||||
storage_impl::StorageError::CustomerRedacted => {
|
||||
api_error_response::ApiErrorResponse::CustomerRedacted
|
||||
}
|
||||
_ => api_error_response::ApiErrorResponse::InternalServerError,
|
||||
};
|
||||
err.change_context(new_err)
|
||||
})
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn to_duplicate_response(
|
||||
self,
|
||||
duplicate_response: api_error_response::ApiErrorResponse,
|
||||
) -> error_stack::Result<T, api_error_response::ApiErrorResponse> {
|
||||
self.map_err(|err| {
|
||||
let new_err = match err.current_context() {
|
||||
storage_impl::StorageError::DuplicateValue { .. } => duplicate_response,
|
||||
_ => api_error_response::ApiErrorResponse::InternalServerError,
|
||||
};
|
||||
err.change_context(new_err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,5 @@
|
||||
pub mod configs;
|
||||
pub mod controller;
|
||||
pub mod core;
|
||||
pub mod helpers;
|
||||
pub mod state;
|
||||
|
||||
@ -3,22 +3,21 @@
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
use common_utils::errors::CustomResult;
|
||||
use common_utils::types::keymanager::KeyManagerState;
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
use common_utils::types::keymanager;
|
||||
use hyperswitch_domain_models::{
|
||||
merchant_account::MerchantAccount, payment_methods::PaymentMethod,
|
||||
};
|
||||
use hyperswitch_domain_models::{
|
||||
merchant_key_store::MerchantKeyStore, payment_methods::PaymentMethodInterface,
|
||||
cards_info, customer, merchant_account, merchant_key_store, payment_methods as pm_domain,
|
||||
};
|
||||
use storage_impl::{errors, kv_router_store::KVRouterStore, DatabaseStore, MockDb, RouterStore};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait PaymentMethodsStorageInterface:
|
||||
Send + Sync + dyn_clone::DynClone + PaymentMethodInterface<Error = errors::StorageError> + 'static
|
||||
Send
|
||||
+ Sync
|
||||
+ dyn_clone::DynClone
|
||||
+ pm_domain::PaymentMethodInterface<Error = errors::StorageError>
|
||||
+ cards_info::CardsInfoInterface<Error = errors::StorageError>
|
||||
+ customer::CustomerInterface<Error = errors::StorageError>
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
dyn_clone::clone_trait_object!(PaymentMethodsStorageInterface);
|
||||
@ -35,10 +34,10 @@ impl<T: DatabaseStore + 'static> PaymentMethodsStorageInterface for KVRouterStor
|
||||
#[derive(Clone)]
|
||||
pub struct PaymentMethodsState {
|
||||
pub store: Box<dyn PaymentMethodsStorageInterface>,
|
||||
pub key_store: Option<MerchantKeyStore>,
|
||||
pub key_manager_state: KeyManagerState,
|
||||
pub key_store: Option<merchant_key_store::MerchantKeyStore>,
|
||||
pub key_manager_state: keymanager::KeyManagerState,
|
||||
}
|
||||
impl From<&PaymentMethodsState> for KeyManagerState {
|
||||
impl From<&PaymentMethodsState> for keymanager::KeyManagerState {
|
||||
fn from(state: &PaymentMethodsState) -> Self {
|
||||
state.key_manager_state.clone()
|
||||
}
|
||||
@ -50,10 +49,10 @@ impl From<&PaymentMethodsState> for KeyManagerState {
|
||||
impl PaymentMethodsState {
|
||||
pub async fn find_payment_method(
|
||||
&self,
|
||||
key_store: &MerchantKeyStore,
|
||||
merchant_account: &MerchantAccount,
|
||||
key_store: &merchant_key_store::MerchantKeyStore,
|
||||
merchant_account: &merchant_account::MerchantAccount,
|
||||
payment_method_id: String,
|
||||
) -> CustomResult<PaymentMethod, errors::StorageError> {
|
||||
) -> CustomResult<pm_domain::PaymentMethod, errors::StorageError> {
|
||||
let db = &*self.store;
|
||||
let key_manager_state = &(self.key_manager_state).clone();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user