From b1ffaf6dcd98fd1f7de72461e4d67fb679369c35 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 8 Aug 2024 00:28:16 +0530 Subject: [PATCH 01/18] feat(payment_methods_v2): Payment methods v2 API models --- crates/api_models/src/payment_methods.rs | 489 +++++++++- .../src/compatibility/stripe/customers.rs | 30 +- .../compatibility/stripe/customers/types.rs | 24 +- crates/router/src/core/locker_migration.rs | 15 + .../router/src/core/payment_methods/cards.rs | 903 +++++++++++++++++- .../surcharge_decision_configs.rs | 17 +- .../src/core/payment_methods/transformers.rs | 130 ++- crates/router/src/core/payments/helpers.rs | 93 ++ .../router/src/core/payments/tokenization.rs | 164 ++++ crates/router/src/core/payouts/helpers.rs | 15 + crates/router/src/routes/payment_methods.rs | 20 +- crates/router/src/types/api/mandates.rs | 32 + .../router/src/types/api/payment_methods.rs | 8 +- crates/router/src/types/transformers.rs | 34 + 14 files changed, 1890 insertions(+), 84 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index c90031dc0772..e29a72eae7c1 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; use cards::CardNumber; use common_utils::{ @@ -20,6 +23,10 @@ use crate::{ payments::{self, BankCodeResponse}, }; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct PaymentMethodCreate { @@ -96,6 +103,52 @@ pub struct PaymentMethodCreate { pub network_transaction_id: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct PaymentMethodCreate { + /// The type of payment method use for the payment. + #[schema(value_type = PaymentMethod,example = "card")] + pub payment_method: Option, + + /// This is a sub-category of payment method. + #[schema(value_type = Option,example = "credit")] + pub payment_method_type: Option, + + /// 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. + #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] + pub metadata: Option, + + /// The unique identifier of the customer. + #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + pub customer_id: Option, + + /// For Client based calls, SDK will use the client_secret + /// in order to call /payment_methods + /// Client secret will be generated whenever a new + /// payment method is created + pub client_secret: Option, + + /// Payment method data to be passed + pub payment_method_data: Option, + + /// The billing details of the payment method + #[schema(value_type = Option
)] + pub billing: Option, + + #[serde(skip_deserializing)] + /// 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, + + #[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, +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] /// This struct is only used by and internal api to migrate payment method pub struct PaymentMethodMigrate { @@ -159,6 +212,10 @@ pub struct PaymentsMandateReferenceRecord { pub original_payment_authorized_currency: Option, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl PaymentMethodCreate { pub fn get_payment_method_create_from_payment_method_migrate( card_number: CardNumber, @@ -202,6 +259,30 @@ impl PaymentMethodCreate { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl PaymentMethodCreate { + pub fn get_payment_method_create_from_payment_method_migrate( + _card_number: CardNumber, + payment_method_migrate: &PaymentMethodMigrate, + ) -> Self { + Self { + customer_id: payment_method_migrate.customer_id.clone(), + payment_method: payment_method_migrate.payment_method, + payment_method_type: payment_method_migrate.payment_method_type, + metadata: payment_method_migrate.metadata.clone(), + payment_method_data: payment_method_migrate.payment_method_data.clone(), + connector_mandate_details: payment_method_migrate.connector_mandate_details.clone(), + client_secret: None, + billing: payment_method_migrate.billing.clone(), + network_transaction_id: payment_method_migrate.network_transaction_id.clone(), + } + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct PaymentMethodUpdate { @@ -218,15 +299,45 @@ pub struct PaymentMethodUpdate { pub client_secret: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct PaymentMethodUpdate { + /// payment method data to be passed + pub payment_method_data: Option, + + /// This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK + #[schema(max_length = 30, min_length = 30, example = "secret_k2uj3he2893eiu2d")] + pub client_secret: Option, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] #[serde(rename_all = "snake_case")] #[serde(rename = "payment_method_data")] +pub enum PaymentMethodUpdateData { + Card(CardDetailUpdate), +} +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "snake_case")] +#[serde(rename = "payment_method_data")] pub enum PaymentMethodCreateData { Card(CardDetail), + #[cfg(feature = "payouts")] + #[schema(value_type = Bank)] + BankTransfer(payouts::Bank), + #[cfg(feature = "payouts")] + #[schema(value_type = Wallet)] + Wallet(payouts::Wallet), } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct CardDetail { @@ -264,6 +375,54 @@ pub struct CardDetail { pub card_type: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive( + Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema, strum::EnumString, strum::Display, +)] +#[serde(rename_all = "snake_case")] +pub enum CardType { + Credit, + Debit, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct CardDetail { + /// Card Number + #[schema(value_type = String,example = "4111111145551142")] + pub card_number: CardNumber, + + /// Card Expiry Month + #[schema(value_type = String,example = "10")] + pub card_exp_month: masking::Secret, + + /// Card Expiry Year + #[schema(value_type = String,example = "25")] + pub card_exp_year: masking::Secret, + + /// Card Holder Name + #[schema(value_type = String,example = "John Doe")] + pub card_holder_name: Option>, + + /// Card Holder's Nick Name + #[schema(value_type = Option,example = "John Doe")] + pub nick_name: Option>, + + /// Card Issuing Country + pub card_issuing_country: Option, + + /// Card's Network + #[schema(value_type = Option)] + pub card_network: Option, + + /// Issuer Bank for Card + pub card_issuer: Option, + + /// Card Type + pub card_type: Option, +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct MigrateCardDetail { @@ -349,6 +508,22 @@ impl CardDetailUpdate { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "snake_case")] +#[serde(rename = "payment_method_data")] +pub enum PaymentMethodResponseData { + Card(CardDetailFromLocker), + #[cfg(feature = "payouts")] + #[schema(value_type = Bank)] + Bank(payouts::Bank), +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct PaymentMethodResponse { /// Unique identifier for a merchant @@ -410,6 +585,52 @@ pub struct PaymentMethodResponse { pub client_secret: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema, Clone)] +pub struct PaymentMethodResponse { + /// Unique identifier for a merchant + #[schema(example = "merchant_1671528864")] + pub merchant_id: id_type::MerchantId, + + /// The unique identifier of the customer. + #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + pub customer_id: Option, + + /// The unique identifier of the Payment method + #[schema(example = "card_rGK4Vi5iSW70MY7J2mIg")] + pub payment_method_id: String, + + /// The type of payment method use for the payment. + #[schema(value_type = PaymentMethod, example = "card")] + pub payment_method: Option, + + /// This is a sub-category of payment method. + #[schema(value_type = Option, example = "credit")] + pub payment_method_type: Option, + + /// Indicates whether the payment method is eligible for recurring payments + #[schema(example = true)] + pub recurring_enabled: bool, + + /// 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. + #[schema(value_type = Option, example = json!({ "city": "NY", "unit": "245" }))] + pub metadata: Option, + + /// A timestamp (ISO 8601 code) that determines when the customer was created + #[schema(value_type = Option, example = "2023-01-18T11:04:09.922Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub created: Option, + + #[schema(value_type = Option, example = "2024-02-24T11:04:09.922Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub last_used_at: Option, + + /// For Client based calls + pub client_secret: Option, + + pub payment_method_data: Option, +} + #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub enum PaymentMethodsData { Card(CardDetailsPaymentMethod), @@ -471,6 +692,11 @@ pub struct Card { pub card_isin: Option, pub nick_name: Option, } + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct CardDetailFromLocker { pub scheme: Option, @@ -507,10 +733,47 @@ pub struct CardDetailFromLocker { pub saved_to_locker: bool, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +pub struct CardDetailFromLocker { + pub issuer_country: Option, + pub last4_digits: Option, + #[serde(skip)] + #[schema(value_type=Option)] + pub card_number: Option, + + #[schema(value_type=Option)] + pub expiry_month: Option>, + + #[schema(value_type=Option)] + pub expiry_year: Option>, + + #[schema(value_type=Option)] + pub card_holder_name: Option>, + + #[schema(value_type=Option)] + pub card_fingerprint: Option>, + + #[schema(value_type=Option)] + pub nick_name: Option>, + + #[schema(value_type = Option)] + pub card_network: Option, + + pub card_isin: Option, + pub card_issuer: Option, + pub card_type: Option, + pub saved_to_locker: bool, +} + fn saved_in_locker_default() -> bool { true } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl From for payments::AdditionalCardInfo { fn from(item: CardDetailFromLocker) -> Self { Self { @@ -533,6 +796,33 @@ impl From for payments::AdditionalCardInfo { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for payments::AdditionalCardInfo { + fn from(item: CardDetailFromLocker) -> Self { + Self { + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type, + card_issuing_country: item.issuer_country.map(|country| country.to_string()), + bank_code: None, + last4: item.last4_digits, + card_isin: item.card_isin, + card_extended_bin: item + .card_number + .map(|card_number| card_number.get_extended_card_bin()), + card_exp_month: item.expiry_month, + card_exp_year: item.expiry_year, + card_holder_name: item.card_holder_name, + payment_checks: None, + authentication_data: None, + } + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl From for CardDetailFromLocker { fn from(item: CardDetailsPaymentMethod) -> Self { Self { @@ -555,6 +845,37 @@ impl From for CardDetailFromLocker { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for CardDetailFromLocker { + fn from(item: CardDetailsPaymentMethod) -> Self { + Self { + issuer_country: item + .issuer_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten(), + last4_digits: item.last4_digits, + card_number: None, + expiry_month: item.expiry_month, + expiry_year: item.expiry_year, + card_holder_name: item.card_holder_name, + card_fingerprint: None, + nick_name: item.nick_name, + card_isin: item.card_isin, + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type, + saved_to_locker: item.saved_to_locker, + } + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl From for CardDetailsPaymentMethod { fn from(item: CardDetailFromLocker) -> Self { Self { @@ -573,6 +894,25 @@ impl From for CardDetailsPaymentMethod { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for CardDetailsPaymentMethod { + fn from(item: CardDetailFromLocker) -> Self { + Self { + issuer_country: item.issuer_country.map(|country| country.to_string()), + last4_digits: item.last4_digits, + expiry_month: item.expiry_month, + expiry_year: item.expiry_year, + nick_name: item.nick_name, + card_holder_name: item.card_holder_name, + card_isin: item.card_isin, + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type, + saved_to_locker: item.saved_to_locker, + } + } +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)] pub struct PaymentExperienceTypes { /// The payment experience enabled @@ -783,6 +1123,10 @@ pub struct RequestPaymentMethodTypes { pub installment_payment_enabled: bool, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] //List Payment Method #[derive(Debug, Clone, serde::Serialize, Default, ToSchema)] #[serde(deny_unknown_fields)] @@ -820,6 +1164,10 @@ pub struct PaymentMethodListRequest { pub limit: Option, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl<'de> serde::Deserialize<'de> for PaymentMethodListRequest { fn deserialize(deserializer: D) -> Result where @@ -901,6 +1249,115 @@ impl<'de> serde::Deserialize<'de> for PaymentMethodListRequest { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +//List Payment Method +#[derive(Debug, Clone, serde::Serialize, Default, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct PaymentMethodListRequest { + /// This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK + #[schema(max_length = 30, min_length = 30, example = "secret_k2uj3he2893eiu2d")] + pub client_secret: Option, + + /// The two-letter ISO currency code + #[schema(value_type = Option>, example = json!(["US", "UK", "IN"]))] + pub accepted_countries: Option>, + + /// Filter by amount + #[schema(example = 60)] + pub amount: Option, + + /// The three-letter ISO currency code + #[schema(value_type = Option>,example = json!(["USD", "EUR"]))] + pub accepted_currencies: Option>, + + /// Indicates whether the payment method is eligible for recurring payments + #[schema(example = true)] + pub recurring_enabled: Option, + + /// Indicates whether the payment method is eligible for card netwotks + #[schema(value_type = Option>, example = json!(["visa", "mastercard"]))] + pub card_networks: Option>, + + /// Indicates the limit of last used payment methods + #[schema(example = 1)] + pub limit: Option, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl<'de> serde::Deserialize<'de> for PaymentMethodListRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> de::Visitor<'de> for FieldVisitor { + type Value = PaymentMethodListRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("Failed while deserializing as map") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut output = PaymentMethodListRequest::default(); + + while let Some(key) = map.next_key()? { + match key { + "client_secret" => { + set_or_reject_duplicate( + &mut output.client_secret, + "client_secret", + map.next_value()?, + )?; + } + "accepted_countries" => match output.accepted_countries.as_mut() { + Some(inner) => inner.push(map.next_value()?), + None => { + output.accepted_countries = Some(vec![map.next_value()?]); + } + }, + "amount" => { + set_or_reject_duplicate( + &mut output.amount, + "amount", + map.next_value()?, + )?; + } + "accepted_currencies" => match output.accepted_currencies.as_mut() { + Some(inner) => inner.push(map.next_value()?), + None => { + output.accepted_currencies = Some(vec![map.next_value()?]); + } + }, + "recurring_enabled" => { + set_or_reject_duplicate( + &mut output.recurring_enabled, + "recurring_enabled", + map.next_value()?, + )?; + } + "card_network" => match output.card_networks.as_mut() { + Some(inner) => inner.push(map.next_value()?), + None => output.card_networks = Some(vec![map.next_value()?]), + }, + "limit" => { + set_or_reject_duplicate(&mut output.limit, "limit", map.next_value()?)?; + } + _ => {} + } + } + + Ok(output) + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } +} + // Try to set the provided value to the data otherwise throw an error fn set_or_reject_duplicate( data: &mut Option, @@ -1015,6 +1472,10 @@ pub struct CustomerPaymentMethodsListResponse { pub is_guest_customer: Option, } +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Serialize, ToSchema)] pub struct PaymentMethodDeleteResponse { /// The unique identifier of the Payment method @@ -1025,6 +1486,14 @@ pub struct PaymentMethodDeleteResponse { #[schema(example = true)] pub deleted: bool, } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct PaymentMethodDeleteResponse { + /// The unique identifier of the Payment method + #[schema(example = "card_rGK4Vi5iSW70MY7J2mIg")] + pub payment_method_id: String, +} #[derive(Debug, serde::Serialize, ToSchema)] pub struct CustomerDefaultPaymentMethodResponse { /// The unique identifier of the Payment method @@ -1063,26 +1532,10 @@ pub struct CustomerPaymentMethod { #[schema(value_type = Option,example = "credit_card")] pub payment_method_type: Option, - /// The name of the bank/ provider issuing the payment method to the end user - #[schema(example = "Citibank")] - pub payment_method_issuer: Option, - - /// A standard code representing the issuer of payment method - #[schema(value_type = Option,example = "jp_applepay")] - pub payment_method_issuer_code: Option, - /// Indicates whether the payment method is eligible for recurring payments #[schema(example = true)] pub recurring_enabled: bool, - /// Indicates whether the payment method is eligible for installment payments - #[schema(example = true)] - pub installment_payment_enabled: bool, - - /// Type of payment experience enabled with the connector - #[schema(value_type = Option>,example = json!(["redirect_to_url"]))] - pub payment_experience: Option>, - /// PaymentMethod Data from locker pub payment_method_data: Option, @@ -1112,7 +1565,7 @@ pub struct CustomerPaymentMethod { pub last_used_at: Option, /// Indicates if the payment method has been set to default or not #[schema(example = true)] - pub default_payment_method_set: bool, + pub is_default: bool, /// The billing details of the payment method #[schema(value_type = Option
)] diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 39616e007bca..440731827ecb 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -17,7 +17,11 @@ use crate::{ types::api::{customers as customer_types, payment_methods}, }; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "customer_v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::CustomersCreate))] pub async fn customer_create( state: web::Data, @@ -59,7 +63,11 @@ pub async fn customer_create( .await } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "customer_v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::CustomersRetrieve))] pub async fn customer_retrieve( state: web::Data, @@ -93,7 +101,11 @@ pub async fn customer_retrieve( .await } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "customer_v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::CustomersUpdate))] pub async fn customer_update( state: web::Data, @@ -138,7 +150,11 @@ pub async fn customer_update( .await } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "customer_v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::CustomersDelete))] pub async fn customer_delete( state: web::Data, @@ -172,7 +188,11 @@ pub async fn customer_delete( .await } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "customer_v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] pub async fn list_customer_payment_method_api( state: web::Data, diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 82b787445cf2..762258113ee9 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -223,27 +223,15 @@ impl From for CustomerPaymentMethodList } } -// Check this in review +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl From for PaymentMethodData { fn from(item: api_types::CustomerPaymentMethod) -> Self { - #[cfg(all( - any(feature = "v1", feature = "v2"), - not(feature = "payment_methods_v2") - ))] let card = item.card.map(From::from); - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] - let card = match item.payment_method_data { - Some(api_types::PaymentMethodListData::Card(card)) => Some(CardDetails::from(card)), - _ => None, - }; Self { - #[cfg(all( - any(feature = "v1", feature = "v2"), - not(feature = "payment_methods_v2") - ))] id: Some(item.payment_token), - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] - id: item.payment_token, object: "payment_method", card, created: item.created, @@ -251,6 +239,10 @@ impl From for PaymentMethodData { } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl From for CardDetails { fn from(item: api_types::CardDetailFromLocker) -> Self { Self { diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs index 9e633f224c04..707304339e17 100644 --- a/crates/router/src/core/locker_migration.rs +++ b/crates/router/src/core/locker_migration.rs @@ -89,6 +89,10 @@ pub async fn rust_locker_migration( )) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn call_to_locker( state: &SessionState, payment_methods: Vec, @@ -185,3 +189,14 @@ pub async fn call_to_locker( Ok(cards_moved) } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn call_to_locker( + _state: &SessionState, + _payment_methods: Vec, + _customer_id: &id_type::CustomerId, + _merchant_id: &id_type::MerchantId, + _merchant_account: &domain::MerchantAccount, +) -> CustomResult { + todo!() +} diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index a7a240339eed..71a7c8b45e9e 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -83,6 +83,10 @@ use crate::{ utils::{self, ConnectorResponseExt, OptionExt}, }; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn create_payment_method( @@ -177,6 +181,105 @@ pub async fn create_payment_method( Ok(response) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +#[allow(clippy::too_many_arguments)] +pub async fn create_payment_method( + state: &routes::SessionState, + req: &api::PaymentMethodCreate, + customer_id: &id_type::CustomerId, + payment_method_id: &str, + locker_id: Option, + merchant_id: &id_type::MerchantId, + pm_metadata: Option, + customer_acceptance: Option, + payment_method_data: Option, + key_store: &domain::MerchantKeyStore, + connector_mandate_details: Option, + status: Option, + network_transaction_id: Option, + storage_scheme: MerchantStorageScheme, + payment_method_billing_address: Option, + card_scheme: Option, +) -> errors::CustomResult { + let db = &*state.store; + let customer = db + .find_customer_by_customer_id_merchant_id( + &state.into(), + customer_id, + merchant_id, + key_store, + 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_owned(), + payment_method_id: payment_method_id.to_string(), + locker_id, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + payment_method_issuer: None, + scheme: card_scheme, + metadata: pm_metadata.map(Secret::new), + payment_method_data, + connector_mandate_details, + customer_acceptance: customer_acceptance.map(Secret::new), + client_secret: Some(client_secret), + status: status.unwrap_or(enums::PaymentMethodStatus::Active), + network_transaction_id: network_transaction_id.to_owned(), + 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, + }, + 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, + merchant_id, + key_store.clone(), + customer_id, + payment_method_id.to_owned(), + storage_scheme, + ) + .await + .map_err(|error| logger::error!(?error, "Failed to set the payment method as default")); + } + Ok(response) +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub fn store_default_payment_method( req: &api::PaymentMethodCreate, customer_id: &id_type::CustomerId, @@ -206,6 +309,33 @@ pub fn store_default_payment_method( (payment_method_response, None) } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub fn store_default_payment_method( + req: &api::PaymentMethodCreate, + customer_id: &id_type::CustomerId, + merchant_id: &id_type::MerchantId, +) -> ( + api::PaymentMethodResponse, + Option, +) { + let pm_id = generate_id(consts::ID_LENGTH, "pm"); + let payment_method_response = api::PaymentMethodResponse { + merchant_id: merchant_id.to_owned(), + customer_id: Some(customer_id.to_owned()), + payment_method_id: pm_id, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + payment_method_data: None, + metadata: req.metadata.clone(), + created: Some(common_utils::date_time::now()), + recurring_enabled: false, //[#219] + last_used_at: Some(common_utils::date_time::now()), + client_secret: None, + }; + + (payment_method_response, None) +} #[instrument(skip_all)] pub async fn get_or_insert_payment_method( state: &routes::SessionState, @@ -276,6 +406,10 @@ pub async fn get_or_insert_payment_method( } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn migrate_payment_method( state: routes::SessionState, req: api::PaymentMethodMigrate, @@ -341,6 +475,17 @@ pub async fn migrate_payment_method( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn migrate_payment_method( + _state: routes::SessionState, + _req: api::PaymentMethodMigrate, + _merchant_id: &id_type::MerchantId, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, +) -> errors::RouterResponse { + todo!() +} + pub async fn populate_bin_details_for_masked_card( card_details: &api_models::payment_methods::MigrateCardDetail, db: &dyn db::StorageInterface, @@ -377,6 +522,10 @@ pub async fn populate_bin_details_for_masked_card( Ok(card_bin_details) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl ForeignTryFrom<( &api_models::payment_methods::MigrateCardDetail, @@ -451,6 +600,209 @@ impl } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl + ForeignTryFrom<( + &api_models::payment_methods::MigrateCardDetail, + Option, + )> for api_models::payment_methods::CardDetailFromLocker +{ + type Error = error_stack::Report; + fn foreign_try_from( + (card_details, card_info): ( + &api_models::payment_methods::MigrateCardDetail, + Option, + ), + ) -> Result { + let (card_isin, last4_digits) = + get_card_bin_and_last4_digits_for_masked_card(card_details.card_number.peek()) + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid card number".to_string(), + })?; + if let Some(card_bin_info) = card_info { + Ok(Self { + last4_digits: Some(last4_digits.clone()), + issuer_country: card_details + .card_issuing_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten() + .or(card_bin_info + .card_issuing_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten()), + card_number: None, + expiry_month: Some(card_details.card_exp_month.clone()), + expiry_year: Some(card_details.card_exp_year.clone()), + card_fingerprint: None, + card_holder_name: card_details.card_holder_name.clone(), + nick_name: card_details.nick_name.clone(), + card_isin: Some(card_isin.clone()), + card_issuer: card_details + .card_issuer + .clone() + .or(card_bin_info.card_issuer), + card_network: card_details + .card_network + .clone() + .or(card_bin_info.card_network), + card_type: card_details.card_type.clone().or(card_bin_info.card_type), + saved_to_locker: false, + }) + } else { + Ok(Self { + last4_digits: Some(last4_digits.clone()), + issuer_country: card_details + .card_issuing_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten(), + card_number: None, + expiry_month: Some(card_details.card_exp_month.clone()), + expiry_year: Some(card_details.card_exp_year.clone()), + card_fingerprint: None, + card_holder_name: card_details.card_holder_name.clone(), + nick_name: card_details.nick_name.clone(), + card_isin: Some(card_isin.clone()), + card_issuer: card_details.card_issuer.clone(), + card_network: card_details.card_network.clone(), + card_type: card_details.card_type.clone(), + saved_to_locker: false, + }) + } + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +pub async fn skip_locker_call_and_migrate_payment_method( + state: routes::SessionState, + req: &api::PaymentMethodMigrate, + merchant_id: id_type::MerchantId, + key_store: &domain::MerchantKeyStore, + merchant_account: &domain::MerchantAccount, + card: api_models::payment_methods::CardDetailFromLocker, +) -> errors::RouterResponse { + let db = &*state.store; + let customer_id = req.customer_id.clone().get_required_value("customer_id")?; + + // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details. + let connector_mandate_details_req = req + .connector_mandate_details + .clone() + .get_required_value("connector mandate details")?; + + let connector_mandate_details = serde_json::to_value(&connector_mandate_details_req) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse connector mandate details")?; + + let payment_method_billing_address: Option>> = req + .billing + .clone() + .async_map(|billing| create_encrypted_data(&state, key_store, billing)) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt Payment method billing address")?; + + let customer = db + .find_customer_by_customer_id_merchant_id( + &(&state).into(), + &customer_id, + &merchant_id, + key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; + + let payment_method_card_details = + PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())); + + let payment_method_data_encrypted: Option>> = Some( + create_encrypted_data(&state, key_store, payment_method_card_details) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt Payment method card details")?, + ); + + let payment_method_metadata: Option = + req.metadata.as_ref().map(|data| data.peek()).cloned(); + + let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); + + 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_owned(), + 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().or(card.scheme.clone()), + metadata: payment_method_metadata.map(Secret::new), + payment_method_data: payment_method_data_encrypted.map(Into::into), + connector_mandate_details: Some(connector_mandate_details), + customer_acceptance: None, + client_secret: None, + 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: payment_method_billing_address.map(Into::into), + updated_by: None, + }, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + + logger::debug!("Payment method inserted in db"); + + if customer.default_payment_method_id.is_none() && req.payment_method.is_some() { + let _ = set_default_payment_method( + &state, + &merchant_id, + key_store.clone(), + &customer_id, + payment_method_id.to_owned(), + merchant_account.storage_scheme, + ) + .await + .map_err(|error| logger::error!(?error, "Failed to set the payment method as default")); + } + Ok(services::api::ApplicationResponse::Json( + api::PaymentMethodResponse::foreign_from((Some(card), response)), + )) +} + +// need to discuss reagrding the migration APIs for v2 +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn skip_locker_call_and_migrate_payment_method( state: routes::SessionState, req: &api::PaymentMethodMigrate, @@ -519,7 +871,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( 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().or(card.scheme.clone()), + scheme: req.card_network.clone(), metadata: payment_method_metadata.map(Secret::new), payment_method_data: payment_method_data_encrypted.map(Into::into), connector_mandate_details: Some(connector_mandate_details), @@ -588,6 +940,10 @@ pub fn get_card_bin_and_last4_digits_for_masked_card( Ok((card_isin, last4_digits)) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub async fn get_client_secret_or_add_payment_method( state: &routes::SessionState, @@ -665,6 +1021,17 @@ pub async fn get_client_secret_or_add_payment_method( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn get_client_secret_or_add_payment_method( + _state: &routes::SessionState, + _req: api::PaymentMethodCreate, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, +) -> errors::RouterResponse { + todo!() +} + #[instrument(skip_all)] pub fn authenticate_pm_client_secret_and_check_expiry( req_client_secret: &String, @@ -693,6 +1060,10 @@ pub fn authenticate_pm_client_secret_and_check_expiry( } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub async fn add_payment_method_data( state: routes::SessionState, @@ -881,6 +1252,10 @@ pub async fn add_payment_method_data( } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub async fn add_payment_method( state: &routes::SessionState, @@ -1128,6 +1503,21 @@ pub async fn add_payment_method( Ok(services::ApplicationResponse::Json(resp)) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn add_payment_method( + _state: &routes::SessionState, + _req: api::PaymentMethodCreate, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, +) -> errors::RouterResponse { + todo!() +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[allow(clippy::too_many_arguments)] pub async fn insert_payment_method( state: &routes::SessionState, @@ -1181,6 +1571,63 @@ pub async fn insert_payment_method( .await } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[allow(clippy::too_many_arguments)] +pub async fn insert_payment_method( + state: &routes::SessionState, + resp: &api::PaymentMethodResponse, + req: &api::PaymentMethodCreate, + key_store: &domain::MerchantKeyStore, + merchant_id: &id_type::MerchantId, + customer_id: &id_type::CustomerId, + pm_metadata: Option, + customer_acceptance: Option, + locker_id: Option, + connector_mandate_details: Option, + network_transaction_id: Option, + storage_scheme: MerchantStorageScheme, + payment_method_billing_address: Option, +) -> errors::RouterResult { + let pm_card_details = match &resp.payment_method_data { + Some(api::PaymentMethodResponseData::Card(card_data)) => Some(PaymentMethodsData::Card( + CardDetailsPaymentMethod::from(card_data.clone()), + )), + _ => None, + }; + + let pm_data_encrypted: Option>> = pm_card_details + .clone() + .async_map(|pm_card| create_encrypted_data(state, key_store, pm_card)) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt payment method data")?; + + create_payment_method( + state, + req, + customer_id, + &resp.payment_method_id, + locker_id, + merchant_id, + pm_metadata, + customer_acceptance, + pm_data_encrypted.map(Into::into), + key_store, + connector_mandate_details, + None, + network_transaction_id, + storage_scheme, + payment_method_billing_address, + None, + ) + .await +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub async fn update_customer_payment_method( state: routes::SessionState, @@ -1396,6 +1843,18 @@ pub async fn update_customer_payment_method( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn update_customer_payment_method( + _state: routes::SessionState, + _merchant_account: domain::MerchantAccount, + _req: api::PaymentMethodUpdate, + _payment_method_id: &str, + _key_store: domain::MerchantKeyStore, +) -> errors::RouterResponse { + todo!() +} + pub fn validate_payment_method_update( card_updation_obj: CardDetailUpdate, existing_card_data: api::CardDetailFromLocker, @@ -3369,6 +3828,202 @@ pub async fn call_surcharge_decision_management_for_saved_card( Ok(()) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[allow(clippy::too_many_arguments)] +pub async fn filter_payment_methods( + graph: &cgraph::ConstraintGraph, + mca_id: String, + payment_methods: &[Secret], + req: &mut api::PaymentMethodListRequest, + resp: &mut Vec, + payment_intent: Option<&storage::PaymentIntent>, + payment_attempt: Option<&storage::PaymentAttempt>, + address: Option<&domain::Address>, + connector: String, + saved_payment_methods: &settings::EligiblePaymentMethods, +) -> errors::CustomResult<(), errors::ApiErrorResponse> { + for payment_method in payment_methods.iter() { + let parse_result = serde_json::from_value::( + payment_method.clone().expose().clone(), + ); + if let Ok(payment_methods_enabled) = parse_result { + let payment_method = payment_methods_enabled.payment_method; + + let allowed_payment_method_types = payment_intent.and_then(|payment_intent| { + payment_intent + .allowed_payment_method_types + .clone() + .map(|val| val.parse_value("Vec")) + .transpose() + .unwrap_or_else(|error| { + logger::error!( + ?error, + "Failed to deserialize PaymentIntent allowed_payment_method_types" + ); + None + }) + }); + + for payment_method_type_info in payment_methods_enabled + .payment_method_types + .unwrap_or_default() + { + if filter_recurring_based(&payment_method_type_info, req.recurring_enabled) + && filter_installment_based( + &payment_method_type_info, + req.installment_payment_enabled, + ) + && filter_amount_based(&payment_method_type_info, req.amount) + { + let payment_method_object = payment_method_type_info.clone(); + + let pm_dir_value: dir::DirValue = + (payment_method_type_info.payment_method_type, payment_method) + .into_dir_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("pm_value_node not created")?; + + let connector_variant = api_enums::Connector::from_str(connector.as_str()) + .change_context(errors::ConnectorError::InvalidConnectorName) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "connector", + }) + .attach_printable_lazy(|| { + format!("unable to parse connector name {connector:?}") + })?; + + let mut context_values: Vec = Vec::new(); + context_values.push(pm_dir_value.clone()); + + payment_intent.map(|intent| { + intent.currency.map(|currency| { + context_values.push(dir::DirValue::PaymentCurrency(currency)) + }) + }); + address.map(|address| { + address.country.map(|country| { + context_values.push(dir::DirValue::BillingCountry( + common_enums::Country::from_alpha2(country), + )) + }) + }); + + // Addition of Connector to context + if let Ok(connector) = api_enums::RoutableConnectors::from_str( + connector_variant.to_string().as_str(), + ) { + context_values.push(dir::DirValue::Connector(Box::new( + api_models::routing::ast::ConnectorChoice { connector }, + ))); + }; + + let filter_pm_based_on_allowed_types = filter_pm_based_on_allowed_types( + allowed_payment_method_types.as_ref(), + &payment_method_object.payment_method_type, + ); + + if payment_attempt + .and_then(|attempt| attempt.mandate_details.as_ref()) + .is_some() + { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NewMandate, + )); + }; + + payment_attempt + .and_then(|attempt| attempt.mandate_data.as_ref()) + .map(|mandate_detail| { + if mandate_detail.update_mandate_id.is_some() { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::UpdateMandate, + )); + } + }); + + payment_attempt + .map(|attempt| { + attempt.mandate_data.is_none() && attempt.mandate_details.is_none() + }) + .and_then(|res| { + res.then(|| { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NonMandate, + )) + }) + }); + + payment_attempt + .and_then(|inner| inner.capture_method) + .map(|capture_method| { + context_values.push(dir::DirValue::CaptureMethod(capture_method)); + }); + + let filter_pm_card_network_based = filter_pm_card_network_based( + payment_method_object.card_networks.as_ref(), + req.card_networks.as_ref(), + &payment_method_object.payment_method_type, + ); + + let saved_payment_methods_filter = req + .client_secret + .as_ref() + .map(|cs| { + if cs.starts_with("pm_") { + saved_payment_methods + .sdk_eligible_payment_methods + .contains(payment_method.to_string().as_str()) + } else { + true + } + }) + .unwrap_or(true); + + let context = AnalysisContext::from_dir_values(context_values.clone()); + logger::info!("Context created for List Payment method is {:?}", context); + + let domain_ident: &[String] = &[mca_id.clone()]; + let result = graph.key_value_analysis( + pm_dir_value.clone(), + &context, + &mut cgraph::Memoization::new(), + &mut cgraph::CycleCheck::new(), + Some(domain_ident), + ); + if let Err(ref e) = result { + logger::error!( + "Error while performing Constraint graph's key value analysis + for list payment methods {:?}", + e + ); + } else if filter_pm_based_on_allowed_types + && filter_pm_card_network_based + && saved_payment_methods_filter + && matches!(result, Ok(())) + { + let response_pm_type = ResponsePaymentMethodIntermediate::new( + payment_method_object, + connector.clone(), + payment_method, + ); + resp.push(response_pm_type); + } else { + logger::error!("Filtering Payment Methods Failed"); + } + } + } + } + } + Ok(()) +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[allow(clippy::too_many_arguments)] pub async fn filter_payment_methods( graph: &cgraph::ConstraintGraph, @@ -3557,6 +4212,25 @@ pub async fn filter_payment_methods( Ok(()) } +// v2 type for PaymentMethodListRequest will not have the installment_payment_enabled field, +// need to re-evaluate filter logic +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[allow(clippy::too_many_arguments)] +pub async fn filter_payment_methods( + _graph: &cgraph::ConstraintGraph, + _mca_id: String, + _payment_methods: &[Secret], + _req: &mut api::PaymentMethodListRequest, + _resp: &mut Vec, + _payment_intent: Option<&storage::PaymentIntent>, + _payment_attempt: Option<&storage::PaymentAttempt>, + _address: Option<&domain::Address>, + _connector: String, + _saved_payment_methods: &settings::EligiblePaymentMethods, +) -> errors::CustomResult<(), errors::ApiErrorResponse> { + todo!() +} + fn filter_amount_based( payment_method: &RequestPaymentMethodTypes, amount: Option, @@ -4129,7 +4803,7 @@ impl SavedPMLPaymentsInfo { ) -> errors::RouterResult { let requires_cvv = db .find_config_by_key_unwrap_or( - format!("{}_requires_cvv", merchant_account.get_id()).as_str(), + format!("{:?}_requires_cvv", merchant_account.get_id()).as_str(), Some("true".to_string()), ) .await @@ -4424,20 +5098,16 @@ async fn generate_saved_pm_response( customer_id: pm.customer_id, payment_method, payment_method_type: pm.payment_method_type, - payment_method_issuer: pm.payment_method_issuer, payment_method_data: pmd, metadata: pm.metadata, - payment_method_issuer_code: pm.payment_method_issuer_code, recurring_enabled: mca_enabled, - installment_payment_enabled: false, - payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), created: Some(pm.created_at), bank: bank_details, surcharge_details: None, requires_cvv: requires_cvv && !(off_session_payment_flag && pm.connector_mandate_details.is_some()), last_used_at: Some(pm.last_used_at), - default_payment_method_set: customer.default_payment_method_id.is_some() + is_default: customer.default_payment_method_id.is_some() && customer.default_payment_method_id == Some(pm.payment_method_id), billing: payment_method_billing, }; @@ -4524,6 +5194,10 @@ where .attach_printable("unable to parse generic data value") } +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] pub async fn get_card_details_with_locker_fallback( pm: &payment_method::PaymentMethod, state: &routes::SessionState, @@ -4560,6 +5234,46 @@ pub async fn get_card_details_with_locker_fallback( }) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn get_card_details_with_locker_fallback( + pm: &payment_method::PaymentMethod, + state: &routes::SessionState, + key_store: &domain::MerchantKeyStore, +) -> errors::RouterResult> { + let key = key_store.key.get_inner().peek(); + let identifier = Identifier::Merchant(key_store.merchant_id.clone()); + let card_decrypted = decrypt_optional::( + &state.into(), + pm.payment_method_data.clone(), + identifier, + key, + ) + .await + .change_context(errors::StorageError::DecryptionError) + .attach_printable("unable to decrypt card details") + .ok() + .flatten() + .map(|x| x.into_inner().expose()) + .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|pmd| match pmd { + PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)), + _ => None, + }); + + Ok(if let Some(crd) = card_decrypted { + Some(crd) + } else { + logger::debug!( + "Getting card details from locker as it is not found in payment methods table" + ); + Some(get_card_details_from_locker(state, pm).await?) + }) +} + +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] pub async fn get_card_details_without_locker_fallback( pm: &payment_method::PaymentMethod, state: &routes::SessionState, @@ -4596,6 +5310,41 @@ pub async fn get_card_details_without_locker_fallback( }) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn get_card_details_without_locker_fallback( + pm: &payment_method::PaymentMethod, + state: &routes::SessionState, + key_store: &domain::MerchantKeyStore, +) -> errors::RouterResult { + let key = key_store.key.get_inner().peek(); + let identifier = Identifier::Merchant(key_store.merchant_id.clone()); + let card_decrypted = decrypt_optional::( + &state.into(), + pm.payment_method_data.clone(), + identifier, + key, + ) + .await + .change_context(errors::StorageError::DecryptionError) + .attach_printable("unable to decrypt card details") + .ok() + .flatten() + .map(|x| x.into_inner().expose()) + .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|pmd| match pmd { + PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)), + _ => None, + }); + Ok(if let Some(crd) = card_decrypted { + crd + } else { + logger::debug!( + "Getting card details from locker as it is not found in payment methods table" + ); + get_card_details_from_locker(state, pm).await? + }) +} + pub async fn get_card_details_from_locker( state: &routes::SessionState, pm: &storage::PaymentMethod, @@ -4972,6 +5721,10 @@ impl TempLockerCardSupport { } } +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub async fn retrieve_payment_method( state: routes::SessionState, @@ -5027,6 +5780,62 @@ pub async fn retrieve_payment_method( )) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn retrieve_payment_method( + state: routes::SessionState, + pm: api::PaymentMethodId, + key_store: domain::MerchantKeyStore, + merchant_account: domain::MerchantAccount, +) -> errors::RouterResponse { + let db = state.store.as_ref(); + let pm = db + .find_payment_method(&pm.payment_method_id, merchant_account.storage_scheme) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + + let card = if pm.payment_method == Some(enums::PaymentMethod::Card) { + let card_detail = if state.conf.locker.locker_enabled { + let card = get_card_from_locker( + &state, + &pm.customer_id, + &pm.merchant_id, + pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting card from card vault")?; + payment_methods::get_card_detail(&pm, card) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting card details from locker")? + } else { + get_card_details_without_locker_fallback(&pm, &state, &key_store).await? + }; + Some(card_detail) + } else { + None + }; + Ok(services::ApplicationResponse::Json( + api::PaymentMethodResponse { + merchant_id: pm.merchant_id, + customer_id: Some(pm.customer_id), + payment_method_id: pm.payment_method_id, + payment_method: pm.payment_method, + payment_method_type: pm.payment_method_type, + metadata: pm.metadata, + created: Some(pm.created_at), + recurring_enabled: false, + payment_method_data: card.clone().map(api::PaymentMethodResponseData::Card), + last_used_at: Some(pm.last_used_at), + client_secret: pm.client_secret, + }, + )) +} + +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub async fn delete_payment_method( state: routes::SessionState, @@ -5107,6 +5916,86 @@ pub async fn delete_payment_method( )) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn delete_payment_method( + state: routes::SessionState, + merchant_account: domain::MerchantAccount, + pm_id: api::PaymentMethodId, + key_store: domain::MerchantKeyStore, +) -> errors::RouterResponse { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + let key = db + .find_payment_method( + pm_id.payment_method_id.as_str(), + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + + let customer = db + .find_customer_by_customer_id_merchant_id( + key_manager_state, + &key.customer_id, + merchant_account.get_id(), + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Customer not found for the payment method")?; + + if key.payment_method == Some(enums::PaymentMethod::Card) { + let response = delete_card_from_locker( + &state, + &key.customer_id, + &key.merchant_id, + key.locker_id.as_ref().unwrap_or(&key.payment_method_id), + ) + .await?; + + if response.status == "Ok" { + logger::info!("Card From locker deleted Successfully!"); + } else { + logger::error!("Error: Deleting Card From Locker!\n{:#?}", response); + Err(errors::ApiErrorResponse::InternalServerError)? + } + } + + db.delete_payment_method_by_merchant_id_payment_method_id( + merchant_account.get_id(), + pm_id.payment_method_id.as_str(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + + if customer.default_payment_method_id.as_ref() == Some(&pm_id.payment_method_id) { + let customer_update = CustomerUpdate::UpdateDefaultPaymentMethod { + default_payment_method_id: Some(None), + }; + + db.update_customer_by_customer_id_merchant_id( + key_manager_state, + key.customer_id, + key.merchant_id, + customer, + customer_update, + &key_store, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update the default payment method id for the customer")?; + }; + + Ok(services::ApplicationResponse::Json( + api::PaymentMethodDeleteResponse { + payment_method_id: key.payment_method_id, + }, + )) +} + pub async fn create_encrypted_data( state: &routes::SessionState, key_store: &domain::MerchantKeyStore, diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 23f79848dee8..86b4f4c95374 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -403,21 +403,8 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( backend_input.payment_method.payment_method_type = customer_payment_method.payment_method_type; - let card_network = match &customer_payment_method.payment_method_data { - Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => card - .scheme - .as_ref() - .map(|scheme| { - scheme - .clone() - .parse_enum("CardNetwork") - .change_context(ConfigError::DslExecutionError) - }) - .transpose()?, - _ => None, - }; - - backend_input.payment_method.card_network = card_network; + // check in review + backend_input.payment_method.card_network = None; let surcharge_details = surcharge_source .generate_surcharge_details_and_populate_surcharge_metadata( diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 9ca2bffc88c2..3580d2159832 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -1,3 +1,12 @@ +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + headers, + pii::{prelude::*, Secret}, + services::{api as services, encryption}, + types::{api, storage}, + utils::OptionExt, +}; use api_models::{enums as api_enums, payment_methods::Card}; use common_utils::{ ext_traits::{Encode, StringExt}, @@ -8,16 +17,7 @@ use common_utils::{ use error_stack::ResultExt; use josekit::jwe; use serde::{Deserialize, Serialize}; - -use crate::{ - configs::settings, - core::errors::{self, CustomResult}, - headers, - pii::{prelude::*, Secret}, - services::{api as services, encryption}, - types::{api, storage}, - utils::OptionExt, -}; +use std::str::FromStr; #[derive(Debug, Serialize)] #[serde(untagged)] @@ -312,7 +312,11 @@ pub async fn mk_add_locker_request_hs( Ok(request) } -#[cfg(feature = "payouts")] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2"), + feature = "payouts" +))] pub fn mk_add_bank_response_hs( bank: api::BankPayout, bank_reference: String, @@ -337,6 +341,32 @@ pub fn mk_add_bank_response_hs( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2", feature = "payouts"))] +pub fn mk_add_bank_response_hs( + bank: api::BankPayout, + bank_reference: String, + req: api::PaymentMethodCreate, + merchant_id: &id_type::MerchantId, +) -> api::PaymentMethodResponse { + api::PaymentMethodResponse { + merchant_id: merchant_id.to_owned(), + customer_id: req.customer_id, + payment_method_id: bank_reference, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + payment_method_data: Some(api::PaymentMethodResponseData::Bank(bank)), + metadata: req.metadata, + created: Some(common_utils::date_time::now()), + recurring_enabled: false, // [#256] + last_used_at: Some(common_utils::date_time::now()), + client_secret: None, + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub fn mk_add_card_response_hs( card: api::CardDetail, card_reference: String, @@ -386,6 +416,47 @@ pub fn mk_add_card_response_hs( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub fn mk_add_card_response_hs( + card: api::CardDetail, + card_reference: String, + req: api::PaymentMethodCreate, + merchant_id: &id_type::MerchantId, +) -> api::PaymentMethodResponse { + let card_number = card.card_number.clone(); + let last4_digits = card_number.get_last4(); + let card_isin = card_number.get_card_isin(); + + let card = api::CardDetailFromLocker { + last4_digits: Some(last4_digits), + issuer_country: card.card_issuing_country, + card_number: Some(card.card_number.clone()), + expiry_month: Some(card.card_exp_month.clone()), + expiry_year: Some(card.card_exp_year.clone()), + card_fingerprint: None, + card_holder_name: card.card_holder_name.clone(), + nick_name: card.nick_name.clone(), + card_isin: Some(card_isin), + card_issuer: card.card_issuer, + card_network: card.card_network, + card_type: card.card_type.map(|card| card.to_string()), + saved_to_locker: true, + }; + api::PaymentMethodResponse { + merchant_id: merchant_id.to_owned(), + customer_id: req.customer_id, + payment_method_id: card_reference, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + payment_method_data: Some(api::PaymentMethodResponseData::Card(card)), + metadata: req.metadata, + created: Some(common_utils::date_time::now()), + recurring_enabled: false, // [#256] + last_used_at: Some(common_utils::date_time::now()), // [#256] + client_secret: req.client_secret, + } +} + pub async fn mk_get_card_request_hs( jwekey: &settings::Jwekey, locker: &settings::Locker, @@ -502,6 +573,10 @@ pub fn mk_delete_card_response( }) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub fn get_card_detail( pm: &storage::PaymentMethod, response: Card, @@ -530,6 +605,39 @@ pub fn get_card_detail( Ok(card_detail) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub fn get_card_detail( + pm: &storage::PaymentMethod, + response: Card, +) -> CustomResult { + let card_number = response.card_number; + let last4_digits = card_number.clone().get_last4(); + //fetch form card bin + + let card_detail = api::CardDetailFromLocker { + issuer_country: pm + .issuer_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten(), + last4_digits: Some(last4_digits), + card_number: Some(card_number), + expiry_month: Some(response.card_exp_month), + expiry_year: Some(response.card_exp_year), + card_fingerprint: None, + card_holder_name: response.name_on_card, + nick_name: response.nick_name.map(Secret::new), + card_isin: None, + card_issuer: None, + card_network: None, + card_type: None, + saved_to_locker: true, + }; + Ok(card_detail) +} + //------------------------------------------------TokenizeService------------------------------------------------ pub fn mk_crud_locker_request( locker: &settings::Locker, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 6b39ec672e4c..06b05500f393 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1336,6 +1336,10 @@ where Box::new(PaymentResponse) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub(crate) async fn get_payment_method_create_request( payment_method_data: Option<&domain::PaymentMethodData>, @@ -1419,6 +1423,83 @@ pub(crate) async fn get_payment_method_create_request( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub(crate) async fn get_payment_method_create_request( + payment_method_data: Option<&domain::PaymentMethodData>, + payment_method: Option, + payment_method_type: Option, + customer_id: &Option, + billing_name: Option>, +) -> RouterResult { + match payment_method_data { + Some(pm_data) => match payment_method { + Some(payment_method) => match pm_data { + domain::PaymentMethodData::Card(card) => { + let card_detail = api::CardDetail { + card_number: card.card_number.clone(), + card_exp_month: card.card_exp_month.clone(), + card_exp_year: card.card_exp_year.clone(), + card_holder_name: billing_name, + nick_name: card.nick_name.clone(), + card_issuing_country: card + .card_issuing_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten(), + card_network: card.card_network.clone(), + card_issuer: card.card_issuer.clone(), + card_type: card + .card_type + .as_ref() + .map(|c| api::CardType::from_str(c)) + .transpose() + .ok() + .flatten(), + }; + let payment_method_request = api::PaymentMethodCreate { + payment_method: Some(payment_method), + payment_method_type, + metadata: None, + customer_id: customer_id.clone(), + client_secret: None, + payment_method_data: Some(api::PaymentMethodCreateData::Card(card_detail)), + billing: None, + connector_mandate_details: None, + network_transaction_id: None, + }; + Ok(payment_method_request) + } + _ => { + let payment_method_request = api::PaymentMethodCreate { + payment_method: Some(payment_method), + payment_method_type, + metadata: None, + customer_id: customer_id.clone(), + client_secret: None, + payment_method_data: None, + billing: None, + connector_mandate_details: None, + network_transaction_id: None, + }; + + Ok(payment_method_request) + } + }, + None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_type" + }) + .attach_printable("PaymentMethodType Required")), + }, + None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_data" + }) + .attach_printable("PaymentMethodData required Or Card is already saved")), + } +} + pub async fn get_customer_from_details( state: &SessionState, customer_id: Option, @@ -4049,6 +4130,10 @@ pub async fn get_additional_payment_data( } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn populate_bin_details_for_payment_method_create( card_details: api_models::payment_methods::CardDetail, db: &dyn StorageInterface, @@ -4106,6 +4191,14 @@ pub async fn populate_bin_details_for_payment_method_create( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn populate_bin_details_for_payment_method_create( + card_details: api_models::payment_methods::CardDetail, + db: &dyn StorageInterface, +) -> api_models::payment_methods::CardDetail { + todo!() +} + pub fn validate_customer_access( payment_intent: &PaymentIntent, auth_flow: services::AuthFlow, diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 144c7e87e2bc..775eeeda5d2a 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -54,6 +54,10 @@ impl From<&types::RouterData } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn save_payment_method( @@ -634,6 +638,35 @@ where } } +// check in review +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +#[allow(clippy::too_many_arguments)] +pub async fn save_payment_method( + _state: &SessionState, + _connector_name: String, + _merchant_connector_id: Option, + _save_payment_method_data: SavePaymentMethodData, + _customer_id: Option, + _merchant_account: &domain::MerchantAccount, + _payment_method_type: Option, + _key_store: &domain::MerchantKeyStore, + _amount: Option, + _currency: Option, + _billing_name: Option>, + _payment_method_billing_address: Option<&api::Address>, + _business_profile: &storage::business_profile::BusinessProfile, +) -> RouterResult<(Option, Option)> +where + FData: mandate::MandateBehaviour + Clone, +{ + todo!() +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] async fn skip_saving_card_in_locker( merchant_account: &domain::MerchantAccount, payment_method_request: api::PaymentMethodCreate, @@ -722,6 +755,88 @@ async fn skip_saving_card_in_locker( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +async fn skip_saving_card_in_locker( + merchant_account: &domain::MerchantAccount, + payment_method_request: api::PaymentMethodCreate, +) -> RouterResult<( + api_models::payment_methods::PaymentMethodResponse, + Option, +)> { + let merchant_id = merchant_account.get_id(); + let customer_id = payment_method_request + .clone() + .customer_id + .clone() + .get_required_value("customer_id")?; + let payment_method_id = common_utils::generate_id(consts::ID_LENGTH, "pm"); + + let req_card = match payment_method_request.payment_method_data { + Some(api::PaymentMethodCreateData::Card(card)) => Some(card.clone()), + _ => None, + }; + + let last4_digits = req_card.as_ref().map(|c| c.card_number.get_last4()); + + let card_isin = req_card.as_ref().map(|c| c.card_number.get_card_isin()); + + match req_card { + Some(card) => { + let card_detail = CardDetailFromLocker { + issuer_country: card.card_issuing_country.clone(), + last4_digits: last4_digits.clone(), + card_number: None, + expiry_month: Some(card.card_exp_month.clone()), + expiry_year: Some(card.card_exp_year), + card_holder_name: card.card_holder_name.clone(), + card_fingerprint: None, + nick_name: None, + card_isin: card_isin.clone(), + card_issuer: card.card_issuer.clone(), + card_network: card.card_network.clone(), + card_type: card.card_type.map(|card| card.to_string()), + saved_to_locker: false, + }; + let pm_resp = api::PaymentMethodResponse { + merchant_id: merchant_id.to_owned(), + customer_id: Some(customer_id), + payment_method_id, + payment_method: payment_method_request.payment_method, + payment_method_type: payment_method_request.payment_method_type, + payment_method_data: Some(api::PaymentMethodResponseData::Card(card_detail)), + recurring_enabled: false, + metadata: None, + created: Some(common_utils::date_time::now()), + last_used_at: Some(common_utils::date_time::now()), + client_secret: None, + }; + + Ok((pm_resp, None)) + } + None => { + let pm_id = common_utils::generate_id(consts::ID_LENGTH, "pm"); + let payment_method_response = api::PaymentMethodResponse { + merchant_id: merchant_id.to_owned(), + customer_id: Some(customer_id), + payment_method_id: pm_id, + payment_method: payment_method_request.payment_method, + payment_method_type: payment_method_request.payment_method_type, + metadata: None, + created: Some(common_utils::date_time::now()), + recurring_enabled: false, + last_used_at: Some(common_utils::date_time::now()), + client_secret: None, + payment_method_data: None, + }; + Ok((payment_method_response, None)) + } + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn save_in_locker( state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -772,6 +887,55 @@ pub async fn save_in_locker( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn save_in_locker( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + payment_method_request: api::PaymentMethodCreate, +) -> RouterResult<( + api_models::payment_methods::PaymentMethodResponse, + Option, +)> { + payment_method_request.validate()?; + let merchant_id = merchant_account.get_id(); + let customer_id = payment_method_request + .customer_id + .clone() + .get_required_value("customer_id")?; + match payment_method_request.payment_method_data.clone() { + Some(api::PaymentMethodCreateData::Card(card)) => { + Box::pin(payment_methods::cards::add_card_to_locker( + state, + payment_method_request, + &card, + &customer_id, + merchant_account, + None, + )) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add Card Failed") + } + _ => { + let pm_id = common_utils::generate_id(consts::ID_LENGTH, "pm"); + let payment_method_response = api::PaymentMethodResponse { + merchant_id: merchant_id.clone(), + customer_id: Some(customer_id), + payment_method_id: pm_id, + payment_method: payment_method_request.payment_method, + payment_method_type: payment_method_request.payment_method_type, + metadata: None, + created: Some(common_utils::date_time::now()), + recurring_enabled: false, //[#219] + last_used_at: Some(common_utils::date_time::now()), + client_secret: None, + payment_method_data: None, + }; + Ok((payment_method_response, None)) + } + } +} + pub fn create_payment_method_metadata( metadata: Option<&pii::SecretSerdeValue>, connector_token: Option<(String, String)>, diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index c4010490c202..ebd1f25c1c83 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -195,6 +195,10 @@ pub async fn make_payout_method_data<'a>( } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn save_payout_data_to_locker( state: &SessionState, payout_data: &mut PayoutData, @@ -603,6 +607,17 @@ pub async fn save_payout_data_to_locker( Ok(()) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn save_payout_data_to_locker( + _state: &SessionState, + _payout_data: &mut PayoutData, + _payout_method_data: &api::PayoutMethodData, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, +) -> RouterResult<()> { + todo!() +} + #[cfg(all(feature = "v2", feature = "customer_v2"))] pub async fn get_or_create_customer_details( _state: &SessionState, diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 7b2c406c7749..c193c212b722 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -168,6 +168,10 @@ pub async fn migrate_payment_methods( .await } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodSave))] pub async fn save_payment_method_api( state: web::Data, @@ -706,14 +710,14 @@ mod tests { use super::*; - #[test] - fn test_custom_list_deserialization() { - let dummy_data = "amount=120&recurring_enabled=true&installment_payment_enabled=true"; - let de_query: web::Query = - web::Query::from_query(dummy_data).unwrap(); - let de_struct = de_query.into_inner(); - assert_eq!(de_struct.installment_payment_enabled, Some(true)) - } + // #[test] + // fn test_custom_list_deserialization() { + // let dummy_data = "amount=120&recurring_enabled=true&installment_payment_enabled=true"; + // let de_query: web::Query = + // web::Query::from_query(dummy_data).unwrap(); + // let de_struct = de_query.into_inner(); + // assert_eq!(de_struct.installment_payment_enabled, Some(true)) + // } #[test] fn test_custom_list_deserialization_multi_amount() { diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs index 686f7f3ce495..b478b201cab7 100644 --- a/crates/router/src/types/api/mandates.rs +++ b/crates/router/src/types/api/mandates.rs @@ -3,6 +3,7 @@ pub use api_models::mandates::{MandateId, MandateResponse, MandateRevokedRespons use common_utils::ext_traits::OptionExt; use error_stack::ResultExt; use serde::{Deserialize, Serialize}; +use std::str::FromStr; use crate::{ core::{ @@ -108,6 +109,10 @@ impl MandateResponseExt for MandateResponse { } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl From for MandateCardDetails { fn from(card_details_from_locker: api::payment_methods::CardDetailFromLocker) -> Self { mandates::MandateCardDetails { @@ -128,3 +133,30 @@ impl From for MandateCardDetails { .into() } } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for MandateCardDetails { + fn from(card_details_from_locker: api::payment_methods::CardDetailFromLocker) -> Self { + mandates::MandateCardDetails { + last4_digits: card_details_from_locker.last4_digits, + card_exp_month: card_details_from_locker.expiry_month.clone(), + card_exp_year: card_details_from_locker.expiry_year.clone(), + card_holder_name: card_details_from_locker.card_holder_name, + card_token: None, + scheme: None, + issuer_country: card_details_from_locker + .issuer_country + .map(|country| country.to_string()), + card_fingerprint: card_details_from_locker.card_fingerprint, + card_isin: card_details_from_locker.card_isin, + card_issuer: card_details_from_locker.card_issuer, + card_network: card_details_from_locker.card_network, + card_type: card_details_from_locker + .card_type + .as_ref() + .map(|c| c.to_string()), + nick_name: card_details_from_locker.nick_name, + } + .into() + } +} diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 062a2fdf6307..8f5605dc10c9 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -1,14 +1,14 @@ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub use api_models::payment_methods::{ - CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, + CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardType, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, - PaymentMethodMigrate, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, - TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, - TokenizedWalletValue1, TokenizedWalletValue2, + PaymentMethodMigrate, PaymentMethodResponse, PaymentMethodResponseData, PaymentMethodUpdate, + PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, + TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, }; #[cfg(all( any(feature = "v2", feature = "v1"), diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 57d3b4578562..f87fe5e6279a 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -81,6 +81,10 @@ impl ForeignFrom for storage_enums::RefundType } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl ForeignFrom<( Option, @@ -113,6 +117,36 @@ impl } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl + ForeignFrom<( + Option, + diesel_models::PaymentMethod, + )> for payment_methods::PaymentMethodResponse +{ + fn foreign_from( + (card_details, item): ( + Option, + diesel_models::PaymentMethod, + ), + ) -> Self { + Self { + merchant_id: item.merchant_id, + customer_id: Some(item.customer_id), + payment_method_id: item.payment_method_id, + payment_method: item.payment_method, + payment_method_type: item.payment_method_type, + payment_method_data: card_details + .map(|card| payment_methods::PaymentMethodResponseData::Card(card.clone())), + recurring_enabled: false, + metadata: item.metadata, + created: Some(item.created_at), + last_used_at: None, + client_secret: item.client_secret, + } + } +} + impl ForeignFrom for storage_enums::IntentStatus { fn foreign_from(s: storage_enums::AttemptStatus) -> Self { match s { From dd73c0c182ee4a8ded7ea6c9e1c0c34b78498b08 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:01:10 +0000 Subject: [PATCH 02/18] chore: run formatter --- .../src/core/payment_methods/transformers.rs | 22 ++++++++++--------- crates/router/src/types/api/mandates.rs | 3 ++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 3580d2159832..4c0faec6569e 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -1,12 +1,5 @@ -use crate::{ - configs::settings, - core::errors::{self, CustomResult}, - headers, - pii::{prelude::*, Secret}, - services::{api as services, encryption}, - types::{api, storage}, - utils::OptionExt, -}; +use std::str::FromStr; + use api_models::{enums as api_enums, payment_methods::Card}; use common_utils::{ ext_traits::{Encode, StringExt}, @@ -17,7 +10,16 @@ use common_utils::{ use error_stack::ResultExt; use josekit::jwe; use serde::{Deserialize, Serialize}; -use std::str::FromStr; + +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + headers, + pii::{prelude::*, Secret}, + services::{api as services, encryption}, + types::{api, storage}, + utils::OptionExt, +}; #[derive(Debug, Serialize)] #[serde(untagged)] diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs index b478b201cab7..2378593c551d 100644 --- a/crates/router/src/types/api/mandates.rs +++ b/crates/router/src/types/api/mandates.rs @@ -1,9 +1,10 @@ +use std::str::FromStr; + use api_models::mandates; pub use api_models::mandates::{MandateId, MandateResponse, MandateRevokedResponse}; use common_utils::ext_traits::OptionExt; use error_stack::ResultExt; use serde::{Deserialize, Serialize}; -use std::str::FromStr; use crate::{ core::{ From 013a793896a2eb88d9abdf0e4a731ff18d896020 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:05:11 +0000 Subject: [PATCH 03/18] docs(openapi): re-generate OpenAPI specification --- api-reference/openapi_spec.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 1e62053dde18..0f645f9ff1f4 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -14399,6 +14399,28 @@ "$ref": "#/components/schemas/CardDetail" } } + }, + { + "type": "object", + "required": [ + "bank_transfer" + ], + "properties": { + "bank_transfer": { + "$ref": "#/components/schemas/Bank" + } + } + }, + { + "type": "object", + "required": [ + "wallet" + ], + "properties": { + "wallet": { + "$ref": "#/components/schemas/Wallet" + } + } } ] }, From b0cd0f2c7e995812982223044e9406596805697d Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 8 Aug 2024 00:47:08 +0530 Subject: [PATCH 04/18] fix(payment_methods_v2): script changes --- scripts/ci-checks-v2.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci-checks-v2.sh b/scripts/ci-checks-v2.sh index cbdae114024f..d1936cfe4983 100755 --- a/scripts/ci-checks-v2.sh +++ b/scripts/ci-checks-v2.sh @@ -32,7 +32,7 @@ if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]]; then # A package must be checked if it has been modified if grep --quiet --extended-regexp "^crates/${package_name}" <<< "${files_modified}"; then if [[ "${package_name}" == "storage_impl" ]]; then - all_commands+=("cargo hack clippy --features 'v2,payment_v2,customer_v2' -p storage_impl") + all_commands+=("cargo hack clippy --features 'v2,payment_v2,customer_v2,payment_methods_v2' -p storage_impl") else all_commands+=("cargo hack clippy --feature-powerset --depth 2 --ignore-unknown-features --at-least-one-of 'v2 ' --include-features '${v2_feature_set}' --package '${package_name}'") fi @@ -47,7 +47,7 @@ if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]]; then else # If we are doing this locally or on merge queue, then check for all the V2 crates - all_commands+=("cargo hack clippy --features 'v2,payment_v2,customer_v2' -p storage_impl") + all_commands+=("cargo hack clippy --features 'v2,payment_v2,customer_v2,payment_methods_v2' -p storage_impl") common_command="cargo hack clippy --feature-powerset --depth 2 --ignore-unknown-features --at-least-one-of 'v2 ' --include-features '${v2_feature_set}'" crates_to_include="" From 8a5ef21a24ecf898c0d04c360b28a41ed3623b57 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Mon, 12 Aug 2024 13:34:29 +0530 Subject: [PATCH 05/18] fix(payment_methods_v2): Resolved comments --- crates/api_models/src/payment_methods.rs | 40 ++++++++++++++++--- .../router/src/core/payment_methods/cards.rs | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index e29a72eae7c1..502b363a8c54 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -1,8 +1,3 @@ -use std::{ - collections::{HashMap, HashSet}, - str::FromStr, -}; - use cards::CardNumber; use common_utils::{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, @@ -12,6 +7,9 @@ use common_utils::{ }; use masking::PeekInterface; use serde::de; +use std::collections::{HashMap, HashSet}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use std::str::FromStr; use utoipa::{schema, ToSchema}; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] @@ -149,6 +147,38 @@ pub struct PaymentMethodCreate { pub network_transaction_id: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct PaymentMethodIntentCreate { + /// 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. + #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] + pub metadata: Option, + + /// The billing details of the payment method + #[schema(value_type = Option
)] + pub billing: Option, + + /// The unique identifier of the customer. + #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + pub customer_id: id_type::CustomerId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct PaymentMethodIntentConfirm { + /// For SDK based calls, client_secret would be required + pub client_secret: Option, + + /// The unique identifier of the customer. + #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + pub customer_id: Option, + + /// Payment method data to be passed + pub payment_method_data: Option, +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] /// This struct is only used by and internal api to migrate payment method pub struct PaymentMethodMigrate { diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 9f81c33f5c34..e54df9106313 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -801,7 +801,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( )) } -// need to discuss reagrding the migration APIs for v2 +// need to discuss regarding the migration APIs for v2 #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn skip_locker_call_and_migrate_payment_method( state: routes::SessionState, From 9ca881cc1779e8c78c89abeecc9b82130ce7a14b Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:06:21 +0000 Subject: [PATCH 06/18] chore: run formatter --- crates/api_models/src/payment_methods.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 502b363a8c54..95af8d1a99fb 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -1,3 +1,7 @@ +use std::collections::{HashMap, HashSet}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use std::str::FromStr; + use cards::CardNumber; use common_utils::{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, @@ -7,9 +11,6 @@ use common_utils::{ }; use masking::PeekInterface; use serde::de; -use std::collections::{HashMap, HashSet}; -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use std::str::FromStr; use utoipa::{schema, ToSchema}; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] From 78069f4c80176b03ea4c49244ad03414ec7233b9 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Mon, 12 Aug 2024 15:56:20 +0530 Subject: [PATCH 07/18] fix(payment_methods_v2): Resolved comments --- crates/api_models/src/payment_methods.rs | 14 - .../router/src/core/payment_methods/cards.rs | 600 +++++++++--------- .../src/core/payment_methods/transformers.rs | 1 + crates/router/src/types/api/mandates.rs | 1 + 4 files changed, 295 insertions(+), 321 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 95af8d1a99fb..c9a2d96214ec 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -134,18 +134,6 @@ pub struct PaymentMethodCreate { /// The billing details of the payment method #[schema(value_type = Option
)] pub billing: Option, - - #[serde(skip_deserializing)] - /// 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, - - #[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, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -302,10 +290,8 @@ impl PaymentMethodCreate { payment_method_type: payment_method_migrate.payment_method_type, metadata: payment_method_migrate.metadata.clone(), payment_method_data: payment_method_migrate.payment_method_data.clone(), - connector_mandate_details: payment_method_migrate.connector_mandate_details.clone(), client_secret: None, billing: payment_method_migrate.billing.clone(), - network_transaction_id: payment_method_migrate.network_transaction_id.clone(), } } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index e54df9106313..04d0f8b5baa2 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1112,144 +1112,137 @@ pub async fn add_payment_method_data( return Err((errors::ApiErrorResponse::ClientSecretExpired).into()); }; - match pmd { - api_models::payment_methods::PaymentMethodCreateData::Card(card) => { + let response = match &pmd { + api::PaymentMethodCreateData::Card(card) => { helpers::validate_card_expiry(&card.card_exp_month, &card.card_exp_year)?; - let resp = Box::pin(add_card_to_locker( + Box::pin(add_card_to_locker( &state, req.clone(), - &card, + card, &customer_id, &merchant_account, None, )) .await - .change_context(errors::ApiErrorResponse::InternalServerError); - - match resp { - Ok((mut pm_resp, duplication_check)) => { - if duplication_check.is_some() { - let pm_update = storage::PaymentMethodUpdate::StatusUpdate { - status: Some(enums::PaymentMethodStatus::Inactive), - }; + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add Card Failed") + } + #[cfg(feature = "payouts")] + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_account.get_id(), + )), + }; - db.update_payment_method( - payment_method, - pm_update, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - - get_or_insert_payment_method( - &state, - req.clone(), - &mut pm_resp, - &merchant_account, - &customer_id, - &key_store, - ) - .await?; + let resp = match response { + Ok((mut pm_resp, duplication_check)) => match duplication_check { + Some(payment_methods::DataDuplicationCheck::Duplicated) => { + let existing_pm = get_pre_existing_pm( + &state, + &req, + &merchant_account, + &key_store, + &customer_id, + &mut pm_resp, + payment_method, + ) + .await?; - return Ok(services::ApplicationResponse::Json(pm_resp)); - } else { - let locker_id = pm_resp.payment_method_id.clone(); - pm_resp.payment_method_id.clone_from(&pm_id); - pm_resp.client_secret = Some(client_secret.clone()); + pm_resp.client_secret.clone_from(&existing_pm.client_secret); + pm_resp + } + Some(payment_methods::DataDuplicationCheck::MetaDataChanged) => { + let existing_pm = get_pre_existing_pm( + &state, + &req, + &merchant_account, + &key_store, + &customer_id, + &mut pm_resp, + payment_method, + ) + .await?; - let card_isin = card.card_number.get_card_isin(); + let req_card = match &pmd { + api::PaymentMethodCreateData::Card(card) => Some(card.clone()), + #[cfg(feature = "payouts")] + _ => None, + }; - let card_info = db - .get_card_info(card_isin.as_str()) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get card info")?; + pm_resp.client_secret.clone_from(&existing_pm.client_secret); - let updated_card = CardDetailsPaymentMethod { - issuer_country: card_info - .as_ref() - .and_then(|ci| ci.card_issuing_country.clone()), - last4_digits: Some(card.card_number.get_last4()), - expiry_month: Some(card.card_exp_month), - expiry_year: Some(card.card_exp_year), - nick_name: card.nick_name, - card_holder_name: card.card_holder_name, - card_network: card_info.as_ref().and_then(|ci| ci.card_network.clone()), - card_isin: Some(card_isin), - card_issuer: card_info.as_ref().and_then(|ci| ci.card_issuer.clone()), - card_type: card_info.as_ref().and_then(|ci| ci.card_type.clone()), - saved_to_locker: true, - }; - - let pm_data_encrypted: Encryptable> = - create_encrypted_data( - &state, - &key_store, - PaymentMethodsData::Card(updated_card), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt payment method data")?; + if let Some(card) = req_card { + re_add_payment_method( + &state, + &req, + &customer_id, + &merchant_account, + &key_store, + card, + existing_pm, + ) + .await + .attach_printable("Failed to re add payment method to locker")?; + } - let pm_update = storage::PaymentMethodUpdate::AdditionalDataUpdate { - payment_method_data: Some(pm_data_encrypted.into()), - status: Some(enums::PaymentMethodStatus::Active), - locker_id: Some(locker_id), - payment_method: req.payment_method, - payment_method_issuer: req.payment_method_issuer, - payment_method_type: req.payment_method_type, - }; + pm_resp + } + None => { + let locker_id = pm_resp.payment_method_id.clone(); + pm_resp.payment_method_id.clone_from(&pm_id); + pm_resp.client_secret = Some(client_secret.clone()); - db.update_payment_method( - payment_method, - pm_update, - 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() { - let _ = set_default_payment_method( - &state, - merchant_account.get_id(), - key_store.clone(), - &customer_id, - pm_id, - merchant_account.storage_scheme, - ) - .await - .map_err(|error| { - logger::error!( - ?error, - "Failed to set the payment method as default" - ) - }); - } + let req_card = match &pmd { + api::PaymentMethodCreateData::Card(card) => Some(card.clone()), + #[cfg(feature = "payouts")] + _ => None, + }; - return Ok(services::ApplicationResponse::Json(pm_resp)); - } - } - Err(e) => { - let pm_update = storage::PaymentMethodUpdate::StatusUpdate { - status: Some(enums::PaymentMethodStatus::Inactive), - }; + make_pmd_and_update_payment_method( + &state, + &req, + &merchant_account, + &key_store, + req_card, + payment_method, + locker_id, + ) + .await?; - db.update_payment_method( - payment_method, - pm_update, + if customer.default_payment_method_id.is_none() { + let _ = set_default_payment_method( + &state, + merchant_account.get_id(), + key_store.clone(), + &customer_id, + pm_id, merchant_account.storage_scheme, ) .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to update payment method in db")?; - - return Err(e.attach_printable("Failed to add card to locker")); + .map_err( + |err| logger::error!(error=?err,"Failed to set the payment method as default"), + ); } + + pm_resp } + }, + Err(e) => { + let pm_update = storage::PaymentMethodUpdate::StatusUpdate { + status: Some(enums::PaymentMethodStatus::Inactive), + }; + + db.update_payment_method(payment_method, pm_update, merchant_account.storage_scheme) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update payment method in db")?; + + return Err(e.attach_printable("Failed to add card to locker")); } - } + }; + + return Ok(services::ApplicationResponse::Json(resp)); } #[cfg(all( @@ -1503,6 +1496,191 @@ pub async fn add_payment_method( Ok(services::ApplicationResponse::Json(resp)) } +async fn get_pre_existing_pm( + state: &routes::SessionState, + req: &api::PaymentMethodCreate, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + customer_id: &id_type::CustomerId, + pm_resp: &mut api::PaymentMethodResponse, + payment_method: diesel_models::PaymentMethod, +) -> errors::RouterResult { + let db = &*state.store; + let pm_update = storage::PaymentMethodUpdate::StatusUpdate { + status: Some(enums::PaymentMethodStatus::Inactive), + }; + + db.update_payment_method(payment_method, pm_update, merchant_account.storage_scheme) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + + get_or_insert_payment_method( + state, + req.clone(), + pm_resp, + merchant_account, + customer_id, + key_store, + ) + .await +} + +async fn make_pmd_and_update_payment_method( + state: &routes::SessionState, + req: &api::PaymentMethodCreate, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + req_card: Option, + payment_method: diesel_models::PaymentMethod, + locker_id: String, +) -> errors::RouterResult<()> { + let db = &*state.store; + let pm_data_encrypted = if let Some(card) = req_card { + let card_isin = card.card_number.get_card_isin(); + + let card_info = db + .get_card_info(card_isin.as_str()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get card info")?; + + let updated_card = CardDetailsPaymentMethod { + issuer_country: card_info + .as_ref() + .and_then(|ci| ci.card_issuing_country.clone()), + last4_digits: Some(card.card_number.get_last4()), + expiry_month: Some(card.card_exp_month), + expiry_year: Some(card.card_exp_year), + nick_name: card.nick_name, + card_holder_name: card.card_holder_name, + card_network: card_info.as_ref().and_then(|ci| ci.card_network.clone()), + card_isin: Some(card_isin), + card_issuer: card_info.as_ref().and_then(|ci| ci.card_issuer.clone()), + card_type: card_info.as_ref().and_then(|ci| ci.card_type.clone()), + saved_to_locker: true, + }; + + let payment_method_data_encrypted: Option>> = Some( + create_encrypted_data(state, key_store, updated_card) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt Payment method card details")?, + ); + payment_method_data_encrypted + } else { + None + } + .map(|details| details.into()); + + let pm_update = storage::PaymentMethodUpdate::AdditionalDataUpdate { + payment_method_data: pm_data_encrypted, + status: Some(enums::PaymentMethodStatus::Active), + locker_id: Some(locker_id), + payment_method: req.payment_method, + payment_method_issuer: req.payment_method_issuer.clone(), + payment_method_type: req.payment_method_type, + }; + + db.update_payment_method(payment_method, pm_update, merchant_account.storage_scheme) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + + Ok(()) +} + +async fn re_add_payment_method( + state: &routes::SessionState, + req: &api::PaymentMethodCreate, + customer_id: &id_type::CustomerId, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + card: api::CardDetail, + existing_pm: diesel_models::PaymentMethod, +) -> errors::RouterResult<()> { + let db = &*state.store; + + delete_card_from_locker( + state, + customer_id, + merchant_account.get_id(), + existing_pm + .locker_id + .as_ref() + .unwrap_or(&existing_pm.payment_method_id), + ) + .await?; + + let add_card_resp = add_card_hs( + state, + req.clone(), + &card, + customer_id, + merchant_account, + api::enums::LockerChoice::HyperswitchCardVault, + Some( + existing_pm + .locker_id + .as_ref() + .unwrap_or(&existing_pm.payment_method_id), + ), + ) + .await; + + if let Err(err) = add_card_resp { + logger::error!(vault_err=?err); + db.delete_payment_method_by_merchant_id_payment_method_id( + merchant_account.get_id(), + &existing_pm.payment_method_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + + Err(report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while updating card metadata changes"))? + }; + + let updated_card = Some(api::CardDetailFromLocker { + scheme: None, + last4_digits: Some(card.card_number.get_last4()), + issuer_country: None, + card_number: Some(card.card_number), + expiry_month: Some(card.card_exp_month), + expiry_year: Some(card.card_exp_year), + card_token: None, + card_fingerprint: None, + card_holder_name: card.card_holder_name, + nick_name: card.nick_name, + card_network: None, + card_isin: None, + card_issuer: None, + card_type: None, + saved_to_locker: true, + }); + + let updated_pmd = updated_card + .as_ref() + .map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))); + let pm_data_encrypted: Option>> = updated_pmd + .async_map(|updated_pmd| create_encrypted_data(state, key_store, updated_pmd)) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt payment method data")?; + + let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate { + payment_method_data: pm_data_encrypted.map(Into::into), + }; + + db.update_payment_method(existing_pm, pm_update, merchant_account.storage_scheme) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + + Ok(()) +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all)] pub async fn add_payment_method( @@ -4036,198 +4214,6 @@ pub async fn filter_payment_methods( Ok(()) } -#[cfg(all( - any(feature = "v1", feature = "v2"), - not(feature = "payment_methods_v2") -))] -#[allow(clippy::too_many_arguments)] -pub async fn filter_payment_methods( - graph: &cgraph::ConstraintGraph, - mca_id: String, - payment_methods: &[Secret], - req: &mut api::PaymentMethodListRequest, - resp: &mut Vec, - payment_intent: Option<&storage::PaymentIntent>, - payment_attempt: Option<&storage::PaymentAttempt>, - address: Option<&domain::Address>, - connector: String, - saved_payment_methods: &settings::EligiblePaymentMethods, -) -> errors::CustomResult<(), errors::ApiErrorResponse> { - for payment_method in payment_methods.iter() { - let parse_result = serde_json::from_value::( - payment_method.clone().expose().clone(), - ); - if let Ok(payment_methods_enabled) = parse_result { - let payment_method = payment_methods_enabled.payment_method; - - let allowed_payment_method_types = payment_intent.and_then(|payment_intent| { - payment_intent - .allowed_payment_method_types - .clone() - .map(|val| val.parse_value("Vec")) - .transpose() - .unwrap_or_else(|error| { - logger::error!( - ?error, - "Failed to deserialize PaymentIntent allowed_payment_method_types" - ); - None - }) - }); - - for payment_method_type_info in payment_methods_enabled - .payment_method_types - .unwrap_or_default() - { - if filter_recurring_based(&payment_method_type_info, req.recurring_enabled) - && filter_installment_based( - &payment_method_type_info, - req.installment_payment_enabled, - ) - && filter_amount_based(&payment_method_type_info, req.amount) - { - let payment_method_object = payment_method_type_info.clone(); - - let pm_dir_value: dir::DirValue = - (payment_method_type_info.payment_method_type, payment_method) - .into_dir_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("pm_value_node not created")?; - - let connector_variant = api_enums::Connector::from_str(connector.as_str()) - .change_context(errors::ConnectorError::InvalidConnectorName) - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "connector", - }) - .attach_printable_lazy(|| { - format!("unable to parse connector name {connector:?}") - })?; - - let mut context_values: Vec = Vec::new(); - context_values.push(pm_dir_value.clone()); - - payment_intent.map(|intent| { - intent.currency.map(|currency| { - context_values.push(dir::DirValue::PaymentCurrency(currency)) - }) - }); - address.map(|address| { - address.country.map(|country| { - context_values.push(dir::DirValue::BillingCountry( - common_enums::Country::from_alpha2(country), - )) - }) - }); - - // Addition of Connector to context - if let Ok(connector) = api_enums::RoutableConnectors::from_str( - connector_variant.to_string().as_str(), - ) { - context_values.push(dir::DirValue::Connector(Box::new( - api_models::routing::ast::ConnectorChoice { connector }, - ))); - }; - - let filter_pm_based_on_allowed_types = filter_pm_based_on_allowed_types( - allowed_payment_method_types.as_ref(), - &payment_method_object.payment_method_type, - ); - - if payment_attempt - .and_then(|attempt| attempt.mandate_details.as_ref()) - .is_some() - { - context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::NewMandate, - )); - }; - - payment_attempt - .and_then(|attempt| attempt.mandate_data.as_ref()) - .map(|mandate_detail| { - if mandate_detail.update_mandate_id.is_some() { - context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::UpdateMandate, - )); - } - }); - - payment_attempt - .map(|attempt| { - attempt.mandate_data.is_none() && attempt.mandate_details.is_none() - }) - .and_then(|res| { - res.then(|| { - context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::NonMandate, - )) - }) - }); - - payment_attempt - .and_then(|inner| inner.capture_method) - .map(|capture_method| { - context_values.push(dir::DirValue::CaptureMethod(capture_method)); - }); - - let filter_pm_card_network_based = filter_pm_card_network_based( - payment_method_object.card_networks.as_ref(), - req.card_networks.as_ref(), - &payment_method_object.payment_method_type, - ); - - let saved_payment_methods_filter = req - .client_secret - .as_ref() - .map(|cs| { - if cs.starts_with("pm_") { - saved_payment_methods - .sdk_eligible_payment_methods - .contains(payment_method.to_string().as_str()) - } else { - true - } - }) - .unwrap_or(true); - - let context = AnalysisContext::from_dir_values(context_values.clone()); - logger::info!("Context created for List Payment method is {:?}", context); - - let domain_ident: &[String] = &[mca_id.clone()]; - let result = graph.key_value_analysis( - pm_dir_value.clone(), - &context, - &mut cgraph::Memoization::new(), - &mut cgraph::CycleCheck::new(), - Some(domain_ident), - ); - if let Err(ref e) = result { - logger::error!( - "Error while performing Constraint graph's key value analysis - for list payment methods {:?}", - e - ); - } else if filter_pm_based_on_allowed_types - && filter_pm_card_network_based - && saved_payment_methods_filter - && matches!(result, Ok(())) - { - let response_pm_type = ResponsePaymentMethodIntermediate::new( - payment_method_object, - connector.clone(), - payment_method, - ); - resp.push(response_pm_type); - } else { - logger::error!("Filtering Payment Methods Failed"); - } - } - } - } - } - Ok(()) -} - // v2 type for PaymentMethodListRequest will not have the installment_payment_enabled field, // need to re-evaluate filter logic #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 4c0faec6569e..d913b6ea53c4 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -1,3 +1,4 @@ +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use std::str::FromStr; use api_models::{enums as api_enums, payment_methods::Card}; diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs index 2378593c551d..79e9f62f9c04 100644 --- a/crates/router/src/types/api/mandates.rs +++ b/crates/router/src/types/api/mandates.rs @@ -1,3 +1,4 @@ +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use std::str::FromStr; use api_models::mandates; From 39c128c521d8cf76282e1c00dbce3b6333b2fcf4 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Tue, 13 Aug 2024 18:28:51 +0530 Subject: [PATCH 08/18] fix(payment_methods_v2): Resolved comments --- crates/api_models/src/payment_methods.rs | 13 + .../router/src/core/payment_methods/cards.rs | 408 +++++------------- .../surcharge_decision_configs.rs | 15 +- 3 files changed, 141 insertions(+), 295 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index c9a2d96214ec..c7bb6a68ebbb 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -337,6 +337,7 @@ pub enum PaymentMethodUpdateData { Card(CardDetailUpdate), } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] #[serde(rename_all = "snake_case")] @@ -351,6 +352,18 @@ pub enum PaymentMethodCreateData { Wallet(payouts::Wallet), } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "snake_case")] +#[serde(rename = "payment_method_data")] +pub enum PaymentMethodCreateData { + Card(CardDetail), +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 04d0f8b5baa2..9e919ee60a03 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1112,137 +1112,144 @@ pub async fn add_payment_method_data( return Err((errors::ApiErrorResponse::ClientSecretExpired).into()); }; - let response = match &pmd { - api::PaymentMethodCreateData::Card(card) => { + match pmd { + api_models::payment_methods::PaymentMethodCreateData::Card(card) => { helpers::validate_card_expiry(&card.card_exp_month, &card.card_exp_year)?; - Box::pin(add_card_to_locker( + let resp = Box::pin(add_card_to_locker( &state, req.clone(), - card, + &card, &customer_id, &merchant_account, None, )) .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Add Card Failed") - } - #[cfg(feature = "payouts")] - _ => Ok(store_default_payment_method( - &req, - &customer_id, - merchant_account.get_id(), - )), - }; + .change_context(errors::ApiErrorResponse::InternalServerError); - let resp = match response { - Ok((mut pm_resp, duplication_check)) => match duplication_check { - Some(payment_methods::DataDuplicationCheck::Duplicated) => { - let existing_pm = get_pre_existing_pm( - &state, - &req, - &merchant_account, - &key_store, - &customer_id, - &mut pm_resp, - payment_method, - ) - .await?; + match resp { + Ok((mut pm_resp, duplication_check)) => { + if duplication_check.is_some() { + let pm_update = storage::PaymentMethodUpdate::StatusUpdate { + status: Some(enums::PaymentMethodStatus::Inactive), + }; - pm_resp.client_secret.clone_from(&existing_pm.client_secret); - pm_resp - } - Some(payment_methods::DataDuplicationCheck::MetaDataChanged) => { - let existing_pm = get_pre_existing_pm( - &state, - &req, - &merchant_account, - &key_store, - &customer_id, - &mut pm_resp, - payment_method, - ) - .await?; + db.update_payment_method( + payment_method, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?; + + get_or_insert_payment_method( + &state, + req.clone(), + &mut pm_resp, + &merchant_account, + &customer_id, + &key_store, + ) + .await?; - let req_card = match &pmd { - api::PaymentMethodCreateData::Card(card) => Some(card.clone()), - #[cfg(feature = "payouts")] - _ => None, - }; + return Ok(services::ApplicationResponse::Json(pm_resp)); + } else { + let locker_id = pm_resp.payment_method_id.clone(); + pm_resp.payment_method_id.clone_from(&pm_id); + pm_resp.client_secret = Some(client_secret.clone()); - pm_resp.client_secret.clone_from(&existing_pm.client_secret); + let card_isin = card.card_number.get_card_isin(); - if let Some(card) = req_card { - re_add_payment_method( - &state, - &req, - &customer_id, - &merchant_account, - &key_store, - card, - existing_pm, - ) - .await - .attach_printable("Failed to re add payment method to locker")?; - } + let card_info = db + .get_card_info(card_isin.as_str()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get card info")?; - pm_resp - } - None => { - let locker_id = pm_resp.payment_method_id.clone(); - pm_resp.payment_method_id.clone_from(&pm_id); - pm_resp.client_secret = Some(client_secret.clone()); + let updated_card = CardDetailsPaymentMethod { + issuer_country: card_info + .as_ref() + .and_then(|ci| ci.card_issuing_country.clone()), + last4_digits: Some(card.card_number.get_last4()), + expiry_month: Some(card.card_exp_month), + expiry_year: Some(card.card_exp_year), + nick_name: card.nick_name, + card_holder_name: card.card_holder_name, + card_network: card_info.as_ref().and_then(|ci| ci.card_network.clone()), + card_isin: Some(card_isin), + card_issuer: card_info.as_ref().and_then(|ci| ci.card_issuer.clone()), + card_type: card_info.as_ref().and_then(|ci| ci.card_type.clone()), + saved_to_locker: true, + }; + + let pm_data_encrypted: Encryptable> = + create_encrypted_data( + &state, + &key_store, + PaymentMethodsData::Card(updated_card), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt payment method data")?; - let req_card = match &pmd { - api::PaymentMethodCreateData::Card(card) => Some(card.clone()), - #[cfg(feature = "payouts")] - _ => None, - }; + let pm_update = storage::PaymentMethodUpdate::AdditionalDataUpdate { + payment_method_data: Some(pm_data_encrypted.into()), + status: Some(enums::PaymentMethodStatus::Active), + locker_id: Some(locker_id), + payment_method: req.payment_method, + payment_method_issuer: req.payment_method_issuer, + payment_method_type: req.payment_method_type, + }; - make_pmd_and_update_payment_method( - &state, - &req, - &merchant_account, - &key_store, - req_card, - payment_method, - locker_id, - ) - .await?; + db.update_payment_method( + payment_method, + pm_update, + 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() { + let _ = set_default_payment_method( + &state, + merchant_account.get_id(), + key_store.clone(), + &customer_id, + pm_id, + merchant_account.storage_scheme, + ) + .await + .map_err(|error| { + logger::error!( + ?error, + "Failed to set the payment method as default" + ) + }); + } - if customer.default_payment_method_id.is_none() { - let _ = set_default_payment_method( - &state, - merchant_account.get_id(), - key_store.clone(), - &customer_id, - pm_id, + return Ok(services::ApplicationResponse::Json(pm_resp)); + } + } + Err(e) => { + let pm_update = storage::PaymentMethodUpdate::StatusUpdate { + status: Some(enums::PaymentMethodStatus::Inactive), + }; + + db.update_payment_method( + payment_method, + pm_update, merchant_account.storage_scheme, ) .await - .map_err( - |err| logger::error!(error=?err,"Failed to set the payment method as default"), - ); - } + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update payment method in db")?; - pm_resp + return Err(e.attach_printable("Failed to add card to locker")); + } } - }, - Err(e) => { - let pm_update = storage::PaymentMethodUpdate::StatusUpdate { - status: Some(enums::PaymentMethodStatus::Inactive), - }; - - db.update_payment_method(payment_method, pm_update, merchant_account.storage_scheme) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to update payment method in db")?; - - return Err(e.attach_printable("Failed to add card to locker")); } - }; - - return Ok(services::ApplicationResponse::Json(resp)); + } } #[cfg(all( @@ -1496,191 +1503,6 @@ pub async fn add_payment_method( Ok(services::ApplicationResponse::Json(resp)) } -async fn get_pre_existing_pm( - state: &routes::SessionState, - req: &api::PaymentMethodCreate, - merchant_account: &domain::MerchantAccount, - key_store: &domain::MerchantKeyStore, - customer_id: &id_type::CustomerId, - pm_resp: &mut api::PaymentMethodResponse, - payment_method: diesel_models::PaymentMethod, -) -> errors::RouterResult { - let db = &*state.store; - let pm_update = storage::PaymentMethodUpdate::StatusUpdate { - status: Some(enums::PaymentMethodStatus::Inactive), - }; - - db.update_payment_method(payment_method, pm_update, merchant_account.storage_scheme) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - - get_or_insert_payment_method( - state, - req.clone(), - pm_resp, - merchant_account, - customer_id, - key_store, - ) - .await -} - -async fn make_pmd_and_update_payment_method( - state: &routes::SessionState, - req: &api::PaymentMethodCreate, - merchant_account: &domain::MerchantAccount, - key_store: &domain::MerchantKeyStore, - req_card: Option, - payment_method: diesel_models::PaymentMethod, - locker_id: String, -) -> errors::RouterResult<()> { - let db = &*state.store; - let pm_data_encrypted = if let Some(card) = req_card { - let card_isin = card.card_number.get_card_isin(); - - let card_info = db - .get_card_info(card_isin.as_str()) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get card info")?; - - let updated_card = CardDetailsPaymentMethod { - issuer_country: card_info - .as_ref() - .and_then(|ci| ci.card_issuing_country.clone()), - last4_digits: Some(card.card_number.get_last4()), - expiry_month: Some(card.card_exp_month), - expiry_year: Some(card.card_exp_year), - nick_name: card.nick_name, - card_holder_name: card.card_holder_name, - card_network: card_info.as_ref().and_then(|ci| ci.card_network.clone()), - card_isin: Some(card_isin), - card_issuer: card_info.as_ref().and_then(|ci| ci.card_issuer.clone()), - card_type: card_info.as_ref().and_then(|ci| ci.card_type.clone()), - saved_to_locker: true, - }; - - let payment_method_data_encrypted: Option>> = Some( - create_encrypted_data(state, key_store, updated_card) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt Payment method card details")?, - ); - payment_method_data_encrypted - } else { - None - } - .map(|details| details.into()); - - let pm_update = storage::PaymentMethodUpdate::AdditionalDataUpdate { - payment_method_data: pm_data_encrypted, - status: Some(enums::PaymentMethodStatus::Active), - locker_id: Some(locker_id), - payment_method: req.payment_method, - payment_method_issuer: req.payment_method_issuer.clone(), - payment_method_type: req.payment_method_type, - }; - - db.update_payment_method(payment_method, pm_update, merchant_account.storage_scheme) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - - Ok(()) -} - -async fn re_add_payment_method( - state: &routes::SessionState, - req: &api::PaymentMethodCreate, - customer_id: &id_type::CustomerId, - merchant_account: &domain::MerchantAccount, - key_store: &domain::MerchantKeyStore, - card: api::CardDetail, - existing_pm: diesel_models::PaymentMethod, -) -> errors::RouterResult<()> { - let db = &*state.store; - - delete_card_from_locker( - state, - customer_id, - merchant_account.get_id(), - existing_pm - .locker_id - .as_ref() - .unwrap_or(&existing_pm.payment_method_id), - ) - .await?; - - let add_card_resp = add_card_hs( - state, - req.clone(), - &card, - customer_id, - merchant_account, - api::enums::LockerChoice::HyperswitchCardVault, - Some( - existing_pm - .locker_id - .as_ref() - .unwrap_or(&existing_pm.payment_method_id), - ), - ) - .await; - - if let Err(err) = add_card_resp { - logger::error!(vault_err=?err); - db.delete_payment_method_by_merchant_id_payment_method_id( - merchant_account.get_id(), - &existing_pm.payment_method_id, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; - - Err(report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while updating card metadata changes"))? - }; - - let updated_card = Some(api::CardDetailFromLocker { - scheme: None, - last4_digits: Some(card.card_number.get_last4()), - issuer_country: None, - card_number: Some(card.card_number), - expiry_month: Some(card.card_exp_month), - expiry_year: Some(card.card_exp_year), - card_token: None, - card_fingerprint: None, - card_holder_name: card.card_holder_name, - nick_name: card.nick_name, - card_network: None, - card_isin: None, - card_issuer: None, - card_type: None, - saved_to_locker: true, - }); - - let updated_pmd = updated_card - .as_ref() - .map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))); - let pm_data_encrypted: Option>> = updated_pmd - .async_map(|updated_pmd| create_encrypted_data(state, key_store, updated_pmd)) - .await - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt payment method data")?; - - let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate { - payment_method_data: pm_data_encrypted.map(Into::into), - }; - - db.update_payment_method(existing_pm, pm_update, merchant_account.storage_scheme) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - - Ok(()) -} - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all)] pub async fn add_payment_method( diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 86b4f4c95374..372d918f1b41 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -403,8 +403,19 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( backend_input.payment_method.payment_method_type = customer_payment_method.payment_method_type; - // check in review - backend_input.payment_method.card_network = None; + let card_network = match customer_payment_method.payment_method_data.as_ref() { + api_models::payment_methods::PaymentMethodListData::Card(card) => { + card.card_network.map(|network| { + network + .clone() + .parse_enum("CardNetwork") + .change_context(ConfigError::DslExecutionError) + }) + } + _ => None, + } + .transpose()?; + backend_input.payment_method.card_network = card_network; let surcharge_details = surcharge_source .generate_surcharge_details_and_populate_surcharge_metadata( From 169a890ae32aa9efd6f1639f94e95877841cd2ec Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 07:40:13 +0000 Subject: [PATCH 09/18] docs(openapi): re-generate OpenAPI specification --- api-reference/openapi_spec.json | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index a688ce9dc4e1..e16343595655 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -14522,28 +14522,6 @@ "$ref": "#/components/schemas/CardDetail" } } - }, - { - "type": "object", - "required": [ - "bank_transfer" - ], - "properties": { - "bank_transfer": { - "$ref": "#/components/schemas/Bank" - } - } - }, - { - "type": "object", - "required": [ - "wallet" - ], - "properties": { - "wallet": { - "$ref": "#/components/schemas/Wallet" - } - } } ] }, From bba3b8ae44df2cbebe8285932850475f13db4892 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 14 Aug 2024 14:41:28 +0530 Subject: [PATCH 10/18] fix(payment_methods_v2): fixed v2 funcs --- .../router/src/core/payment_methods/cards.rs | 296 ++---------------- 1 file changed, 25 insertions(+), 271 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 220817f9ca07..94ca57cda607 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -185,95 +185,24 @@ pub async fn create_payment_method( #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn create_payment_method( - state: &routes::SessionState, - req: &api::PaymentMethodCreate, - customer_id: &id_type::CustomerId, - payment_method_id: &str, - locker_id: Option, - merchant_id: &id_type::MerchantId, - pm_metadata: Option, - customer_acceptance: Option, - payment_method_data: Option, - key_store: &domain::MerchantKeyStore, - connector_mandate_details: Option, - status: Option, - network_transaction_id: Option, - storage_scheme: MerchantStorageScheme, - payment_method_billing_address: Option, - card_scheme: Option, + _state: &routes::SessionState, + _req: &api::PaymentMethodCreate, + _customer_id: &id_type::CustomerId, + _payment_method_id: &str, + _locker_id: Option, + _merchant_id: &id_type::MerchantId, + _pm_metadata: Option, + _customer_acceptance: Option, + _payment_method_data: Option, + _key_store: &domain::MerchantKeyStore, + _connector_mandate_details: Option, + _status: Option, + _network_transaction_id: Option, + _storage_scheme: MerchantStorageScheme, + _payment_method_billing_address: Option, + _card_scheme: Option, ) -> errors::CustomResult { - let db = &*state.store; - let customer = db - .find_customer_by_customer_id_merchant_id( - &state.into(), - customer_id, - merchant_id, - key_store, - 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_owned(), - payment_method_id: payment_method_id.to_string(), - locker_id, - payment_method: req.payment_method, - payment_method_type: req.payment_method_type, - payment_method_issuer: None, - scheme: card_scheme, - metadata: pm_metadata.map(Secret::new), - payment_method_data, - connector_mandate_details, - customer_acceptance: customer_acceptance.map(Secret::new), - client_secret: Some(client_secret), - status: status.unwrap_or(enums::PaymentMethodStatus::Active), - network_transaction_id: network_transaction_id.to_owned(), - 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, - }, - 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, - merchant_id, - key_store.clone(), - customer_id, - payment_method_id.to_owned(), - storage_scheme, - ) - .await - .map_err(|error| logger::error!(?error, "Failed to set the payment method as default")); - } - Ok(response) + todo!() } #[cfg(all( @@ -804,120 +733,14 @@ pub async fn skip_locker_call_and_migrate_payment_method( // need to discuss regarding the migration APIs for v2 #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn skip_locker_call_and_migrate_payment_method( - state: routes::SessionState, - req: &api::PaymentMethodMigrate, - merchant_id: id_type::MerchantId, - key_store: &domain::MerchantKeyStore, - merchant_account: &domain::MerchantAccount, - card: api_models::payment_methods::CardDetailFromLocker, + _state: routes::SessionState, + _req: &api::PaymentMethodMigrate, + _merchant_id: id_type::MerchantId, + _key_store: &domain::MerchantKeyStore, + _merchant_account: &domain::MerchantAccount, + _card: api_models::payment_methods::CardDetailFromLocker, ) -> errors::RouterResponse { - let db = &*state.store; - let customer_id = req.customer_id.clone().get_required_value("customer_id")?; - - // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details. - let connector_mandate_details_req = req - .connector_mandate_details - .clone() - .get_required_value("connector mandate details")?; - - let connector_mandate_details = serde_json::to_value(&connector_mandate_details_req) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to parse connector mandate details")?; - - let payment_method_billing_address: Option>> = req - .billing - .clone() - .async_map(|billing| create_encrypted_data(&state, key_store, billing)) - .await - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt Payment method billing address")?; - - let customer = db - .find_customer_by_customer_id_merchant_id( - &(&state).into(), - &customer_id, - &merchant_id, - key_store, - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - - let payment_method_card_details = - PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())); - - let payment_method_data_encrypted: Option>> = Some( - create_encrypted_data(&state, key_store, payment_method_card_details) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt Payment method card details")?, - ); - - let payment_method_metadata: Option = - req.metadata.as_ref().map(|data| data.peek()).cloned(); - - let payment_method_id = generate_id(consts::ID_LENGTH, "pm"); - - 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_owned(), - 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.map(Into::into), - connector_mandate_details: Some(connector_mandate_details), - customer_acceptance: None, - client_secret: None, - 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: payment_method_billing_address.map(Into::into), - updated_by: None, - }, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - - logger::debug!("Payment method inserted in db"); - - if customer.default_payment_method_id.is_none() && req.payment_method.is_some() { - let _ = set_default_payment_method( - &state, - &merchant_id, - key_store.clone(), - &customer_id, - payment_method_id.to_owned(), - merchant_account.storage_scheme, - ) - .await - .map_err(|error| logger::error!(?error, "Failed to set the payment method as default")); - } - Ok(services::api::ApplicationResponse::Json( - api::PaymentMethodResponse::foreign_from((Some(card), response)), - )) + todo!() } pub fn get_card_bin_and_last4_digits_for_masked_card( @@ -5797,76 +5620,7 @@ pub async fn delete_payment_method( pm_id: api::PaymentMethodId, key_store: domain::MerchantKeyStore, ) -> errors::RouterResponse { - let db = state.store.as_ref(); - let key_manager_state = &(&state).into(); - let key = db - .find_payment_method( - pm_id.payment_method_id.as_str(), - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; - - let customer = db - .find_customer_by_customer_id_merchant_id( - key_manager_state, - &key.customer_id, - merchant_account.get_id(), - &key_store, - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Customer not found for the payment method")?; - - if key.payment_method == Some(enums::PaymentMethod::Card) { - let response = delete_card_from_locker( - &state, - &key.customer_id, - &key.merchant_id, - key.locker_id.as_ref().unwrap_or(&key.payment_method_id), - ) - .await?; - - if response.status == "Ok" { - logger::info!("Card From locker deleted Successfully!"); - } else { - logger::error!("Error: Deleting Card From Locker!\n{:#?}", response); - Err(errors::ApiErrorResponse::InternalServerError)? - } - } - - db.delete_payment_method_by_merchant_id_payment_method_id( - merchant_account.get_id(), - pm_id.payment_method_id.as_str(), - ) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; - - if customer.default_payment_method_id.as_ref() == Some(&pm_id.payment_method_id) { - let customer_update = CustomerUpdate::UpdateDefaultPaymentMethod { - default_payment_method_id: Some(None), - }; - - db.update_customer_by_customer_id_merchant_id( - key_manager_state, - key.customer_id, - key.merchant_id, - customer, - customer_update, - &key_store, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to update the default payment method id for the customer")?; - }; - - Ok(services::ApplicationResponse::Json( - api::PaymentMethodDeleteResponse { - payment_method_id: key.payment_method_id, - }, - )) + todo!() } pub async fn create_encrypted_data( From 2c0e5e64458738e7b7abdbef0f00e6af20fd8adc Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 21 Aug 2024 15:06:49 +0530 Subject: [PATCH 11/18] fix(payment_methods_v2): Fixed errors --- .../router/src/core/payment_methods/cards.rs | 20 +++++++++++-------- .../surcharge_decision_configs.rs | 12 +++-------- crates/router/src/core/payments/helpers.rs | 4 ---- crates/router/src/core/payouts/helpers.rs | 1 + 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 5ee53956c6cd..7d3ded09979f 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4970,13 +4970,15 @@ pub async fn get_card_details_with_locker_fallback( ) -> errors::RouterResult> { let key = key_store.key.get_inner().peek(); let identifier = Identifier::Merchant(key_store.merchant_id.clone()); - let card_decrypted = decrypt_optional::( + let card_decrypted = domain::types::crypto_operation::( &state.into(), - pm.payment_method_data.clone(), + type_name!(payment_method::PaymentMethod), + domain::types::CryptoOperation::DecryptOptional(pm.payment_method_data.clone()), identifier, key, ) .await + .and_then(|val| val.try_into_optionaloperation()) .change_context(errors::StorageError::DecryptionError) .attach_printable("unable to decrypt card details") .ok() @@ -5048,13 +5050,15 @@ pub async fn get_card_details_without_locker_fallback( ) -> errors::RouterResult { let key = key_store.key.get_inner().peek(); let identifier = Identifier::Merchant(key_store.merchant_id.clone()); - let card_decrypted = decrypt_optional::( + let card_decrypted = domain::types::crypto_operation::( &state.into(), - pm.payment_method_data.clone(), + type_name!(payment_method::PaymentMethod), + domain::types::CryptoOperation::DecryptOptional(pm.payment_method_data.clone()), identifier, key, ) .await + .and_then(|val| val.try_into_optionaloperation()) .change_context(errors::StorageError::DecryptionError) .attach_printable("unable to decrypt card details") .ok() @@ -5680,10 +5684,10 @@ pub async fn delete_payment_method( #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all)] pub async fn delete_payment_method( - state: routes::SessionState, - merchant_account: domain::MerchantAccount, - pm_id: api::PaymentMethodId, - key_store: domain::MerchantKeyStore, + _state: routes::SessionState, + _merchant_account: domain::MerchantAccount, + _pm_id: api::PaymentMethodId, + _key_store: domain::MerchantKeyStore, ) -> errors::RouterResponse { todo!() } diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 372d918f1b41..477f025c7850 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -404,17 +404,11 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( customer_payment_method.payment_method_type; let card_network = match customer_payment_method.payment_method_data.as_ref() { - api_models::payment_methods::PaymentMethodListData::Card(card) => { - card.card_network.map(|network| { - network - .clone() - .parse_enum("CardNetwork") - .change_context(ConfigError::DslExecutionError) - }) + Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => { + card.card_network.clone() } _ => None, - } - .transpose()?; + }; backend_input.payment_method.card_network = card_network; let surcharge_details = surcharge_source diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index ef273bd92ab2..d7f950bf3fbf 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1482,8 +1482,6 @@ pub(crate) async fn get_payment_method_create_request( client_secret: None, payment_method_data: Some(api::PaymentMethodCreateData::Card(card_detail)), billing: None, - connector_mandate_details: None, - network_transaction_id: None, }; Ok(payment_method_request) } @@ -1496,8 +1494,6 @@ pub(crate) async fn get_payment_method_create_request( client_secret: None, payment_method_data: None, billing: None, - connector_mandate_details: None, - network_transaction_id: None, }; Ok(payment_method_request) diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 562b577fc5ea..c666731fd732 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -612,6 +612,7 @@ pub async fn save_payout_data_to_locker( pub async fn save_payout_data_to_locker( _state: &SessionState, _payout_data: &mut PayoutData, + customer_id: &id_type::CustomerId, _payout_method_data: &api::PayoutMethodData, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, From 194a2bae6d194c7e7f643d50eb74e4ba5cdfdf00 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Wed, 21 Aug 2024 16:08:22 +0530 Subject: [PATCH 12/18] fix(payment_methods_v2): Fixed errors --- crates/api_models/src/payment_methods.rs | 44 +++++++++++++++++++ .../compatibility/stripe/customers/types.rs | 4 ++ .../router/src/core/payment_methods/cards.rs | 30 +++++++++++++ crates/router/src/core/payments/helpers.rs | 4 +- crates/router/src/core/payouts/helpers.rs | 2 +- crates/router/src/types/api/mandates.rs | 3 -- 6 files changed, 81 insertions(+), 6 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index c7bb6a68ebbb..6d3b74463a9b 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -490,6 +490,10 @@ pub struct MigrateCardDetail { pub card_type: Option, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct CardDetailUpdate { @@ -510,6 +514,10 @@ pub struct CardDetailUpdate { pub nick_name: Option>, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl CardDetailUpdate { pub fn apply(&self, card_data_from_locker: Card) -> CardDetail { CardDetail { @@ -538,6 +546,42 @@ impl CardDetailUpdate { } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct CardDetailUpdate { + /// Card Holder Name + #[schema(value_type = String,example = "John Doe")] + pub card_holder_name: Option>, + + /// Card Holder's Nick Name + #[schema(value_type = Option,example = "John Doe")] + pub nick_name: Option>, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl CardDetailUpdate { + pub fn apply(&self, card_data_from_locker: Card) -> CardDetail { + CardDetail { + card_number: card_data_from_locker.card_number, + card_exp_month: card_data_from_locker.card_exp_month, + card_exp_year: card_data_from_locker.card_exp_year, + card_holder_name: self + .card_holder_name + .clone() + .or(card_data_from_locker.name_on_card), + nick_name: self + .nick_name + .clone() + .or(card_data_from_locker.nick_name.map(masking::Secret::new)), + card_issuing_country: None, + card_network: None, + card_issuer: None, + card_type: None, + } + } +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index c30a02d92e50..22d9ba4cea57 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -209,6 +209,10 @@ pub struct CardDetails { pub fingerprint: Option>, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] impl From for CustomerPaymentMethodListResponse { fn from(item: api::CustomerPaymentMethodsListResponse) -> Self { let customer_payment_methods = item.customer_payment_methods; diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 7d3ded09979f..ba895f730ef1 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -274,6 +274,11 @@ pub fn store_default_payment_method( (payment_method_response, None) } + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[instrument(skip_all)] pub async fn get_or_insert_payment_method( state: &routes::SessionState, @@ -344,6 +349,19 @@ pub async fn get_or_insert_payment_method( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn get_or_insert_payment_method( + _state: &routes::SessionState, + _req: api::PaymentMethodCreate, + _resp: &mut api::PaymentMethodResponse, + _merchant_account: &domain::MerchantAccount, + _customer_id: &id_type::CustomerId, + _key_store: &domain::MerchantKeyStore, +) -> errors::RouterResult { + todo!() +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -1699,6 +1717,10 @@ pub async fn update_customer_payment_method( todo!() } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub fn validate_payment_method_update( card_updation_obj: CardDetailUpdate, existing_card_data: api::CardDetailFromLocker, @@ -1749,6 +1771,14 @@ pub fn validate_payment_method_update( }) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub fn validate_payment_method_update( + _card_updation_obj: CardDetailUpdate, + _existing_card_data: api::CardDetailFromLocker, +) -> bool { + todo!() +} + // Wrapper function to switch lockers #[cfg(feature = "payouts")] diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index d7f950bf3fbf..ff8d21e5fb09 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4217,8 +4217,8 @@ pub async fn populate_bin_details_for_payment_method_create( #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn populate_bin_details_for_payment_method_create( - card_details: api_models::payment_methods::CardDetail, - db: &dyn StorageInterface, + _card_details: api_models::payment_methods::CardDetail, + _db: &dyn StorageInterface, ) -> api_models::payment_methods::CardDetail { todo!() } diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index c666731fd732..ffc2803a63a8 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -612,7 +612,7 @@ pub async fn save_payout_data_to_locker( pub async fn save_payout_data_to_locker( _state: &SessionState, _payout_data: &mut PayoutData, - customer_id: &id_type::CustomerId, + _customer_id: &id_type::CustomerId, _payout_method_data: &api::PayoutMethodData, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs index 79e9f62f9c04..bf8f00526e99 100644 --- a/crates/router/src/types/api/mandates.rs +++ b/crates/router/src/types/api/mandates.rs @@ -1,6 +1,3 @@ -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use std::str::FromStr; - use api_models::mandates; pub use api_models::mandates::{MandateId, MandateResponse, MandateRevokedResponse}; use common_utils::ext_traits::OptionExt; From c5b29a2912819d56321f2bb50009bdee0fd8a449 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 22 Aug 2024 16:19:20 +0530 Subject: [PATCH 13/18] fix(payment_methods_v2): Resolved comments --- crates/api_models/src/payment_methods.rs | 32 +++- .../compatibility/stripe/customers/types.rs | 10 +- crates/router/src/core/locker_migration.rs | 35 +++- crates/router/src/core/payment_methods.rs | 171 +++++++++++++++++- .../router/src/core/payment_methods/cards.rs | 74 ++++---- .../src/core/payment_methods/transformers.rs | 63 +------ crates/router/src/core/payments/helpers.rs | 160 ---------------- .../router/src/core/payments/tokenization.rs | 137 ++------------ crates/router/src/types/transformers.rs | 2 +- 9 files changed, 304 insertions(+), 380 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 6d3b74463a9b..20d648318ffc 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -668,7 +668,7 @@ pub struct PaymentMethodResponse { /// The unique identifier of the customer. #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: Option, + pub customer_id: id_type::CustomerId, /// The unique identifier of the Payment method #[schema(example = "card_rGK4Vi5iSW70MY7J2mIg")] @@ -2028,6 +2028,10 @@ pub enum MigrationStatus { type PaymentMethodMigrationResponseType = (Result, PaymentMethodRecord); +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] impl From for PaymentMethodMigrationResponse { fn from((response, record): PaymentMethodMigrationResponseType) -> Self { match response { @@ -2053,6 +2057,32 @@ impl From for PaymentMethodMigrationResponse } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for PaymentMethodMigrationResponse { + fn from((response, record): PaymentMethodMigrationResponseType) -> Self { + match response { + Ok(res) => Self { + payment_method_id: Some(res.payment_method_id), + payment_method: res.payment_method, + payment_method_type: res.payment_method_type, + customer_id: Some(res.customer_id), + migration_status: MigrationStatus::Success, + migration_error: None, + card_number_masked: Some(record.card_number_masked), + line_number: record.line_number, + }, + Err(e) => Self { + customer_id: Some(record.customer_id), + migration_status: MigrationStatus::Failed, + migration_error: Some(e), + card_number_masked: Some(record.card_number_masked), + line_number: record.line_number, + ..Self::default() + }, + } + } +} + impl From for PaymentMethodMigrate { fn from(record: PaymentMethodRecord) -> Self { let mut mandate_reference = HashMap::new(); diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 22d9ba4cea57..736ff188da7d 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -1,6 +1,9 @@ -use std::{convert::From, default::Default}; - -use api_models::{payment_methods as api_types, payments}; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use api_models::payment_methods as api_types; +use api_models::payments; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use common_utils::{crypto::Encryptable, date_time}; use common_utils::{ @@ -9,6 +12,7 @@ use common_utils::{ types::Description, }; use serde::{Deserialize, Serialize}; +use std::{convert::From, default::Default}; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use crate::logger; diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs index 707304339e17..14a27c9ecff5 100644 --- a/crates/router/src/core/locker_migration.rs +++ b/crates/router/src/core/locker_migration.rs @@ -1,6 +1,16 @@ -use api_models::{enums as api_enums, locker_migration::MigrateCardResponse}; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use api_models::enums as api_enums; +use api_models::locker_migration::MigrateCardResponse; use common_utils::{errors::CustomResult, id_type}; -use diesel_models::{enums as storage_enums, PaymentMethod}; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use diesel_models::enums as storage_enums; +use diesel_models::PaymentMethod; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use error_stack::FutureExt; use error_stack::ResultExt; @@ -9,13 +19,22 @@ use futures::TryFutureExt; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use super::errors::StorageErrorExt; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] use super::payment_methods::cards; -use crate::{ - errors, - routes::SessionState, - services::{self, logger}, - types::{api, domain}, -}; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use crate::services::logger; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use crate::types::api; +use crate::{errors, routes::SessionState, services, types::domain}; #[cfg(all(feature = "v2", feature = "customer_v2"))] pub async fn rust_locker_migration( diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 14fad68c171d..18026535a188 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -9,7 +9,11 @@ pub mod vault; use std::borrow::Cow; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use std::collections::HashSet; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use std::str::FromStr; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub use api_models::enums as api_enums; pub use api_models::enums::Connector; use api_models::payment_methods; #[cfg(feature = "payouts")] @@ -38,7 +42,10 @@ use crate::{ }, routes::{app::StorageInterface, SessionState}, services, - types::{domain, storage}, + types::{ + domain, + storage::{self, enums as storage_enums}, + }, }; const PAYMENT_METHOD_STATUS_UPDATE_TASK: &str = "PAYMENT_METHOD_STATUS_UPDATE"; @@ -579,3 +586,165 @@ pub async fn retrieve_payment_method_with_token( }; Ok(token) } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub(crate) async fn get_payment_method_create_request( + payment_method_data: Option<&domain::PaymentMethodData>, + payment_method: Option, + payment_method_type: Option, + customer_id: &Option, + billing_name: Option>, +) -> RouterResult { + match payment_method_data { + Some(pm_data) => match payment_method { + Some(payment_method) => match pm_data { + domain::PaymentMethodData::Card(card) => { + let card_detail = payment_methods::CardDetail { + card_number: card.card_number.clone(), + card_exp_month: card.card_exp_month.clone(), + card_exp_year: card.card_exp_year.clone(), + card_holder_name: billing_name, + nick_name: card.nick_name.clone(), + card_issuing_country: card + .card_issuing_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten(), + card_network: card.card_network.clone(), + card_issuer: card.card_issuer.clone(), + card_type: card + .card_type + .as_ref() + .map(|c| payment_methods::CardType::from_str(c)) + .transpose() + .ok() + .flatten(), + }; + let payment_method_request = payment_methods::PaymentMethodCreate { + payment_method: Some(payment_method), + payment_method_type, + metadata: None, + customer_id: customer_id.clone(), + client_secret: None, + payment_method_data: Some(payment_methods::PaymentMethodCreateData::Card( + card_detail, + )), + billing: None, + }; + Ok(payment_method_request) + } + _ => { + let payment_method_request = payment_methods::PaymentMethodCreate { + payment_method: Some(payment_method), + payment_method_type, + metadata: None, + customer_id: customer_id.clone(), + client_secret: None, + payment_method_data: None, + billing: None, + }; + + Ok(payment_method_request) + } + }, + None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_type" + }) + .attach_printable("PaymentMethodType Required")), + }, + None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_data" + }) + .attach_printable("PaymentMethodData required Or Card is already saved")), + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[instrument(skip_all)] +pub(crate) async fn get_payment_method_create_request( + payment_method_data: Option<&domain::PaymentMethodData>, + payment_method: Option, + payment_method_type: Option, + customer_id: &Option, + billing_name: Option>, +) -> RouterResult { + match payment_method_data { + Some(pm_data) => match payment_method { + Some(payment_method) => match pm_data { + domain::PaymentMethodData::Card(card) => { + let card_detail = payment_methods::CardDetail { + card_number: card.card_number.clone(), + card_exp_month: card.card_exp_month.clone(), + card_exp_year: card.card_exp_year.clone(), + card_holder_name: billing_name, + 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.clone(), + }; + let payment_method_request = payment_methods::PaymentMethodCreate { + payment_method: Some(payment_method), + payment_method_type, + payment_method_issuer: card.card_issuer.clone(), + payment_method_issuer_code: None, + #[cfg(feature = "payouts")] + bank_transfer: None, + #[cfg(feature = "payouts")] + wallet: None, + card: Some(card_detail), + metadata: None, + customer_id: customer_id.clone(), + card_network: card + .card_network + .as_ref() + .map(|card_network| card_network.to_string()), + client_secret: None, + payment_method_data: None, + billing: None, + connector_mandate_details: None, + network_transaction_id: None, + }; + Ok(payment_method_request) + } + _ => { + let payment_method_request = payment_methods::PaymentMethodCreate { + payment_method: Some(payment_method), + payment_method_type, + payment_method_issuer: None, + payment_method_issuer_code: None, + #[cfg(feature = "payouts")] + bank_transfer: None, + #[cfg(feature = "payouts")] + wallet: None, + card: None, + metadata: None, + customer_id: customer_id.clone(), + card_network: None, + client_secret: None, + payment_method_data: None, + billing: None, + connector_mandate_details: None, + network_transaction_id: None, + }; + + Ok(payment_method_request) + } + }, + None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_type" + }) + .attach_printable("PaymentMethodType Required")), + }, + None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_data" + }) + .attach_printable("PaymentMethodData required Or Card is already saved")), + } +} diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index ba895f730ef1..7bd41c1178b9 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4,19 +4,27 @@ use std::{ str::FromStr, }; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] use api_models::{ admin::PaymentMethodsEnabled, + payment_methods::{ + CardNetworkTypes, PaymentExperienceTypes, RequiredFieldInfo, ResponsePaymentMethodTypes, + }, + pm_auth::PaymentMethodAuthConfig, +}; +use api_models::{ enums as api_enums, payment_methods::{ - BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CardNetworkTypes, + BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CountryCodeWithName, CustomerDefaultPaymentMethodResponse, ListCountriesCurrenciesRequest, - ListCountriesCurrenciesResponse, MaskedBankDetails, PaymentExperienceTypes, - PaymentMethodsData, RequestPaymentMethodTypes, RequiredFieldInfo, - ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes, + ListCountriesCurrenciesResponse, MaskedBankDetails, PaymentMethodsData, + RequestPaymentMethodTypes, ResponsePaymentMethodIntermediate, ResponsePaymentMethodsEnabled, }, payments::BankCodeResponse, - pm_auth::PaymentMethodAuthConfig, surcharge_decision_configs as api_surcharge_decision_configs, }; use common_enums::{enums::MerchantStorageScheme, ConnectorType}; @@ -33,13 +41,19 @@ use common_utils::{ }; use diesel_models::payment_method; use error_stack::{report, ResultExt}; -use euclid::{ - dssa::graph::{AnalysisContext, CgraphExt}, - frontend::dir, -}; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use euclid::dssa::graph::{AnalysisContext, CgraphExt}; +use euclid::frontend::dir; use hyperswitch_constraint_graph as cgraph; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::customer::CustomerUpdate; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] use kgraph_utils::transformers::IntoDirValue; use masking::Secret; use router_env::{instrument, metrics::add_attributes, tracing}; @@ -49,6 +63,15 @@ use super::surcharge_decision_configs::{ perform_surcharge_decision_management_for_payment_method_list, perform_surcharge_decision_management_for_saved_cards, }; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use crate::payment_methods::{ + add_payment_method_status_update_task, + types::transformers::ForeignFrom, + utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache}, +}; #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2"), @@ -63,11 +86,7 @@ use crate::{ configs::settings, core::{ errors::{self, StorageErrorExt}, - payment_methods::{ - add_payment_method_status_update_task, transformers as payment_methods, - utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache}, - vault, - }, + payment_methods::{transformers as payment_methods, vault}, payments::{ helpers, routing::{self, SessionFlowRoutingInput}, @@ -82,7 +101,7 @@ use crate::{ api::{self, routing as routing_types, PaymentMethodCreateExt}, domain::{self, BusinessProfile}, storage::{self, enums, PaymentMethodListContext, PaymentTokenData}, - transformers::{ForeignFrom, ForeignTryFrom}, + transformers::ForeignTryFrom, }, utils::{ConnectorResponseExt, OptionExt}, }; @@ -250,29 +269,14 @@ pub fn store_default_payment_method( #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub fn store_default_payment_method( - req: &api::PaymentMethodCreate, - customer_id: &id_type::CustomerId, - merchant_id: &id_type::MerchantId, + _req: &api::PaymentMethodCreate, + _customer_id: &id_type::CustomerId, + _merchant_id: &id_type::MerchantId, ) -> ( api::PaymentMethodResponse, Option, ) { - let pm_id = generate_id(consts::ID_LENGTH, "pm"); - let payment_method_response = api::PaymentMethodResponse { - merchant_id: merchant_id.to_owned(), - customer_id: Some(customer_id.to_owned()), - payment_method_id: pm_id, - payment_method: req.payment_method, - payment_method_type: req.payment_method_type, - payment_method_data: None, - metadata: req.metadata.clone(), - created: Some(common_utils::date_time::now()), - recurring_enabled: false, //[#219] - last_used_at: Some(common_utils::date_time::now()), - client_secret: None, - }; - - (payment_method_response, None) + todo!() } #[cfg(all( @@ -5602,7 +5606,7 @@ pub async fn retrieve_payment_method( Ok(services::ApplicationResponse::Json( api::PaymentMethodResponse { merchant_id: pm.merchant_id, - customer_id: Some(pm.customer_id), + customer_id: pm.customer_id, payment_method_id: pm.payment_method_id, payment_method: pm.payment_method, payment_method_type: pm.payment_method_type, diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index d913b6ea53c4..ed4f2d9d15fa 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -346,24 +346,12 @@ pub fn mk_add_bank_response_hs( #[cfg(all(feature = "v2", feature = "payment_methods_v2", feature = "payouts"))] pub fn mk_add_bank_response_hs( - bank: api::BankPayout, - bank_reference: String, - req: api::PaymentMethodCreate, - merchant_id: &id_type::MerchantId, + _bank: api::BankPayout, + _bank_reference: String, + _req: api::PaymentMethodCreate, + _merchant_id: &id_type::MerchantId, ) -> api::PaymentMethodResponse { - api::PaymentMethodResponse { - merchant_id: merchant_id.to_owned(), - customer_id: req.customer_id, - payment_method_id: bank_reference, - payment_method: req.payment_method, - payment_method_type: req.payment_method_type, - payment_method_data: Some(api::PaymentMethodResponseData::Bank(bank)), - metadata: req.metadata, - created: Some(common_utils::date_time::now()), - recurring_enabled: false, // [#256] - last_used_at: Some(common_utils::date_time::now()), - client_secret: None, - } + todo!() } #[cfg(all( @@ -421,43 +409,12 @@ pub fn mk_add_card_response_hs( #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub fn mk_add_card_response_hs( - card: api::CardDetail, - card_reference: String, - req: api::PaymentMethodCreate, - merchant_id: &id_type::MerchantId, + _card: api::CardDetail, + _card_reference: String, + _req: api::PaymentMethodCreate, + _merchant_id: &id_type::MerchantId, ) -> api::PaymentMethodResponse { - let card_number = card.card_number.clone(); - let last4_digits = card_number.get_last4(); - let card_isin = card_number.get_card_isin(); - - let card = api::CardDetailFromLocker { - last4_digits: Some(last4_digits), - issuer_country: card.card_issuing_country, - card_number: Some(card.card_number.clone()), - expiry_month: Some(card.card_exp_month.clone()), - expiry_year: Some(card.card_exp_year.clone()), - card_fingerprint: None, - card_holder_name: card.card_holder_name.clone(), - nick_name: card.nick_name.clone(), - card_isin: Some(card_isin), - card_issuer: card.card_issuer, - card_network: card.card_network, - card_type: card.card_type.map(|card| card.to_string()), - saved_to_locker: true, - }; - api::PaymentMethodResponse { - merchant_id: merchant_id.to_owned(), - customer_id: req.customer_id, - payment_method_id: card_reference, - payment_method: req.payment_method, - payment_method_type: req.payment_method_type, - payment_method_data: Some(api::PaymentMethodResponseData::Card(card)), - metadata: req.metadata, - created: Some(common_utils::date_time::now()), - recurring_enabled: false, // [#256] - last_used_at: Some(common_utils::date_time::now()), // [#256] - client_secret: req.client_secret, - } + todo!() } pub async fn mk_get_card_request_hs( diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index ff8d21e5fb09..afdb6a82f62a 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1351,166 +1351,6 @@ where Box::new(PaymentResponse) } -#[cfg(all( - any(feature = "v1", feature = "v2"), - not(feature = "payment_methods_v2") -))] -#[instrument(skip_all)] -pub(crate) async fn get_payment_method_create_request( - payment_method_data: Option<&domain::PaymentMethodData>, - payment_method: Option, - payment_method_type: Option, - customer_id: &Option, - billing_name: Option>, -) -> RouterResult { - match payment_method_data { - Some(pm_data) => match payment_method { - Some(payment_method) => match pm_data { - domain::PaymentMethodData::Card(card) => { - let card_detail = api::CardDetail { - card_number: card.card_number.clone(), - card_exp_month: card.card_exp_month.clone(), - card_exp_year: card.card_exp_year.clone(), - card_holder_name: billing_name, - 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.clone(), - }; - let payment_method_request = api::PaymentMethodCreate { - payment_method: Some(payment_method), - payment_method_type, - payment_method_issuer: card.card_issuer.clone(), - payment_method_issuer_code: None, - #[cfg(feature = "payouts")] - bank_transfer: None, - #[cfg(feature = "payouts")] - wallet: None, - card: Some(card_detail), - metadata: None, - customer_id: customer_id.clone(), - card_network: card - .card_network - .as_ref() - .map(|card_network| card_network.to_string()), - client_secret: None, - payment_method_data: None, - billing: None, - connector_mandate_details: None, - network_transaction_id: None, - }; - Ok(payment_method_request) - } - _ => { - let payment_method_request = api::PaymentMethodCreate { - payment_method: Some(payment_method), - payment_method_type, - payment_method_issuer: None, - payment_method_issuer_code: None, - #[cfg(feature = "payouts")] - bank_transfer: None, - #[cfg(feature = "payouts")] - wallet: None, - card: None, - metadata: None, - customer_id: customer_id.clone(), - card_network: None, - client_secret: None, - payment_method_data: None, - billing: None, - connector_mandate_details: None, - network_transaction_id: None, - }; - - Ok(payment_method_request) - } - }, - None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { - field_name: "payment_method_type" - }) - .attach_printable("PaymentMethodType Required")), - }, - None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { - field_name: "payment_method_data" - }) - .attach_printable("PaymentMethodData required Or Card is already saved")), - } -} - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub(crate) async fn get_payment_method_create_request( - payment_method_data: Option<&domain::PaymentMethodData>, - payment_method: Option, - payment_method_type: Option, - customer_id: &Option, - billing_name: Option>, -) -> RouterResult { - match payment_method_data { - Some(pm_data) => match payment_method { - Some(payment_method) => match pm_data { - domain::PaymentMethodData::Card(card) => { - let card_detail = api::CardDetail { - card_number: card.card_number.clone(), - card_exp_month: card.card_exp_month.clone(), - card_exp_year: card.card_exp_year.clone(), - card_holder_name: billing_name, - nick_name: card.nick_name.clone(), - card_issuing_country: card - .card_issuing_country - .as_ref() - .map(|c| api_enums::CountryAlpha2::from_str(c)) - .transpose() - .ok() - .flatten(), - card_network: card.card_network.clone(), - card_issuer: card.card_issuer.clone(), - card_type: card - .card_type - .as_ref() - .map(|c| api::CardType::from_str(c)) - .transpose() - .ok() - .flatten(), - }; - let payment_method_request = api::PaymentMethodCreate { - payment_method: Some(payment_method), - payment_method_type, - metadata: None, - customer_id: customer_id.clone(), - client_secret: None, - payment_method_data: Some(api::PaymentMethodCreateData::Card(card_detail)), - billing: None, - }; - Ok(payment_method_request) - } - _ => { - let payment_method_request = api::PaymentMethodCreate { - payment_method: Some(payment_method), - payment_method_type, - metadata: None, - customer_id: customer_id.clone(), - client_secret: None, - payment_method_data: None, - billing: None, - }; - - Ok(payment_method_request) - } - }, - None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { - field_name: "payment_method_type" - }) - .attach_printable("PaymentMethodType Required")), - }, - None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { - field_name: "payment_method_data" - }) - .attach_printable("PaymentMethodData required Or Card is already saved")), - } -} - pub async fn get_customer_from_details( state: &SessionState, customer_id: Option, diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 5cf9cf1e2ff9..f26ed588a010 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -1,5 +1,9 @@ use std::collections::HashMap; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] use api_models::payment_methods::PaymentMethodsData; use common_enums::PaymentMethod; use common_utils::{ @@ -184,14 +188,15 @@ where .attach_printable("Unable to serialize customer acceptance to value")?; let pm_id = if customer_acceptance.is_some() { - let payment_method_create_request = helpers::get_payment_method_create_request( - Some(&save_payment_method_data.request.get_payment_method_data()), - Some(save_payment_method_data.payment_method), - payment_method_type, - &customer_id.clone(), - billing_name, - ) - .await?; + let payment_method_create_request = + payment_methods::get_payment_method_create_request( + Some(&save_payment_method_data.request.get_payment_method_data()), + Some(save_payment_method_data.payment_method), + payment_method_type, + &customer_id.clone(), + billing_name, + ) + .await?; let customer_id = customer_id.to_owned().get_required_value("customer_id")?; let merchant_id = merchant_account.get_id(); let (mut resp, duplication_check) = if !state.conf.locker.locker_enabled { @@ -662,7 +667,7 @@ pub async fn save_payment_method( _currency: Option, _billing_name: Option>, _payment_method_billing_address: Option<&api::Address>, - _business_profile: &storage::business_profile::BusinessProfile, + _business_profile: &domain::BusinessProfile, ) -> RouterResult<(Option, Option)> where FData: mandate::MandateBehaviour + Clone, @@ -770,74 +775,7 @@ async fn skip_saving_card_in_locker( api_models::payment_methods::PaymentMethodResponse, Option, )> { - let merchant_id = merchant_account.get_id(); - let customer_id = payment_method_request - .clone() - .customer_id - .clone() - .get_required_value("customer_id")?; - let payment_method_id = common_utils::generate_id(consts::ID_LENGTH, "pm"); - - let req_card = match payment_method_request.payment_method_data { - Some(api::PaymentMethodCreateData::Card(card)) => Some(card.clone()), - _ => None, - }; - - let last4_digits = req_card.as_ref().map(|c| c.card_number.get_last4()); - - let card_isin = req_card.as_ref().map(|c| c.card_number.get_card_isin()); - - match req_card { - Some(card) => { - let card_detail = CardDetailFromLocker { - issuer_country: card.card_issuing_country.clone(), - last4_digits: last4_digits.clone(), - card_number: None, - expiry_month: Some(card.card_exp_month.clone()), - expiry_year: Some(card.card_exp_year), - card_holder_name: card.card_holder_name.clone(), - card_fingerprint: None, - nick_name: None, - card_isin: card_isin.clone(), - card_issuer: card.card_issuer.clone(), - card_network: card.card_network.clone(), - card_type: card.card_type.map(|card| card.to_string()), - saved_to_locker: false, - }; - let pm_resp = api::PaymentMethodResponse { - merchant_id: merchant_id.to_owned(), - customer_id: Some(customer_id), - payment_method_id, - payment_method: payment_method_request.payment_method, - payment_method_type: payment_method_request.payment_method_type, - payment_method_data: Some(api::PaymentMethodResponseData::Card(card_detail)), - recurring_enabled: false, - metadata: None, - created: Some(common_utils::date_time::now()), - last_used_at: Some(common_utils::date_time::now()), - client_secret: None, - }; - - Ok((pm_resp, None)) - } - None => { - let pm_id = common_utils::generate_id(consts::ID_LENGTH, "pm"); - let payment_method_response = api::PaymentMethodResponse { - merchant_id: merchant_id.to_owned(), - customer_id: Some(customer_id), - payment_method_id: pm_id, - payment_method: payment_method_request.payment_method, - payment_method_type: payment_method_request.payment_method_type, - metadata: None, - created: Some(common_utils::date_time::now()), - recurring_enabled: false, - last_used_at: Some(common_utils::date_time::now()), - client_secret: None, - payment_method_data: None, - }; - Ok((payment_method_response, None)) - } - } + todo!() } #[cfg(all( @@ -896,51 +834,14 @@ pub async fn save_in_locker( #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn save_in_locker( - state: &SessionState, - merchant_account: &domain::MerchantAccount, - payment_method_request: api::PaymentMethodCreate, + _state: &SessionState, + _merchant_account: &domain::MerchantAccount, + _payment_method_request: api::PaymentMethodCreate, ) -> RouterResult<( api_models::payment_methods::PaymentMethodResponse, Option, )> { - payment_method_request.validate()?; - let merchant_id = merchant_account.get_id(); - let customer_id = payment_method_request - .customer_id - .clone() - .get_required_value("customer_id")?; - match payment_method_request.payment_method_data.clone() { - Some(api::PaymentMethodCreateData::Card(card)) => { - Box::pin(payment_methods::cards::add_card_to_locker( - state, - payment_method_request, - &card, - &customer_id, - merchant_account, - None, - )) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Add Card Failed") - } - _ => { - let pm_id = common_utils::generate_id(consts::ID_LENGTH, "pm"); - let payment_method_response = api::PaymentMethodResponse { - merchant_id: merchant_id.clone(), - customer_id: Some(customer_id), - payment_method_id: pm_id, - payment_method: payment_method_request.payment_method, - payment_method_type: payment_method_request.payment_method_type, - metadata: None, - created: Some(common_utils::date_time::now()), - recurring_enabled: false, //[#219] - last_used_at: Some(common_utils::date_time::now()), - client_secret: None, - payment_method_data: None, - }; - Ok((payment_method_response, None)) - } - } + todo!() } pub fn create_payment_method_metadata( diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 46684a7e3ae5..2cc95281f030 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -132,7 +132,7 @@ impl ) -> Self { Self { merchant_id: item.merchant_id, - customer_id: Some(item.customer_id), + customer_id: item.customer_id, payment_method_id: item.payment_method_id, payment_method: item.payment_method, payment_method_type: item.payment_method_type, From aab6e8c8060e7ce1a65b42f279b38c0060a51930 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:51:40 +0000 Subject: [PATCH 14/18] chore: run formatter --- crates/router/src/compatibility/stripe/customers/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 736ff188da7d..2b62d8f5dacb 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -1,3 +1,5 @@ +use std::{convert::From, default::Default}; + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -12,7 +14,6 @@ use common_utils::{ types::Description, }; use serde::{Deserialize, Serialize}; -use std::{convert::From, default::Default}; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use crate::logger; From 835bf153ebeb31c45568cee584f8be5cd8b14c58 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 22 Aug 2024 17:12:40 +0530 Subject: [PATCH 15/18] fix(payment_methods_v2): Fixed errors --- .../router/src/core/payment_methods/cards.rs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 7bd41c1178b9..77df20dde5cf 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -63,15 +63,6 @@ use super::surcharge_decision_configs::{ perform_surcharge_decision_management_for_payment_method_list, perform_surcharge_decision_management_for_saved_cards, }; -#[cfg(all( - any(feature = "v1", feature = "v2"), - not(feature = "payment_methods_v2") -))] -use crate::payment_methods::{ - add_payment_method_status_update_task, - types::transformers::ForeignFrom, - utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache}, -}; #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2"), @@ -105,6 +96,17 @@ use crate::{ }, utils::{ConnectorResponseExt, OptionExt}, }; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use crate::{ + core::payment_methods::{ + add_payment_method_status_update_task, + utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache}, + }, + types::transformers::ForeignFrom, +}; #[cfg(all( any(feature = "v1", feature = "v2"), From 5009e57572d3670dbeb011aff7b2ee1b173271a6 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 22 Aug 2024 18:36:05 +0530 Subject: [PATCH 16/18] fix(payment_methods_v2): Resolved comments --- crates/api_models/src/payment_methods.rs | 34 +++++----------- crates/router/src/core/payment_methods.rs | 39 ++++++++++--------- crates/router/src/services/authentication.rs | 23 +++++++++-- .../router/src/types/api/payment_methods.rs | 23 ++++++++++- 4 files changed, 71 insertions(+), 48 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 20d648318ffc..de94d4f5872b 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -108,28 +108,22 @@ pub struct PaymentMethodCreate { pub struct PaymentMethodCreate { /// The type of payment method use for the payment. #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method: Option, + pub payment_method: api_enums::PaymentMethod, /// This is a sub-category of payment method. - #[schema(value_type = Option,example = "credit")] - pub payment_method_type: Option, + #[schema(value_type = PaymentMethodType,example = "credit")] + pub payment_method_type: api_enums::PaymentMethodType, /// 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. #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, /// The unique identifier of the customer. - #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: Option, - - /// For Client based calls, SDK will use the client_secret - /// in order to call /payment_methods - /// Client secret will be generated whenever a new - /// payment method is created - pub client_secret: Option, + #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + pub customer_id: id_type::CustomerId, /// Payment method data to be passed - pub payment_method_data: Option, + pub payment_method_data: PaymentMethodCreateData, /// The billing details of the payment method #[schema(value_type = Option
)] @@ -158,14 +152,14 @@ pub struct PaymentMethodIntentCreate { #[serde(deny_unknown_fields)] pub struct PaymentMethodIntentConfirm { /// For SDK based calls, client_secret would be required - pub client_secret: Option, + pub client_secret: String, /// The unique identifier of the customer. #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] pub customer_id: Option, /// Payment method data to be passed - pub payment_method_data: Option, + pub payment_method_data: PaymentMethodCreateData, } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] @@ -282,17 +276,9 @@ impl PaymentMethodCreate { impl PaymentMethodCreate { pub fn get_payment_method_create_from_payment_method_migrate( _card_number: CardNumber, - payment_method_migrate: &PaymentMethodMigrate, + _payment_method_migrate: &PaymentMethodMigrate, ) -> Self { - Self { - customer_id: payment_method_migrate.customer_id.clone(), - payment_method: payment_method_migrate.payment_method, - payment_method_type: payment_method_migrate.payment_method_type, - metadata: payment_method_migrate.metadata.clone(), - payment_method_data: payment_method_migrate.payment_method_data.clone(), - client_secret: None, - billing: payment_method_migrate.billing.clone(), - } + todo!() } } diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 18026535a188..cadeee51dc4e 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -20,6 +20,8 @@ use api_models::payment_methods; pub use api_models::{enums::PayoutConnectors, payouts as payout_types}; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use common_utils::ext_traits::Encode; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use common_utils::ext_traits::OptionExt; use common_utils::{consts::DEFAULT_LOCALE, id_type::CustomerId}; use diesel_models::{ enums, GenericLinkNew, PaymentMethodCollectLink, PaymentMethodCollectLinkData, @@ -624,31 +626,30 @@ pub(crate) async fn get_payment_method_create_request( .flatten(), }; let payment_method_request = payment_methods::PaymentMethodCreate { - payment_method: Some(payment_method), - payment_method_type, + payment_method: payment_method, + payment_method_type: payment_method_type + .get_required_value("Payment_method_type") + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_data", + })?, metadata: None, - customer_id: customer_id.clone(), - client_secret: None, - payment_method_data: Some(payment_methods::PaymentMethodCreateData::Card( + customer_id: customer_id + .clone() + .get_required_value("customer_id") + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "customer_id", + })?, + payment_method_data: payment_methods::PaymentMethodCreateData::Card( card_detail, - )), - billing: None, - }; - Ok(payment_method_request) - } - _ => { - let payment_method_request = payment_methods::PaymentMethodCreate { - payment_method: Some(payment_method), - payment_method_type, - metadata: None, - customer_id: customer_id.clone(), - client_secret: None, - payment_method_data: None, + ), billing: None, }; - Ok(payment_method_request) } + _ => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method_data" + }) + .attach_printable("Payment method data is incorrect")), }, None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { field_name: "payment_method_type" diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 30857f797fbb..3d40ef76af44 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -1,10 +1,14 @@ use actix_web::http::header::HeaderMap; +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] +use api_models::payment_methods::PaymentMethodCreate; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use api_models::payment_methods::PaymentMethodIntentConfirm; #[cfg(feature = "payouts")] use api_models::payouts; -use api_models::{ - payment_methods::{PaymentMethodCreate, PaymentMethodListRequest}, - payments, -}; +use api_models::{payment_methods::PaymentMethodListRequest, payments}; use async_trait::async_trait; use common_enums::TokenPurpose; use common_utils::{date_time, id_type}; @@ -1286,12 +1290,23 @@ impl ClientSecretFetch for PaymentMethodListRequest { } } +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] impl ClientSecretFetch for PaymentMethodCreate { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl ClientSecretFetch for PaymentMethodIntentConfirm { + fn get_client_secret(&self) -> Option<&String> { + Some(&self.client_secret) + } +} + impl ClientSecretFetch for api_models::cards_info::CardsInfoRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 8f5605dc10c9..f4d1492fb449 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -4,7 +4,8 @@ pub use api_models::payment_methods::{ CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, - PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, + PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, + PaymentMethodIntentConfirm, PaymentMethodIntentCreate, PaymentMethodList, PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate, PaymentMethodResponse, PaymentMethodResponseData, PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, @@ -37,6 +38,10 @@ pub(crate) trait PaymentMethodCreateExt { } // convert self.payment_method_type to payment_method and compare it against self.payment_method +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] impl PaymentMethodCreateExt for PaymentMethodCreate { fn validate(&self) -> RouterResult<()> { if let Some(pm) = self.payment_method { @@ -52,3 +57,19 @@ impl PaymentMethodCreateExt for PaymentMethodCreate { Ok(()) } } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl PaymentMethodCreateExt for PaymentMethodCreate { + fn validate(&self) -> RouterResult<()> { + if !validate_payment_method_type_against_payment_method( + self.payment_method, + self.payment_method_type, + ) { + return Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid 'payment_method_type' provided".to_string() + }) + .attach_printable("Invalid payment method type")); + } + Ok(()) + } +} From 0dd4f3dca20455a4c42cde464459c340881e1a28 Mon Sep 17 00:00:00 2001 From: Sarthak Soni Date: Thu, 22 Aug 2024 19:38:39 +0530 Subject: [PATCH 17/18] fix(payment_methods_v2): Fixed errors --- crates/router/src/core/payment_methods/cards.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 77df20dde5cf..46e194903323 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -8,23 +8,19 @@ use std::{ any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") ))] -use api_models::{ - admin::PaymentMethodsEnabled, - payment_methods::{ - CardNetworkTypes, PaymentExperienceTypes, RequiredFieldInfo, ResponsePaymentMethodTypes, - }, - pm_auth::PaymentMethodAuthConfig, -}; +use api_models::admin::PaymentMethodsEnabled; use api_models::{ enums as api_enums, payment_methods::{ - BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, + BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CardNetworkTypes, CountryCodeWithName, CustomerDefaultPaymentMethodResponse, ListCountriesCurrenciesRequest, - ListCountriesCurrenciesResponse, MaskedBankDetails, PaymentMethodsData, - RequestPaymentMethodTypes, ResponsePaymentMethodIntermediate, + ListCountriesCurrenciesResponse, MaskedBankDetails, PaymentExperienceTypes, + PaymentMethodsData, RequestPaymentMethodTypes, RequiredFieldInfo, + ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes, ResponsePaymentMethodsEnabled, }, payments::BankCodeResponse, + pm_auth::PaymentMethodAuthConfig, surcharge_decision_configs as api_surcharge_decision_configs, }; use common_enums::{enums::MerchantStorageScheme, ConnectorType}; From be4bee64c9b4a402b066f2b5419680e2640a8c0e Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:26:33 +0000 Subject: [PATCH 18/18] docs(openapi): re-generate OpenAPI specification --- api-reference-v2/openapi_spec.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 1dcc79c255d2..d2b533a0d1d0 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -10278,17 +10278,6 @@ } } }, - { - "type": "object", - "required": [ - "paypal" - ], - "properties": { - "paypal": { - "type": "object" - } - } - }, { "type": "object", "required": [