Skip to content

Commit

Permalink
feat(router): add api to migrate card from basilisk to rust (#2853)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ShankarSinghC and github-actions[bot] committed Nov 17, 2023
1 parent 974c296 commit e760a16
Show file tree
Hide file tree
Showing 24 changed files with 274 additions and 20 deletions.
2 changes: 2 additions & 0 deletions config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ kms_encrypted_recon_admin_api_key = "" # Base64-encoded (KMS encrypted) ciph
# like card details
[locker]
host = "" # Locker host
host_rs = "" # Rust Locker host
mock_locker = true # Emulate a locker locally using Postgres
basilisk_host = "" # Basilisk host
locker_signing_key_id = "1" # Key_id to sign basilisk hs locker
Expand All @@ -130,6 +131,7 @@ locker_encryption_key2 = "" # public key 2 in pem format, corresponding private
locker_decryption_key1 = "" # private key 1 in pem format, corresponding public key in basilisk
locker_decryption_key2 = "" # private key 2 in pem format, corresponding public key in basilisk
vault_encryption_key = "" # public key in pem format, corresponding private key in basilisk-hs
rust_locker_encryption_key = "" # public key in pem format, corresponding private key in rust locker
vault_private_key = "" # private key in pem format, corresponding public key in basilisk-hs


Expand Down
2 changes: 2 additions & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ applepay_endpoint = "DOMAIN SPECIFIC ENDPOINT"

[locker]
host = ""
host_rs = ""
mock_locker = true
basilisk_host = ""

Expand All @@ -59,6 +60,7 @@ locker_encryption_key2 = ""
locker_decryption_key1 = ""
locker_decryption_key2 = ""
vault_encryption_key = ""
rust_locker_encryption_key = ""
vault_private_key = ""
tunnel_private_key = ""

Expand Down
2 changes: 2 additions & 0 deletions config/docker_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ recon_admin_api_key = "recon_test_admin"

[locker]
host = ""
host_rs = ""
mock_locker = true
basilisk_host = ""

Expand All @@ -55,6 +56,7 @@ locker_encryption_key2 = ""
locker_decryption_key1 = ""
locker_decryption_key2 = ""
vault_encryption_key = ""
rust_locker_encryption_key = ""
vault_private_key = ""

[redis]
Expand Down
6 changes: 6 additions & 0 deletions crates/api_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,3 +562,9 @@ pub enum RetryAction {
/// Denotes that the payment is requeued
Requeue,
}

#[derive(Clone, Copy)]
pub enum LockerChoice {
Basilisk,
Tartarus,
}
1 change: 1 addition & 0 deletions crates/api_models/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod customer;
pub mod gsm;
mod locker_migration;
pub mod payment;
#[cfg(feature = "payouts")]
pub mod payouts;
Expand Down
9 changes: 9 additions & 0 deletions crates/api_models/src/events/locker_migration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use common_utils::events::ApiEventMetric;

use crate::locker_migration::MigrateCardResponse;

impl ApiEventMetric for MigrateCardResponse {
fn get_api_event_type(&self) -> Option<common_utils::events::ApiEventsType> {
Some(common_utils::events::ApiEventsType::RustLocker)
}
}
1 change: 1 addition & 0 deletions crates/api_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod errors;
pub mod events;
pub mod files;
pub mod gsm;
pub mod locker_migration;
pub mod mandates;
pub mod organization;
pub mod payment_methods;
Expand Down
8 changes: 8 additions & 0 deletions crates/api_models/src/locker_migration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MigrateCardResponse {
pub status_message: String,
pub status_code: String,
pub customers_moved: usize,
pub cards_moved: usize,
}
1 change: 1 addition & 0 deletions crates/common_utils/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub enum ApiEventsType {
Gsm,
// TODO: This has to be removed once the corresponding apiEventTypes are created
Miscellaneous,
RustLocker,
}

impl ApiEventMetric for serde_json::Value {}
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/configs/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ impl Default for super::settings::Locker {
fn default() -> Self {
Self {
host: "localhost".into(),
host_rs: "localhost".into(),
mock_locker: true,
basilisk_host: "localhost".into(),
locker_signing_key_id: "1".into(),
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/configs/kms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl KmsDecrypt for settings::Jwekey {
self.locker_decryption_key1,
self.locker_decryption_key2,
self.vault_encryption_key,
self.rust_locker_encryption_key,
self.vault_private_key,
self.tunnel_private_key,
) = tokio::try_join!(
Expand All @@ -26,6 +27,7 @@ impl KmsDecrypt for settings::Jwekey {
kms_client.decrypt(self.locker_decryption_key1),
kms_client.decrypt(self.locker_decryption_key2),
kms_client.decrypt(self.vault_encryption_key),
kms_client.decrypt(self.rust_locker_encryption_key),
kms_client.decrypt(self.vault_private_key),
kms_client.decrypt(self.tunnel_private_key),
)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/configs/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ pub struct Secrets {
#[serde(default)]
pub struct Locker {
pub host: String,
pub host_rs: String,
pub mock_locker: bool,
pub basilisk_host: String,
pub locker_signing_key_id: String,
Expand Down Expand Up @@ -448,6 +449,7 @@ pub struct Jwekey {
pub locker_decryption_key1: String,
pub locker_decryption_key2: String,
pub vault_encryption_key: String,
pub rust_locker_encryption_key: String,
pub vault_private_key: String,
pub tunnel_private_key: String,
}
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod disputes;
pub mod errors;
pub mod files;
pub mod gsm;
pub mod locker_migration;
pub mod mandate;
pub mod metrics;
pub mod payment_link;
Expand Down
131 changes: 131 additions & 0 deletions crates/router/src/core/locker_migration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use api_models::{enums as api_enums, locker_migration::MigrateCardResponse};
use common_utils::errors::CustomResult;
use diesel_models::PaymentMethod;
use error_stack::{FutureExt, ResultExt};
use futures::TryFutureExt;

use super::{errors::StorageErrorExt, payment_methods::cards};
use crate::{
errors,
routes::AppState,
services::{self, logger},
types::{api, domain},
};

pub async fn rust_locker_migration(
state: AppState,
merchant_id: &str,
) -> CustomResult<services::ApplicationResponse<MigrateCardResponse>, errors::ApiErrorResponse> {
let db = state.store.as_ref();

let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
merchant_id,
&state.store.get_master_key().to_vec().into(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)?;

let merchant_account = db
.find_merchant_account_by_merchant_id(merchant_id, &key_store)
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
.change_context(errors::ApiErrorResponse::InternalServerError)?;

let domain_customers = db
.list_customers_by_merchant_id(merchant_id, &key_store)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)?;

let mut customers_moved = 0;
let mut cards_moved = 0;

for customer in domain_customers {
let result = db
.find_payment_method_by_customer_id_merchant_id_list(&customer.customer_id, merchant_id)
.change_context(errors::ApiErrorResponse::InternalServerError)
.and_then(|pm| {
call_to_locker(
&state,
pm,
&customer.customer_id,
merchant_id,
&merchant_account,
)
})
.await?;

customers_moved += 1;
cards_moved += result;
}

Ok(services::api::ApplicationResponse::Json(
MigrateCardResponse {
status_code: "200".to_string(),
status_message: "Card migration completed".to_string(),
customers_moved,
cards_moved,
},
))
}

pub async fn call_to_locker(
state: &AppState,
payment_methods: Vec<PaymentMethod>,
customer_id: &String,
merchant_id: &str,
merchant_account: &domain::MerchantAccount,
) -> CustomResult<usize, errors::ApiErrorResponse> {
let mut cards_moved = 0;

for pm in payment_methods {
let card =
cards::get_card_from_locker(state, customer_id, merchant_id, &pm.payment_method_id)
.await?;

let card_details = api::CardDetail {
card_number: card.card_number,
card_exp_month: card.card_exp_month,
card_exp_year: card.card_exp_year,
card_holder_name: card.name_on_card,
nick_name: card.nick_name.map(masking::Secret::new),
};

let pm_create = api::PaymentMethodCreate {
payment_method: pm.payment_method,
payment_method_type: pm.payment_method_type,
payment_method_issuer: pm.payment_method_issuer,
payment_method_issuer_code: pm.payment_method_issuer_code,
card: Some(card_details.clone()),
metadata: pm.metadata,
customer_id: Some(pm.customer_id),
card_network: card.card_brand,
};

let (_add_card_rs_resp, _is_duplicate) = cards::add_card_hs(
state,
pm_create,
card_details,
customer_id.to_string(),
merchant_account,
api_enums::LockerChoice::Tartarus,
Some(&pm.payment_method_id),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(format!(
"Card migration failed for merchant_id: {merchant_id}, customer_id: {customer_id}, payment_method_id: {} ",
pm.payment_method_id
))?;

cards_moved += 1;

logger::info!(
"Card migrated for merchant_id: {merchant_id}, customer_id: {customer_id}, payment_method_id: {} ",
pm.payment_method_id
);
}

Ok(cards_moved)
}
31 changes: 23 additions & 8 deletions crates/router/src/core/payment_methods/cards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,20 @@ pub async fn add_card_to_locker(
metrics::STORED_TO_LOCKER.add(&metrics::CONTEXT, 1, &[]);
request::record_operation_time(
async {
add_card_hs(state, req, card, customer_id, merchant_account)
.await
.map_err(|error| {
metrics::CARD_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]);
error
})
add_card_hs(
state,
req,
card,
customer_id,
merchant_account,
api_enums::LockerChoice::Basilisk,
None,
)
.await
.map_err(|error| {
metrics::CARD_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]);
error
})
},
&metrics::CARD_ADD_TIME,
&[],
Expand Down Expand Up @@ -282,10 +290,13 @@ pub async fn add_card_hs(
card: api::CardDetail,
customer_id: String,
merchant_account: &domain::MerchantAccount,
locker_choice: api_enums::LockerChoice,
card_reference: Option<&str>,
) -> errors::CustomResult<(api::PaymentMethodResponse, bool), errors::VaultError> {
let payload = payment_methods::StoreLockerReq::LockerCard(payment_methods::StoreCardReq {
merchant_id: &merchant_account.merchant_id,
merchant_customer_id: customer_id.to_owned(),
card_reference: card_reference.map(str::to_string),
card: payment_methods::Card {
card_number: card.card_number.to_owned(),
name_on_card: card.card_holder_name.to_owned(),
Expand All @@ -296,7 +307,8 @@ pub async fn add_card_hs(
nick_name: card.nick_name.as_ref().map(masking::Secret::peek).cloned(),
},
});
let store_card_payload = call_to_locker_hs(state, &payload, &customer_id).await?;
let store_card_payload =
call_to_locker_hs(state, &payload, &customer_id, locker_choice).await?;

let payment_method_resp = payment_methods::mk_add_card_response_hs(
card,
Expand Down Expand Up @@ -394,6 +406,7 @@ pub async fn call_to_locker_hs<'a>(
state: &routes::AppState,
payload: &payment_methods::StoreLockerReq<'a>,
customer_id: &str,
locker_choice: api_enums::LockerChoice,
) -> errors::CustomResult<payment_methods::StoreCardRespPayload, errors::VaultError> {
let locker = &state.conf.locker;
#[cfg(not(feature = "kms"))]
Expand All @@ -402,7 +415,9 @@ pub async fn call_to_locker_hs<'a>(
let jwekey = &state.kms_secrets;
let db = &*state.store;
let stored_card_response = if !locker.mock_locker {
let request = payment_methods::mk_add_locker_request_hs(jwekey, locker, payload).await?;
let request =
payment_methods::mk_add_locker_request_hs(jwekey, locker, payload, locker_choice)
.await?;
let response = services::call_connector_api(state, request)
.await
.change_context(errors::VaultError::SaveCardFailed);
Expand Down
Loading

0 comments on commit e760a16

Please sign in to comment.