diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 757beea4b652..0505dd498e3f 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -10283,17 +10283,6 @@ } } }, - { - "type": "object", - "required": [ - "paypal" - ], - "properties": { - "paypal": { - "type": "object" - } - } - }, { "type": "object", "required": [ diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index c90031dc0772..de94d4f5872b 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -1,4 +1,6 @@ use std::collections::{HashMap, HashSet}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use std::str::FromStr; use cards::CardNumber; use common_utils::{ @@ -20,6 +22,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 +102,66 @@ 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: api_enums::PaymentMethod, + + /// This is a sub-category of payment method. + #[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 = 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: PaymentMethodCreateData, + + /// The billing details of the payment method + #[schema(value_type = Option
)] + pub billing: 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: 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: PaymentMethodCreateData, +} + #[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 +225,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 +272,20 @@ 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 { + todo!() + } +} + +#[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 +302,58 @@ 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), +} + +#[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 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)] +#[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") +))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct CardDetail { @@ -264,6 +391,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 { @@ -301,6 +476,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 { @@ -321,6 +500,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 { @@ -349,6 +532,58 @@ 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)] +#[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 +645,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: id_type::CustomerId, + + /// 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), @@ -469,12 +750,53 @@ pub struct Card { pub card_exp_year: masking::Secret, pub card_brand: Option, pub card_isin: Option, - pub nick_name: 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, + 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_token: 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, } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct CardDetailFromLocker { - pub scheme: Option, - pub issuer_country: Option, + pub issuer_country: Option, pub last4_digits: Option, #[serde(skip)] #[schema(value_type=Option)] @@ -486,9 +808,6 @@ pub struct CardDetailFromLocker { #[schema(value_type=Option)] pub expiry_year: Option>, - #[schema(value_type=Option)] - pub card_token: Option>, - #[schema(value_type=Option)] pub card_holder_name: Option>, @@ -511,6 +830,10 @@ 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 +856,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 +905,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 +954,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 +1183,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 +1224,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 +1309,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 +1532,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 +1546,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 +1592,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 +1625,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
)] @@ -1501,6 +2014,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 { @@ -1526,6 +2043,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.rs b/crates/router/src/compatibility/stripe/customers.rs index 330610a9a36e..7cecf49496b3 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, @@ -145,7 +157,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, @@ -179,7 +195,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 9f5c546a18ed..2b62d8f5dacb 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -1,6 +1,11 @@ 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::{ @@ -209,6 +214,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; @@ -223,27 +232,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 +248,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..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( @@ -89,6 +108,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 +208,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.rs b/crates/router/src/core/payment_methods.rs index 14fad68c171d..cadeee51dc4e 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -9,13 +9,19 @@ 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")] 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, @@ -38,7 +44,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 +588,164 @@ 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: 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() + .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) + } + _ => 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" + }) + .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 f305a0100aa7..274ef3c0966f 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -4,8 +4,12 @@ use std::{ str::FromStr, }; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use api_models::admin::PaymentMethodsEnabled; use api_models::{ - admin::PaymentMethodsEnabled, enums as api_enums, payment_methods::{ BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CardNetworkTypes, @@ -33,13 +37,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}; @@ -66,11 +76,7 @@ use crate::{ }, 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}, @@ -85,36 +91,27 @@ 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}, }; +#[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(feature = "v2", feature = "customer_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 { - todo!() -} - -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2"), + not(feature = "customer_v2") +))] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn create_payment_method( @@ -209,6 +206,38 @@ pub async fn create_payment_method( Ok(response) } +#[cfg(all( + feature = "v2", + feature = "payment_methods_v2", + feature = "customer_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 { + todo!() +} + +#[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, @@ -238,6 +267,23 @@ 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, +) { + todo!() +} + +#[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, @@ -308,6 +354,23 @@ 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") +))] pub async fn migrate_payment_method( state: routes::SessionState, req: api::PaymentMethodMigrate, @@ -373,6 +436,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, @@ -409,6 +483,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, @@ -483,19 +561,91 @@ impl } } -#[cfg(all(feature = "v2", feature = "customer_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 { - todo!() +#[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 = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2"), + not(feature = "customer_v2") +))] pub async fn skip_locker_call_and_migrate_payment_method( state: routes::SessionState, req: &api::PaymentMethodMigrate, @@ -613,6 +763,23 @@ 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", + feature = "customer_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 { + todo!() +} + pub fn get_card_bin_and_last4_digits_for_masked_card( masked_card_number: &str, ) -> Result<(String, String), cards::CardNumberValidationErr> { @@ -633,6 +800,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, @@ -710,6 +881,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, @@ -738,19 +920,11 @@ pub fn authenticate_pm_client_secret_and_check_expiry( } } -#[cfg(all(feature = "v2", feature = "customer_v2"))] -#[instrument(skip_all)] -pub async fn add_payment_method_data( - _state: routes::SessionState, - _req: api::PaymentMethodCreate, - _merchant_account: domain::MerchantAccount, - _key_store: domain::MerchantKeyStore, - _pm_id: String, -) -> errors::RouterResponse { - todo!() -} - -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2"), + not(feature = "customer_v2") +))] #[instrument(skip_all)] pub async fn add_payment_method_data( state: routes::SessionState, @@ -940,6 +1114,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, @@ -1187,6 +1365,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, @@ -1240,6 +1433,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, @@ -1460,6 +1710,22 @@ 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!() +} + +#[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, @@ -1510,6 +1776,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")] @@ -3497,6 +3771,10 @@ 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, @@ -3685,6 +3963,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, @@ -4585,20 +4882,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, }; @@ -4688,6 +4981,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, @@ -4726,6 +5023,48 @@ 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 = domain::types::crypto_operation::( + &state.into(), + 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() + .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, @@ -4764,6 +5103,43 @@ 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 = domain::types::crypto_operation::( + &state.into(), + 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() + .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, @@ -5160,6 +5536,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, @@ -5215,6 +5595,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: 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)] #[cfg(all(feature = "v2", feature = "customer_v2"))] pub async fn delete_payment_method( @@ -5306,6 +5742,17 @@ 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 { + todo!() +} + 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..477f025c7850 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -403,20 +403,12 @@ 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()?, + let card_network = match customer_payment_method.payment_method_data.as_ref() { + Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => { + card.card_network.clone() + } _ => None, }; - backend_input.payment_method.card_network = card_network; let surcharge_details = surcharge_source diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 9ca2bffc88c2..ed4f2d9d15fa 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -1,3 +1,6 @@ +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use std::str::FromStr; + use api_models::{enums as api_enums, payment_methods::Card}; use common_utils::{ ext_traits::{Encode, StringExt}, @@ -312,7 +315,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 +344,20 @@ 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 { + todo!() +} + +#[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 +407,16 @@ 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 { + todo!() +} + pub async fn mk_get_card_request_hs( jwekey: &settings::Jwekey, locker: &settings::Locker, @@ -502,6 +533,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 +565,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 3715adefed49..a1b65fa2682d 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1354,89 +1354,6 @@ where Box::new(PaymentResponse) } -#[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 = "customer_v2"))] pub async fn get_customer_from_details( _state: &SessionState, @@ -4081,6 +3998,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, @@ -4138,6 +4059,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 e9ebeafafd9b..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::{ @@ -54,6 +58,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( @@ -180,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 { @@ -641,6 +650,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: &domain::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, @@ -729,6 +767,21 @@ 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, +)> { + todo!() +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn save_in_locker( state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -779,6 +832,18 @@ 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, +)> { + todo!() +} + 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 4f9e3f9529e8..2cfeb9691d08 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -193,6 +193,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, @@ -605,6 +609,18 @@ 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, + _customer_id: &id_type::CustomerId, + _payout_method_data: &api::PayoutMethodData, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, +) -> RouterResult<()> { + todo!() +} + #[cfg(all(feature = "v2", feature = "customer_v2"))] pub(super) 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 36f709572070..b3c7f5cc59b6 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, @@ -712,14 +716,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/services/authentication.rs b/crates/router/src/services/authentication.rs index 4ac2c02c94c4..9ab2e2e3b076 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}; @@ -1366,12 +1370,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/mandates.rs b/crates/router/src/types/api/mandates.rs index 686f7f3ce495..bf8f00526e99 100644 --- a/crates/router/src/types/api/mandates.rs +++ b/crates/router/src/types/api/mandates.rs @@ -108,6 +108,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 +132,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..f4d1492fb449 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -1,14 +1,15 @@ #[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, + PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, + PaymentMethodIntentConfirm, PaymentMethodIntentCreate, 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"), @@ -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(()) + } +} diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 67af63b154e0..2cc95281f030 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: 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 { diff --git a/scripts/ci-checks-v2.sh b/scripts/ci-checks-v2.sh index bebfce27e73a..151e5a382d06 100755 --- a/scripts/ci-checks-v2.sh +++ b/scripts/ci-checks-v2.sh @@ -7,7 +7,7 @@ diesel_models hyperswitch_domain_models storage_impl' -v2_feature_set='v2,merchant_account_v2,payment_v2,customer_v2,routing_v2,business_profile_v2' +v2_feature_set='v2,merchant_account_v2,payment_v2,customer_v2,routing_v2,business_profile_v2,payment_methods_v2' packages_checked=() packages_skipped=() @@ -38,7 +38,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 valid_features="$(features_to_run "$package_name")" all_commands+=("cargo hack clippy --feature-powerset --depth 2 --ignore-unknown-features --at-least-one-of 'v2 ' --include-features '${valid_features}' --package '${package_name}'")