diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index e16a206b11..1f296a1081 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -13446,7 +13446,7 @@ "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" + "$ref": "#/components/schemas/PaymentLinkConfigRequest" } ], "nullable": true @@ -13486,6 +13486,7 @@ "PaymentsCreateIntentResponse": { "type": "object", "required": [ + "id", "amount_details", "client_secret", "capture_method", @@ -13495,9 +13496,14 @@ "apply_mit_exemption", "payment_link_enabled", "request_incremental_authorization", + "expires_on", "request_external_three_ds_authentication" ], "properties": { + "id": { + "type": "string", + "description": "Global Payment Id for the payment" + }, "amount_details": { "$ref": "#/components/schemas/AmountDetails" }, @@ -13626,7 +13632,7 @@ "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" + "$ref": "#/components/schemas/PaymentLinkConfigRequest" } ], "nullable": true @@ -13634,13 +13640,10 @@ "request_incremental_authorization": { "$ref": "#/components/schemas/RequestIncrementalAuthorization" }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 + "expires_on": { + "type": "string", + "format": "date-time", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds" }, "frm_metadata": { "type": "object", diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 1f66fb521e..7bbff94d45 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -1,5 +1,9 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; +#[cfg(feature = "v2")] +use super::PaymentsCreateIntentRequest; +#[cfg(feature = "v2")] +use super::PaymentsCreateIntentResponse; #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -29,6 +33,8 @@ use crate::{ PaymentsStartRequest, RedirectionResponse, }, }; + +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsRetrieveRequest { fn get_api_event_type(&self) -> Option { match self.resource_id { @@ -40,6 +46,7 @@ impl ApiEventMetric for PaymentsRetrieveRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsStartRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -48,6 +55,7 @@ impl ApiEventMetric for PaymentsStartRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsCaptureRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -56,6 +64,7 @@ impl ApiEventMetric for PaymentsCaptureRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsCompleteAuthorizeRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -64,6 +73,7 @@ impl ApiEventMetric for PaymentsCompleteAuthorizeRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsDynamicTaxCalculationRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -72,6 +82,7 @@ impl ApiEventMetric for PaymentsDynamicTaxCalculationRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsPostSessionTokensRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -80,6 +91,7 @@ impl ApiEventMetric for PaymentsPostSessionTokensRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsPostSessionTokensResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -88,8 +100,10 @@ impl ApiEventMetric for PaymentsPostSessionTokensResponse { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsDynamicTaxCalculationResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsCancelRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -98,6 +112,7 @@ impl ApiEventMetric for PaymentsCancelRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsApproveRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -106,6 +121,7 @@ impl ApiEventMetric for PaymentsApproveRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsRejectRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -114,6 +130,7 @@ impl ApiEventMetric for PaymentsRejectRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsRequest { fn get_api_event_type(&self) -> Option { match self.payment_id { @@ -125,6 +142,23 @@ impl ApiEventMetric for PaymentsRequest { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsCreateIntentRequest { + fn get_api_event_type(&self) -> Option { + None + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsCreateIntentResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -262,6 +296,7 @@ impl ApiEventMetric for PaymentsAggregateResponse { impl ApiEventMetric for RedirectionResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsIncrementalAuthorizationRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -270,8 +305,10 @@ impl ApiEventMetric for PaymentsIncrementalAuthorizationRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsExternalAuthenticationResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsExternalAuthenticationRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -280,8 +317,10 @@ impl ApiEventMetric for PaymentsExternalAuthenticationRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for ExtendedCardInfoResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsManualUpdateRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -290,6 +329,7 @@ impl ApiEventMetric for PaymentsManualUpdateRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsManualUpdateResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -298,6 +338,7 @@ impl ApiEventMetric for PaymentsManualUpdateResponse { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsSessionResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 674a19f3ef..9b4bd78b09 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5,6 +5,8 @@ use std::{ }; pub mod additional_info; use cards::CardNumber; +#[cfg(feature = "v2")] +use common_utils::id_type::GlobalPaymentId; use common_utils::{ consts::default_payments_list_limit, crypto, @@ -162,7 +164,7 @@ pub struct PaymentsCreateIntentRequest { /// The URL to which you want the user to be redirected after the completion of the payment operation #[schema(value_type = Option, example = "https://hyperswitch.io")] - pub return_url: Option, + pub return_url: Option, #[schema(value_type = Option, example = "off_session")] pub setup_future_usage: Option, @@ -203,8 +205,8 @@ pub struct PaymentsCreateIntentRequest { pub payment_link_enabled: Option, /// Configure a custom payment link for the particular payment - #[schema(value_type = Option)] - pub payment_link_config: Option, + #[schema(value_type = Option)] + pub payment_link_config: Option, ///Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it. #[schema(value_type = Option)] @@ -225,16 +227,81 @@ pub struct PaymentsCreateIntentRequest { Option, } +#[cfg(feature = "v2")] +impl PaymentsCreateIntentRequest { + pub fn get_feature_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + Ok(self + .feature_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose()? + .map(Secret::new)) + } + + pub fn get_connector_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + Ok(self + .connector_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose()? + .map(Secret::new)) + } + + pub fn get_allowed_payment_method_types_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + Ok(self + .allowed_payment_method_types + .as_ref() + .map(Encode::encode_to_value) + .transpose()? + .map(Secret::new)) + } + + pub fn get_order_details_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option>, + common_utils::errors::ParsingError, + > { + self.order_details + .as_ref() + .map(|od| { + od.iter() + .map(|order| order.encode_to_value().map(Secret::new)) + .collect::, _>>() + }) + .transpose() + } +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] #[cfg(feature = "v2")] pub struct PaymentsCreateIntentResponse { + /// Global Payment Id for the payment + #[schema(value_type = String)] + pub id: GlobalPaymentId, + /// The amount details for the payment pub amount_details: AmountDetails, /// It's a token used for client side verification. #[schema(value_type = String, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")] - pub client_secret: Secret, + pub client_secret: common_utils::types::ClientSecret, /// Unique identifier for the payment. This ensures idempotency for multiple payments /// that have been done by a single merchant. @@ -257,9 +324,11 @@ pub struct PaymentsCreateIntentResponse { pub authentication_type: api_enums::AuthenticationType, /// The billing details of the payment. This address will be used for invoicing. + #[schema(value_type = Option
)] pub billing: Option
, /// The shipping address for the payment + #[schema(value_type = Option
)] pub shipping: Option
, /// The identifier for the customer @@ -276,7 +345,7 @@ pub struct PaymentsCreateIntentResponse { /// The URL to which you want the user to be redirected after the completion of the payment operation #[schema(value_type = Option, example = "https://hyperswitch.io")] - pub return_url: Option, + pub return_url: Option, #[schema(value_type = FutureUsage, example = "off_session")] pub setup_future_usage: api_enums::FutureUsage, @@ -300,34 +369,35 @@ pub struct PaymentsCreateIntentResponse { /// Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent #[schema(value_type = Option>)] - pub allowed_payment_method_types: Option>, + pub allowed_payment_method_types: Option, /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)] pub metadata: Option, /// Some connectors like Apple pay, Airwallex and Noon might require some additional information, find specific details in the child attributes below. - pub connector_metadata: Option, + #[schema(value_type = Option)] + pub connector_metadata: Option, /// Additional data that might be required by hyperswitch based on the requested features by the merchants. - pub feature_metadata: Option, + #[schema(value_type = Option)] + pub feature_metadata: Option, /// Whether to generate the payment link for this payment or not (if applicable) #[schema(value_type = EnablePaymentLinkRequest)] pub payment_link_enabled: common_enums::EnablePaymentLinkRequest, /// Configure a custom payment link for the particular payment - #[schema(value_type = Option)] - pub payment_link_config: Option, + #[schema(value_type = Option)] + pub payment_link_config: Option, ///Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it. #[schema(value_type = RequestIncrementalAuthorization)] pub request_incremental_authorization: common_enums::RequestIncrementalAuthorization, - ///Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config - ///(900) for 15 mins - #[schema(example = 900)] - pub session_expiry: Option, + ///Will be used to expire client secret after certain amount of time to be supplied in seconds + #[serde(with = "common_utils::custom_serde::iso8601")] + pub expires_on: PrimitiveDateTime, /// Additional data related to some frm(Fraud Risk Management) connectors #[schema(value_type = Option, example = r#"{ "coverage_request" : "fraud", "fulfillment_method" : "delivery" }"#)] @@ -366,6 +436,58 @@ pub struct AmountDetails { tax_on_surcharge: Option, } +#[cfg(feature = "v2")] +pub struct AmountDetailsSetter { + pub order_amount: Amount, + pub currency: common_enums::Currency, + pub shipping_cost: Option, + pub order_tax_amount: Option, + pub skip_external_tax_calculation: common_enums::TaxCalculationOverride, + pub skip_surcharge_calculation: common_enums::SurchargeCalculationOverride, + pub surcharge_amount: Option, + pub tax_on_surcharge: Option, +} + +#[cfg(feature = "v2")] +impl AmountDetails { + pub fn new(amount_details_setter: AmountDetailsSetter) -> Self { + Self { + order_amount: amount_details_setter.order_amount, + currency: amount_details_setter.currency, + shipping_cost: amount_details_setter.shipping_cost, + order_tax_amount: amount_details_setter.order_tax_amount, + skip_external_tax_calculation: amount_details_setter.skip_external_tax_calculation, + skip_surcharge_calculation: amount_details_setter.skip_surcharge_calculation, + surcharge_amount: amount_details_setter.surcharge_amount, + tax_on_surcharge: amount_details_setter.tax_on_surcharge, + } + } + pub fn order_amount(&self) -> Amount { + self.order_amount + } + pub fn currency(&self) -> common_enums::Currency { + self.currency + } + pub fn shipping_cost(&self) -> Option { + self.shipping_cost + } + pub fn order_tax_amount(&self) -> Option { + self.order_tax_amount + } + pub fn skip_external_tax_calculation(&self) -> common_enums::TaxCalculationOverride { + self.skip_external_tax_calculation.clone() + } + pub fn skip_surcharge_calculation(&self) -> common_enums::SurchargeCalculationOverride { + self.skip_surcharge_calculation.clone() + } + pub fn surcharge_amount(&self) -> Option { + self.surcharge_amount + } + pub fn tax_on_surcharge(&self) -> Option { + self.tax_on_surcharge + } +} + #[derive( Default, Debug, @@ -3489,6 +3611,8 @@ pub struct Address { pub email: Option, } +impl masking::SerializableSecret for Address {} + impl Address { /// Unify the address, giving priority to `self` when details are present in both pub fn unify_address(self, other: Option<&Self>) -> Self { @@ -4808,6 +4932,8 @@ pub struct OrderDetailsWithAmount { pub product_tax_code: Option, } +impl masking::SerializableSecret for OrderDetailsWithAmount {} + #[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(rename_all = "snake_case")] pub enum ProductType { diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index fdc9670045..dcdb4c760b 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" license.workspace = true [features] -default = ["v1"] +default = [] keymanager = ["dep:router_env"] keymanager_mtls = ["reqwest/rustls-tls"] encryption_service = ["dep:router_env"] diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index 8b8707d9b9..c7f1c286c5 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -485,6 +485,24 @@ impl Encryptable { pub fn into_encrypted(self) -> Secret, EncryptionStrategy> { self.encrypted } + + /// + /// Deserialize inner value and return new Encryptable object + /// + pub fn deserialize_inner_value( + self, + f: F, + ) -> CustomResult, errors::ParsingError> + where + F: FnOnce(T) -> CustomResult, + U: Clone, + { + // Option::map(self, f) + let inner = self.inner; + let encrypted = self.encrypted; + let inner = f(inner)?; + Ok(Encryptable { inner, encrypted }) + } } impl Deref for Encryptable> { diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 2b1571617e..1b03f8f19f 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -15,9 +15,14 @@ pub enum ApiEventsType { Payout { payout_id: String, }, + #[cfg(feature = "v1")] Payment { payment_id: id_type::PaymentId, }, + #[cfg(feature = "v2")] + Payment { + payment_id: id_type::GlobalPaymentId, + }, Refund { payment_id: Option, refund_id: String, @@ -82,6 +87,7 @@ pub enum ApiEventsType { impl ApiEventMetric for serde_json::Value {} impl ApiEventMetric for () {} +#[cfg(feature = "v1")] impl ApiEventMetric for id_type::PaymentId { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { diff --git a/crates/common_utils/src/id_type/global_id/payment.rs b/crates/common_utils/src/id_type/global_id/payment.rs index edc7e3fb96..a404c7bc9c 100644 --- a/crates/common_utils/src/id_type/global_id/payment.rs +++ b/crates/common_utils/src/id_type/global_id/payment.rs @@ -1,4 +1,6 @@ -use crate::errors; +use error_stack::ResultExt; + +use crate::{errors, generate_id_with_default_len, generate_time_ordered_id_without_prefix, types}; /// A global id that can be used to identify a payment #[derive( @@ -22,6 +24,17 @@ impl GlobalPaymentId { pub fn get_string_repr(&self) -> &str { self.0.get_string_repr() } + + /// Generate a new GlobalPaymentId from a cell id + pub fn generate(cell_id: crate::id_type::CellId) -> Self { + let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Payment); + Self(global_id) + } + + /// Generate a new ClientId from self + pub fn generate_client_secret(&self) -> types::ClientSecret { + types::ClientSecret::new(self.clone(), generate_time_ordered_id_without_prefix()) + } } // TODO: refactor the macro to include this id use case as well diff --git a/crates/common_utils/src/id_type/payment.rs b/crates/common_utils/src/id_type/payment.rs index d82a697b08..60bc9968f3 100644 --- a/crates/common_utils/src/id_type/payment.rs +++ b/crates/common_utils/src/id_type/payment.rs @@ -75,6 +75,10 @@ crate::impl_id_type_methods!(PaymentReferenceId, "payment_reference_id"); crate::impl_debug_id_type!(PaymentReferenceId); crate::impl_try_from_cow_str_id_type!(PaymentReferenceId, "payment_reference_id"); +// Database related implementations so that this field can be used directly in the database tables +crate::impl_queryable_id_type!(PaymentReferenceId); +crate::impl_to_sql_from_sql_id_type!(PaymentReferenceId); + #[cfg(feature = "metrics")] /// This is implemented so that we can use payment id directly as attribute in metrics impl From for router_env::opentelemetry::Value { diff --git a/crates/common_utils/src/id_type/profile.rs b/crates/common_utils/src/id_type/profile.rs index e9d90e7bba..a73cdfdd53 100644 --- a/crates/common_utils/src/id_type/profile.rs +++ b/crates/common_utils/src/id_type/profile.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + crate::id_type!( ProfileId, "A type for profile_id that can be used for business profile ids" @@ -20,3 +22,12 @@ impl crate::events::ApiEventMetric for ProfileId { }) } } + +impl FromStr for ProfileId { + type Err = error_stack::Report; + + fn from_str(s: &str) -> Result { + let cow_string = std::borrow::Cow::Owned(s.to_string()); + Self::try_from(cow_string) + } +} diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index ecffa259bc..923bdf89c0 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -264,6 +264,12 @@ pub fn generate_time_ordered_id(prefix: &str) -> String { format!("{prefix}_{}", uuid::Uuid::now_v7().as_simple()) } +/// Generate a time-ordered (time-sortable) unique identifier using the current time without prefix +#[inline] +pub fn generate_time_ordered_id_without_prefix() -> String { + uuid::Uuid::now_v7().as_simple().to_string() +} + #[allow(missing_docs)] pub trait DbConnectionParams { fn get_username(&self) -> &str; diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 0cad88bfd4..e11ddbf7a3 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -633,6 +633,7 @@ mod client_secret_type { use std::fmt; use masking::PeekInterface; + use router_env::logger; use super::*; use crate::id_type; @@ -656,6 +657,14 @@ mod client_secret_type { self.secret.peek() ) } + + /// Create a new client secret + pub(crate) fn new(payment_id: id_type::GlobalPaymentId, secret: String) -> Self { + Self { + payment_id, + secret: masking::Secret::new(secret), + } + } } impl<'de> Deserialize<'de> for ClientSecret { @@ -730,7 +739,23 @@ mod client_secret_type { { fn from_sql(value: DB::RawValue<'_>) -> deserialize::Result { let string_repr = String::from_sql(value)?; - Ok(serde_json::from_str(&string_repr)?) + let (payment_id, secret) = + string_repr + .rsplit_once("_secret_") + .ok_or(ParsingError::EncodeError( + "Expected a string in the format '{payment_id}_secret_{secret}'", + ))?; + + let payment_id = id_type::GlobalPaymentId::try_from(Cow::Owned(payment_id.to_owned())) + .map_err(|err| { + logger::error!(global_payment_id_error=?err); + ParsingError::EncodeError("Error while constructing GlobalPaymentId") + })?; + + Ok(Self { + payment_id, + secret: masking::Secret::new(secret.to_owned()), + }) } } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 23d1a0f1ac..f53594075a 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -31,7 +31,7 @@ pub struct PaymentIntent { pub last_synced: Option, pub setup_future_usage: Option, pub client_secret: common_utils::types::ClientSecret, - pub active_attempt_id: String, + pub active_attempt_id: Option, #[diesel(deserialize_as = super::OptionalDieselArray)] pub order_details: Option>, pub allowed_payment_method_types: Option, @@ -44,7 +44,7 @@ pub struct PaymentIntent { pub surcharge_applicable: Option, pub request_incremental_authorization: Option, pub authorization_count: Option, - pub session_expiry: Option, + pub session_expiry: PrimitiveDateTime, pub request_external_three_ds_authentication: Option, pub frm_metadata: Option, pub customer_details: Option, @@ -52,7 +52,7 @@ pub struct PaymentIntent { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, - pub merchant_reference_id: Option, + pub merchant_reference_id: Option, pub billing_address: Option, pub shipping_address: Option, pub capture_method: Option, @@ -228,7 +228,7 @@ pub struct PaymentIntentNew { pub last_synced: Option, pub setup_future_usage: Option, pub client_secret: common_utils::types::ClientSecret, - pub active_attempt_id: String, + pub active_attempt_id: Option, #[diesel(deserialize_as = super::OptionalDieselArray)] pub order_details: Option>, pub allowed_payment_method_types: Option, @@ -241,8 +241,8 @@ pub struct PaymentIntentNew { pub surcharge_applicable: Option, pub request_incremental_authorization: Option, pub authorization_count: Option, - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub session_expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub session_expiry: PrimitiveDateTime, pub request_external_three_ds_authentication: Option, pub frm_metadata: Option, pub customer_details: Option, @@ -250,7 +250,7 @@ pub struct PaymentIntentNew { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, - pub merchant_reference_id: Option, + pub merchant_reference_id: Option, pub billing_address: Option, pub shipping_address: Option, pub capture_method: Option, diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 53f568f18c..9d1a47e4e0 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -845,7 +845,7 @@ diesel::table! { #[max_length = 128] client_secret -> Varchar, #[max_length = 64] - active_attempt_id -> Varchar, + active_attempt_id -> Nullable, order_details -> Nullable>>, allowed_payment_method_types -> Nullable, connector_metadata -> Nullable, @@ -860,7 +860,7 @@ diesel::table! { surcharge_applicable -> Nullable, request_incremental_authorization -> Nullable, authorization_count -> Nullable, - session_expiry -> Nullable, + session_expiry -> Timestamp, request_external_three_ds_authentication -> Nullable, frm_metadata -> Nullable, customer_details -> Nullable, diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index c99fbd89cd..4803514617 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -54,3 +54,109 @@ impl RemoteStorageObject { } } } + +use std::fmt::Debug; + +pub trait ApiModelToDieselModelConvertor { + /// Convert from a foreign type to the current type + fn convert_from(from: F) -> Self; +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for diesel_models::payment_intent::PaymentLinkConfigRequestForPayments +{ + fn convert_from(item: api_models::admin::PaymentLinkConfigRequest) -> Self { + Self { + theme: item.theme, + logo: item.logo, + seller_name: item.seller_name, + sdk_layout: item.sdk_layout, + display_sdk_only: item.display_sdk_only, + enabled_saved_payment_method: item.enabled_saved_payment_method, + transaction_details: item.transaction_details.map(|transaction_details| { + transaction_details + .into_iter() + .map(|transaction_detail| { + diesel_models::PaymentLinkTransactionDetails::convert_from( + transaction_detail, + ) + }) + .collect() + }), + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for diesel_models::PaymentLinkTransactionDetails +{ + fn convert_from(from: api_models::admin::PaymentLinkTransactionDetails) -> Self { + Self { + key: from.key, + value: from.value, + ui_configuration: from + .ui_configuration + .map(diesel_models::TransactionDetailsUiConfiguration::convert_from), + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for diesel_models::TransactionDetailsUiConfiguration +{ + fn convert_from(from: api_models::admin::TransactionDetailsUiConfiguration) -> Self { + Self { + position: from.position, + is_key_bold: from.is_key_bold, + is_value_bold: from.is_value_bold, + } + } +} + +#[cfg(feature = "v2")] +impl From for payments::AmountDetails { + fn from(amount_details: api_models::payments::AmountDetails) -> Self { + Self { + order_amount: amount_details.order_amount().into(), + currency: amount_details.currency(), + shipping_cost: amount_details.shipping_cost(), + tax_details: Some(diesel_models::TaxDetails { + default: amount_details + .order_tax_amount() + .map(|order_tax_amount| diesel_models::DefaultTax { order_tax_amount }), + payment_method_type: None, + }), + skip_external_tax_calculation: payments::TaxCalculationOverride::from( + amount_details.skip_external_tax_calculation(), + ), + skip_surcharge_calculation: payments::SurchargeCalculationOverride::from( + amount_details.skip_surcharge_calculation(), + ), + surcharge_amount: amount_details.surcharge_amount(), + tax_on_surcharge: amount_details.tax_on_surcharge(), + } + } +} + +#[cfg(feature = "v2")] +impl From for payments::SurchargeCalculationOverride { + fn from(surcharge_calculation_override: common_enums::SurchargeCalculationOverride) -> Self { + match surcharge_calculation_override { + common_enums::SurchargeCalculationOverride::Calculate => Self::Calculate, + common_enums::SurchargeCalculationOverride::Skip => Self::Skip, + } + } +} + +#[cfg(feature = "v2")] +impl From for payments::TaxCalculationOverride { + fn from(tax_calculation_override: common_enums::TaxCalculationOverride) -> Self { + match tax_calculation_override { + common_enums::TaxCalculationOverride::Calculate => Self::Calculate, + common_enums::TaxCalculationOverride::Skip => Self::Skip, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 2d7d49e81c..4c8cb9c38e 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -1,8 +1,14 @@ #[cfg(feature = "v2")] use std::marker::PhantomData; +#[cfg(feature = "v2")] +use api_models::payments::Address; +#[cfg(feature = "v2")] +use api_models::payments::OrderDetailsWithAmount; use common_utils::{self, crypto::Encryptable, id_type, pii, types::MinorUnit}; use diesel_models::payment_intent::TaxDetails; +#[cfg(feature = "v2")] +use error_stack::ResultExt; use masking::Secret; use time::PrimitiveDateTime; @@ -13,6 +19,10 @@ use common_enums as storage_enums; use self::payment_attempt::PaymentAttempt; use crate::RemoteStorageObject; +#[cfg(feature = "v2")] +use crate::{business_profile, merchant_account}; +#[cfg(feature = "v2")] +use crate::{errors, ApiModelToDieselModelConvertor}; #[cfg(feature = "v1")] #[derive(Clone, Debug, PartialEq, serde::Serialize)] @@ -200,13 +210,13 @@ pub struct PaymentIntent { pub modified_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601::option")] pub last_synced: Option, - pub setup_future_usage: Option, + pub setup_future_usage: storage_enums::FutureUsage, /// The client secret that is generated for the payment. This is used to authenticate the payment from client facing apis. pub client_secret: common_utils::types::ClientSecret, /// The active attempt for the payment intent. This is the payment attempt that is currently active for the payment intent. - pub active_attempt: RemoteStorageObject, + pub active_attempt: Option>, /// The order details for the payment. - pub order_details: Option>, + pub order_details: Option>>, /// This is the list of payment method types that are allowed for the payment intent. /// This field allows the merchant to restrict the payment methods that can be used for the payment intent. pub allowed_payment_method_types: Option, @@ -225,12 +235,12 @@ pub struct PaymentIntent { /// Denotes the last instance which updated the payment pub updated_by: String, /// Denotes whether merchant requested for incremental authorization to be enabled for this payment. - pub request_incremental_authorization: Option, + pub request_incremental_authorization: storage_enums::RequestIncrementalAuthorization, /// Denotes the number of authorizations that have been made for the payment. pub authorization_count: Option, /// Denotes the client secret expiry for the payment. This is the time at which the client secret will expire. - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub session_expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub session_expiry: PrimitiveDateTime, /// Denotes whether merchant requested for 3ds authentication to be enabled for this payment. pub request_external_three_ds_authentication: common_enums::External3dsAuthenticationRequest, /// Metadata related to fraud and risk management @@ -238,15 +248,15 @@ pub struct PaymentIntent { /// The details of the customer in a denormalized form. Only a subset of fields are stored. pub customer_details: Option>>, /// The reference id for the order in the merchant's system. This value can be passed by the merchant. - pub merchant_reference_id: Option, + pub merchant_reference_id: Option, /// The billing address for the order in a denormalized form. - pub billing_address: Option>>, + pub billing_address: Option>>, /// The shipping address for the order in a denormalized form. - pub shipping_address: Option>>, + pub shipping_address: Option>>, /// Capture method for the payment - pub capture_method: Option, + pub capture_method: storage_enums::CaptureMethod, /// Authentication type that is requested by the merchant for this payment. - pub authentication_type: Option, + pub authentication_type: common_enums::AuthenticationType, /// This contains the pre routing results that are done when routing is done during listing the payment methods. pub prerouting_algorithm: Option, /// The organization id for the payment. This is derived from the merchant account @@ -263,6 +273,117 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, } +#[cfg(feature = "v2")] +impl PaymentIntent { + fn get_request_incremental_authorization_value( + request: &api_models::payments::PaymentsCreateIntentRequest, + ) -> common_utils::errors::CustomResult< + common_enums::RequestIncrementalAuthorization, + errors::api_error_response::ApiErrorResponse, + > { + request.request_incremental_authorization + .map(|request_incremental_authorization| { + if request_incremental_authorization == common_enums::RequestIncrementalAuthorization::True { + if request.capture_method == Some(common_enums::CaptureMethod::Automatic) { + Err(errors::api_error_response::ApiErrorResponse::InvalidRequestData { message: "incremental authorization is not supported when capture_method is automatic".to_owned() })? + } + Ok(common_enums::RequestIncrementalAuthorization::True) + } else { + Ok(common_enums::RequestIncrementalAuthorization::False) + } + }) + .unwrap_or(Ok(common_enums::RequestIncrementalAuthorization::default())) + } + pub async fn create_domain_model_from_request( + payment_id: &id_type::GlobalPaymentId, + merchant_account: &merchant_account::MerchantAccount, + profile: &business_profile::Profile, + request: api_models::payments::PaymentsCreateIntentRequest, + billing_address: Option>>, + shipping_address: Option>>, + ) -> common_utils::errors::CustomResult + { + let allowed_payment_method_types = request + .get_allowed_payment_method_types_as_value() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting allowed payment method types as value")?; + let connector_metadata = request + .get_connector_metadata_as_value() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting connector metadata as value")?; + let feature_metadata = request + .get_feature_metadata_as_value() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting feature metadata as value")?; + let request_incremental_authorization = + Self::get_request_incremental_authorization_value(&request)?; + let session_expiry = + common_utils::date_time::now().saturating_add(time::Duration::seconds( + request.session_expiry.map(i64::from).unwrap_or( + profile + .session_expiry + .unwrap_or(common_utils::consts::DEFAULT_SESSION_EXPIRY), + ), + )); + let client_secret = payment_id.generate_client_secret(); + let order_details = request + .order_details + .map(|order_details| order_details.into_iter().map(Secret::new).collect()); + Ok(Self { + id: payment_id.clone(), + merchant_id: merchant_account.get_id().clone(), + // Intent status would be RequiresPaymentMethod because we are creating a new payment intent + status: common_enums::IntentStatus::RequiresPaymentMethod, + amount_details: AmountDetails::from(request.amount_details), + amount_captured: None, + customer_id: request.customer_id, + description: request.description, + return_url: request.return_url, + metadata: request.metadata, + statement_descriptor: request.statement_descriptor, + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + last_synced: None, + setup_future_usage: request.setup_future_usage.unwrap_or_default(), + client_secret, + active_attempt: None, + order_details, + allowed_payment_method_types, + connector_metadata, + feature_metadata, + // Attempt count is 0 in create intent as no attempt is made yet + attempt_count: 0, + profile_id: profile.get_id().clone(), + payment_link_id: None, + frm_merchant_decision: None, + updated_by: merchant_account.storage_scheme.to_string(), + request_incremental_authorization, + // Authorization count is 0 in create intent as no authorization is made yet + authorization_count: Some(0), + session_expiry, + request_external_three_ds_authentication: request + .request_external_three_ds_authentication + .unwrap_or_default(), + frm_metadata: request.frm_metadata, + customer_details: None, + merchant_reference_id: request.merchant_reference_id, + billing_address, + shipping_address, + capture_method: request.capture_method.unwrap_or_default(), + authentication_type: request.authentication_type.unwrap_or_default(), + prerouting_algorithm: None, + organization_id: merchant_account.organization_id.clone(), + enable_payment_link: request.payment_link_enabled.unwrap_or_default(), + apply_mit_exemption: request.apply_mit_exemption.unwrap_or_default(), + customer_present: request.customer_present.unwrap_or_default(), + payment_link_config: request + .payment_link_config + .map(ApiModelToDieselModelConvertor::convert_from), + routing_algorithm_id: request.routing_algorithm_id, + }) + } +} + #[cfg(feature = "v2")] #[derive(Clone)] pub struct PaymentIntentData diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index be2b354711..ebd630beb6 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1,4 +1,6 @@ use common_enums as storage_enums; +#[cfg(feature = "v2")] +use common_utils::ext_traits::{Encode, ValueExt}; use common_utils::{ consts::{PAYMENTS_LIST_MAX_LIMIT_V1, PAYMENTS_LIST_MAX_LIMIT_V2}, crypto::{self, Encryptable}, @@ -16,6 +18,8 @@ use diesel_models::{ PaymentIntent as DieselPaymentIntent, PaymentIntentNew as DieselPaymentIntentNew, }; use error_stack::ResultExt; +#[cfg(feature = "v2")] +use masking::ExposeInterface; use masking::{Deserialize, PeekInterface, Secret}; use rustc_hash::FxHashMap; use serde::Serialize; @@ -1524,10 +1528,20 @@ impl behaviour::Conversion for PaymentIntent { created_at, modified_at, last_synced, - setup_future_usage, + setup_future_usage: Some(setup_future_usage), client_secret, - active_attempt_id: active_attempt.get_id(), - order_details, + active_attempt_id: active_attempt.map(|attempt| attempt.get_id()), + order_details: order_details + .map(|order_details| { + order_details + .into_iter() + .map(|order_detail| order_detail.encode_to_value().map(Secret::new)) + .collect::, _>>() + }) + .transpose() + .change_context(ValidationError::InvalidValue { + message: "invalid value found for order_details".to_string(), + })?, allowed_payment_method_types, connector_metadata, feature_metadata, @@ -1537,7 +1551,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_id, updated_by, - request_incremental_authorization, + request_incremental_authorization: Some(request_incremental_authorization), authorization_count, session_expiry, request_external_three_ds_authentication: Some( @@ -1547,9 +1561,9 @@ impl behaviour::Conversion for PaymentIntent { customer_details: customer_details.map(Encryption::from), billing_address: billing_address.map(Encryption::from), shipping_address: shipping_address.map(Encryption::from), - capture_method, + capture_method: Some(capture_method), id, - authentication_type, + authentication_type: Some(authentication_type), prerouting_algorithm, merchant_reference_id, surcharge_amount: amount_details.surcharge_amount, @@ -1608,6 +1622,23 @@ impl behaviour::Conversion for PaymentIntent { storage_model.surcharge_applicable, ), }; + let billing_address = data + .billing + .map(|billing| { + billing.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; + + let shipping_address = data + .shipping + .map(|shipping| { + shipping.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; Ok::>(Self { merchant_id: storage_model.merchant_id, @@ -1622,10 +1653,23 @@ impl behaviour::Conversion for PaymentIntent { created_at: storage_model.created_at, modified_at: storage_model.modified_at, last_synced: storage_model.last_synced, - setup_future_usage: storage_model.setup_future_usage, + setup_future_usage: storage_model.setup_future_usage.unwrap_or_default(), client_secret: storage_model.client_secret, - active_attempt: RemoteStorageObject::ForeignID(storage_model.active_attempt_id), - order_details: storage_model.order_details, + active_attempt: storage_model + .active_attempt_id + .map(RemoteStorageObject::ForeignID), + order_details: storage_model + .order_details + .map(|order_details| { + order_details + .into_iter() + .map(|order_detail| { + order_detail.expose().parse_value("OrderDetailsWithAmount") + }) + .collect::, _>>() + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed)?, allowed_payment_method_types: storage_model.allowed_payment_method_types, connector_metadata: storage_model.connector_metadata, feature_metadata: storage_model.feature_metadata, @@ -1634,7 +1678,9 @@ impl behaviour::Conversion for PaymentIntent { frm_merchant_decision: storage_model.frm_merchant_decision, payment_link_id: storage_model.payment_link_id, updated_by: storage_model.updated_by, - request_incremental_authorization: storage_model.request_incremental_authorization, + request_incremental_authorization: storage_model + .request_incremental_authorization + .unwrap_or_default(), authorization_count: storage_model.authorization_count, session_expiry: storage_model.session_expiry, request_external_three_ds_authentication: @@ -1643,13 +1689,13 @@ impl behaviour::Conversion for PaymentIntent { ), frm_metadata: storage_model.frm_metadata, customer_details: data.customer_details, - billing_address: data.billing, - shipping_address: data.shipping, - capture_method: storage_model.capture_method, + billing_address, + shipping_address, + capture_method: storage_model.capture_method.unwrap_or_default(), id: storage_model.id, merchant_reference_id: storage_model.merchant_reference_id, organization_id: storage_model.organization_id, - authentication_type: storage_model.authentication_type, + authentication_type: storage_model.authentication_type.unwrap_or_default(), prerouting_algorithm: storage_model.prerouting_algorithm, enable_payment_link: common_enums::EnablePaymentLinkRequest::from( storage_model.enable_payment_link, @@ -1689,10 +1735,21 @@ impl behaviour::Conversion for PaymentIntent { created_at: self.created_at, modified_at: self.modified_at, last_synced: self.last_synced, - setup_future_usage: self.setup_future_usage, + setup_future_usage: Some(self.setup_future_usage), client_secret: self.client_secret, - active_attempt_id: self.active_attempt.get_id(), - order_details: self.order_details, + active_attempt_id: self.active_attempt.map(|attempt| attempt.get_id()), + order_details: self + .order_details + .map(|order_details| { + order_details + .into_iter() + .map(|order_detail| order_detail.encode_to_value().map(Secret::new)) + .collect::, _>>() + }) + .transpose() + .change_context(ValidationError::InvalidValue { + message: "Invalid value found for ".to_string(), + })?, allowed_payment_method_types: self.allowed_payment_method_types, connector_metadata: self.connector_metadata, feature_metadata: self.feature_metadata, @@ -1702,7 +1759,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_id: self.payment_link_id, updated_by: self.updated_by, - request_incremental_authorization: self.request_incremental_authorization, + request_incremental_authorization: Some(self.request_incremental_authorization), authorization_count: self.authorization_count, session_expiry: self.session_expiry, request_external_three_ds_authentication: Some( @@ -1712,10 +1769,10 @@ impl behaviour::Conversion for PaymentIntent { customer_details: self.customer_details.map(Encryption::from), billing_address: self.billing_address.map(Encryption::from), shipping_address: self.shipping_address.map(Encryption::from), - capture_method: self.capture_method, + capture_method: Some(self.capture_method), id: self.id, merchant_reference_id: self.merchant_reference_id, - authentication_type: self.authentication_type, + authentication_type: Some(self.authentication_type), prerouting_algorithm: self.prerouting_algorithm, surcharge_amount: amount_details.surcharge_amount, tax_on_surcharge: amount_details.tax_on_surcharge, diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs index 10153fc05e..602c184139 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs @@ -56,5 +56,8 @@ pub struct CalculateTax; #[derive(Debug, Clone)] pub struct SdkSessionUpdate; +#[derive(Debug, Clone)] +pub struct CreateIntent; + #[derive(Debug, Clone)] pub struct PostSessionTokens; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index d599a2caa7..704387ad46 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -526,14 +526,6 @@ pub struct SurchargeDetails { } impl SurchargeDetails { - pub fn is_request_surcharge_matching( - &self, - request_surcharge_details: RequestSurchargeDetails, - ) -> bool { - request_surcharge_details.surcharge_amount == self.surcharge_amount - && request_surcharge_details.tax_amount.unwrap_or_default() - == self.tax_on_surcharge_amount - } pub fn get_total_surcharge_amount(&self) -> MinorUnit { self.surcharge_amount + self.tax_on_surcharge_amount } diff --git a/crates/openapi/Cargo.toml b/crates/openapi/Cargo.toml index 6aae926011..dac22071c3 100644 --- a/crates/openapi/Cargo.toml +++ b/crates/openapi/Cargo.toml @@ -12,7 +12,7 @@ utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order # First party crates api_models = { version = "0.1.0", path = "../api_models", features = ["frm", "payouts", "openapi"] } -common_utils = { version = "0.1.0", path = "../common_utils" } +common_utils = { version = "0.1.0", path = "../common_utils", features = ["logs"] } router_env = { version = "0.1.0", path = "../router_env" } [features] diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 7cecf49496..190e25d71c 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -54,7 +54,7 @@ pub async fn customer_create( state.into_inner(), &req, create_cust_req, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { customers::create_customer(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -92,7 +92,7 @@ pub async fn customer_retrieve( state.into_inner(), &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { customers::retrieve_customer(state, auth.merchant_account, None, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -142,7 +142,7 @@ pub async fn customer_update( state.into_inner(), &req, cust_update_req, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { customers::update_customer( state, auth.merchant_account, @@ -225,7 +225,7 @@ pub async fn list_customer_payment_method_api( state.into_inner(), &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { cards::do_list_customer_pm_fetch_customer_if_not_passed( state, auth.merchant_account, diff --git a/crates/router/src/compatibility/stripe/payment_intents.rs b/crates/router/src/compatibility/stripe/payment_intents.rs index ec3dd9cc95..901ca3c11c 100644 --- a/crates/router/src/compatibility/stripe/payment_intents.rs +++ b/crates/router/src/compatibility/stripe/payment_intents.rs @@ -71,7 +71,7 @@ pub async fn payment_intents_create( state.into_inner(), &req, create_payment_req, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { let eligible_connectors = req.connector.clone(); payments::payments_core::< api_types::Authorize, @@ -452,7 +452,7 @@ pub async fn payment_intents_capture( state.into_inner(), &req, payload, - |state, auth, payload, req_state| { + |state, auth: auth::AuthenticationData, payload, req_state| { payments::payments_core::< api_types::Capture, api_types::PaymentsResponse, @@ -581,7 +581,7 @@ pub async fn payment_intent_list( state.into_inner(), &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payments::list_payments(state, auth.merchant_account, None, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), diff --git a/crates/router/src/compatibility/stripe/refunds.rs b/crates/router/src/compatibility/stripe/refunds.rs index 09585cd9c3..63141f7311 100644 --- a/crates/router/src/compatibility/stripe/refunds.rs +++ b/crates/router/src/compatibility/stripe/refunds.rs @@ -49,7 +49,7 @@ pub async fn refund_create( state.into_inner(), &req, create_refund_req, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { refunds::refund_create_core(state, auth.merchant_account, None, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -93,7 +93,7 @@ pub async fn refund_retrieve_with_gateway_creds( state.into_inner(), &req, refund_request, - |state, auth, refund_request, _| { + |state, auth: auth::AuthenticationData, refund_request, _| { refunds::refund_response_wrapper( state, auth.merchant_account, @@ -136,7 +136,7 @@ pub async fn refund_retrieve( state.into_inner(), &req, refund_request, - |state, auth, refund_request, _| { + |state, auth: auth::AuthenticationData, refund_request, _| { refunds::refund_response_wrapper( state, auth.merchant_account, @@ -177,7 +177,9 @@ pub async fn refund_update( state.into_inner(), &req, create_refund_update_req, - |state, auth, req, _| refunds::refund_update_core(state, auth.merchant_account, req), + |state, auth: auth::AuthenticationData, req, _| { + refunds::refund_update_core(state, auth.merchant_account, req) + }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, )) diff --git a/crates/router/src/compatibility/stripe/setup_intents.rs b/crates/router/src/compatibility/stripe/setup_intents.rs index fbd2fa8120..e5e68fb4a8 100644 --- a/crates/router/src/compatibility/stripe/setup_intents.rs +++ b/crates/router/src/compatibility/stripe/setup_intents.rs @@ -58,7 +58,7 @@ pub async fn setup_intents_create( state.into_inner(), &req, create_payment_req, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::SetupMandate, api_types::PaymentsResponse, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index fed284955b..545f94cfeb 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3450,9 +3450,12 @@ impl ProfileCreateBridge for api::ProfileCreate { .unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64)); let payment_link_config = self.payment_link_config.map(ForeignInto::foreign_into); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -3567,9 +3570,12 @@ impl ProfileCreateBridge for api::ProfileCreate { .unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64)); let payment_link_config = self.payment_link_config.map(ForeignInto::foreign_into); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -3828,9 +3834,12 @@ impl ProfileUpdateBridge for api::ProfileUpdate { }) .transpose()? .map(Secret::new); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -3929,9 +3938,12 @@ impl ProfileUpdateBridge for api::ProfileUpdate { }) .transpose()? .map(Secret::new); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index fc7d717d5f..19fbbf1952 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -211,15 +211,18 @@ impl CustomerCreateBridge for customers::CustomerRequest { ) -> errors::CustomResult { let default_customer_billing_address = self.get_default_customer_billing_address(); let encrypted_customer_billing_address = default_customer_billing_address - .async_map(|billing_address| create_encrypted_data(state, key_store, billing_address)) + .async_map(|billing_address| { + create_encrypted_data(key_state, key_store, billing_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) .attach_printable("Unable to encrypt default customer billing address")?; let default_customer_shipping_address = self.get_default_customer_shipping_address(); - let encrypted_customer_shipping_address = default_customer_shipping_address - .async_map(|shipping_address| create_encrypted_data(state, key_store, shipping_address)) + .async_map(|shipping_address| { + create_encrypted_data(key_state, key_store, shipping_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) @@ -1239,18 +1242,20 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { domain_customer: &'a domain::Customer, ) -> errors::CustomResult { let default_billing_address = self.get_default_customer_billing_address(); - let encrypted_customer_billing_address = default_billing_address - .async_map(|billing_address| create_encrypted_data(state, key_store, billing_address)) + .async_map(|billing_address| { + create_encrypted_data(key_manager_state, key_store, billing_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) .attach_printable("Unable to encrypt default customer billing address")?; let default_shipping_address = self.get_default_customer_shipping_address(); - let encrypted_customer_shipping_address = default_shipping_address - .async_map(|shipping_address| create_encrypted_data(state, key_store, shipping_address)) + .async_map(|shipping_address| { + create_encrypted_data(key_manager_state, key_store, shipping_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 4c72fd6d12..005b5aeba6 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -852,11 +852,11 @@ pub async fn create_payment_method( ) .await .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - + let key_manager_state = state.into(); let payment_method_billing_address: Option>> = req .billing .clone() - .async_map(|billing| cards::create_encrypted_data(state, key_store, billing)) + .async_map(|billing| cards::create_encrypted_data(&key_manager_state, key_store, billing)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -964,11 +964,11 @@ pub async fn payment_method_intent_create( ) .await .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - + let key_manager_state = state.into(); let payment_method_billing_address: Option>> = req .billing .clone() - .async_map(|billing| cards::create_encrypted_data(state, key_store, billing)) + .async_map(|billing| cards::create_encrypted_data(&key_manager_state, key_store, billing)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -1251,9 +1251,9 @@ pub async fn create_pm_additional_data_update( api::PaymentMethodsData::Card(card.clone().into()) } }; - + let key_manager_state = state.into(); let pmd: Encryptable> = - cards::create_encrypted_data(state, key_store, card) + cards::create_encrypted_data(&key_manager_state, key_store, card) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to encrypt Payment method data")?; @@ -1922,7 +1922,7 @@ impl pm_types::SavedPMLPaymentsInfo { let off_session_payment_flag = matches!( payment_intent.setup_future_usage, - Some(common_enums::FutureUsage::OffSession) + common_enums::FutureUsage::OffSession ); let profile_id = &payment_intent.profile_id; diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 5da298815e..8419a600f3 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -663,11 +663,11 @@ pub async fn skip_locker_call_and_migrate_payment_method( let connector_mandate_details = serde_json::to_value(&connector_mandate_details_req) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to parse connector mandate details")?; - + let key_manager_state = (&state).into(); let payment_method_billing_address: Option>> = req .billing .clone() - .async_map(|billing| create_encrypted_data(&state, key_store, billing)) + .async_map(|billing| create_encrypted_data(&key_manager_state, key_store, billing)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -688,7 +688,7 @@ pub async fn skip_locker_call_and_migrate_payment_method( PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())); let payment_method_data_encrypted: Option>> = Some( - create_encrypted_data(&state, key_store, payment_method_card_details) + create_encrypted_data(&key_manager_state, key_store, payment_method_card_details) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to encrypt Payment method card details")?, @@ -821,11 +821,11 @@ pub async fn get_client_secret_or_add_payment_method( let condition = req.card.is_some(); #[cfg(feature = "payouts")] let condition = req.card.is_some() || req.bank_transfer.is_some() || req.wallet.is_some(); - + let key_manager_state = state.into(); let payment_method_billing_address: Option>> = req .billing .clone() - .async_map(|billing| create_encrypted_data(state, key_store, billing)) + .async_map(|billing| create_encrypted_data(&key_manager_state, key_store, billing)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -973,7 +973,7 @@ pub async fn add_payment_method_data( if client_secret_expired { return Err((errors::ApiErrorResponse::ClientSecretExpired).into()); }; - + let key_manager_state = (&state).into(); match pmd { api_models::payment_methods::PaymentMethodCreateData::Card(card) => { helpers::validate_card_expiry(&card.card_exp_month, &card.card_exp_year)?; @@ -1045,10 +1045,9 @@ pub async fn add_payment_method_data( card_type: card_info.as_ref().and_then(|ci| ci.card_type.clone()), saved_to_locker: true, }; - let pm_data_encrypted: Encryptable> = create_encrypted_data( - &state, + &key_manager_state, &key_store, PaymentMethodsData::Card(updated_card), ) @@ -1139,10 +1138,11 @@ pub async fn add_payment_method( let merchant_id = merchant_account.get_id(); let customer_id = req.customer_id.clone().get_required_value("customer_id")?; let payment_method = req.payment_method.get_required_value("payment_method")?; + let key_manager_state = state.into(); let payment_method_billing_address: Option>> = req .billing .clone() - .async_map(|billing| create_encrypted_data(state, key_store, billing)) + .async_map(|billing| create_encrypted_data(&key_manager_state, key_store, billing)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -1316,7 +1316,7 @@ pub async fn add_payment_method( let pm_data_encrypted: Option>> = updated_pmd .async_map(|updated_pmd| { - create_encrypted_data(state, key_store, updated_pmd) + create_encrypted_data(&key_manager_state, key_store, updated_pmd) }) .await .transpose() @@ -1407,10 +1407,10 @@ pub async fn insert_payment_method( .card .clone() .map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))); - + let key_manager_state = state.into(); let pm_data_encrypted: crypto::OptionalEncryptableValue = pm_card_details .clone() - .async_map(|pm_card| create_encrypted_data(state, key_store, pm_card)) + .async_map(|pm_card| create_encrypted_data(&key_manager_state, key_store, pm_card)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -1633,9 +1633,11 @@ pub async fn update_customer_payment_method( let updated_pmd = updated_card .as_ref() .map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))); - + let key_manager_state = (&state).into(); let pm_data_encrypted: Option>> = updated_pmd - .async_map(|updated_pmd| create_encrypted_data(&state, &key_store, updated_pmd)) + .async_map(|updated_pmd| { + create_encrypted_data(&key_manager_state, &key_store, updated_pmd) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -5341,7 +5343,7 @@ pub async fn delete_payment_method( } pub async fn create_encrypted_data( - state: &routes::SessionState, + key_manager_state: &KeyManagerState, key_store: &domain::MerchantKeyStore, data: T, ) -> Result>, error_stack::Report> @@ -5350,7 +5352,6 @@ where { let key = key_store.key.get_inner().peek(); let identifier = Identifier::Merchant(key_store.merchant_id.clone()); - let key_manager_state: KeyManagerState = state.into(); let encoded_data = Encode::encode_to_value(&data) .change_context(errors::StorageError::SerializationFailed) @@ -5359,7 +5360,7 @@ where let secret_data = Secret::<_, masking::WithType>::new(encoded_data); let encrypted_data = domain::types::crypto_operation( - &key_manager_state, + key_manager_state, type_name!(payment_method::PaymentMethod), domain::types::CryptoOperation::Encrypt(secret_data), identifier.clone(), diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 68f318958a..ded1ce6a32 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -673,6 +673,7 @@ where )) } +#[cfg(feature = "v1")] // This function is intended for use when the feature being implemented is not aligned with the // core payment operations. #[allow(clippy::too_many_arguments, clippy::type_complexity)] @@ -873,6 +874,69 @@ where )) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +#[instrument(skip_all, fields(payment_id, merchant_id))] +pub async fn payments_intent_operation_core( + state: &SessionState, + _req_state: ReqState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + operation: Op, + req: Req, + auth_flow: services::AuthFlow, + header_payload: HeaderPayload, +) -> RouterResult<(D, Req, Option)> +where + F: Send + Clone + Sync, + Req: Authenticate + Clone, + Op: Operation + Send + Sync, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, +{ + let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation); + + tracing::Span::current().record("merchant_id", merchant_account.get_id().get_string_repr()); + + let (operation, _validate_result) = operation + .to_validate_request()? + .validate_request(&req, &merchant_account)?; + + let payment_id = id_type::GlobalPaymentId::generate(state.conf.cell_information.id.clone()); + + tracing::Span::current().record("global_payment_id", payment_id.get_string_repr()); + + let operations::GetTrackerResponse { + operation, + mut payment_data, + } = operation + .to_get_tracker()? + .get_trackers( + state, + &payment_id, + &req, + &merchant_account, + &profile, + &key_store, + auth_flow, + &header_payload, + ) + .await?; + + let (_operation, customer) = operation + .to_domain()? + .get_customer_details( + state, + &mut payment_data, + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) + .attach_printable("Failed while fetching/creating customer")?; + Ok((payment_data, req, customer)) +} + #[instrument(skip_all)] #[cfg(feature = "v1")] pub async fn call_decision_manager( @@ -1245,6 +1309,52 @@ where ) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn payments_intent_core( + state: SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + operation: Op, + req: Req, + auth_flow: services::AuthFlow, + header_payload: HeaderPayload, +) -> RouterResponse +where + F: Send + Clone + Sync, + Op: Operation + Send + Sync + Clone, + Req: Debug + Authenticate + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + Res: transformers::ToResponse, +{ + let (payment_data, _req, customer) = payments_intent_operation_core::<_, _, _, _>( + &state, + req_state, + merchant_account, + profile, + key_store, + operation.clone(), + req, + auth_flow, + header_payload.clone(), + ) + .await?; + + Res::generate_response( + payment_data, + customer, + auth_flow, + &state.base_url, + operation, + &state.conf.connector_request_reference_id_config, + None, + None, + header_payload.x_hs_latency, + ) +} + fn is_start_pay(operation: &Op) -> bool { format!("{operation:?}").eq("PaymentStart") } @@ -5963,7 +6073,7 @@ impl OperationSessionGetters for PaymentData { #[cfg(feature = "v2")] fn get_capture_method(&self) -> Option { - self.payment_intent.capture_method + Some(self.payment_intent.capture_method) } } @@ -6044,6 +6154,7 @@ impl OperationSessionSetters for PaymentData { self.mandate_id = Some(mandate_id); } + #[cfg(feature = "v1")] fn set_setup_future_usage_in_payment_intent( &mut self, setup_future_usage: storage_enums::FutureUsage, @@ -6051,6 +6162,14 @@ impl OperationSessionSetters for PaymentData { self.payment_intent.setup_future_usage = Some(setup_future_usage); } + #[cfg(feature = "v2")] + fn set_setup_future_usage_in_payment_intent( + &mut self, + setup_future_usage: storage_enums::FutureUsage, + ) { + self.payment_intent.setup_future_usage = setup_future_usage; + } + #[cfg(feature = "v1")] fn set_straight_through_algorithm_in_payment_attempt( &mut self, @@ -6265,7 +6384,7 @@ impl OperationSessionSetters for PaymentIntentData { &mut self, setup_future_usage: storage_enums::FutureUsage, ) { - self.payment_intent.setup_future_usage = Some(setup_future_usage); + self.payment_intent.setup_future_usage = setup_future_usage; } fn set_connector_in_payment_attempt(&mut self, _connector: Option) { diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index c3a47a740c..7c97516192 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1567,10 +1567,12 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( .or(parsed_customer_data.phone_country_code.clone()), }) .or(temp_customer_data); - + let key_manager_state = state.into(); payment_data.payment_intent.customer_details = raw_customer_details .clone() - .async_map(|customer_details| create_encrypted_data(state, key_store, customer_details)) + .async_map(|customer_details| { + create_encrypted_data(&key_manager_state, key_store, customer_details) + }) .await .transpose() .change_context(errors::StorageError::EncryptionError) @@ -3172,11 +3174,7 @@ pub fn authenticate_client_secret( } else { let current_timestamp = common_utils::date_time::now(); - let session_expiry = payment_intent.session_expiry.unwrap_or( - payment_intent - .created_at - .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), - ); + let session_expiry = payment_intent.session_expiry; fp_utils::when(current_timestamp > session_expiry, || { Err(errors::ApiErrorResponse::ClientSecretExpired) @@ -5927,6 +5925,7 @@ pub async fn config_skip_saving_wallet_at_connector( }) } +#[cfg(feature = "v1")] pub async fn override_setup_future_usage_to_on_session( db: &dyn StorageInterface, payment_data: &mut D, diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index efbe9bc8fc..56b198c538 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -28,6 +28,9 @@ pub mod payments_incremental_authorization; #[cfg(feature = "v1")] pub mod tax_calculation; +#[cfg(feature = "v2")] +pub mod payment_create_intent; + use api_models::enums::FrmSuggestion; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use api_models::routing::RoutableConnectorChoice; @@ -35,6 +38,8 @@ use async_trait::async_trait; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; +#[cfg(feature = "v2")] +pub use self::payment_create_intent::PaymentCreateIntent; pub use self::payment_response::PaymentResponse; #[cfg(feature = "v1")] pub use self::{ @@ -93,6 +98,7 @@ pub trait Operation: Send + std::fmt::Debug { } } +#[cfg(feature = "v1")] #[derive(Clone)] pub struct ValidateResult { pub merchant_id: common_utils::id_type::MerchantId, @@ -101,6 +107,15 @@ pub struct ValidateResult { pub requeue: bool, } +#[cfg(feature = "v2")] +#[derive(Clone)] +pub struct ValidateResult { + pub merchant_id: common_utils::id_type::MerchantId, + pub storage_scheme: enums::MerchantStorageScheme, + pub requeue: bool, +} + +#[cfg(feature = "v1")] #[allow(clippy::type_complexity)] pub trait ValidateRequest { fn validate_request<'b>( @@ -110,6 +125,22 @@ pub trait ValidateRequest { ) -> RouterResult<(BoxedOperation<'b, F, R, D>, ValidateResult)>; } +#[cfg(feature = "v2")] +pub trait ValidateRequest { + fn validate_request<'b>( + &'b self, + request: &R, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult<(BoxedOperation<'b, F, R, D>, ValidateResult)>; +} + +#[cfg(feature = "v2")] +pub struct GetTrackerResponse<'a, F: Clone, R, D> { + pub operation: BoxedOperation<'a, F, R, D>, + pub payment_data: D, +} + +#[cfg(feature = "v1")] pub struct GetTrackerResponse<'a, F: Clone, R, D> { pub operation: BoxedOperation<'a, F, R, D>, pub customer_details: Option, @@ -118,6 +149,7 @@ pub struct GetTrackerResponse<'a, F: Clone, R, D> { pub mandate_type: Option, } +#[cfg(feature = "v1")] #[async_trait] pub trait GetTracker: Send { #[allow(clippy::too_many_arguments)] @@ -133,8 +165,26 @@ pub trait GetTracker: Send { ) -> RouterResult>; } +#[cfg(feature = "v2")] +#[async_trait] +pub trait GetTracker: Send { + #[allow(clippy::too_many_arguments)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &common_utils::id_type::GlobalPaymentId, + request: &R, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + mechant_key_store: &domain::MerchantKeyStore, + auth_flow: services::AuthFlow, + header_payload: &api::HeaderPayload, + ) -> RouterResult>; +} + #[async_trait] pub trait Domain: Send + Sync { + #[cfg(feature = "v1")] /// This will fetch customer details, (this operation is flow specific) async fn get_or_create_customer_details<'a>( &'a self, @@ -145,6 +195,16 @@ pub trait Domain: Send + Sync { storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option), errors::StorageError>; + #[cfg(feature = "v2")] + /// This will fetch customer details, (this operation is flow specific) + async fn get_customer_details<'a>( + &'a self, + state: &SessionState, + payment_data: &mut D, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option), errors::StorageError>; + #[allow(clippy::too_many_arguments)] async fn make_pm_data<'a>( &'a self, @@ -344,13 +404,12 @@ where #[instrument(skip_all)] #[cfg(feature = "v2")] - async fn get_or_create_customer_details<'a>( + async fn get_customer_details<'a>( &'a self, - state: &SessionState, - payment_data: &mut D, - _request: Option, - merchant_key_store: &domain::MerchantKeyStore, - storage_scheme: enums::MerchantStorageScheme, + _state: &SessionState, + _payment_data: &mut D, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsRetrieveRequest, D>, @@ -449,13 +508,12 @@ where #[instrument(skip_all)] #[cfg(feature = "v2")] - async fn get_or_create_customer_details<'a>( + async fn get_customer_details<'a>( &'a self, - state: &SessionState, - payment_data: &mut D, - _request: Option, - merchant_key_store: &domain::MerchantKeyStore, - storage_scheme: enums::MerchantStorageScheme, + _state: &SessionState, + _payment_data: &mut D, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( BoxedOperation<'a, F, api::PaymentsCaptureRequest, D>, @@ -554,12 +612,11 @@ where #[instrument(skip_all)] #[cfg(feature = "v2")] - async fn get_or_create_customer_details<'a>( + async fn get_customer_details<'a>( &'a self, - state: &SessionState, - payment_data: &mut D, - _request: Option, - merchant_key_store: &domain::MerchantKeyStore, + _state: &SessionState, + _payment_data: &mut D, + _merchant_key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( @@ -617,6 +674,7 @@ impl &'a Op: Operation, { + #[cfg(feature = "v1")] #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, @@ -635,6 +693,24 @@ where Ok((Box::new(self), None)) } + #[cfg(feature = "v2")] + #[instrument(skip_all)] + async fn get_customer_details<'a>( + &'a self, + _state: &SessionState, + _payment_data: &mut D, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult< + ( + BoxedOperation<'a, F, api::PaymentsRejectRequest, D>, + Option, + ), + errors::StorageError, + > { + Ok((Box::new(self), None)) + } + #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 5dccea54f5..9ec2438ddb 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1361,8 +1361,11 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen ); let billing_address = payment_data.address.get_payment_billing(); + let key_manager_state = state.into(); let billing_details = billing_address - .async_map(|billing_details| create_encrypted_data(state, key_store, billing_details)) + .async_map(|billing_details| { + create_encrypted_data(&key_manager_state, key_store, billing_details) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -1370,7 +1373,9 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen let shipping_address = payment_data.address.get_shipping(); let shipping_details = shipping_address - .async_map(|shipping_details| create_encrypted_data(state, key_store, shipping_details)) + .async_map(|shipping_details| { + create_encrypted_data(&key_manager_state, key_store, shipping_details) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index a715aadf33..779b2d29c0 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -39,10 +39,7 @@ use crate::{ utils as core_utils, }, db::StorageInterface, - routes::{ - app::{ReqState, SessionStateInfo}, - SessionState, - }, + routes::{app::ReqState, SessionState}, services, types::{ self, @@ -844,14 +841,13 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen let raw_customer_details = customer .map(|customer| CustomerData::foreign_try_from(customer.clone())) .transpose()?; - let session_state = state.session_state(); - + let key_manager_state = state.into(); // Updation of Customer Details for the cases where both customer_id and specific customer // details are provided in Payment Create Request let customer_details = raw_customer_details .clone() .async_map(|customer_details| { - create_encrypted_data(&session_state, key_store, customer_details) + create_encrypted_data(&key_manager_state, key_store, customer_details) }) .await .transpose() diff --git a/crates/router/src/core/payments/operations/payment_create_intent.rs b/crates/router/src/core/payments/operations/payment_create_intent.rs new file mode 100644 index 0000000000..c0b66b4446 --- /dev/null +++ b/crates/router/src/core/payments/operations/payment_create_intent.rs @@ -0,0 +1,312 @@ +use std::marker::PhantomData; + +use api_models::{enums::FrmSuggestion, payments::PaymentsCreateIntentRequest}; +use async_trait::async_trait; +use common_utils::{ + errors::CustomResult, + ext_traits::{AsyncExt, ValueExt}, +}; +use error_stack::ResultExt; +use router_env::{instrument, tracing}; + +use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; +use crate::{ + core::{ + errors::{self, RouterResult, StorageErrorExt}, + payments::{self, cards::create_encrypted_data, helpers, operations}, + }, + routes::{app::ReqState, SessionState}, + services, + types::{ + api, domain, + storage::{self, enums}, + }, +}; + +#[derive(Debug, Clone, Copy)] +pub struct PaymentCreateIntent; + +impl Operation for &PaymentCreateIntent { + type Data = payments::PaymentIntentData; + fn to_validate_request( + &self, + ) -> RouterResult< + &(dyn ValidateRequest + Send + Sync), + > { + Ok(*self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + { + Ok(*self) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Ok(*self) + } + fn to_update_tracker( + &self, + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + { + Ok(*self) + } +} + +impl Operation for PaymentCreateIntent { + type Data = payments::PaymentIntentData; + fn to_validate_request( + &self, + ) -> RouterResult< + &(dyn ValidateRequest + Send + Sync), + > { + Ok(self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + { + Ok(self) + } + fn to_domain(&self) -> RouterResult<&dyn Domain> { + Ok(self) + } + fn to_update_tracker( + &self, + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + { + Ok(self) + } +} + +type PaymentsCreateIntentOperation<'b, F> = + BoxedOperation<'b, F, PaymentsCreateIntentRequest, payments::PaymentIntentData>; + +#[async_trait] +impl GetTracker, PaymentsCreateIntentRequest> + for PaymentCreateIntent +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &common_utils::id_type::GlobalPaymentId, + request: &PaymentsCreateIntentRequest, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + _auth_flow: services::AuthFlow, + _header_payload: &api::HeaderPayload, + ) -> RouterResult< + operations::GetTrackerResponse< + 'a, + F, + PaymentsCreateIntentRequest, + payments::PaymentIntentData, + >, + > { + let db = &*state.store; + let key_manager_state = &state.into(); + + let storage_scheme = merchant_account.storage_scheme; + // Derivation of directly supplied Billing Address data in our Payment Create Request + // Encrypting our Billing Address Details to be stored in Payment Intent + let billing_address = request + .billing + .clone() + .async_map(|billing_details| { + create_encrypted_data(key_manager_state, key_store, billing_details) + }) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt billing details")? + .map(|encrypted_value| { + encrypted_value.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to deserialize decrypted value to Address")?; + + // Derivation of directly supplied Shipping Address data in our Payment Create Request + // Encrypting our Shipping Address Details to be stored in Payment Intent + let shipping_address = request + .shipping + .clone() + .async_map(|shipping_details| { + create_encrypted_data(key_manager_state, key_store, shipping_details) + }) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt shipping details")? + .map(|encrypted_value| { + encrypted_value.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to deserialize decrypted value to Address")?; + let payment_intent_domain = + hyperswitch_domain_models::payments::PaymentIntent::create_domain_model_from_request( + payment_id, + merchant_account, + profile, + request.clone(), + billing_address, + shipping_address, + ) + .await?; + + let payment_intent = db + .insert_payment_intent( + key_manager_state, + payment_intent_domain, + key_store, + storage_scheme, + ) + .await + .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError { + message: format!( + "Payment Intent with payment_id {} already exists", + payment_id.get_string_repr() + ), + }) + .attach_printable("failed while inserting new payment intent")?; + + let payment_data = payments::PaymentIntentData { + flow: PhantomData, + payment_intent, + }; + + let get_trackers_response = operations::GetTrackerResponse { + operation: Box::new(self), + payment_data, + }; + + Ok(get_trackers_response) + } +} + +#[async_trait] +impl UpdateTracker, PaymentsCreateIntentRequest> + for PaymentCreateIntent +{ + #[instrument(skip_all)] + async fn update_trackers<'b>( + &'b self, + _state: &'b SessionState, + _req_state: ReqState, + payment_data: payments::PaymentIntentData, + _customer: Option, + _storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, + _key_store: &domain::MerchantKeyStore, + _frm_suggestion: Option, + _header_payload: api::HeaderPayload, + ) -> RouterResult<( + PaymentsCreateIntentOperation<'b, F>, + payments::PaymentIntentData, + )> + where + F: 'b + Send, + { + Ok((Box::new(self), payment_data)) + } +} + +impl + ValidateRequest> + for PaymentCreateIntent +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + _request: &PaymentsCreateIntentRequest, + merchant_account: &'a domain::MerchantAccount, + ) -> RouterResult<( + PaymentsCreateIntentOperation<'b, F>, + operations::ValidateResult, + )> { + Ok(( + Box::new(self), + operations::ValidateResult { + merchant_id: merchant_account.get_id().to_owned(), + storage_scheme: merchant_account.storage_scheme, + requeue: false, + }, + )) + } +} + +#[async_trait] +impl Domain> + for PaymentCreateIntent +{ + #[instrument(skip_all)] + async fn get_customer_details<'a>( + &'a self, + state: &SessionState, + payment_data: &mut payments::PaymentIntentData, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult< + ( + BoxedOperation<'a, F, PaymentsCreateIntentRequest, payments::PaymentIntentData>, + Option, + ), + errors::StorageError, + > { + // validate customer_id if sent in the request + if let Some(id) = payment_data.payment_intent.customer_id.clone() { + state + .store + .find_customer_by_global_id( + &state.into(), + id.get_string_repr(), + &payment_data.payment_intent.merchant_id, + merchant_key_store, + storage_scheme, + ) + .await?; + } + Ok((Box::new(self), None)) + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a SessionState, + _payment_data: &mut payments::PaymentIntentData, + _storage_scheme: enums::MerchantStorageScheme, + _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, + _business_profile: &domain::Profile, + ) -> RouterResult<( + PaymentsCreateIntentOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } + + async fn get_connector<'a>( + &'a self, + _merchant_account: &domain::MerchantAccount, + state: &SessionState, + _request: &PaymentsCreateIntentRequest, + _payment_intent: &storage::PaymentIntent, + _merchant_key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + helpers::get_connector_default(state, None).await + } + + #[instrument(skip_all)] + async fn guard_payment_against_blocklist<'a>( + &'a self, + _state: &SessionState, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _payment_data: &mut payments::PaymentIntentData, + ) -> CustomResult { + Ok(false) + } +} diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 2257c42f44..58f591e5c1 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -640,6 +640,8 @@ impl PostUpdateTracker, types::SdkPaymentsSessionUpd .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("connector not found")?; + let key_manager_state = db.into(); + // For PayPal, if we call TaxJar for tax calculation, we need to call the connector again to update the order amount so that we can confirm the updated amount and order details. Therefore, we will store the required changes in the database during the post_update_tracker call. if payment_data.should_update_in_post_update_tracker() { match router_data.response.clone() { @@ -653,7 +655,11 @@ impl PostUpdateTracker, types::SdkPaymentsSessionUpd let shipping_details = shipping_address .clone() .async_map(|shipping_details| { - create_encrypted_data(db, key_store, shipping_details) + create_encrypted_data( + &key_manager_state, + key_store, + shipping_details, + ) }) .await .transpose() diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 45a358ab2d..c1eae806ce 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -847,11 +847,13 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .payment_intent .statement_descriptor_suffix .clone(); - + let key_manager_state = state.into(); let billing_details = payment_data .address .get_payment_billing() - .async_map(|billing_details| create_encrypted_data(state, key_store, billing_details)) + .async_map(|billing_details| { + create_encrypted_data(&key_manager_state, key_store, billing_details) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -860,7 +862,9 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen let shipping_details = payment_data .address .get_shipping() - .async_map(|shipping_details| create_encrypted_data(state, key_store, shipping_details)) + .async_map(|shipping_details| { + create_encrypted_data(&key_manager_state, key_store, shipping_details) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payments/operations/tax_calculation.rs b/crates/router/src/core/payments/operations/tax_calculation.rs index d72d25cdd5..c72dcb6f56 100644 --- a/crates/router/src/core/payments/operations/tax_calculation.rs +++ b/crates/router/src/core/payments/operations/tax_calculation.rs @@ -389,11 +389,12 @@ impl UpdateTracker, api::PaymentsDynamicTaxCalculati .tax_data .clone() .map(|tax_data| tax_data.shipping_details); + let key_manager_state = state.into(); let shipping_details = shipping_address .clone() .async_map(|shipping_details| { - create_encrypted_data(state, key_store, shipping_details) + create_encrypted_data(&key_manager_state, key_store, shipping_details) }) .await .transpose() diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index ee75536e7c..213a9211c0 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -277,10 +277,12 @@ where let pm_card_details = resp.card.as_ref().map(|card| { PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())) }); - + let key_manager_state = state.into(); let pm_data_encrypted: Option>> = pm_card_details - .async_map(|pm_card| create_encrypted_data(state, key_store, pm_card)) + .async_map(|pm_card| { + create_encrypted_data(&key_manager_state, key_store, pm_card) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -295,7 +297,9 @@ where }); pm_token_details - .async_map(|pm_card| create_encrypted_data(state, key_store, pm_card)) + .async_map(|pm_card| { + create_encrypted_data(&key_manager_state, key_store, pm_card) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -307,7 +311,9 @@ where let encrypted_payment_method_billing_address: Option< Encryptable>, > = payment_method_billing_address - .async_map(|address| create_encrypted_data(state, key_store, address.clone())) + .async_map(|address| { + create_encrypted_data(&key_manager_state, key_store, address.clone()) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -627,7 +633,9 @@ where let pm_data_encrypted: Option< Encryptable>, > = updated_pmd - .async_map(|pmd| create_encrypted_data(state, key_store, pmd)) + .async_map(|pmd| { + create_encrypted_data(&key_manager_state, key_store, pmd) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 72e6c6b997..63847b65f5 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -516,6 +516,79 @@ where } } +#[cfg(feature = "v2")] +impl ToResponse for api::PaymentsCreateIntentResponse +where + F: Clone, + Op: Debug, + D: OperationSessionGetters, +{ + #[allow(clippy::too_many_arguments)] + fn generate_response( + payment_data: D, + _customer: Option, + _auth_flow: services::AuthFlow, + _base_url: &str, + operation: Op, + _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + _connector_http_status_code: Option, + _external_latency: Option, + _is_latency_header_enabled: Option, + ) -> RouterResponse { + let payment_intent = payment_data.get_payment_intent(); + Ok(services::ApplicationResponse::JsonWithHeaders(( + Self { + id: payment_intent.id.clone(), + amount_details: api_models::payments::AmountDetails::foreign_from( + payment_intent.amount_details.clone(), + ), + client_secret: payment_intent.client_secret.clone(), + merchant_reference_id: payment_intent.merchant_reference_id.clone(), + routing_algorithm_id: payment_intent.routing_algorithm_id.clone(), + capture_method: payment_intent.capture_method, + authentication_type: payment_intent.authentication_type, + billing: payment_intent + .billing_address + .clone() + .map(|billing| billing.into_inner().expose()), + shipping: payment_intent + .shipping_address + .clone() + .map(|shipping| shipping.into_inner().expose()), + customer_id: payment_intent.customer_id.clone(), + customer_present: payment_intent.customer_present.clone(), + description: payment_intent.description.clone(), + return_url: payment_intent.return_url.clone(), + setup_future_usage: payment_intent.setup_future_usage, + apply_mit_exemption: payment_intent.apply_mit_exemption.clone(), + statement_descriptor: payment_intent.statement_descriptor.clone(), + order_details: payment_intent.order_details.clone().map(|order_details| { + order_details + .into_iter() + .map(|order_detail| order_detail.expose()) + .collect() + }), + allowed_payment_method_types: payment_intent.allowed_payment_method_types.clone(), + metadata: payment_intent.metadata.clone(), + connector_metadata: payment_intent.connector_metadata.clone(), + feature_metadata: payment_intent.feature_metadata.clone(), + payment_link_enabled: payment_intent.enable_payment_link.clone(), + payment_link_config: payment_intent + .payment_link_config + .clone() + .map(ForeignFrom::foreign_from), + request_incremental_authorization: payment_intent.request_incremental_authorization, + expires_on: payment_intent.session_expiry, + frm_metadata: payment_intent.frm_metadata.clone(), + request_external_three_ds_authentication: payment_intent + .request_external_three_ds_authentication + .clone(), + }, + vec![], + ))) + } +} + #[cfg(feature = "v1")] impl ToResponse for api::PaymentsPostSessionTokensResponse where @@ -2168,6 +2241,17 @@ impl TryFrom> for types::PaymentsRejectDa }) } } + +#[cfg(feature = "v2")] +impl TryFrom> for types::PaymentsSessionData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + todo!(); + } +} + +#[cfg(feature = "v1")] impl TryFrom> for types::PaymentsSessionData { type Error = error_stack::Report; @@ -2529,3 +2613,220 @@ impl ForeignFrom for router_request_types::CustomerDetails { } } } + +#[cfg(feature = "v2")] +impl ForeignFrom + for hyperswitch_domain_models::payments::AmountDetails +{ + fn foreign_from(amount_details: api_models::payments::AmountDetails) -> Self { + Self { + order_amount: amount_details.order_amount().into(), + currency: amount_details.currency(), + shipping_cost: amount_details.shipping_cost(), + tax_details: amount_details.order_tax_amount().map(|order_tax_amount| { + diesel_models::TaxDetails { + default: Some(diesel_models::DefaultTax { order_tax_amount }), + payment_method_type: None, + } + }), + skip_external_tax_calculation: + hyperswitch_domain_models::payments::TaxCalculationOverride::foreign_from( + amount_details.skip_external_tax_calculation(), + ), + skip_surcharge_calculation: + hyperswitch_domain_models::payments::SurchargeCalculationOverride::foreign_from( + amount_details.skip_surcharge_calculation(), + ), + surcharge_amount: amount_details.surcharge_amount(), + tax_on_surcharge: amount_details.tax_on_surcharge(), + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for api_models::payments::AmountDetails +{ + fn foreign_from(amount_details: hyperswitch_domain_models::payments::AmountDetails) -> Self { + Self::new(api_models::payments::AmountDetailsSetter { + order_amount: amount_details.order_amount.into(), + currency: amount_details.currency, + shipping_cost: amount_details.shipping_cost, + order_tax_amount: amount_details.tax_details.and_then(|tax_details| { + tax_details.default.map(|default| default.order_tax_amount) + }), + skip_external_tax_calculation: common_enums::TaxCalculationOverride::foreign_from( + amount_details.skip_external_tax_calculation, + ), + skip_surcharge_calculation: common_enums::SurchargeCalculationOverride::foreign_from( + amount_details.skip_surcharge_calculation, + ), + surcharge_amount: amount_details.surcharge_amount, + tax_on_surcharge: amount_details.tax_on_surcharge, + }) + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for hyperswitch_domain_models::payments::TaxCalculationOverride +{ + fn foreign_from(tax_calculation_override: common_enums::TaxCalculationOverride) -> Self { + match tax_calculation_override { + common_enums::TaxCalculationOverride::Calculate => Self::Calculate, + common_enums::TaxCalculationOverride::Skip => Self::Skip, + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for common_enums::TaxCalculationOverride +{ + fn foreign_from( + tax_calculation_override: hyperswitch_domain_models::payments::TaxCalculationOverride, + ) -> Self { + match tax_calculation_override { + hyperswitch_domain_models::payments::TaxCalculationOverride::Calculate => { + Self::Calculate + } + hyperswitch_domain_models::payments::TaxCalculationOverride::Skip => Self::Skip, + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for hyperswitch_domain_models::payments::SurchargeCalculationOverride +{ + fn foreign_from( + surcharge_calculation_override: common_enums::SurchargeCalculationOverride, + ) -> Self { + match surcharge_calculation_override { + common_enums::SurchargeCalculationOverride::Calculate => Self::Calculate, + common_enums::SurchargeCalculationOverride::Skip => Self::Skip, + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for common_enums::SurchargeCalculationOverride +{ + fn foreign_from( + surcharge_calculation_override: hyperswitch_domain_models::payments::SurchargeCalculationOverride, + ) -> Self { + match surcharge_calculation_override { + hyperswitch_domain_models::payments::SurchargeCalculationOverride::Calculate => { + Self::Calculate + } + hyperswitch_domain_models::payments::SurchargeCalculationOverride::Skip => Self::Skip, + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for diesel_models::PaymentLinkConfigRequestForPayments +{ + fn foreign_from(config: api_models::admin::PaymentLinkConfigRequest) -> Self { + Self { + theme: config.theme, + logo: config.logo, + seller_name: config.seller_name, + sdk_layout: config.sdk_layout, + display_sdk_only: config.display_sdk_only, + enabled_saved_payment_method: config.enabled_saved_payment_method, + transaction_details: config.transaction_details.map(|transaction_details| { + transaction_details + .iter() + .map(|details| { + diesel_models::PaymentLinkTransactionDetails::foreign_from(details.clone()) + }) + .collect() + }), + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for diesel_models::PaymentLinkTransactionDetails +{ + fn foreign_from(from: api_models::admin::PaymentLinkTransactionDetails) -> Self { + Self { + key: from.key, + value: from.value, + ui_configuration: from + .ui_configuration + .map(diesel_models::TransactionDetailsUiConfiguration::foreign_from), + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for diesel_models::TransactionDetailsUiConfiguration +{ + fn foreign_from(from: api_models::admin::TransactionDetailsUiConfiguration) -> Self { + Self { + position: from.position, + is_key_bold: from.is_key_bold, + is_value_bold: from.is_value_bold, + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for api_models::admin::PaymentLinkConfigRequest +{ + fn foreign_from(config: diesel_models::PaymentLinkConfigRequestForPayments) -> Self { + Self { + theme: config.theme, + logo: config.logo, + seller_name: config.seller_name, + sdk_layout: config.sdk_layout, + display_sdk_only: config.display_sdk_only, + enabled_saved_payment_method: config.enabled_saved_payment_method, + transaction_details: config.transaction_details.map(|transaction_details| { + transaction_details + .iter() + .map(|details| { + api_models::admin::PaymentLinkTransactionDetails::foreign_from( + details.clone(), + ) + }) + .collect() + }), + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for api_models::admin::PaymentLinkTransactionDetails +{ + fn foreign_from(from: diesel_models::PaymentLinkTransactionDetails) -> Self { + Self { + key: from.key, + value: from.value, + ui_configuration: from + .ui_configuration + .map(api_models::admin::TransactionDetailsUiConfiguration::foreign_from), + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for api_models::admin::TransactionDetailsUiConfiguration +{ + fn foreign_from(from: diesel_models::TransactionDetailsUiConfiguration) -> Self { + Self { + position: from.position, + is_key_bold: from.is_key_bold, + is_value_bold: from.is_value_bold, + } + } +} diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 7417947cac..46f4590544 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -208,6 +208,7 @@ pub async fn save_payout_data_to_locker( key_store: &domain::MerchantKeyStore, ) -> RouterResult<()> { let payouts = &payout_data.payouts; + let key_manager_state = state.into(); let (mut locker_req, card_details, bank_details, wallet_details, payment_method_type) = match payout_method_data { payouts::PayoutMethodData::Card(card) => { @@ -485,7 +486,7 @@ pub async fn save_payout_data_to_locker( }); ( Some( - cards::create_encrypted_data(state, key_store, pm_data) + cards::create_encrypted_data(&key_manager_state, key_store, pm_data) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to encrypt customer details")?, diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index a37a91a55f..2c3f76c88a 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -373,7 +373,7 @@ async fn store_bank_details_in_payment_methods( payment_methods::PaymentMethodDataBankCreds, ), > = HashMap::new(); - + let key_manager_state = (&state).into(); for pm in payment_methods { if pm.payment_method == Some(enums::PaymentMethod::BankDebit) && pm.payment_method_data.is_some() @@ -480,8 +480,9 @@ async fn store_bank_details_in_payment_methods( ); let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd); + let encrypted_data = - cards::create_encrypted_data(&state, &key_store, payment_method_data) + cards::create_encrypted_data(&key_manager_state, &key_store, payment_method_data) .await .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Unable to encrypt customer details")?; @@ -493,11 +494,14 @@ async fn store_bank_details_in_payment_methods( update_entries.push((pm.clone(), pm_update)); } else { let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd); - let encrypted_data = - cards::create_encrypted_data(&state, &key_store, Some(payment_method_data)) - .await - .change_context(ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt customer details")?; + let encrypted_data = cards::create_encrypted_data( + &key_manager_state, + &key_store, + Some(payment_method_data), + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt customer details")?; #[cfg(all( any(feature = "v1", feature = "v2"), diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index c1eac13832..bb85230022 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -74,6 +74,7 @@ pub mod headers { pub const X_DATE: &str = "X-Date"; pub const X_WEBHOOK_SIGNATURE: &str = "X-Webhook-Signature-512"; pub const X_REQUEST_ID: &str = "X-Request-Id"; + pub const X_PROFILE_ID: &str = "X-Profile-Id"; pub const STRIPE_COMPATIBLE_WEBHOOK_SIGNATURE: &str = "Stripe-Signature"; pub const STRIPE_COMPATIBLE_CONNECT_ACCOUNT: &str = "Stripe-Account"; pub const X_CLIENT_VERSION: &str = "X-Client-Version"; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 560aa0d34e..274ed6a5cc 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -517,6 +517,8 @@ pub struct Payments; impl Payments { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/v2/payments").app_data(web::Data::new(state)); + route = route + .service(web::resource("/create-intent").route(web::post().to(payments_create_intent))); route = route .service( web::resource("/{payment_id}/saved_payment_methods") diff --git a/crates/router/src/routes/currency.rs b/crates/router/src/routes/currency.rs index 80b74b5c0c..4d800ddf1a 100644 --- a/crates/router/src/routes/currency.rs +++ b/crates/router/src/routes/currency.rs @@ -39,7 +39,7 @@ pub async fn convert_forex( state.clone(), &req, (), - |state, _, _, _| { + |state, _: auth::AuthenticationData, _, _| { currency::convert_forex( state, amount.get_amount_as_i64(), diff --git a/crates/router/src/routes/customers.rs b/crates/router/src/routes/customers.rs index 66ff1f8e32..536bca10f3 100644 --- a/crates/router/src/routes/customers.rs +++ b/crates/router/src/routes/customers.rs @@ -23,7 +23,9 @@ pub async fn customers_create( state, &req, json_payload.into_inner(), - |state, auth, req, _| create_customer(state, auth.merchant_account, auth.key_store, req), + |state, auth: auth::AuthenticationData, req, _| { + create_customer(state, auth.merchant_account, auth.key_store, req) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -68,7 +70,7 @@ pub async fn customers_retrieve( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { retrieve_customer( state, auth.merchant_account, @@ -111,7 +113,9 @@ pub async fn customers_retrieve( state, &req, payload, - |state, auth, req, _| retrieve_customer(state, auth.merchant_account, auth.key_store, req), + |state, auth: auth::AuthenticationData, req, _| { + retrieve_customer(state, auth.merchant_account, auth.key_store, req) + }, &*auth, api_locking::LockAction::NotApplicable, )) @@ -132,7 +136,7 @@ pub async fn customers_list( state, &req, payload, - |state, auth, request, _| { + |state, auth: auth::AuthenticationData, request, _| { list_customers( state, auth.merchant_account.get_id().to_owned(), @@ -171,7 +175,7 @@ pub async fn customers_update( state, &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { update_customer( state, auth.merchant_account, @@ -209,7 +213,7 @@ pub async fn customers_update( state, &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { update_customer( state, auth.merchant_account, @@ -246,7 +250,9 @@ pub async fn customers_delete( state, &req, payload, - |state, auth, req, _| delete_customer(state, auth.merchant_account, req, auth.key_store), + |state, auth: auth::AuthenticationData, req, _| { + delete_customer(state, auth.merchant_account, req, auth.key_store) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -278,7 +284,9 @@ pub async fn customers_delete( state, &req, payload, - |state, auth, req, _| delete_customer(state, auth.merchant_account, req, auth.key_store), + |state, auth: auth::AuthenticationData, req, _| { + delete_customer(state, auth.merchant_account, req, auth.key_store) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -309,7 +317,7 @@ pub async fn get_customer_mandates( state, &req, customer_id, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { crate::core::mandate::get_customer_mandates( state, auth.merchant_account, diff --git a/crates/router/src/routes/disputes.rs b/crates/router/src/routes/disputes.rs index 1a8c4410a5..22c1e3f198 100644 --- a/crates/router/src/routes/disputes.rs +++ b/crates/router/src/routes/disputes.rs @@ -44,7 +44,7 @@ pub async fn retrieve_dispute( state, &req, dispute_id, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::retrieve_dispute(state, auth.merchant_account, auth.profile_id, req) }, auth::auth_type( @@ -96,7 +96,7 @@ pub async fn retrieve_disputes_list( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::retrieve_disputes_list(state, auth.merchant_account, None, req) }, auth::auth_type( @@ -149,7 +149,7 @@ pub async fn retrieve_disputes_list_profile( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::retrieve_disputes_list( state, auth.merchant_account, @@ -189,7 +189,9 @@ pub async fn get_disputes_filters(state: web::Data, req: HttpRequest) state, &req, (), - |state, auth, _, _| disputes::get_filters_for_disputes(state, auth.merchant_account, None), + |state, auth: auth::AuthenticationData, _, _| { + disputes::get_filters_for_disputes(state, auth.merchant_account, None) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -225,7 +227,7 @@ pub async fn get_disputes_filters_profile( state, &req, (), - |state, auth, _, _| { + |state, auth: auth::AuthenticationData, _, _| { disputes::get_filters_for_disputes( state, auth.merchant_account, @@ -275,7 +277,7 @@ pub async fn accept_dispute( state, &req, dispute_id, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::accept_dispute( state, auth.merchant_account, @@ -321,7 +323,7 @@ pub async fn submit_dispute_evidence( state, &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::submit_evidence( state, auth.merchant_account, @@ -375,7 +377,7 @@ pub async fn attach_dispute_evidence( state, &req, attach_evidence_request, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::attach_evidence( state, auth.merchant_account, @@ -427,7 +429,7 @@ pub async fn retrieve_dispute_evidence( state, &req, dispute_id, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::retrieve_dispute_evidence(state, auth.merchant_account, auth.profile_id, req) }, auth::auth_type( @@ -470,7 +472,9 @@ pub async fn delete_dispute_evidence( state, &req, json_payload.into_inner(), - |state, auth, req, _| disputes::delete_evidence(state, auth.merchant_account, req), + |state, auth: auth::AuthenticationData, req, _| { + disputes::delete_evidence(state, auth.merchant_account, req) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -498,7 +502,7 @@ pub async fn get_disputes_aggregate( state, &req, query_param, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::get_aggregates_for_disputes(state, auth.merchant_account, None, req) }, auth::auth_type( @@ -528,7 +532,7 @@ pub async fn get_disputes_aggregate_profile( state, &req, query_param, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { disputes::get_aggregates_for_disputes( state, auth.merchant_account, diff --git a/crates/router/src/routes/ephemeral_key.rs b/crates/router/src/routes/ephemeral_key.rs index cec7439940..dd11bee24b 100644 --- a/crates/router/src/routes/ephemeral_key.rs +++ b/crates/router/src/routes/ephemeral_key.rs @@ -26,7 +26,7 @@ pub async fn ephemeral_key_create( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { helpers::make_ephemeral_key( state, req.get_merchant_reference_id(), @@ -53,7 +53,7 @@ pub async fn ephemeral_key_delete( state, &req, payload, - |state, _, req, _| helpers::delete_ephemeral_key(state, req), + |state, _: auth::AuthenticationData, req, _| helpers::delete_ephemeral_key(state, req), &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, ) diff --git a/crates/router/src/routes/files.rs b/crates/router/src/routes/files.rs index 028b00a6a6..2ebb1176ae 100644 --- a/crates/router/src/routes/files.rs +++ b/crates/router/src/routes/files.rs @@ -44,7 +44,9 @@ pub async fn files_create( state, &req, create_file_request, - |state, auth, req, _| files_create_core(state, auth.merchant_account, auth.key_store, req), + |state, auth: auth::AuthenticationData, req, _| { + files_create_core(state, auth.merchant_account, auth.key_store, req) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::DashboardNoPermissionAuth, @@ -86,7 +88,9 @@ pub async fn files_delete( state, &req, file_id, - |state, auth, req, _| files_delete_core(state, auth.merchant_account, req), + |state, auth: auth::AuthenticationData, req, _| { + files_delete_core(state, auth.merchant_account, req) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::DashboardNoPermissionAuth, @@ -128,7 +132,7 @@ pub async fn files_retrieve( state, &req, file_id, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { files_retrieve_core(state, auth.merchant_account, auth.key_store, req) }, auth::auth_type( diff --git a/crates/router/src/routes/fraud_check.rs b/crates/router/src/routes/fraud_check.rs index f51becf251..5e7b7f3ef6 100644 --- a/crates/router/src/routes/fraud_check.rs +++ b/crates/router/src/routes/fraud_check.rs @@ -19,7 +19,7 @@ pub async fn frm_fulfillment( state.clone(), &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: services::authentication::AuthenticationData, req, _| { frm_core::frm_fulfillment_core(state, auth.merchant_account, auth.key_store, req) }, &services::authentication::ApiKeyAuth, diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index d47ee3caec..cb16d6e050 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -137,6 +137,7 @@ impl From for ApiIdentifier { | Flow::PaymentsCompleteAuthorize | Flow::PaymentsManualUpdate | Flow::SessionUpdateTaxCalculation + | Flow::PaymentsCreateIntent | Flow::PaymentsPostSessionTokens => Self::Payments, Flow::PayoutsCreate diff --git a/crates/router/src/routes/mandates.rs b/crates/router/src/routes/mandates.rs index 4435c59b42..c0ccbfa9f8 100644 --- a/crates/router/src/routes/mandates.rs +++ b/crates/router/src/routes/mandates.rs @@ -42,7 +42,7 @@ pub async fn get_mandate( state, &req, mandate_id, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { mandate::get_mandate(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -67,7 +67,7 @@ pub async fn revoke_mandate( state, &req, mandate_id, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { mandate::revoke_mandate(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -111,7 +111,7 @@ pub async fn retrieve_mandates_list( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { mandate::retrieve_mandates_list(state, auth.merchant_account, auth.key_store, req) }, auth::auth_type( diff --git a/crates/router/src/routes/payment_link.rs b/crates/router/src/routes/payment_link.rs index be6ef9009b..783566335b 100644 --- a/crates/router/src/routes/payment_link.rs +++ b/crates/router/src/routes/payment_link.rs @@ -72,7 +72,7 @@ pub async fn initiate_payment_link( state, &req, payload.clone(), - |state, auth, _, _| { + |state, auth: auth::AuthenticationData, _, _| { initiate_payment_link_flow( state, auth.merchant_account, @@ -108,7 +108,7 @@ pub async fn initiate_secure_payment_link( state, &req, payload.clone(), - |state, auth, _, _| { + |state, auth: auth::AuthenticationData, _, _| { initiate_secure_payment_link_flow( state, auth.merchant_account, @@ -160,7 +160,9 @@ pub async fn payments_link_list( state, &req, payload, - |state, auth, payload, _| list_payment_link(state, auth.merchant_account, payload), + |state, auth: auth::AuthenticationData, payload, _| { + list_payment_link(state, auth.merchant_account, payload) + }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, )) @@ -188,7 +190,7 @@ pub async fn payment_link_status( state, &req, payload.clone(), - |state, auth, _, _| { + |state, auth: auth::AuthenticationData, _, _| { get_payment_link_status( state, auth.merchant_account, diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 77c072448a..8d95ee64ad 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -20,8 +20,8 @@ use crate::core::payment_methods::{ }; use crate::{ core::{ - api_locking, errors, - errors::utils::StorageErrorExt, + api_locking, + errors::{self, utils::StorageErrorExt}, payment_methods::{self as payment_methods_routes, cards}, }, services::{api, authentication as auth, authorization::permissions::Permission}, @@ -58,7 +58,7 @@ pub async fn create_payment_method_api( state, &req, json_payload.into_inner(), - |state, auth, req, _| async move { + |state, auth: auth::AuthenticationData, req, _| async move { Box::pin(cards::get_client_secret_or_add_payment_method( &state, req, @@ -87,7 +87,7 @@ pub async fn create_payment_method_api( state, &req, json_payload.into_inner(), - |state, auth, req, _| async move { + |state, auth: auth::AuthenticationDataV2, req, _| async move { Box::pin(create_payment_method( &state, req, @@ -116,7 +116,7 @@ pub async fn create_payment_method_intent_api( state, &req, json_payload.into_inner(), - |state, auth, req, _| async move { + |state, auth: auth::AuthenticationDataV2, req, _| async move { Box::pin(payment_method_intent_create( &state, req, @@ -162,7 +162,7 @@ pub async fn confirm_payment_method_intent_api( state, &req, inner_payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { let pm_id = pm_id.clone(); async move { Box::pin(payment_method_intent_confirm( @@ -198,7 +198,7 @@ pub async fn payment_method_update_api( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationDataV2, req, _| { update_payment_method( state, auth.merchant_account, @@ -231,7 +231,7 @@ pub async fn payment_method_retrieve_api( state, &req, payload, - |state, auth, pm, _| { + |state, auth: auth::AuthenticationDataV2, pm, _| { retrieve_payment_method(state, pm, auth.key_store, auth.merchant_account) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -258,7 +258,7 @@ pub async fn payment_method_delete_api( state, &req, payload, - |state, auth, pm, _| { + |state, auth: auth::AuthenticationDataV2, pm, _| { delete_payment_method(state, pm, auth.key_store, auth.merchant_account) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -397,7 +397,7 @@ pub async fn save_payment_method_api( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { Box::pin(cards::add_payment_method_data( state, req, @@ -430,7 +430,7 @@ pub async fn list_payment_method_api( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { cards::list_payment_methods(state, auth.merchant_account, auth.key_store, req) }, &*auth, @@ -487,7 +487,7 @@ pub async fn list_customer_payment_method_api( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { cards::do_list_customer_pm_fetch_customer_if_not_passed( state, auth.merchant_account, @@ -549,7 +549,7 @@ pub async fn list_customer_payment_method_for_payment( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { list_customer_payment_method_util( state, auth.merchant_account, @@ -614,7 +614,7 @@ pub async fn list_customer_payment_method_api( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { list_customer_payment_method_util( state, auth.merchant_account, @@ -679,7 +679,7 @@ pub async fn list_customer_payment_method_api_client( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { cards::do_list_customer_pm_fetch_customer_if_not_passed( state, auth.merchant_account, @@ -708,7 +708,7 @@ pub async fn initiate_pm_collect_link_flow( state, &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payment_methods_routes::initiate_pm_collect_link( state, auth.merchant_account, @@ -740,7 +740,7 @@ pub async fn render_pm_collect_link( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payment_methods_routes::render_pm_collect_link( state, auth.merchant_account, @@ -775,7 +775,7 @@ pub async fn payment_method_retrieve_api( state, &req, payload, - |state, auth, pm, _| { + |state, auth: auth::AuthenticationData, pm, _| { cards::retrieve_payment_method(state, pm, auth.key_store, auth.merchant_account) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -809,7 +809,7 @@ pub async fn payment_method_update_api( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { cards::update_customer_payment_method( state, auth.merchant_account, @@ -848,7 +848,7 @@ pub async fn payment_method_delete_api( state, &req, pm, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { cards::delete_payment_method(state, auth.merchant_account, req, auth.key_store) }, &*ephemeral_auth, diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 7410c5ee36..829f295cac 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -75,7 +75,7 @@ pub async fn payments_create( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { authorize_verify_select::<_>( payments::PaymentCreate, state, @@ -104,6 +104,62 @@ pub async fn payments_create( .await } +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreateIntent, payment_id))] +pub async fn payments_create_intent( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, +) -> impl Responder { + use hyperswitch_domain_models::payments::PaymentIntentData; + + let flow = Flow::PaymentsCreateIntent; + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: auth::AuthenticationDataV2, req, req_state| { + payments::payments_intent_core::< + api_types::CreateIntent, + payment_types::PaymentsCreateIntentResponse, + _, + _, + PaymentIntentData, + >( + state, + req_state, + auth.merchant_account, + auth.profile, + auth.key_store, + payments::operations::PaymentCreateIntent, + req, + api::AuthFlow::Client, + header_payload.clone(), + ) + }, + match env::which() { + env::Env::Production => &auth::HeaderAuth(auth::ApiKeyAuth), + _ => auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::PaymentWrite, + minimum_entity_level: EntityType::Profile, + }, + req.headers(), + ), + }, + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(feature = "v1")] #[instrument(skip(state, req), fields(flow = ?Flow::PaymentsStart, payment_id))] pub async fn payments_start( @@ -131,7 +187,7 @@ pub async fn payments_start( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::Authorize, payment_types::PaymentsResponse, @@ -205,7 +261,7 @@ pub async fn payments_retrieve( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::PSync, payment_types::PaymentsResponse, @@ -276,7 +332,7 @@ pub async fn payments_retrieve_with_gateway_creds( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::PSync, payment_types::PaymentsResponse, @@ -337,7 +393,7 @@ pub async fn payments_update( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { authorize_verify_select::<_>( payments::PaymentUpdate, state, @@ -457,7 +513,7 @@ pub async fn payments_confirm( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { authorize_verify_select::<_>( payments::PaymentConfirm, state, @@ -500,7 +556,7 @@ pub async fn payments_capture( state, &req, payload, - |state, auth, payload, req_state| { + |state, auth: auth::AuthenticationData, payload, req_state| { payments::payments_core::< api_types::Capture, payment_types::PaymentsResponse, @@ -559,7 +615,7 @@ pub async fn payments_dynamic_tax_calculation( state, &req, payload, - |state, auth, payload, req_state| { + |state, auth: auth::AuthenticationData, payload, req_state| { payments::payments_core::< api_types::SdkSessionUpdate, payment_types::PaymentsDynamicTaxCalculationResponse, @@ -627,7 +683,7 @@ pub async fn payments_connector_session( state, &req, payload, - |state, auth, payload, req_state| { + |state, auth: auth::AuthenticationData, payload, req_state| { payments::payments_core::< api_types::Session, payment_types::PaymentsSessionResponse, @@ -688,7 +744,7 @@ pub async fn payments_redirect_response( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { ::handle_payments_redirect_response( &payments::PaymentRedirectSync {}, state, @@ -738,7 +794,7 @@ pub async fn payments_redirect_response_with_creds_identifier( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { ::handle_payments_redirect_response( &payments::PaymentRedirectSync {}, state, @@ -788,7 +844,7 @@ pub async fn payments_complete_authorize_redirect( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { ::handle_payments_redirect_response( &payments::PaymentRedirectCompleteAuthorize {}, @@ -843,7 +899,7 @@ pub async fn payments_complete_authorize( state, &req, payload, - |state, auth, _req, req_state| { + |state, auth: auth::AuthenticationData, _req, req_state| { payments::payments_core::< api_types::CompleteAuthorize, payment_types::PaymentsResponse, @@ -892,7 +948,7 @@ pub async fn payments_cancel( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::Void, payment_types::PaymentsResponse, @@ -934,7 +990,7 @@ pub async fn payments_list( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payments::list_payments(state, auth.merchant_account, None, auth.key_store, req) }, auth::auth_type( @@ -964,7 +1020,7 @@ pub async fn profile_payments_list( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payments::list_payments( state, auth.merchant_account, @@ -1177,7 +1233,7 @@ pub async fn payments_approve( state, &http_req, payload.clone(), - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::Capture, payment_types::PaymentsResponse, @@ -1241,7 +1297,7 @@ pub async fn payments_reject( state, &http_req, payload.clone(), - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::Void, payment_types::PaymentsResponse, @@ -1422,7 +1478,7 @@ pub async fn payments_incremental_authorization( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::IncrementalAuthorization, payment_types::PaymentsResponse, @@ -1471,7 +1527,7 @@ pub async fn payments_external_authentication( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payments::payment_external_authentication( state, auth.merchant_account, @@ -1519,7 +1575,7 @@ pub async fn post_3ds_payments_authorize( state, &req, payload, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { ::handle_payments_redirect_response( &payments::PaymentAuthenticateCompleteAuthorize {}, state, @@ -1580,7 +1636,7 @@ pub async fn retrieve_extended_card_info( state, &req, payment_id, - |state, auth, payment_id, _| { + |state, auth: auth::AuthenticationData, payment_id, _| { payments::get_extended_card_info( state, auth.merchant_account.get_id().to_owned(), diff --git a/crates/router/src/routes/payouts.rs b/crates/router/src/routes/payouts.rs index 28b2afe2be..ad860195a2 100644 --- a/crates/router/src/routes/payouts.rs +++ b/crates/router/src/routes/payouts.rs @@ -42,7 +42,7 @@ pub async fn payouts_create( state, &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_create_core(state, auth.merchant_account, auth.key_store, req, &locale) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -71,7 +71,7 @@ pub async fn payouts_retrieve( state, &req, payout_retrieve_request, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_retrieve_core( state, auth.merchant_account, @@ -111,7 +111,7 @@ pub async fn payouts_update( state, &req, payout_update_payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_update_core(state, auth.merchant_account, auth.key_store, req, &locale) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -172,7 +172,7 @@ pub async fn payouts_cancel( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_cancel_core(state, auth.merchant_account, auth.key_store, req, &locale) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -198,7 +198,7 @@ pub async fn payouts_fulfill( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_fulfill_core(state, auth.merchant_account, auth.key_store, req, &locale) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -224,7 +224,7 @@ pub async fn payouts_list( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_list_core( state, auth.merchant_account, @@ -264,7 +264,7 @@ pub async fn payouts_list_profile( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_list_core( state, auth.merchant_account, @@ -304,7 +304,7 @@ pub async fn payouts_list_by_filter( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_filtered_list_core( state, auth.merchant_account, @@ -344,7 +344,7 @@ pub async fn payouts_list_by_filter_profile( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_filtered_list_core( state, auth.merchant_account, @@ -384,7 +384,7 @@ pub async fn payouts_list_available_filters_for_merchant( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_list_available_filters_core(state, auth.merchant_account, None, req, &locale) }, auth::auth_type( @@ -417,7 +417,7 @@ pub async fn payouts_list_available_filters_for_profile( state, &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payouts_list_available_filters_core( state, auth.merchant_account, diff --git a/crates/router/src/routes/profiles.rs b/crates/router/src/routes/profiles.rs index e254447cde..f2dc322af1 100644 --- a/crates/router/src/routes/profiles.rs +++ b/crates/router/src/routes/profiles.rs @@ -306,7 +306,9 @@ pub async fn toggle_connector_agnostic_mit( state, &req, json_payload.into_inner(), - |state, _, req, _| connector_agnostic_mit_toggle(state, &merchant_id, &profile_id, req), + |state, _: auth::AuthenticationData, req, _| { + connector_agnostic_mit_toggle(state, &merchant_id, &profile_id, req) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs index 4c9194dac1..c76ee600ef 100644 --- a/crates/router/src/routes/refunds.rs +++ b/crates/router/src/routes/refunds.rs @@ -37,7 +37,7 @@ pub async fn refunds_create( state, &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { refund_create_core( state, auth.merchant_account, @@ -100,7 +100,7 @@ pub async fn refunds_retrieve( state, &req, refund_request, - |state, auth, refund_request, _| { + |state, auth: auth::AuthenticationData, refund_request, _| { refund_response_wrapper( state, auth.merchant_account, @@ -155,7 +155,7 @@ pub async fn refunds_retrieve_with_body( state, &req, json_payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { refund_response_wrapper( state, auth.merchant_account, @@ -204,7 +204,9 @@ pub async fn refunds_update( state, &req, refund_update_req, - |state, auth, req, _| refund_update_core(state, auth.merchant_account, req), + |state, auth: auth::AuthenticationData, req, _| { + refund_update_core(state, auth.merchant_account, req) + }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, )) @@ -237,7 +239,9 @@ pub async fn refunds_list( state, &req, payload.into_inner(), - |state, auth, req, _| refund_list(state, auth.merchant_account, None, req), + |state, auth: auth::AuthenticationData, req, _| { + refund_list(state, auth.merchant_account, None, req) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -278,7 +282,7 @@ pub async fn refunds_list_profile( state, &req, payload.into_inner(), - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { refund_list( state, auth.merchant_account, @@ -326,7 +330,9 @@ pub async fn refunds_filter_list( state, &req, payload.into_inner(), - |state, auth, req, _| refund_filter_list(state, auth.merchant_account, req), + |state, auth: auth::AuthenticationData, req, _| { + refund_filter_list(state, auth.merchant_account, req) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -362,7 +368,9 @@ pub async fn get_refunds_filters(state: web::Data, req: HttpRequest) - state, &req, (), - |state, auth, _, _| get_filters_for_refunds(state, auth.merchant_account, None), + |state, auth: auth::AuthenticationData, _, _| { + get_filters_for_refunds(state, auth.merchant_account, None) + }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), &auth::JWTAuth { @@ -401,7 +409,7 @@ pub async fn get_refunds_filters_profile( state, &req, (), - |state, auth, _, _| { + |state, auth: auth::AuthenticationData, _, _| { get_filters_for_refunds( state, auth.merchant_account, diff --git a/crates/router/src/routes/verification.rs b/crates/router/src/routes/verification.rs index f2996009f8..17c946c481 100644 --- a/crates/router/src/routes/verification.rs +++ b/crates/router/src/routes/verification.rs @@ -23,7 +23,7 @@ pub async fn apple_pay_merchant_registration( state, &req, json_payload.into_inner(), - |state, auth, body, _| { + |state, auth: auth::AuthenticationData, body, _| { verification::verify_merchant_creds_for_applepay( state.clone(), body, @@ -59,7 +59,7 @@ pub async fn retrieve_apple_pay_verified_domains( state, &req, merchant_id.clone(), - |state, _, _, _| { + |state, _: auth::AuthenticationData, _, _| { verification::get_verified_apple_domains_with_mid_mca_id( state, merchant_id.to_owned(), diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 1c0463da93..579a4ac4ff 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1262,6 +1262,10 @@ impl Authenticate for api_models::payments::PaymentsIncrementalAuthorizationRequ impl Authenticate for api_models::payments::PaymentsStartRequest {} // impl Authenticate for api_models::payments::PaymentsApproveRequest {} impl Authenticate for api_models::payments::PaymentsRejectRequest {} +#[cfg(feature = "v2")] +impl Authenticate for api_models::payments::PaymentsCreateIntentRequest {} +// #[cfg(feature = "v2")] +// impl Authenticate for api_models::payments::PaymentsCreateIntentResponse {} pub fn build_redirection_form( form: &RedirectForm, diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 3731d8ee08..a731b7e6db 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -58,6 +58,13 @@ pub struct AuthenticationData { pub profile_id: Option, } +#[derive(Clone, Debug)] +pub struct AuthenticationDataV2 { + pub merchant_account: domain::MerchantAccount, + pub key_store: domain::MerchantKeyStore, + pub profile: domain::Profile, +} + #[derive(Clone, Debug)] pub struct AuthenticationDataWithMultipleProfiles { pub merchant_account: domain::MerchantAccount, @@ -275,6 +282,12 @@ impl AuthInfo for AuthenticationData { } } +impl AuthInfo for AuthenticationDataV2 { + fn get_merchant_id(&self) -> Option<&id_type::MerchantId> { + Some(self.merchant_account.get_id()) + } +} + impl AuthInfo for AuthenticationDataWithMultipleProfiles { fn get_merchant_id(&self) -> Option<&id_type::MerchantId> { Some(self.merchant_account.get_id()) @@ -296,6 +309,9 @@ where #[derive(Debug)] pub struct ApiKeyAuth; +#[derive(Debug)] +pub struct ApiKeyAuthV2; + pub struct NoAuth; #[cfg(feature = "partial-auth")] @@ -349,6 +365,97 @@ where } } +#[async_trait] +impl AuthenticateAndFetch for ApiKeyAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationDataV2, AuthenticationType)> { + let api_key = get_api_key(request_headers) + .change_context(errors::ApiErrorResponse::Unauthorized)? + .trim(); + if api_key.is_empty() { + return Err(errors::ApiErrorResponse::Unauthorized) + .attach_printable("API key is empty"); + } + + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + + let api_key = api_keys::PlaintextApiKey::from(api_key); + let hash_key = { + let config = state.conf(); + config.api_keys.get_inner().get_hash_key()? + }; + let hashed_api_key = api_key.keyed_hash(hash_key.peek()); + + let stored_api_key = state + .store() + .find_api_key_by_hash_optional(hashed_api_key.into()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed + .attach_printable("Failed to retrieve API key")? + .ok_or(report!(errors::ApiErrorResponse::Unauthorized)) // If retrieve returned `None` + .attach_printable("Merchant not authenticated")?; + + if stored_api_key + .expires_at + .map(|expires_at| expires_at < date_time::now()) + .unwrap_or(false) + { + return Err(report!(errors::ApiErrorResponse::Unauthorized)) + .attach_printable("API key has expired"); + } + + let key_manager_state = &(&state.session_state()).into(); + + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &stored_api_key.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let profile = state + .store() + .find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &stored_api_key.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationDataV2 { + merchant_account: merchant, + key_store, + profile, + }; + Ok(( + auth.clone(), + AuthenticationType::ApiKey { + merchant_id: auth.merchant_account.get_id().clone(), + key_id: stored_api_key.key_id, + }, + )) + } +} + #[async_trait] impl AuthenticateAndFetch for ApiKeyAuth where @@ -531,6 +638,48 @@ where } } +#[cfg(feature = "partial-auth")] +#[async_trait] +impl AuthenticateAndFetch for HeaderAuth +where + A: SessionStateInfo + Sync, + I: AuthenticateAndFetch + + AuthenticateAndFetch + + GetAuthType + + Sync + + Send, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationDataV2, AuthenticationType)> { + let (auth_data, auth_type): (AuthenticationData, AuthenticationType) = self + .0 + .authenticate_and_fetch(request_headers, state) + .await?; + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + let key_manager_state = &(&state.session_state()).into(); + let profile = state + .store() + .find_business_profile_by_profile_id( + key_manager_state, + &auth_data.key_store, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let auth_data_v2 = AuthenticationDataV2 { + merchant_account: auth_data.merchant_account, + key_store: auth_data.key_store, + profile, + }; + Ok((auth_data_v2, auth_type)) + } +} + #[cfg(feature = "partial-auth")] async fn construct_authentication_data( state: &A, @@ -1605,16 +1754,82 @@ where .await .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant account for the merchant id")?; - + let merchant_id = merchant.get_id().clone(); let auth = AuthenticationData { merchant_account: merchant, key_store, profile_id: payload.profile_id, }; Ok(( - auth.clone(), + auth, AuthenticationType::MerchantJwt { - merchant_id: auth.merchant_account.get_id().clone(), + merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +#[async_trait] +impl AuthenticateAndFetch for JWTAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationDataV2, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; + + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let profile = state + .store() + .find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + let merchant_id = merchant.get_id().clone(); + let auth = AuthenticationDataV2 { + merchant_account: merchant, + key_store, + profile, + }; + Ok(( + auth, + AuthenticationType::MerchantJwt { + merchant_id, user_id: Some(payload.user_id), }, )) @@ -1996,6 +2211,20 @@ pub fn get_header_value_by_key(key: String, headers: &HeaderMap) -> RouterResult }) .transpose() } +pub fn get_id_type_by_key_from_headers( + key: String, + headers: &HeaderMap, +) -> RouterResult> { + get_header_value_by_key(key.clone(), headers)? + .map(|str_value| T::from_str(str_value)) + .transpose() + .map_err(|_err| { + error_stack::report!(errors::ApiErrorResponse::InvalidDataFormat { + field_name: key, + expected_format: "Valid Id String".to_string(), + }) + }) +} pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&str> { headers diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index 8d52e5dc11..02951d1a18 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -265,10 +265,15 @@ pub async fn create_profile_from_merchant_account( .unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64)); let payment_link_config = request.payment_link_config.map(ForeignInto::foreign_into); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = request .outgoing_webhook_custom_http_headers .async_map(|headers| { - core::payment_methods::cards::create_encrypted_data(state, key_store, headers) + core::payment_methods::cards::create_encrypted_data( + &key_manager_state, + key_store, + headers, + ) }) .await .transpose() diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index aedf91a3ce..1bd3d48441 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -16,12 +16,14 @@ pub use api_models::payments::{ PaymentsStartRequest, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, VerifyRequest, VerifyResponse, WalletData, }; +#[cfg(feature = "v2")] +pub use api_models::payments::{PaymentsCreateIntentRequest, PaymentsCreateIntentResponse}; use error_stack::ResultExt; pub use hyperswitch_domain_models::router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentMethodToken, - PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, Void, + CreateConnectorCustomer, CreateIntent, IncrementalAuthorization, InitPayment, PSync, + PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, + Session, SetupMandate, Void, }; pub use hyperswitch_interfaces::api::payments::{ ConnectorCustomer, MandateSetup, Payment, PaymentApprove, PaymentAuthorize, diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 674f418785..8000aa37ec 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -169,6 +169,8 @@ pub enum Flow { PaymentsFilters, /// Payments aggregates flow PaymentsAggregate, + /// Payments Create Intent flow + PaymentsCreateIntent, #[cfg(feature = "payouts")] /// Payouts create flow PayoutsCreate, diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql index 33a58538a8..318e57d595 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql @@ -86,6 +86,8 @@ ALTER TABLE payment_intent ALTER COLUMN currency DROP NOT NULL, ALTER COLUMN client_secret DROP NOT NULL, ALTER COLUMN profile_id DROP NOT NULL; +ALTER TABLE payment_intent ALTER COLUMN active_attempt_id SET NOT NULL; +ALTER TABLE payment_intent ALTER COLUMN session_expiry DROP NOT NULL; ------------------------ Payment Attempt ----------------------- ALTER TABLE payment_attempt DROP CONSTRAINT payment_attempt_pkey; diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql index 33718e2050..eca7603e2b 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql @@ -105,3 +105,9 @@ SET NOT NULL, SET NOT NULL, ALTER COLUMN client_secret SET NOT NULL; +ALTER TABLE payment_intent ALTER COLUMN session_expiry SET NOT NULL; + +-- This migration is to make fields optional in payment_intent table +ALTER TABLE payment_intent ALTER COLUMN active_attempt_id DROP NOT NULL; + +