mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 20:23:43 +08:00
feat(data-migration): add connector customer and mandate details support for multiple profiles (#8473)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -19,7 +19,7 @@ use utoipa::{schema, ToSchema};
|
|||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
use crate::payouts;
|
use crate::payouts;
|
||||||
use crate::{
|
use crate::{
|
||||||
admin, customers, enums as api_enums,
|
admin, enums as api_enums,
|
||||||
payments::{self, BankCodeResponse},
|
payments::{self, BankCodeResponse},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2517,6 +2517,7 @@ pub struct PaymentMethodRecord {
|
|||||||
pub billing_address_line3: Option<masking::Secret<String>>,
|
pub billing_address_line3: Option<masking::Secret<String>>,
|
||||||
pub raw_card_number: Option<masking::Secret<String>>,
|
pub raw_card_number: Option<masking::Secret<String>>,
|
||||||
pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
|
pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
|
||||||
|
pub merchant_connector_ids: Option<String>,
|
||||||
pub original_transaction_amount: Option<i64>,
|
pub original_transaction_amount: Option<i64>,
|
||||||
pub original_transaction_currency: Option<common_enums::Currency>,
|
pub original_transaction_currency: Option<common_enums::Currency>,
|
||||||
pub line_number: Option<i64>,
|
pub line_number: Option<i64>,
|
||||||
@ -2526,18 +2527,6 @@ pub struct PaymentMethodRecord {
|
|||||||
pub network_token_requestor_ref_id: Option<String>,
|
pub network_token_requestor_ref_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub struct ConnectorCustomerDetails {
|
|
||||||
pub connector_customer_id: String,
|
|
||||||
pub merchant_connector_id: id_type::MerchantConnectorAccountId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub struct PaymentMethodCustomerMigrate {
|
|
||||||
pub customer: customers::CustomerRequest,
|
|
||||||
pub connector_customer_details: Option<ConnectorCustomerDetails>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, serde::Serialize)]
|
#[derive(Debug, Default, serde::Serialize)]
|
||||||
pub struct PaymentMethodMigrationResponse {
|
pub struct PaymentMethodMigrationResponse {
|
||||||
pub line_number: Option<i64>,
|
pub line_number: Option<i64>,
|
||||||
@ -2654,47 +2643,56 @@ impl From<PaymentMethodMigrationResponseType> for PaymentMethodMigrationResponse
|
|||||||
|
|
||||||
impl
|
impl
|
||||||
TryFrom<(
|
TryFrom<(
|
||||||
PaymentMethodRecord,
|
&PaymentMethodRecord,
|
||||||
id_type::MerchantId,
|
id_type::MerchantId,
|
||||||
Option<id_type::MerchantConnectorAccountId>,
|
Option<&Vec<id_type::MerchantConnectorAccountId>>,
|
||||||
)> for PaymentMethodMigrate
|
)> for PaymentMethodMigrate
|
||||||
{
|
{
|
||||||
type Error = error_stack::Report<errors::ValidationError>;
|
type Error = error_stack::Report<errors::ValidationError>;
|
||||||
|
|
||||||
fn try_from(
|
fn try_from(
|
||||||
item: (
|
item: (
|
||||||
PaymentMethodRecord,
|
&PaymentMethodRecord,
|
||||||
id_type::MerchantId,
|
id_type::MerchantId,
|
||||||
Option<id_type::MerchantConnectorAccountId>,
|
Option<&Vec<id_type::MerchantConnectorAccountId>>,
|
||||||
),
|
),
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
let (record, merchant_id, mca_id) = item;
|
let (record, merchant_id, mca_ids) = item;
|
||||||
let billing = record.create_billing();
|
let billing = record.create_billing();
|
||||||
|
let connector_mandate_details = if let Some(payment_instrument_id) =
|
||||||
// if payment instrument id is present then only construct this
|
&record.payment_instrument_id
|
||||||
let connector_mandate_details = if record.payment_instrument_id.is_some() {
|
{
|
||||||
Some(PaymentsMandateReference(HashMap::from([(
|
let ids = mca_ids.get_required_value("mca_ids")?;
|
||||||
mca_id.get_required_value("merchant_connector_id")?,
|
let mandate_map: HashMap<_, _> = ids
|
||||||
|
.iter()
|
||||||
|
.map(|mca_id| {
|
||||||
|
(
|
||||||
|
mca_id.clone(),
|
||||||
PaymentsMandateReferenceRecord {
|
PaymentsMandateReferenceRecord {
|
||||||
connector_mandate_id: record
|
connector_mandate_id: payment_instrument_id.peek().to_string(),
|
||||||
.payment_instrument_id
|
|
||||||
.get_required_value("payment_instrument_id")?
|
|
||||||
.peek()
|
|
||||||
.to_string(),
|
|
||||||
payment_method_type: record.payment_method_type,
|
payment_method_type: record.payment_method_type,
|
||||||
original_payment_authorized_amount: record.original_transaction_amount,
|
original_payment_authorized_amount: record.original_transaction_amount,
|
||||||
original_payment_authorized_currency: record.original_transaction_currency,
|
original_payment_authorized_currency: record
|
||||||
|
.original_transaction_currency,
|
||||||
},
|
},
|
||||||
)])))
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Some(PaymentsMandateReference(mandate_map))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
merchant_id,
|
merchant_id,
|
||||||
customer_id: Some(record.customer_id),
|
customer_id: Some(record.customer_id.clone()),
|
||||||
card: Some(MigrateCardDetail {
|
card: Some(MigrateCardDetail {
|
||||||
card_number: record.raw_card_number.unwrap_or(record.card_number_masked),
|
card_number: record
|
||||||
card_exp_month: record.card_expiry_month,
|
.raw_card_number
|
||||||
card_exp_year: record.card_expiry_year,
|
.clone()
|
||||||
|
.unwrap_or_else(|| record.card_number_masked.clone()),
|
||||||
|
card_exp_month: record.card_expiry_month.clone(),
|
||||||
|
card_exp_year: record.card_expiry_year.clone(),
|
||||||
card_holder_name: record.name.clone(),
|
card_holder_name: record.name.clone(),
|
||||||
card_network: None,
|
card_network: None,
|
||||||
card_type: None,
|
card_type: None,
|
||||||
@ -2704,10 +2702,16 @@ impl
|
|||||||
}),
|
}),
|
||||||
network_token: Some(MigrateNetworkTokenDetail {
|
network_token: Some(MigrateNetworkTokenDetail {
|
||||||
network_token_data: MigrateNetworkTokenData {
|
network_token_data: MigrateNetworkTokenData {
|
||||||
network_token_number: record.network_token_number.unwrap_or_default(),
|
network_token_number: record.network_token_number.clone().unwrap_or_default(),
|
||||||
network_token_exp_month: record.network_token_expiry_month.unwrap_or_default(),
|
network_token_exp_month: record
|
||||||
network_token_exp_year: record.network_token_expiry_year.unwrap_or_default(),
|
.network_token_expiry_month
|
||||||
card_holder_name: record.name,
|
.clone()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
network_token_exp_year: record
|
||||||
|
.network_token_expiry_year
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
card_holder_name: record.name.clone(),
|
||||||
nick_name: record.nick_name.clone(),
|
nick_name: record.nick_name.clone(),
|
||||||
card_issuing_country: None,
|
card_issuing_country: None,
|
||||||
card_network: None,
|
card_network: None,
|
||||||
@ -2716,6 +2720,7 @@ impl
|
|||||||
},
|
},
|
||||||
network_token_requestor_ref_id: record
|
network_token_requestor_ref_id: record
|
||||||
.network_token_requestor_ref_id
|
.network_token_requestor_ref_id
|
||||||
|
.clone()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
}),
|
}),
|
||||||
payment_method: record.payment_method,
|
payment_method: record.payment_method,
|
||||||
@ -2740,45 +2745,6 @@ impl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "v1")]
|
|
||||||
impl From<(PaymentMethodRecord, id_type::MerchantId)> for PaymentMethodCustomerMigrate {
|
|
||||||
fn from(value: (PaymentMethodRecord, id_type::MerchantId)) -> Self {
|
|
||||||
let (record, merchant_id) = value;
|
|
||||||
Self {
|
|
||||||
customer: customers::CustomerRequest {
|
|
||||||
customer_id: Some(record.customer_id),
|
|
||||||
merchant_id,
|
|
||||||
name: record.name,
|
|
||||||
email: record.email,
|
|
||||||
phone: record.phone,
|
|
||||||
description: None,
|
|
||||||
phone_country_code: record.phone_country_code,
|
|
||||||
address: Some(payments::AddressDetails {
|
|
||||||
city: record.billing_address_city,
|
|
||||||
country: record.billing_address_country,
|
|
||||||
line1: record.billing_address_line1,
|
|
||||||
line2: record.billing_address_line2,
|
|
||||||
state: record.billing_address_state,
|
|
||||||
line3: record.billing_address_line3,
|
|
||||||
zip: record.billing_address_zip,
|
|
||||||
first_name: record.billing_address_first_name,
|
|
||||||
last_name: record.billing_address_last_name,
|
|
||||||
}),
|
|
||||||
metadata: None,
|
|
||||||
},
|
|
||||||
connector_customer_details: record
|
|
||||||
.connector_customer_id
|
|
||||||
.zip(record.merchant_connector_id)
|
|
||||||
.map(
|
|
||||||
|(connector_customer_id, merchant_connector_id)| ConnectorCustomerDetails {
|
|
||||||
connector_customer_id,
|
|
||||||
merchant_connector_id,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||||
pub struct CardNetworkTokenizeRequest {
|
pub struct CardNetworkTokenizeRequest {
|
||||||
/// Merchant ID associated with the tokenization request
|
/// Merchant ID associated with the tokenization request
|
||||||
|
|||||||
@ -36,6 +36,7 @@ pub mod router_response_types;
|
|||||||
pub mod routing;
|
pub mod routing;
|
||||||
#[cfg(feature = "tokenization_v2")]
|
#[cfg(feature = "tokenization_v2")]
|
||||||
pub mod tokenization;
|
pub mod tokenization;
|
||||||
|
pub mod transformers;
|
||||||
pub mod type_encryption;
|
pub mod type_encryption;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod vault;
|
pub mod vault;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#[cfg(feature = "v2")]
|
#[cfg(feature = "v2")]
|
||||||
use api_models::payment_methods::PaymentMethodsData;
|
use api_models::payment_methods::PaymentMethodsData;
|
||||||
|
use api_models::{customers, payment_methods, payments};
|
||||||
// specific imports because of using the macro
|
// specific imports because of using the macro
|
||||||
use common_enums::enums::MerchantStorageScheme;
|
use common_enums::enums::MerchantStorageScheme;
|
||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
@ -27,11 +28,12 @@ use time::PrimitiveDateTime;
|
|||||||
#[cfg(feature = "v2")]
|
#[cfg(feature = "v2")]
|
||||||
use crate::address::Address;
|
use crate::address::Address;
|
||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
use crate::{mandates, type_encryption::AsyncLift};
|
use crate::type_encryption::AsyncLift;
|
||||||
use crate::{
|
use crate::{
|
||||||
mandates::CommonMandateReference,
|
mandates::{self, CommonMandateReference},
|
||||||
merchant_key_store::MerchantKeyStore,
|
merchant_key_store::MerchantKeyStore,
|
||||||
payment_method_data as domain_payment_method_data,
|
payment_method_data as domain_payment_method_data,
|
||||||
|
transformers::ForeignTryFrom,
|
||||||
type_encryption::{crypto_operation, CryptoOperation},
|
type_encryption::{crypto_operation, CryptoOperation},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,7 +89,6 @@ pub struct PaymentMethod {
|
|||||||
pub network_token_locker_id: Option<String>,
|
pub network_token_locker_id: Option<String>,
|
||||||
pub network_token_payment_method_data: OptionalEncryptableValue,
|
pub network_token_payment_method_data: OptionalEncryptableValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "v2")]
|
#[cfg(feature = "v2")]
|
||||||
#[derive(Clone, Debug, router_derive::ToEncryption)]
|
#[derive(Clone, Debug, router_derive::ToEncryption)]
|
||||||
pub struct PaymentMethod {
|
pub struct PaymentMethod {
|
||||||
@ -915,6 +916,136 @@ pub struct PaymentMethodsSessionUpdateInternal {
|
|||||||
pub tokenization_data: Option<pii::SecretSerdeValue>,
|
pub tokenization_data: Option<pii::SecretSerdeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ConnectorCustomerDetails {
|
||||||
|
pub connector_customer_id: String,
|
||||||
|
pub merchant_connector_id: id_type::MerchantConnectorAccountId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct PaymentMethodCustomerMigrate {
|
||||||
|
pub customer: customers::CustomerRequest,
|
||||||
|
pub connector_customer_details: Option<Vec<ConnectorCustomerDetails>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
impl TryFrom<(payment_methods::PaymentMethodRecord, id_type::MerchantId)>
|
||||||
|
for PaymentMethodCustomerMigrate
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<ValidationError>;
|
||||||
|
fn try_from(
|
||||||
|
value: (payment_methods::PaymentMethodRecord, id_type::MerchantId),
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let (record, merchant_id) = value;
|
||||||
|
let connector_customer_details = record
|
||||||
|
.connector_customer_id
|
||||||
|
.and_then(|connector_customer_id| {
|
||||||
|
// Handle single merchant_connector_id
|
||||||
|
record
|
||||||
|
.merchant_connector_id
|
||||||
|
.as_ref()
|
||||||
|
.map(|merchant_connector_id| {
|
||||||
|
Ok(vec![ConnectorCustomerDetails {
|
||||||
|
connector_customer_id: connector_customer_id.clone(),
|
||||||
|
merchant_connector_id: merchant_connector_id.clone(),
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
// Handle comma-separated merchant_connector_ids
|
||||||
|
.or_else(|| {
|
||||||
|
record
|
||||||
|
.merchant_connector_ids
|
||||||
|
.as_ref()
|
||||||
|
.map(|merchant_connector_ids_str| {
|
||||||
|
merchant_connector_ids_str
|
||||||
|
.split(',')
|
||||||
|
.map(|id| id.trim())
|
||||||
|
.filter(|id| !id.is_empty())
|
||||||
|
.map(|merchant_connector_id| {
|
||||||
|
id_type::MerchantConnectorAccountId::wrap(
|
||||||
|
merchant_connector_id.to_string(),
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
error_stack::report!(ValidationError::InvalidValue {
|
||||||
|
message: format!(
|
||||||
|
"Invalid merchant_connector_account_id: {}",
|
||||||
|
merchant_connector_id
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(
|
||||||
|
|merchant_connector_id| ConnectorCustomerDetails {
|
||||||
|
connector_customer_id: connector_customer_id
|
||||||
|
.clone(),
|
||||||
|
merchant_connector_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
customer: customers::CustomerRequest {
|
||||||
|
customer_id: Some(record.customer_id),
|
||||||
|
merchant_id,
|
||||||
|
name: record.name,
|
||||||
|
email: record.email,
|
||||||
|
phone: record.phone,
|
||||||
|
description: None,
|
||||||
|
phone_country_code: record.phone_country_code,
|
||||||
|
address: Some(payments::AddressDetails {
|
||||||
|
city: record.billing_address_city,
|
||||||
|
country: record.billing_address_country,
|
||||||
|
line1: record.billing_address_line1,
|
||||||
|
line2: record.billing_address_line2,
|
||||||
|
state: record.billing_address_state,
|
||||||
|
line3: record.billing_address_line3,
|
||||||
|
zip: record.billing_address_zip,
|
||||||
|
first_name: record.billing_address_first_name,
|
||||||
|
last_name: record.billing_address_last_name,
|
||||||
|
}),
|
||||||
|
metadata: None,
|
||||||
|
},
|
||||||
|
connector_customer_details,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
impl ForeignTryFrom<(&[payment_methods::PaymentMethodRecord], id_type::MerchantId)>
|
||||||
|
for Vec<PaymentMethodCustomerMigrate>
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<ValidationError>;
|
||||||
|
|
||||||
|
fn foreign_try_from(
|
||||||
|
(records, merchant_id): (&[payment_methods::PaymentMethodRecord], id_type::MerchantId),
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let (customers_migration, migration_errors): (Self, Vec<_>) = records
|
||||||
|
.iter()
|
||||||
|
.map(|record| {
|
||||||
|
PaymentMethodCustomerMigrate::try_from((record.clone(), merchant_id.clone()))
|
||||||
|
})
|
||||||
|
.fold((Self::new(), Vec::new()), |mut acc, result| {
|
||||||
|
match result {
|
||||||
|
Ok(customer) => acc.0.push(customer),
|
||||||
|
Err(e) => acc.1.push(e.to_string()),
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
migration_errors
|
||||||
|
.is_empty()
|
||||||
|
.then_some(customers_migration)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
error_stack::report!(ValidationError::InvalidValue {
|
||||||
|
message: migration_errors.join(", "),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
9
crates/hyperswitch_domain_models/src/transformers.rs
Normal file
9
crates/hyperswitch_domain_models/src/transformers.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
pub trait ForeignFrom<F> {
|
||||||
|
fn foreign_from(from: F) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ForeignTryFrom<F>: Sized {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn foreign_try_from(from: F) -> Result<Self, Self::Error>;
|
||||||
|
}
|
||||||
@ -24,20 +24,22 @@ pub async fn migrate_payment_methods(
|
|||||||
payment_methods: Vec<pm_api::PaymentMethodRecord>,
|
payment_methods: Vec<pm_api::PaymentMethodRecord>,
|
||||||
merchant_id: &common_utils::id_type::MerchantId,
|
merchant_id: &common_utils::id_type::MerchantId,
|
||||||
merchant_context: &merchant_context::MerchantContext,
|
merchant_context: &merchant_context::MerchantContext,
|
||||||
mca_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
|
mca_ids: Option<Vec<common_utils::id_type::MerchantConnectorAccountId>>,
|
||||||
controller: &dyn pm::PaymentMethodsController,
|
controller: &dyn pm::PaymentMethodsController,
|
||||||
) -> PmMigrationResult<Vec<pm_api::PaymentMethodMigrationResponse>> {
|
) -> PmMigrationResult<Vec<pm_api::PaymentMethodMigrationResponse>> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::with_capacity(payment_methods.len());
|
||||||
|
|
||||||
for record in payment_methods {
|
for record in payment_methods {
|
||||||
let req = pm_api::PaymentMethodMigrate::try_from((
|
let req = pm_api::PaymentMethodMigrate::try_from((
|
||||||
record.clone(),
|
&record,
|
||||||
merchant_id.clone(),
|
merchant_id.clone(),
|
||||||
mca_id.clone(),
|
mca_ids.as_ref(),
|
||||||
))
|
))
|
||||||
.map_err(|err| errors::ApiErrorResponse::InvalidRequestData {
|
.map_err(|err| errors::ApiErrorResponse::InvalidRequestData {
|
||||||
message: format!("error: {:?}", err),
|
message: format!("error: {:?}", err),
|
||||||
})
|
})
|
||||||
.attach_printable("record deserialization failed");
|
.attach_printable("record deserialization failed");
|
||||||
|
|
||||||
let res = match req {
|
let res = match req {
|
||||||
Ok(migrate_request) => {
|
Ok(migrate_request) => {
|
||||||
let res = migrate_payment_method(
|
let res = migrate_payment_method(
|
||||||
@ -56,6 +58,7 @@ pub async fn migrate_payment_methods(
|
|||||||
}
|
}
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
result.push(pm_api::PaymentMethodMigrationResponse::from((res, record)));
|
result.push(pm_api::PaymentMethodMigrationResponse::from((res, record)));
|
||||||
}
|
}
|
||||||
Ok(api::ApplicationResponse::Json(result))
|
Ok(api::ApplicationResponse::Json(result))
|
||||||
@ -69,7 +72,138 @@ pub struct PaymentMethodsMigrateForm {
|
|||||||
pub merchant_id: text::Text<common_utils::id_type::MerchantId>,
|
pub merchant_id: text::Text<common_utils::id_type::MerchantId>,
|
||||||
|
|
||||||
pub merchant_connector_id:
|
pub merchant_connector_id:
|
||||||
text::Text<Option<common_utils::id_type::MerchantConnectorAccountId>>,
|
Option<text::Text<common_utils::id_type::MerchantConnectorAccountId>>,
|
||||||
|
|
||||||
|
pub merchant_connector_ids: Option<text::Text<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MerchantConnectorValidator;
|
||||||
|
|
||||||
|
impl MerchantConnectorValidator {
|
||||||
|
fn parse_comma_separated_ids(
|
||||||
|
ids_string: &str,
|
||||||
|
) -> Result<Vec<common_utils::id_type::MerchantConnectorAccountId>, errors::ApiErrorResponse>
|
||||||
|
{
|
||||||
|
// Estimate capacity based on comma count
|
||||||
|
let capacity = ids_string.matches(',').count() + 1;
|
||||||
|
let mut result = Vec::with_capacity(capacity);
|
||||||
|
|
||||||
|
for id in ids_string.split(',') {
|
||||||
|
let trimmed_id = id.trim();
|
||||||
|
if !trimmed_id.is_empty() {
|
||||||
|
let mca_id =
|
||||||
|
common_utils::id_type::MerchantConnectorAccountId::wrap(trimmed_id.to_string())
|
||||||
|
.map_err(|_| errors::ApiErrorResponse::InvalidRequestData {
|
||||||
|
message: format!(
|
||||||
|
"Invalid merchant_connector_account_id: {}",
|
||||||
|
trimmed_id
|
||||||
|
),
|
||||||
|
})?;
|
||||||
|
result.push(mca_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_form_csv_conflicts(
|
||||||
|
records: &[pm_api::PaymentMethodRecord],
|
||||||
|
form_has_single_id: bool,
|
||||||
|
form_has_multiple_ids: bool,
|
||||||
|
) -> Result<(), errors::ApiErrorResponse> {
|
||||||
|
if form_has_single_id {
|
||||||
|
// If form has merchant_connector_id, CSV records should not have merchant_connector_ids
|
||||||
|
for (index, record) in records.iter().enumerate() {
|
||||||
|
if record.merchant_connector_ids.is_some() {
|
||||||
|
return Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||||
|
message: format!(
|
||||||
|
"Record at line {} has merchant_connector_ids but form has merchant_connector_id. Only one should be provided",
|
||||||
|
index + 1
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if form_has_multiple_ids {
|
||||||
|
// If form has merchant_connector_ids, CSV records should not have merchant_connector_id
|
||||||
|
for (index, record) in records.iter().enumerate() {
|
||||||
|
if record.merchant_connector_id.is_some() {
|
||||||
|
return Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||||
|
message: format!(
|
||||||
|
"Record at line {} has merchant_connector_id but form has merchant_connector_ids. Only one should be provided",
|
||||||
|
index + 1
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MigrationValidationResult = Result<
|
||||||
|
(
|
||||||
|
common_utils::id_type::MerchantId,
|
||||||
|
Vec<pm_api::PaymentMethodRecord>,
|
||||||
|
Option<Vec<common_utils::id_type::MerchantConnectorAccountId>>,
|
||||||
|
),
|
||||||
|
errors::ApiErrorResponse,
|
||||||
|
>;
|
||||||
|
|
||||||
|
impl PaymentMethodsMigrateForm {
|
||||||
|
pub fn validate_and_get_payment_method_records(self) -> MigrationValidationResult {
|
||||||
|
// Step 1: Validate form-level conflicts
|
||||||
|
let form_has_single_id = self.merchant_connector_id.is_some();
|
||||||
|
let form_has_multiple_ids = self.merchant_connector_ids.is_some();
|
||||||
|
|
||||||
|
if form_has_single_id && form_has_multiple_ids {
|
||||||
|
return Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||||
|
message: "Both merchant_connector_id and merchant_connector_ids cannot be provided"
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure at least one is provided
|
||||||
|
if !form_has_single_id && !form_has_multiple_ids {
|
||||||
|
return Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||||
|
message: "Either merchant_connector_id or merchant_connector_ids must be provided"
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Parse CSV
|
||||||
|
let records = parse_csv(self.file.data.to_bytes()).map_err(|e| {
|
||||||
|
errors::ApiErrorResponse::PreconditionFailed {
|
||||||
|
message: e.to_string(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Step 3: Validate CSV vs Form conflicts
|
||||||
|
MerchantConnectorValidator::validate_form_csv_conflicts(
|
||||||
|
&records,
|
||||||
|
form_has_single_id,
|
||||||
|
form_has_multiple_ids,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Step 4: Prepare the merchant connector account IDs for return
|
||||||
|
let mca_ids = if let Some(ref single_id) = self.merchant_connector_id {
|
||||||
|
Some(vec![(**single_id).clone()])
|
||||||
|
} else if let Some(ref ids_string) = self.merchant_connector_ids {
|
||||||
|
let parsed_ids = MerchantConnectorValidator::parse_comma_separated_ids(ids_string)?;
|
||||||
|
if parsed_ids.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(parsed_ids)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 5: Return the updated structure
|
||||||
|
Ok((self.merchant_id.clone(), records, mca_ids))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_csv(data: &[u8]) -> csv::Result<Vec<pm_api::PaymentMethodRecord>> {
|
fn parse_csv(data: &[u8]) -> csv::Result<Vec<pm_api::PaymentMethodRecord>> {
|
||||||
@ -84,27 +218,6 @@ fn parse_csv(data: &[u8]) -> csv::Result<Vec<pm_api::PaymentMethodRecord>> {
|
|||||||
}
|
}
|
||||||
Ok(records)
|
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)]
|
#[instrument(skip_all)]
|
||||||
pub fn validate_card_expiry(
|
pub fn validate_card_expiry(
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use common_utils::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use error_stack::{report, ResultExt};
|
use error_stack::{report, ResultExt};
|
||||||
|
use hyperswitch_domain_models::payment_methods as payment_methods_domain;
|
||||||
use masking::{ExposeInterface, Secret, SwitchStrategy};
|
use masking::{ExposeInterface, Secret, SwitchStrategy};
|
||||||
use payment_methods::controller::PaymentMethodsController;
|
use payment_methods::controller::PaymentMethodsController;
|
||||||
use router_env::{instrument, tracing};
|
use router_env::{instrument, tracing};
|
||||||
@ -27,7 +28,7 @@ use crate::{
|
|||||||
routes::{metrics, SessionState},
|
routes::{metrics, SessionState},
|
||||||
services,
|
services,
|
||||||
types::{
|
types::{
|
||||||
api::{customers, payment_methods as payment_methods_api},
|
api::customers,
|
||||||
domain::{self, types},
|
domain::{self, types},
|
||||||
storage::{self, enums},
|
storage::{self, enums},
|
||||||
transformers::ForeignFrom,
|
transformers::ForeignFrom,
|
||||||
@ -41,7 +42,7 @@ pub async fn create_customer(
|
|||||||
state: SessionState,
|
state: SessionState,
|
||||||
merchant_context: domain::MerchantContext,
|
merchant_context: domain::MerchantContext,
|
||||||
customer_data: customers::CustomerRequest,
|
customer_data: customers::CustomerRequest,
|
||||||
connector_customer_details: Option<payment_methods_api::ConnectorCustomerDetails>,
|
connector_customer_details: Option<Vec<payment_methods_domain::ConnectorCustomerDetails>>,
|
||||||
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
||||||
let db: &dyn StorageInterface = state.store.as_ref();
|
let db: &dyn StorageInterface = state.store.as_ref();
|
||||||
let key_manager_state = &(&state).into();
|
let key_manager_state = &(&state).into();
|
||||||
@ -96,7 +97,9 @@ pub async fn create_customer(
|
|||||||
trait CustomerCreateBridge {
|
trait CustomerCreateBridge {
|
||||||
async fn create_domain_model_from_request<'a>(
|
async fn create_domain_model_from_request<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>,
|
connector_customer_details: &'a Option<
|
||||||
|
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||||
|
>,
|
||||||
db: &'a dyn StorageInterface,
|
db: &'a dyn StorageInterface,
|
||||||
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
||||||
merchant_context: &'a domain::MerchantContext,
|
merchant_context: &'a domain::MerchantContext,
|
||||||
@ -115,7 +118,9 @@ trait CustomerCreateBridge {
|
|||||||
impl CustomerCreateBridge for customers::CustomerRequest {
|
impl CustomerCreateBridge for customers::CustomerRequest {
|
||||||
async fn create_domain_model_from_request<'a>(
|
async fn create_domain_model_from_request<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>,
|
connector_customer_details: &'a Option<
|
||||||
|
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||||
|
>,
|
||||||
db: &'a dyn StorageInterface,
|
db: &'a dyn StorageInterface,
|
||||||
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
||||||
merchant_context: &'a domain::MerchantContext,
|
merchant_context: &'a domain::MerchantContext,
|
||||||
@ -175,13 +180,15 @@ impl CustomerCreateBridge for customers::CustomerRequest {
|
|||||||
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
||||||
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
||||||
|
|
||||||
let connector_customer = connector_customer_details.as_ref().map(|details| {
|
let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
|
||||||
let merchant_connector_id = details.merchant_connector_id.get_string_repr().to_string();
|
let mut map = serde_json::Map::new();
|
||||||
let connector_customer_id = details.connector_customer_id.to_string();
|
for details in details_vec {
|
||||||
let object = serde_json::json!({
|
let merchant_connector_id =
|
||||||
merchant_connector_id: connector_customer_id
|
details.merchant_connector_id.get_string_repr().to_string();
|
||||||
});
|
let connector_customer_id = details.connector_customer_id.clone();
|
||||||
pii::SecretSerdeValue::new(object)
|
map.insert(merchant_connector_id, connector_customer_id.into());
|
||||||
|
}
|
||||||
|
pii::SecretSerdeValue::new(serde_json::Value::Object(map))
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(domain::Customer {
|
Ok(domain::Customer {
|
||||||
@ -227,7 +234,9 @@ impl CustomerCreateBridge for customers::CustomerRequest {
|
|||||||
impl CustomerCreateBridge for customers::CustomerRequest {
|
impl CustomerCreateBridge for customers::CustomerRequest {
|
||||||
async fn create_domain_model_from_request<'a>(
|
async fn create_domain_model_from_request<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>,
|
connector_customer_details: &'a Option<
|
||||||
|
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||||
|
>,
|
||||||
_db: &'a dyn StorageInterface,
|
_db: &'a dyn StorageInterface,
|
||||||
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
||||||
merchant_context: &'a domain::MerchantContext,
|
merchant_context: &'a domain::MerchantContext,
|
||||||
@ -297,12 +306,16 @@ impl CustomerCreateBridge for customers::CustomerRequest {
|
|||||||
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
|
||||||
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
.change_context(errors::CustomersErrorResponse::InternalServerError)?;
|
||||||
|
|
||||||
let connector_customer = connector_customer_details.as_ref().map(|details| {
|
let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
|
||||||
let mut map = std::collections::HashMap::new();
|
let map: std::collections::HashMap<_, _> = details_vec
|
||||||
map.insert(
|
.iter()
|
||||||
|
.map(|details| {
|
||||||
|
(
|
||||||
details.merchant_connector_id.clone(),
|
details.merchant_connector_id.clone(),
|
||||||
details.connector_customer_id.to_string(),
|
details.connector_customer_id.to_string(),
|
||||||
);
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
common_types::customers::ConnectorCustomerMap::new(map)
|
common_types::customers::ConnectorCustomerMap::new(map)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1060,7 +1073,9 @@ pub async fn update_customer(
|
|||||||
trait CustomerUpdateBridge {
|
trait CustomerUpdateBridge {
|
||||||
async fn create_domain_model_from_request<'a>(
|
async fn create_domain_model_from_request<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>,
|
connector_customer_details: &'a Option<
|
||||||
|
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||||
|
>,
|
||||||
db: &'a dyn StorageInterface,
|
db: &'a dyn StorageInterface,
|
||||||
merchant_context: &'a domain::MerchantContext,
|
merchant_context: &'a domain::MerchantContext,
|
||||||
key_manager_state: &'a KeyManagerState,
|
key_manager_state: &'a KeyManagerState,
|
||||||
@ -1232,7 +1247,9 @@ impl VerifyIdForUpdateCustomer<'_> {
|
|||||||
impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
||||||
async fn create_domain_model_from_request<'a>(
|
async fn create_domain_model_from_request<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
_connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>,
|
_connector_customer_details: &'a Option<
|
||||||
|
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||||
|
>,
|
||||||
db: &'a dyn StorageInterface,
|
db: &'a dyn StorageInterface,
|
||||||
merchant_context: &'a domain::MerchantContext,
|
merchant_context: &'a domain::MerchantContext,
|
||||||
key_manager_state: &'a KeyManagerState,
|
key_manager_state: &'a KeyManagerState,
|
||||||
@ -1337,7 +1354,9 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
|||||||
impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
||||||
async fn create_domain_model_from_request<'a>(
|
async fn create_domain_model_from_request<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>,
|
connector_customer_details: &'a Option<
|
||||||
|
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||||
|
>,
|
||||||
db: &'a dyn StorageInterface,
|
db: &'a dyn StorageInterface,
|
||||||
merchant_context: &'a domain::MerchantContext,
|
merchant_context: &'a domain::MerchantContext,
|
||||||
key_manager_state: &'a KeyManagerState,
|
key_manager_state: &'a KeyManagerState,
|
||||||
@ -1454,7 +1473,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
|||||||
|
|
||||||
pub async fn migrate_customers(
|
pub async fn migrate_customers(
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
customers_migration: Vec<payment_methods_api::PaymentMethodCustomerMigrate>,
|
customers_migration: Vec<payment_methods_domain::PaymentMethodCustomerMigrate>,
|
||||||
merchant_context: domain::MerchantContext,
|
merchant_context: domain::MerchantContext,
|
||||||
) -> errors::CustomerResponse<()> {
|
) -> errors::CustomerResponse<()> {
|
||||||
for customer_migration in customers_migration {
|
for customer_migration in customers_migration {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use diesel_models::enums::IntentStatus;
|
|||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use hyperswitch_domain_models::{
|
use hyperswitch_domain_models::{
|
||||||
bulk_tokenization::CardNetworkTokenizeRequest, merchant_key_store::MerchantKeyStore,
|
bulk_tokenization::CardNetworkTokenizeRequest, merchant_key_store::MerchantKeyStore,
|
||||||
|
payment_methods::PaymentMethodCustomerMigrate, transformers::ForeignTryFrom,
|
||||||
};
|
};
|
||||||
use router_env::{instrument, logger, tracing, Flow};
|
use router_env::{instrument, logger, tracing, Flow};
|
||||||
|
|
||||||
@ -331,10 +332,10 @@ pub async fn migrate_payment_methods(
|
|||||||
MultipartForm(form): MultipartForm<migration::PaymentMethodsMigrateForm>,
|
MultipartForm(form): MultipartForm<migration::PaymentMethodsMigrateForm>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let flow = Flow::PaymentMethodsMigrate;
|
let flow = Flow::PaymentMethodsMigrate;
|
||||||
let (merchant_id, records, merchant_connector_id) =
|
let (merchant_id, records, merchant_connector_ids) =
|
||||||
match migration::get_payment_method_records(form) {
|
match form.validate_and_get_payment_method_records() {
|
||||||
Ok((merchant_id, records, merchant_connector_id)) => {
|
Ok((merchant_id, records, merchant_connector_ids)) => {
|
||||||
(merchant_id, records, merchant_connector_id)
|
(merchant_id, records, merchant_connector_ids)
|
||||||
}
|
}
|
||||||
Err(e) => return api::log_and_return_error_response(e.into()),
|
Err(e) => return api::log_and_return_error_response(e.into()),
|
||||||
};
|
};
|
||||||
@ -345,7 +346,7 @@ pub async fn migrate_payment_methods(
|
|||||||
records,
|
records,
|
||||||
|state, _, req, _| {
|
|state, _, req, _| {
|
||||||
let merchant_id = merchant_id.clone();
|
let merchant_id = merchant_id.clone();
|
||||||
let merchant_connector_id = merchant_connector_id.clone();
|
let merchant_connector_ids = merchant_connector_ids.clone();
|
||||||
async move {
|
async move {
|
||||||
let (key_store, merchant_account) =
|
let (key_store, merchant_account) =
|
||||||
get_merchant_account(&state, &merchant_id).await?;
|
get_merchant_account(&state, &merchant_id).await?;
|
||||||
@ -354,18 +355,41 @@ pub async fn migrate_payment_methods(
|
|||||||
domain::Context(merchant_account.clone(), key_store.clone()),
|
domain::Context(merchant_account.clone(), key_store.clone()),
|
||||||
));
|
));
|
||||||
|
|
||||||
customers::migrate_customers(
|
let mut mca_cache = std::collections::HashMap::new();
|
||||||
state.clone(),
|
let customers = Vec::<PaymentMethodCustomerMigrate>::foreign_try_from((
|
||||||
req.iter()
|
&req,
|
||||||
.map(|e| {
|
|
||||||
payment_methods::PaymentMethodCustomerMigrate::from((
|
|
||||||
e.clone(),
|
|
||||||
merchant_id.clone(),
|
merchant_id.clone(),
|
||||||
))
|
))
|
||||||
})
|
.map_err(|e| errors::ApiErrorResponse::InvalidRequestData {
|
||||||
.collect(),
|
message: e.to_string(),
|
||||||
merchant_context.clone(),
|
})?;
|
||||||
|
|
||||||
|
for record in &customers {
|
||||||
|
if let Some(connector_customer_details) = &record.connector_customer_details {
|
||||||
|
for connector_customer in connector_customer_details {
|
||||||
|
if !mca_cache.contains_key(&connector_customer.merchant_connector_id) {
|
||||||
|
let mca = state
|
||||||
|
.store
|
||||||
|
.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
||||||
|
&(&state).into(),
|
||||||
|
&merchant_id,
|
||||||
|
&connector_customer.merchant_connector_id,
|
||||||
|
merchant_context.get_merchant_key_store(),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(
|
||||||
|
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
|
id: connector_customer.merchant_connector_id.get_string_repr().to_string(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
mca_cache
|
||||||
|
.insert(connector_customer.merchant_connector_id.clone(), mca);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customers::migrate_customers(state.clone(), customers, merchant_context.clone())
|
||||||
.await
|
.await
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||||
let controller = cards::PmCards {
|
let controller = cards::PmCards {
|
||||||
@ -377,7 +401,7 @@ pub async fn migrate_payment_methods(
|
|||||||
req,
|
req,
|
||||||
&merchant_id,
|
&merchant_id,
|
||||||
&merchant_context,
|
&merchant_context,
|
||||||
merchant_connector_id,
|
merchant_connector_ids,
|
||||||
&controller,
|
&controller,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -1,32 +1,30 @@
|
|||||||
#[cfg(feature = "v2")]
|
#[cfg(feature = "v2")]
|
||||||
pub use api_models::payment_methods::{
|
pub use api_models::payment_methods::{
|
||||||
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
|
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
|
||||||
CardNetworkTokenizeResponse, CardType, ConnectorCustomerDetails,
|
CardNetworkTokenizeResponse, CardType, CustomerPaymentMethodResponseItem,
|
||||||
CustomerPaymentMethodResponseItem, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest,
|
DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, GetTokenizePayloadResponse,
|
||||||
GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, MigrateCardDetail,
|
ListCountriesCurrenciesRequest, MigrateCardDetail, NetworkTokenDetailsPaymentMethod,
|
||||||
NetworkTokenDetailsPaymentMethod, NetworkTokenDetailsResponse, NetworkTokenResponse,
|
NetworkTokenDetailsResponse, NetworkTokenResponse, PaymentMethodCollectLinkRenderRequest,
|
||||||
PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate,
|
PaymentMethodCollectLinkRequest, PaymentMethodCreate, PaymentMethodCreateData,
|
||||||
PaymentMethodCreateData, PaymentMethodCustomerMigrate, PaymentMethodDeleteResponse,
|
PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodIntentConfirm,
|
||||||
PaymentMethodId, PaymentMethodIntentConfirm, PaymentMethodIntentCreate, PaymentMethodListData,
|
PaymentMethodIntentCreate, PaymentMethodListData, PaymentMethodListRequest,
|
||||||
PaymentMethodListRequest, PaymentMethodListResponseForSession, PaymentMethodMigrate,
|
PaymentMethodListResponseForSession, PaymentMethodMigrate, PaymentMethodMigrateResponse,
|
||||||
PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodResponseData,
|
PaymentMethodResponse, PaymentMethodResponseData, PaymentMethodUpdate, PaymentMethodUpdateData,
|
||||||
PaymentMethodUpdate, PaymentMethodUpdateData, PaymentMethodsData, TokenDataResponse,
|
PaymentMethodsData, TokenDataResponse, TokenDetailsResponse, TokenizePayloadEncrypted,
|
||||||
TokenDetailsResponse, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1,
|
TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1,
|
||||||
TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2,
|
TokenizedWalletValue2, TotalPaymentMethodCountResponse,
|
||||||
TotalPaymentMethodCountResponse,
|
|
||||||
};
|
};
|
||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
pub use api_models::payment_methods::{
|
pub use api_models::payment_methods::{
|
||||||
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
|
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
|
||||||
CardNetworkTokenizeResponse, ConnectorCustomerDetails, CustomerPaymentMethod,
|
CardNetworkTokenizeResponse, CustomerPaymentMethod, CustomerPaymentMethodsListResponse,
|
||||||
CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest,
|
DefaultPaymentMethod, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest,
|
||||||
GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest,
|
GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, MigrateCardDetail,
|
||||||
MigrateCardDetail, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest,
|
PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate,
|
||||||
PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodCustomerMigrate,
|
PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId,
|
||||||
PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodListRequest,
|
PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate,
|
||||||
PaymentMethodListResponse, PaymentMethodMigrate, PaymentMethodMigrateResponse,
|
PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData,
|
||||||
PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizeCardRequest,
|
TokenizeCardRequest, TokenizeDataRequest, TokenizePayloadEncrypted, TokenizePayloadRequest,
|
||||||
TokenizeDataRequest, TokenizePayloadEncrypted, TokenizePayloadRequest,
|
|
||||||
TokenizePaymentMethodRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1,
|
TokenizePaymentMethodRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1,
|
||||||
TokenizedWalletValue2,
|
TokenizedWalletValue2,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
openssl
|
openssl
|
||||||
pkg-config
|
pkg-config
|
||||||
postgresql # for libpq
|
postgresql # for libpq
|
||||||
|
protobuf
|
||||||
];
|
];
|
||||||
|
|
||||||
# Minimal packages for running hyperswitch
|
# Minimal packages for running hyperswitch
|
||||||
@ -40,6 +41,7 @@
|
|||||||
|
|
||||||
# Development packages
|
# Development packages
|
||||||
devPackages = base ++ (with pkgs; [
|
devPackages = base ++ (with pkgs; [
|
||||||
|
cargo-watch
|
||||||
nixd
|
nixd
|
||||||
rust-bin.stable.${rustDevVersion}.default
|
rust-bin.stable.${rustDevVersion}.default
|
||||||
swagger-cli
|
swagger-cli
|
||||||
|
|||||||
Reference in New Issue
Block a user