Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 47 additions & 81 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use utoipa::{schema, ToSchema};
#[cfg(feature = "payouts")]
use crate::payouts;
use crate::{
admin, customers, enums as api_enums,
admin, enums as api_enums,
payments::{self, BankCodeResponse},
};

Expand Down Expand Up @@ -2500,6 +2500,7 @@ pub struct PaymentMethodRecord {
pub billing_address_line3: Option<masking::Secret<String>>,
pub raw_card_number: Option<masking::Secret<String>>,
pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
pub merchant_connector_ids: Option<String>,
pub original_transaction_amount: Option<i64>,
pub original_transaction_currency: Option<common_enums::Currency>,
pub line_number: Option<i64>,
Expand All @@ -2509,18 +2510,6 @@ pub struct PaymentMethodRecord {
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)]
pub struct PaymentMethodMigrationResponse {
pub line_number: Option<i64>,
Expand Down Expand Up @@ -2637,47 +2626,56 @@ impl From<PaymentMethodMigrationResponseType> for PaymentMethodMigrationResponse

impl
TryFrom<(
PaymentMethodRecord,
&PaymentMethodRecord,
id_type::MerchantId,
Option<id_type::MerchantConnectorAccountId>,
Option<&Vec<id_type::MerchantConnectorAccountId>>,
)> for PaymentMethodMigrate
{
type Error = error_stack::Report<errors::ValidationError>;

fn try_from(
item: (
PaymentMethodRecord,
&PaymentMethodRecord,
id_type::MerchantId,
Option<id_type::MerchantConnectorAccountId>,
Option<&Vec<id_type::MerchantConnectorAccountId>>,
),
) -> Result<Self, Self::Error> {
let (record, merchant_id, mca_id) = item;
let (record, merchant_id, mca_ids) = item;
let billing = record.create_billing();

// if payment instrument id is present then only construct this
let connector_mandate_details = if record.payment_instrument_id.is_some() {
Some(PaymentsMandateReference(HashMap::from([(
mca_id.get_required_value("merchant_connector_id")?,
PaymentsMandateReferenceRecord {
connector_mandate_id: record
.payment_instrument_id
.get_required_value("payment_instrument_id")?
.peek()
.to_string(),
payment_method_type: record.payment_method_type,
original_payment_authorized_amount: record.original_transaction_amount,
original_payment_authorized_currency: record.original_transaction_currency,
},
)])))
let connector_mandate_details = if let Some(payment_instrument_id) =
&record.payment_instrument_id
{
let ids = mca_ids.get_required_value("mca_ids")?;
let mandate_map: HashMap<_, _> = ids
.iter()
.map(|mca_id| {
(
mca_id.clone(),
PaymentsMandateReferenceRecord {
connector_mandate_id: payment_instrument_id.peek().to_string(),
payment_method_type: record.payment_method_type,
original_payment_authorized_amount: record.original_transaction_amount,
original_payment_authorized_currency: record
.original_transaction_currency,
},
)
})
.collect();
Some(PaymentsMandateReference(mandate_map))
} else {
None
};

Ok(Self {
merchant_id,
customer_id: Some(record.customer_id),
customer_id: Some(record.customer_id.clone()),
card: Some(MigrateCardDetail {
card_number: record.raw_card_number.unwrap_or(record.card_number_masked),
card_exp_month: record.card_expiry_month,
card_exp_year: record.card_expiry_year,
card_number: record
.raw_card_number
.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_network: None,
card_type: None,
Expand All @@ -2687,10 +2685,16 @@ impl
}),
network_token: Some(MigrateNetworkTokenDetail {
network_token_data: MigrateNetworkTokenData {
network_token_number: record.network_token_number.unwrap_or_default(),
network_token_exp_month: record.network_token_expiry_month.unwrap_or_default(),
network_token_exp_year: record.network_token_expiry_year.unwrap_or_default(),
card_holder_name: record.name,
network_token_number: record.network_token_number.clone().unwrap_or_default(),
network_token_exp_month: record
.network_token_expiry_month
.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(),
card_issuing_country: None,
card_network: None,
Expand All @@ -2699,6 +2703,7 @@ impl
},
network_token_requestor_ref_id: record
.network_token_requestor_ref_id
.clone()
.unwrap_or_default(),
}),
payment_method: record.payment_method,
Expand All @@ -2723,45 +2728,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)]
pub struct CardNetworkTokenizeRequest {
/// Merchant ID associated with the tokenization request
Expand Down
1 change: 1 addition & 0 deletions crates/hyperswitch_domain_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub mod router_response_types;
pub mod routing;
#[cfg(feature = "tokenization_v2")]
pub mod tokenization;
pub mod transformers;
pub mod type_encryption;
pub mod types;
pub mod vault;
Expand Down
137 changes: 134 additions & 3 deletions crates/hyperswitch_domain_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[cfg(feature = "v2")]
use api_models::payment_methods::PaymentMethodsData;
use api_models::{customers, payment_methods, payments};
// specific imports because of using the macro
use common_enums::enums::MerchantStorageScheme;
#[cfg(feature = "v1")]
Expand Down Expand Up @@ -27,11 +28,12 @@ use time::PrimitiveDateTime;
#[cfg(feature = "v2")]
use crate::address::Address;
#[cfg(feature = "v1")]
use crate::{mandates, type_encryption::AsyncLift};
use crate::type_encryption::AsyncLift;
use crate::{
mandates::CommonMandateReference,
mandates::{self, CommonMandateReference},
merchant_key_store::MerchantKeyStore,
payment_method_data as domain_payment_method_data,
transformers::ForeignTryFrom,
type_encryption::{crypto_operation, CryptoOperation},
};

Expand Down Expand Up @@ -87,7 +89,6 @@ pub struct PaymentMethod {
pub network_token_locker_id: Option<String>,
pub network_token_payment_method_data: OptionalEncryptableValue,
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug, router_derive::ToEncryption)]
pub struct PaymentMethod {
Expand Down Expand Up @@ -915,6 +916,136 @@ pub struct PaymentMethodsSessionUpdateInternal {
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(test)]
mod tests {
Expand Down
9 changes: 9 additions & 0 deletions crates/hyperswitch_domain_models/src/transformers.rs
Original file line number Diff line number Diff line change
@@ -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>;
}
Loading
Loading