use api_models::{payment_methods as payment_methods_api, payments as payments_api};
use cards::CardNumber;
use common_enums as enums;
use common_utils::{
errors,
ext_traits::OptionExt,
id_type, pii,
transformers::{ForeignFrom, ForeignTryFrom},
};
use error_stack::report;
use crate::{
address::{Address, AddressDetails, PhoneDetails},
router_request_types::CustomerDetails,
};
#[derive(Debug)]
pub struct CardNetworkTokenizeRequest {
pub data: TokenizeDataRequest,
pub customer: CustomerDetails,
pub billing: Option
,
pub metadata: Option,
pub payment_method_issuer: Option,
}
#[derive(Debug)]
pub enum TokenizeDataRequest {
Card(TokenizeCardRequest),
ExistingPaymentMethod(TokenizePaymentMethodRequest),
}
#[derive(Clone, Debug)]
pub struct TokenizeCardRequest {
pub raw_card_number: CardNumber,
pub card_expiry_month: masking::Secret,
pub card_expiry_year: masking::Secret,
pub card_cvc: Option>,
pub card_holder_name: Option>,
pub nick_name: Option>,
pub card_issuing_country: Option,
pub card_network: Option,
pub card_issuer: Option,
pub card_type: Option,
}
#[derive(Clone, Debug)]
pub struct TokenizePaymentMethodRequest {
pub payment_method_id: String,
pub card_cvc: Option>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize)]
pub struct CardNetworkTokenizeRecord {
// Card details
pub raw_card_number: Option,
pub card_expiry_month: Option>,
pub card_expiry_year: Option>,
pub card_cvc: Option>,
pub card_holder_name: Option>,
pub nick_name: Option>,
pub card_issuing_country: Option,
pub card_network: Option,
pub card_issuer: Option,
pub card_type: Option,
// Payment method details
pub payment_method_id: Option,
pub payment_method_type: Option,
pub payment_method_issuer: Option,
// Customer details
pub customer_id: id_type::CustomerId,
#[serde(rename = "name")]
pub customer_name: Option>,
#[serde(rename = "email")]
pub customer_email: Option,
#[serde(rename = "phone")]
pub customer_phone: Option>,
#[serde(rename = "phone_country_code")]
pub customer_phone_country_code: Option,
// Billing details
pub billing_address_city: Option,
pub billing_address_country: Option,
pub billing_address_line1: Option>,
pub billing_address_line2: Option>,
pub billing_address_line3: Option>,
pub billing_address_zip: Option>,
pub billing_address_state: Option>,
pub billing_address_first_name: Option>,
pub billing_address_last_name: Option>,
pub billing_phone_number: Option>,
pub billing_phone_country_code: Option,
pub billing_email: Option,
// Other details
pub line_number: Option,
pub merchant_id: Option,
}
impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::CustomerDetails {
fn foreign_from(record: &CardNetworkTokenizeRecord) -> Self {
Self {
id: record.customer_id.clone(),
name: record.customer_name.clone(),
email: record.customer_email.clone(),
phone: record.customer_phone.clone(),
phone_country_code: record.customer_phone_country_code.clone(),
}
}
}
impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::Address {
fn foreign_from(record: &CardNetworkTokenizeRecord) -> Self {
Self {
address: Some(payments_api::AddressDetails {
first_name: record.billing_address_first_name.clone(),
last_name: record.billing_address_last_name.clone(),
line1: record.billing_address_line1.clone(),
line2: record.billing_address_line2.clone(),
line3: record.billing_address_line3.clone(),
city: record.billing_address_city.clone(),
zip: record.billing_address_zip.clone(),
state: record.billing_address_state.clone(),
country: record.billing_address_country,
}),
phone: Some(payments_api::PhoneDetails {
number: record.billing_phone_number.clone(),
country_code: record.billing_phone_country_code.clone(),
}),
email: record.billing_email.clone(),
}
}
}
impl ForeignTryFrom for payment_methods_api::CardNetworkTokenizeRequest {
type Error = error_stack::Report;
fn foreign_try_from(record: CardNetworkTokenizeRecord) -> Result {
let billing = Some(payments_api::Address::foreign_from(&record));
let customer = payments_api::CustomerDetails::foreign_from(&record);
let merchant_id = record.merchant_id.get_required_value("merchant_id")?;
match (
record.raw_card_number,
record.card_expiry_month,
record.card_expiry_year,
record.payment_method_id,
) {
(Some(raw_card_number), Some(card_expiry_month), Some(card_expiry_year), None) => {
Ok(Self {
merchant_id,
data: payment_methods_api::TokenizeDataRequest::Card(
payment_methods_api::TokenizeCardRequest {
raw_card_number,
card_expiry_month,
card_expiry_year,
card_cvc: record.card_cvc,
card_holder_name: record.card_holder_name,
nick_name: record.nick_name,
card_issuing_country: record.card_issuing_country,
card_network: record.card_network,
card_issuer: record.card_issuer,
card_type: record.card_type.clone(),
},
),
billing,
customer,
metadata: None,
payment_method_issuer: record.payment_method_issuer,
})
}
(None, None, None, Some(payment_method_id)) => Ok(Self {
merchant_id,
data: payment_methods_api::TokenizeDataRequest::ExistingPaymentMethod(
payment_methods_api::TokenizePaymentMethodRequest {
payment_method_id,
card_cvc: record.card_cvc,
},
),
billing,
customer,
metadata: None,
payment_method_issuer: record.payment_method_issuer,
}),
_ => Err(report!(errors::ValidationError::InvalidValue {
message: "Invalid record in bulk tokenization - expected one of card details or payment method details".to_string()
})),
}
}
}
impl ForeignFrom<&TokenizeCardRequest> for payment_methods_api::MigrateCardDetail {
fn foreign_from(card: &TokenizeCardRequest) -> Self {
Self {
card_number: masking::Secret::new(card.raw_card_number.get_card_no()),
card_exp_month: card.card_expiry_month.clone(),
card_exp_year: card.card_expiry_year.clone(),
card_holder_name: card.card_holder_name.clone(),
nick_name: card.nick_name.clone(),
card_issuing_country: card.card_issuing_country.clone(),
card_network: card.card_network.clone(),
card_issuer: card.card_issuer.clone(),
card_type: card
.card_type
.as_ref()
.map(|card_type| card_type.to_string()),
}
}
}
impl ForeignTryFrom for payments_api::CustomerDetails {
type Error = error_stack::Report;
fn foreign_try_from(customer: CustomerDetails) -> Result {
Ok(Self {
id: customer.customer_id.get_required_value("customer_id")?,
name: customer.name,
email: customer.email,
phone: customer.phone,
phone_country_code: customer.phone_country_code,
})
}
}
impl ForeignFrom for CardNetworkTokenizeRequest {
fn foreign_from(req: payment_methods_api::CardNetworkTokenizeRequest) -> Self {
Self {
data: TokenizeDataRequest::foreign_from(req.data),
customer: CustomerDetails::foreign_from(req.customer),
billing: req.billing.map(ForeignFrom::foreign_from),
metadata: req.metadata,
payment_method_issuer: req.payment_method_issuer,
}
}
}
impl ForeignFrom for TokenizeDataRequest {
fn foreign_from(req: payment_methods_api::TokenizeDataRequest) -> Self {
match req {
payment_methods_api::TokenizeDataRequest::Card(card) => {
Self::Card(TokenizeCardRequest {
raw_card_number: card.raw_card_number,
card_expiry_month: card.card_expiry_month,
card_expiry_year: card.card_expiry_year,
card_cvc: card.card_cvc,
card_holder_name: card.card_holder_name,
nick_name: card.nick_name,
card_issuing_country: card.card_issuing_country,
card_network: card.card_network,
card_issuer: card.card_issuer,
card_type: card.card_type,
})
}
payment_methods_api::TokenizeDataRequest::ExistingPaymentMethod(pm) => {
Self::ExistingPaymentMethod(TokenizePaymentMethodRequest {
payment_method_id: pm.payment_method_id,
card_cvc: pm.card_cvc,
})
}
}
}
}
impl ForeignFrom for CustomerDetails {
fn foreign_from(req: payments_api::CustomerDetails) -> Self {
Self {
customer_id: Some(req.id),
name: req.name,
email: req.email,
phone: req.phone,
phone_country_code: req.phone_country_code,
}
}
}
impl ForeignFrom for Address {
fn foreign_from(req: payments_api::Address) -> Self {
Self {
address: req.address.map(ForeignFrom::foreign_from),
phone: req.phone.map(ForeignFrom::foreign_from),
email: req.email,
}
}
}
impl ForeignFrom for AddressDetails {
fn foreign_from(req: payments_api::AddressDetails) -> Self {
Self {
city: req.city,
country: req.country,
line1: req.line1,
line2: req.line2,
line3: req.line3,
zip: req.zip,
state: req.state,
first_name: req.first_name,
last_name: req.last_name,
}
}
}
impl ForeignFrom for PhoneDetails {
fn foreign_from(req: payments_api::PhoneDetails) -> Self {
Self {
number: req.number,
country_code: req.country_code,
}
}
}