mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 21:07:58 +08:00
feat(payment_methods): add support to migrate existing customer PMs from processor to hyperswitch (#5306)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -470,3 +470,28 @@ pub async fn update_customer(
|
||||
customers::CustomerResponse::from((response, update_customer.address)),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn migrate_customers(
|
||||
state: SessionState,
|
||||
customers: Vec<customers::CustomerRequest>,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
) -> errors::CustomerResponse<()> {
|
||||
for customer in customers {
|
||||
match create_customer(
|
||||
state.clone(),
|
||||
merchant_account.clone(),
|
||||
key_store.clone(),
|
||||
customer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(e) => match e.current_context() {
|
||||
errors::CustomersErrorResponse::CustomerAlreadyExists => (),
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(services::ApplicationResponse::Json(()))
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod cards;
|
||||
pub mod migration;
|
||||
pub mod surcharge_decision_configs;
|
||||
pub mod transformers;
|
||||
pub mod utils;
|
||||
|
||||
@ -269,23 +269,10 @@ pub async fn get_or_insert_payment_method(
|
||||
pub async fn migrate_payment_method(
|
||||
state: routes::SessionState,
|
||||
req: api::PaymentMethodMigrate,
|
||||
merchant_id: &str,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> errors::RouterResponse<api::PaymentMethodResponse> {
|
||||
let merchant_id = &req.merchant_id;
|
||||
let key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
merchant_id,
|
||||
&state.store.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
|
||||
let merchant_account = state
|
||||
.store
|
||||
.find_merchant_account_by_merchant_id(merchant_id, &key_store)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
|
||||
let card_details = req.card.as_ref().get_required_value("card")?;
|
||||
|
||||
let card_number_validation_result =
|
||||
@ -294,7 +281,7 @@ pub async fn migrate_payment_method(
|
||||
if let Some(connector_mandate_details) = &req.connector_mandate_details {
|
||||
helpers::validate_merchant_connector_ids_in_connector_mandate_details(
|
||||
&*state.store,
|
||||
&key_store,
|
||||
key_store,
|
||||
connector_mandate_details,
|
||||
merchant_id,
|
||||
)
|
||||
@ -311,8 +298,8 @@ pub async fn migrate_payment_method(
|
||||
get_client_secret_or_add_payment_method(
|
||||
state,
|
||||
payment_method_create_request,
|
||||
&merchant_account,
|
||||
&key_store,
|
||||
merchant_account,
|
||||
key_store,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -322,8 +309,8 @@ pub async fn migrate_payment_method(
|
||||
state,
|
||||
&req,
|
||||
merchant_id.into(),
|
||||
&key_store,
|
||||
&merchant_account,
|
||||
key_store,
|
||||
merchant_account,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -513,7 +500,10 @@ pub async fn skip_locker_call_and_migrate_payment_method(
|
||||
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(),
|
||||
scheme: req
|
||||
.card_network
|
||||
.clone()
|
||||
.or(card.clone().and_then(|card| card.scheme.clone())),
|
||||
metadata: payment_method_metadata.map(Secret::new),
|
||||
payment_method_data: payment_method_data_encrypted.map(Into::into),
|
||||
connector_mandate_details: Some(connector_mandate_details),
|
||||
|
||||
85
crates/router/src/core/payment_methods/migration.rs
Normal file
85
crates/router/src/core/payment_methods/migration.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use actix_multipart::form::{bytes::Bytes, MultipartForm};
|
||||
use api_models::payment_methods::{PaymentMethodMigrationResponse, PaymentMethodRecord};
|
||||
use csv::Reader;
|
||||
use rdkafka::message::ToBytes;
|
||||
|
||||
use crate::{
|
||||
core::{errors, payment_methods::cards::migrate_payment_method},
|
||||
routes, services,
|
||||
types::{api, domain},
|
||||
};
|
||||
|
||||
pub async fn migrate_payment_methods(
|
||||
state: routes::SessionState,
|
||||
payment_methods: Vec<PaymentMethodRecord>,
|
||||
merchant_id: &str,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> errors::RouterResponse<Vec<PaymentMethodMigrationResponse>> {
|
||||
let mut result = Vec::new();
|
||||
for record in payment_methods {
|
||||
let res = migrate_payment_method(
|
||||
state.clone(),
|
||||
api::PaymentMethodMigrate::from(record.clone()),
|
||||
merchant_id,
|
||||
merchant_account,
|
||||
key_store,
|
||||
)
|
||||
.await;
|
||||
result.push(PaymentMethodMigrationResponse::from((
|
||||
match res {
|
||||
Ok(services::api::ApplicationResponse::Json(response)) => Ok(response),
|
||||
Err(e) => Err(e.to_string()),
|
||||
_ => Err("Failed to migrate payment method".to_string()),
|
||||
},
|
||||
record,
|
||||
)));
|
||||
}
|
||||
Ok(services::api::ApplicationResponse::Json(result))
|
||||
}
|
||||
|
||||
#[derive(Debug, MultipartForm)]
|
||||
pub struct PaymentMethodsMigrateForm {
|
||||
#[multipart(limit = "1MB")]
|
||||
pub file: Bytes,
|
||||
}
|
||||
|
||||
fn parse_csv(data: &[u8]) -> csv::Result<Vec<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: 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<(String, Vec<PaymentMethodRecord>), errors::ApiErrorResponse> {
|
||||
match parse_csv(form.file.data.to_bytes()) {
|
||||
Ok(records) => {
|
||||
if let Some(first_record) = records.first() {
|
||||
if records
|
||||
.iter()
|
||||
.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 {
|
||||
message: e.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user