fix(router): Update request body for migrate-batch api (#6429)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Prasunna Soppa
2024-10-25 18:30:50 +05:30
committed by GitHub
parent 2807622ba6
commit 53075792b3
3 changed files with 100 additions and 51 deletions

View File

@ -6,6 +6,8 @@ use cards::CardNumber;
use common_utils::{ use common_utils::{
consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH,
crypto::OptionalEncryptableName, crypto::OptionalEncryptableName,
errors,
ext_traits::OptionExt,
id_type, link_utils, pii, id_type, link_utils, pii,
types::{MinorUnit, Percentage, Surcharge}, types::{MinorUnit, Percentage, Surcharge},
}; };
@ -2065,16 +2067,16 @@ pub struct PaymentMethodRecord {
pub email: Option<pii::Email>, pub email: Option<pii::Email>,
pub phone: Option<masking::Secret<String>>, pub phone: Option<masking::Secret<String>>,
pub phone_country_code: Option<String>, pub phone_country_code: Option<String>,
pub merchant_id: id_type::MerchantId, pub merchant_id: Option<id_type::MerchantId>,
pub payment_method: Option<api_enums::PaymentMethod>, pub payment_method: Option<api_enums::PaymentMethod>,
pub payment_method_type: Option<api_enums::PaymentMethodType>, pub payment_method_type: Option<api_enums::PaymentMethodType>,
pub nick_name: masking::Secret<String>, pub nick_name: masking::Secret<String>,
pub payment_instrument_id: masking::Secret<String>, pub payment_instrument_id: Option<masking::Secret<String>>,
pub card_number_masked: masking::Secret<String>, pub card_number_masked: masking::Secret<String>,
pub card_expiry_month: masking::Secret<String>, pub card_expiry_month: masking::Secret<String>,
pub card_expiry_year: masking::Secret<String>, pub card_expiry_year: masking::Secret<String>,
pub card_scheme: Option<String>, pub card_scheme: Option<String>,
pub original_transaction_id: String, pub original_transaction_id: Option<String>,
pub billing_address_zip: masking::Secret<String>, pub billing_address_zip: masking::Secret<String>,
pub billing_address_state: masking::Secret<String>, pub billing_address_state: masking::Secret<String>,
pub billing_address_first_name: masking::Secret<String>, pub billing_address_first_name: masking::Secret<String>,
@ -2085,7 +2087,7 @@ pub struct PaymentMethodRecord {
pub billing_address_line2: Option<masking::Secret<String>>, pub billing_address_line2: Option<masking::Secret<String>>,
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: id_type::MerchantConnectorAccountId, pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
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>,
@ -2171,31 +2173,54 @@ impl From<PaymentMethodMigrationResponseType> for PaymentMethodMigrationResponse
} }
} }
impl From<PaymentMethodRecord> for PaymentMethodMigrate { impl
fn from(record: PaymentMethodRecord) -> Self { TryFrom<(
let mut mandate_reference = HashMap::new(); PaymentMethodRecord,
mandate_reference.insert( id_type::MerchantId,
record.merchant_connector_id, Option<id_type::MerchantConnectorAccountId>,
)> for PaymentMethodMigrate
{
type Error = error_stack::Report<errors::ValidationError>;
fn try_from(
item: (
PaymentMethodRecord,
id_type::MerchantId,
Option<id_type::MerchantConnectorAccountId>,
),
) -> Result<Self, Self::Error> {
let (record, merchant_id, mca_id) = item;
// 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 { PaymentsMandateReferenceRecord {
connector_mandate_id: record.payment_instrument_id.peek().to_string(), connector_mandate_id: record
.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,
}, },
); )])))
Self { } else {
merchant_id: record.merchant_id, None
};
Ok(Self {
merchant_id,
customer_id: Some(record.customer_id), customer_id: Some(record.customer_id),
card: Some(MigrateCardDetail { card: Some(MigrateCardDetail {
card_number: record.raw_card_number.unwrap_or(record.card_number_masked), card_number: record.raw_card_number.unwrap_or(record.card_number_masked),
card_exp_month: record.card_expiry_month, card_exp_month: record.card_expiry_month,
card_exp_year: record.card_expiry_year, card_exp_year: record.card_expiry_year,
card_holder_name: record.name, card_holder_name: record.name.clone(),
card_network: None, card_network: None,
card_type: None, card_type: None,
card_issuer: None, card_issuer: None,
card_issuing_country: None, card_issuing_country: None,
nick_name: Some(record.nick_name), nick_name: Some(record.nick_name.clone()),
}), }),
payment_method: record.payment_method, payment_method: record.payment_method,
payment_method_type: record.payment_method_type, payment_method_type: record.payment_method_type,
@ -2218,7 +2243,7 @@ impl From<PaymentMethodRecord> for PaymentMethodMigrate {
}), }),
email: record.email, email: record.email,
}), }),
connector_mandate_details: Some(PaymentsMandateReference(mandate_reference)), connector_mandate_details,
metadata: None, metadata: None,
payment_method_issuer_code: None, payment_method_issuer_code: None,
card_network: None, card_network: None,
@ -2227,17 +2252,18 @@ impl From<PaymentMethodRecord> for PaymentMethodMigrate {
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
wallet: None, wallet: None,
payment_method_data: None, payment_method_data: None,
network_transaction_id: record.original_transaction_id.into(), network_transaction_id: record.original_transaction_id,
} })
} }
} }
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
impl From<PaymentMethodRecord> for customers::CustomerRequest { impl From<(PaymentMethodRecord, id_type::MerchantId)> for customers::CustomerRequest {
fn from(record: PaymentMethodRecord) -> Self { fn from(value: (PaymentMethodRecord, id_type::MerchantId)) -> Self {
let (record, merchant_id) = value;
Self { Self {
customer_id: Some(record.customer_id), customer_id: Some(record.customer_id),
merchant_id: record.merchant_id, merchant_id,
name: record.name, name: record.name,
email: record.email, email: record.email,
phone: record.phone, phone: record.phone,

View File

@ -1,6 +1,7 @@
use actix_multipart::form::{bytes::Bytes, MultipartForm}; use actix_multipart::form::{bytes::Bytes, text::Text, MultipartForm};
use api_models::payment_methods::{PaymentMethodMigrationResponse, PaymentMethodRecord}; use api_models::payment_methods::{PaymentMethodMigrationResponse, PaymentMethodRecord};
use csv::Reader; use csv::Reader;
use error_stack::ResultExt;
use rdkafka::message::ToBytes; use rdkafka::message::ToBytes;
use crate::{ use crate::{
@ -15,12 +16,32 @@ pub async fn migrate_payment_methods(
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
merchant_account: &domain::MerchantAccount, merchant_account: &domain::MerchantAccount,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
mca_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
) -> errors::RouterResponse<Vec<PaymentMethodMigrationResponse>> { ) -> errors::RouterResponse<Vec<PaymentMethodMigrationResponse>> {
let mut result = Vec::new(); let mut result = Vec::new();
for record in payment_methods { for record in payment_methods {
let req = 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");
match req {
Ok(_) => (),
Err(e) => {
result.push(PaymentMethodMigrationResponse::from((
Err(e.to_string()),
record,
)));
continue;
}
};
let res = migrate_payment_method( let res = migrate_payment_method(
state.clone(), state.clone(),
api::PaymentMethodMigrate::from(record.clone()), req?,
merchant_id, merchant_id,
merchant_account, merchant_account,
key_store, key_store,
@ -42,6 +63,10 @@ pub async fn migrate_payment_methods(
pub struct PaymentMethodsMigrateForm { pub struct PaymentMethodsMigrateForm {
#[multipart(limit = "1MB")] #[multipart(limit = "1MB")]
pub file: Bytes, pub file: Bytes,
pub merchant_id: Text<common_utils::id_type::MerchantId>,
pub merchant_connector_id: Text<Option<common_utils::id_type::MerchantConnectorAccountId>>,
} }
fn parse_csv(data: &[u8]) -> csv::Result<Vec<PaymentMethodRecord>> { fn parse_csv(data: &[u8]) -> csv::Result<Vec<PaymentMethodRecord>> {
@ -58,26 +83,19 @@ fn parse_csv(data: &[u8]) -> csv::Result<Vec<PaymentMethodRecord>> {
} }
pub fn get_payment_method_records( pub fn get_payment_method_records(
form: PaymentMethodsMigrateForm, form: PaymentMethodsMigrateForm,
) -> Result<(common_utils::id_type::MerchantId, Vec<PaymentMethodRecord>), errors::ApiErrorResponse> ) -> Result<
{ (
common_utils::id_type::MerchantId,
Vec<PaymentMethodRecord>,
Option<common_utils::id_type::MerchantConnectorAccountId>,
),
errors::ApiErrorResponse,
> {
match parse_csv(form.file.data.to_bytes()) { match parse_csv(form.file.data.to_bytes()) {
Ok(records) => { Ok(records) => {
if let Some(first_record) = records.first() { let merchant_id = form.merchant_id.clone();
if records let mca_id = form.merchant_connector_id.clone();
.iter() Ok((merchant_id.clone(), records, mca_id))
.all(|merchant_id| merchant_id.merchant_id == first_record.merchant_id)
{
Ok((first_record.merchant_id.clone(), records))
} else {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: "Only one merchant id can be updated at a time".to_string(),
})
}
} else {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: "No records found".to_string(),
})
}
} }
Err(e) => Err(errors::ApiErrorResponse::PreconditionFailed { Err(e) => Err(errors::ApiErrorResponse::PreconditionFailed {
message: e.to_string(), message: e.to_string(),

View File

@ -331,8 +331,11 @@ 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) = match migration::get_payment_method_records(form) { let (merchant_id, records, merchant_connector_id) =
Ok((merchant_id, records)) => (merchant_id, records), match migration::get_payment_method_records(form) {
Ok((merchant_id, records, merchant_connector_id)) => {
(merchant_id, records, merchant_connector_id)
}
Err(e) => return api::log_and_return_error_response(e.into()), Err(e) => return api::log_and_return_error_response(e.into()),
}; };
Box::pin(api::server_wrap( Box::pin(api::server_wrap(
@ -342,6 +345,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();
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?;
@ -349,7 +353,7 @@ pub async fn migrate_payment_methods(
customers::migrate_customers( customers::migrate_customers(
state.clone(), state.clone(),
req.iter() req.iter()
.map(|e| CustomerRequest::from(e.clone())) .map(|e| CustomerRequest::from((e.clone(), merchant_id.clone())))
.collect(), .collect(),
merchant_account.clone(), merchant_account.clone(),
key_store.clone(), key_store.clone(),
@ -362,6 +366,7 @@ pub async fn migrate_payment_methods(
&merchant_id, &merchant_id,
&merchant_account, &merchant_account,
&key_store, &key_store,
merchant_connector_id,
)) ))
.await .await
} }