From 9b9bce80a6419abdd5318d993f1abd6598853dd3 Mon Sep 17 00:00:00 2001
From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com>
Date: Wed, 20 Mar 2024 16:36:26 +0530
Subject: [PATCH] refactor(payment_method_data): add a trait to retrieve
billing from payment method data (#4095)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
---
crates/api_models/src/payments.rs | 600 ++++++++++++++++++++++++++++--
1 file changed, 576 insertions(+), 24 deletions(-)
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 40d11543c0..349e3548eb 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -992,6 +992,49 @@ pub enum PayLaterData {
AtomeRedirect {},
}
+impl GetAddressFromPaymentMethodData for PayLaterData {
+ fn get_billing_address(&self) -> Option
{
+ match self {
+ Self::KlarnaRedirect {
+ billing_email,
+ billing_country,
+ } => {
+ let address_details = AddressDetails {
+ country: Some(*billing_country),
+ ..AddressDetails::default()
+ };
+
+ Some(Address {
+ address: Some(address_details),
+ email: Some(billing_email.clone()),
+ phone: None,
+ })
+ }
+ Self::AfterpayClearpayRedirect {
+ billing_email,
+ billing_name,
+ } => {
+ let address_details = AddressDetails {
+ first_name: Some(billing_name.clone()),
+ ..AddressDetails::default()
+ };
+
+ Some(Address {
+ address: Some(address_details),
+ email: Some(billing_email.clone()),
+ phone: None,
+ })
+ }
+ Self::PayBrightRedirect {}
+ | Self::WalleyRedirect {}
+ | Self::AlmaRedirect {}
+ | Self::KlarnaSdk { .. }
+ | Self::AffirmRedirect {}
+ | Self::AtomeRedirect {} => None,
+ }
+ }
+}
+
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, ToSchema, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum BankDebitData {
@@ -1059,6 +1102,50 @@ pub enum BankDebitData {
},
}
+impl GetAddressFromPaymentMethodData for BankDebitData {
+ fn get_billing_address(&self) -> Option {
+ fn get_billing_address_inner(
+ bank_debit_billing: &BankDebitBilling,
+ bank_account_holder_name: Option<&Secret>,
+ ) -> Option {
+ // We will always have address here
+ let mut address = bank_debit_billing.get_billing_address()?;
+
+ // Prefer `account_holder_name` over `name`
+ address.address.as_mut().map(|address| {
+ address.first_name = bank_account_holder_name
+ .or(address.first_name.as_ref())
+ .cloned();
+ });
+
+ Some(address)
+ }
+
+ match self {
+ Self::AchBankDebit {
+ billing_details,
+ bank_account_holder_name,
+ ..
+ }
+ | Self::SepaBankDebit {
+ billing_details,
+ bank_account_holder_name,
+ ..
+ }
+ | Self::BecsBankDebit {
+ billing_details,
+ bank_account_holder_name,
+ ..
+ }
+ | Self::BacsBankDebit {
+ billing_details,
+ bank_account_holder_name,
+ ..
+ } => get_billing_address_inner(billing_details, bank_account_holder_name.as_ref()),
+ }
+ }
+}
+
/// Custom serializer and deserializer for PaymentMethodData
mod payment_method_data_serde {
@@ -1196,35 +1283,44 @@ pub enum PaymentMethodData {
CardToken(CardToken),
}
-impl PaymentMethodData {
- pub fn get_payment_method_type_if_session_token_type(
- &self,
- ) -> Option {
+pub trait GetAddressFromPaymentMethodData {
+ fn get_billing_address(&self) -> Option;
+}
+
+impl GetAddressFromPaymentMethodData for PaymentMethodData {
+ fn get_billing_address(&self) -> Option {
match self {
- Self::Wallet(wallet) => match wallet {
- WalletData::ApplePay(_) => Some(api_enums::PaymentMethodType::ApplePay),
- WalletData::GooglePay(_) => Some(api_enums::PaymentMethodType::GooglePay),
- WalletData::PaypalSdk(_) => Some(api_enums::PaymentMethodType::Paypal),
- _ => None,
- },
- Self::PayLater(pay_later) => match pay_later {
- PayLaterData::KlarnaSdk { .. } => Some(api_enums::PaymentMethodType::Klarna),
- _ => None,
- },
- Self::Card(_)
- | Self::CardRedirect(_)
- | Self::BankRedirect(_)
- | Self::BankDebit(_)
- | Self::BankTransfer(_)
- | Self::Crypto(_)
- | Self::MandatePayment
- | Self::Reward {}
+ Self::Card(card_data) => {
+ card_data
+ .card_holder_name
+ .as_ref()
+ .map(|card_holder_name| Address {
+ address: Some(AddressDetails {
+ first_name: Some(card_holder_name.clone()),
+ ..AddressDetails::default()
+ }),
+ email: None,
+ phone: None,
+ })
+ }
+ Self::CardRedirect(_) => None,
+ Self::Wallet(wallet_data) => wallet_data.get_billing_address(),
+ Self::PayLater(pay_later_data) => pay_later_data.get_billing_address(),
+ Self::BankRedirect(bank_redirect_data) => bank_redirect_data.get_billing_address(),
+ Self::BankDebit(bank_debit_data) => bank_debit_data.get_billing_address(),
+ Self::BankTransfer(bank_transfer_data) => bank_transfer_data.get_billing_address(),
+ Self::Voucher(voucher_data) => voucher_data.get_billing_address(),
+ Self::Crypto(_)
+ | Self::Reward
| Self::Upi(_)
- | Self::Voucher(_)
| Self::GiftCard(_)
- | Self::CardToken(_) => None,
+ | Self::CardToken(_)
+ | Self::MandatePayment => None,
}
}
+}
+
+impl PaymentMethodData {
pub fn apply_additional_payment_data(
&self,
additional_payment_data: AdditionalPaymentData,
@@ -1647,6 +1743,123 @@ pub enum BankRedirectData {
},
}
+impl GetAddressFromPaymentMethodData for BankRedirectData {
+ fn get_billing_address(&self) -> Option {
+ let get_billing_address_inner = |bank_redirect_billing: Option<&BankRedirectBilling>,
+ billing_country: Option<&common_enums::CountryAlpha2>,
+ billing_email: Option<&Email>|
+ -> Option {
+ let address = bank_redirect_billing
+ .and_then(GetAddressFromPaymentMethodData::get_billing_address);
+
+ let address = match (address, billing_country) {
+ (Some(mut address), Some(billing_country)) => {
+ address
+ .address
+ .as_mut()
+ .map(|address| address.country = Some(*billing_country));
+
+ Some(address)
+ }
+ (Some(address), None) => Some(address),
+ (None, Some(billing_country)) => Some(Address {
+ address: Some(AddressDetails {
+ country: Some(*billing_country),
+ ..AddressDetails::default()
+ }),
+ phone: None,
+ email: None,
+ }),
+ (None, None) => None,
+ };
+
+ match (address, billing_email) {
+ (Some(mut address), Some(email)) => {
+ address.email = Some(email.clone());
+ Some(address)
+ }
+ (Some(address), None) => Some(address),
+ (None, Some(billing_email)) => Some(Address {
+ address: None,
+ phone: None,
+ email: Some(billing_email.clone()),
+ }),
+ (None, None) => None,
+ }
+ };
+
+ match self {
+ Self::BancontactCard {
+ billing_details,
+ card_holder_name,
+ ..
+ } => {
+ let address = get_billing_address_inner(billing_details.as_ref(), None, None);
+
+ if let Some(mut address) = address {
+ address.address.as_mut().map(|address| {
+ address.first_name = card_holder_name
+ .as_ref()
+ .or(address.first_name.as_ref())
+ .cloned();
+ });
+
+ Some(address)
+ } else {
+ Some(Address {
+ address: Some(AddressDetails {
+ first_name: card_holder_name.clone(),
+ ..AddressDetails::default()
+ }),
+ phone: None,
+ email: None,
+ })
+ }
+ }
+ Self::Eps {
+ billing_details,
+ country,
+ ..
+ }
+ | Self::Giropay {
+ billing_details,
+ country,
+ ..
+ }
+ | Self::Ideal {
+ billing_details,
+ country,
+ ..
+ }
+ | Self::Sofort {
+ billing_details,
+ country,
+ ..
+ } => get_billing_address_inner(billing_details.as_ref(), country.as_ref(), None),
+ Self::Interac { country, email } => {
+ get_billing_address_inner(None, Some(country), Some(email))
+ }
+ Self::OnlineBankingFinland { email } => {
+ get_billing_address_inner(None, None, email.as_ref())
+ }
+ Self::OpenBankingUk { country, .. } => {
+ get_billing_address_inner(None, country.as_ref(), None)
+ }
+ Self::Przelewy24 {
+ billing_details, ..
+ } => get_billing_address_inner(Some(billing_details), None, None),
+ Self::Trustly { country } => get_billing_address_inner(None, Some(country), None),
+ Self::OnlineBankingFpx { .. }
+ | Self::OnlineBankingThailand { .. }
+ | Self::Bizum {}
+ | Self::OnlineBankingPoland { .. }
+ | Self::OnlineBankingSlovakia { .. }
+ | Self::OnlineBankingCzechRepublic { .. }
+ | Self::Blik { .. } => None,
+ }
+ }
+}
+
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct AlfamartVoucherData {
/// The billing first name for Alfamart
@@ -1755,6 +1968,28 @@ pub struct BankRedirectBilling {
pub email: Option,
}
+impl GetAddressFromPaymentMethodData for BankRedirectBilling {
+ fn get_billing_address(&self) -> Option {
+ let address_details = self
+ .billing_name
+ .as_ref()
+ .map(|billing_name| AddressDetails {
+ first_name: Some(billing_name.clone()),
+ ..AddressDetails::default()
+ });
+
+ if address_details.is_some() || self.email.is_some() {
+ Some(Address {
+ address: address_details,
+ phone: None,
+ email: self.email.clone(),
+ })
+ } else {
+ None
+ }
+ }
+}
+
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum BankTransferData {
@@ -1810,6 +2045,59 @@ pub enum BankTransferData {
Pse {},
}
+impl GetAddressFromPaymentMethodData for BankTransferData {
+ fn get_billing_address(&self) -> Option {
+ match self {
+ Self::AchBankTransfer { billing_details } => Some(Address {
+ address: None,
+ phone: None,
+ email: Some(billing_details.email.clone()),
+ }),
+ Self::SepaBankTransfer {
+ billing_details,
+ country,
+ } => Some(Address {
+ address: Some(AddressDetails {
+ country: Some(*country),
+ first_name: Some(billing_details.name.clone()),
+ ..AddressDetails::default()
+ }),
+ phone: None,
+ email: Some(billing_details.email.clone()),
+ }),
+ Self::BacsBankTransfer { billing_details } => Some(Address {
+ address: Some(AddressDetails {
+ first_name: Some(billing_details.name.clone()),
+ ..AddressDetails::default()
+ }),
+ phone: None,
+ email: Some(billing_details.email.clone()),
+ }),
+ Self::MultibancoBankTransfer { billing_details } => Some(Address {
+ address: None,
+ phone: None,
+ email: Some(billing_details.email.clone()),
+ }),
+ Self::PermataBankTransfer { billing_details }
+ | Self::BcaBankTransfer { billing_details }
+ | Self::BniVaBankTransfer { billing_details }
+ | Self::BriVaBankTransfer { billing_details }
+ | Self::CimbVaBankTransfer { billing_details }
+ | Self::DanamonVaBankTransfer { billing_details }
+ | Self::MandiriVaBankTransfer { billing_details } => Some(Address {
+ address: Some(AddressDetails {
+ first_name: Some(billing_details.first_name.clone()),
+ last_name: billing_details.last_name.clone(),
+ ..AddressDetails::default()
+ }),
+ phone: None,
+ email: Some(billing_details.email.clone()),
+ }),
+ Self::Pix {} | Self::Pse {} => None,
+ }
+ }
+}
+
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, ToSchema, Eq, PartialEq)]
pub struct BankDebitBilling {
/// The billing name for bank debits
@@ -1822,6 +2110,30 @@ pub struct BankDebitBilling {
pub address: Option,
}
+impl GetAddressFromPaymentMethodData for BankDebitBilling {
+ fn get_billing_address(&self) -> Option {
+ let address = if let Some(mut address) = self.address.clone() {
+ address.first_name = Some(self.name.clone());
+ Address {
+ address: Some(address),
+ email: Some(self.email.clone()),
+ phone: None,
+ }
+ } else {
+ Address {
+ address: Some(AddressDetails {
+ first_name: Some(self.name.clone()),
+ ..AddressDetails::default()
+ }),
+ email: Some(self.email.clone()),
+ phone: None,
+ }
+ };
+
+ Some(address)
+ }
+}
+
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum WalletData {
@@ -1878,6 +2190,57 @@ pub enum WalletData {
SwishQr(SwishQrData),
}
+impl GetAddressFromPaymentMethodData for WalletData {
+ fn get_billing_address(&self) -> Option {
+ match self {
+ Self::MbWayRedirect(mb_way_redirect) => {
+ let phone = PhoneDetails {
+ // Portuguese country code, this payment method is applicable only in portugal
+ country_code: Some("+351".into()),
+ number: Some(mb_way_redirect.telephone_number.clone()),
+ };
+
+ Some(Address {
+ phone: Some(phone),
+ address: None,
+ email: None,
+ })
+ }
+ Self::MobilePayRedirect(_) => None,
+ Self::PaypalRedirect(paypal_redirect) => {
+ paypal_redirect.email.clone().map(|email| Address {
+ email: Some(email),
+ address: None,
+ phone: None,
+ })
+ }
+ Self::AliPayQr(_)
+ | Self::AliPayRedirect(_)
+ | Self::AliPayHkRedirect(_)
+ | Self::MomoRedirect(_)
+ | Self::KakaoPayRedirect(_)
+ | Self::GoPayRedirect(_)
+ | Self::GcashRedirect(_)
+ | Self::ApplePay(_)
+ | Self::ApplePayRedirect(_)
+ | Self::ApplePayThirdPartySdk(_)
+ | Self::DanaRedirect {}
+ | Self::GooglePay(_)
+ | Self::GooglePayRedirect(_)
+ | Self::GooglePayThirdPartySdk(_)
+ | Self::PaypalSdk(_)
+ | Self::SamsungPay(_)
+ | Self::TwintRedirect {}
+ | Self::VippsRedirect {}
+ | Self::TouchNGoRedirect(_)
+ | Self::WeChatPayRedirect(_)
+ | Self::WeChatPayQr(_)
+ | Self::CashappQr(_)
+ | Self::SwishQr(_) => None,
+ }
+ }
+}
+
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct SamsungPayWalletData {
@@ -2062,6 +2425,54 @@ pub enum VoucherData {
PayEasy(Box),
}
+impl GetAddressFromPaymentMethodData for VoucherData {
+ fn get_billing_address(&self) -> Option {
+ match self {
+ Self::Alfamart(voucher_data) => Some(Address {
+ address: Some(AddressDetails {
+ first_name: Some(voucher_data.first_name.clone()),
+ last_name: voucher_data.last_name.clone(),
+ ..AddressDetails::default()
+ }),
+ phone: None,
+ email: Some(voucher_data.email.clone()),
+ }),
+ Self::Indomaret(voucher_data) => Some(Address {
+ address: Some(AddressDetails {
+ first_name: Some(voucher_data.first_name.clone()),
+ last_name: voucher_data.last_name.clone(),
+ ..AddressDetails::default()
+ }),
+ phone: None,
+ email: Some(voucher_data.email.clone()),
+ }),
+ Self::Lawson(voucher_data)
+ | Self::MiniStop(voucher_data)
+ | Self::FamilyMart(voucher_data)
+ | Self::Seicomart(voucher_data)
+ | Self::PayEasy(voucher_data)
+ | Self::SevenEleven(voucher_data) => Some(Address {
+ address: Some(AddressDetails {
+ first_name: Some(voucher_data.first_name.clone()),
+ last_name: voucher_data.last_name.clone(),
+ ..AddressDetails::default()
+ }),
+ phone: Some(PhoneDetails {
+ number: Some(voucher_data.phone_number.clone().into()),
+ country_code: None,
+ }),
+ email: Some(voucher_data.email.clone()),
+ }),
+ Self::Boleto(_)
+ | Self::Efecty
+ | Self::PagoEfectivo
+ | Self::RedCompra
+ | Self::RedPagos
+ | Self::Oxxo => None,
+ }
+ }
+}
+
/// Use custom serializer to provide backwards compatible response for `reward` payment_method_data
pub fn serialize_payment_method_data_response(
payment_method_data_response: &Option,
@@ -4133,3 +4544,144 @@ mod payments_request_api_contract {
);
}
}
+
+/// Set of tests to extract billing details from payment method data
+/// These are required for backwards compatibility
+#[cfg(test)]
+mod billing_from_payment_method_data {
+ #![allow(clippy::unwrap_used)]
+ use common_enums::CountryAlpha2;
+
+ use super::*;
+
+ const TEST_COUNTRY: CountryAlpha2 = CountryAlpha2::US;
+ const TEST_FIRST_NAME: &str = "John";
+
+ #[test]
+ fn test_wallet_payment_method_data_paypal() {
+ let test_email: Email = Email::try_from("example@example.com".to_string()).unwrap();
+
+ let paypal_wallet_payment_method_data =
+ PaymentMethodData::Wallet(WalletData::PaypalRedirect(PaypalRedirection {
+ email: Some(test_email.clone()),
+ }));
+
+ let billing_address = paypal_wallet_payment_method_data
+ .get_billing_address()
+ .unwrap();
+
+ assert_eq!(billing_address.email.unwrap(), test_email);
+
+ assert!(billing_address.address.is_none());
+ assert!(billing_address.phone.is_none());
+ }
+
+ #[test]
+ fn test_bank_redirect_payment_method_data_eps() {
+ let test_email = Email::try_from("example@example.com".to_string()).unwrap();
+ let test_first_name = Secret::new(String::from("Chaser"));
+
+ let bank_redirect_billing = BankRedirectBilling {
+ billing_name: Some(test_first_name.clone()),
+ email: Some(test_email.clone()),
+ };
+
+ let eps_bank_redirect_payment_method_data =
+ PaymentMethodData::BankRedirect(BankRedirectData::Eps {
+ billing_details: Some(bank_redirect_billing),
+ bank_name: None,
+ country: Some(TEST_COUNTRY),
+ });
+
+ let billing_address = eps_bank_redirect_payment_method_data
+ .get_billing_address()
+ .unwrap();
+
+ let address_details = billing_address.address.unwrap();
+
+ assert_eq!(billing_address.email.unwrap(), test_email);
+ assert_eq!(address_details.country.unwrap(), TEST_COUNTRY);
+ assert_eq!(address_details.first_name.unwrap(), test_first_name);
+ assert!(billing_address.phone.is_none());
+ }
+
+ #[test]
+ fn test_paylater_payment_method_data_klarna() {
+ let test_email: Email = Email::try_from("example@example.com".to_string()).unwrap();
+
+ let klarna_paylater_payment_method_data =
+ PaymentMethodData::PayLater(PayLaterData::KlarnaRedirect {
+ billing_email: test_email.clone(),
+ billing_country: TEST_COUNTRY,
+ });
+
+ let billing_address = klarna_paylater_payment_method_data
+ .get_billing_address()
+ .unwrap();
+
+ assert_eq!(billing_address.email.unwrap(), test_email);
+ assert_eq!(
+ billing_address.address.unwrap().country.unwrap(),
+ TEST_COUNTRY
+ );
+ assert!(billing_address.phone.is_none());
+ }
+
+ #[test]
+ fn test_bank_debit_payment_method_data_ach() {
+ let test_email = Email::try_from("example@example.com".to_string()).unwrap();
+ let test_first_name = Secret::new(String::from("Chaser"));
+
+ let bank_redirect_billing = BankDebitBilling {
+ name: test_first_name.clone(),
+ address: None,
+ email: test_email.clone(),
+ };
+
+ let ach_bank_debit_payment_method_data =
+ PaymentMethodData::BankDebit(BankDebitData::AchBankDebit {
+ billing_details: bank_redirect_billing,
+ account_number: Secret::new("1234".to_string()),
+ routing_number: Secret::new("1235".to_string()),
+ card_holder_name: None,
+ bank_account_holder_name: None,
+ bank_name: None,
+ bank_type: None,
+ bank_holder_type: None,
+ });
+
+ let billing_address = ach_bank_debit_payment_method_data
+ .get_billing_address()
+ .unwrap();
+
+ let address_details = billing_address.address.unwrap();
+
+ assert_eq!(billing_address.email.unwrap(), test_email);
+ assert_eq!(address_details.first_name.unwrap(), test_first_name);
+ assert!(billing_address.phone.is_none());
+ }
+
+ #[test]
+ fn test_card_payment_method_data() {
+ let card_payment_method_data = PaymentMethodData::Card(Card {
+ card_holder_name: Some(Secret::new(TEST_FIRST_NAME.into())),
+ ..Default::default()
+ });
+
+ let billing_address = card_payment_method_data.get_billing_address().unwrap();
+
+ assert_eq!(
+ billing_address.address.unwrap().first_name.unwrap(),
+ Secret::new(TEST_FIRST_NAME.into())
+ );
+ }
+
+ #[test]
+ fn test_card_payment_method_data_empty() {
+ let card_payment_method_data = PaymentMethodData::Card(Card::default());
+
+ let billing_address = card_payment_method_data.get_billing_address();
+
+ assert!(billing_address.is_none());
+ }
+}