fix(router): Add additional card info in payment response (#1745)

Co-authored-by: Sahkal Poddar <sahkal.poddar@juspay.in>
This commit is contained in:
Sahkal Poddar
2023-07-21 02:23:00 +05:30
committed by GitHub
parent 3c5d725cc2
commit a891708f67
4 changed files with 143 additions and 74 deletions

View File

@ -6,7 +6,7 @@ use common_utils::{
ext_traits::Encode,
pii::{self, Email},
};
use masking::{PeekInterface, Secret};
use masking::Secret;
use router_derive::Setter;
use time::PrimitiveDateTime;
use url::Url;
@ -723,16 +723,25 @@ pub enum PaymentMethodData {
Upi(UpiData),
}
#[derive(Default, Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct AdditionalCardInfo {
pub card_issuer: Option<String>,
pub card_network: Option<api_enums::CardNetwork>,
pub card_type: Option<String>,
pub card_issuing_country: Option<String>,
pub bank_code: Option<String>,
pub last4: String,
pub card_isin: String,
pub card_exp_month: Secret<String>,
pub card_exp_year: Secret<String>,
pub card_holder_name: Secret<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AdditionalPaymentData {
Card {
card_issuer: Option<String>,
card_network: Option<api_enums::CardNetwork>,
card_type: Option<String>,
card_issuing_country: Option<String>,
bank_code: Option<String>,
},
Card(Box<AdditionalCardInfo>),
BankRedirect {
bank_name: Option<api_enums::BankNames>,
},
@ -1136,11 +1145,17 @@ pub struct ApplepayPaymentMethod {
pub pm_type: String,
}
#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize)]
#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CardResponse {
last4: String,
exp_month: String,
exp_year: String,
pub last4: String,
pub card_type: Option<String>,
pub card_network: Option<api_enums::CardNetwork>,
pub card_issuer: Option<String>,
pub card_issuing_country: Option<String>,
pub card_isin: String,
pub card_exp_month: Secret<String>,
pub card_exp_year: Secret<String>,
pub card_holder_name: Secret<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
@ -1150,21 +1165,21 @@ pub struct RewardData {
pub merchant_id: String,
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)]
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaymentMethodDataResponse {
#[serde(rename = "card")]
Card(CardResponse),
BankTransfer(BankTransferData),
Wallet(WalletData),
PayLater(PayLaterData),
BankTransfer,
Wallet,
PayLater,
Paypal,
BankRedirect(BankRedirectData),
Crypto(CryptoData),
BankDebit(BankDebitData),
BankRedirect,
Crypto,
BankDebit,
MandatePayment,
Reward(RewardData),
Upi(UpiData),
Reward,
Upi,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
@ -1802,35 +1817,35 @@ impl From<PaymentsStartRequest> for PaymentsRequest {
}
}
impl From<Card> for CardResponse {
fn from(card: Card) -> Self {
let card_number_length = card.card_number.peek().clone().len();
impl From<AdditionalCardInfo> for CardResponse {
fn from(card: AdditionalCardInfo) -> Self {
Self {
last4: card.card_number.peek().clone()[card_number_length - 4..card_number_length]
.to_string(),
exp_month: card.card_exp_month.peek().clone(),
exp_year: card.card_exp_year.peek().clone(),
last4: card.last4,
card_type: card.card_type,
card_network: card.card_network,
card_issuer: card.card_issuer,
card_issuing_country: card.card_issuing_country,
card_isin: card.card_isin,
card_exp_month: card.card_exp_month,
card_exp_year: card.card_exp_year,
card_holder_name: card.card_holder_name,
}
}
}
impl From<PaymentMethodData> for PaymentMethodDataResponse {
fn from(payment_method_data: PaymentMethodData) -> Self {
impl From<AdditionalPaymentData> for PaymentMethodDataResponse {
fn from(payment_method_data: AdditionalPaymentData) -> Self {
match payment_method_data {
PaymentMethodData::Card(card) => Self::Card(CardResponse::from(card)),
PaymentMethodData::PayLater(pay_later_data) => Self::PayLater(pay_later_data),
PaymentMethodData::Wallet(wallet_data) => Self::Wallet(wallet_data),
PaymentMethodData::BankRedirect(bank_redirect_data) => {
Self::BankRedirect(bank_redirect_data)
}
PaymentMethodData::BankTransfer(bank_transfer_data) => {
Self::BankTransfer(*bank_transfer_data)
}
PaymentMethodData::Crypto(crpto_data) => Self::Crypto(crpto_data),
PaymentMethodData::BankDebit(bank_debit_data) => Self::BankDebit(bank_debit_data),
PaymentMethodData::MandatePayment => Self::MandatePayment,
PaymentMethodData::Reward(reward_data) => Self::Reward(reward_data),
PaymentMethodData::Upi(upi_data) => Self::Upi(upi_data),
AdditionalPaymentData::Card(card) => Self::Card(CardResponse::from(*card)),
AdditionalPaymentData::PayLater {} => Self::PayLater,
AdditionalPaymentData::Wallet {} => Self::Wallet,
AdditionalPaymentData::BankRedirect { .. } => Self::BankRedirect,
AdditionalPaymentData::Crypto {} => Self::Crypto,
AdditionalPaymentData::BankDebit {} => Self::BankDebit,
AdditionalPaymentData::MandatePayment {} => Self::MandatePayment,
AdditionalPaymentData::Reward {} => Self::Reward,
AdditionalPaymentData::Upi {} => Self::Upi,
AdditionalPaymentData::BankTransfer {} => Self::BankTransfer,
}
}
}

View File

@ -22,6 +22,17 @@ impl CardNumber {
pub fn get_card_isin(self) -> String {
self.0.peek().chars().take(6).collect::<String>()
}
pub fn get_last4(self) -> String {
self.0
.peek()
.chars()
.rev()
.take(4)
.collect::<String>()
.chars()
.rev()
.collect::<String>()
}
}
impl FromStr for CardNumber {

View File

@ -2506,43 +2506,65 @@ pub async fn get_additional_payment_data(
) -> api_models::payments::AdditionalPaymentData {
match pm_data {
api_models::payments::PaymentMethodData::Card(card_data) => {
let card_isin = card_data.card_number.clone().get_card_isin();
let last4 = card_data.card_number.clone().get_last4();
if card_data.card_issuer.is_some()
&& card_data.card_network.is_some()
&& card_data.card_type.is_some()
&& card_data.card_issuing_country.is_some()
&& card_data.bank_code.is_some()
{
api_models::payments::AdditionalPaymentData::Card {
api_models::payments::AdditionalPaymentData::Card(Box::new(
api_models::payments::AdditionalCardInfo {
card_issuer: card_data.card_issuer.to_owned(),
card_network: card_data.card_network.clone(),
card_type: card_data.card_type.to_owned(),
card_issuing_country: card_data.card_issuing_country.to_owned(),
bank_code: card_data.bank_code.to_owned(),
}
card_exp_month: card_data.card_exp_month.clone(),
card_exp_year: card_data.card_exp_year.clone(),
card_holder_name: card_data.card_holder_name.clone(),
last4: last4.clone(),
card_isin: card_isin.clone(),
},
))
} else {
let card_number = card_data.clone().card_number;
let card_info = db
.get_card_info(&card_number.get_card_isin())
.get_card_info(&card_isin.clone())
.await
.map_err(|error| services::logger::warn!(card_info_error=?error))
.ok()
.flatten()
.map(
|card_info| api_models::payments::AdditionalPaymentData::Card {
.map(|card_info| {
api_models::payments::AdditionalPaymentData::Card(Box::new(
api_models::payments::AdditionalCardInfo {
card_issuer: card_info.card_issuer,
card_network: card_info.card_network.clone(),
bank_code: card_info.bank_code,
card_type: card_info.card_type,
card_issuing_country: card_info.card_issuing_country,
last4: last4.clone(),
card_isin: card_isin.clone(),
card_exp_month: card_data.card_exp_month.clone(),
card_exp_year: card_data.card_exp_year.clone(),
card_holder_name: card_data.card_holder_name.clone(),
},
);
card_info.unwrap_or(api_models::payments::AdditionalPaymentData::Card {
))
});
card_info.unwrap_or(api_models::payments::AdditionalPaymentData::Card(Box::new(
api_models::payments::AdditionalCardInfo {
card_issuer: None,
card_network: None,
bank_code: None,
card_type: None,
card_issuing_country: None,
})
last4,
card_isin,
card_exp_month: card_data.card_exp_month.clone(),
card_exp_year: card_data.card_exp_year.clone(),
card_holder_name: card_data.card_holder_name.clone(),
},
)))
}
}
api_models::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => {

View File

@ -232,6 +232,17 @@ where
_server: &Server,
_operation: Op,
) -> RouterResponse<Self> {
let additional_payment_method_data: Option<api_models::payments::AdditionalPaymentData> =
data.payment_attempt
.payment_method_data
.clone()
.map(|data| data.parse_value("payment_method_data"))
.transpose()
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "payment_method_data",
})?;
let payment_method_data_response =
additional_payment_method_data.map(api::PaymentMethodDataResponse::from);
Ok(services::ApplicationResponse::Json(Self {
verify_id: Some(data.payment_intent.payment_id),
merchant_id: Some(data.payment_intent.merchant_id),
@ -248,9 +259,7 @@ where
.and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())),
mandate_id: data.mandate_id.map(|mandate_ids| mandate_ids.mandate_id),
payment_method: data.payment_attempt.payment_method,
payment_method_data: data
.payment_method_data
.map(api::PaymentMethodDataResponse::from),
payment_method_data: payment_method_data_response,
payment_token: data.token,
error_code: data.payment_attempt.error_code,
error_message: data.payment_attempt.error_message,
@ -325,6 +334,18 @@ where
.as_ref()
.map(ToString::to_string)
.unwrap_or("".to_owned());
let additional_payment_method_data: Option<api_models::payments::AdditionalPaymentData> =
payment_attempt
.payment_method_data
.clone()
.map(|data| data.parse_value("payment_method_data"))
.transpose()
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "payment_method_data",
})?;
let payment_method_data_response =
additional_payment_method_data.map(api::PaymentMethodDataResponse::from);
let output = Ok(match payment_request {
Some(_request) => {
@ -437,7 +458,7 @@ where
auth_flow == services::AuthFlow::Merchant,
)
.set_payment_method_data(
payment_method_data.map(api::PaymentMethodDataResponse::from),
payment_method_data_response,
auth_flow == services::AuthFlow::Merchant,
)
.set_payment_token(payment_attempt.payment_token)
@ -496,7 +517,7 @@ where
capture_method: payment_attempt.capture_method,
error_message: payment_attempt.error_message,
error_code: payment_attempt.error_code,
payment_method_data: payment_method_data.map(api::PaymentMethodDataResponse::from),
payment_method_data: payment_method_data_response,
email: customer
.as_ref()
.and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())),