Skip to content

Commit

Permalink
accept masked card as input for the payment method migration
Browse files Browse the repository at this point in the history
  • Loading branch information
ShankarSinghC committed Jul 4, 2024
1 parent 7f65067 commit e70bbc2
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 17 deletions.
80 changes: 74 additions & 6 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,20 @@ pub struct PaymentMethodCreate {
pub payment_method_billing_address: Option<payments::Address>,

#[serde(skip_deserializing)]
/// The connector mandate details of the payment method, this is skipped during deserialization
/// as this should not be passed in the api request
/// The connector mandate details of the payment method, this is added only for cards migration
/// api and is skipped during deserialization of the payment method create request as this
/// it should not be passed in the request
pub connector_mandate_details: Option<PaymentsMandateReference>,

#[serde(skip_deserializing)]
/// The transaction id of a CIT (customer initiated transaction) associated with the payment method,
/// this is added only for cards migration api and is skipped during deserialization of the
/// payment method create request as it should not be passed in the request
pub network_transaction_id: Option<String>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
/// This struct is only used by and internal api to migrate payment method
pub struct PaymentMethodMigrate {
/// The type of payment method use for the payment.
pub payment_method: Option<api_enums::PaymentMethod>,
Expand All @@ -101,7 +109,7 @@ pub struct PaymentMethodMigrate {
pub payment_method_issuer_code: Option<api_enums::PaymentMethodIssuerCode>,

/// Card Details
pub card: Option<CardDetail>,
pub card: Option<MigrateCardDetail>,

/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
pub metadata: Option<pii::SecretSerdeValue>,
Expand Down Expand Up @@ -135,6 +143,9 @@ pub struct PaymentMethodMigrate {

/// The connector mandate details of the payment method
pub connector_mandate_details: Option<PaymentsMandateReference>,

// The CIT (customer initiated transaction) transaction id associated with the payment method
pub network_transaction_id: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand All @@ -148,8 +159,27 @@ pub struct PaymentsMandateReferenceRecord {
pub original_payment_authorized_currency: Option<common_enums::Currency>,
}

impl From<PaymentMethodMigrate> for PaymentMethodCreate {
fn from(payment_method_migrate: PaymentMethodMigrate) -> Self {
impl PaymentMethodCreate {
pub fn get_payment_method_create_from_payment_method_migrate(
card_number: CardNumber,
payment_method_migrate: &PaymentMethodMigrate,
) -> Self {
let card_details =
payment_method_migrate
.card
.as_ref()
.map(|payment_method_migrate_card| CardDetail {
card_number: card_number,
card_exp_month: payment_method_migrate_card.card_exp_month.clone(),
card_exp_year: payment_method_migrate_card.card_exp_year.clone(),
card_holder_name: payment_method_migrate_card.card_holder_name.clone(),
nick_name: payment_method_migrate_card.nick_name.clone(),
card_issuing_country: payment_method_migrate_card.card_issuing_country.clone(),
card_network: payment_method_migrate_card.card_network.clone(),
card_issuer: payment_method_migrate_card.card_issuer.clone(),
card_type: payment_method_migrate_card.card_type.clone(),
});

Self {
customer_id: payment_method_migrate.customer_id.clone(),
payment_method: payment_method_migrate.payment_method,
Expand All @@ -163,10 +193,11 @@ impl From<PaymentMethodMigrate> for PaymentMethodCreate {
payment_method_billing_address: payment_method_migrate
.payment_method_billing_address
.clone(),
card: payment_method_migrate.card.clone(),
card: card_details,
card_network: payment_method_migrate.card_network.clone(),
bank_transfer: payment_method_migrate.bank_transfer.clone(),
wallet: payment_method_migrate.wallet.clone(),
network_transaction_id: payment_method_migrate.network_transaction_id.clone(),
}
}
}
Expand Down Expand Up @@ -233,6 +264,43 @@ pub struct CardDetail {
pub card_type: Option<String>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct MigrateCardDetail {
/// Card Number
#[schema(value_type = String,example = "4111111145551142")]
pub card_number: String,

/// Card Expiry Month
#[schema(value_type = String,example = "10")]
pub card_exp_month: masking::Secret<String>,

/// Card Expiry Year
#[schema(value_type = String,example = "25")]
pub card_exp_year: masking::Secret<String>,

/// Card Holder Name
#[schema(value_type = String,example = "John Doe")]
pub card_holder_name: Option<masking::Secret<String>>,

/// Card Holder's Nick Name
#[schema(value_type = Option<String>,example = "John Doe")]
pub nick_name: Option<masking::Secret<String>>,

/// Card Issuing Country
pub card_issuing_country: Option<String>,

/// Card's Network
#[schema(value_type = Option<CardNetwork>)]
pub card_network: Option<api_enums::CardNetwork>,

/// Issuer Bank for Card
pub card_issuer: Option<String>,

/// Card Type
pub card_type: Option<String>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct CardDetailUpdate {
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/core/locker_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ pub async fn call_to_locker(
payment_method_data: None,
payment_method_billing_address: None,
connector_mandate_details: None,
network_transaction_id: None,
};

let add_card_result = cards::add_card_hs(
Expand Down
186 changes: 183 additions & 3 deletions crates/router/src/core/payment_methods/cards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ pub async fn get_or_insert_payment_method(

pub async fn migrate_payment_method(
state: routes::SessionState,
req: api::PaymentMethodCreate,
req: api::PaymentMethodMigrate,
merchant_id: String,
) -> errors::RouterResponse<api::PaymentMethodResponse> {
let key_store = state
Expand All @@ -281,7 +281,186 @@ pub async fn migrate_payment_method(
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

get_client_secret_or_add_payment_method(state, req, &merchant_account, &key_store).await
let card_details = req
.card
.as_ref()
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable("The provide payment method can not be migrated")?;

let card_number_validation = cards::CardNumber::from_str(&card_details.card_number);

if card_number_validation.is_ok() {
let card_number = card_number_validation
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Invalid card number")?;

let payment_method_create_request =
api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate(
card_number,
&req,
);
get_client_secret_or_add_payment_method(
state,
payment_method_create_request,
&merchant_account,
&key_store,
)
.await
} else {
skip_locker_call_and_migrate_payment_method(
state,
&req,
merchant_id,
&key_store,
&merchant_account,
)
.await
}
}

pub async fn skip_locker_call_and_migrate_payment_method(
state: routes::SessionState,
req: &api::PaymentMethodMigrate,
merchant_id: String,
key_store: &domain::MerchantKeyStore,
merchant_account: &domain::MerchantAccount,
) -> errors::RouterResponse<api::PaymentMethodResponse> {
let db = &*state.store;
let customer_id = req.customer_id.clone().get_required_value("customer_id")?;

let connector_mandate_details = req
.connector_mandate_details
.clone()
.map(serde_json::to_value)
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)?;

let payment_method_billing_address =
create_encrypted_data(key_store, req.payment_method_billing_address.clone())
.await
.map(|details| details.into());

let card = if let Some(card_details) = &req.card {
helpers::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year)?;
let card_number = card_details.card_number.clone();
let last4_digits = card_number
.chars()
.rev()
.take(4)
.collect::<String>()
.chars()
.rev()
.collect::<String>();

let card_isin = card_number.chars().take(6).collect::<String>();

Some(api::CardDetailFromLocker {
scheme: None,
last4_digits: Some(last4_digits),
issuer_country: card_details.card_issuing_country.clone(),
card_number: None,
expiry_month: Some(card_details.card_exp_month.clone()),
expiry_year: Some(card_details.card_exp_year.clone()),
card_token: None,
card_fingerprint: None,
card_holder_name: card_details.card_holder_name.clone(),
nick_name: card_details.nick_name.clone(),
card_isin: Some(card_isin),
card_issuer: card_details.card_issuer.clone(),
card_network: card_details.card_network.clone(),
card_type: card_details.card_type.clone(),
saved_to_locker: true,
})
} else {
None
};

let payment_method_card_details = card
.as_ref()
.map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));

let payment_method_data_encrypted =
create_encrypted_data(key_store, payment_method_card_details)
.await
.map(|details| details.into());

let payment_method_metadata: Option<serde_json::Value> =
req.metadata.as_ref().map(|data| data.peek()).cloned();

let payment_method_id = generate_id(consts::ID_LENGTH, "pm");

let customer = db
.find_customer_by_customer_id_merchant_id(
&customer_id,
&merchant_id,
key_store,
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;

let client_secret = generate_id(
consts::ID_LENGTH,
format!("{payment_method_id}_secret").as_str(),
);

let current_time = common_utils::date_time::now();

let response = db
.insert_payment_method(
storage::PaymentMethodNew {
customer_id: customer_id.to_owned(),
merchant_id: merchant_id.to_string(),
payment_method_id: payment_method_id.to_string(),
locker_id: None,
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(),
metadata: payment_method_metadata.map(Secret::new),
payment_method_data: payment_method_data_encrypted,
connector_mandate_details,
customer_acceptance: None,
client_secret: Some(client_secret),
status: enums::PaymentMethodStatus::Active,
network_transaction_id: None,
payment_method_issuer_code: None,
accepted_currency: None,
token: None,
cardholder_name: None,
issuer_name: None,
issuer_country: None,
payer_country: None,
is_stored: None,
swift_code: None,
direct_debit_token: None,
created_at: current_time,
last_modified: current_time,
last_used_at: current_time,
payment_method_billing_address,
updated_by: None,
},
merchant_account.storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to add payment method in db")?;

if customer.default_payment_method_id.is_none() && req.payment_method.is_some() {
let _ = set_default_payment_method(
&*state.store,
merchant_id.to_string(),
key_store.clone(),
&customer_id,
payment_method_id.to_owned(),
merchant_account.storage_scheme,
)
.await
.map_err(|err| logger::error!(error=?err,"Failed to set the payment method as default"));
}
Ok(services::api::ApplicationResponse::Json(
api::PaymentMethodResponse::foreign_from((card, response)),
))
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -352,7 +531,7 @@ pub async fn get_client_secret_or_add_payment_method(
}

Ok(services::api::ApplicationResponse::Json(
api::PaymentMethodResponse::foreign_from(res),
api::PaymentMethodResponse::foreign_from((None, res)),
))
}
}
Expand Down Expand Up @@ -947,6 +1126,7 @@ pub async fn update_customer_payment_method(
card_network: None,
payment_method_billing_address: None,
connector_mandate_details: None,
network_transaction_id: None,
};
new_pm.validate()?;

Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/core/payments/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,7 @@ pub(crate) async fn get_payment_method_create_request(
payment_method_data: None,
payment_method_billing_address: None,
connector_mandate_details: None,
network_transaction_id: None,
};
Ok(payment_method_request)
}
Expand All @@ -1391,6 +1392,7 @@ pub(crate) async fn get_payment_method_create_request(
payment_method_data: None,
payment_method_billing_address: None,
connector_mandate_details: None,
network_transaction_id: None,
};

Ok(payment_method_request)
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/core/payouts/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ pub async fn save_payout_data_to_locker(
payment_method_data: None,
payment_method_billing_address: None,
connector_mandate_details: None,
network_transaction_id: None,
};

let pm_data = card_isin
Expand Down Expand Up @@ -476,6 +477,7 @@ pub async fn save_payout_data_to_locker(
payment_method_data: None,
payment_method_billing_address: None,
connector_mandate_details: None,
network_transaction_id: None,
},
)
};
Expand Down
1 change: 0 additions & 1 deletion crates/router/src/core/pm_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,6 @@ async fn store_bank_details_in_payment_methods(
last_used_at: now,
connector_mandate_details: None,
customer_acceptance: None,

network_transaction_id: None,
client_secret: None,
payment_method_billing_address: None,
Expand Down
Loading

0 comments on commit e70bbc2

Please sign in to comment.