diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 3385b63369..b39355d2c9 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1288,15 +1288,56 @@ mod payment_method_data_serde { OptionalPaymentMethod(serde_json::Value), } + // This struct is an intermediate representation + // This is required in order to catch deserialization errors when deserializing `payment_method_data` + // The #[serde(flatten)] attribute applied on `payment_method_data` discards + // any of the error when deserializing and deserializes to an option instead + #[derive(serde::Deserialize, Debug)] + struct __InnerPaymentMethodData { + billing: Option
, + #[serde(flatten)] + payment_method_data: Option, + } + let deserialize_to_inner = __Inner::deserialize(deserializer)?; + match deserialize_to_inner { __Inner::OptionalPaymentMethod(value) => { - let parsed_value = serde_json::from_value::(value) + let parsed_value = serde_json::from_value::<__InnerPaymentMethodData>(value) .map_err(|serde_json_error| { serde::de::Error::custom(serde_json_error.to_string()) })?; - Ok(Some(parsed_value)) + let payment_method_data = if let Some(payment_method_data_value) = + parsed_value.payment_method_data + { + // Even though no data is passed, the flatten serde_json::Value is deserialized as Some(Object {}) + if let serde_json::Value::Object(ref inner_map) = payment_method_data_value { + if inner_map.is_empty() { + None + } else { + Some( + serde_json::from_value::( + payment_method_data_value, + ) + .map_err(|serde_json_error| { + serde::de::Error::custom(serde_json_error.to_string()) + })?, + ) + } + } else { + Err(serde::de::Error::custom( + "Expected a map for payment_method_data", + ))? + } + } else { + None + }; + + Ok(Some(PaymentMethodDataRequest { + payment_method_data, + billing: parsed_value.billing, + })) } __Inner::RewardString(inner_string) => { let payment_method_data = match inner_string.as_str() { @@ -1305,7 +1346,7 @@ mod payment_method_data_serde { }; Ok(Some(PaymentMethodDataRequest { - payment_method_data, + payment_method_data: Some(payment_method_data), billing: None, })) } @@ -1320,21 +1361,29 @@ mod payment_method_data_serde { S: Serializer, { if let Some(payment_method_data_request) = payment_method_data_request { - match payment_method_data_request.payment_method_data { - PaymentMethodData::Reward => serializer.serialize_str("reward"), - PaymentMethodData::CardRedirect(_) - | PaymentMethodData::BankDebit(_) - | PaymentMethodData::BankRedirect(_) - | PaymentMethodData::BankTransfer(_) - | PaymentMethodData::CardToken(_) - | PaymentMethodData::Crypto(_) - | PaymentMethodData::GiftCard(_) - | PaymentMethodData::PayLater(_) - | PaymentMethodData::Upi(_) - | PaymentMethodData::Voucher(_) - | PaymentMethodData::Card(_) - | PaymentMethodData::MandatePayment - | PaymentMethodData::Wallet(_) => payment_method_data_request.serialize(serializer), + if let Some(payment_method_data) = + payment_method_data_request.payment_method_data.as_ref() + { + match payment_method_data { + PaymentMethodData::Reward => serializer.serialize_str("reward"), + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::Card(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Wallet(_) => { + payment_method_data_request.serialize(serializer) + } + } + } else { + payment_method_data_request.serialize(serializer) } } else { serializer.serialize_none() @@ -1345,7 +1394,7 @@ mod payment_method_data_serde { #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema, Eq, PartialEq)] pub struct PaymentMethodDataRequest { #[serde(flatten)] - pub payment_method_data: PaymentMethodData, + pub payment_method_data: Option, /// billing details for the payment method. /// This billing details will be passed to the processor as billing address. /// If not passed, then payment.billing will be considered @@ -4715,7 +4764,7 @@ mod payments_request_api_contract { let payments_request = serde_json::from_str::(payments_request); assert!(payments_request.is_ok()); - if let PaymentMethodData::Card(card_data) = payments_request + if let Some(PaymentMethodData::Card(card_data)) = payments_request .unwrap() .payment_method_data .unwrap() @@ -4747,7 +4796,7 @@ mod payments_request_api_contract { .payment_method_data .unwrap() .payment_method_data, - PaymentMethodData::Reward + Some(PaymentMethodData::Reward) ); } } diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 404d0e93db..eed80a1128 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -335,7 +335,9 @@ impl TryFrom for payments::PaymentsRequest { pmd.payment_method_details .as_ref() .map(|spmd| payments::PaymentMethodDataRequest { - payment_method_data: payments::PaymentMethodData::from(spmd.to_owned()), + payment_method_data: Some(payments::PaymentMethodData::from( + spmd.to_owned(), + )), billing: pmd.billing_details.clone().map(payments::Address::from), }) }), diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index d29bf235b5..03335d4272 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -246,7 +246,9 @@ impl TryFrom for payments::PaymentsRequest { pmd.payment_method_details .as_ref() .map(|spmd| payments::PaymentMethodDataRequest { - payment_method_data: payments::PaymentMethodData::from(spmd.to_owned()), + payment_method_data: Some(payments::PaymentMethodData::from( + spmd.to_owned(), + )), billing: pmd.billing_details.clone().map(payments::Address::from), }) }), diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index be04d2fb1a..702b63b5c0 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1159,7 +1159,7 @@ pub fn verify_mandate_details_for_recurring_payments( #[instrument(skip_all)] pub fn payment_attempt_status_fsm( - payment_method_data: &Option, + payment_method_data: Option<&api::payments::PaymentMethodData>, confirm: Option, ) -> storage_enums::AttemptStatus { match payment_method_data { @@ -1172,7 +1172,7 @@ pub fn payment_attempt_status_fsm( } pub fn payment_intent_status_fsm( - payment_method_data: &Option, + payment_method_data: Option<&api::PaymentMethodData>, confirm: Option, ) -> storage_enums::IntentStatus { match payment_method_data { @@ -2129,8 +2129,14 @@ pub(crate) fn validate_amount_to_capture( pub(crate) fn validate_payment_method_fields_present( req: &api::PaymentsRequest, ) -> RouterResult<()> { + let payment_method_data = + req.payment_method_data + .as_ref() + .and_then(|request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }); utils::when( - req.payment_method.is_none() && req.payment_method_data.is_some(), + req.payment_method.is_none() && payment_method_data.is_some(), || { Err(errors::ApiErrorResponse::MissingRequiredField { field_name: "payment_method", @@ -2152,7 +2158,7 @@ pub(crate) fn validate_payment_method_fields_present( utils::when( req.payment_method.is_some() - && req.payment_method_data.is_none() + && payment_method_data.is_none() && req.payment_token.is_none() && req.recurring_details.is_none(), || { @@ -2194,14 +2200,14 @@ pub(crate) fn validate_payment_method_fields_present( }; utils::when( - req.payment_method.is_some() && req.payment_method_data.is_some(), + req.payment_method.is_some() && payment_method_data.is_some(), || { - req.payment_method_data - .clone() - .map_or(Ok(()), |req_payment_method_data| { + payment_method_data + .cloned() + .map_or(Ok(()), |payment_method_data| { req.payment_method.map_or(Ok(()), |req_payment_method| { validate_payment_method_and_payment_method_data( - req_payment_method_data.payment_method_data, + payment_method_data, req_payment_method, ) }) @@ -3409,7 +3415,7 @@ impl AttemptType { // In case if fields are not overridden by the request then they contain the same data that was in the previous attempt provided it is populated in this function. #[inline(always)] fn make_new_payment_attempt( - payment_method_data: &Option, + payment_method_data: Option<&api_models::payments::PaymentMethodData>, old_payment_attempt: PaymentAttempt, new_attempt_count: i16, storage_scheme: enums::MerchantStorageScheme, @@ -3507,7 +3513,11 @@ impl AttemptType { let new_payment_attempt = db .insert_payment_attempt( Self::make_new_payment_attempt( - &request.payment_method_data, + request.payment_method_data.as_ref().and_then( + |request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }, + ), fetched_payment_attempt, new_attempt_count, storage_scheme, @@ -3524,7 +3534,11 @@ impl AttemptType { fetched_payment_intent, storage::PaymentIntentUpdate::StatusAndAttemptUpdate { status: payment_intent_status_fsm( - &request.payment_method_data, + request.payment_method_data.as_ref().and_then( + |request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }, + ), Some(true), ), active_attempt_id: new_payment_attempt.attempt_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index d4dc52ce27..d31ec14a72 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -138,7 +138,7 @@ impl &request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), &request.payment_method_type, &mandate_type, &token, @@ -284,7 +284,7 @@ impl payment_method_data: request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), payment_method_info, force_sync: None, refunds: vec![], diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 79680cb80c..125f79956a 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -341,7 +341,7 @@ impl request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), )?; payment_attempt.browser_info = browser_info; @@ -412,7 +412,7 @@ impl let n_request_payment_method_data = request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()); + .and_then(|pmd| pmd.payment_method_data.clone()); let store = state.clone().store; @@ -524,7 +524,7 @@ impl &request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), &request.payment_method_type, &mandate_type, &token, @@ -570,11 +570,12 @@ impl let payment_method_data_after_card_bin_call = request .payment_method_data .as_ref() + .and_then(|request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }) .zip(additional_pm_data) .map(|(payment_method_data, additional_payment_data)| { - payment_method_data - .payment_method_data - .apply_additional_payment_data(additional_payment_data) + payment_method_data.apply_additional_payment_data(additional_payment_data) }); let authentication = payment_attempt.authentication_id.as_ref().async_map(|authentication_id| async move { state diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 78162fbfa6..aaebccd880 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -396,11 +396,14 @@ impl let payment_method_data_after_card_bin_call = request .payment_method_data .as_ref() + .and_then(|payment_method_data_from_request| { + payment_method_data_from_request + .payment_method_data + .as_ref() + }) .zip(additional_payment_data) .map(|(payment_method_data, additional_payment_data)| { - payment_method_data - .payment_method_data - .apply_additional_payment_data(additional_payment_data) + payment_method_data.apply_additional_payment_data(additional_payment_data) }); let amount = payment_attempt.get_total_amount().into(); @@ -702,7 +705,7 @@ impl ValidateRequest ValidateRequest, )> { + let payment_method_data = + request + .payment_method_data + .as_ref() + .and_then(|payment_method_data_request| { + payment_method_data_request.payment_method_data.as_ref() + }); + let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); - let status = - helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm); + let status = helpers::payment_attempt_status_fsm(payment_method_data, request.confirm); let (amount, currency) = (money.0, Some(money.1)); let mut additional_pm_data = request .payment_method_data .as_ref() + .and_then(|payment_method_data_request| { + payment_method_data_request.payment_method_data.as_ref() + }) .async_map(|payment_method_data| async { - helpers::get_additional_payment_data( - &payment_method_data.payment_method_data, - &*state.store, - ) - .await + helpers::get_additional_payment_data(payment_method_data, &*state.store).await }) .await; @@ -944,8 +953,16 @@ impl PaymentCreate { session_expiry: PrimitiveDateTime, ) -> RouterResult { let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); - let status = - helpers::payment_intent_status_fsm(&request.payment_method_data, request.confirm); + + let status = helpers::payment_intent_status_fsm( + request + .payment_method_data + .as_ref() + .and_then(|request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }), + request.confirm, + ); let client_secret = crate::utils::generate_id(consts::ID_LENGTH, format!("{payment_id}_secret").as_str()); let (amount, currency) = (money.0, Some(money.1)); diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index ecf1fe998b..804d1dbc80 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -81,7 +81,7 @@ impl request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), )?; helpers::validate_payment_status_against_not_allowed_statuses( @@ -265,7 +265,7 @@ impl &request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), &request.payment_method_type, &mandate_type, &token, @@ -430,7 +430,7 @@ impl payment_method_data: request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), payment_method_info, force_sync: None, refunds: vec![], diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 4fc3fb4452..e36c68c27d 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -261,7 +261,7 @@ mod payments_test { PaymentsRequest { amount: Some(Amount::from(200)), payment_method_data: Some(PaymentMethodDataRequest { - payment_method_data: PaymentMethodData::Card(card()), + payment_method_data: Some(PaymentMethodData::Card(card())), billing: None, }), ..PaymentsRequest::default() diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 7a990464da..646a11d4cd 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -317,7 +317,7 @@ async fn payments_create_core() { setup_future_usage: Some(api_enums::FutureUsage::OnSession), authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "4242424242424242".to_string().try_into().unwrap(), card_exp_month: "10".to_string().into(), card_exp_year: "35".to_string().into(), @@ -329,7 +329,7 @@ async fn payments_create_core() { card_issuing_country: None, bank_code: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), payment_method: Some(api_enums::PaymentMethod::Card), @@ -499,7 +499,7 @@ async fn payments_create_core_adyen_no_redirect() { setup_future_usage: Some(api_enums::FutureUsage::OnSession), authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "5555 3412 4444 1115".to_string().try_into().unwrap(), card_exp_month: "03".to_string().into(), card_exp_year: "2030".to_string().into(), @@ -511,7 +511,7 @@ async fn payments_create_core_adyen_no_redirect() { card_issuing_country: None, bank_code: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), payment_method: Some(api_enums::PaymentMethod::Card), diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index cce589fb03..9b622d11fd 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -77,7 +77,7 @@ async fn payments_create_core() { setup_future_usage: None, authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "4242424242424242".to_string().try_into().unwrap(), card_exp_month: "10".to_string().into(), card_exp_year: "35".to_string().into(), @@ -89,7 +89,7 @@ async fn payments_create_core() { card_issuing_country: None, bank_code: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), payment_method: Some(api_enums::PaymentMethod::Card), @@ -266,7 +266,7 @@ async fn payments_create_core_adyen_no_redirect() { setup_future_usage: Some(api_enums::FutureUsage::OffSession), authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "5555 3412 4444 1115".to_string().try_into().unwrap(), card_exp_month: "03".to_string().into(), card_exp_year: "2030".to_string().into(), @@ -278,7 +278,7 @@ async fn payments_create_core_adyen_no_redirect() { card_type: None, card_issuing_country: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index d795387853..d278c50a38 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -12787,7 +12787,12 @@ "PaymentMethodDataRequest": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodData" + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodData" + } + ], + "nullable": true }, { "type": "object",